在 Vue 中使用 Bootstrap Modal

在前端視覺 UI 上常常有些情境是,點擊某個按鈕後觸發一個資料輸入面板,如果是使用 Bootstrap 5 當作網頁視覺框架,這樣的介面互動可以藉由 Bootstrap 的 Modal(互動視窗) 元件來實踐。

先到 Bootstrap 的官方文件玩玩看!

https://bootstrap5.hexschool.com/docs/5.1/components/modal/

官方 Modal HTML 程式碼

<!-- 觸發 Modal 的按鈕 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
  Launch demo modal
</button>

<!-- Modal 本體 -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        ...
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

以上的 Modal 互動模式已經被封裝在 Bootstrap 所提供的程式裡面,藉由 data-bs- 開頭的屬性來操作,如果要在 VUE3 把 Modal 做成元件來使用,則需要使用 Bootstrap 所提供的 JavaScript 方法

var myModal = new bootstrap.Modal(document.getElementById('myModal'), options)

今天就讓我來整理一下如何在 VUE 裡面使用 Bootstrap 的 Modal 元件,不過在這之前我們要先做好兩件事。

  • 假設使用 VITE 當腳手架,透過 NPM 安裝好 Bootstrap,並且在 main.js 引入 Bootstrap 與 Bootstrap CSS。
  • 理解在 VUE 裡面透過 ref 取得 DOM 元素的方法。

以下分為 Option API 與 Composition API 的 setup 模式來進行說明:

Option API 的 BS Modal

Step1 : 我們先把 App.vue 當成父層,放入觸發 Modal 的按鈕。

<template>
  <!-- data-bs-toggle="modal" 要拿掉 -->
  <button
    type="button"
    class="btn btn-primary"
    data-bs-target="#exampleModal"
  >
    Launch demo modal
  </button>  
</template>

Step2 :建立一個 modal.vue 檔案,在 template 放入 Modal 的本體,並且在整個 Modal 元件的元素最外層,加上ref="exampleModal"

<div class="modal" tabindex="-1" ref="exampleModal">
<div class="modal" tabindex="-1" ref="exampleModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Modal title</h5>
          <button
            type="button"
            class="btn-close"
            data-bs-dismiss="modal"
            aria-label="Close"
          ></button>
        </div>
        <div class="modal-body">
          <p>Modal body text goes here.</p>
        </div>
        <div class="modal-footer">
          <!--  data-bs-dismiss="modal" -->
          <button type="button" class="btn btn-secondary" @click="hideModal()">
            Close
          </button>
          <button type="button" class="btn btn-primary" @click="hideModal()">
            Save changes
          </button>
        </div>
      </div>
    </div>
  </div>

Step3 :在 modal.vue 的 script 中 import bootstrap 的 modal 方法。

import Modal from 'bootstrap/js/dist/modal';

Step4 :然後在 data 建立一個 modal 的空物件,並且在 mounted 生命週期中,使用 import 進來的 Modal 方法,指向 ref="exampleModal",並賦予給modal 空物件 。

export default {
  data() {
    return {
      modal: {},
    };
  },
  mounted() {
    // 透過refs的方式,把 DOM 元素指向外層的modal
    // 而 myModal 再指回 data 裡的變數
    this.modal = new Modal(this.$refs.exampleModal);
    // 用show呈現在畫面上
    // this.modal.show()
  },
};

Step5 :在 methods 中建立打開 modal 跟關閉 modal 的方法:

showModal() {
      this.modal.show();
    },
    hideModal() {
      this.modal.hide();
    },

完整的 modal.vue 程式碼:

<template>
  <div class="modal" tabindex="-1" ref="exampleModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Modal title</h5>
          <button
            type="button"
            class="btn-close"
            data-bs-dismiss="modal"
            aria-label="Close"
          ></button>
        </div>
        <div class="modal-body">
          <p>Modal body text goes here.</p>
        </div>
        <div class="modal-footer">
          <!--  data-bs-dismiss="modal" -->
          <button type="button" class="btn btn-secondary" @click="hideModal()">
            Close
          </button>
          <button type="button" class="btn btn-primary" @click="hideModal()">
            Save changes
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Modal from 'bootstrap/js/dist/modal';

export default {
  data() {
    return {
      modal: {},
    };
  },
  methods: {
    showModal() {
      this.modal.show();
    },
    hideModal() {
      this.modal.hide();
    },
  },
  mounted() {
    // 透過refs的方式,把 DOM 元素指向外層的modal
    // 而 myModal 再指回 data 裡的變數
    this.modal = new Modal(this.$refs.exampleModal);
    // 用show呈現在畫面上
    // this.modal.show()
  },
};
</script>

Step6:最後在 App.vue 中 import modal.vue,然後在外層的button加上 :

@click="$refs.exampleModal.showModal()"
 <button
    type="button"
    class="btn btn-primary"
    @click="$refs.exampleModal.showModal()"
  >
    Launch demo modal
  </button>

option api 範例:https://stackblitz.com/edit/vitejs-vite-us3dki?file=src%2FApp.vue

Composition API 的 BS Modal

在Composition API 中,我以 Setup function 來做說明。一樣是以 App.vue 當作父層,modal.vue 當作子元件。

Step1 : 在 modal.vue 的 template 中放入 modal 本體。在最外層的 div 加入 ref=”modal”。

<div class="modal" tabindex="-1" ref="modal">

modal.vue:

<template>
  <div class="modal" tabindex="-1" ref="modal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Modal title</h5>
          <button
            type="button"
            class="btn-close"
            data-bs-dismiss="modal"
            aria-label="Close"
          ></button>
        </div>
        <div class="modal-body">
          <p>Modal body text goes here.</p>
        </div>
        <div class="modal-footer">
          <button
            type="button"
            class="btn btn-secondary"
            data-bs-dismiss="modal"
          >
            Close
          </button>
          <button type="button" class="btn btn-primary" @click="myModal_hide()">
            Save changes
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

Step2 : 在 <script setup>中 import bootstrap 的 Modal 方法,並建立 modal 與 myModal 的 ref,modal呼應前面在 modal 本體外層 div 放入的 ref=”modal”:

<script setup>
import { onMounted, ref } from 'vue';
import Modal from 'bootstrap/js/dist/modal';
const modal = ref(null);
const myModal = ref(null);

Step3 : 在 onMounted 中使用 bootstrap 的 Modal 方法指向 Modal 本體的ref modal.value,並賦值給 myModal.value。

onMounted(() => {
  myModal.value = new Modal(modal.value);
});

Step4 : 建立開啟與關閉 modal 的方法:

const myModal_show = () => {
  alert('hi');
  myModal.value.show();
};

const myModal_hide = () => {
  myModal.value.hide();
};

Step5 : 使用 defineExpose 把封裝在 setup 中的 myModal_show 跟 myModal_hide這兩個方法曝露給父元件。

defineExpose({
  myModal_show,
  myModal_hide,
});
</script>

完整的 modal.vue 程式碼:

<template>
  <div class="modal" tabindex="-1" ref="modal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Modal title</h5>
          <button
            type="button"
            class="btn-close"
            data-bs-dismiss="modal"
            aria-label="Close"
          ></button>
        </div>
        <div class="modal-body">
          <p>Modal body text goes here.</p>
        </div>
        <div class="modal-footer">
          <button
            type="button"
            class="btn btn-secondary"
            @click="myModal_hide()"
          >
            Close
          </button>
          <button type="button" class="btn btn-primary" @click="myModal_hide()">
            Save changes
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import Modal from 'bootstrap/js/dist/modal';
const modal = ref(null);
const myModal = ref(null);

onMounted(() => {
  myModal.value = new Modal(modal.value);
});

const myModal_show = () => {
  alert('hi');
  myModal.value.show();
};

const myModal_hide = () => {
  myModal.value.hide();
};

defineExpose({
  myModal_show,
  myModal_hide,
});
</script>

Step6 : 在父元件引入 modal.vue 元件,並建立一個 ref 指向 modal 元件。

<template>
  <!-- data-bs-toggle="modal" 要拿掉 -->
  <button type="button" class="btn btn-primary">
    Launch demo modal
  </button>
  <MeModal ref="XsModal"></MeModal>
</template>

<script setup>
import { ref } from 'vue';
import MeModal from './components/modal.vue';
const XsModal = ref(null);
</script>

step7 : 在 button 上加入 @click=”XsModal.myModal_show()”

完整的父元件程式碼:

<template>
  <!-- data-bs-toggle="modal" 要拿掉 -->
  <button type="button" class="btn btn-primary" @click="XsModal.myModal_show()">
    Launch demo modal
  </button>
  <MeModal ref="XsModal"></MeModal>
</template>

<script setup>
import { ref } from 'vue';
import MeModal from './components/modal.vue';
const XsModal = ref(null);
</script>

setup 範例:https://stackblitz.com/edit/vitejs-vite-lh2jvc?file=src%2Fcomponents%2Fmodal.vue

參考資料:

Leave a Reply

Your email address will not be published. Required fields are marked *