/** * License: MIT * Url: https://github.com/linjingming/sim-tree */ ;(function (window, factroy) { if (!window || !window.document) { throw new Error('simTree need window') } factroy(window); })(typeof window !== 'undefined' ? window : this, function (window) { "use strict"; var name = 'simTree'; var version = '0.0.2'; var document = window.document; var defaultConfig = { linkParent: false, response: { name: 'name', id: 'id', pid: 'pid', checked: 'checked', open: 'open', expand: 'expand', disabled: 'disabled' } }; var err = function (msg) { throw new Error(msg); }; var simTpl = function (tpl, data) { return tpl.replace(/\{\{(.+?)\}\}/g, function ($1, $2) { return data[$2] ? data[$2] : ''; }); }; var Class = function (options) { if (typeof $ === 'undefined') { err(name + 'need jquery'); } if (!$.isPlainObject(options)) return; if (!options.el) { err('你没有传el'); } if (!(this instanceof Class)) { return new Class(options); } this.options = $.extend(true, {}, defaultConfig, options); this.init(); }; //初始化时候 用 checkId 存储选中的Id, 用openId 存储要展开的节点Id, var $prevA, checkId = [], openId = []; Class.prototype = { version: version, constructor: Class, /** * 绑定自定义事件 * * @param type {string} 自定义事件名称 * @param cb {function} 回调函数 * @param isCover {boolean} 是否覆盖之前的回调 * @return this {object} 实列对象 */ on: function (type, cb, isCover) { var isTriggered, args; this.handles[type] = this.handles[type] || []; isTriggered = this.handles[type].isTriggered; args = this.handles[type].args; if ($.isFunction(cb)) { if (isCover === true) { this.handles[type] = [cb]; } else { this.handles[type].push(cb); } if (isTriggered) { cb.call(this, args); } } return this; }, // 解除绑定的自定义事件 off: function (type) { this.handles[type] = []; return this; }, // 触发自定义事件 trigger: function (type, args) { var i, len; this.handles[type] = this.handles[type] || []; i = 0; len = this.handles[type].length; // isTriggered 表示已经触发过 this.handles[type].isTriggered = true; // args 参数 this.handles[type].args = args; for (; i < len; i++) { this.handles[type][i].call(this, args); } }, init: function () { var options = this.options; var data = options.data; this.handles = {}; this.$el = $(options.el); this.data = data; this.event(); this.render(); }, /** * */ dataCallback: function () { var args = arguments; if (args.length === 1) { this.render(args[0]); } else { this.doRender(args[0], args[1]); } }, /** * 把data解析成树型数据 */ parse: function (data) { var options = this.options; var response = options.response; var res = []; var map = {}; var i = 0; var len = data.length; var id = response.id; var pid = response.pid; // 如果子节点异步加载 直接返回 if (options.childNodeAsy) { return data; } for (; i < len; i++) { var item = data[i]; var tempId = item[id]; // 如果节点有children这个字段则判断数据本就是树型结构 直接返回原始数据 if (item.children) { return data; } if (tempId) { map[tempId] = item } } for (i = 0; i < len; i++) { var item = data[i]; var tempPid = item[pid]; var parent = map[tempPid]; if (tempPid && parent) { (parent.children || (parent.children = [])).push(item); } else { res.push(item); } } return res; }, render: function (data) { var data = data || this.data; if ($.isFunction(data)) { data({}, this.dataCallback.bind(this)) } if ($.isArray(data)) { data = this.parse(data); this.doRender(this.$el, data); } }, doRender: function ($el, data, level) { var self = this; var options = this.options; var response = options.response; var len = data.length; var i = 0; var item; var id = response.id; var text = response.name; var level = level || 1; var tpl = '{{text}}'; var isRootEle = ($el === this.$el); var $outEl = $(document.createElement('ul')); var oLi, $li, hasChild, disabled, expand; var asy = options.childNodeAsy ? 'asy' : ''; if (false && !options.check) { // 单选 tpl = tpl.replace('', ''); } for (; i < len; i++) { item = data[i]; oLi = document.createElement('li'); hasChild = !!item.children; disabled = item[response.disabled]; expand = item[response.expand] || false; oLi.innerHTML = simTpl(tpl, { asy: asy, text: item[text], spreadIcon: hasChild ? (expand ? 'sim-icon-d' : 'sim-icon-r') : 'sim-hidden' }); oLi.setAttribute('data-level', level); oLi.setAttribute('data-id', item[id]); disabled && oLi.setAttribute('class', 'disabled'); $li = $(oLi); $li.data('data', item); if (expand) { $outEl.addClass("show"); } $outEl.append($li); if (hasChild) { this.doRender($li, item.children, level + 1); } item[response.checked] && checkId.push(item[id]); item[response.open] && openId.push(item[id]); } len && $el.append($outEl); if (isRootEle) { $outEl.addClass('sim-tree'); this.trigger('done', data); $.each(openId, function (index, id) { self.expandNode(id); }); this.setSelected(checkId); } else { // 异步加载移除loading if (options.childNodeAsy) { this.hideLoading($el.find('.sim-tree-spread')); $outEl.addClass('show'); } } }, /** * */ event: function () { var self = this; this.$el.off('click').on('click', function (e) { var $tar = $(e.target); if ($tar.hasClass('sim-tree-spread')) { self.spread.call(self, $tar); } if ($tar.hasClass('sim-tree-checkbox')) { $tar = $tar.parent(); } if ($tar[0].tagName.toLowerCase() === 'a') { self.clickNode.call(self, $tar); } return false; }); this.$el.on('selectstart', function () { return false; }); if (this.options.done) { this.on('done', this.options.done); } if (this.options.onClick) { this.on('click', this.options.onClick); } if (this.options.onChange) { this.on('change', this.options.onChange); } if (this.options.onSearch) { this.on('search', this.options.onSearch); } }, // 展开或收起 spread: function ($el) { if ($el.hasClass('sim-icon-r')) { this.doSpread($el, true); } else { this.doSpread($el, false); } }, // 添加loading showLoading: function ($el) { $el.addClass('sim-loading'); }, // 移除loading hideLoading: function ($el) { $el.removeClass('sim-loading'); }, /** * 展开或者收起 * @param $el {jquery object} jquery对象 * @param status {boolean} true 展开 false收起 */ doSpread: function ($el, status) { var $pli = $el.parent(); var $ul = $pli.children('ul'); var item = $pli.data('data'); if (!item.children) return; if (status) { $el.removeClass('sim-icon-r').addClass('sim-icon-d'); // 异步加载子节点 if ($el.data('type') === 'asy' && $.isFunction(this.data)) { this.showLoading($el); this.data($pli.data('data'), this.dataCallback.bind(this, $pli)); $el.data('type', ''); } $ul.addClass('show'); } else { $el.removeClass('sim-icon-d').addClass('sim-icon-r'); $ul.removeClass('show'); } }, // 点击节点 clickNode: function ($tar) { var self = this; var $pli = $tar.parent(); var $li = this.$el.find('li'); var len = $li.length; var i = 0; var list = []; var data, $childUl, $childCheck; var isChange = false; if ($pli.hasClass('disabled')) return; if (this.options.check) { isChange = true; this.doCheck($tar.find('.sim-tree-checkbox')); if (this.options.linkParent) { $childUl = $pli.children('ul'); $childCheck = $childUl.find('.sim-tree-checkbox'); $.each($childCheck, function () { self.doCheck($(this), $pli.data('checked'), true); }); } for (; i < len; i++) { data = $li.eq(i).data(); if (data['checked'] === true) { list.push(data.data); } } } else { //单选模式下仅保留当前选择 $(".sim-tree-checkbox", this.$el).removeClass("checked"); $(".sim-tree-checkbox", $tar).addClass("checked"); if ($prevA) { $prevA.css('font-weight', 'normal'); } $tar.css('font-weight', 'bold'); $prevA = $tar; data = $pli.data('data'); list = [data]; if (this.sels) { isChange = !(this.sels[0] === data); } else { isChange = true; } } this.sels = list; this.trigger('click', list); isChange && this.trigger('change', list); }, // 多选设置选中状态 doCheck: function ($check, status, flag) { var self = this; var $li = $check.closest('li'); var $childUl, $childUlCheck; var data = $li.data(); if (typeof status === 'undefined') { status = !data.checked; } if (status === true) { $check.removeClass('sim-tree-semi').addClass('checked'); } else if (status === false) { $check.removeClass('checked sim-tree-semi'); } else if (status === 'semi') { $check.removeClass('checked').addClass('sim-tree-semi'); } $li.data('checked', status); if (this.options.linkParent === true) { !flag && this.setParentCheck($li); } }, setParentCheck: function ($li) { var $pul = $li.parent('ul'); var $pli = $pul.parent('li'); var $lis = $pul.children('li'); var $plicheck = $pli.find('>a .sim-tree-checkbox'); var checked = []; var maxLen = $lis.length; var isChecked, checkedLen; if (!$pli.length) return; if ($li.find('>a .sim-tree-checkbox').hasClass('sim-tree-semi')) { this.doCheck($plicheck, 'semi'); return; } // 获取同级的选中状态 $.each($lis, function () { isChecked = $(this).data('checked'); isChecked === true && checked.push($(this)); }); checkedLen = checked.length; // 如果选中长度跟li长度一样 则父级选中 if (maxLen === checkedLen) { this.doCheck($plicheck, true); } // 如果选中长度为0 则父级没选中 if (!checkedLen) { this.doCheck($plicheck, false); } // 如果没有全选中 则父级设置成半选状态 if (checkedLen >= 1 && checkedLen < maxLen) { this.doCheck($plicheck, 'semi'); } }, // 树搜索 search: function (val) { if (!this.$el) return; var val = $.trim(val); var $li = this.$el.find('li'); var i = 0; var len = $li.length; var text, $mLi, data; var res = []; var reg = new RegExp(val, 'i'); $li.hide().children('.sim-tree-spread').addClass('sim-hidden'); for (; i < len; i++) { $mLi = $li.eq(i); text = $mLi.children('a').text(); data = $mLi.data('data'); if (!val) { $mLi.show(); if (data.children) { $mLi.children('.sim-tree-spread').removeClass('sim-hidden'); } } else if (text.search(reg) !== -1) { if (parseInt($mLi.data('level')) !== 1) { // 不是顶级的需要展开父节点 this.expandNode(data[this.options.response.pid]); } $mLi.parents('li').add($mLi).show(); res.push($mLi); } } this.trigger('search', val); }, // 展开某节点 expandNode: function (id) { var $li = id.addClass ? id : this.$el.find('[data-id=' + id + ']'); var data = $li.data('data'); var pid = data[this.options.response.pid]; var $spread = $li.children('.sim-tree-spread'); var level = parseInt($li.data('level')); if (data.children && $spread.length) { $spread.removeClass('sim-hidden'); this.doSpread($spread, true); } if (level !== 1) { this.expandNode(pid); } }, // 设置选中 setSelected: function (id) { var self = this; var aId = id; var aData = []; var res = []; if (typeof (aId) === 'string' || typeof (aId) === 'number') { aId = [aId]; } if (!$.isArray(aId)) { return; } if (!this.options.check) { // 单选 aId = [aId[0]]; } $.each(aId, function (index, id) { var $li = self.$el.find('[data-id=' + id + ']'); var $a = $li.children('a'); var $check = $a.children('.sim-tree-checkbox'); var data = $li.data('data'); if (!$li.length) { return true; } if (!$check.length) { // 单选 $a.css('font-weight', 'bold'); } else { // 多选 self.doCheck($check, true); } if (parseInt($li.data('level')) !== 1) { // 不是顶级的需要展开父节点 self.expandNode(data[self.options.response.pid]); } aData.push(data); res.push($li[0]); }); self.sels = aData; self.trigger('click', aData); }, // 获取选中值 getSelected: function () { return this.sels; }, // 设置禁止选中 disableNode: function (id) { var self = this; var aId = id; if (typeof (aId) === 'string' || typeof (aId) === 'number') { aId = [aId]; } if (!$.isArray(aId)) { return; } $.each(aId, function (index, id) { var $li = self.$el.find('[data-id=' + id + ']'); $li.addClass('disabled'); }); }, // 树销毁 destroy: function () { this.$el.html(''); for (var key in this) { delete this[key] } delete this; }, // 树刷新 refresh: function (data) { this.$el.html(''); this.render(data); } } window[name] = Class; $.fn[name] = function (opt) { opt = $.extend(true, {el: this}, opt); return Class(opt); }; return Class; });