Translate

2011年5月3日火曜日

JavaScriptでSalesforce上のApexコード(SOAP API)を呼び出す

Force.com上のApexコードをSOAP APIとして公開するのは
とても簡単だ。

以下のコードは
Force.com開発者コースでもらった
サンプルコードですが、
見てもらって分かるとおり
すでにApexコードが実装されているのであれば
あとはWSDLをダウンロードできる状態にするために
クラス委定義にglobalキーワードを加え
公開するwebserviceキーワードを追加するだけだ。

こうすれば
設定画面のApexクラス一覧からWSDLがダウンロードできるようになる。




// Apex Code 演習8-1 カスタムSOAP Webサービス(1)
// EmployeeReferralタブのVisualforce画面から入力しsaveボタン押下
// →CandidateKeyWebService#submitEmployeeReferral()をSOAP API呼び出し
//   →CandidateレコードとJobApplicationレコードを格納する
//
// Webサービスとして呼び出すクラスはglobalでなくてはならない
global class CandidateKeyWebService {

 // Webサービス呼び出しメソッドは webServiceキーワードをつける
 // EmployeeReferralタブ上のページから呼び出され
 // 応募者(Candidate__c)レコードと申込(JobApplication__c)レコードを
 // 新規追加する
 // ただしすでに存在する応募者の場合は応募者レコードは追加しない
 //
 // 引数: posId 申込レコード上の募集職種ID(参照項目)
 //  c 応募者レコード
 // 戻り値: 真:成功、偽:失敗
 webService static Boolean submitEmployeeReferral(
 String posId, Candidate__c c){

  // 応募者レコード作成フラグ
  boolean cCreate = true;

  // 項目Emailが設定されている場合、重複チェックをかける
  if (c.Email__c != null){
   // LastNameとEmailを連結した文字列を作成
   String uKey =
    c.Last_Name__c.toLowerCase() +
    c.Email__c.toLowerCase();

   // unique_key(自動計算される項目)と突き合わせ、
   // 1件以上存在すれば重複
   if ([select count()
    from Candidate__c
    where unique_key__c = :uKey] >= 1){

    // 既に存在する→作成フラグを偽に変更
    cCreate=false;
    // 引数cを既存レコードのForce.com IDに変更
    // limit 1を付けると最初の1件をとることができる
    c = [select Id
     from Candidate__c
     where unique_key__c = :uKey limit 1];
   }
  }

  // エラーフラグ
  boolean err = false;

  // 応募者レコードを新規作成する必要がある場合
  if (cCreate){
   try{
    // 新規追加
    insert c;
    // DBエラーの場合
   } catch (System.DmlException e) {
    // エラーフラグを真にする
    err = true;
    // デバッグログへ例外情報を記述
    System.debug(
     'error bulk inserting new candidate record');
    for (Integer k = 0; k < e.getNumDml(); k++) {
     System.debug(e.getDmlMessage(k));
    }
   }
  }

  // 応募者レコード新規追加に成功、もしくは既存レコードが存在し
  // 新規追加操作をしていない場合
  //  応募者レコード新規格納失敗の場合に申込レコードのみを追加
  //  してしまうとDBの整合性が荒れてしまうので
  if (!err){
   // 新規申込レコードを作成
   Job_Application__c j = new Job_Application__c();

   j.Status__c = 'Open'; // Status値をOpenに設定
   j.Stage__c = 'New'; // Stage値をNewに設定
   j.Position__c = posId; // Position値を引数posIdに設定
   j.Candidate__c = c.Id; // Candidate値を応募者IDに設定

   try{
    // 申込レコードを新規追加する
    insert j;
   // DBエラーの場合
   } catch (System.DmlException e) {
    // デバッグログに例外情報を格納
    System.debug(
     'error bulk inserting new job application');
    for (Integer k = 0; k < e.getNumDml(); k++) {
     System.debug(e.getDmlMessage(k));
    }

    // 本来はエラーフラグを立て後続のハンドリング
    // 処理判定に使用する
   }
  }

  // エラーがない場合
  if (!err) {
   // 真を返却
   return true;

  // エラーが発生した場合
  } else {
   // 本来はエラーハンドリング処理をここに書く
   // たとえばErrorLogレコードに格納する、など

   // 偽を返却
   return false;
  }
 }
}




できあがったWSDLをAXIS2のwsdl2javaへかませば
Javaから呼び出せるし
VisualStudioにかませれば
そっくりAPI呼び出し準備が完了する。

ただこの研修では
SOAPクライアント側をSalesforceが用意する
JavaScriptライブラリを利用して使用している
サンプルを紹介している。

Visualforceページになっているが
呼び出ししている
SOAP API呼び出しは
まるまるJavaScriptなので
apexタグを知らなくても読めると思う。




<!-- Apex Code 演習8-1 カスタムSOAP Webサービス(1) -->
<!-- EmployeeReferralタブのVisualforce画面から入力しsaveボタン押下 -->
<!-- →CandidateKeyWebService#submitEmployeeReferral()をSOAP API呼び出し -->
<!-- →CandidateレコードとJobApplicationレコードを格納する -->

<!-- controllerは定義されていない→JavaScriptからSOAP APIを呼び出している -->
<apex:page >
//Note that if the org password is NOT 'password1' that you will need to change it twice below!

<!-- Visualforceは終了タグを明記しないとエラーになる -->
<script type="text/javascript" src="/js/functions.js"></script>
<script src="/soap/ajax/17.0/connection.js"></script>
<script src="/soap/ajax/17.0/apex.js" type="text/javascript"></script>

<script type="text/javascript">
 <!-- 入力チェックを実施、すべてOkならばsave()を実行 -->
 function validate(){
  <!-- connection.js上のメソッドを呼び出し -->
  <!-- $Api/$APIはSalesforce上のメタデータを入手する際に -->
  <!-- 使用するキーワード、ほかにも$Referenceなどがある -->
  sforce.connection.init(
   "{!$API.Session_ID}", "{!$Api.Partner_Server_URL_140}");
  <!-- 判定結果格納用変数 -->
  var ok2Go = true;

  <!-- preSelectorリストボックスの選択値を取得 -->
  var ps = document.getElementById("posSelector");
  if (ps.options.length != 0){
   var posId = ps.options[ps.selectedIndex].value;
  }

  <!-- 選択値がnullもしくは空文字の場合 -->
  if ((posId == null)||(posId == "")) {
   <!-- ダイアログでアラート表示し判定結果をNGに -->
   alert("A position must be selected");
   ok2Go = false;
  }

  <!-- form"refForm"内のID値"lastname"の値を取得 -->
  var ln = document.forms["refForm"].elements["lastname"].value;
  <!-- lastname値が空文字もしくはnullの場合 -->
  if ((ln == "")||(ln == null)){
   <!-- ダイアログでアラート表示し判定結果をNGに -->
   alert("Last name is required");
   ok2Go = false;
  }

  // form"refForm"内のID値"email"の値を取得 -->
  var email = document.forms["refForm"].elements["email"].value; 
  <!-- email値が空文字もしくはnullの場合 -->
  if ((email == "")||(email == null)){
   <!-- ダイアログでアラート表示し判定結果をNGに -->
   alert("Email is required");
   ok2Go = false;
  }

  <!-- 判定結果OKの場合 -->
  if (ok2Go) {
   <!-- save()を呼び出しCandidateレコードを追加 -->
   <!-- 引数posIdは-->
   save(posId);

  <!-- 判定結果NGの場合 -->
  } else {
   <!-- 戻り値falseを返す -->
   return false;
  }
 }

 <!-- ※使用されていない※ -->
 <!-- グローバルオブジェクトの情報を入手する -->
 <!-- apexドキュメントをdescribeGlobalで検索→サンプルコード参考の事 -->
 function doDescribeGlobal(){
  try{
   var dgResults = sforce.connection.describeGlobal();
  } catch (e) {
   sforce.debug.open();
   sforce.debug.log(e);
  }

  return dgResults;
 }

 <!-- ※使用されていない※ -->
 <!-- グローバルSObjectの情報を入手する -->
 <!-- apexドキュメントをdescribeSObject検索→サンプルコード参考の事 -->
 function doDescribeSObject(entity){
  try{
   var dso = sforce.connection.describeSObject(entity);
  }catch (e) {
   sforce.debug.open();
   sforce.debug.log(e);
  }
  return dso;
 }

 <!-- 引数で与えられたIDのリストボックス内のオプションをクリア -->
 function clearSelect(name){
  <!-- IDからオブジェクトを入手 -->
  var sel = document.getElementById(name);

  <!-- 空になるまでループ -->
  while (sel.length > 0){
   <!-- 先頭のオプションを削除 -->
   sel.remove(0);
  }
 }

 <!-- Department欄が選択されたら呼び出される -->
 <!-- Department欄の状態によってposSelectorリストボックスを更新する -->
 function deptChanged(department){
  <!-- connection.jsを使用してコネクションを張る -->
  sforce.connection.init("{!$API.Session_ID}", "{!$Api.Partner_Server_URL_140}");

  <!-- IDが"posSelector"のリストボックス(Open Positions)を -->
  <!-- クリア -->
  clearSelect("posSelector");

  <!-- 選択された部門(Department)に合致する募集職種レコード -->
  <!-- を取得するクエリ文字列構築 -->
  var qStr = "select Id, Name, Location__c, Department__c, Type__c, Status__c from Position__c where Status__c='Open' and Department__c = '" + department + "'";

  try{ 
   <!-- クエリを実行し結果を取得 -->
   var queryResults = sforce.connection.query(qStr);
   <!-- 1件以上取得できたら'records'レコード情報を -->
   <!-- 配列として取得 -->
   if (queryResults != null){
    if (queryResults.size > 0){ 
     var records = queryResults.getArray('records'); 
    }
   }

  <!-- 例外発生時処理 -->
  } catch (e){
   <!-- Salesforceデバッグログへ例外情報を書き込む -->
   sforce.debug.open();
   sforce.debug.log(e);
  }

  <!-- IDが"posSelector"であるオブジェクトを取得 -->
  var ps = document.getElementById("posSelector");

  <!-- 結果が存在する場合 -->
  if (records != null){
   <!-- 結果レコードの全件ループ -->
   for (var i=0; i<records.length; i++) {
    <!-- JavaScriptのDOMを使ってoptionタグを -->
    <!-- 新規生成 -->
    var optNew = document.createElement('option');

    <!-- value属性値としてPosition__cの -->
    <!-- Force.com ID値を設定 -->
    optNew.value = records[i].Id;

    <!-- text属性値としてPosition__cのNameと -->
    <!-- Location__c、Type__cを連結した文字列 -->
    <!-- を設定 -->
    optNew.text = records[i].Name + " : " + records[i].Location__c + " : " + records[i].Type__c;

    <!-- IEのDOM方言対応のためのtry-catch句 -->
    try {
     <!-- posSelectorリストボックスの -->
     <!-- オプションとして格納 -->
     ps.add(optNew, null); // IE以外の場合
    <!-- 例外発生時(IE)処理 -->
    } catch(ex) {
     <!-- IEの場合は引数1つらしい -->
     ps.add(optNew); // IE の場合
    }
   }
  }
 }

 <!-- 応募者レコードを新規格納する関数 -->
 <!-- 引数posId: 応募者レコードに紐づける募集職種レコードID -->
 function save(posId){

  <!-- Candidate__c(応募者)レコードを新規生成
  var candidate = new sforce.SObject("Candidate__c");
  <!-- firstname値を応募者レコードのFirst_Name__cへ設定 -->
  candidate.First_Name__c = document.forms["refForm"].elements["firstname"].value; 
  <!-- lastname値を応募者レコードのLast_Name__cへ設定 -->
  candidate.Last_Name__c = document.forms["refForm"].elements["lastname"].value;
  <!-- phone値を応募者レコードのPhone__cへ設定 -->
  candidate.Phone__c = document.forms["refForm"].elements["phone"].value;
  <!-- mobile値を応募者レコードのMobile__cへ設定 -->
  candidate.Mobile__c = document.forms["refForm"].elements["mobile"].value;
  <!-- email値を応募者レコードのEmail__cへ設定 -->
  candidate.Email__c = document.forms["refForm"].elements["email"].value;

  <!-- Webサービスを呼び出し、応募者レコードを格納している -->
  try {
   <!-- Apexクラス(WebService): CandidateKeyWebService -->
   <!-- 呼び出すメソッド名:    submitEmployeeReferral -->
   <!-- 渡す引数:              募集職種レコードID -->
   <!--                         応募者レコード -->
   <!-- 戻り値:                成功→true -->
   var success = sforce.apex.execute("CandidateKeyWebService","submitEmployeeReferral",{a:posId,b:candidate});

   <!-- 値が返ってきた場合 -->
   if (success != null) {
    <!-- DB格納成功の場合 -->
    if (success == "true") {
     <!-- DOMを使ってBODYタグ内を下記 -->
     <!-- HTMLに書き換える-->
     document.body.innerHTML = "<h1>Referral Successfully Submitted. Thank You!</h1><br/><br/><br/><br/>";

    <!-- DB格納失敗の場合 -->
    } else {
     <!-- DOMを使ってBODYタグ内を下記 -->
     <!-- HTMLに書き換える-->
     document.body.innerHTML = "<h1>Temporarily unable to submit referrals. Please try again later.</h1><br/><br/><br/><br/>";
    }
   <!-- SOAP API呼び出し失敗の場合 -->
   <!-- デバッグログのトレースをtrueに変更 -->
   } else { sforce.debug.trace = true; }

  <!-- 例外発生時 -->
  } catch(e) {
   <!-- デバッグログのトレースをtrueに変更 -->
   sforce.debug.trace = true;
   <!-- デバッグログに例外情報を書き込む -->
   sforce.debug.open();
   sforce.debug.log(e);
  }
 }
</script>

<!-- formタグ相当 -->
<form id="refForm" name="refForm">

<!-- 申込レコードに設定するための既存のオープンされた募集職種 -->
<!-- レコードを選択させるためのテーブル -->
<table ID="Table1">
<tr>
<th colspan="2">Department</th>
<th colspan="2">Open Positions</th>
</tr>

<tr>
<td>Choose:</td>
<td>
<!-- Department欄のリストボックス -->
<!-- 選択状態変更→deptChanged関数呼び出し -->
<!-- 引数には選択ぽうしょんのテキスト値が渡される -->
<select id="deptSelector" type="select-one" size="1" NAME="deptSelector" onchange="javascript:deptChanged(this.options[this.selectedIndex].text);">
<!-- ここは決め打ちリストボックスとして定義されている -->
<option value="none">-- None --</option>
<option value="Engineering">Engineering</option>
<option value="IT">IT</option>
<option value="Finance">Finance</option>
<option value="Support">Support</option>
<option value="Sales">Sales</option>
</select>
</td>
<td>Choose:</td>
<td>
<!-- Open Positions欄のリストボックス -->
<!-- Department欄の値によりオプションが変わる -->
<select id="posSelector" NAME="posSelector" type="select-one" size="1">
<!-- 初期状態ではoptionタグは存在しない -->
</select>
</td>
</tr>

<tr>
<td colspan="4"> <br /></td>
</tr>

<td colspan="1"><h3>Candidate Info:</h3></td>
<td colspan="3"> </td>

<tr>
</tr>

</table>

<!-- 応募者レコードの属性値を入力させるためのテーブル -->
<table id="candidate">

<tr>
<!-- First_Name__cを格納するためのテキストフィールド -->
<td>First Name:</td><td>
<input type="text" id="firstname" /></td>
<!-- Phone__cを格納するためのテキストフィールド -->
<td>Phone:</td>
<td><input type="text" id="phone" /></td>
</tr>

<tr>
<!-- Last_Name__cを格納するためのテキストフィールド -->
<td><font color="#ff2222">Last Name:</font></td>
<td><input type="text" id="lastname" /></td>
<!-- Mobile__cを格納するためのテキストフィールド -->
<td>Mobile:</td>
<td><input type="text" id="mobile" /></td>
</tr>

<tr>
<!-- Email__cを格納するためのテキストフィールド -->
<td><font color="#ff2222">Email:</font></td>
<td><input type="text" id="email" /></td>
<td colspan="2"> </td>
</tr>

<tr>
<!-- レジュメ(ファイル)を格納するためのファイル入力フィールド -->
<!-- ※使用していない※ -->
<td>Resume:</td><td>
<input type="file" id="resume" name="resume" /></td>
<td colspan="2"> </td>
</tr>

<tr>
<td colspan="4"><br /></td>
</tr>

<table cellpadding="0" cellspacing="0" border="0" ID="Table2">
<tr>
<td class="pbTitle"><img src="/s.gif" alt="" width="1" height="1" class="minWidth" title="" /> </td>
<td class="pbButtonb">
<!-- saveボタン:押下→validate()呼び出し -->
<input value=" Save " class="btn" type="button" title="Save" name="save" onclick='javascript:validate();' ID="Button1" />
</td>
</tr>
</table>
</table>
</form>
</apex:page>




このサンプルでは
SOAP APIへ接続するために
Salesforceが用意している
・function.js
・connection.js
・apex.js
を使っている。
これだとWSDLをかませて云々の行程が不要だ。

SOAP APIのほかに
SOQLを文字列加工して実行する方法、
それとDepartment欄が変更された際
即時にForce.com上のDBをアクセスしてとなりの
リストボックス内容を書き換える
なんちゃってAjax(完全非同期ではないので..)の
コードなども読むことが出来る
おいしいつくりになっている。

ご参考まで。

0 件のコメント:

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...