Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

はじめに

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));
})

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

tinymouse
SI 企業の SE であり、日曜プログラマであり、二児の父。
https://tinymouse.hatenadiary.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした