Salesforce World Tour Tokyo2014: MiniHack を(ちょこっと)やってみたよ
先日、Salesforce World Tour Tokyoが盛大に開催されましたね。Dev向けの会場は虎ノ門ヒルズでしたので、当日は虎ノ門ヒルズにずっと居座ってました。(別会場の方も行ってみたかったのですが...色々な事情で諦めました。。)
さて、今回も恒例の"MiniHack"が行われました。6つのお題("H", "A", "C", "K", "E", "R")に答えて景品をGETする!という類いのものです。
「C」のお題:Visualforce Remote Object
お題の内容はこんなところです。
【チャレンジ内容】
Visualforce Remote Objectを駆使して、Apexを利用せずに、取引先検索のVisualforceページを実現してみましょう。
【要件】
- 任意のDeveloper Edition組織に以下のパッケージをインストールします。
- "ContactSearch" Visualforceページを更新し、Apexコントローラを利用しないで、Visualforce Remote Object を使って同様の機能を実現してください。もし必要ならば外部のJavaScriptライブラリは自由に使用できます。
- このようなrerender属性を使用しない場合、Backbone.jsやAngular.jsなど、動的に画面を更新する機能を持つライブラリの利用も有効です。
元々のApexコントローラを見てみましょう。
public class ContactSearchController { @RemoteAction public static List<Contact> retrieveContactRemote(String searchQuery){ String likeQuery = '%' + searchQuery + '%'; return [SELECT ID,Name,FirstName,LastName,Email,Phone FROM Contact WHERE Firstname Like :likeQuery OR LastName like :likeQuery OR Email like :likeQuery Limit 100]; } }
なるほど、検索キーを受け取って、性、名、Emailで取引先責任者を検索してます。さらに、JavaScript RemoteActionを使ってJavaScriptから利用できるようにしてますね。
ではVisualforceページも見てみましょう。
<apex:page controller="ContactSearchController" showHeader="false" applyHtmlTag="false" applyBodyTag="false" standardStylesheets="false"> <html> <head> <script type="text/javascript" src="//code.jquery.com/jquery-2.1.1.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"></link> <script id="resultTemplateHead" type="text/template"> <thead> <tr> <th>氏名</th> <th>電話</th> <th>Email</th> </tr> </thead> </script> <script id="resultTemplate" type="text/template"> <tr> <td><a href="/<%= contact.Id %>"><%= contact.LastName %> <%= contact.FirstName %></a></td> <td><%= contact.Phone %></td> <td><%= contact.Email %></td> </tr> </script> <script type="text/javascript"> var resultHeadCompiled = _.template($('#resultTemplateHead').html()); var resultCompiled = _.template($('#resultTemplate').html()); function fireSearch(){ var searchQuery = $("#searchQuery").val(); Visualforce.remoting.Manager.invokeAction( '{!$RemoteAction.ContactSearchController.retrieveContactRemote}', searchQuery, function(result, event){ $("#searchResult").empty(); $("#searchResult").append(resultHeadCompiled({})); $("#searchResult").append("<tbody></tbody>"); for(var i = 0; i < result.length; i++){ $("#searchResult > tbody").append(resultCompiled({contact : result[i]})); } }, {escape: false} ); } $(function(){ $("#searchButton").click(function(){fireSearch();}); }); </script> </head> <body> <div id="container"> <h2>取引先責任者</h2> <form class="navbar-form navbar-left" role="search"> <div class="form-group"> <input class="form-control" id="searchQuery" type="text" value=""/> </div> <button type="button" class="btn btn-primary" id="searchButton">検索</button> </form> <div> <table id="searchResult" class="table table-hover"></table> </div> </div> </body> </html> </apex:page>
JavaScript RemoteActionで受け取ったデータを、テーブル形式に表示すれば良いようです。
いざ実装
まず、Remote Objectを利用するには、apex:remoteObjectsタグを使って公開するオブジェクトとフィールドを宣言します。
<apex:page controller="ContactSearchController" showHeader="false" applyHtmlTag="false" applyBodyTag="false" standardStylesheets="false"> <apex:remoteObjects > <apex:remoteObjectModel name="Contact" jsShorthand="con" fields="Id,Name,LastName,FirstName,Phone,Email"> </apex:remoteObjectModel> </apex:remoteObjects> <html> ...
次に、RemoteActionで検索している箇所を、Remote Objectを使った検索に切り替えます。具体的には、"fireSearch()"の中身を一部修正しています。
... <script type="text/javascript"> var resultHeadCompiled = _.template($('#resultTemplateHead').html()); var resultCompiled = _.template($('#resultTemplate').html()); function fireSearch(){ var searchQuery = $("#searchQuery").val(), queryParam = '%' + searchQuery + '%'; var contactSearch = new SObjectModel.con(); contactSearch.retrieve({ where: { or: { FirstName: {like: queryParam}, LastName: {like: queryParam}, Email: {like: queryParam} } }, limit: 100 }, function(err, result, event){ $("#searchResult").empty(); $("#searchResult").append(resultHeadCompiled({})); $("#searchResult").append("<tbody></tbody>"); for(var i = 0; i < result.length; i++){ $("#searchResult > tbody").append(resultCompiled({contact : result[i]})); } }); } $(function(){ $("#searchButton").click(function(){fireSearch();}); }); </script> ...
最後に、表示する箇所をRemote Object用に修正します。具体的には contact.Id と書かれた箇所を contact.get('Id') と変更しております。(その他、Phone, Emailも)
... <script id="resultTemplate" type="text/template"> <tr> <td><a href="/<%= contact.get('Id') %>"><%= contact.get('LastName') %> <%= contact.get('FirstName') %></a></td> <td><%= contact.get('Phone') %></td> <td><%= contact.get('Email') %></td> </tr> </script> ...
だけれども
いざ実際に画面で確認すると、以下のエラーが返ってきて、うまく検索できません。。。うーむ....
無効な取得条件が指定されています。ValidationError [code=11, message=Data does not match any schemas from "oneOf" Where schemaKey = null
どうも、類似した現象が他にも報告されているようで、ORに指定する条件が3つ以上だとエラーになるようです。(え?
...というわけで、同等の機能を実装するために、姓、名の検索をNameの検索に切り替えました。。
... <script type="text/javascript"> var resultHeadCompiled = _.template($('#resultTemplateHead').html()); var resultCompiled = _.template($('#resultTemplate').html()); function fireSearch(){ var searchQuery = $("#searchQuery").val(), queryParam = '%' + searchQuery + '%'; var contactSearch = new SObjectModel.con(); contactSearch.retrieve({ where: { or: { Name: {like: queryParam}, Email: {like: queryParam} } }, limit: 100 ...
結果
修正したVisualforceページを表示して、検索ボタンをクリックすると、取引先責任者が正しく表示されるようになりました!
おわりに
それにしても、Remote Objectにて、ORの検索条件を3つ以上並べるのはNG...という事実にはビックリしました。。
※1 うっかり、こういったバグ?も踏むことができる MiniHack はなかなか興味深いと思いませんか?(笑)
※2 で、実際のところはどうなのか(仕様?バグ?)中の人に教えて頂きたいです!
ちなみに...今回、取引先責任者を検索するようにしてますが、お題の方には
Visualforce Remote Objectを駆使して、Apexを利用せずに、取引先検索のVisualforceページを実現してみましょう。
そう、取引先責任者を使うのではなくて、取引先の検索なのです。。
【要件】に指定されている
同様の機能を実現してください
ということで、取引先責任者の検索と勘違いして、完全に見落としてました。(でもOK頂きました...ありがとうございます)
ちなみに、今回の景品はこんな感じでした。
- 2枚クリアで、Amazon Gift Card ¥5,000 分(先着20名)
- 4枚クリアで、PS4(先着3名?)
- 6枚クリアで、Macbook Air 13inch(抽選1名)
4枚クリアの景品も終了間際まで残ってたりして、結構チャンスはあるようでしたね。
今年参加できなかった方は、来年こそは賞品ゲット...にトライしてみてください!
なお、この記事はSalesforce1 Advent Calendar 2014 の12/8分となります。
いやー今年も無事に書けて良かった....