TypeScript+Vueでダイアログなどの動的コンポーネントを表示する。

# 開発環境
* TypeScript
* Vue.js
* vue-class-component
* vue-property-decorator

↓ここの内容で環境つくった状態でやります。
[TypeScriptではじめるVueコンポーネント（vue-class-component）](https://qiita.com/koya_kon/items/8d9968d07748d20825f8)

# なにをするか
コンポーネント（ダイアログやコンテキストメニューなど）を動的に生成する。


# 公式のそれっぽい例があるけど...
[モーダルコンポーネント の例](https://jp.vuejs.org/v2/examples/modal.html)

あらかじめv-ifの値をfalseにしてダイアログ要素を記述して、表示するときはそのフラグをtrueにしている。
でもこれだとダイアログの種類が増えたときとか、パラメータ渡したいときに微妙な気が。。。


## やりたいこと
1. ↓ こんな感じで、メソッド呼び出しの形で表示したい。
〇：this.showDialog()
×：this.showDialog = true
   
2. ダイアログ表示するときにパラメータを渡したい。

## どうすればいいか
公式のvm.$mount()の説明にヒントが。
https://jp.vuejs.org/v2/api/#vm-mount

```typescript
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
```

newしたコンポーネントを\$mount()して、それの\$elを追加する。
なるほろ。

# 実装する
TypeScriptベースで書いてますがJavaScriptでもたいして変わらないはず。

## ダイアログをつくる
propsに渡したメッセージと、OKボタン、CANCELボタンを表示する。

```typescript:Dialog.ts
import Vue from 'vue'
import {Component, Prop} from 'vue-property-decorator'

@Component({
  template: require('./Dialog.html'),
})
export default class Dialog extends Vue {

  @Prop()
  message: string = ''

  ok(){
    console.log('OK!!!')
    this.close() // ボタン押したあと閉じるなら必要
  }

  cancel(){
    console.log('CANCEL!!!')
    this.close() // ボタン押したあと閉じるなら必要
  }

  close(){
    // 自身を削除し、後始末する
    const parent: any = this.$el.parentNode
    parent.removeChild(this.$el)
    this.$destroy()
  }

}
```

```html:Dialog.html
<!-- 実際はcssで絶対配置にしたりとかするけどそこは省略します。 -->
<div class="dialog">
  <p>{{message}}</p>
  <button @click="ok">OK</button>
  <button @click="cancel">CANCEL</button>
</div>
```

## ダイアログを呼ぶ側をつくる
動的にコンポーネントを生成するときは、propsDataオプションを使ってpropsを渡せる。

```typescript:App.ts
import Vue from 'vue'
import {Component} from 'vue-property-decorator'

@Component({
  template: require('./App.html'),
})
export default class App extends Vue {

  showDialog(){
    const dialog = new Dialog({
      propsData: {
        message: 'こうかいしませんね？' // propsDataオプションでpropsを渡せる
      }
    }).$mount()
    this.$el.appendChild(dialog.$el)
  }

}
```

```html:App.html
<div class="app">
    <button @click="showDialog"></button>
</div>
```


### 補足
ダイアログでボタンを押したときの処理を呼び出し元に委ねたいときはpropsにcallbackを追加すればよいです。

```typescript:Dialog.ts
import Vue from 'vue'
import {Component, Prop} from 'vue-property-decorator'

@Component({
  template: require('./Dialog.html'),
})
export default class Dialog extends Vue {

  @Prop()
  message: string = ''
  @Prop()
  callback : (result: string) => void // こんな感じで

  ok(){
    this.callback('OK') // こんな感じで
    this.close()
  }

  cancel(){
    this.callback('CANCEL') // こんな感じで
    this.close()
  }

  close(){
    const parent: any = this.$el.parentNode
    parent.removeChild(this.$el)
    this.$destroy()
  }

}
```

```typescript:App.ts
import Vue from 'vue'
import {Component} from 'vue-property-decorator'

@Component({
  template: require('./App.html'),
})
export default class App extends Vue {

  showDialog(){
    const dialog = new Dialog({
      propsData: {
        message: 'こうかいしませんね？',
        callback: (result: string) => console.log(`ダイアログの結果は${result}です。`) // こんな感じで
      }
    }).$mount()
    this.$el.appendChild(dialog.$el)
  }

}
```

# まとめ
でけた！！