Hero Image
[DIY] 設計一個可回傳 Promise 的 Dialog 元件方法

有用過 sweetalert2 的話,應該會喜歡可以同步等待對話框回傳值的方式, 這裡做一個 Vue2 元件,呼叫該元件的方法會彈出對話框等待使用者輸入,並且回傳 Promise, 如此一來就能夠在同一個函式當中處理使用者輸入值。 Dialog 元件設計原理: 元件方法 GetConfirm() 顯示 Dialog 元件並回傳一個 Promise,。 設置watcher讓元件取得使用者輸入後 resolve promise 得利於上述元件的設計,實際上的效益是將複雜度封裝到子元件裡面(watcher移動到元件內), 如此不需在上層元件撰寫使用者輸入取值的監視邏輯, 讓我們得以在上層元件直接 await GetConfirm 同步取得值進行操作。 這個概念的用途非常廣,例如 Vue router 的 component route guard,在離開表單頁面前跳出使用者確認的 Dialog。 Vue3 實作 <template> <v-dialog v-model="dialog" v-bind="$attrs"> <slot v-bind="{ Resolve }"></slot> </v-dialog> </template> <script setup> import { ref } from "vue"; const dialog = ref(false); let resolve = null; const Resolve = (v) => { resolve(v); dialog.value = false; }; const GetResult = async () => { dialog.value = true; return new Promise((res) => (resolve = res)); }; defineExpose({ GetResult, Resolve }); </script> [舊]Vuejs 實作 <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> <script src="https://cdn.jsdelivr.net/npm/[email protected]/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> /* 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%; } [舊]Vue-next 實作 這裡使用 vue-next/setup/quasar/typescript

Hero Image
[DIY] 用 Render Function 打造靈活的 CheckBox 元件範例

情境1:要選取多個 ckeckbox 對應到資料庫的欄位,欄位值是一串YN代表某個選項是否有被選去,例如: YNNYYNNYYN 情境2:要選取多個 ckeckbox 對應到資料庫的欄位,欄位值只有一個,可能是任何字元,例如: 1 可以打造兩個元件,分別對應至單選、多選 單選元件 程式碼 (Code) Vue.component('x-ck-single', { props: { disabled: { type: Boolean, default: () => false }, // checkbox 的標記 [string] || [{text:string, value:any}] labels: { type: Array, default: () => ['Yes', 'No'] }, value: { default: () => null }, trueValue: { default: () => 'Y' }, falseValue: { default: () => 'N' }, inline: { type: Boolean, default: () => false }, }, data() { return { innervalue_: this.value, } }, watch: { value(v) { this.innervalue_ = v }, }, computed: { innervalue: { get() { return this.innervalue_ }, set(v) { this.innervalue_ = v this.$emit('input', v) }, }, }, render: function (h) { const self = this let len = self.labels.length // labels 的長度 let allStr = self.labels.every((label) => typeof label == 'string') // 是否為 string let allOkObj = self.labels.every((l) => !!l.text && !!l.value) // 是否為合法的物件(如果不是 string) let siblingConf = null if (allOkObj) { siblingConf = self.labels.map((l) => _.pick(l, ['text', 'value'])) } else if (allStr && len <= 2) { siblingConf = self.labels.map((text, idx) => { let value = idx === 0 ? self.trueValue : self.falseValue return { text, value } }) } if (!siblingConf) { let errStr = '無法正確設定元件,len,allStr,allOkObj' return h('div', errStr, len, allStr, allOkObj) } // 設定 CheckBox let { disabled } = self let hideDetails = true let dense = true const baseProps = { hideDetails, dense, disabled } const baseClass = self.inline ? ['d-inline-block'] : [] let siblings = siblingConf.map((c) => { let props = { label: c.text, inputValue: self.innervalue === c.value, ...baseProps, } // 如果只有一個選項,取消勾選時就顯示 falseValue let valueOnNull = len === 1 ? self.falseValue : null let on = { change: (e) => (self.innervalue = e ? c.value : valueOnNull), } return h("v-checkbox", { props, class: baseClass, on }) }) // 傳回整個元件 return h('div', {}, siblings) } }) {{value === null ? 'null' : value}} 多選元件 程式碼 (Code)