Ruby's Louvre

每天学习一点点算法

导航

利用状态模式处理多个模态弹出层的显示隐藏

使用MVVM架构处理页面的生成与更新的好处是,我们从DOM的桎梏中解放出来,重点转移到数据的处理。数据的组织与维护向来是设计模式的阵地,这让我们亲近设计模式,写出高可维护性的软件。以近日我在公司遇到的弹出层为例吧。

后台管理界面的特点是多表格多弹出层,这样才方便展现更多数据更多功能。我们项目有一个叫云储存的模块,可以看作是115网盘的微缩版。这模块里有许多弹出层,用于处理分区,文件夹,文件的增删改查。为了防止用户在一个操作不结束时干另一个操作引起混乱,都做成模态对话框。换言之,它们都有浅黑色的半透明遮罩层。由于可能一个弹出层外弹出另一个弹出层,比如删除时的确认面板,因此我又不能让所有弹出层共享一个外框,唯一能共享的是遮罩层。

再看我的代码,每个弹出层在VM中对应两个方法与一个属性,那个属性用于负责弹出层的显示与隐藏,其他两个方法用于改变这个属性。这是一种非常经典的做法。但遮罩层怎么办呢?它也应该跟弹出层一同显示隐藏。但如何让多个弹出层一起操作它?这时我使用了一个技巧——使用"短路或"!

<div class="mask" ms-visible="flagCreatePartition || flagNewFolder || flagDeleteFiles || flagUploadFiles">

</div>
<div class="dialog" ms-if="flagCreatePartition" ms-include-src="'modules/apps/storage/createPartitionDialog.html'">

</div>
<div class="dialog" ms-if="flagDeleteFiles" ms-include-src="'modules/apps/storage/deleteFilesDialog.html'">

</div>
<div class="dialog" ms-if="flagNewFolder" ms-include-src="'modules/apps/storage/newFolderDialog.html'">

</div>
<div class="dialog" ms-if="flagUploadFiles" ms-include-src="'modules/apps/storage/uploadFilesDialog.html'">

</div>
  var model =  avalon.define("appstorage", function(vm) {
                        vm.flagCreatePartition = false
                        vm.openCreatePartition = function() {
                            model.flagCreatePartition = true
                        }   
                        vm.closeCreatePartition = function(create) {
                            if (create === true) {
                               //创建新分区
                            }
                            model.flagCreatePartition = false
                        }


                        vm.flagNewFolder = false
                        vm.openNewFolder = function() {
                            model.flagNewFolder = true
                        }
                        vm.closeNewFolder = function(create) {
                            if (create === true) {
                              //创建文件夹
                            }
                            model.flagNewFolder = false
                        }

                        vm.flagDeleteFiles = false
                        vm.openDeleteFiles = function() {
                            vm.flagDeleteFiles = true
                        }
                        vm.closeDeleteFiles = function() {
                            vm.flagDeleteFiles = false
                        }


                        vm.flagUploadFiles = false
                        vm.openUploadFiles = function(create) {
                            if (create === true) {
                              //上传文件
                            }
                            vm.flagUploadFiles = true
                        }
                        vm.closeUploadFiles = function() {
                            vm.flagUploadFiles = false
                        }
                        //略
                    })

这是一个非常工整划一的结构。当我们想打开某人弹出层时,只要在对应的按钮上绑定ms-click="openUploadFiles", 关闭弹出层,我们在弹出层的关闭按钮上绑定ms-click="closeUploadFiles"。弹出层里存在两种按钮,一种是确认,是存在实际操作,一种是取消,单纯的关闭,我们可以通过传参区分它们。下面是某一个弹出层的内容,它是通过ms-include实现动态加载。

<div class='wrapper'>
    <div class='title'>{{i18ndeletefile}}<span class='close' ms-click='closeDeleteFiles'></span></div>
    <p>Are you sure to delete seleted files?</p>
    <span class="button" ms-click="closeDeleteFiles(true)"  >{{i18nok}}</span>
</div>

弹出层出现的入口:

<header>
    <span class="newFolder iconbutton" ms-click="openNewFolder">{{ i18nnewFolder }}</span>   
    <span class="uploadFile iconbutton" ms-click="openUploadFiles">{{ i18nuploadFile }}</span>

    <span class="moveBtn fastener">{{ i18nmove }}</span>

    <span class="removeBtn fastener" ms-click="openDeleteFiles">{{ i18nremove }}</span>

    <span class="renameBtn fastener">{{ i18nrename }}</span>
</header>

一切是乎很完美,但细看,它是违反“开闭原则”,每添加一个新弹出层都要在VM加三个东西,而且视图上也要遮罩层的ms-visible的值进行修改。这时我们就需要查找重复代码了,把变化的东西封装起来。我们发现所有弹出层与遮罩层都是受某些变量而改变(flagXXXX),它们是一个简单的布尔,如果把它们合并起来做成一个变量更好用些。

                vm.flagDialog = ""
                vm.openDialog = function(name) {
                    vm.flagDialog = name
                }
                vm.openDialog = function(command, execute) {
                    if (execute) {
                        vm[command]()
                    }
                    vm.flagDialog = ""
                }
                vm.createPartition = function() {
                }
                vm.newFolder = function() {
                }
                vm.deleteFiles = function() {
                }
                vm.uploadFiles = function() {
                }

在JAVA中,它要求每个类都一个控制状态的内部类。在JS这样动态的语言中,我们可以把这个内部类改成对象,这里更进一步,改成一个字符串。然后我们只要控制这个字符串,就能批量控制N个弹出层了。

打开这些弹出层的HTML代码变成:

<header>
    <span class="newFolder iconbutton" ms-click="openDialog('newFolder')">{{ i18nnewFolder }}</span>   
    <span class="uploadFile iconbutton" ms-click="openDialog('uploadFiles')">{{ i18nuploadFile }}</span>

    <span class="moveBtn fastener">{{ i18nmove }}</span>

    <span class="removeBtn fastener" ms-click="openDialog('deleteFiles')">{{ i18nremove }}</span>

    <span class="renameBtn fastener">{{ i18nrename }}</span>
</header>

而弹出层的代码就变成这样了:


<div class="mask" ms-visible="flagDialog">

</div>
  
<div class="dialog" ms-if="flagDialog === 'createPartition'" 
     ms-include-src="'modules/apps/storage/createPartitionDialog.html'">
</div>
  
<div class="dialog" ms-if="flagDialog === 'deleteFiles' "
     ms-include-src="'modules/apps/storage/deleteFilesDialog.html'">
</div>
  
<div class="dialog" ms-if="flagDialog === 'newFolder'" 
     ms-include-src="'modules/apps/storage/newFolderDialog.html'">
</div>
  
<div class="dialog" ms-if="flagDialog === 'uploadFiles'"
     ms-include-src="'modules/apps/storage/uploadFilesDialog.html'">
</div>

弹出层的内容大概成为这样:

<div class='wrapper'>

    <div class='title'>{{i18nnewFolder}}<span class='close' ms-click='closeDialog'></span></div>

    <input class="partition" ms-duplex="folderName">

    <span class="button partitionbtn" ms-click="closeDialog('newFolder',true)" ms-class-blue="folderName.length" >{{i18nok}}</span>

</div>

我们发现每一个弹出层的外框都很像,因此可以循环生成


<div ms-each-name="dialogNames">
    <div class="dialog" ms-if="flagDialog === name" 
         ms-include-src="'modules/apps/storage/{{name}}Dialog.html'">
    </div>
</div>

VM上添加一个dialogNames的数组,里面包含所有弹出层的名字就行了。这样我的HTML与JS就干净多。

最后附上一张设计模式的图片:

posted on 2013-09-24 10:58  司徒正美  阅读(2994)  评论(3编辑  收藏  举报