最近的項目里用上了vue和element-ui。vue這種輕量級漸進式框架的舒適自不必說,但一直困擾著我的,是如何方便又優雅的彈出模態dialog... 對於我這種在jquery出現之前就用document.getElementById敲代碼的老頑固來說,我始終不能完全接受把dialog在編碼期就寫 ...
最近的項目里用上了vue和element-ui。vue這種輕量級漸進式框架的舒適自不必說,但一直困擾著我的,是如何方便又優雅的彈出模態dialog...
對於我這種在jquery出現之前就用document.getElementById敲代碼的老頑固來說,我始終不能完全接受把dialog在編碼期就寫入模板的方式,下麵是尤大在知乎某個相關問題的回答節選(全文請看https://www.zhihu.com/question/35820643):
為什麼一定要非同步插入?
其實以前也有一些用戶跟我糾結過這個問題,他們覺得一定要在需要的時候創建這個組件才是符合他們思維的做法。在我看來,這是沒有理解『狀態驅動的界面』的一種表現。
傳統的命令式 (Imperative) 的思維寫出來的代碼:$('.open-modal').on('click', function () { var modal = new Modal() modal.$appendTo('body') modal.open() }) // 在 modal 內部還要處理關閉、銷毀自身的邏輯狀態驅動的思維寫出來的代碼:
this.showModal = true // 關掉 this.showModal = false
不可否認,尤大所說的狀態驅動確實是vue的精髓,但是在實際應用中,dialog往往需要直接在body下才能避免這樣那樣的問題,就比如本文要說的element-ui的el-dialog問題:如果你在一個el-dialog里,嵌套了另外一個el-dialog,那麼彈窗的遮罩層會相互影響,導致用戶無法使用(新發佈的element-ui 2.0已經解決了嵌套彈窗的問題,文檔在這裡http://element.eleme.io/#/zh-CN/component/dialog)。
這就要求我們把系統中所有可能出現的dialog,都預先放在vue的根組件中,但顯然這是不合理的,根組件無法預知業務模塊中將會出現的dialog。dialog應該和alert、messagebox、toast一樣,提供方法級別的調用,但不知為何element-ui為後者們提供了全局方法,但對dialog卻沒有。
本文的目的,就是為了分享一個為dialog提供全局方法的做法。這是我在csdn上看到的一篇文章,確實解決了我的問題,原文在這裡:http://blog.csdn.net/zmy_coder/article/details/78042485
原理就是在方法被調用時,在body里create一個div,並且創建一個Vue實例,指定el屬性為這個div。
//dialog.js function makeDialog(option) { var dom = document.createElement('div'); document.getElementsByTagName('body')[0].appendChild(dom); let tpl = '\ <el-dialog \ :close-on-click-modal="false" \ :custom-class="customClass" \ :title="title" \ :visible.sync="show" \ :size="size" \ :before-close="handleClose" \ @close="close">\ <dialogContent @close="closeDialog" @confirm="confirmDialog" v-model="dialogData"></dialogContent>\ </el-dialog>'; var vue = new Vue({ el: dom, data: function () { return { title: option.title, size: option.size || 'small', show: true, dialogData: option.data, }; }, template: tpl, computed: { customClass(){ return `el-dialog--width-${option.size || 'auto'}`; } }, methods: { handleClose(done){ if (option.beforeClose) { option.beforeClose(done); } else { done(); } }, close() { if (option.close) { option.close(); } }, closeDialog(){ this.show = false }, confirmDialog(result){ this.show = false option.confirm && option.confirm(result) } }, components: { dialogContent: option.component, }, }); return vue; } export default { open(options){ return makeDialog(options) } }
在創建的這個Vue實例里,用到了el-dialog組件,並且具體的內容由外部調用者以component的形式傳入,如果該component需要初始數據,需要為該component定義一個value屬性,並且在調用open方法時,用options.data傳入,並且可以設置在對話框beforeClose、close、confirm時的回調
用法示例:
對話框內容:
<!--SimpleDialogTest.vue--> <template> <div class="tutorial"> 請輸入您的姓名 <input class="form-control" v-model="name"> <button type="button" class="btn btn-primary" @click="submit">確定</button> <button type="button" class="btn btn-default" @click="cancel">取消</button> </div> </template> <style lang="scss" rel="stylesheet/scss" scoped> </style> <script type="text/ecmascript-6"> import dialog from '../../assets/js/dialog' export default{ props: { value: Object, }, data(){ return { name : this.value.name } }, methods: { submit(){ console.log('your name is ' + this.name) //do something if you like //... //關閉對話框 this.$emit('close'); //關閉對話框, 並回調調用者的option.confirm方法 // this.$emit('confirm', { // ... // }); }, cancel(){ this.$emit('close') } }, } </script>
調用方:
<!---調用方--> <template> <button @click="openDialog">彈出對話框</button> </template> <script type="text/ecmascript-6"> import dialog from '../assets/js/dialog' import SimpleDialogTest from 'SimpleDialogTest.vue' export default{ data(){ return{ } }, methods:{ openDialog(){ dialog.open({ title: '標題標題', size:'small', //可選項tiny/small/large/full, 對應el-dialog的size屬性 component: SimpleDialogTest, data: { name: 'your name', }, // beforeClose: (done) => { // //點右上角關閉按鈕後觸發 // console.log('dialog is closing'); // done() // }, close: () => { //關閉後觸發 console.log('dialog is closed') }, confirm: (result) => { //顯式$emit('confirm')時觸發 console.log('dialog is confirmed, and dialog result is ', result) } }) } } } </script>
關於option的size
el-dialog中size的四個選項tiny/small/large/full在實際應用中是不夠的,有時候我們希望能為dialog能自適應內容組件的寬度,也就是說由內容組件來決定寬度,應該怎麼做呢?
首先定義一個全局的css:
.el-dialog.el-dialog--width-auto{ width:auto !important; }
然後在調用dialog.open()的時候,不要指定size屬性就行了。