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

出来るんですよ

# mixin/IndependentComponent.js
これをmixinすることで呼び出すコンポーネントをvmへ追加してあげます
肝なのが、`mounted`のなかで`$el`(自分自身の要素)をcreatedで作ったvmへ差し込む部分と`created`でその差し込み先のコンポーネントを作っているところです（つまり全部）

```js
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
```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`はこの辺で適当に宣言してます

```js
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
```vuejs
<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
```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

```js
this.$confirm({
    title: 'hoge',
    body: 'message'
})
```

# 合わせて読みたい
勝手にリコメンドします

* [Vue.jsで自動的にプラグインを読み込む](https://qiita.com/angeart/items/a56601903c8fc3bd5950)