Onsen UI+Vue.js を使ってみた

  • 23
    Like
  • 0
    Comment

はじめに

HTML+JavaScript でアプリ開発するにあたって、UI ライブラリをどうするか悩みました。モバイルアプリは Onsen UI がよさそうです。
Onsen UI は、当初 AngularJS をベースにしたフレームワークでしたが、2 になって別のフレームワークと一緒に使うのが容易になりました。
以前に Backbone.js+Marionette.js と一緒に使ってみましたが、簡単なアプリでも記述量が少なくありません。そこで、Vue.js と一緒に使ってみたいと思います。

参考

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

使用します。

app.html
<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 は独自タグを使います。

app.html
<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+VueJS</div>
        </ons-toolbar>
        <ons-list id="contact-list">
        </ons-list>
    </ons-page>
</ons-template>

ツールバーがついたリストビューが本体のページを準備します。

JavaScript のコードは ons.ready() 関数に記述します。

app.js
ons.ready(function(){
    ....
});

ドキュメント

Vue.js を使う

以下のページからダウンロードします。

Vue.js
(https://jp.vuejs.org/)

以下のファイルをワークスペースにコピーします。

lib
    vue.js

使用します。

app.html
<script src="lib/vue.js"></script>

データを準備する

まず、データを準備します。
Vue.js で扱うデータは通常の JavaScript オブジェクトです。ただし配列データを扱うときは、配列そのものでなく、配列データを含むオブジェクトを用意します。

app.js
var app = {};
app.data = {
    contacts: [
        { 
            firstName: 'Alice', lastName: 'Arten', hasPhone: true, phoneNumber: '555-0184' 
        },{ 
            firstName: 'Bob', lastName: 'Brigham', hasPhone: false, phoneNumber: '555-0163' 
        },{ 
            firstName: 'Charlie', lastName: 'Campbell', hasPhone: true, phoneNumber: '555-0129' 
        }
    ]
};

データをレンダリングする

<ons-list id="content-list"> にデータがリスト表示されるようテンプレートを追記します。
リストを表示するので v-for ディレクティブを使います。

app.html
    <ons-list id="contact-list">
        <template v-for='contact in contacts'>
            <ons-list-item>
                <ons-row>
                    <ons-col>{{ contact.firstName }} {{ contact.lastName }}</ons-col>
                </ons-row>
            </ons-list-item>
        </template>        
    </ons-list>

Vue のインスタンスを生成します。

app.js
new Vue({
    el: '#contact-list',
    data: app.data,
});

これをいつ実行すべきかですが、
el に指定する <ons-list id="contact-list"> が生成されてからでないといけません。
これが含まれる <ons-page id="list-page"> が生成されるタイミングで実行するにはこうします。↓

app.js
$(document).on('init', '#list-page', function(){
    console.log("#list-page.init");
    new Vue({
        el: '#contact-list',
        data: app.data,
    });
});

※ el に指定するのに Vue.js 1.0 では <ons-page id="list-page"> でも問題なかったのですが、Vue.js 2.0 では問題あります。

この記述するために、jQuery を使います。

app.html
<script src="lib/jquery.js"></script>

ユーザ入力をハンドリングする

リストビューに「編集」と「削除」ボタンを追加します。
v-on ディレクティブで実行したいメソッドを指定します。

app.html
    <template v-for='contact in contacts'>
        <ons-list-item>
            <ons-row>
                中略
                <ons-col>
                    <ons-button modifier="light" v-on:click="showDetail(contact)">
                        <ons-icon icon="pencil"></ons-icon></ons-button>
                    <ons-button modifier="light" v-on:click="deleteItem(contact)">
                        <ons-icon icon="trash-o"></ons-icon></ons-button>
                </ons-col>

実行するメソッドを設定します。

app.js
new Vue({
    el: '#contact-list',
    中略
    methods: {
        showDetail: function(contact){
            console.log("showDetail:");
        },
        deleteItem: function(contact){
            console.log("deleteItem:");
        },
    }
});

データを削除できるようにする

app.js
new Vue({
    中略
    methods: {
        deleteItem: function(contact){
            中略
            var index = this.contacts.indexOf(contact);
            this.contacts.splice(index, 1);
        }

データを操作しただけだが、画面も変わります。

※ Vue.js 1.0 では、配列から指定したデータを削除するのに、$remove() メソッドを使いましたが、Vue.js 2.0 ではこれがなくなりました。

詳細ページを表示する

app.html
<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="FirstName"></ons-input>
            </ons-list-item>
            <ons-list-item>
                <ons-input placeholder="LastName"></ons-input>
            </ons-list-item>
        </ons-list>
    </ons-page>
</ons-template>
app.js
new Vue({
    中略
    methods: {
        showDetail: function(contact){
            中略
            document.querySelector('#main-navi').pushPage('detail-page'); 
        },

$(document).on('init', '#detail-page', function(){
    console.log("#detail-page.init");
});

Onsen UI の機能でページ遷移します。

詳細ページに内容を表示する

app.js
new Vue({
    中略
    methods: {
        showDetail: function(contact){
            中略
            app.selected = contact;
            document.querySelector('#main-navi').pushPage('detail-page'); 
        },

app オブジェクトに selected を追加して別のページに渡せるようにします↑

app.html
<ons-template id="detail-page">
    中略
        <ons-list id="detail">
            <ons-list-item>
                <ons-input v-model="firstName" placeholder="FirstName"></ons-input>
            </ons-list-item>
            <ons-list-item>
                <ons-input v-model="lastName" placeholder="LastName"></ons-input>
            </ons-list-item>

データバインディングのため v-model ディレクティブを追記します。↑

app.js
new Vue({
    el: '#detail',
    data: app.selected,
});

新たに Vue のインスタンスを生成します。data プロパティに app.selected をセットします。↑
これを実行するのもページ初期化のタイミングです。

app.js
$(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> に書換します。

app.html
<ons-template id="detail-page">
    中略
        <ons-list id="detail">
            <ons-list-item>
                <input v-model="firstName" class="text-input" placeholder="FirstName">
            </ons-list-item>
            <ons-list-item>
                <input v-model="lastName" class="text-input" placeholder="LastName">
            </ons-list-item>

双方向データバインディングする

app.selected の内容が画面に表示されるだけでなく、
画面の要素の内容で app.selected の内容が更新されるようになります。

app.selected はポインタです。値がコピーされているわけではありません。
詳細画面の要素の内容を変えると、app.data の内容も変わっています。

さらに、リストビューの画面の内容も変わっています。

Vue.js のおかげで、双方向データバインディングが簡単にできます。

カスタムディレクティブを使う

v-model<input> でないと使えないので <ons-input><input> に書換しましたが、不本意です。
Vue.js のカスタムディレクティブを使えばいいのではないかと、試してみました。

app.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 になったため書換しました。

app.html
<ons-template id="detail-page">
      中略
            <ons-list-item>
                <ons-input v-model-ex="firstName" placeholder="FirstName"></ons-input>
            </ons-list-item>
            <ons-list-item>
                <ons-input v-model-ex="lastName" placeholder="LastName"></ons-input>
            </ons-list-item>

データを追加できるようにする

ツールバーに「+」ボタンを追加して、データを追加できるようにします。

app.html
<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>
app.js
new Vue({
    el: '#list-toolbar',
    中略
    methods: {
        addItem: function(){
            console.log("addItem:");
            var contact = { firstName: "", LastName: "", hasPhone: false, phoneNumber: "" }; 
            this.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 を使って保存できるようにします。

起動時に読込します↓

app.js
    app.data = JSON.parse(localStorage.getItem('data')) || app.data;

いつ保存すればいいでしょうか。

app.js
new Vue({
    el: '#list-page',
    中略
    methods: {
        deleteItem: function(contact){
            this.contacts.$remove(contact);
            localStorage.setItem('data', JSON.stringify(app.data));
        },

データを削除したとき↑

app.js
$(document).on('destroy', '#detail-page', function(){
    console.log("#detail-page.destroy");
    localStorage.setItem('data', JSON.stringify(app.data));
})

詳細画面から一覧画面に戻るとき↑