Ext JS 3.4 アプリをExt JS 6 にアップグレードする方法
こんにちは、ゼノフィseoです。
Ext JS は機能面において非常に成熟したフレームワークです。2007年に最初にリリースして以来、莫大な数の強力な機能を追加してきました。フレームワークのそれぞれのバージョンを通して何千というアプリケーションが作られており、そのうち成功しているプロジェクトのいくつかはまだExt JS 3.4 を利用しています。
プロジェクトを最新版にマイグレーションして最新の機能を活用したいにもかかわらず、その手順がわからないという開発者の方はいます。マイグレーションをまだしない理由として多くの理由は同様です。開発したアプリケーションは安定しており、バグは全て取り除かれ、すべてが問題なく動作しています。また同時に、そのアプリケーションは複雑であり、カスタムコンポーネントやエクステンションを多数抱えているためにマイグレーションは簡単でないと感じているのです。
一方、Ext JS 6 へのアップグレードに関してほとんどの開発者は同じメリットを挙げています:
- ブラウザのアップデートをコントロールする必要がない
- アプリケーションのメンテナンスの時間と労力を減少できる
- パフォーマンスとメモリの使用を改善できる
- モバイル端末とタッチイベントに対するサポートを活用できる
- 数行のコードでを書くだけでExt JSの新機能を利用できる
お客様には常に、フレームワークの最新版Ext JS 6 へのアップグレードをお奨めしています。
この記事では簡単なマイグレーションの例をお見せします。実例としてExt JS 3.4 のグリッドをExt JS 6 にマイグレートしました。オリジナルと最終的なソースコードはレビューしていただけるようにしました。
Ext JS 6 アプリケーションはMVVM(Model-View-ViewModel)アーキテクチャパターンをベースにビルドします。MVVMにはView(ViewModel)という概念があり、これによりModelのデータと、Viewにおけるそのデータ表現(例えばデータバインディング)の間の変更を管理します。MVVMパターンについて詳しくはこちらをお読みください。
空のExt JS 6 プロジェクトを新規に生成する
コマンドラインツールであるSencha Cmdを使用して空のExt JS 6 プロジェクトを生成することができます。Ext JS 6 SDK フォルダまでコマンドラインもしくはターミナルで移動し、次のコマンドを実行します:
sencha generate app -classic -starter=false Migration ../migration |
Sencha Cmdの機能について詳しくはこちらをお読みください 。
新規プロジェクトを初期化する
空のプロジェクトにApplicationクラス、およびMain Viewクラスを追加する必要があります。これらのクラスはアプリケーションを実行する上で必要です。
- メインビューも同様にSencha Cmdで生成することができます。新規に作成したマイグレーションフォルダに移動し、以下のコマンドを実行します:
- Applicationクラスは手動で生成します。app/Application.jsファイルを下記の内容で作成します:
- 最後に、Ext JS 6 アプリケーションをビルドし、内部のウェブサーバを利用して実行します。次のコマンドをmigrationフォルダから実行します:
- ブラウザでhttp://localhost:1841/ に移動し、アプリケーションが実行されていることを確かめます。以下のような画面が確認できるかと思います:
sencha generate view main.Main |
Ext.define('Migration.Application', { extend: 'Ext.app.Application', name: 'Migration' }); |
sencha app build sencha web start |
Modelをマイグレートする
ここからExt JS 3.4 コードを新しいExt JS 6 プロジェクトにマイグレートすることができます。まずはModelをマイグレートします。アプリケーションで使用されているデータフォーマットをModelで表現します。データのフォーマットはExt JS 3.4 ではストア定義の中で、フィールドプロパティにより定義します。
var store = new Ext.data.ArrayStore({ fields: [ {name: 'company'}, {name: 'price', type: 'float'}, {name: 'change', type: 'float'}, {name: 'pctChange', type: 'float'}, {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'} ] }); |
これは後方互換性の定義であるため、Ext JS 6 を含む上位バージョンでも同じ方法でデータフォーマットを定義することができます。しかしModelクラスで分離して定義する方が適切で、このクラスではデータに紐づくフィールドやその他のロジックを定義します。
- Modelクラスを作成します
- app/model/Company.jsファイルを下記の内容で作成します:
- Ext JS 3.4 のストアからフィールドプロパティをモデルにコピーします:
- Storeクラスを作成します
- app/store/Company.jsファイルを下記の内容で作成します:
- Storeクラスには新規に作成したModelを使用します:
Ext.define('Migration.model.Company', { extend: 'Ext.data.Model' }); |
Ext.define('Migration.model.Company', { extend: 'Ext.data.Model', fields: [ {name: 'company'}, {name: 'price', type: 'float'}, {name: 'change', type: 'float'}, {name: 'pctChange', type: 'float'}, {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'} ] }); |
Storeは基本的にExt JS 3.4 と同じで、レコード(Modelクラスのインスタンス)の集合です。
Ext.define('Migration.store.Companies', { extend: 'Ext.data.ArrayStore', alias: 'store.companies' }); |
Ext.define('Migration.store.Companies', { extend: 'Ext.data.ArrayStore', alias: 'store.companies', requires: ['Migration.model.Company'], model: 'Migration.model.Company' }); |
Viewをマイグレートする
Grid Panelの基本的な扱い方はExt JS 3.4 と同一です。主な違いは今回はGrid Panelを分離してView Classに定義する必要があるという点です。
- まずView Classの空のテンプレートをGrid Panel用に作る必要があります。次のコマンドをマイグレーションフォルダから実行します:
- app/view/company/Grid.jsファイルを編集します:
- Ext.grid.Panelにベースクラスをセットします(Ext JS 3.4 の場合のようにExt.grid.GridPanelではありません:
extend: “Ext.grid.Panel” - xtype: ‘companygrid’プロパティを追加します。これはGrid Panelへのリファレンスで、後ほど使用します
- htmlプロパティを取り除きます
- 新規に作成したGrid Viewに、Ext JS 3.4 のグリッドカラム定義およびその他のグリッドパネルのプロパティをコピーし、すべてのファンクションをひとまずコメントアウトします(それらは後ほどマイグレートします)。
- autoExpandColumn: ‘company’コンフィグを取り除き、flex: 1コンフィグをCompanyカラムに追加します。flexプロパティはカラム幅を管理するための新しい定義です。Ext.Component-cfg-flexについて詳しくはこちらをお読みください 。
- アクションカラムのiconプロパティおよびgetClassプロパティを、FontAwesomeアイコンへのリファレンスを持ったiconClsに変更します。
- ここで、Main ViewにGridを加える必要があります。Main ViewはExt.panel.Panelのインスタンスであり、Main Viewのitemsアレイ(先ほど定義したxtypeを使用します)、およびrequiresアレイ(クラス名を使用します)にGridを追加するだけで済みます:
- 次のコマンドをマイグレーションフォルダから実行します:
- ブラウザでhttp://localhost:1841/ に移動し、アプリケーションが実行されていることを確かめます。以下のような画面が確認できるかと思います:
sencha generate view company.Grid |
上記の手順を終えると、コードはこのような具合になっていると思います:
Ext.define("Migration.view.company.Grid",{ extend: "Ext.grid.Panel", requires: [ "Migration.view.company.GridController", "Migration.view.company.GridModel" ], controller: "company-grid", viewModel: { type: "company-grid" }, columns: [ { id :'company', header : 'Company', width : 160, sortable : true, dataIndex: 'company', flex: 1 }, { header : 'Price', width : 75, sortable : true, //renderer : 'usMoney', dataIndex: 'price' }, { header : 'Change', width : 75, sortable : true, //renderer : change, dataIndex: 'change' }, { header : '% Change', width : 75, sortable : true, //renderer : pctChange, dataIndex: 'pctChange' }, { header : 'Last Updated', width : 85, sortable : true, //renderer : Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange' }, { xtype: 'actioncolumn', width: 50, items: [{ iconCls: 'x-fa fa-minus', tooltip: 'Sell stock'/*, handler: function(grid, rowIndex, colIndex) { var rec = store.getAt(rowIndex); alert("Sell " + rec.get('company')); }*/ }, { iconCls: 'x-fa fa-plus', /* handler: function(grid, rowIndex, colIndex) { var rec = store.getAt(rowIndex); alert("Buy " + rec.get('company')); }*/ }] } ], stripeRows: true, height: 350, width: 600, title: 'Array Grid', stateful: true, stateId: 'grid' }); |
Ext.define("Migration.view.main.Main",{ extend: "Ext.panel.Panel", requires: [ "Migration.view.main.MainController", "Migration.view.main.MainModel", "Migration.view.company.Grid" ], controller: "main-main", viewModel: { type: "main-main" }, items: [{ xtype: 'companygrid' }] }); |
sencha app build sencha web start |
ViewModelを作成する
ViewModelはExt JS 5 で導入された新しい概念です。Viewと、それに紐づけられたDataの間の変更を管理します。Sencha Cmdを使用してGrid Viewを生成すると、ViewModelクラスも同時に生成されます。
- ViewModelファイルであるapp/view/main/GridModel.jsを編集します:
- ViewModelクラスよりdataプロパティを取り除きます
- ViewModelクラスのstoresコンフィグにcompaniesストアを加えます
- companiesストアのdataコンフィグを、Ext JS 3.4 コードからもってきたデータにアサインします
- ViewModelにGridをバインドします
Ext.define('Migration.view.company.GridModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.company-grid', requires: [ "Migration.store.Companies" ], stores: { companies : { type: 'companies', data: [ ['3m Co', 71.72, 0.02, 0.03, '9/1 12:00am'], ['Alcoa Inc', 29.01, 0.42, 1.47, '9/1 12:00am'], … ] } } }); |
Grid Viewにbindコンフィグを追加し、ViewModelのcompaniesストアにグリッドをバインドします:
bind: { store: '{companies}' }, |
ViewControllewにコードをマイグレートする
ViewControllerもMVVMパターンの一部です。Viewに対してそれぞれViewControllerが個別に作成されます。ViewControllerはイベントをリッスンし、アプリケーションのビジネスロジックを実行します。今回の場合、先ほどコメントアウトした機能をViewControllerに移します。ViewControllerはactioncolumnのaddボタンおよびdeleteボタンやカラムrendreresのハンドラを持ちます。Gid ViewのViewControllerもまたSencha Cmdにより生成されます。
- Ext JS 3.4 のchangeファンクションをGridController.jsファイルに移し、columnRendererにリネームします
- Ext JS 3.4 のpctChangeファンクションをGridController.jsファイルに移し、 pctColumnRendererにリネームします
- actioncolumnの削除ボタンのhandlerファンクションをGridController.jsに移動し、sellStockにリネームします。コード内のストアへのレファレンスをgrid.getStore()に変更します。
- actioncolumnの追加ボタンのhandlerファンクションをGridController.jsに移動し、buyStockにリネームします。コード内のストアへのレファレンスをgrid.getStore()に変更します。
- Gid View内の当該機能へのリファレンスを加え、renderer: ‘usMoney’ をrenderer: Ext.util.Format.usMoneyに置き換えます:
- ブラウザでhttp://localhost:1841/ に移動し、アプリケーションが実行されていることを確かめます。以下のような画面が確認できるかと思います:
上記の手順を終えると、GridController.jsはこのような具合になっていると思います:
Ext.define('Migration.view.company.GridController', { extend: 'Ext.app.ViewController', alias: 'controller.company-grid', columnRenderer : function(val) { if (val > 0) { return '<span style="color:green;">' + val + '</span>'; } else if (val < 0) { return '<span style="color:red;">' + val + '</span>'; } return val; }, pctColumnRenderer : function (val) { if (val > 0) { return '<span style="color:green;">' + val + '%</span>'; } else if (val < 0) { return '<span style="color:red;">' + val + '%</span>'; } return val; }, sellStock: function(grid, rowIndex, colIndex) { var rec = grid.getStore().getAt(rowIndex); alert("Sell " + rec.get('company')); }, buyStock: function(grid, rowIndex, colIndex) { var rec = grid.getStore().getAt(rowIndex); alert("Buy " + rec.get('company')); } }); |
Ext.define("Migration.view.company.Grid",{ extend: "Ext.grid.Panel", requires: [ "Migration.view.company.GridController", "Migration.view.company.GridModel" ], xtype: 'companygrid', controller: "company-grid", viewModel: { type: "company-grid" }, bind: { store: '{companies}' }, columns: [ { id :'company', header : 'Company', flex : 1, sortable : true, dataIndex: 'company' }, { header : 'Price', width : 75, sortable : true, renderer : Ext.util.Format.usMoney, dataIndex: 'price' }, { header : 'Change', width : 75, sortable : true, renderer : 'columnRenderer', dataIndex: 'change' }, { header : '% Change', width : 75, sortable : true, renderer : 'pctColumnRenderer', dataIndex: 'pctChange' }, { header : 'Last Updated', width : 85, sortable : true, renderer : Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange' }, { xtype: 'actioncolumn', width: 50, items: [{ iconCls: 'x-fa fa-minus', tooltip: 'Sell stock', handler: 'sellStock' }, { iconCls: 'x-fa fa-plus', handler: 'buyStock' } ] } ], stripeRows: true, height: 350, width: 600, title: 'Array Grid', stateful: true, stateId: 'grid' }); |
まとめ
このガイドはExt JS 3.4 からExt JS 6 にマイグレートするためのサンプルです。詳しくはアップグレードガイドに記載しています (英語):