最近一直在给公司做各种控件,一开始设计控件的时候,老板要求尽量使用简单,最好是开发人员调用一个方法,控件就可以自动生成。所以在控件设计上,都是基于jquery插件的写法,并把所有的事件都封装到内部,然而随着控件越来越多,应用场景越来越广泛,发现这么设计有很大的弊端,比如:不管什么页面,想要用到控件,要把所有的控件js都加载进来(js是经过压缩的)如下图,由于每个控件耦合度较高,在多个控件组合使用时,代码组织起来相当麻烦。使用上的麻烦,维护上的困难,再加上性能问题,导致不得不对其进行重构。
重构思路:基于模块化开发,用reuqirejs进行管理,把每个控件都做成模块化,页面上要使用时,单独的去引用某个模块就可以。多个控件如果要组合使用,以及和页面上的元素做交互的话,在控件名对应的extra.js中去做处理。
下面就是贴代码时间(由于公司有保密规定,这里不方便把整个代码打包和大家分享,只能展示部分代码):
1、工程目录:
2、ProvinceAndCity.js 省份城市多选控件代码
/** * @author:tengri * @time:2017-07-26 * @desc: 酒店城市、省份多选控件 */ define(["jquery"],function($){ /** * 默认配置参数 * @type {{}} */ var defaultOpts = { simpleData: { id: "id", name: "name" }, splitStr: "," //分割符 }; /** * 构造函数 * @param {Object} options * @param {Object} cbFn 回调函数 */ function ProvinceAndCity(elem, options, cbFn) { this.id = "vetech-provinceAndCity-" + String(Math.random()).replace(/D/g, ""); this.elem = elem; this.$elem = $(elem); this.opts = $.extend(true, {}, defaultOpts, options); this.callback = cbFn || $.noop; //回调函数 this.$hiddenInput = $("#" + (this.opts.hiddenName || "")); this.init(); } /** * 初始化整个容器 */ ProvinceAndCity.prototype.init = function () { //外层容器 this.$container = $('<div class="vetech-provinceAndCity-container">'); //toolbar this.$toolbar = $('<div class="toolbar"></div>'); this.$toolbar.append('<strong>选择地区</strong><input type="button" value="确定" />'); //checkedArea this.$checkedArea = $('<div class="checkedArea clear">'); //content this.$content = $('<div class="content clear">'); //cityPanel this.$cityPanel = $('<div class="cityPanel">'); this.$container.append(this.$toolbar).append(this.$checkedArea).append(this.$content); $(document.body).append(this.$container); this.addEvent(); this.bindEvent(); }; /** * 加载数据 * @param url 可以传一个url地址,也可以传一个数据对象 * @param callback 回调函数 */ ProvinceAndCity.prototype.load = function(url,callback){ callback = (callback && $.type(callback) === "function") ? callback :function(){}; var _this = this; if($.type(url) === "string"){ $.ajax({ url:url, data:this.opts.qDatas, success:function(data){ _this.opts.data = data; callback.call(_this); } }) }else{ this.opts.data = url; callback.call(_this); } }; /** * 渲染 */ ProvinceAndCity.prototype.render = function () { var provinceData = this.opts.data || []; var _this = this; $.each(provinceData, function (i, item) { var $span = $(addItem.call(_this, item)); $span.data("citys", item["citys"] || []); //省对应的城市数据 $span.find(".province").data("data", item); //省的数据 _this.$content.append($span); }); }; ProvinceAndCity.prototype.writeValue = function () { var _this = this; if (this.$hiddenInput.length && this.$hiddenInput.val()) { var value = this.$hiddenInput.val(); value = value.split(this.opts.splitStr); //过滤数据,先找出省份,再找出城市 var provinceDatas = [], cityDatas = {}, //城市的用对象字面量来表示 values = [], //文本框中要显示的信息 indexName = this.opts.simpleData.id; for (var i = 0, len = value.length; i < len; i++) { var id = value[i]; innerNoop://命名内圈语句 for (var j = 0, len2 = this.opts.data.length; j < len2; j++) { var provinceItem = this.opts.data[j]; if (id === provinceItem[indexName]) { provinceDatas.push(provinceItem); _this.addCheckedItem.call(_this, provinceItem, "province"); values.push(provinceItem[_this.opts.simpleData.name]); break; } else { var citys = provinceItem["citys"]; //城市 for (var k = 0, len3 = citys.length; k < len3; k++) { var city = citys[k]; if (id === city[indexName]) { cityDatas[city.pid] = cityDatas[city.pid] || []; cityDatas[city.pid].push(city); _this.addCheckedItem.call(_this, city, "city"); values.push(city[_this.opts.simpleData.name]); break innerNoop; } } } } } this.$elem.val(values.join(this.opts.splitStr)); //省份回调 $.each(provinceDatas, function (i, provinceValue) { var $span = $("#" + provinceValue[_this.opts.simpleData.id]), $input = $span.find(".province"); $span.css("color", "orange"); setChecked($input,true); _this.$container.trigger("choose.provinceAndCity", $input); }); //城市进行回调 $.each(this.$container.find(".province"), function (i, input) { var $input = $(input), currData = $input.data("data"), $span = $input.parents(".item"), checkedCitys = cityDatas[currData[_this.opts.simpleData.id]]; if (checkedCitys) { $span.data("checkedData", checkedCitys); $span.css("color", "orange"); } }); } }; ProvinceAndCity.prototype.bindEvent = function () { var _this = this; this.$container.on("click.provinceAndCity", "span.item", function (ev) { //由于cityPanel加在span中,所以点击cityPanel中的内容时,也会出发该事件,所以在这里要判断一下 if ($(ev.target).hasClass("item") || $(ev.target).hasClass("collapse")) { _this.$container.find(".detail").hide(); _this.$container.trigger("expand.provinceAndCity", this); } }).on("mouseleave.provinceAndCity", "span.item", function () { _this.$container.trigger("collapse.provinceAndCity", this); }).on("click.provinceAndCity", "input[type='checkbox']", function () { _this.$container.trigger("choose.provinceAndCity", this); }).on("click.provinceAndCity", ".expand", function () { _this.$container.trigger("collapse.provinceAndCity", $(this).parents(".item")); }); this.$checkedArea.on("click.provinceAndCity", ".close", function () { _this.$container.trigger("delete.provinceAndCity", $(this).parent()); }); this.$toolbar.on("click.provinceAndCity", "input", function () { _this.$container.trigger("sure.provinceAndCity"); _this.hide(); }); }; /** * 添加自定义事件 */ ProvinceAndCity.prototype.addEvent = function () { var _this = this; //自定义展开函数(点击省份,展开城市) this.$container.on("expand.provinceAndCity", function (e, span) { var $span = $(span); $span.find(".detail").show(); _this.$cityPanel.html(""); $.each($span.data("citys"), function (i, item) { var $label = $('<label id="' + item[_this.opts.simpleData.id] + '" class="ellipsis" title="' + item[_this.opts.simpleData.name] + '"><input type="checkbox" />' + item[_this.opts.simpleData.name] + '</label>'); $label.find("input").data("data", item); _this.$cityPanel.append($label); }); //展开城市的时候,要进行回填 if ($span.find(".province").is(":checked")) { var $inputs = _this.$cityPanel.find("input"); setChecked($inputs,true); $inputs.attr("disabled","disabled"); } else { var tempData = $span.data("checkedData") || []; $.each(tempData, function (i, item) { setChecked(_this.$cityPanel.find("#" + item[_this.opts.simpleData.id]).find("input"),true); }); } $span.append(_this.$cityPanel.show()); }); //自定义折叠函数(点击折叠,关闭城市弹出层) this.$container.on("collapse.provinceAndCity", function (e, span) { var $span = $(span); $span.find(".detail").hide().end().find(".cityPanel").hide(); }); //input checked事件 this.$container.on("choose.provinceAndCity", function (e, input) { var $span = $(input).parents(".item"), $input = $(input), currData = $input.data("data"); //当前选择的input框对应的数据值 if ($input.hasClass("province")) { if ($input.is(":checked")) { var $inputs = _this.$cityPanel.find("input"); setChecked($inputs,true); $inputs.attr("disabled","disabled"); $span.data("checkedData", []); //清除选中的data _this.addCheckedItem.call(_this, currData, "province"); } else { var $inputs = _this.$cityPanel.find("input"); setChecked($inputs,false); $inputs.removeAttr("disabled"); $.each(_this.$checkedArea.find(".checkedItem"), function (i, item2) { var $item = $(item2), td = $item.data("data"); if (currData[_this.opts.simpleData.id] === td[_this.opts.simpleData.id]) $item.remove(); }); } } else { var tempData = $span.data("checkedData") || []; if ($input.is(":checked")) { //把选择的城市放入缓存 tempData.push(currData); _this.addCheckedItem.call(_this, currData, "city"); } else { //当没有选中的时候,要在数据缓存中过滤掉被取消勾选的数据 var newData = tempData; tempData = []; $.each(newData, function (i, item) { if (currData[_this.opts.simpleData.id] !== item[_this.opts.simpleData.id]) { tempData.push(item); } else { $.each(_this.$checkedArea.find(".checkedItem"), function (i, item2) { var $item = $(item2), td = $item.data("data"); if (item[_this.opts.simpleData.id] === td[_this.opts.simpleData.id]) $item.remove(); }); } }); } $span.data("checkedData", tempData); } }); //删除操作(拿到所有省的节点,判断当前删除的是不是省的数据,如果是省的数据,直接删除已经选择的节点,把该省对应的checkbox设置为不选中并执行choose.provinceAndCity函数,如果不是省的数据,则要遍历每个省下面的缓存已选中的城市,并踢出该城市) this.$container.on("delete.provinceAndCity", function (e, span) { var currData = $(span).data("data"); $.each(_this.$container.find(".province"), function (i, input) { var $input = $(input); var tempData = $input.data("data"); if (currData[_this.opts.simpleData.id] === tempData[_this.opts.simpleData.id]) { setChecked($input,false); _this.$container.trigger("choose.provinceAndCity", input); } var $pSpan = $input.parents(".item"); var newData = []; $.each($pSpan.data("checkedData") || [], function (i, itemValue) { if (itemValue[_this.opts.simpleData.id] !== currData[_this.opts.simpleData.id]) { newData.push(itemValue); } }); $pSpan.data("checkedData", newData); }); $(span).remove(); }); //确认操作 this.$container.on("sure.provinceAndCity", function () { var resultData = [], ids = [], values = []; $.each(_this.$checkedArea.find(".checkedItem ") || [], function (i, item) { var itemValue = $(item).data("data"); ids.push(itemValue[_this.opts.simpleData.id]); values.push(itemValue[_this.opts.simpleData.name]); resultData.push(itemValue); }); _this.$elem.val(values.join(_this.opts.splitStr)); _this.$hiddenInput.length && _this.$hiddenInput.val(ids.join(_this.opts.splitStr)); //执行回调 _this.callback(resultData); }); }; /** * 添加选择的项目 * @param {Object} data * @param {Object} type */ ProvinceAndCity.prototype.addCheckedItem = function (data, type) { var needAdd = true; //标记是否需要添加 if (type === "province") { //先检查已经选择的数据中,是否有省内的城市,如果有省内城市,则清除 var id = this.opts.simpleData.id; $.each(this.$checkedArea.find(".checkedItem"), function (i, item) { var $item = $(item), tempData = $item.data("data"); if (tempData["pid"] === data[id]) { $item.remove(); }else if(tempData[id] === data[id]){ needAdd= false; return false; } }); } needAdd && this.$checkedArea.append(createCheckedItem.call(this, data)); }; function createCheckedItem(data) { var $span = $('<span class="checkedItem ellipsis">' + data[this.opts.simpleData.name] + '<i class="close"></i></span>'); $span.data("data", data); return $span; } /** *显示 */ ProvinceAndCity.prototype.show = function () { this.setPos(); this.$container.css("visibility", "visible"); }; ProvinceAndCity.prototype.setPos = function () { var pointer = this.$elem.offset(); this.$container.css({"left": pointer.left, "top": pointer.top + this.$elem.outerHeight()}); }; ProvinceAndCity.prototype.hide = function () { this.$container.css({"visibility": "hidden", "left": "-1000px", "top": "-1000px"}); }; ProvinceAndCity.prototype.destroy = function () { //移除该控件绑定的所有的事件 this.clear(); this.$container.remove(); delete this; }; ProvinceAndCity.prototype.clear = function(){ this.$container.off(".provinceAndCity"); this.$checkedArea.off(".provinceAndCity"); this.$toolbar.off(".provinceAndCity"); }; /** * 添加item * @param {Object} item */ function addItem(item) { return '<span class="item" id="' + item[this.opts.simpleData.id] + '">' + '<i class="collapse"></i>' + item[this.opts.simpleData.name] + '' + '<div class="detail">' + '<i class="expand"></i>' + '<label class="ellipsis"><input type="checkbox" class="province" value="' + item[this.opts.simpleData.id] + '" />' + item[this.opts.simpleData.name] + '</label>' + '</div>' + '</span>'; } /** * 设置选中 * @param $inputs * @param state */ function setChecked($inputs,state){ $.each($inputs, function(i,input) { input.checked = state; }); } return ProvinceAndCity; });
3、provinceAndCityExtra.js 代码的二次封装
/** * Created by zh on 2017/8/11. * @desc:酒店身份城市多选控件封装 */ define(["jquery","js/core/ProvinceAndCity"],function($,ProvinceAndCity){ var data = ssqx; var provinceData = data[0].data, cityData = data[1].data; var newData = []; $.each(provinceData, function(i,item) { item.type = "p"; item.citys = getCitysByProvinceId(item.id,cityData); newData.push(item); }); delete data; delete provinceData; delete cityData; $.fn.showProvinceAndCity = function(options,cbFn){ var pc = new ProvinceAndCity(this.get(0),options,cbFn); pc.load(newData,function(){ this.render(); this.writeValue(); }); this.on("click",function(){ pc.show(); }); }; function getCitysByProvinceId(pId,cityData){ var cityDatas = []; $.each(cityData, function(i,item) { if(item.pid === pId){ item.type ="c"; cityDatas.push(item); } }); return cityDatas; } });
4、页面入口文件main.js
/** * Created by zh on 2017/8/11. */ requirejs.config({ baseUrl:"http://localhost:63343/WS_webStorm/components/", //后面应用到项目中,会是项目的一个地址,用全局变量basePath代替 paths:{ "jquery":"lib/jquery-1.9.1", "hotel":"js/fl/hotel/", "common":"js/fl/common/" } }); require(["jquery","hotel/provinceAndCityExtra","common/yearAndMonthExtra"],function($,b){ $("#txt1").showProvinceAndCity({ hiddenName:"txt2" },function(data){ console.log(data); }); $("#yearAndMonthDiv").showYearAndMonth({ startTime:1933, disabledLater:true, yearName:"year", montName:"month" },function(select,value){ console.log(select); console.log(value); }); });
5、页面文件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>所有控件例子入口</title> <link rel="stylesheet" type="text/css" href="../css/provinceAndCity.css"/> <!--这里是静态数据文件,模拟ajax请求--> <script type="text/javascript" src="../data/ssqx.js" ></script> <script type="text/javascript" src="../lib/require.js" data-main="main"></script> </head> <body> <h1>酒店省份和城市多选控件</h1> 请选择省市:<input type="text" id="txt1" /><input type="text" id="txt2" value="10114,10113,10435,10333,02013,10176,10452,10858,10189,10216,10444,02028" /> <h1>年月控件</h1> <div id="yearAndMonthDiv"></div> </body> </html>
6、效果图:
以上看似风平浪静,顺理成章。然而,突然眉头一紧,模块化以后,拆分了这么多js文件,怎么进行压缩合并呢?
先在requirejs官网上找了一把,看到了r.js,然后再一搜索,找到了一篇文章。地址:http://www.oschina.net/translate/optimize-requirejs-projects
真是柳暗花明又一村,二话不说,直接跟着文章上的描述开始操作,一把成功。截图如下:
然
并
卵。。。。。。。
我司项目都是管理型的后台项目,又是十几个产品线在一起的,每个产品线又有几十个甚至上百个jsp文件。按照requirejs提供的压缩方式(根据入口函数,把每个页面需要的模块合并压缩到main.js中),我这要如何去做?难道每次新增一个入口函数,就要去改build.js配置文件?我在想,是否有自动化构建的工具呢,可以直接根据每个文件夹下的main.js文件进行打包呢?还是说有更好的办法?
我已黔驴技穷,但是我相信互联网的力量。我想正在看的你,还有即将看的你,肯定会有自己的想法,那为什么不留下你的想法和交流方式呢?山不转水转,说不定哪天我们又有缘相见呢?
求赐教!!!