JavaScript
Node.js
SPA
Vue.js
webpack

[Vue.js] えっ、モーダルコンポーネントってthis.$confirm的な記法で呼び出せるんですか!

出来るんですよ


mixin/IndependentComponent.js

これをmixinすることで呼び出すコンポーネントをvmへ追加してあげます

肝なのが、mountedのなかで$el(自分自身の要素)をcreatedで作ったvmへ差し込む部分とcreatedでその差し込み先のコンポーネントを作っているところです(つまり全部)

import Vue from 'vue'

import camelCase from 'lodash/camelCase'
import upperFirst from 'lodash/upperFirst'

export default {
created () {
let name = this.$options.name
if (!name) {
console.error('please set components name.')
if (process.env.NODE_ENV === 'development') {
console.warn('create unique id')
const uuidv4 = require('uuid/v4')
name = uuidv4()
}
}
name = upperFirst(camelCase(name))
let $parent = this.$parent
if (!$parent) {
let parent = document.querySelector(this.container)
if (!parent) {
// Lazy creating `div.notifications` container.
const className = this.container.replace('.', '')
const Component = Vue.extend({
name,
render (h) {
return h('div', {
'class': {
[`${className}`]: true
}
})
}
})
$parent = new Component().$mount()
document.body.appendChild($parent.$el)
} else {
$parent = parent.__vue__
}
// Hacked.
this.$_parent_ = $parent
}
},
mounted () {
// independent instance
if (this.$_parent_) {
this.$_parent_.$el.appendChild(this.$el)
this.$parent = this.$_parent_
delete this.$_parent_
}
}
}


genModalComponent.js

import Vue from 'vue'

import store from 'src/store'
import router from 'src/router'

export function genModalComponent (component) {
const Instance = Vue.extend(component)
// param has to be Object
return (param) => {
return new Promise((resolve, reject) => {
let notUsedValue = new Instance({ // eslint-disable-line no-unused-vars
el: document.createElement('div'),
store,
router,
param: param || {},
propsData: {
active: true,
callback: function (result, ...param) {
if (result === this.$enums.result.CANCELED) {
resolve(null)
}
if (result === this.$enums.result.OK) {
if (param instanceof Array && param.length === 1) {
param = param[0]
}
resolve(param)
}
}
}
})
})
}
}

ここで新規Vueインスタンスを生成しています

当然元のVueインスタンスと別になるので現状のVue.js Devtoolsではインスペクトすることができません(対応してくれ〜〜〜)

propにcallback関数を渡していますが、これは呼び出し側へ値を返却する必要がある際に利用します

ここの中の書き方はもうちょっといい方法があるかもしれません


plugins/enum.js

genModalComponent.jsで使用している$enumsはこの辺で適当に宣言してます

const promise = {

RESOLVED: Symbol('enum_promise_resolved'),
FAILED: Symbol('enum_promise_failed')
}
const result = {
OK: Symbol('enum_result_ok'),
CANCELED: Symbol('enum_result_canceled'),
FAILED: Symbol('enum_result_failed')
}

export default {
install: function (vue) {
vue.prototype.$enums = {
promise,
result
}
}
}


使い方

Confirmモーダルをだす例をあげます (Buefy使用)


components/Confirm.vue

<template>

<b-modal :active.sync="isActive" @close="cancel">
<div class="modal-card" ref="modal">
<header class="modal-card-head">
<p class="modal-card-title">{{ title }}</p>
</header>
<section class="modal-card-body">
<div class="content block">
{{ body }}
</div>
</section>
<footer class="modal-card-foot">
<button class="button" type="button" @click='submit()'>OK</button>
<button class="button" type="button" @click='cancel()'>Cancel</button>
</footer>
</div>
</b-modal>
</template>

<script>
import IndependentComponent from 'components/mixins/IndependentComponent'
export default {
name: 'Confirm',
mixins: [ IndependentComponent ],
props: {
active: {
type: Boolean,
default: false
},
callback: {
type: Function, // status, id
default: () => {}
},
container: {
type: String,
default: '.confirm--container'
}
},
data () {
return {
isActive: false,
title: this.$options.param.title,
body: this.$options.param.body
}
},
methods: {
cancel () {
this.callback(this.$enums.result.CANCELED)
this.close()
},
close () {
this.$emit('close')
this.$emit('update:active', false)
this.isActive = false
},
submit () {
this.callback(this.$enums.result.OK, true)
this.close()
}
},
mounted () {
// Delay assign
this.isActive = this.active
}
}
</script>

呼び出し側からの変数はthis.$options.paramに詰め込まれています

b-modalに渡しているisActiveですがpropsをそのまま渡してしまうとmountされる前にactive === trueとなってしまい虚空の彼方へ消え去りますので、mountedの中でisActive = activeとしています


plugins/Confirm.js

import Confirm from 'components/Confirm'

import { genModalComponent } from 'src/utils/genModalComponent'

export default {
install: function (vue, options) {
vue.prototype.$confirm = genModalComponent(Confirm)
}
}

pluginとして登録してあげる事によって、呼び出したいコンポーネントからthis.$confirmのような形で書くことができます

パラメータを渡す際は以下のようにObjectに詰めてあげるとOK

this.$confirm({

title: 'hoge',
body: 'message'
})


合わせて読みたい

勝手にリコメンドします