Appirio's Tech Blog

2012年5月8日火曜日

Dynamic Visualforce Componentsを使ってみよう!

今年の3月にSpring12がリリースされました。

http://blogjp.sforce.com/2012/01/spring-12-10-6f25.html

上記URLにあるように、様々な新機能がリリースされました。

今回はこの中でも、Visualforceページに表示するコンポーネントを動的に生成する機能「Dynamic Visualforce Components」を使って以下のような簡単なアンケートシステムを作成してみたいと思います。


一般的なアンケートでは、例えば「氏名」を問う質問であった場合は「任意入力形式」、「性別」を問う質問であった場合は「男性」または「女性」の「単一選択形式」、「好きなスポーツ」を問う質問であった場合は「複数選択形式」といったように、その質問内容によって回答形式が変化することがあると思います。

このように質問によって回答形式が変化するようなアンケート回答フォームをVisualforceページで実装する場合、以前は以下のようなコードで実装していたと思います。



<table>
  <tbody>
    <tr>
      <th>番号</th>
      <td><apex:outputText value="{!questionNo}" /></td>
     </tr>
    <tr>
      <th>質問</th>
      <td><apex:outputText value="{!survey.question}" /></td>
     </tr>
     <tr>
      <th>回答</th>
      <td>
        <!-- テキスト -->
        <apex:inputText value="{!survey.answer}" rendered="{!survey.type=='1'}" />
        <!-- 単一選択式 -->
        <apex:selectRadio value="{!survey.answer}" rendered="{!survey.type=='2'}" >
          <apex:selectoptions value="{!survey.selectOptionList}" />
        </apex:selectRadio>
        <!-- 複数選択式 -->
        <apex:selectCheckboxes value="{!survey.multipleAnswer}" rendered="{!survey.type=='3'}">
          <apex:selectoptions value="{!survey.selectOptionList}" />
        </apex:selectCheckboxes>
      </td> 
     </tr>
  </tbody>
</table>

必要な回答形式に対応するコンポーネントを列挙しておき、「rendered」プロパティによって各コンポーネントの表示・非表示を制御する方法です。
この方法ですと、Viewに表示制御のロジックが混在しているため、実際動作させた際にどのような見た目になるのかを認識しずらいといったデメリットがありました。

一方「Dynamic Visualforce Components」を使った場合は、どのようになるでしょうか?以下のコードをご覧下さい。

<table>
  <tbody>
    <tr>
      <th>番号</th>
      <td><apex:outputText value="{!questionNo}" /></td>
     </tr>
    <tr>
      <th>質問</th>
      <td><apex:outputText value="{!survey.question}" /></td>
     </tr>
     <tr>
      <th>回答</th>
      <td>
        <!-- ダイナミックコンポーネント -->
        <apex:dynamicComponent componentValue="{!answerComponent}" />
      </td> 
     </tr>
  </tbody>
</table>

回答欄が「apex:dynamicComponent」という1つのタグで表現され、Viewの見通しが良くなったと思います。
「apex:dynamicComponent」タグには「componentValue」というプロパティがあり、このプロパティにはコンポーネントを返却するコントローラのメソッドを指定します。上記サンプルコードでは「getAnswerComponent」というメソッドが返却するコンポーネントが表示されることになります。

では、次にコンポーネントを返却するコントローラのメソッドを見てみましょう。

public Component.Apex.OutputPanel getAnswerComponent() {
  Component.Apex.OutputPanel panel = new Component.Apex.OutputPanel();
  if (survey.type == Survey_Ctrl.TEXT_SINGLE) {
    Component.Apex.InputText inputText = new Component.Apex.InputText();
    inputText.expressions.value = '{!survey.answer}';
    panel.childComponents.add(inputText);
  }
  else if (survey.type == Survey_Ctrl.SELECTION_SINGLE) {
    Component.Apex.SelectRadio radio = new Component.Apex.SelectRadio();
    radio.expressions.value = '{!survey.answer}';
    Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
    options.expressions.value = '{!survey.selectOptionList}';
    radio.childComponents.add(options);
    panel.childComponents.add(radio);
  }
  else if (survey.type == Survey_Ctrl.SELECTION_MULTIPLE) {
    Component.Apex.SelectCheckboxes checkbox = new Component.Apex.SelectCheckboxes();
    checkbox.expressions.value = '{!survey.multipleAnswer}';
    Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
    options.expressions.value = '{!survey.selectOptionList}';
    checkbox.childComponents.add(options);
    panel.childComponents.add(checkbox);
  }
  return panel; 
}

このメソッドは、OutputPanelコンポーネントを返却しています。OutputPanelにはsurvey.type(質問の種類)に応じてInputText、SelectRadio、SelectCheckboxesコンポーネントが追加されるようになっています。
ここでポイントになるのは、実際Visualforceページでタグを書いた場合と同じ構成(階層構造)を生成する必要があるということです。事前にVisualforceページでタグを書いて包含関係を把握しておく必要があります。

では具体的に、SelectCheckboxesコンポーネントを生成するコードを解説します。「apex:dynamicComponent」タグを使わない場合のVisualforceページではSelectCheckboxesコンポーネントを以下のようなタグで表現していました。

<apex:selectCheckboxes value="{!survey.multipleAnswer}" rendered="{!survey.type=='3'}">
  <apex:selectoptions value="{!survey.selectOptionList}" />
</apex:selectCheckboxes>

包含関係を見ると、SelectCheckboxesの子要素としてSelectOptionsがあるという構造になっています。この構造をApexコードで表現すると、以下のようになります。

Component.Apex.SelectCheckboxes checkbox = new Component.Apex.SelectCheckboxes();
checkbox.expressions.value = '{!survey.multipleAnswer}';
Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
options.expressions.value = '{!survey.selectOptionList}';
checkbox.childComponents.add(options);

各コンポーネントにはchildComponentsというListのプロパティがあり、このListへ子要素となるコンポーネントを追加することで階層構造を構築します。
また、各コンポーネントでexpressions.valueを設定していますが、これは各タグのvalueプロパティを設定していることになります。今回はrenderedプロパティを使わない代わりに動的にコンポーネントを生成しているので、renderedプロパティは設定していませんが、もしrenderedプロパティを設定する場合は以下のようなコードになります。

checkbox.expressions.rendered = '{!survey.type=='3'}';

このようにVisualforceページにおいてタグで表現していた部分をコントローラ側で動的に生成することができました。Visualforceページ側で多数のコンポーネントに対して複雑な表示制御を行う場合などは、この「Dynamic Visualforce Components」を利用することを検討してみてはいかがでしょうか?

参考までに今回作成したアンケートシステムの全ソースコードを添付します。

*Visualforceページ
<apex:page showHeader="false" sidebar="false" controller="Survey_Ctrl" >
<h1>アンケートシステム</h1>
<hr/>
<apex:form >
<table>
  <tbody>
    <tr>
      <th>番号</th>
      <td><apex:outputText value="{!questionNo}" /></td>
     </tr>
    <tr>
      <th>質問</th>
      <td><apex:outputText value="{!survey.question}" /></td>
     </tr>
     <tr>
      <th>回答</th>
      <td>
        <!-- ダイナミックコンポーネント -->
        <apex:dynamicComponent componentValue="{!answerComponent}" />
      </td> 
     </tr>
  </tbody>
</table>
<hr/>
<apex:commandButton action="{!prev}" value="前の質問" disabled="{!NOT(hasPrev)}" />
<apex:commandButton action="{!next}" value="次の質問" disabled="{!NOT(hasNext)}" />
</apex:form>
</apex:page>

*Apexクラス(コントラーラ)
public with sharing class Survey_Ctrl {
  
  public static final String TEXT_SINGLE = '1';
  public static final String SELECTION_SINGLE = '2';
  public static final String SELECTION_MULTIPLE = '3';
  
  public String questionNo { get {
    return String.valueOf(questionIndex+1);
  }}
  
  public Survey survey { get {
    return surveyList.get(questionIndex);
  } private set;}
  
  public Boolean hasNext { get {
    return questionIndex < surveyList.size()-1;
  }}
  
  public Boolean hasPrev { get {
    return questionIndex > 0;
  }}
  
  private List surveyList = null;
  private Integer questionIndex = 0;
  
  public Survey_Ctrl() {
    initSurvey();
  }
  
  public PageReference prev() {
    if (hasPrev) questionIndex--;
    return null;
  }
  
  public PageReference next() {
    if (hasNext) questionIndex++;
    return null;
  }
  
  public Component.Apex.OutputPanel getAnswerComponent() {
    Component.Apex.OutputPanel panel = new Component.Apex.OutputPanel();
    if (survey.type == Survey_Ctrl.TEXT_SINGLE) {
      Component.Apex.InputText inputText = new Component.Apex.InputText();
      inputText.expressions.value = '{!survey.answer}';
      panel.childComponents.add(inputText);
    }
    else if (survey.type == Survey_Ctrl.SELECTION_SINGLE) {
      Component.Apex.SelectRadio radio = new Component.Apex.SelectRadio();
      radio.expressions.value = '{!survey.answer}';
      Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
      options.expressions.value = '{!survey.selectOptionList}';
      radio.childComponents.add(options);
      panel.childComponents.add(radio);
    }
    else if (survey.type == Survey_Ctrl.SELECTION_MULTIPLE) {
      Component.Apex.SelectCheckboxes checkbox = new Component.Apex.SelectCheckboxes();
      checkbox.expressions.value = '{!survey.multipleAnswer}';
      Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
      options.expressions.value = '{!survey.selectOptionList}';
      checkbox.childComponents.add(options);
      panel.childComponents.add(checkbox);
    }
    return panel; 
  }

  private void initSurvey() {
    surveyList = new List();
    
    Survey survey1 = new Survey(Survey_Ctrl.TEXT_SINGLE, '名前は?');
    Survey survey2 = new Survey(Survey_Ctrl.SELECTION_SINGLE, '性別は?');
    survey2.addSelection('男');
    survey2.addSelection('女');
    Survey survey3 = new Survey(Survey_Ctrl.SELECTION_MULTIPLE, '好きなスポーツは?');
    survey3.addSelection('野球');
    survey3.addSelection('サッカー');
    survey3.addSelection('ゴルフ');
    survey3.addSelection('マラソン');
    
    surveyList.add(survey1);
    surveyList.add(survey2);
    surveyList.add(survey3);
    
  }
  
  public class Survey {
    public String type {set; get;}
    public String question {set; get;}
    public String answer {set; get;}
    public List multipleAnswer {set; get;}
    public List selectOptionList {set; get;}
    
    public Survey(String type, String question) {
      this.type = type;
      this.question = question;
      this.multipleAnswer = new List();
      this.selectOptionList = new List();
    }
    
    public void addSelection(String selection) {
      selectOptionList.add(new SelectOption(selection,selection));
    }
  }
  
}


Posted by Oi Jun (Appirio Japan)

続きを読む


2012年4月10日火曜日

Force.com Streaming APIでChatterへの投稿を通知

Winter '12ではパイロットリリースだったStreaming APIが、Spring '12では一般で利用可能になったことを受け、試しに使ってみました。
StreamingAPIは、サーバからクライアントに向けてデータをプッシュ配信する仕組みで、Force.com上で定義したSOQLクエリにマッチしたレコードが作成・変更された際に、通知として受け取る事が可能になります。

今回は、「Getting Started with Force.com Streaming API」で紹介されている、Visualforce Pageでの実装例を参考に、CometDのJavascriptライブラリを用いてカスタムオブジェクトを監視し、Chatterに投稿があったタイミングで通知を発するページを作成します。


参考:Getting Started with Force.com Streaming API
http://www.salesforce.com/us/developer/docs/api_streaming/index.htm

StreamingAPIの制限

StreamingAPIでChatterオブジェクトを監視し、投稿された際に画面に通知する・・・という機能を想定していましたが、上記の制限により、取得できるオブジェクト・フィールドが限られるということが判明しました。
そこで今回は、Chatterの投稿を管理するFeedItemにトリガーを仕込み、Messageカスタムオブジェクトに一旦レコードを複製し、StreamingAPIからはMessageオブジェクトを監視するという手法をとることとします。

サンプルの概要

今回作成するサンプルのおおまかな流れは下記の通りです。

≪Chatterへの投稿通知≫
  1. StreamingAPI接続のクライアントページを表示します。
  2. Chatterの有効になっているオブジェクトにレコードを作成します。
  3. 上記で作成したレコードのChatterエリアにテキストを入力し、「共有」ボタンを押下します。
    (FeedItemオブジェクトにレコードが追加されます。)
  4. FeedItemのトリガーにより、Messageオブジェクトに投稿内容が作成されます。
  5. PushTopicに登録されたクエリがMessageオブジェクトの作成を評価します。
  6. クライアントページにChatterの投稿を通知します。
今回使用するファイルの一覧は次の通りです。
  • InsertFeedItem_Trg.trigger – Chatterに投稿(FeedItemオブジェクトにレコードが作成)が行われた際に、投稿内容をMessageオブジェクトに登録するApexトリガー
  • StreamingAPI_Ctrl.cls – Messageオブジェクトに登録されたレコードすべてを表示するためのコントローラクラス
  • StreamingAPI_Page.page – StreamingAPI接続のクライアントとなるVisualforceページ

事前準備

■Force.com環境の設定
  1. ストリーミングAPIを有効にします。
    [アプリケーション設定]-[カスタマイズ]-[ユーザーインターフェース]から「ストリーミングAPIを有効化」をチェックします。
  2. 任意のオブジェクトのChatterのフィード追跡を有効にします。
    [アプリケーション設定]-[カスタマイズ]-[Chatter]-[フィード追跡]から任意のオブジェクトを選択し、「フィード追跡の有効化」をチェックします。

■オブジェクトの作成

Message__c メッセージ string(255)
ParentId__c 親レコードID string(18)
CreatedName__c 投稿者 string(100)

■PushTopicレコードの登録

PushTopicオブジェクトにSOQLクエリを含むレコードを登録します。
作成したそれぞれのPushTopicレコードは、CometDのチャンネルに対応し、Bayeuxクライアントは、このチャンネルに流されたイベントを受け取ることができます。
PushTopicレコードが作成されるとすぐに、システムはレコード作成・更新を評価します。

  1. 「開発者コンソール」を起動します。
  2. 下記のスクリプトを実行します。
    PushTopic pushTopic = new PushTopic();
    pushTopic.Name = 'MessageUpdates';
    pushtopic.Query = 'select id,Message__c,CreatedName__c,ParentId__c from Message__c';
    pushTopic.ApiVersion = 24.0;
    pushTopic.NotifyForOperations = 'All';
    pushTopic.NotifyForFields = 'Referenced';
    insert pushTopic;
    

[Query]
SOQLクエリを登録します。登録されたクエリに基づきレコードのイベントを評価し、通知を行います。

[NotifyForOperations]
通知を生成するレコードイベントを指定します。有効な項目は、次のとおりです。
  • All – デフォルト:Queryの対象となるレコードがCreateまたはUpdateされたときに通知を行います。
  • Create – Queryの対象となるレコードがCreateされたときにのみ通知を行います。
  • Update – Queryの対象となるレコードがUpdateされたときにのみ通知を行います。

[NotifyForFields]
PushTopic クエリに対して新規または更新されたレコードがどのように評価されるかが指定されます。有効な項目は、次のとおりです。
  • All – レコードの何れかのフィールドが変更された時に評価します。
  • Referenced – デフォルト:Select,Where句両方で指定されたフィールドが変更された時のみ評価します。
  • Select – Select句で指定されたフィールドが変更された時のみ評価します。
  • Where – Where句で指定されたフィールドが変更された時のみ評価します。

■jQueryの登録
下記の手順で、StreamingAPI接続に使用する為のJavascriptファイルを静的リソースに登録します。
http://www.salesforce.com/us/developer/docs/api_streaming/Content/code_sample_vfp_create_static_resources.htm

下記の4ファイルを「js」フォルダに格納、zip 形式でアーカイブし、静的リソースに "StreamJS" という名前で登録します。
  • cometd.js
  • jquery-1.5.1.js
  • json2.js
  • jquery.cometd.js

次に、Notification Popupで使用するjQueryのプラグインを静的リソースに登録します。
http://thrivingkings.com/sticky/
の「Download full」からZipファイルをダウンロードし、静的リソースに"Sticky"という名前で登録します。

クライアントページの作成

StreamingAPI接続のクライアントとなるページを作成します。
JavascriptによりStreamingAPIの接続が確立され、通知されるのを待機します。

StreamingAPI_Ctrl
public with sharing class StreamingAPI_Ctrl {
  public List<Message__c> messages {get; private set;}
  public StreamingAPI_Ctrl() {
    this.messages = [select id,Message__c,CreatedName__c,CreatedDate from Message__c order by LastModifiedDate ];
  }
}

StreamingAPI_Page
<apex:page showHeader="true" controller="StreamingAPI_Ctrl">
  <apex:includeScript value="{!$Resource.StreamJS}/js/cometd.js"/>
  <apex:includeScript value="{!$Resource.StreamJS}/js/jquery-1.5.1.js"/>
  <apex:includeScript value="{!$Resource.StreamJS}/js/json2.js"/>
  <apex:includeScript value="{!$Resource.StreamJS}/js/jquery.cometd.js"/>
  <!-- Nortification Popupの読み込み -->
  <apex:includeScript value="{!$Resource.Sticky}/sticky.full.js"/>
  <apex:stylesheet value="{!$Resource.Sticky}/sticky.full.css"/>

  <script type="text/javascript">
    jQuery.noConflict();
  
    var cometdUrl = 'https://' + window.location.hostname + '/cometd/24.0/';
    var auth = 'OAuth {!$Api.Session_ID}';

    jQuery(document).ready(function() {
      // 接続
      jQuery.cometd.init({
        url: cometdUrl,
        requestHeaders: { Authorization: auth }
      });
      // サブスクライブ
      jQuery.cometd.subscribe('/topic/MessageUpdates', function(message) {
        // Nortification Popupを表示
        jQuery.sticky(
          message.data.sobject.CreatedName__c + '<br />' +
          '<div>メッセージを受信しました。</div>' +
          '<p><a href="/' + message.data.sobject.ParentId__c + '" target="_blank"><i>“' + message.data.sobject.Message__c + '”</i></a></p>' +
          '<div>' + DateUtil.formatDate(new Date(message.data.event.createdDate), 'yyyy/MM/dd HH:mm') + '</div>'
        );
        // メッセージ一覧に表示
        jQuery('#appendedMessages').append(
          '<tr><td>' + message.data.sobject.Message__c + '</td>' +
          '<td>' + DateUtil.formatDate(new Date(message.data.event.createdDate), 'yyyy/MM/dd HH:mm') + '</td><td>' + message.data.sobject.CreatedName__c + '</td></tr>'
        );
      });
    });
  </script>
  <!-- 投稿履歴の表示 -->
  <table id="appendedMessages">
    <tr>
      <th width="200">メッセージ</th>
      <th width="200">投稿時間</th>
      <th width="200">投稿者</th>
    </tr>
    <apex:repeat value="{!messages}" var="m">
    <tr>
      <td><apex:outputField value="{!m.Message__c}" /></td>
      <td><apex:outputField value="{!m.CreatedDate}" /></td>
      <td><apex:outputField value="{!m.CreatedName__c}" /></td>
    </tr>
    </apex:repeat>
  </table>
</apex:page>

トリガーの作成

StreamingAPIでは、直接FeedItemオブジェクトを評価することができないため、今回はMessageオブジェクトにFeedItemの内容をコピーし、Messageオブジェクトを評価します。ここでは、FeedItemオブジェクトにレコードが作成された際に、投稿内容をMessageオブジェクトに登録するApexトリガーを作成します。

InsertFeedItem_Trg
trigger InsertFeedItem_Trg on FeedItem (after insert) {
  List<Message__c> msgList = new List<Message__c>();
  Set<String> ids = new Set<String>();
  for(FeedItem fi : Trigger.New) {
    if(fi.Type != 'TextPost'){
      continue;
    }
    Message__c msg = new Message__c();
    msg.Message__c = fi.Body;
    msg.ParentId__c = fi.ParentId;
    msg.CreatedName__c = fi.CreatedById;
    ids.add(fi.CreatedById);
    msgList.add(msg);
  }
  // 投稿者名のセット
  Map<String,User> usrMap = new Map<String,User>([Select Id,Name From User where Id in :ids]);
  for(Message__c msg : msgList){
    msg.CreatedName__c = usrMap.get(msg.CreatedName__c).Name;
  }
    
  insert msgList;
}

サンプルの実行

作成したサンプルを実際に動作させてみます。

  1. 「/apex/StreamingAPI_Page」を開き待機します。


  2. Chatterの有効になっているオブジェクトの任意のレコードを表示します。

  3. 上記レコードのChatterエリアにテキストを入力し、「共有」ボタンを押下します。

  4. クライアントページにChatterの投稿内容が通知されます。

Posted by Yoshida Tokuji (Appirio Japan)

続きを読む


2012年3月30日金曜日

Rails & Databasedotcom Gem 入門講座

新しい年がまた始まり、解決策を紹介する時が来ました。約1年前にRubyを学ぶための方法についてまとめましたが、もしあなたが何かを学ぼうとしているのなら、私はRubyを推奨します。その理由は以下の通りです:
  • Salesforce.comがHerokuを220万ドルで買収
  • Salesforce.comはかなり以前から「オープン. ソーシャル. モバイル」を推奨してしている
  • Rubyはオープンソースの技術でソーシャルなモバイルアプリケーションを作成するのに優れている
上記の項目に相関関係があるのが分かりますか?Herokuを使用しているForce.com開発者の前には大きなものが待ち受けています。Herokuは複数の言語に対応したプラットフォームですが、Rubyが輝きを放っています。時間があるなら、あなたの引き出しにRubyを追加しましょう。

私はアピリオの継続教育向けWebinarの中で、この話題についてプレゼンしましたが、Rubyを学ぼうとしている他の人にもこれを手軽に提供出来るかも知れないと考えました。ビデオの中で私は取引先の一覧を表示するだけの簡単なアプリケーションを構築するためにDatabasedotcom gemを使用しています。具体的には、選択された取引先の詳細を表示したり、キーワードによって検索を行ったりしています。私はRubyやRailsを始める際に助けとなるいくつかのリンクをビデオの後に追加しました。

ソースコードはこちらから参照できます。

ビデオについて
冒頭はブログ内で記載しているリンクの簡単な紹介です。
5:58辺りからターミナル上で実際にRuby on Railsのデモアプリケーションの作成を実演しています。






Links Referenced in the Video

Railsnstaller (Windows)
Homebrew
Ruby Version Manager (RVM)

Rails Guides
Railscasts
Rails for Zombies
Code School
Try Ruby
Ruby on Rails Tutorial
Ruby on Rails Documentation

Databasedotcom gem
Building and Deploying Great Applications with Salesforce, Ruby, and Heroku (Dreamforce)


Posted by Jeff Douglas (Appirio US)
[原文へ(英語)]

続きを読む


2011年12月28日水曜日

Salesforce.com上で数式項目を活用する

Salesforceを利用/導入していてプロジェクトの方針等によりプログラム開発を制限されたことはありませんか?私はこれまでそのようなことが多々あり標準機能で対応可能な範囲を探りながら、都度それぞれの実現方法を検討してきました。(「Apexを書けば早いのに」と思いながらも...)

そこで今回は、これまで私が比較的多く遭遇した部分で標準機能の数式項目を用いて実現可能なことについて記載していこうと思います。

ここで記載している数式項目とは...

  • 他の項目や式を元に値を自動的に計算するオブジェクトに作成できるカスタム項目のこと
  • ”IF”や”DATE”等の関数、”$User”や”$Organization”等のグローバル変数などが利用可能

和暦の表示

既に様々な方法で実現している方も多いと思いますが、以下のような数式を作成することにより日付項目を元にして和暦を表示することができます。
例では、取引先責任者の「誕生日(Birthdate)」を元にしています。その他の項目を和暦表示する場合は、「Birthdate」の部分を該当項目のAPI参照名で置き換えて下さい。
IF( NOT( ISNULL( Birthdate ) ),
  IF(
    /* 明治を判定 */
    AND( Birthdate >= DATE(1868, 9, 8), Birthdate <= DATE(1912, 7, 29) ),
    "明治" & IF( YEAR( Birthdate ) = 1868, "元", TEXT(YEAR( Birthdate ) - 1867) ),
    IF(
      /* 大正を判定 */
      AND( Birthdate >= DATE(1912, 7, 30), Birthdate <= DATE(1926, 12, 24) ),
      "大正" & IF( YEAR( Birthdate ) = 1912, "元", TEXT(YEAR( Birthdate ) - 1911) ),
      IF(
        /* 昭和を判定 */
        AND( Birthdate >= DATE(1926, 12, 25), Birthdate <= DATE(1989, 1, 7) ),
        "昭和" & IF( YEAR( Birthdate ) = 1926, "元", TEXT(YEAR( Birthdate ) - 1925) ),
        IF(
          /* 平成を判定 */
          Birthdate >=DATE(1989, 1, 8),
          "平成" & IF( YEAR( Birthdate ) = 1989, "元", TEXT(YEAR( Birthdate ) - 1988) ),
          TEXT( YEAR( Birthdate ) )
        )
      )
    )
  )
  & "年"
  & LPAD(TEXT( MONTH( Birthdate )), 2, "0") & "月"
  & LPAD(TEXT( DAY( Birthdate )), 2, "0") & "日",
  null
)

上記の数式の場合、年号毎の範囲は下記の通りとなり各最初の年は"元年"と表示されます。
範囲の微調整を行いたい場合は各”DATE”の中を修正します。
明治:1868/09/08 ~ 1912/07/29
大正:1912/07/30 ~ 1926/12/24
昭和:1926/12/25 ~ 1989/01/07
平成:1989/01/08 ~
※その他の日付(1868/09/08以前)の形式:YYYY年MM月DD日

ちなみにカスタム項目の日付の範囲は、”1700/01/01”〜”4000/12/31”です。”4000/12/31”を指定すると”平成2012年12月31日”と表示されます。
和暦表示に対するニーズは多くの場面で出て来ると思いますので知っていると便利かと思います。

時間(時間帯)の表示

以下のような数式を作成することにより日時から時間を取得することができます。
MID( TEXT( Nichiji__c ), 12, 2 )
ただし、このままではGMTでの時間が取得されてしまうので、もし利用する組織が日本標準時(GMT+09:00)のみであれば以下のようにすることで日本時間で取得することができます。
MID( TEXT( Nichiji__c + 9/24), 12, 2 )
「9」の部分を適宜GMTとの時差に変更することにより、利用環境にあわせた時間を取得できます。
例:
 太平洋標準時(GMT-08:00) -> -8
 インド標準時(GMT+05:30) -> 5.5

また、”IF”や”CASE”等の関数を利用することによって取得した時間から時間帯を表示することも可能です。
「10:00〜12:00」「10〜12」、「AM」「PM」…
IF( ISNULL( Nichiji__c), null, IF(VALUE( MID( TEXT(  TaioNichiji__c + 9/24 ), 12, 2 ) ) >= 12 ,"PM","AM") )
作成した数式項目はレポートでのグルーピング項目に指定することができるため、コールセンター等で時間帯ごとの問い合わせ数の集計などが可能です。

(標準の活用と言いながらトリガを利用するため)以降蛇足

UserオブジェクトにそのユーザのGMTとの時差を保持していれば、さらに便利に利用することができます。
MID( TEXT( Nichiji__c + $User.TimeZone__c /24 ), 12, 2 )
「TimeZone__c」に以下のようなTriggerを利用して各ユーザごとに値を設定することができます。
trigger updateUserInfo on User (before insert, before update) {
  Long timezone;
  Datetime nowDt = Datetime.now();
  for (User usr : Trigger.new) {        
    // GMTとユーザの時差(ミリ秒)
    timezone = Datetime.valueOf(nowDt.format('yyyy-MM-dd HH:mm:ss', usr.TimeZoneSidKey)).getTime() - Datetime.valueOf(nowDt.formatGMT('yyyy-MM-dd HH:mm:ss')).getTime();
    // ミリ秒単位を時間単位に変更
    usr.TimeZone__c = Decimal.valueOf(timezone).divide(3600000, 2);
  }
}

実行ユーザの情報を検索条件にしたView/Report

通常ViewやReportでは、日付の特別値(”今日”、”過去30日間”など)を検索条件に利用することはできますが、実行ユーザの値を検索条件に指定することはできません。しかし、数式項目を利用することにより実行ユーザの値を検索条件に指定できるようになります。
実現方法は単純で、「オブジェクト内の数式項目で表示条件を設定する」ことだけです。
具体的には、検索対象のオブジェクトに数式項目で条件を指定して、レコードが条件と合致するかどうかを戻り値で返します。数式で返される値をViewやReportで条件に指定することで実行ユーザの項目を条件にレコードを抽出することができるようになります。

例:実行ユーザが参照項目(User)に設定されている一覧を表示する

数式項目名:自担当フラグ
IF( $User.Id = LookupUserId__c, 1, 0)

数式項目で“OR”などの関数を利用することにより、複数の参照項目に設定されている一覧を表示することも可能になります。
また応用として数式項目では参照しているオブジェクトをたどって値を取得することができますので、複数オブジェクトの値を条件にしてViewを表示することが可能となります。

なおこれらの方法は数式項目内で”$User”を利用しているためViewやReportのパフォーマンスに良い設定とは言えません。
そのためレコード数や利用箇所等に十分に注意してください。タイムアウトする可能性が高いです。
また日付時間項目からTEXT変換後時間の取得部分などSalesforceの仕様が変更される可能性も0ではありませんのでご注意ください。

標準機能の活用が必要となった時/プログラムの作成に疲れた時の1つの手段として検討してみてください。

Posted by Hokazono Kazushi (Appirio Japan)

続きを読む


2011年12月27日火曜日

Salesforce上でのトランスレーションワークベンチを使った単一言語での項目値制御

今回は、トランスレーションワークベンチの翻訳機能を使って、「0=有効、1=無効」といったデータ項目要件の実現についてご紹介します。

例えば、既存のシステムとの連携があるシナリオの場合、「0」というデータが連携されてきても画面上は「有効」と表示したい場合があります。また、画面上では「東京都」と入力しても、バックエンドシステムには「13」という都道府県コードを送りたい場合があります。このような場合に、本来は多言語の翻訳表示に利用されるトランスレーションワークベンチを日本語のみの環境で利用することも可能です。

その設定例については、下記のスクリーンショットを御覧ください。

1) 選択リストのカスタム項目の設定


「都道府県」の選択リストには、都道府県名ではなく「11」~「14」が設定されています。


有効フラグ項目には「0」と「1」が選択リスト値として設定されています。


2) トランスレーションワークベンチの有効化
お使いのSalesforce組織環境のトランスレーションワークベンチが有効化されていない場合、管理者設定から、トランスレーションワークベンチ > 翻訳設定と進み、有効化を行って下さい。



3) 使用言語の有効化
ここでは日本語を有効化するのみで問題ありません。


4) 言語の編集 - 有効化と翻訳者の設定


5) 翻訳の設定
1で作成した2つの選択リストについて、表示の際のラベルを設定します。ドロップダウンリストから、言語(日本語)と設定コンポーネント(選択リスト値)、それから対象となるオブジェクトを選ぶと、画像のように選択リスト全てが表示されます。インラインエディットですので、ダブルクリックしてラベル値を入力し保存を行います。


6) 動作確認
対象となるオブジェクトの新規レコードを追加します。言語設定が日本語のユーザでログインしている場合は、画面表示上は選択リストの実際の値ではなく、翻訳のラベル値が表示されます。


試しに、ログインユーザの言語を英語に切り替えて確認してみます。


同じレコードですが、翻訳ラベルが無いため実際にデータベースに記録されている値そのままが表示されることになります。


従来トランスレーションワークベンチの設定は全ての翻訳対象項目に設定する手間がかかりましたが、インポート機能が利用出来るようになったため便利に利用出来るようになりました。
ただし、この翻訳ラベルで表示している内容は、数式項目からは取得できず、本来の選択リストの値が表示されます。参照関係にあるオブジェクトのレコード項目を取得する数式項目は大変便利ですが、この点ご注意ください。


Posted by Kubomura Masaki (Appirio Japan)

続きを読む


2011年12月12日月曜日

SalesforceとAmazon Simple Queue Serviceでへメッセージの送受信をしてみよう

SalesforceとAmazon Simple Queue Serviceでへメッセージの送受信をしてみよう

ある2つのシステムAとBとの間でメッセージ(ビジネスデータ)の送受信を行うことを考えてみます。

直接AとBの間でメッセージのやり取りを行うことも可能でしょう。しかし、Aがメッセージを送信する(生成する)速度に比べて、Bがメッセージを取得(消費)する速度が遅い場合、Aがメッセージを送信しても、Bがすぐにはそれを処理(消費)できません。場合によってはタイムアウトが発生してしまうかもしれません。

また、Bが稼動している時間帯に制限がある場合、時間外に発生したAのメッセージは、どこかに保存しておくための特別な仕組みを考慮しなければなりません。システム間が密結合していると、一方のシステムが他方のシステムの状況に引っ張られてしまいます。

このような場合、キューを使用すると、システム間の結合性を疎にすることができます。
システムAはキューへメッセージを送信し、一方でシステムBは、キューからメッセージを取得することで、お互いのシステムが直接メッセージのやりとりをしません。つまり、相手のシステムの状況に引っ張られることがなくなります。

Amazon Simple Queue Service (SQS)は、Amazonが提供するキューイングサービスです。Amazon上にキューを作成することで、Webを介してキューに対してメッセージ(ビジネスデータ)の送信および、キューからメッセージを取り出すことができるようになります。もちろん、特別なアプリケーションをインストールする必要はありません。

今回は、SQSを使用して、Salesforce(Apex)とSQSへメッセージの送受信をするサンプルをご紹介しましょう。

SQSを操作するには、Amazonが提供するAPIを使用します。また、Javaや.Net等用にSDK(http://aws.amazon.com/code)も提供されています。

http://aws.amazon.com/jp/eclipse/
http://aws.amazon.com/code

また、Scratchpad(http://aws.amazon.com/code/1254?_encoding=UTF8&jiveRedirect=1)と呼ばれるJavascriptによるアプリケーションも利用することができます。(Scratchpadはあとで利用します)

1. SalesforceからSQSへメッセージを送信する。

1) Queueを作成する

まずはじめに、メッセージを受け取るQueueを作成しましょう。
Scratchpadを開き、Secret KeyとAccess Keyを入力し、"Explore API"で"CreateQueue"を選択します。


"Invoke Request"を押してみます。


<queueurl>タグに、SFQueueが登録されています。以後のサンプルでは、このQueueUrlの値を使います。もし、QueueUrlの値を忘れてしまった場合、Scratchpadを開き、"ListQueue"を選択して、"Invoke Request"ボタンを押すと、いつでも、QueueUrlの値を確認することができます。

2) Apex Classを作成する(その1)

SQSへ送信するメッセージはVisualforce Page(VF)から入力します。


VFの作成の前に、VFからの入力値を保持するフィールドとボタンに関連させるアクション(メソッド)を定義しておきましょう。
public with sharing class SendSQSMessage_Ctrl {

 public String message {get; set;}

 public SendSQSMessage_Ctrl() { 

 }

 public PageReference sendMessage() { 
  return null;
 }

}

3) Visualforce Pageを作成する。

VFページを以下のように作成します。
<apex:page Controller="SendSQSMessage_Ctrl"
      tabStyle="Send_SQS_Message__tab"
      standardStylesheets="true">
  <apex:pageMessages id="pm" />
  <apex:form id="f">
    <apex:pageBlock title="">
      <apex:pageBlockSection title="Messageを入れてください。" columns="1">
        <apex:inputText value="{!message}" id="m"/>
      </apex:pageBlockSection>
      <apex:commandButton value="Send" action="{!sendMessage}"  id="button01" />
    </apex:pageBlock>
  </apex:form>
</apex:page>

4) Apex Classを作成する(その2)

ApexからはSQSへは、HTTP GETリクエストを使用してメッセージを送信しますが、URLの形式は以下のようになります。

https://queue.amazonaws.com/515865986603/SFQueue?MessageBody=Hello&Action=SendMessage&SignatureMethod=HmacSHA1&AWSAccessKeyId=MyAccessKey&Version=2009-02-01&SignatureVersion=2&Signature=MySignature&Timestamp=2011-11-06T06%3A28%3A27.047Z

ここで、URLのパラメータには以下が含まれています。
MessageBody : SQSへ送信するメッセージ本体
Action : SendMessage
SignatureMethod : HmacSHA1
AWSAccessKeyId : 自分のAccess Key Id
Version : 2009-09-01
SignatureVersion : 2
Timestamp : yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 形式
Signature : HmacSHA1でのメッセージ認証符号
となります。

Signatureは、上記の各パラメータとQueue名、Host名、自分のSecret Keyと、それからHTTPのメソッド(GET)を使用して生成します。
つまり、
GET + 改行
+ host(SQSのHost) + 改行
+ Queue名 + 改行
+ AWSAccessKeyId=MyAccessKey
+ '&' + Action=SendMessage
+ '&' + MessageBody=Message本体
+ '&' + 'SignatureMethod=HmacSHA1'
+ '&' + 'SignatureVersion=2'
+ '&' + 'Timestamp=yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 形式
+ '&' + 'Version=2009-02-01'
という文字列と、Secret Keyを使用して、HmacSHA1でのSignatureを作成します。

Signatureの作成は、SQSUtilというクラスを作成し、以下のメソッドを定義します。
ここで、strには、Signatureにする文字列を、skeyにはSecret Keyを指定します。
private static String createMac(String str, String skey) { 
  Blob b = Blob.valueOf(str);
  Blob key = Blob.valueOf(skey);
  Blob sign = Crypto.generateMac('hmacSHA1', b, key);
  return encode(EncodingUtil.base64Encode(sign));
}
このようにしてSingatureを生成することができます。
(使用する場合は、accessKeyと、secretKey、queueを適切な値に変更してください。)

SQSへメッセージを送信するApexクラスは、以下のようになります。
SendSQSMessage_Ctrl.cls
public with sharing class SendSQSMessage_Ctrl {

  public String message {get; set;}
  
  public SendSQSMessage_Ctrl() { 
  }

  /* 以下は自分のKeyやQueueに合わせて書き換えてください。 */  
  private String accessKey = 'My_Access_Key';
  private String secretKey = 'My_Secret_Key';
  private String host = 'queue.amazonaws.com';
  private String queue = '/xxxxxxxxxxxx/SFQueue';
  
  public PageReference sendMessage() { 
    if(SQSUtil.isNullOrEmpty(message )) { 
      ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'メッセージを入力して下さい。'));
    }

    //1. Signatureを作成するための値をMapへ設定    
    Map<String, String> params = SQSUtil.createDefaultMap();
    
    params.put(SQSUtil.AKEY, accessKey);
    params.put(SQSUtil.ACTION, SQSUtil.SEND);
    
    //2. 現在の時刻を設定
    String current = SQSUtil.getFormattedCurrentDTAsGMT();
    params.put(SQSUtil.TIMESTAMP, current);
    
    //3. Messageを設定
    params.put(SQSUtil.MESSAGEBODY, SQSUtil.encode(message));
    
    //4. Signatureを設定
    String signature = SQSUtil.createSendMessageSignature(host, queue, secretKey, params);
    params.put(SQSUtil.SIGNATURE, signature);
    
    String url = 'https://'  + host + queue + '?';
    Boolean firstKey = false;
    for(String key : params.keySet()) { 
      if(firstKey) { 
        url = url + '&';
      }
      firstKey = true;
      url = url + key + '=' + params.get(key);
    }  
    
    //5. MessageをSQSへ送信
    HttpRequest req = new HttpRequest();
    req.setEndPoint(url);
    req.setMethod('GET');
    Http http = new Http();
    try {
      HttpResponse res = http.send(req);
      
      if(res.getStatusCode() == 200) { 
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'メッセージ送信が成功しました。' ));
      } else { 
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'メッセージ送信に失敗しました。'));
      }

    } catch (System.CalloutException e) {
      ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'エラーが発生しました。' + e));
    }
    return null;
  }
}

また、Utilityを提供するSQSUtil.clsは以下のようになります。
SQSUtil.cls
public with sharing class SQSUtil {

  public final static String AKEY = 'AWSAccessKeyId';
  public final static String ACTION = 'Action';
  public final static String TIMESTAMP = 'Timestamp';
  public final static String MESSAGEBODY = 'MessageBody';
  public final static String SEND ='SendMessage';
  public final static String SIGNATURE = 'Signature';
  
  /**
  * Check the Stirng is a null or empty. 
  */ 
  public static Boolean isNullOrEmpty(String str) { 
    return str == null || str.length() == 0;
  }
  
  /**
  * Create current DateTime as GMT with 'yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\' format
  */   
  public static String getFormattedCurrentDTAsGMT() { 
    String formattedDT = System.now().formatGmt('yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'');
    return encode(formattedDT);
  }

  /**
  * Create Signature for Send Message Action
  */ 
  public static String createSendMessageSignature(String host, String queue, String skey, Map<String, String> params) { 
    String str = 'GET\n'
        + host
        + '\n'
        + queue
        + '\n'
        + SQSUtil.AKEY + '=' + params.get(SQSUtil.AKEY)
        + '&' 
        + SQSUtil.ACTION + '=' + params.get(SQSUtil.ACTION)
        + '&' 
        + SQSUtil.MESSAGEBODY + '=' + params.get(SQSUtil.MESSAGEBODY)
        + '&'
        + 'SignatureMethod' + '=' + 'HmacSHA1'
        + '&'
        + 'SignatureVersion' + '=' + '2'
        + '&'
        + SQSUtil.TIMESTAMP + '=' + params.get(SQSUtil.TIMESTAMP)
        + '&'
        + 'Version' + '=' + '2009-02-01';
    
    return createMac(str, skey);
  }

  public static String encode(String str) { 
    return EncodingUtil.urlEncode(str, 'UTF-8').replace('+', '%20');
  }
  
  public static Map<String, String> createDefaultMap() { 
    return new Map<String, String>{
      'SignatureMethod' => 'HmacSHA1',
      'SignatureVersion' => '2',
      'Version' =>'2009-02-01'
    };
  }

  private static String createMac(String str, String skey) { 
    Blob b = Blob.valueOf(str);
    Blob key = Blob.valueOf(skey);
    Blob sign = Crypto.generateMac('hmacSHA1', b, key);
    return encode(EncodingUtil.base64Encode(sign));
  }

}

5) リモートサイトを設定する。

Salesforceの組織外へのアクセスが必要になりますので、「リモートサイトの設定」で、"https://queue.amazonaws.com"を追加しておきましょう。

6) メッセージを送信してみる。

早速メッセージを送信してみます。まず、Queueには何もメッセージが登録されていないことを確認しておきましょう。
再び、Scratchpadを開き、"Explore API"には、"ReceiveMessage"を指定し、"Invoke request"を押下します。


もちろん、Queueには何もメッセージを送信していませんので、メッセージは取得されません。


さて、VFページを開きましょう。"Hello SQS World"というメッセージを入力し、"Send"ボタンを押します。「メッセージ送信が成功しました。」というメッセージが画面に表示されていれば、SQSへのメッセージ送信が成功しています。


本当にSQSへメッセージが送信されているかは、Scratchpadを開き、先程同様に、"ReceiveMessage"を選択して、確認してみましょう。


"Hello SQS World"という文字列が取得できていることが確認できます。SQSへメッセージを送信することができました!

2. SQSからメッセージを取得する。

今度は逆に、SQSからメッセージを取得してみます。
メッセージを送信する場合と方法はほとんど同じです。URLに指定するパラメータに多少の違いがあるだけと、取得したメッセージはXML(Figure8参照)なので、その解析が必要である、という点です。つまり、MessageBodyがなくなり、MaxNumberOfMessages=1を使用し、また、Actionは、"ReceiveMessage"を使用します。URLは以下のようになります。

https://queue.amazonaws.com/515865986603/SFQueue?Action=ReceiveMessage&SignatureMethod=HmacSHA1&AWSAccessKeyId=MyAccessKey&Version=2009-02-01&SignatureVersion=2&Signature=MySignature&Timestamp=2011-11-06T07%3A08%3A30.136Z&MaxNumberOfMessages=1

SQSからメッセージを受信するApexクラスは、以下のようになります。
RetrieveSQSMessage_Ctrl.cls
public with sharing class RetrieveSQSMessage_Ctrl {

  public String message { get; private set;}
  
  /* 以下は自分のKeyやQueueに合わせて書き換えてください。 */  
  private String accessKey = 'My_Access_Key';
  private String secretKey = 'My_Secret_Key';
  private String host = 'queue.amazonaws.com';
  private String queue = '/XXXXXXXXXX/SFQueue';

  public RetrieveSQSMessage_Ctrl() { 
    
  }
  
  public PageReference receiveMessage() { 

    //1. Signatureを作成するための値をMapへ設定    
    Map<String, String> params = SQSUtil.createDefaultMap();
    
    params.put(SQSUtil.AKEY, accessKey);
    params.put(SQSUtil.ACTION, SQSUtil.RECEIVE);
    params.put(SQSUtil.MAX_MESSAGE_NUM, '10');

    //2. 現在の時刻を設定
    String current = SQSUtil.getFormattedCurrentDTAsGMT();
    params.put(SQSUtil.TIMESTAMP, current);
    
    //3. Signatureを設定
    String signature = SQSUtil.createReceiveMessageSignature(host, queue, secretKey, params);
    params.put(SQSUtil.SIGNATURE, signature);
    
    String url = 'https://'  + host + queue + '?';
    Boolean firstKey = false;
    for(String key : params.keySet()) { 
      if(firstKey) { 
        url = url + '&';
      }
      firstKey = true;
      url = url + key + '=' + params.get(key);
    }  
    
    //4. MessageをSQSから取得
    HttpRequest req = new HttpRequest();
    req.setEndPoint(url);
    req.setMethod('GET');
    Http http = new Http();
    try {
      HttpResponse res = http.send(req);
      if(res.getStatusCode() != 200) { 
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'メッセージ受信に失敗しました。'));
        return null;
      }
      Dom.Document doc = res.getBodyDocument();
      Dom.XMLNode rootNode = doc.getRootElement();
      String namespace = 'http://queue.amazonaws.com/doc/2009-02-01/';
      List<string> tagList = new List<string>{'ReceiveMessageResult', 'Message','Body'};
      Dom.XMLNode node = rootNode;
      for(String tag : tagList) { 
        Dom.XMLNode temp = node.getChildElement(tag, namespace);
        if(temp != null) { 
          node = temp;
        }
      }
      message = node.getText();
    } catch (System.CalloutException e) {
      ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'エラーが発生しました。' + e));
    }
    return null;
  }  
}
また、SQSUtil.clsには、Receive用のSignatureを作成するメソッドを追加しています。
SQSUtil.cls(抜粋)
/**
* Create Signature for Send Message Action
*/
public static String createReceiveMessageSignature(String host, String queue, String skey, Map<String, String> params) { 
  String str = 'GET\n'
      + host
      + '\n'
      + queue
      + '\n'
      + SQSUtil.AKEY + '=' + params.get(SQSUtil.AKEY)
      + '&' 
      + SQSUtil.ACTION + '=' + params.get(SQSUtil.ACTION)
      + '&' 
      + SQSUtil.MAX_MESSAGE_NUM + '=' + '10'
      + '&'
      + 'SignatureMethod' + '=' + 'HmacSHA1'
      + '&'
      + 'SignatureVersion' + '=' + '2'
      + '&'
      + SQSUtil.TIMESTAMP + '=' + params.get(SQSUtil.TIMESTAMP)
      + '&'
      + 'Version' + '=' + '2009-02-01';

  return createMac(str, skey);
}
最後に、VFページは以下のようになります。
ReceiveMessage_Page.page
<apex:page Controller="RetrieveSQSMessage_Ctrl" 
    tabStyle="Receive_SQS_Message__tab"
    standardStylesheets="true">

  <apex:pageMessages id="pm"/>
  <apex:form id="f">
    <apex:pageBlock title="">
      <apex:pageBlockSection title="ボタンを押してください。" columns="1">
        <apex:commandButton value="Receive" action="{!receiveMessage}"  id="button01" />
      </apex:pageBlockSection>
      <apex:outputPanel rendered="{!NOT(ISBLANK(message)) }" >
        <apex:outputLabel for="m" value="受信したメッセージは以下です。"/>
          <br />
        <apex:outputText value="{!message}" id="m"/>
      </apex:outputPanel>
    </apex:pageBlock>
  </apex:form>
</apex:page>
メッセージは、Scratchpadから送信してみましょう。


SQSへメッセージが送信できたら、 VFページを開いて、"Receive"ボタンを押してみます。


Scratchpadから送信したメッセージが、VFページでも表示されました!

このようにして、Salesforceからも、Amazon SQSとの間でのメッセージの送受信を実現することができます。

最後に、今回のSQS送信部分のコードは、http://webmonkeyswithlaserbeams.wordpress.com/tag/apex/ を参考にさせて頂きました。
また、今回使用したコードはここからダウンロードできます。

Posted by Shiroo Takayuki (Appirio Japan)

続きを読む


2011年12月1日木曜日

Salesforce の新機能『スキーマビルダー』を使ってみよう

Salesforce の Winter '12 リリースで『スキーマビルダー』という機能が新たに追加されました。
スキーマビルダーは Salesforce の標準/カスタムオブジェクト情報を ER 図(クラス図)ライクに表示する機能です。

習うより慣れろ、ということで早速見てみましょう。

Salesforce にログインし、設定画面に移動してください。
左ペインの「アプリケーションの設定」にある「スキーマビルダー」リンク、もしくは「アプリケーションの設定 > 作成 > オブジェクト」にある「スキーマビルダー」ボタンからスキーマビルダーに移動することができます。


スキーマビルダーの使い方はとても簡単です。
サイドバーで選択されたオブジェクトが右側の図に表示されます。
表示されたオブジェクトはドラッグで見易い場所に移動したり、オブジェクト/フィールドの詳細画面を開いたりすることができます。

「ビューオプション」で表示内容をカスタマイズしたり、「自動レイアウト」でオブジェクトの表示位置を整理することができます。

スキーマビルダーを活用することでオブジェクトの管理がぐっと簡単になります。
是非活用してみましょう。

Posted by Takahashi Eiichiro (Appirio Japan)
続きを読む


 
2006-2011 Appirio Inc. All rights reserved.
アピリオ | リソースセンター(英語) | お問い合わせ先 | 採用情報 | プライバシーポリシー(英語)