Lightning Component開発にトライしてみました with Sencha Ext JS


# はじめに

Force.comエンジニアの皆さまにおかれましては、すでに"Lightning Component"という単語を耳にしない日はない！というほどに"Lightning Component"ネタは広まっていると思います。

※本当に皆さん（様々な形の）愛情をもって"Lightning Component"に接していて、敬意を表さずにはいられません...!

Lightning Componentを開発するベストプラクティスがだんだんと研ぎすまされてきている中で、ちょっと違ったアプローチでLightning Component開発を試してみたことを残そうと思います。

というわけで、今回はタイトルにもあるように"Sencha Ext JS"を試してみました。（試したバージョンは 5.1.2 です）

# Sencha Ext JS を VisualforcePageでは

Lightning Component開発の前に、VisualforcePage上にExt JSアプリを構築する、というのは既に試していて、(ある程度は)出来上がりつつあります。

※[参考資料(LT @ Salesforce World Tour Tokyo 2015: Developer Theater)](http://www.slideshare.net/yonet77/visualforce-55868193) では、Ext JSをVisualforcePageで表示させたものを紹介しています。

これは別の機会で掘り下げしたい...と思いますので割愛します (すみません)


# LightningComponentに適用するには

今回は「Ext JSのGridをLightning Componentとして使えるようにしてみる」にトライしてみようと思います。

Gridの構成要素としては大きく

1. Data Store(グリッドに表示するデータを管理する)
2. Column(グリッドに表示するカラム)

があり、さらにStoreは

1. Model
2. Proxy(サーバー側とデータのやり取りを担当)

が重要な要素となります。

ここで、"Model", "Column"はLightningComponentのattributeを元に作成するようにしても当面は良いと考えます。

※"Model"に関しては、まずはオブジェクトAPIがあれば良いですし、"Column"も表示させたいフィールドの配列があれば良いので、当初はそれほど難しく考えなくても良いかな、と。

- - -
【以下、ModelとColumnの例】

```javascript
// Model
var modelCls = Ext.define('sfdc.model.Account', {
  extend: 'Ext.data.Model',
  fields: [{name: 'Id', type: 'string'}],
  idProperty: 'Id'
});
```

```javascript
// Columns
var columns = [
  {
    xtype: 'gridcolumn',
    dataIndex: 'Id',
    text: 'ID',
    hidden: true
  },{
    xtype: 'gridcolumn',
    dataIndex: 'Name',
    text: '取引先名'
  },{
    xtype: 'gridcolumn',
    dataIndex: 'AccountNumber',
    text: '取引先番号'
  }
];
```
- - -

というわけで、"Proxy"にLightningComponent開発に登場してくる```$A```を適用してみましょう。

【試したサンプル】

```javascript
var proxyCls = Ext.define('sfdc.proxy.LtngProxy', {
  extend: 'Ext.data.proxy.Ajax',
  alias: 'proxy.ltng',

  config: {
    ltngAction: null
  },

  /**
   * リクエストを送信する
   *  -> $A.enqueueAction を使ってキューに流す
   * @param {Ext.data.Request} request The request
   * @return {Ext.data.Request} The request
   * @private
   */
  sendRequest: function(request){
    var me = this,
        action = me.getLtngAction();

    me.lastRequest = request;

    // サーバー側に渡すパラメータを生成する
    var actionParams = Ext.Object.merge(me.getParams(request), me.getExtraParams());

    action.setParams(actionParams);
    action.setCallback(this, function(res){

      // request.getCallback()は、request, success(boolean), responseをパラメータに持つメソッドを返す
      Ext.callback(request.getCallback(), request.scope,
        [request, (res.getState() === 'SUCCESS'), res.getReturnValue()]
      );

    });

    $A.enqueueAction(action);
    return request;
  }

});
```
* サーバー側とやり取りするようなProxyは、```Ext.data.proxy.Ajax```, ```Ext.data.proxy.Direct```, ```Ext.data.proxy.JsonP```の3種類が元々実装されています。ここでは```Ext.data.proxy.Ajax```をベースにカスタマイズするのが適している（と思う）ので、それをベースに新たなProxyを定義しています。
* 実際にサーバーにリクエストを投げる箇所を上書きして、```$A.enqueueAction```で、actionをqueueにためるようにしています。
    * ```Ext.data.proxy.Ajax``` では、```sendRequest```メソッドにてAjaxでリクエストを送信しています。従って、まずはここで```$A.enquueAction```を使うように上書きしてみました。
* 【ご参考】(色々と説明省略してすみません)
    * [Ext.data.proxy.Server](http://docs.sencha.com/extjs/5.1/5.1.2-apidocs/#!/api/Ext.data.proxy.Server) と、そのサブクラスあたりも参考にしました。


# サンプル

* 以下のサンプルでは、"click me"ボタンをクリックすると「最近参照した取引先」をロードします。
    * Lightning Applicationを作成して、それをVisualforcePageに組み込みました。
    * Visualforceタグを作成して、LightningExperienceから表示してみました。
* サンプルのソースコードは別途どこかにアップする予定です...(たぶん)

![ltng_extjsgrid_sample.png](https://qiita-image-store.s3.amazonaws.com/0/1840/7e07be2a-e2ff-305c-e1e7-300da63fae82.png "ltng_extjsgrid_sample.png")


# さいごに

とりあえず、何か動きそうなものが出来上がりました...が、上記のサンプルでは

* 最近参照したデータのロードしか実装してない
    * 追加、編集、削除は未実装
    * 検索条件を指定しての検索は未実装
* "click me"ボタン(ui:button で実装したもの)からはロードできるが、"load..."ボタンはうまく動かない
    * "click me"ボタンからは ```$A.enqueueAction``` にセットしたアクションが実行されますが、Ext JSのボタンからは ```$A.enqueueAction``` にセットしたアクションが実行されない..というか開始されない模様です。
    * ["Queueing of Server-Side Actions"(リファレンス)のtips](https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_actions_queue.htm)にある内容に関係しているのかな、と推測しているところですが..

    > If your action is not executing, make sure that you're not executing code outside the framework's normal rerendering lifecycle.
    > For example, if you use window.setTimeout() in an event handler to execute some logic after a time delay, you must wrap your code in $A.getCallback().


今後の[もくもく会](https://salesforcedugjapan.doorkeeper.jp/events/35707)などで引き続きトライしていきたいと思います。


# 後日談

某S先生から以下のコメントを頂きました。

![advice_from_stomita.png](https://qiita-image-store.s3.amazonaws.com/0/1840/e77587bd-1e9d-ff7c-006c-1be99b0b3bff.png "advice_from_stomita.png")

そんなわけで、もう少しイジッてみました。

①Controller.jsの中でこんなfunctionを定義してみます。

```javascript
({
  doInit: function(cmp, event, helper) {

    //------------------------------------------------
    // $A.getCallback をAura Loop外でも実行する...
    window.ltngAuraRun = $A.getCallback(function(fn){
      if(cmp.isValid()) { fn(); }
    });
    //------------------------------------------------
  ...
})
```

②それを```sendRequest```で使ってみます。

```javascript
var proxyCls = Ext.define('sfdc.proxy.LtngProxy', {
  extend: 'Ext.data.proxy.Ajax',
  alias: 'proxy.ltng',

  config: {
    ltngAction: null
  },

  /**
   * リクエストを送信する
   *  -> $A.enqueueAction を使ってキューに流す
   * @param {Ext.data.Request} request The request
   * @return {Ext.data.Request} The request
   * @private
   */
  sendRequest: function(request){
    var me = this,
        action = me.getLtngAction();

    //------------------------------------------------
    // controllerのdoInit()で定義したfunctionを使います
    //------------------------------------------------
    window.ltngAuraRun(function(){
      me.lastRequest = request;

      // サーバー側に渡すパラメータを生成する
      var actionParams = Ext.Object.merge(me.getParams(request),
          me.getExtraParams());

      action.setParams(actionParams);
      action.setCallback(this, function(res){
        // request.getCallback()は、request, success(boolean), responseをパラメータに持つメソッドを返す
        Ext.callback(request.getCallback(), request.scope,
          [request, (res.getState() === 'SUCCESS'), res.getReturnValue()]
        );
      });

      $A.enqueueAction(action);
    });

    return request;
  }

});
```

これで、ExtJSのイベントからもコールできるようになりました。(例えば、グリッドのページングツールバーでリロードボタンを押すとアクションが実行されました)
stomita先生、多々感謝です！

※詳細は...これから少しずつ掘り下げていければいいな、と思います。(思うだけかも)

これをキッカケにして、もう少しLightning Component開発にも手を出していければいいなぁと思う今日この頃でした。