Alloy Modelの学習
実は、これまではAlloyといってもView/Controllerしか使っておらず、データベースを使う場合も Titanium.Database
を使ってしまっていたので、ちゃんとModelを活用したことがありませんでした。
というわけで、今更ながらにAlloy Modelを触り始めてみました。
今回作るもの
Alloyの基本的な動きを把握するため、SQLiteデータベースを使う、簡単なメモツールのようなものを作りました。
ソースは umi-uyura/AlloyModelStudy です。
仕様的なもの
- シンプルに入力欄とリストのみ
- 入力欄に入力したテキストを、順番にリストに登録していくだけ
- リストの項目をタップすると、操作メニューを表示
- 操作メニューからは、お気に入り登録(するとメモの頭に
*
をつける)もしくはメモの削除ができる - アプリを一旦終了して再度起動しても、登録したデータが表示されている
という感じで、Alloy Modelを使ったCRUDとListViewへのData Bindingをざっと触れる想定です。
実装
プロジェクトの作成
CLIベースで開発しているため、ターミナルからiPhoneとAndroidを対象にしたAlloyプロジェクトを作成。
$ titanium create --type app --id com.example.titanium.alloymodelstudy --name AlloyModelStudy --platforms iphone,android --url http://www.example.com --workspace-dir . $ alloy new AlloyModelStudy
Modelを作成
メモを保存するためのModelを作成します。
テーブルの構造は超シンプルに以下のような感じとしました。
内容 | カラム | 型 | 主キー | 他 |
---|---|---|---|---|
レコードの識別子 | id | INTEGER | ◯ | AUTOINCREMENT |
メモの内容 | contents | TEXT | - | - |
これをもとに alloy generate model
でModelのコードを生成します。モデル名につづいて sql
を指定することで、SQLite向けの構成になります。
$ alloy generate model memo sql 'id:integer primary key autoincrement' contents:text
プロジェクト内の app/models/memo.js に生成されました。
生成されたコードに1点手を加える必要があり、テーブル内のレコードを識別するためのカラムを設定するために、 config.adapter.idAttribute
を追加して id
を指定しました。
できあがったモデルのコードは以下のような感じ。
app/models/memo.js
exports.definition = { config: { columns: { "id": "integer primary key autoincrement", "contents": "text" }, adapter: { type: "sql", collection_name: "memo", idAttribute: "id" } }, extendModel: function(Model) { _.extend(Model.prototype, { // extended functions and properties go here }); return Model; }, extendCollection: function(Collection) { _.extend(Collection.prototype, { // extended functions and properties go here // For Backbone v1.1.2, uncomment the following to override the // fetch method to account for a breaking change in Backbone. /* fetch: function(options) { options = options ? _.clone(options) : {}; options.reset = true; return Backbone.Collection.prototype.fetch.call(this, options); } */ }); return Collection; } };
UIを実装
私のふだんのAlloy開発スタイルどおり、UIはJade&Stylusで作っています。もしプロジェクトを動かす方がいたら、JadeとStylusをグローバルにインストールしておいてください。
また、iPhoneとAndroid両方でナビゲーションバー/アクションバーを表示するために、Fokke Zandbergen氏が作っている xp.ui を使っています。これは、本来iOSでしか使えないNavigationWindowのダミーを用意することで、Androidとコードを共通化できる便利なライブラリです。
そんな感じで、スタイルの方はソースを見ていただくとして、Viewとなる index.jade は以下のようにしました。
app/view/index.jade
Alloy Collection(src="memo")/ NavigationWindow(module="xp.ui") Window.container(title="Alloy Model Study", onOpen="doOpen") View.controller TextField#memoText Button#addButton(title="Add", onClick="addText") ListView#memoList(onItemclick="clickItem") ListSection(dataCollection="memo") ListItem(title="{contents}", itemId="{id}")/
まずは、このViewで利用するCollectionを Alloy
タグの直下に指定。
Alloy Collection(src="memo")/
さらに、後述するData Bindingのために、ListSectionに利用するCollectionの指定を、またListItemに表示する memo
Modelのフィールドを指定しています。
ListSection(dataCollection="memo") ListItem(title="{contents}", itemId="{id}")/
データベース操作の実装
app/controllers/index.js に、Data Bindingを利用したデータベース操作を実装しました。
レコードの読み込み、ListViewへの表示
アプリ起動時に、データベースからレコードを読み込みます。
これは Collection.fetch()
で読み込むことができるので、Windowの open
イベント内で呼び出しておきます。これによって、画面が開かれたタイミングで、データベースにレコードがあれば、それが読み込まれることになります。
var memos = Alloy.Collections.memo; function doOpen() { memos.fetch(); }
さらに、View内でListSectionに memo
Collectionを紐付けている( dataCollection
)ので、読み込まれたレコード分、そのListSectionのListItemとして生成されます。
ListItemには title
として memo
モデルの contents
フィールドを指定しているので、そこに登録されているレコードがListItemに表示されることになります。
レコードの追加
TextFieldに文字を入力して Add ボタンを押すと、新しいレコードを登録します。
その処理は以下のように実装。
function addText() { var txt = $.memoText.getValue(); var m = Alloy.createModel('memo', { contents: txt }); memos.add(m); m.save(); memos.fetch(); $.memoText.setValue(''); }
新しいレコードを作成するには、 Alloy.createModel
で新しいModelオブジェクトを生成。
そのModelオブジェクトをCollectionにを追加して Model.save()
を呼び出すことで、データベースにレコードが保存されます。
その後、再び Collection.fetch()
を呼び出すと、追加したレコードに対応するListItemがリストに追加されます。
レコードを編集・更新
編集画面を別途作ろうかと思ったのですが、少々面倒なので中止。かわりに、お気に入りという扱いとして、登録した文字列の先頭に *
を付与するというだけの仕様にしています。
ListViewの click
イベントでOptionDialogを表示して、お気に入り登録か削除かをするようにしました。
どのListItemに対する操作なのかを判別するための情報は、イベントの引数から取得できます。
function clickItem(e) { var secIdx = e.sectionIndex; var itemIdx = e.itemIndex; var itemId = e.itemId; var text = $.memoList.sections[e.sectionIndex].items[e.itemIndex].properties.title; if (OS_ANDROID) { itemId = parseInt(itemId); } ...
ここでひとつハマったのが、レコードのキーとなる itemId
。
memo
modelの id
を紐付けているのですが、元の id
は数値型( integer
)にしていたのに対して、 itemId
はAPIドキュメントによると文字列型( string
)でした。
この点、iOSではうまいこと型変換してくれているらしく問題はなかったのですが、Androidに関してはドキュメントどおり文字列型で取れるため、このあとの Collection.where
で型不一致のせいかデータが取れないという問題がありました。
もしかしたら、数値型IDは itemId
として使わないほうが良いのかもしれませんが、今回はAndroidの場合 parseInt
することで対処しています。
その後、既存レコードを編集します。
var m = memos.where({id: itemId}); if (m.length) { var contents = '*' + m[0].get('contents'); m[0].set({contents: contents}); m[0].save(); }
Collection.where()
は配列を返すので、 Array.length
で存在を確認してから処理を実施。
変更内容を Model.set()
して、 Model.save()
を呼ぶことで、Data Bindingのおかげで、すぐに変更がリストにも反映されます。
ちなみに、Alloy Modelの仕組みのベースになっているのはBackbone.jsですが、Alloyに含まれているBackbone.jsは 0.9.2
と古いバージョンです。このBackbone.jsのバージョンを 1.1.2
にすると単独のModelを返す Collection.findWhere()
が使えるようになるので、上記のようなわかりにくさが解消できそうです。
Backbone.jsのバージョンを変える方法は Alloy Backbone Migration を参照。
レコードを削除
レコードの削除処理は以下のように実装。
var dialogs = require('alloy/dialogs'); ... dialogs.confirm({ title: 'Do you want to delete ?', message: '"' + text + '"', callback: function() { var m = memos.where({id: itemId}); if (m.length) { m[0].destroy(); } } });
削除を確認するダイアログを出したのち、削除を実行します。
レコードの編集と同様の方法で既存レコードを取得し、 Model.destroy()
で、呼出し後リストから該当項目が消えました。
まとめ
簡単なサンプルですが、最低限のAlloy Modelの使い方は理解できたかな、と思います。
今まで使う機会がなかったのですが、やはりData Bindingは便利ですね。
ここ を見ると、ListView(正確にはListSection)以外にもData BindingできるViewはありますが、やはり一番使いどころがあるのはListViewだと思いますし、さらにListItemのテンプレートの仕組みと組み合わせると、いろんな表現ができそう。
Appcelerator Titanium Smartphone App Development Cookbook - Second Edition
- 作者: Jason Kneen
- 出版社/メーカー: Packt Publishing
- 発売日: 2015/11/30
- メディア: Kindle版
- この商品を含むブログを見る