Vue.jsでモーダルウィンドウを作ってみる
2015/10/31追記
この記事はVue.js@0.12.1を元に書かれています。最新版は1系統で書式に大きな変更がありますでのご注意ください。
Vue.jsを0.12.1から1.0.3にアップデートした際にハマったこと - 俺、サービス売って家買うんだ
Vue.jsやAngularJSのデータバインディングを使ってDOMの並び替えやフィルタを実装することは多いと思います。しかし、WindowsやiOSで実装されているUIパターンの実装依頼をされた時に困ることって多いのではないでしょうか?私も困ります。
今日は試しにモーダルウィンドウをVue.jsで作ってみることにしました。
Componentsの機能を使っているので先に以下の記事を読んでおくと理解が捗るかもしれません。
作りたいものイメージ
- メインウィンドウのボタンを押してモーダルを開く
- モーダル内の商品を選択する
- モーダルを閉じる
- メインウィンドウに選択した商品とその合計値段が表示される
- 購入ボタンを押すとアラートで選択中の商品が表示される
文章では伝わりづらいので実際に作ったものはこちら。
まぁ、よくある感じのやつです。
使ったもの
HTML
とりあえずHTMLから見てみましょう。
(CSS/デザイン周りはかなり適当です。すいません。)
<div id="app" class="app"> <div>選択中の商品</div> <ul> <li v-repeat="items" v-show="selected">{{name}}</li> </ul> <div>合計{{sumPrice}}円</div> <button type="button" v-on="click:openModal">商品を選択</button> <button type="button" v-on="click:purchase">購入</button> <my-modal items="{{items}}" title="{{title}}"></my-modal> </div> <div id="overlay" class="overlay"></div> <script id="modal" type="x-template"> <div class="modal"> <div>{{title}}</div> <table> <thead> <tr> <th>選択</th> <th>名前</th> <th>値段</th> </tr> </thead> <tbody> <tr v-repeat="items"> <td><input type="checkbox" v-model="selected"></td> <td>{{name}}</td> <td>{{price}}円</td> </tr> </tbody> </table> <button v-on="click:commit">決定</button> </div> </script>
構造
- #appがbody直下のdiv
- #overlayはモーダルが表示された時の黒半透明のオーバーレイ
ポイント
- <my-modal />が#modalテンプレートを元にしたコンポーネント
JavaScript
$(function() { var modalVM = Vue.extend({ template: '#modal', props: ['title', 'items'], created: function() { this.$on('open-modal', function() { this.open(); }); $('#overlay').on('click', this.close); }, methods: { open: function() { $(this.$el).show(); $('#overlay').show(); }, close: function() { $(this.$el).hide(); $('#overlay').hide(); }, commit: function() { this.close(); } } }); Vue.component('my-modal', modalVM); var app = new Vue({ el: '#app', compiled: function() { $(this.$el).show(); }, computed: { sumPrice: { get: function() { var sum = 0; this.items.forEach(function(item) { if (item.selected) { sum += item.price; } }); return sum; } } }, data: { title: '商品選択', items: [{ id: 0, name: 'りんご', price: 100, selected: false }, { id: 1, name: 'いちご', price: 50, selected: false }, { id: 2, name: '梨', price: 100, selected: false }, { id: 3, name: 'メロン', price: 100, selected: false }] }, methods: { openModal: function() { this.$broadcast('open-modal', this); }, purchase: function() { var ids = this.items .filter(function(elm) { return elm.selected; }) .map(function(elm) { return elm.name; }); if (ids.length === 0) { alert('商品を選んでください'); } else { alert(ids.join(',') + 'を買う'); } } } }); });
ポイント
- propsの設定を忘れずに(・ω<)
- my-modalは別のファイルに分けて使いまわしたりしたい
- モーダルのタイトル(title)も親から渡せるようにする
- computedを使って選択中の商品の合計値段をgetterとして登録
- モーダルの開く動作は親からのイベント(open-modal)
全体的なポイント
- 親子のやりとり
- propsを通して子どもが触れる$dataの範囲を明確にする
- eventを通じて疎通
- スタイルシートはコンポーネント用にネームスペースを切るべき
- Componentsの中にローカルなCSSを書いたりはできない
- 親と子で共通のスタイルを使うとComponentsの思想に反する
悩み
- DOMをキャッシュさせるタイミングをどこでやるか
- 生DOMを触るのもの面倒なので、#overlayとか$elを扱うときにjQueryオブジェクトにしてキャッシュしたい
- overlayに関しては特殊だけど、もうちょっとhtml要素をちまちま弄りたい時ってある
- Marionette.jsでいうuiみたいなのがほしい
- そもそもこういう設計がだめで頭を切り替えるべき?
まとめ
なんというか作る機会の多いモーダルとはいえフレームワークをかませたとたんに実装が厄介になってきますね。これでカルーセルとか作れとなった暁にはもう何も考えたくなくなります。普通に作りたい。
ってわけで、Vue.jsは気に入っているんですがUIの実装が複雑になってきた場合は、UIはjQuery + CSSのDOMの一般的な実装にしてデータバインディングで楽したいところだけVue.jsに頼るみたいな局所的な使い方にすることが多いです。
いつも手探り実装なので、もう少し他の方が作ったコードも読んでみたいところです。
₍₍ (ง ˙ω˙)ว ⁾⁾オラッオラッ
関連書籍
- 作者: mio
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2018/05/29
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
- 作者: 川口和也,喜多啓介,野田陽平,手島拓也,片山真也
- 出版社/メーカー: 技術評論社
- 発売日: 2018/09/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る