Sencha Touch App をExt JS 6 モダンツールキットにアップグレードする方法 パート2
こんにちは、ゼノフィseoです。
このブログシリーズのパート1ではExt JS 6 モダンツールにおける変更点を解説し、Sencha Touch アプリのベーシックモバイルアップグレードをどのように進めるかをお見せしました。この記事では、アドバンストモバイルアップグレードを解説します。
アドバンストモバイルアップグレード
アドバンストモバイルアップグレードではMVVMパターンを使用します。手順と時間はより必要としますが、最新のフレームワークの新機能やクラスを活用できるようになります。
また、新しいMVVMアーキテクチャにより、アプリケーションパフォーマンスとコードベースを改良できます。
dinmu1フォルダを新しいフォルダdinmu2にクローンしましたので、そこで違いを確認していただけます。
ビューのマイグレーションから始めましょう
- app/viewフォルダの中に下記のフォルダー構造を作ります(ビューはそれぞれ独自のサブフォルダを持ちます):
- app/view/main
- app/view/settings
- Main.jsをメインサブフォルダに入れ、SettingsView.jsをセッティングサブフォルダに移動します(また、SettingsView.jsをSettings.jsという名称に変更しました)
- これら2つのビューのネームスペースを以下のように編集します:
- Dinmu.view.main.Main
- Dinmu.view.settings.Setting
- この時点ではアプリは壊れた状態です。ビューポートはMain.jsを見つけることができず、Main.jsビューはSettingsビューを見つけることができないからです。これを修正します:
- app.jsで下記の行を削除します:
Ext.Viewport.add(Ext.create(‘Dinmu.view.Main’)); - launch()メソッドの上に新しいビューポートを用意します。Ext 6 が提供する新しい方法に従い、mainViewプロパティを設定します: mainView:
‘Dinmu.view.main.Main’, - app.jsよりviews:
[‘main’] を取り除きます - requires配列に‘Dinmu.view.main.Main’ を追加します
- Main.jsで、Settings View のrequiresを ‘Dinmu.view.settings.Settings’ に変更します
- この修正で何も壊していないことを確かめるためsencha app refresh を実行し、エラーのないことを確認してください。
コントローラをビューコントローラにマイグレートします
- 以下のファイルを新規作成します:
app/view/main/MainController.js - 以下のクラス定義を作成します:
Ext.define('Dinmu.view.main.MainController', { extend: 'Ext.app.ViewController', alias: 'controller.main', //all the VC methods init: function(){ console.log("new VC is initialized"); } });
- ビューコントローラをメインビューに紐付けます。
Main.jsで次の行を加えます: controller: ‘main’, - requires配列にメインコントローラを追加します: Dinmu.view.main.MainController
- sencha app refresh を再度実行し、ブラウザでアプリをテストします。VCの紐付けに成功した旨のログメッセージが表示されるはずです。このコントローラをSettingsビューに紐付ける必要はありません。これはSettingsビューがメインビュー内にネストしているためで、このためメインコントローラには常にアクセスできます
- controllers配列は必要ないため、app.jsから取り除きます
- ビューコントローラからinitメソッドを除外し、新しいビューコントローラに app/controller/Main.js のメソッドを全てコピーします
- ここから少し難しくなります。refsやcontrolブロックも必要ないので、これらを修正します。コントロールブロックの代わりにリスナーをビューに作ります
入れ替える必要のあるコントロールが5つあります:
- onCarouselChange – メインビューのactiveitemchangeです
- btnBack – タイトルバーの戻るボタン操作です
- onSettingsBtnTap – settingsビューのsettingsボタン操作です
- onToggle – togglebuttonのtoggle操作です
- onRefresh – 再読み込みボタンのタップ操作です
Main.jsビュークラスに、activeitem changeリスナーを生成します:
listeners: { 'activeitemchange': 'onCarouselChange' },
Main.jsの戻るボタン上に、タップリスナーを生成します:
listeners: { 'tap': 'onBackBtnTap' }
Main.jsのsettingsボタン上に、タップリスナーを生成します:
listeners: { 'tap': 'onSettingsBtnTap' }
Settings.jsの toggleボタン上に、toggleリスナーを生成します:
listeners: { change: 'onToggle' }
Settings.jsの再読み込みボタン上に、タップリスナーを生成します:
listeners: { 'tap': 'onRefresh' }
- ブラウザでアプリケーションを実行すると、様々なイベントエラーが発生します。コンポーネントクエリをもつレファレンスが壊れたためです。これらを修正します。
- onLaunchメソッドをinit に変更します。Dinmu.utils.Functions.loadData() はSettingストアを使用しているのですが、この変更のためにコントローラに紐付かなくなるため、アプリケーションは壊れます。ひとまずDinmu.utils.Functions.loadData() の行をコメントアウトします。
- sencha app refresh を再度実行し、ブラウザでアプリをテストします。再読み込みボタン以外全てが動くはずです。再読み込みボタンはストアを必要とするのですが、それがまだ紐付けられていません。
this.getMainView() へのレファレンスの全てはthis.getView()に置き換えることができます。ビューコントローラがビューの振る舞いも理解しているため、簡単に直すことができます。私のアプリでは3箇所置き換えました。
もう一つ必要なビューレファレンスは、コンポーネントのレファレンスを取得するので後で調べます。Settings.jsに次のプロパティを加えます:
reference: ‘settings’.
MainControllerで、this.getSettingsView() を this.lookupReference(‘settings’) に置き換えます
onToggleメソッドは下記のように修正します:
var s = this.lookupReference('settings'); if (!newVal) { s.down('field[name="city"]').enable(); s.down('field[name="country"]').enable(); s.down('field[name="units"]').enable(); } else { s.down('field[name="city"]').disable(); s.down('field[name="country"]').disable(); s.down('field[name="units"]').disable(); s.down('field[name="city"]').reset(); s.down('field[name="country"]').reset(); } |
Main.jsビューで、titlebarコンフィギュレーションにレファレンスを置きます:
reference: ‘titlebar’,
そしてonCarouselChangeメソッド を以下で置き換えます:
onCarouselChange: function(carousel, newVal, oldVal) { var t = this.lookupReference('titlebar'); if (newVal.getItemId() == "mainview") { t.down('button[action=back]').hide(); t.down('button[action=settings]').show(); t.setTitle('Do I need my Umbrella?'); } else { t.down('button[action=back]').show(); t.down('button[action=settings]').hide(); t.setTitle('Settings'); } }, |
ビューモデルにストアをリンクします
- 以下のファイルを新規作成します:
app/view/main/MainModel.js - 次のクラス定義を作成します:
Ext.define('Dinmu.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', requires: [ ], stores: { } });
- ビューモデルをメインビューに紐付けます:
Main.jsに次の行を追加します: viewModel: ‘main’,
requires配列にDinmu.view.main.MainModel を忘れずに追加してください
- ここでSettingsストアをリンクします。まずrequires配列にDinmu.store.Settings を追加します
- Settingsストアで、ストアクラス定義にalias: ‘store.settings’ を追加します
- Ext JS 6 でストアは、storeId をクラス名に自動的にはセットしません。ですのでstoreIdを‘Settings’にセットします。これでストアマネジャは Ext.getStore(‘Settings’) を経由してストアを見つけることができます
- ストアオブジェクトに次のストアを追加します(settingsエイリアスを示すタイプです):
'settings': { type: 'settings' },
- さきほどMainControllerでコメントアウトしたDinmu.utils.Functions.loadData() 行を有効にします。そしてsencha app refresh を再度実行し、ブラウザでアプリをテストします。
MVVMパターンを利用したアプリはここで出来上がった状態になり、アプリが動作します。
アプリのその他の改良
- このアプリケーションはストアのデータフィードを使いません。しかしExt JS 6 のもう1つの大きなメリットは、Model定義内のモデルフィールドを全てコーディングする必要がないという点にあります。データをフィードから直接取得することができるからです。フィードに全てのデータを入力する手間は省け、モデル定義を非常に小さくて収めることができます。
- Ext JS 6 のこの他の新しい点にコンフィグブロックがあります。Sencha Touch では全てをconfigブロックに定義しました。Ext JS 6 ではゲッター、セッター、アプライ、アップデートメソッドの自動生成に必要なプロパティのみコンフィグブロックに入れます。Dinmuアプリケーションでは、コンフィグブロックの中身をほとんど取り除く必要がありました。たいていのクラスはSencha Touch のスタイルに従ったコンフィグブロックで問題なく動作しますが、放っておくとどこかで問題を起こしてしまうかもしれません。
- プロミスとディファード対応です。私がコーディングしたセッティングフォームの保存の処理が動作したことはには少し驚きました。sync()メソッドやそのレコード作成、削除、編集処理にはたくさんの仕掛けがあります。以下のようにコーディングしておくとさらに良かったと考えています:
- フォームを入力
- ローカルストレージに以前の設定が残っていないかを確認
- 以前のレコードが残っている場合はそれを除外
- ストアを同期。同期完了後に新しいレコードを追加
- ストアを同期し、レコードを追加した後、ストア内のデータをロードする
Ext JS 6 はプロミスとディファードに対応しているためこれが可能で、このため、then()メソッドを経由してメソッドをチェーン化することができます。removeAllSettings メソッドとaddSettingsメソッドをどのようにコーディングしたかご覧ください。onRefresh メソッドではそれらをチェーン化しました。dinmu1ファイルもしくはtouchdinmuファイル と比較し、コードがどのように異なっているか確認してください。
テーマをアップグレード
- app.jsonファイルのテーマプロパティを変更することで、テーマを切り替えることができます。初期状態では次のテーマの中から選ぶことができます:
- theme-cupertino (iosテーマ)
- theme-mountainview (androidテーマ)
- theme-blackberry (blackberryテーマ)
- theme-windows (windowsテーマ)
- theme-neptune
- theme-triton (デフォルト)
テーマを切り替えた後はsencha app build を再度実行する必要があります。
- Ext JS のプラットホームスイッチャーが新しくなりました。代わりにapp.json内のプロフィールのbuildブロックを使用します。これをセットアップするため、app.jsonに以下を記述します:
"builds": { "ios": { "toolkit": "modern", "theme": "theme-cupertino" }, "android": { "toolkit": "modern", "theme": "theme-mountainview" }, "windows": { "toolkit": "modern", "theme": "theme-windows" }, "bb": { "toolkit": "modern", "theme": "theme-blackberry" }, "default": { "toolkit": "modern", "theme": "theme-triton" } },
複数のテーマを開発機で有効にするため、app.json bootstrapブロックに次の行を追加します:
"bootstrap": { "base": "${app.dir}", "microloader": "bootstrap.js", "css": "bootstrap.css", "manifest": "${build.id}.json" //this is the magic, which generates a manifest file, to load on local. },
複数のテーマをプロダクションビルドで有効にするため、app.json outputブロックに次の行を追加します:
"output": { "base": "${workspace.build.dir}/${build.environment}/${app.name}", "appCache": { "enable": false }, "manifest": "${build.id}.json", "js": "${build.id}/app.js", "resources": { "path": "${build.id}/resources", "shared": "resources" } },
index.htmlには以下を記述します:
Ext.beforeLoad = function (tags) { var s = location.search, // the query string (ex "?foo=1&bar") profile; if (s.match(/\bios\b/) || tags.ios !==0) { profile = 'ios'; } else if (s.match(/\bandroid\b/) || tags.android !==0) { profile = 'android'; } else if (s.match(/\bwindows\b/) || tags.windows !==0) { profile = 'windows'; } else if (s.match(/\bbb\b/) || tags.bb !==0 ) { profile = 'bb'; } else { profile = 'default'; } Ext.manifest = profile; // this name must match a build profile name };
sencha app refreshおよびsencha app build を実行します。これにより全てのプロフィールがビルドされ、実行可能となります
- Ext JS 6 モダンツールキット用のテーマはExt JS と同じパッケージ構造を使っています。あなた独自のテーマパッケージを拡張し、Sencha Cmd によってカスタムテーマを生成できるという点で、これは優れています:
sencha generate theme theme-MyTheme
カスタムテーマパッケージを作る予定はなくとも、テーマ付けの機能は向上しています。既存のテーマをアップグレードするには、変数を全てsass/var/ フォルダに入れてください。
私の天気アプリに使ったsass/var/all.scss をご覧ください。カスタムSass/CSS クラスはsass/src/ フォルダにストアされます。カスタムテーマパッケージのないアプリケーションでは、JSアプリケーションのフォルダ構造をマップする必要があります。つまり app/view/main/Main.jsのSassファイルを下記の場所に置きます: sass/src/view/main/Main.scss.

私の場合はSencha Touch アプリケーションからほとんどのスタイリングを持ってくることができました。しかし今ではSencha Touch のデフォルトテーマは存在せず、代わりにNeptuneとTriton というテーマになっています。異なったSass変数がそれぞれにあり、異なったDOMを必要とします。
つまり例えば、テンプレートでカスタムスタイリングを使っていた場合、アップグレードしたアプリでも特に問題ありません。ですがカスタムSassを使ってSencha Touch テーマをオーバーライドしていた場合は、その見た目の違いに気付くでしょう。ここでのベストプラクティスは、ブラウザで全てのビューを確認して、スタイリングが正しいかを確かめることです。私が天気アプリに使った sass/src/view/main/Main.scssをご覧ください。
このシリーズの次回では、アドバンストユニバーサルアップグレードについて解説します。