この章を読み進める前に、Onsen UIをはじめようとOnsen UIの基礎に目を通して頂くことを推奨いたします。心配ありません、読み終わるまで5分もかかりません。
Onsen UI向けのVueバインディング(VueOnsen)は、Web Componentsでできたコアをラッピングし、Vue形式のAPIでアクセスできるようにしたものです。
このガイドでは、Onsen UIとVueを組み合わせた場合の開発方法について紹介いたします。
Vueバインディングは、vue-onsenui
パッケージとして配布されています。NPMを用いて下記のようにダウンロードできます。
$ npm install onsenui vue-onsenui --save-dev
他にもCDN経由で使ったり、Onsen UIのリリースに含まれているVueバインディングを使うこともできます。
Onsen UIを<script></script>
や<link>
タグを使って、下記のように読み込めます:
<link rel="stylesheet" href="onsenui.css">
<link rel="stylesheet" href="onsen-css-components.css">
<script src="vue.js"></script>
<script src="onsenui.js"></script>
<script src="vue-onsenui.js"></script>
もしくはWebpackを使う場合は、node_modules/vue-onsenui
からVueOnsen
インポートしてください:
// Webpack CSS import
import 'onsenui/css/onsenui.css';
import 'onsenui/css/onsen-css-components.css';
// JS import
import Vue from 'vue';
import VueOnsen from 'vue-onsenui'; // This already imports 'onsenui'
Vue.use(VueOnsen);
Vue.Use
を用いてVueOnsen
を読み込みます(window.Vue
が存在する場合には、これも自動的に行われます)。ES6以降で備わったImport機能を使用しない場合は、require
を用いて読み込むこともできます。
Vue CLIで使えるテンプレートも提供しています:
$ vue init OnsenUI/vue-cordova-webpack hello-world
$ vue init OnsenUI/vue-pwa-webpack hello-world
これらのテンプレートは、Onsen UIとVueを組み合わせたもので、ユニットテストとE2Eテストのための機能も組み込まれています。
VueコンポーネントAPIリファレンスではOnsen UIに含まれるすべてのVueコンポーネントが一覧で掲載されています。
また、Kitchensinkアプリをご覧いただくと、それぞれのコンポーネントの挙動を確認することができます。こちらからコードをご確認ください。
では簡単なHello Worldアプリを開発してみましょう。下記のコードは、Onsen UIとVueを用いたHello Worldコードの例になります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="onsenui.css">
<link rel="stylesheet" href="onsen-css-components.css">
<script src="vue.js"></script>
<script src="onsenui.js"></script>
<script src="vue-onsenui.js"></script>
</head>
<body>
<template id="main-page">
<v-ons-page>
<v-ons-toolbar>
<div class="center">Title</div>
</v-ons-toolbar>
<p style="text-align: center">
<v-ons-button @click="$ons.notification.alert('Hello World!')">
Click me!
</v-ons-button>
</p>
</v-ons-page>
</template>
<div id="app"></div>
<script>
var vm = new Vue({
el: '#app',
template: '#main-page'
});
</script>
</body>
</html>
ここで<div id="app"></div>
は、Vueがコンテンツをレンダリングする起点となります(コンストラクタでel
プロパティで指定しています)。
これはES5を用いた例ですが、ES2015とvue-loader
を組み合わせると*.vue
ファイルを用いた開発ができるのでお勧めです。
Vueに関する詳細は、公式のVueドキュメントを参照してください。
より深い理解のためにも、Onsen UIとVueコンポーネントの関係について知っておくのが良いでしょう。
これらOnsen UIのVueコンポーネントは、Web ComponentsであるCustom Elementsをラッピングしたものになります。そのため、VueコンポーネントのPropは、Custom ElementsのDOMプロパティや属性、メソッドと対応しています。また、ネイティブイベントを受け取った場合は、Vueイベントを呼び出します。DevTools等でDOMをインスペクトすると、v-*
から始まらないons-*
コンポーネントが見えるでしょう。これらはWeb Componentsでできた、Onsen UIのHTML要素です。詳細の実装については、こちらのコードを参照してください。
v-ons-*
ではじまるコンポーネントは、すべてons-*
からはじまるDOM要素にコンパイルされることから、CSS等でスタイルを定義する場合はタグ名を使用してください。
v-ons-page
コンポーネントはons-page
のカスタム要素にコンパイルされます。この要素はその中に配置された要素を確認し、スクロールされるべき内容と、固定化される内容を判定します。スクロールできる内容はdiv.page__content
ラッパーに配置されます。この場合、たとえばv-for
により非同期的にデータを処理するケースなどで問題になることがあります。そのため、下記のように手動でdiv.content
要素を追加することを推奨します。
<v-ons-page>
<v-ons-toolbar></v-ons-toolbar>
<div class="content">
<!-- スクロール可能なコンテンツ -->
<v-ons-input></v-ons-input>
<div v-for="item in asyncAjaxItems"></div>
</div>
<!-- 固定配置するコンテンツ -->
<v-ons-fab></v-ons-fab>
</v-ons-page>
詳細については、コンポーネントのコンパイルを参照してください。
グローバルで提供されるons
objectオブジェクトは、Vueでは利用できません。その代わりに、すべてのVueインスタンスにはthis.$ons
オブジェクトを利用できます。
<v-ons-button @click="$ons.notification.alert('Hi there!')">
Click me
</v-ons-button>
Onsen UI要素で実行されたDOMイベントは、対応するコンポーネントのVueイベントに変換されます。たとえば、v-ons-navigator
はpostpush
イベントを、@postpush="..."
という形で受け取れます。
`vue@2.4.0と
vue-onsenui@2.1.0以降では、ネイティブのDOM要素が子要素に渡されます。そのため、
といったイベントは
native修飾子を**外して**おく必要があります。以前のバージョンでは、
@change.native=”…”`構文が必要でした。ただし、これらは引き続きDOMイベントのため、Vue DevToolsはVueイベントとして認識しません。
ただし、click
イベントについては、いくつかのコンポーネントではVueイベントとして認識されます。これは、デフォルトの挙動が上書きされているためです。
<v-ons-back-button @click.prevent="pageStack.splice(1)"></v-ons-back-button>
この例では、v-ons-back-button
のデフォルトの振る舞いを変更し、1ページ前に戻った後、先頭ページにリセットを行っています。なお、ここで使われているprevent
修正子は$event.preventDefault()
を呼び出すために使用しています。
Cordovaのbackbutton
イベント(Androidの戻るボタン)に対応しているコンポーネントについては、@deviceBackButton
ハンドラーを使ってイベントを受け取ることができます。
<v-ons-dialog @deviceBackButton="$event.callParentHandler()"></v-ons-dialog>
このイベントに関する詳細は、Cordova特有の機能を使用するを参照してください。
Onsen UIの入力コンポーネント(v-ons-input
やv-ons-checkbox
)では、Vueが提供するv-model
ディレクティブを使用できます。
<v-ons-input v-model="something"></v-ons-input>
ただし、v-model
修正子については、Vue 2.3の段階ではカスタムコンポーネントに対して対応されていません。しかし、最も重要なlazy
修正子について、下記のように回避することができます。
<v-ons-input v-model="something" model-event="change"></v-ons-input>
この例では、input
イベントの代わりにchange
イベントを用いてモデルの更新を行います。残りの修正子も、同じように実装することができます。
また、v-ons-checkbox
やv-ons-switch
などのコンポーネントに対して配列をバインドできます。それにより、<input type=“checkbox”>
と同じように扱うことができます。また、v-ons-radio
にv-model
を使ってグループを作成することもできます。
ガイドのOnsen UIの一部機能を無効にするに記述されている通り、onsenui.js
を読み込んだ直後に設定を行う必要があります。具体的にはコンポーネントを描画するよりも前に実施される必要があります。しかしVueではons
オブジェクトをグローバルで取得できないため、最初に$ons
を受け取れる箇所で設定を行います。
import Vue from 'vue';
import VueOnsen from 'vue-onsenui';
Vue.use(VueOnsen);
new Vue({
el: '#app',
render: h => h(...),
beforeCreate() {
this.$ons.disableAutoStyling(); // Or any other method
}
})
このように設定することで、コンポーネントが描画されるよりも先に設定を適用できます。
ナビゲーターのページは、兄弟関係を持つ要素となります。そのため、Vueを用いた場合のデータの受け渡しには工夫が必要です。Vuexを使用する方法だけでなく、下記のように継承を用いてデータを渡すこともできます。
import nextPage from 'nextPage.vue';
// ...
pageStack.push({
extends: nextPage,
data() {
return {
myCustomDataHere: 42
};
}
})
また、ページが戻す際に前ページにデータを渡したい場合は、Vueのドキュメントにある親子関係を伴わないコミュニケーションが参考になるでしょう。
もちろんです。Vuexを使うと、コンポーネント間の通信が書きやすくなります。多くのPropsがあり、イベントが頻繁に発行されるようなケースではVuexを使うのが良いでしょう。Onsen UIとVuexを組み合わせがサンプルは、キッチンシンクアプリを参考にしてみてください。
Onsen UIでは、v-ons-navigator
、v-ons-tabbar
およびv-ons-splitter
を、ルーティング用のコンポーネントとして提供しています。これらを組み合わせることで、必要なパターンのルーティングを実装できます。SPA形式のモバイルアプリ開発においては、これらのコンポーネントだけで足りるケースが多く、追加でルーターを導入する必要はないかもしれません。
もちろん、vue-routerはOnsen UIと完全に互換性があります。上に挙げた3種類のルーティング用コンポーネントと組み合わせてください。
スプリッターと使う:
v-ons-splitter
と組み合わせる場合は、v-ons-page
コンポーネントが通常設置される場所に、router-view
を配置してください。言い換えると、v-ons-splitter-content
やv-ons-splitter-side
の内部に、ルーター用ビューを配置します。
<v-ons-splitter>
<v-ons-splitter-side>...</v-ons-splitter-side>
<v-ons-splitter-content>
<router-view></router-view>
</v-ons-splitter-content>
</v-ons-splitter>
タブバーと使う:
v-ons-tabbar
はスロットに対応しているため、router-view
をページスロットに組み込めることができます。また、v-ons-tab
の標準的な挙動はclick
ハンドラーでオーバーライドされ、ルーターが認識できている必要があります。
<v-ons-tabbar>
<router-view slot="pages"></router-view>
<v-ons-tab v-for="name in ['Home', 'Settings']" :key="name"
:label="name"
:active="$route.name === name"
@click.prevent="$router.push({name})"
></v-ons-tab>
</v-ons-tabbar>
ナビゲーターと使う: この場合<router-view>
を使う必要はありません。ルーターはナビゲーターの次のページスタックを判断し、トランジションやイベント発火などを自動的に実行します。
template: `
<div id="app">
<v-ons-navigator swipeable
:page-stack="pageStack"
:pop-page="goBack"
></v-ons-navigator>
</div>
`,
methods: {
goBack() {
this.$router.push({ name: this.$route.matched[this.$route.matched.length - 2].name });
}
},
created() {
const mapRouteStack = route => this.pageStack = route.matched.map(m => m.components.default);
mapRouteStack(this.$route);
this.$router.beforeEach((to, from, next) => mapRouteStack(to) && next());
}
上の例は、ナビゲーターのページスタックのすべてのルートに追加しています。すべてのルートに使われるページ(v-ons-page
)が登録されていることを想定していますが、そうでない場合は不要なルートを外す必要があります。
ルーターがナビゲーターに新しいページスタックを渡した際には、現在のスタックと以前のスタックの比較を行います。もしページやルートの数が削減されたり、最上位のページに変更がおこなわれた場合はpopPage
処理が実施されます。そうでない場合はpushPage
処理が実行されます。
この挙動のため、ブラウザの履歴($router.go(-1)
)を用いて前のページに戻る場合は、popPageでなくpushPageが実行されます。そのため、v-ons-back-button
を用いて履歴を戻る方法は推奨されず、自前でページに戻る遷移を記述する必要があります。
上の例では、v-ons-back-button
コンポーネントはgoBack
メソッドを実行します。このメソッドはブラウザの履歴をたどるのではなく、親のルート(1つ前のv-ons-page
である想定です)を追加します。この挙動で多くのモバイルアプリのユースケースに対応できるはずです。
vue-router
とv-ons-navigator
を組み合わせたより多くの例は、このレポジトリを参照してください。