Ext JS 5 と Sencha Touch でコードを再利用して Sencha Space アプリを作る
こんにちは、ゼノフィnakamuraです。

Ext JS 5 によって、モバイルアプリケーション (Sencha Touch) とデスクトップ/タブレットアプリケーション (Ext JS) のコードを共有するという夢が実現できました。この記事では、Sencha Cmd 5 を使って、Ext JS 5 と Sencha Touch 2 とでコードを共有する Sencha Space アプリケーションを作成します。
この記事の目的は次の通りです。
- 出来るだけコードを再利用する Sencha Touch と Ext JS アプリケーションを作成する
- 共有機能が含まれているパッケージを利用する
- 非互換性の扱い方を学ぶ
- Sencha Space の環境でアプリケーションをデプロイする
目的に到達するために、グリッドとチャートの形式で 2012年の最も人気な Web ブラウザのデータを表示するアプリケーションを生成します。この記事のソースコードは全てフォーク/ダウンロードできます。
準備
成功するためには準備するすることが重要です。 ワークスペースとパッケージは 以前からありますが、Ext JS 5 では、複数アプリケーションの環境をよりクリアにします。
さて、まずはワークスペースを作りましょう。Sencha Cmd 5 がインストールされていることを確認して下さい。
$ sencha generate workspace Ext5Tablet Sencha Cmd v5.0.0.160 ... |
これでアプリケーションとフレームワークをホストするディレクトリができました。ワークスペースのディレクトリに移動して、次を実行してください:
gmac15:Ext5Tablet grgur$ sencha -sdk /path/to/ext-5.0.0 generate app MyApp ./ExtSpace |
アプリケーション名を MyApp とします。この Ext JS 5 アプリケーションはタブレットユーザーむけになります。スマートフォンのユーザーをサポートするには、別の Sencha Touch アプリケーションを生成します:
$ sencha -sdk /path/to/touch-2.3.1-complete generate app MyApp ./TouchSpace |
最後に、共有するコードを格納するパッケージを生成しましょう。その中にチャートやグリッド/リストコンポーネントをコーディングしますので、パッケージをGridChartと呼ぶことにします。ワークスペースディレクトリで次を実行して下さい:
$ sencha generate package GridChart |
ファイル構造は次のようになります。
ExtSpaceとTouchSpaceはアプリケーションフォルダで、packages/GridChart には共有するコンポーネントがあり、ext と touch はフレームワークのSDKを参照します。次は各部分をもっと深く検討します。
共有チャートコンポーネント
Ext JS 5 は、Sencha Touch 2 から引き継いだ素晴らしい新たなチャートが含まれています。その上、コンフィグのパターンが非常に似ていますので、簡単に再利用できます。
Ext.chart.Chart ウィジェットで必要なチャートをインテリジェントにインスタンス化することが出来ますので、それを両方のフレームワークのために行えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Ext.define('GC.Chart', { extend : 'Ext.chart.Chart', xtype : 'gcchart', config : { animate : true, background : 'white', insetPadding : 40, interactions : 'itemhighlight', legend : { docked : 'bottom' }, colors: [...], series: [...], axes : [...] } }); |
完全なソースコードはここにあります。
注: Sencha Touchのチャートは Sencha Complete スイートの一部となります。
この例で二つのパターンが見えます。まずは、コードが共有されるということ。ということは、クラス定義は全て Sencha Touch 2 と Ext JS 5 とで互換性があるということです。
次に、Ext JS 5 は、config オブジェクトからのプロパティを理解でき、ブロック外のものにマッピングします。このチャートコンフィグは Sencha Touch にそっくりですが Ext JS でも無事に動作している理由がそれです。
ユニバーサルなチャートウィジェットができましたので、二つのフレームワークに対して、少しだけ違うところがある共有コードの生成に付いて検討できます。
Grid vs. List
グリッドのようなビューで、データを表示したい。Ext JS grid ウィジェットはタブレット向けのタスクを実行し、Sencha Touch List はスマートフォン用にデータを表示します。しかし、両方のコードは同じパッケージに存在します。
このアプローチの魅力は、GridChart パッケージの ‘package.json’ ファイルの1行に入っています。ワークスペースのディレクトリから ~/packages/GridChart/package.json を変更して、次の行を追加して下さい:
"classpath": "${package.dir}/src,${package.dir}/src-${framework.name}" |
‘src-${framework.name}’ という部分によって、それぞれのフレームワークのソースコードを保存する二つのフォルダを作ることができます。そのフォルダ名は ‘src-touch’ (List) と ‘src-ext’ (Grid) となります。
双方のウィジェットはとてもシンプルになります。タブレット用のグリッドの設定は次のようになります:
Ext.define('GC.Grid', { extend : 'Ext.grid.Panel', xtype : 'gcgrid', title : 'Browsers', split : true, collapsible : true, columns : [ { text : 'Month', flex : 1, dataIndex : 'month' }, { header : 'IE', xtype : 'numbercolumn', width : 70, dataIndex : 'data1', format : '0,0%' }, {...}, {...}, {...} ] }); |
完全なソースコードはここにあります。
また、List ではテンプレートがほとんどを占めます。
Ext.define('GC.Grid', { extend : 'Ext.List', xtype : 'gcgrid', config : { cls : 'browserlist', itemTpl : [ '<h1>{month}</h1>', '<div class="browsers">', '<b>IE</b> {data1} ', '<b>FF</b> {data2} ', '<b>Chrome</b> {data3} ', '<b>Safari</b> {data4} ', '</div>' ].join('') } }); |
完全なソースコードはここにあります。
データ表示のシンプルなアプローチと組み合わせることができます。次のセクションでは、各アプリケーションのプラットフォーム特定のコードに、このコードを挿入することの説明をします。
タブレットレイアウト
共有コードはアプリの大きい部分を占め、そこにほとんどのビジネスロジックが含まれています。コードを挿入するのは簡単で、Ext JS アプリケーションの~/ExtSpace/app.json ファイルに一行を追加するだけです。
"requires": [ "sencha-charts", "GridChart" ], |
完全ソースコードはここにあります。
Ext JS 5 は依存性に関して、モジュール的なアプローチを取ります。他にも ‘sencha-charts’ パッケージを利用したいでしょう。Sencha Cmd 5 はそれぞれを見つけることが出来、アプリケーションでそのクラスが使用可能になります。
注: Ext JS 5 SDK ディレクトリを眺めると、packages フォルダには、ext-charts と sencha-charts があります。ext-charts は古いチャートのパッケージを指し、sencha-charts は新しく改善されたコードです。
Main ビューを編集する前に、Cmd 5 の新しい使い方があります。app watch です。ターミナルがアプリケーションのディレクトリに入っていることを確認し、 (例えば ~/ExtSpace) 次を実行して下さい:
$ sencha app watch Sencha Cmd v5.0.0.160 [INF] Loading app json manifest... ... create MyApp-all.css [INF] Mapping http://localhost:1841/ to /Users/grgur/Projects/modus/modus-presos/Ext5Tablet... [INF] ------------------------------------------------------------------ [INF] Starting web server at : http://localhost:1841 [INF] ------------------------------------------------------------------ [INF] Waiting for changes... |
ベテランの開発者は既にこのコマンドをご存じでしょう。このコマンドは裏で SASS や JavaScript のファイルの変更を待って観察しています。変更があると、アプリケーションをアップデートするため必要なに最小限の操作 (CSS のコンパイルとかクラスメタデータのリフレッシュ) を行います。
さらに、このコマンドは組み込まれたJetty Web サーバーをインスタンス化してファイルをホストします。
全てが準備されたら、メインビューを生成できます。
Ext.define('MyApp.view.main.Main', { extend : 'Ext.container.Container', xtype : 'app-main', requires: [ 'GC.Chart', //#1 'GC.Grid', //#1 'Ext.data.JsonStore', 'Ext.chart.*' ], layout : { //#3 type : 'border' }, config : { //#2 grid : 'west', chart : 'center', store : [ { month : 'Jan', data1 : 20, data2 : 37, data3 : 35, data4 : 4 }, //... { month : 'Dec', data1 : 15, data2 : 31, data3 : 47, data4 : 4 } ] }, /** * storeId または store のインスタンスを元にストアをインスタンス化 */ applyStore : function (data, store) { // #5 return Ext.isString(data) || data.isStore ? Ext.getStore(data) : Ext.factory({ fields : ['month', 'data1', 'data2', 'data3', 'data4' ], data : data }, 'Ext.data.JsonStore', store); }, /** * 共有チャートをインスタンス化 * @param region * @param chart * @returns {GC.Chart} Chart */ applyChart : function (region, chart) { // #5 return Ext.factory({ region : region, width : 400, store : this.getStore() // #4 }, 'GC.Chart', chart); }, /** * グリッドをインスタンス化 */ applyGrid : function (region, grid) { // #5 return Ext.factory({ region : region, width : 400, store : this.getStore(), // #4 title : 'Browsers in 2012' }, 'GC.Grid', grid); }, /** * ビューにアイテムを追加 */ initComponent : function () { var me = this; me.items = [ me.getChart(), me.getGrid() ]; me.callParent(); } }); |
完全ソースコードはここにあります。
上記のコードをレビューすると、いくつかの問題点に気付くでしょう。
- 以前は GridChart パッケージがアプリにインクルードし、Sencha Cmd は位置を正しくマッピングすることが出来ました。そのパッケージのクラスを利用するためには、Sencha の規約通りにrequire する必要があります。
- Sencha Touch 2 と違って、Ext JS 4 のコンフィグオプションは config オブジェクトのメンバーではありません。複数フレームワークで使えるようにするために、Ext JS 5 ではコンストラクタの config オブジェクトからのパラメータはほとんど認識できるようになりました。この例では、store プロパティを再利用し、新しいもの2つ (gridとchart) を追加しています。grid と chart はどちらもボーダーレイアウトでの位置を表しています。
- レイアウトは例外です。Sencha Touch 2 と Ext JS 5 での Ext.Component ライフサイクルの違いのため、layout コンフィグは config ブロックの外ではないいけません。
- grid と chart コンポーネントは同じデータストアを再利用します。
- factory メソッドと appliers を利用する、Sencha Touch モジュール コンポーネント インスタンス化は Ext JS でも対応となっています。applyStore, applyChart, applyGrid メソッドをご覧下さい。
コツ: パッケージの共有コンポーネントを生成する際には、‘constructor’ をオーバーライドできます。
constructor: function (config) { config.layout = 'border'; this.callParent([config]); } |
ほとんどの作業は終わりました。Ext JS 5 のアプリケーションをブラウザで実行すると、次のようなものが表示されます。
このビューを Sencha Touch に複製しましょう。
スマートフォンのレイアウト
ビューのコードを追加する前に、このアプリケーションの ~/TouchSpace/app.json にある app.jsonを確認します。
完全ソースコードはここにあります。
今回は sencha-charts を参照する必要はありません、なぜならチャートは Sencha Touch 2.3 のパッケージの形式ではないからです。
sencha app watch を実行すると、このアプリが効率よく更新されるようになります。
ここで自動生成されたMain ビューを編集して、次の様に変更します。
Ext.define('MyApp.view.Main', { extend : 'Ext.Container', xtype : 'main', requires : [ 'GC.Chart', 'GC.Grid', 'Ext.data.JsonStore', 'Ext.chart.*' ], config : { layout: { type: 'hbox', align: 'stretch' }, grid : 1, chart : 2, store : [...] }, applyStore : function (data, store) {...}, applyChart : function (flex, chart) { return Ext.factory({ flex : flex, store : this.getStore() }, 'GC.Chart', chart); }, applyGrid : function (flex, grid) {...}, /** * Add items to the view */ initialize : function () { var me = this, grid = me.getGrid(), chart = me.getChart(); me.add([ grid, chart ]); me.callParent(); } }); |
完全なソースコードはここ にあります。
ボーダーレイアウトの代わりに、HBox を使いました。他は全てアプリケーションのExt JS バージョンで利用したコードに似ています。
主な違いは、initialize と initComponent ブロックにあり、Ext JS では this.items プロパティをオーバーライドする必要があり、Sencha Touch では add メソッドを使っています。 この理由は単純で、Sencha Touch はインスタンス化の最中にDOM エレメントを生成しますが、Ext JS はレンダリング中にこれを行い、これは DOM にウィジェットを挿入する直前に行うからです。
Ext JS アプリケーションのコードをとても多く共有したので、数分間でよく似た UI を開発できました。ブラウザで動作させると、次のようになるでしょう。
Sencha Spaceへのデプロイ
Sencha Space は保護された、モバイル Web や HTML5 アプリケーションのクロスプラットフォーム環境で、組織がエンドユーザーに企業アプリケーションを提供する際に利用できます。Sencha Space をまだご存知ではなければ、 Sencha Spaceの詳しい説明(Webセミナー) on Vimeo をご覧ください (日本語字幕付き) 。
我々の新しいアプリケーションが、このエコシステムの一部になることで、Sencha SpaceのAPIを利用して、他のアプリケーションと通信することも可能となるという 利点があります。
既にアカウントを持っているとして、 Sencha Space Management Console にログインします。 ここから、アプリケーションを追加し、 あなたのローカルウェブサーバーを指していることを確認して下さい。 まだアプリケーションを一般公開する必要はありませんが、デバッグに役に立ちます。
両方のアプリケーションに (下の図のように) invoke intent の文字列を設定して下さい。このデモは、スマホ用アプリ (Sencha Touch) は *bpmobile* を使用し、タブレット用アプリ (Ext JS) は、*bprich* を利用しています。
Sencha Space API のパスを追加するために、再び両方のアプリケーションの app.json ファイルを編集します。デバッグしたい場合には、あなたのローカル Weinre インスタンスへのパスを追加することもできます。
"js": [ { "path": "http://space.sencha.io/space.js", "remote": true }, // ... ] |
Ext JS 5 アプリとSencha Touch 2 アプリの完全なコードがあります。
最後に素晴らしいことがあります。我々のアプリケーションがデバイスを検出し、ユーザーにもっと最適な体験を得るためには、他のアプリケーションに切り替えるようにプロンプトを出そうと思います。例えば、Ext JS のアプリケーションにスマホでアクセスするユーザーは、その人の端末に適切なソリューションがあることを通知するべきです。
あなたの Ext JS アプリケーションの ~/ExtSpace/app/Application.js ファイルに次の追加をします。
launch : function () { var me = this; Ext.onSpaceReady(function () { var width = Ext.getBody().getWidth(); if (width < 1000) { me.transferToMobile(); } }); }, transferToMobile : function () { Ext.Msg.confirm('Phone detected', 'Do you want to see the phone site instead?', function (resp) { if (resp === 'yes') { Ext.space.Invoke.get('bpmobile').then(function (connection) { connection.send({}, true); }, console.warn.bind(console)); } }); } |
完全なソースコードはここ にあります。
Sencha Space の環境が使用可能になったら、Ext.onSpaceReady が発火します。これはドキュメントが使用可能になった後、数ミリ秒内で起こります。ハンドラーが最初に確認メッセージをプロンプトし、その後は Invoke API を利用して Touch のアプリケーションをフォアグラウンドに持って来ます。どのアプリケーションと通信するか指定するinvoke 文字列 bpmobile に注目して下さい。
さて、ゆっくりして、我々のコード作業の結果を見ましょう!
ソースコード
このアプリケーションの完全に動作するソースコードはここで入手可能です。これをブラウザまたは Sencha Space インスタンスに読み込むだけです。
まとめ
Ext JS 5 はエンタプライズアプリケーションに対して、多くの新しい機能を提供しています。その一つは Sencha Touch との統合とコードの共有です。しかし、統合の可能性は Sencha Cmd 5 と Sencha Space を利用するとより拡張できますので、どのエンタプライズ環境でも満足の行く、最も機能性が豊かなフレームワークを提供しています。この例では、Sencha Touch 2 から受け継いだ新しい Sencha Chart の機能の力も目撃できました。Ext JS 5 は、デスクトップとタブレットデバイスの両方でスムーズで申し分ないユーザー体験を提供します。