有用過 sweetalert2 的話,應該會喜歡可以同步等待對話框回傳值的方式, 這裡做一個 Vue2 元件,呼叫該元件的方法會彈出對話框等待使用者輸入,並且回傳 Promise, 如此一來就能夠在同一個函式當中處理使用者輸入值。

Dialog 元件設計原理:

  1. 元件方法 GetConfirm() 顯示 Dialog 元件並回傳一個 Promise,。
  2. 設置watcher讓元件取得使用者輸入後 resolve promise

得利於上述元件的設計,實際上的效益是將複雜度封裝到子元件裡面(watcher移動到元件內), 如此不需在上層元件撰寫使用者輸入取值的監視邏輯, 讓我們得以在上層元件直接 await GetConfirm 同步取得值進行操作。

這個概念的用途非常廣,例如 Vue router 的 component route guard,在離開表單頁面前跳出使用者確認的 Dialog。

程式碼 (Code)

1
2
3
4
5
6
7
8
9
<button id="xBtn">執行測試</button>
<div id="xApp" class="modal" :style="{display: dialog?'block':'none'}">
  <div class="modal-content">
    <span class="close">Test Modal</span>
    <p>The value selected will resolve by promise.</p>
    <button @click="choose(1)">1</button>
    <button @click="choose(2)">2</button>
  </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
 
<script>
let data = { result: null, dialog: false }
let dialog = new Vue({
  el: '#xApp',
  data:() => data,
  methods: {
   getConfirm() {
     // 先清空 result (避免兩次選中一樣的值無法觸發 watcher)
     this.result = null 
     // open dialog
     this.dialog = true 
     return new Promise((resolve, reject) => {
       try {
         const watcher = this.$watch(
           // 設置監視的對象為 result
           () => this.result ,
           // 一旦 result 的值有改變,就 resolve promise,並啟動下一輪 watcher 
           (newVal) => resolve(newVal) && watcher()
         )
       } catch (error) {
         // 如果出錯就 reject promise
         reject(error)
       }
     })
   },
   choose(value) {
     // 為 result 設置值觸發 watcher 解開 promise
     this.result = value 
     // 關閉 dialog
     this.dialog = false
   }
  }
})
document.getElementById('xBtn')
  .addEventListener( 'click', 
      async e => alert( await dialog.getConfirm() )
    );
</script>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* The Modal (background) */
.modal {
  display: none; /* Hidden by default */
  position: fixed; /* Stay in place */
  z-index: 1; /* Sit on top */
  padding-top: 100px; /* Location of the box */
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: rgb(0,0,0); /* Fallback color */
  background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}

/* Modal Content */
.modal-content {
  background-color: #fefefe;
  margin: auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%;
}