Vue.js + Bootstrap-vue でモーダルウィンドウとフラッシュメッセージの表示サンプル

忘れそうなのでメモしておきます。子が親のメソッドを呼び出したり親が子のメソッドを呼び出したりして分かりづらくなっているので後日もっと良い方法を検討したいと思います。

基本的にはBootstrap-vueの公式に書いてあった方法で実装していきます。
https://bootstrap-vue.js.org/docs

作成したファイルは以下の4つです。
簡単なメモアプリ上でメモを削除する動きを作ってみます。

↓メインのコンポーネント

// App.vue
<template>
  <div id="app">
    <Modal @delete="deleteMemo" ref="childModal"/>
    <Memos @confirm="deleteConfirm" ref="childMemos"/>
    <Message ref="childMessage"/>
  </div>
</template>

<script>
import Modal from './components/Modal.vue'
import Message from './components/Message.vue'
import Memos from './components/Memos.vue'

export default {
  name: 'app',
  components: {
    Modal,
    Memos,
    Message
  },
  methods: {
    deleteConfirm(i) {
      this.$refs.childModal.showModal(i)
    },
    deleteMemo(i) {
      this.$refs.childMemos.deleteMemo(i)
      this.$refs.childModal.hideModal(i)
      this.$refs.childMessage.showMessage(true)
    },
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

↓メモリストのコンポーネント
これは適当に書きました。

// Memos.vue
<template>
  <div class="container">
    <b-list-group>
      <b-list-group-item v-for="memo,i in memos"><b>{{ memo.title }}</b>{{ memo.text }}<b-button variant="danger" @click="deleteComfirm(i)">削除</b-button></b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
export default {
  name: 'Memos',
  data() {
    return {
      memos: [
        {"title": "title1", "text": "memomemoemomeo"},
        {"title": "title2", "text": "memomemomemomemo"},
        {"title": "title3", "text": "memomemomemomemo"},
        {"title": "title4", "text": "memomemomemomemo"},
        {"title": "title5", "text": "memomemomemomemo"},
        {"title": "title6", "text": "memomemomemomemo"},
        {"title": "title7", "text": "memomemomemomemo"},
        {"title": "title8", "text": "memomemomemomemo"},
        {"title": "title9", "text": "memomemomemomemo"},
      ]
    }
  },
  methods: {
    deleteComfirm(i) {
      this.$emit('confirm', i);
    },
    deleteMemo(i) {
      // 実際にはここに削除処理を記述
      this.memos.splice(i, 1);
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

↓モーダルのコンポーネント
https://bootstrap-vue.js.org/docs/components/modal/#using-show-hide-and-toggle-component-methods
こちらの
Using show(), hide(), and toggle() component methods
の方法を使って、モーダルの表示、非表示を操作しています。

// Modal.vue
<template>
  <b-modal ref="modal" size="xl" hide-footer hide-header>
    <div class="d-block text-center">
      <p>本当に削除しますか?</p>
    </div>
    <b-button class="delete-button" variant="success" block @click="hideModal()">いいえ</b-button>
    <b-button class="delete-button" variant="danger" block @click="deleteMemo()">はい</b-button>
  </b-modal>
</template>

<script>
export default {
  name: 'Modal',
  data() {
    return {
      deleteIndex: ""
    }
  },
  methods: {
    showModal(i){
      this.deleteIndex = i;
      this.$refs.modal.show()
    },
    hideModal(){
      this.deleteIndex = "";
      this.$refs.modal.hide()
    },
    deleteMemo(){
      this.$emit('delete', this.deleteIndex);
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

↓フラッシュメッセージのコンポーネント
https://bootstrap-vue.js.org/docs/components/alert/#auto-dismissing-alerts
Auto dismissing alerts
の実装サンプルを使い表示後2秒経過すると消えるようにしています。
また、transitionでアニメーションを入れています。
https://jp.vuejs.org/v2/guide/transitions.html

// Message.vue
<template>
  <transition name="message">
    <div v-if="messageActive" id="message-parent">
      <div id="message">
        <b-alert :show="dismissCountDown" v-bind:variant="variant" @dismiss-count-down="countDownChanged">
          {{ message }}
        </b-alert>
      </div>
     </div>
  </transition>
</template>
<script>
export default {
  name: 'Message',
  data() {
    return {
      // message表示用データ
      dismissSecs: 2,
      dismissCountDown: 0,
      message: '',
      variant:'',
      messageActive:false,
    }
  },
  methods: {
    countDownChanged (dismissCountDown) {
      this.dismissCountDown = dismissCountDown
      if (dismissCountDown == 0) {
        this.messageActive = false;
      }
    },
    showMessage: function (success) {
      this.message = success ? "削除しました" : "エラーが発生しました";
      this.variant = success ? "success" : "danger";
      this.dismissCountDown = this.dismissSecs;
      this.messageActive = true;
    },
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.message-enter-active, .message-leave-active {
  transition: opacity 0.5s;
}
.message-enter, .message-leave-to {
  opacity: 0;
}
#message-parent {
  width:100%;
}
#message {
  position: fixed;
  top: 5px;
  right: 0;
  left: 0;
  margin-left: auto;
  margin-right: auto;
  box-sizing:border-box;
  width:95%;
}
</style>

↓こんな表示になります。
f:id:ti_taka:20190409014407g:plain

処理の順番としては、

  1. Memos.vue中のdeleteComfirm(i)が発火
  2. App.vue中の@confirm="deleteConfirm" → deleteConfirm(i)が発火
  3. Modal.vue中のshowModal(i)が発火
  4. Modalの表示
  5. 「はい」を選択
  6. Modal.vue中のdeleteMemo()が発火
  7. App.vue中の@delete="deleteMemo" → deleteMemo(i)が発火
  8. Memos.vue中のdeleteMemo(i)が発火
  9. Modal.vue中のhideModal(i)が発火
  10. Message.vue中のshowMessage(true)が発火
  11. countDownChanged (dismissCountDown)にて1秒毎にカウントダウンし、設定秒経過したらメッセージを消す

という流れになっています。

とりあえず作りたいものは作れましたが、状態を管理するデータがいろんなコンポーネントに散在してしまっているので、今後はVuexなどを使って管理できるようにしたいと思います。