弹窗或者浮层是页面上看起来微不足道,却又常常起到重要作用的“零件”。比如最近做的项目中,一个页面上涉及了6个以上的弹窗及其对应的逻辑处理,可以说页面几乎是由弹窗逻辑构成的。因此,如何让这些弹窗优雅地弹起来(别人能够很快地对这些代码进行维护),是一个很重要的问题。
对于弹窗的实现,比较旧的做法是直接在html上加上弹窗的元素,在不需要的时候先将其隐藏,通过js逐个实现弹窗的展示和逻辑。但是这种做法在页面弹窗较多或者多个页面使用相同弹窗时,是非常繁琐的,且代码不易维护,复用度低。因此,本文将介绍js模块化结合promise打造弹窗的方法。
1. js模块化
本文主要介绍弹窗的实现,因此不再过多赘述什么是js模块化,也不去对比AMD规范和CMD规范,如有疑问的可以参考Javascript模块化编程。后面将用具体的例子,一步一步说明弹窗的实现过程,例子中使用的js模块加载框架为SeaJS。
什么是SeaJS? |
SeaJS的主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑。SeaJS可以与jQuery这类框架完美集成。使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决目前JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护。 |
Ø 定义弹窗模块
SeaJS遵循CMD规范,因此一个模块就是一个js文件,通过define方法来定义,模块定义的方法如下:
define(function(require, exports, module) { // 模块代码 });
Ÿ exports: 是一个对象,用来向外提供模块接口。通过exports.func=function(){};语句,则可以直接将该方法通过exports暴露出去,不需要将其return。当然也可以通过return的方法来向外提供接口。Ÿ require: 是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口。也即是说在一个模块内部需要加载其他模块,可以通过require(‘./path/another’)实现。
Ÿ module: 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
本文的示例中,将弹窗定义到文件sample.js中,代码如下所示:
define(function (require, exports, module) { //弹窗dom,此处借用了之前项目中的弹窗样式 var tpl = '<style>.survey-confirm{font-family:微软雅黑;border:1px solid #dbdbdb;position:fixed;left:50%;top:40%;width:260px;height:140px;margin-left:-130px;background:#fff;z-index:1003}.survey-confirm-title{height:40px;line-height:40px;padding-left:16px;background:#f1f5fd;border-bottom:1px solid #b0c0e7;color:#2c4a93}.survey-confirm-content{height:40px;padding:10px;color:#333}.survey-confirm-button{text-align:center;height:20px;line-height:20px}.survey-mask{position:fixed;height:100%;width:100%;background:#aaa;z-index:1002;top:0;left:0;opacity:.5;filter:alpha(opacity:50)}.button{height:28x;line-height:18px;border-radius:3px;background:#f1f5fd;border:1px solid #b0c0e7;color:#2c4a93}</style><div class="sample-pop"><div class="survey-confirm"><div class="survey-confirm-title">Notice</div><div class="survey-confirm-content"></div><div class="survey-confirm-button"><input type="button" class="button button-strong" id="survey-confirm-y" value="Confirm"> <input type="button" class="button" id="survey-confirm-n" value="Cancel"></div></div><div class="survey-mask"></div></div>'; // 暴露给外部的弹窗加载方法 exports.init = function () { // 加载弹窗 $('body').append(tpl); var pop = $('.sample-pop'); // button点击事件绑定 pop.find('.button').click(function() { pop.remove(); }); }; });
Ø 弹窗模块的调用
在一个模块进行其他模块的加载,使用require方法,前面已经讲过。在页面上进行模块加载时,使用seajs.use。
// 加载一个模块,在加载完成时,执行回调 seajs.use('./a', function(a) { a.doSomething(); });
这里,我们在页面上添加一个button,用于触发弹窗,代码如下所示:
<html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>seajs</title> <script src="sea.js" type="text/javascript" charset="utf-8" ></script> <script src="jquery.min.js" type="text/javascript" charset="utf-8"></script> <script src="sample.js" type="text/javascript" charset="utf-8" ></script> </head> <body> <input type="button" id="sea1" value="click1"> </body> <script> $(document).ready(function(){ $('#sea1').click(function(){ seajs.use('sample', function (sa) { // 这里的sa即是模块的exports对象 sa.init(); }); }); }); </script> </html>
此时,将弹窗模块化的工作算是告一段落,点击页面上的button,则可以加载出弹窗如下图所示:
Ø 通用的弹窗模块
为了在多个页面复用这个弹窗(或者一个页面的不同弹窗需求),我们需要让这个弹窗更加通用,比如文案,长宽自定义。因此我们需要对弹窗模块进行一些修改,代码如下所示:
define(function (require, exports, module) { var tpl = '…此处省略模板定义,同上…'; exports.init = function (content, style, buttonConfirm, buttonCancel) { $('body').append(tpl); var pop = $('.sample-pop'); pop.find('.survey-confirm-content').text(content); if (style && style['height']) { pop.find('.survey-confirm').css('height',style['height']); } if (style && style['width']) { pop.find('.survey-confirm').css('width',style['width']); } if (buttonConfirm != 'Confirm') { if (buttonConfirm === null) { pop.find('#survey-confirm-y').hide(); } else { pop.find('#survey-confirm-y').val(buttonConfirm); } } if (buttonCancel != 'Cancel') { if (buttonCancel === null) { pop.find('#survey-confirm-n').hide(); } else { pop.find('#survey-confirm-n').val(buttonCancel); } } pop.find('.button').click(function() { pop.remove(); }); }; });
而页面上,我们添加三个button来分别触发三个不同的弹窗调用,代码如下所示:
<html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>seajs</title> <script src="sea.js" type="text/javascript" charset="utf-8" ></script> <script src="jquery.min.js" type="text/javascript" charset="utf-8"></script> <script src="sample.js" type="text/javascript" charset="utf-8" ></script> </head> <body> <input type="button" id="sea1" value="click1"> <input type="button" id="sea2" value="click2"> <input type="button" id="sea3" value="click3"> </body> <script> $(document).ready(function(){ $('#sea1').click(function(){ seajs.use('sample', function (sa) { sa.init('this is a sample', null, 'Confirm', 'Cancel'); }); }); $('#sea2').click(function(){ seajs.use('sample', function (sa) { sa.init('this is another sample', null, 'Yes', 'No,Thx'); }); }); $('#sea3').click(function(){ seajs.use('sample', function (sa) { sa.init('this is a alert', null, null, 'ok'); }); }); }); </script> </html>
三个弹窗的效果依次如下图所示:
好的,到这里我们算是实现了可定制化复用的弹窗模块。接下来,我们该操心另一件事情了,那就是弹窗按钮的点击逻辑。
2. Promise
什么是Promise? Promise是CommonJS的规范之一,拥有resolve、reject、done、fail、then等方法,能够帮助我们控制代码的流程,避免函数的多层嵌套。如今异步在web开发中越来越重要,对于开发人员来说,这种非线性执行的编程会让开发者觉得难以掌控,而Promise可以让我们更好地掌控代码的执行流程,jQuery等流行的js库都已经实现了这个对象 |
在我们的例子中将使用JQuery的promise,来实现对弹窗逻辑的异步处理。JQuery提供了一个方法jQuery.Deferred(),用以创建一个Deferred对象。Deferred对象从名字可以看出,是一个延迟处理的方案。它提供了多个方法,用以实现在未来的某个时间点执行我们期望的回调。下面用具体的例子说明deferred方法的使用。
我们给上一节的三个弹窗分别命名为a、b、c,现在我们需要实现这样的逻辑,弹窗a弹出后,点击确定则弹出弹窗b,点击取消则弹出弹窗c(当然也可以有更复杂的逻辑,我们这里只是为了说明deferred)。
按照旧方法,我们是需要给弹窗a的两个按钮分别绑定点击处理事件的。对于这种弹窗的通用模块来说,这样做肯定是不好的。下面我们看看promise可以做些什么。
exports.init = function (content,style,buttonConfirm,buttonCancel) { var dtd = $.Deferred(); // 创建一个deferred对象 $('body').append(tpl); /* 中间代码省略 */ pop.find('#survey-confirm-y').click(function() { pop.remove(); dtd.resolve(); // 将deferred对象的执行状态修改为已完成 }); pop.find('#survey-confirm-n').click(function() { pop.remove(); dtd.reject(); // 将deferred对象的执行状态修改为失败 }); return dtd.promise(); // 返回另一个deferred对象 };
首先,我们在init函数中定义一个deferred对象dtd,然后分别在两个按钮的点击事件中调用deferred.resolve()和deferred.reject()方法。Deferred对象有三种状态,进行中,已完成和已失败。而这两个方法就分别为将deferred对象的状态修改为已完成和已失败。当deferred对象的执行状态发生变化后,会触发相应的回调方法,后面会讲到。而init方法会在最后执行deferred.promise()方法来返回另一个deferred对象,也即是我们说的promise。这个promise和原本的deferred对象不同,它不具有对deferred状态进行修改的方法(我们只用promise执行状态改变后的回调)。页面上的弹窗调用代码如下所示:
$('#sea1').click(function(){ seajs.use('sample', function (sa) { sa.init('this is a sample', null, 'Confirm', 'Cancel') .done(function(){ // 已完成状态触发调用done seajs.use('sample', function (sa) { sa.init('this is another sample', null, 'Yes', 'No, Thx'); }); }).fail(function(){ // 已失败状态触发调用fail seajs.use('sample', function (sa) { sa.init('this is a sample', null, null, 'ok'); }); }); }); });
init方法调用后返回promise(也即是deferred对象),然后可以通过deferred.done(callback)和deferred.fail(callback)方法来指定deferred对象状态改变后的回调。示例代码中实现的则是已完成状态时调用弹窗b,而已失败状态时调用弹窗c。除了done和fail,还可以用deferred.then(callbackDone, callbackFail)方法来分别指定成功和失败后的回调。而deferred还有一个方法deferred.always(callback),不论状态值变为失败或成功,都执行指定的回调。当需要等待多个deferred对象都改变状态才执行回调时,可以用jquery.when(dtd1, dtd2)方法。
写到这里,我们想要的这种灵活的弹窗已经duang地弹起来啦~