はじめに
HTML+JavaScript でアプリ開発するにあたって、UI ライブラリをどうするか悩みました。モバイルアプリは Onsen UI がよさそうです。
Onsen UI は、当初 AngularJS をベースにしたフレームワークでしたが、2 になって別のフレームワークと一緒に使うのが容易になりました。
以前に Backbone.js+Marionette.js と一緒に使ってみましたが、簡単なアプリでも記述量が少なくありません。そこで、Vue.js と一緒に使ってみたいと思います。
参考
- [Onsen UI+Backbone.js+Marionette.js を使ってみた - Qiita]
(http://qiita.com/tinymouse/items/7b1166c7a0f7c2aa3d29) - [Vue.js を使ってみた - Qiita]
(http://qiita.com/tinymouse/items/d3eceea8d0b4237221f5)
Onsen UI+Vue.js を使ってみた
- Onsen UI 2.0
- Vue.js 2.0
Onsen UI を使う
以下のページからダウンロードします。
Onsen UI 2
(https://onsen.io/v2/)
以下のファイルをワークスペースにコピーします。
lib
onsen
css
onsenui.css
onsen-css-components.css
js
onsenui.js
使用します。
<link href="lib/onsen/css/onsenui.css" rel="stylesheet" />
<link href="lib/onsen/css/onsen-css-components.css" rel="stylesheet" />
<script src="lib/onsen/js/onsenui.js" type="text/javascript"></script>
Onsen UI は独自タグを使います。
<ons-navigator id="main-navi" page="list-page">
</ons-navigator>
<ons-template id="list-page">
<ons-page id="list-page">
<ons-toolbar id="list-toolbar">
<div class="center">Try Onsen+Vue.js</div>
</ons-toolbar>
<ons-list id="contact-list">
</ons-list>
</ons-page>
</ons-template>
ツールバーがついたリストビューが本体のページを準備します。
JavaScript のコードは ons.ready()
関数に記述します。
ons.ready(function(){
....
});
ドキュメント
- Onsen UI 2 Docs - Onsen UI Framework (https://onsen.io/v2/docs/js.html)
Vue.js を使う
以下のページからダウンロードします。
Vue.js
(https://jp.vuejs.org/)
以下のファイルをワークスペースにコピーします。
lib
vue.js
使用します。
<script src="lib/vue.js"></script>
データを準備する
まず、データを準備します。
Vue.js で扱うデータは通常の JavaScript オブジェクトです。ただし配列データを扱うときは、配列そのものでなく、配列データを含むオブジェクトを用意します。
var app = {};
app.data = {
contacts: [
{
firstName: "Alice", lastName: "Arten", hasPhone: true, phoneNumber: "555-0184"
},{
firstName: "Bob", lastName: "Brigham", hasPhone: true, phoneNumber: "555-0163"
},{
firstName: "Charlie", lastName: "Campbell", hasPhone: true, phoneNumber: "555-0129"
}
]
};
データをレンダリングする
<ons-list id="content-list">
にデータがリスト表示されるようテンプレートを追記します。
リストを表示するので v-for
ディレクティブを使います。
<ons-list id="contact-list">
<template v-for='item in list'>
<ons-list-item>
<div class="center">{{ item.firstName }} {{ item.lastName }}</div>
</ons-list-item>
</template>
</ons-list>
Vue のインスタンスを生成します。
new Vue({
el: '#contact-list',
data: {
list: app.data.contacts
}
});
これをいつ実行すべきかですが、
el に指定する <ons-list id="contact-list">
が生成されてからでないといけません。
これが含まれる <ons-page id="list-page">
が生成されるタイミングで実行するにはこうします。↓
$(document).on('init', '#list-page', function(){
console.log("#list-page.init");
new Vue({
el: '#contact-list',
data: {
list: app.data.contacts
}
});
});
※ el に指定するのに Vue.js 1.0 では <ons-page id="list-page">
でも問題なかったのですが、Vue.js 2.0 では問題あります。
この記述するために、jQuery を使います。
<script src="lib/jquery.js"></script>
ユーザ入力をハンドリングする
リストビューに「編集」と「削除」ボタンを追加します。
v-on
ディレクティブで実行したいメソッドを指定します。
<template v-for="item in list">
<ons-list-item>
中略
<div class="right">
<ons-button modifier="quiet" v-on:click='showDetail(item)'><ons-icon icon="pencil"></ons-icon></ons-button>
<ons-button modifier="quiet" v-on:click='deleteItem(item)'><ons-icon icon="trash-o"></ons-icon></ons-button>
</div>
実行するメソッドを設定します。
new Vue({
el: '#contact-list',
中略
methods: {
showDetail: function(item){
console.log("showDetail:", JSON.stringify(item));
},
deleteItem: function(item){
console.log("deleteItem:", JSON.stringify(item));
},
}
});
データを削除できるようにする
new Vue({
中略
methods: {
deleteItem: function(item){
中略
var index = this.list.indexOf(item);
this.list.splice(index, 1);
}
データを操作しただけだが、画面も変わります。
※ Vue.js 1.0 では、配列から指定したデータを削除するのに、$remove()
メソッドを使いましたが、Vue.js 2.0 ではこれがなくなりました。
詳細ページを表示する
<ons-template id="detail-page">
<ons-page id="detail-page">
<ons-toolbar>
<div class="left"><ons-back-button>Back</ons-back-button></div>
<div class="center">Detail</div>
</ons-toolbar>
<ons-list id="detail">
<ons-list-item>
<ons-input placeholder="First Name"></ons-input>
</ons-list-item>
<ons-list-item>
<ons-input placeholder="Last Name"></ons-input>
</ons-list-item>
</ons-list>
</ons-page>
</ons-template>
new Vue({
中略
methods: {
showDetail: function(item){
中略
document.querySelector('#main-navi').pushPage('detail-page');
},
$(document).on('init', '#detail-page', function(){
console.log("#detail-page.init");
});
Onsen UI の機能でページ遷移します。
詳細ページに内容を表示する
new Vue({
中略
methods: {
showDetail: function(item){
中略
app.selected = item;
document.querySelector('#main-navi').pushPage('detail-page');
},
app オブジェクトに selected を追加して別のページに渡せるようにします↑
<ons-template id="detail-page">
中略
<ons-list id="detail">
<ons-list-item>
<ons-input v-model='firstName' placeholder="First Name"></ons-input>
</ons-list-item>
<ons-list-item>
<ons-input v-model='lastName' placeholder="Last Name"></ons-input>
</ons-list-item>
データバインディングのため v-model
ディレクティブを追記します。↑
new Vue({
el: '#detail',
data: app.selected,
});
新たに Vue のインスタンスを生成します。data プロパティに app.selected をセットします。↑
これを実行するのもページ初期化のタイミングです。
$(document).on('init', '#detail-page', function(){
中略
new Vue({
el: '#detail',
data: app.selected,
});
});
※ el に指定するのに Vue.js 1.0 では <ons-page id="detail-page">
でも問題なかったのですが、Vue.js 2.0 では問題あります。
これで app.selected の内容が画面に表示される・・はずですが、
そうなりません。
v-model
は <input>
でないと使えないようです。
なので <ons-input>
を <input>
に書換します。
<ons-template id="detail-page">
中略
<ons-list id="detail">
<ons-list-item>
<input v-model='firstName' class="text-input" placeholder="First Name">
</ons-list-item>
<ons-list-item>
<input v-model='lastName' class="text-input" placeholder="Last Name">
</ons-list-item>
双方向データバインディングする
app.selected の内容が画面に表示されるだけでなく、
画面の要素の内容で app.selected の内容が更新されるようになります。
app.selected はポインタです。値がコピーされているわけではありません。
詳細画面の要素の内容を変えると、app.data の内容も変わっています。
さらに、リストビューの画面の内容も変わっています。
Vue.js のおかげで、双方向データバインディングが簡単にできます。
カスタムディレクティブを使う
v-model
は <input>
でないと使えないので <ons-input>
を <input>
に書換しましたが、不本意です。
Vue.js のカスタムディレクティブを使えばいいのではないかと、試してみました。
Vue.directive('model-ex', {
bind: function(el, binding, vnode){
this.handler = function(){
Vue.set(vnode.context, binding.expression, el.value);
}.bind(this);
el.addEventListener('input', this.handler);
el.value = binding.value;
},
unbind: function(el){
el.removeEventListener('input', this.handler);
},
update: function(el, binding){
el.value = bindig.value;
}
});
※ Vue.js 1.0 から 2.0 になったため書換しました。
<ons-template id="detail-page">
中略
<ons-list-item>
<ons-input v-model-ex='firstName' placeholder="First Name"></ons-input>
</ons-list-item>
<ons-list-item>
<ons-input v-model-ex='lastName' placeholder="Last Name"></ons-input>
</ons-list-item>
データを追加できるようにする
ツールバーに「+」ボタンを追加して、データを追加できるようにします。
<ons-template id="list-page">
<ons-page id="list-page">
<ons-toolbar id="list-toolbar">
中略
<div class="right">
<ons-toolbar-button v-on:click='addItem'><ons-icon icon="fa-plus"></ons-icon></ons-toolbar-button>
</div>
new Vue({
el: '#list-toolbar',
methods: {
addItem: function(){
console.log("addItem:");
var contact = {
firstName: "", lastName: "", hasPhone: false, phoneNumber: ""
};
app.data.contacts.push(contact);
app.selected = contact;
document.querySelector('#main-navi').pushPage('detail-page');
}
※ el に指定するのに Vue.js 1.0 では <ons-page id="list-page">
でも問題なかったのですが、Vue.js 2.0 では問題あります。
データが保存されるようにする
変更したり追加したりしたデータが、実行し直すとなくなってしまいます。
localStorage を使って保存できるようにします。
- JavaScriptプログラミング講座【ウェブストレージについて(Web Storage)】
(http://hakuhin.jp/js/storage.html)
起動時に読込します↓
app.data = JSON.parse(localStorage.getItem('data')) || app.data;
いつ保存すればいいでしょうか。
new Vue({
el: '#list-page',
中略
methods: {
deleteItem: function(item){
中略
localStorage.setItem('data', JSON.stringify(app.data));
},
データを削除したとき↑
$(document).on('destroy', '#detail-page', function(){
console.log("#detail-page.destroy");
localStorage.setItem('data', JSON.stringify(app.data));
})
詳細画面から一覧画面に戻るとき↑