#はじめに
- メニューボタン押すと開く
- メニューボタンもう一度押すと閉じる
- メニューの外側押しても閉じる
どこでもよく見るアレをvue.jsで
#デモ
デモ(jsfiddle)
#説明
##親コンポーネント
<div style="position:relative">
<button v-on:click.stop="showMenu = !showMenu">Menu</button>
<dropdown-menu
v-if="showMenu"
v-on:close="showMenu = false"></dropdown-menu>
</div>
単純に親コンポーネントのv-if="showMenu"
だけで表示の切り替えをしています。
子コンポーネント内で$emit('close')
と発火させるとv-on:close
が拾って親がメニューを閉じてくれます。
v-bind:show.sync="showMenu"
という構文はVue2.0で滅びるようです。
メニューを開く時のボタンクリックはメニューの外側で行うので、バブリングの停止が必要です。
v-on:click.stop="showMenu = !showMenu"
の.stop
modifierでやっています。
##子コンポーネント
Vue.component('dropdown-menu', {
mixins:[listener],
template: '#menu-template',
created:function(){
this.listen(window, 'click', function(e){
if (!this.$el.contains(e.target)){
this.$emit('close');
}
}.bind(this));
}
});
window
をクリックしたとき、自分自身と子孫がターゲットでないならclose
イベントをemitしています。
this.$el
で自身のDOMが取得できます。
##イベントリスナー
var listener = {
methods:{
listen :function(target, eventType, callback) {
if (!this._eventRemovers){
this._eventRemovers = [];
}
target.addEventListener(eventType, callback);
this._eventRemovers.push( {
remove :function() {
target.removeEventListener(eventType, callback)
}
})
}
},
destroyed:function(){
if (this._eventRemovers){
this._eventRemovers.forEach(function(eventRemover){
eventRemover.remove();
});
}
}
}
コンポーネントを破棄したときに自動的にイベントリスナーをremoveしてくれるmixinです。
このデモひとつだけならまったく余計ですが、コンポーネントが幾つもある場合、
addEventListener
とremoveEventListener
に目を光らせずに済むので、少しコーディングが気楽になります。
#コード全文
<div id="app">
<div style="position:relative">
<button v-on:click.stop="showMenu = !showMenu">Menu</button>
<dropdown-menu
v-if="showMenu"
v-on:close="showMenu = false"></dropdown-menu>
</div>
<p>
ボタンをクリックするとドロップダウンします
</p>
</div>
<script type="text/x-template" id="menu-template">
<div class="menu" style="position:absolute">
<div>AAA</div>
<div>BBB</div>
<div>CCC</div>
</div>
</script>
var listener = {
methods:{
listen :function(target, eventType, callback) {
if (!this._eventRemovers){
this._eventRemovers = [];
}
target.addEventListener(eventType, callback);
this._eventRemovers.push( {
remove :function() {
target.removeEventListener(eventType, callback)
}
})
}
},
destroyed:function(){
if (this._eventRemovers){
this._eventRemovers.forEach(function(eventRemover){
eventRemover.remove();
});
}
}
}
Vue.component('dropdown-menu', {
mixins:[listener],
template: '#menu-template',
created:function(){
this.listen(window, 'click', function(e){
if (!this.$el.contains(e.target)){
this.$emit('close');
}
}.bind(this));
}
});
new Vue({
el: '#app',
data: {
showMenu: false
}
})