/**
* 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;
});