Alloyとは?
今回はAppcelerator社公式フレームワークであるAlloyを用いたモダンな開発スタイルへTODOアプリをポーティングするところまでを解説します。
AlloyとはAppcelerator社公式のMVCフレームワークとなります。コントローラとモデルはJavaScriptで、ビューはXML、ビューの修飾はCSSと似たようなTSS(Titanium Style Sheets)で記述していきます。
開発をする上での注意点
Resources配下のファイルは自動で生成されます
Alloyで開発する場合は、プロジェクトフォルダの直下にappというフォルダが作られます。この中にcontrollersやviews,stylesやmodelsといった配置となっております。ビルドをおこなうと、AlloyはこれらのファイルをコンパイルしてResourcesへ自動生成します。つまり、いくらResources配下のファイルを編集しても、ビルドをする度に書き換えられてしまいます。
Alloyオブジェクトとは?
ビュー内で定義されたUIパーツは、Alloyオブジェクトとしてコントローラからアクセスすることができます。ビューに書く際、id="someView"
で名前を定義しておきます。コントローラでは$.someView
でそのUIパーツにアクセスすることが可能になります。
index.xml
1 | < View id = "someView" /> |
index.js
1 2 3 | $.someView.applyProperties({ width: 100 }); |
.tssファイルの中で演算処理等は行えません
画面サイズからラベルの横幅を計算した場合は、予めapp/alloy.js内に定義しておくと良いでしょう。Alloy.Globals
は各コントローラで共通に使いたい関数を、Alloy.CFG
には共通で使いたい値などを定義しておくと、アプリ内で容易に使いまわすことができます。
1 2 3 4 5 | Alloy.Globals.someGlobalFunction = function (){ }; Alloy.Globals.someModule = require( 'somemodule' ); Alloy.CFG.someWidth = Ti.Platform.displayCaps.platformWidth - 100; |
このような定義をすることができ、.tss内では以下のように参照することができます。
1 2 3 | '#someView' : { width: Alloy.CFG.someWidth } |
builtinsを積極的に利用しましょう
Alloyにはよく使うような確認用ダイアログを表示したり、アニメーションの制御や便利なライブラリが予め組み込まれています。こちらはAlloyレポジトリのbuiltinsにソースコードがあり、その中で使い方のサンプルが紹介されていますのでぜひご覧ください。今回は日付ライブラリのMoment.jsと確認用ダイアログを表示するDialogs.jsを利用します。
1 2 | var Moment = require( 'alloy/moment' ), Dialogs = require( 'alloy/dialogs' ); |
また、Dialogsについては確認用のダイアログ表示となり、「OK」「キャンセル」ボタンが表示されてしまうので、今回「OK」ボタンのみのAlert.jsを私の方で用意しました。こちらはassets/alloy/alert.jsに入っております。このように自分で似たようなものを作ることも可能です。
1 | var Alert = require( 'alloy/alert' ); |
画像等のファイルはassetsへ
アプリ内で使う画像等ファイルはassetsへ配置します。Alloyのコンパイルが走ると、assets内のファイルはResources直下へ配置されます。例えば、assets/images/someImage.pngは以下のように参照することができます。
1 2 3 | var imageView = Ti.UI.createImageView({ image: '/images/someImage.png' }); |
アプリを作ってみよう
前回のTODOアプリをAlloy化したものを私のGitHubレポジトリに用意しておきました。まずはこちらをダウンロードして動かしてみてください。ダウンロードした.zipファイルを適当な場所で解凍します。Titanium StudioのFile→ImportからTitaniumのExisting Mobile Projectを選択し、Project directoryで解凍したフォルダを選択してください。これでTitanium Studioでこのアプリを扱うことができるようになります。
ファイル構造
app配下にアプリのソースが格納されています。alloy.jsがエントリーポイントとなり、Alloyを利用したTitanium製のアプリはここから処理が実行されます。
alloy.js
開発する上での注意点でも説明しましたが、アプリ内で共通して使いまわしたい関数や値を定義します。今回のアプリでは、iOS用のTi.UI.TextField
の横幅と、後述するモデルの集合体であるコレクションを定義してあります。
1 2 3 | Alloy.CFG.TextFieldWidth = Ti.Platform.displayCaps.platformWidth - 59; Alloy.Collections.todos = Alloy.createCollection( 'todos' ); |
controllers/index.js
Alloyを用いたアプリでは必ず存在しなければならないファイルとなります。alloy.jsは定義、controllers/index.jsは実際のアプリの処理を記述していくエントリーポイントと思ってください。
今回はビューからのイベントを受け付ける処理や、後述する、タスクのリストを生成するためのData Bindingの処理が書かれています。前回のクラシックスタイルでは、iOS用・Android用でファイルを分けて同じような処理を書いていましたが、今回はこの1ファイルのみとなります。
どうしてもプラットフォームに依存した処理を書かなければならない時は、Alloyに予め定義されているOS_IOS
やOS_ANDROID
で処理を分岐させることができます。
1 2 3 4 5 | if (OS_IOS) { // iOS用の処理 } else if (OS_ANDROID) { // Android用の処理 } |
コントローラの中に、Ti.UI.XXXXX
系のUIパーツを作るような処理を書くことは避けたほうが良いでしょう。折角のMVCですから、UIパーツはビューの中に記述するべきです。
ただし、Alloyではまだサポートしていないけれど、TitaniumではサポートしているUIパーツを使いたい場合はどうしようもありません。執筆時でのAlloyはバージョン1.3.1ですが、前回iOSで利用したTi.UI.RefreshControl
はバージョン1.4.0から利用が可能になるようですので、以下のように記述していきます。
1 2 3 4 5 6 7 8 9 10 | if (OS_IOS) { var control = Ti.UI.createRefreshControl(); control.addEventListener( 'refreshstart' , function (){ // 更新処理 }); // ビューで定義されたtableというTi.UI.TableViewへTi.UI.RefreshControlを使えるようにする $.table.applyProperties({ refreshControl: control }); } |
views/index.xml
画面をxmlでマークアップしていきます。iOSとAndroidでは画面自体は決定的に異なりますので、ここではファイルを分けてあります。Androidのindex.xmlについては、views/android/index.xmlへ記述してあります。Alloyはプラットフォームを自分で判断して適切なビューを読み込んでくれますので、この辺りの処理は快適に記述できるかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | < Alloy > < NavigationWindow platform = "ios" > < Window class = "container" title = "myTODO" > < TableView id = "table" dataCollection = "todos" dataTransform = "doTransform" dataFilter = "doFilter" > < HeaderView > < View id = "addtask" > < TextField id = "textfield" hintText = "New task" /> < Button id = "addbutton" onClick = "doAddtask" >登録</ Button > </ View > </ HeaderView > < TableViewRow taskid = "{id}" > < Label id = "task" text = "{task}" /> < Label id = "created_at" text = "{created_at}" /> </ TableViewRow > </ TableView > < View id = "footer" > < TabbedBar id = "tab" onClick = "doTab" > < Labels > < Label >未完了</ Label > < Label >完了</ Label > < Label >全て</ Label > </ Labels > </ TabbedBar > </ View > </ Window > </ NavigationWindow > </ Alloy > |
HTMLを書かれた方ならぱっと見で大体把握できるかと思います。各UIパーツのタグはTi.UI.create
を書く必要はありません。コントローラ内で操作したいUIパーツはid
属性が定義されています。Ti.UI.TableView
にどうやってタスクのリストが入るのか?は後述のモデルとあわせて解説します。
ここで一つ残念なお知らせです。前回のクラシックスタイルでは、Ti.UI.iOS.TabbedBar
をTi.UI.iOS.Toolbar
の中へ入れて記述しましたが、Alloyでそのようにマークアップすると、ビルド時にエラーとなってしまいます。
Alloyがビルドした結果のJSファイルを見てみましたが、なぜかTi.UI.iOS.TabbedBar
のlabels
がTi.UI.iOS.Toolbar
のプロパティにも定義されてしまっている状態でした。
今回はTi.UI.View
でそれっぽくしましたが、このように、Alloyのコンパイルに引っかかってしまう点もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | < Toolbar > < Items > < FlexSpace /> < TabbedBar > < Labels > < Label >ラベル</ Label > < Label >ラベル</ Label > < Label >ラベル</ Label > </ Labels > </ TabbedBar > < FlexSpace /> </ Items > </ Toolbar > |
また、ビューを一つのファイルでiOSとAndroidを分けて記述する方法もあります。ビューパーツにplatform
属性を付与することで、そのプラットフォームのみで使うことを宣言できます。複雑になりやすいので、ある程度の量があるようでしたら前述の通り、思い切ってファイルを分けてしまったほうが良いでしょう。
1 2 | < Label platform = "ios" >このラベルはiOSだけ</ Label > < Label platform = "android" >このラベルはAndroidだけ</ Label > |
styles/index.tss
Titanium Style Sheets、.tssファイルでUIパーツを修飾します。HTMLに対してのCSSだと思っていただいて問題ございません。
この記述はCSSでいうところのタイプセレクタになります。
1 2 3 4 | 'TableView' : { width: Ti.UI.FILL, height: Ti.UI.FILL } |
こちらはIDセレクタになります。
1 2 3 4 | '#someId' : { width: Ti.UI.FILL, height: Ti.UI.FILL } |
こちらはクラスセレクタですね。
1 2 3 4 | '.someClass' : { width: Ti.UI.FILL, height: Ti.UI.FILL } |
models/todos.js
モデルは自動で生成されますので、ほとんど手を加えることはありません。今回はデータの保存にSQLiteを利用していますので、初期値やNOT NULL制約を定義するために少しだけ手を加えてあります。
それではAlloyを利用することの大きなメリットであるData Bindingの説明をしていきます。
今回はTODOアプリということで、タスク(モデル)の集合体(コレクション)をTi.UI.TableView
へ紐付けて、タスクの一覧表示を簡単に生成しています。
views/index.xml
1 2 3 4 5 6 | < TableView id = "table" dataCollection = "todos" dataTransform = "doTransform" dataFilter = "doFilter" > < TableViewRow taskid = "{id}" > < Label id = "task" text = "{task}" /> < Label id = "created_at" text = "{created_at}" /> </ TableViewRow > </ TableView > |
dataCollection
でコレクションを指定します。今回のコレクション名はtodos
になりますので、dataCollection="todos"
となります。
dataTransform
でデータの加工を行います。コントローラにdoTransform
という関数が定義されていますので、dataTransform="doTransform"
となります。
今回は日付を見やすいフォーマットに整形するために利用しています。
1 2 3 4 5 | function doTransform(_model) { var json = _model.toJSON(); json.created_at = Moment(json.created_at).format( 'YYYY-MM-DD HH:mm' ); return json; } |
dataFilter
で表示するデータにフィルタを掛けます。コントローラにdoFilter
という関数が定義されていますので、dataFilter="doFilter"
となります。TODOアプリではタスクが「未完了」「完了」「全て」の3つの状態で表示を分けていますので表示を振り分けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function doFilter(_collection) { return _collection.filter( function (_predicate){ var json = _predicate.toJSON(); if (json.deleted_at !== '0000-00-00 00:00:00' ) { // 削除日付が入っていたら問答無用でfalseを返却 return false ; } else if (done === 2) { // doneが2(すべて表示)の場合は問答無用でtrueを返却 return true ; } else { return parseInt(json.done, 10) === done; } }); } |
{id}
、{task}
、{created_at}
へ最終的にタスクのデータが挿入され、コレクションの個数分Ti.UI.TableViewRow
が生成されるというわけです。
最後に
Alloyでの開発スタイルはどうでしたか?今回私がサンプルのアプリを書いた時間はiOSで3時間程度で、そこからAndroidの調整は30分程度で終わりました。
クラシックスタイルではソースコードがiOSとAndroidで完全に分離していましたので、どちらかのコードを修正すれば同じようにもう片方にも反映させる、といった煩わしい作業も発生しづらくなるかと思います。また、UIパーツの配置や修飾も完全にファイルが分離していますので、コードの見通しも良いかと思います。
コントローラにおいては、ほぼイベント処理だけです。
第3回目はこのTODOアプリでデータを保存しているSQLiteの部分をTitanium Cloud Serviceへ乗せ換えます。簡単にmBaaSへ触れられるかと思いますので、ぜひご期待ください。