最近一直在给公司做各种控件,一开始设计控件的时候,老板要求尽量使用简单,最好是开发人员调用一个方法,控件就可以自动生成。所以在控件设计上,都是基于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;
});
View Code
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文件进行打包呢?还是说有更好的办法?
我已黔驴技穷,但是我相信互联网的力量。我想正在看的你,还有即将看的你,肯定会有自己的想法,那为什么不留下你的想法和交流方式呢?山不转水转,说不定哪天我们又有缘相见呢?
求赐教!!!
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。