先说一下当前的情况:
来到新公司也已经一个多月,今天是十月三十号,所以,算起来是整整50天(如果是百天,那么小孩子要办百天酒,情侣的这天可能也会过的开心很多)。虽然来了五十天,但是实际上感觉工作了25天左右,因为是第一个项目做完之后,因为人手比较紧张吧,没有后台来,还有需求这块也没有一个需求书,所以我是持续被闲置的状态,所以就每天看看书,小黄书(你不知道的JS)或者是读一读其他大神的博客,要么就是看看代码划划水啥的。这不前两天磊哥让我帮他做一个页面,可能是看不下去了吧哈哈哈。
是一个比较简单的页面 ,那么这个页面的功能和交互:
左侧复选框和右侧列表框联动,右侧列表框有拖拽功能。
基本来说就是这两个。使用Jquery和easyUI。
可能有疑问啊:没啥难的记录他干嘛?
1.是我第一次进行这种封装,对我来说确实是个挑战。
2.不实战就真的不能真正理解看的书中是什么意思。
先把代码贴出来
var Tools = {}; Tools.fieldCheck = (function () { // 初始化复选框数据 var Fn = function (obj) { this.checkArray = [];//被选选项 this.total = 0;// 选项总数,用于确认是否已全选 this.initObj = obj.data;//初始化数组 this.commitFn = obj.commit;//提交的回调函数 this.cancelFn = obj.cancel;//取消的回调函数 this.init(); }; Fn.prototype = { constructor: Fn, init: function () { var str = this.getDomStr(this.initObj); $('#checks').html(str); this.initRender(this.initObj); this.eventBind(); }, /** * @param {Array} array 数据数组用以初始化,控制禁用和勾选回显 * @returns {null} 无返回值 */ initRender: function (array) { var _this = this; $.each(array, function (index, element) { var child = element.child; child.forEach(function (ele) { //总数计算 用来控制 _this.total++; if (ele.checked) { $('#' + ele.id).attr('checked', 'checked'); var checked = ele.label + '-' + ele.fieldName + '-' + ele.id + '-' + ele.disabled; _this.reRender(true, checked); } if (ele.disabled) { $('#' + ele.id).attr('disabled', 'disabled'); } }); }); }, /** * 功能 初始化DOM * @param {Array} array 复选框数据数组 * @returns {String} str 组成复选框的HTML */ getDomStr: function (array) { var str = '<div>'; for (var i = 0; i < array.length; i++) { var p = array[i]; str += '<div><div class="title ">' + p.label + '</div>'; str += '<div class="items" >'; var child = p.child; for (var j = 0; j < child.length; j++) { str += '<div class="item"><input type="checkBox" data-toggle="topjui-checkbox" '; //data-Info 自定义属性存储复选框信息 label、fieldName、disabled、id str += 'data-info="' + child[j].label + '-' + child[j].fieldName + '-' + child[j].id + '-' + child[j].disabled + '" id=' + child[j].id + '>'; str += '<label for="' + child[j].id + '">' + child[j].label + '</label></input></div>'; } str += '</div></div>'; } return str; }, /** * 事件绑定 * @param {function} commitFn 提交按钮回调 * @param {function} cancelFn 取消按钮回调 * @returns {null} 无返回值 */ eventBind: function () { var _this = this; $('#checks').click(function (event) { event = event || window.event; if (event.target.tagName === 'INPUT') { var datastr = $(event.target).attr('data-info'); var dataArray = datastr.split('-'); var dataInfo = dataArray[0] + '-' + dataArray[1] + '-' + dataArray[2] + '-' + dataArray[3]; //存在 var index = _this.checkArray.indexOf(dataInfo); if (index > -1) { _this.reRender(false, dataInfo); } else { _this.reRender(true, dataInfo); } } }); $('#checkAll').click(function (event) { // 判断按钮状态 event = event || window.event; //被选中 $('#checkList').html(''); _this.checkArray = []; if ($('#checkAll:checked').length > 0) { $.each(_this.initObj, function (index, element) { var child = element.child; $.each(child, function (i, ele) { var checked = ele.label + '-' + ele.fieldName + '-' + ele.id + '-' + ele.disabled; _this.reRender(true, checked); }); }); $('#checks input:checkbox').prop('checked', true); } else { $(':checked').prop('checked', false); } }); //确定按钮 $('commit').click(function (event) { event = event || window.event; _this.commitFn(event, this.handleChecked()); }); $('#cancel').click(function (event) { event = event || window.event; _this.cancelFn(event); }); $('#checkList').click(function (event) { event = event || window.event; if (event.target.tagName === 'A') { _this.reRender(false, $(event.target).attr('data-info')); } }); }, /** * @param {boolean} flag true 为新增 false为删除 * @param {str} info 数据字符串 * @returns {null} 无返回值 */ reRender: function (flag, info) { var _this = this; var infoArray = info.split('-'); //新增 if (flag) { this.checkArray.push(info); var str = '<div type="text" class="label" id="' + infoArray[1] + '">'; str += '<div id="' + infoArray[1] + '_drag" >' + infoArray[0]; if (infoArray[3] !== 'true') { str += '<a href="javascript:;" data-info="' + info + '" class="textbox-icon icon-clear clear " />'; } str += '</div></div>'; $('#checkList').append(str); if ($('#checkList').children().length === this.total) { $('#checkAll').prop('checked', true); } else { $('#checkAll').prop('checked', false); } // 如果全部选中则勾选全选按钮 $('#' + infoArray[1]).draggable({ handle: '#' + infoArray[1] + '_drag', axis: 'v', proxy: function (source) { var p = $('<div style="border:2px solid #ccc;width:80px"></div>'); p.html($(source).html()).appendTo('body'); return p; }, onBeforeDrag: function (e) { //点击A标签停止拖动 if (e.target.tagName == 'A') { return false; } $('.label').droppable({ accept: '#' + infoArray[1], onDrop: function (e, source) { $(e.target).removeClass('ondrop'); // $(source).removeClass("ondrop"); //获取到当前降落的元素 // e为被降落的元素; // source为降落元素 var id = e.target.id; //清除由于拖拽产生的绝对定位样式 $(source).css('position', 'static'); $(source).css('top', 0); $(source).css('left', 0); $('#' + id).before(source); _this.resort(source.id, id); }, onDragEnter: function (e, source) { $(e.target).addClass('ondrop'); }, onDragLeave: function (e, source) { $(e.target).removeClass('ondrop'); }, }); }, onStopDrag: function (e) { $('#' + infoArray[1]).css('position', 'static'); $('#' + infoArray[1]).css('top', 0); $('#' + infoArray[1]).css('left', 0); }, }); } else { this.checkArray.splice(this.checkArray.indexOf(info), 1); $('#' + infoArray[1]).remove(); $('#' + infoArray[2]).prop('checked', false); $('#checkAll').prop('checked', false); } }, /** * 功能 重排数组顺序使与HTML顺序保持一致 * @param {any} prev 拖动元素 * @param {any} next 被插入元素 * @returns {null} 无返回值 */ resort: function (prev, next) { var prevIndex = -1; var nextIndex = -1; $.each(this.checkArray, function (index, ele) { var infoArray = ele.split('-'); if (infoArray[1] === next) { nextIndex = index; } if (infoArray[1] === prev) { prevIndex = index; } }); this.checkArray.splice(nextIndex, 0, this.checkArray[prevIndex]); var k = prevIndex > nextIndex ? 1 : 0; this.checkArray.splice(prevIndex + k, 1); }, getChecked: function () { return this.handleChecked(); }, /** * 功能:将已选中的字符串对象转为对象 * @returns {null} 已选对象数组 */ handleChecked: function () { var array = []; $.each(this.checkArray, function (index, ele) { var objArray = ele.split('-'); array.push({ label: objArray[0], fieldName: objArray[1], id: objArray[2] }); }); return array; }, }; return Fn; }());
并没有贴出HTML:)
下面写下我的收获吧:
1.现在面向对象思想开发,感觉也就是原子性的体现,同时也是可能也是高内聚,低耦合的一种体现。
一个组件公开和私有的是要分清楚的,比如我有些方法实际上并不是接口的,但是我还是把他放在了原型链对象上,这是不应该的,因为这是一个立即执行函数,所以函数内也是可以声名一些函数的,即
(function(){
/*可以写一些公共函数供构造函数中的函数公用,能找到是因为作用域链的原因。这样这些函数就成了封闭性的函数,而不是接口了,不过不好的应该是函数会一直在内存中存在,因为原型对象的调用会持有他//的引用,所以这些公共函数无法退出内存。,但是我这个做的并不好,因为我全都放在了原型链上,让一个对象很笨重,另外就是给使用者一种可以调用的感觉,其实是这些函数是不能调用的,擅自调用甚至会有一些问题,以后这个问题就要避免,可以问一问磊哥,感觉这样是不对的。*/
}());
2.是实现思路上的一个问题:
先说一下整体思路:传入一个data ,data中的对象会有四个属性:label fieldName id checked disabled child 六个属性 checked和disabled用来控制复选框状态 ,child是子数组,属性和父级一样,然后一个数组checkedList用来记录被勾选的选项,渲染左侧复选框时,我会把元素信息存在标签上的一个自定义属性中,这么做的原因是:因为点击了复选框,那么就需要在右侧列表添加一个或者删除一个列表元素,但是我不想再点击复选框时,再去data中遍历查询一些信息,因为data是有层级关系,遍历起来很麻烦,我实在不想再前端进行很多的循环,所以我就将所需的信息存了起来,然后再新增或者删除时,就直接拿到信息,然后查询元素信息,就可以直接进行操作了。但是有一个弊端,checkedList中的元素是一个平面一个字符串(有人就问,为啥要用字符串呢?你直接把对象序列化存进去,然后反序列化放数组里多好,反序列化会增加一个问题,就是在数组中找到一个对象,是不行的,因为数组的indexOf使用的是‘===’,而当我去验证我的checkedList中是否有这个对象时,那就还要写forEach,并且去比较属性的值,我觉得更加复杂。)会让数据看起来有些奇怪。然后就是在取数据是也会让他看起来怪怪的,就是要反序列化,我是使用分隔符 '-'进行分割的,所以就代码就看起来很麻烦。写这一段时总想还不如循环,现在想想循环可能更加的简洁,只是启动很多次循环会让我有点烦。我还是更喜欢索引,够直接。我采用这个方案的初衷就是为了提高性能。可能我觉得代码上使他看起来更麻烦。代码逻辑也复杂了不少。究竟孰优孰劣,我想这也不是一个问题,因为这个功能太小了,以至于没人会在意他,但是我还是希望能真正注意执行的效率。尽管我的处理实际上可能是更糟糕的,如果哪位大神觉得确实是这样,或者是有更好的方案,一定要给我留言,谢谢!
3.我想到另外一种实现,就在遍历时就直接创建出所有的元素 形成一个map 左侧复选框和右侧列表元素的map ,使用使用id作为属性值,对象作为属性值,另外一个map使用另外一个属性名作为属性值,这样再查询dom时更加的方便,这样就可以实现dom复用,然后要在右侧列表中添加一个属性控制是否显示,其实是是否被勾选的标记这样就从重新构建元素变成了存取元素。不知道jquery被easyUI修改后还能不能存储?还有很多疑问...
4.不知道我的上述思考会不会显得不太聪明的样子哈哈哈,请见谅。
那我就继续看书去了,拜拜!