simtree.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /**
  2. * License: MIT
  3. * Url: https://github.com/linjingming/sim-tree
  4. */
  5. ;(function (window, factroy) {
  6. if (!window || !window.document) {
  7. throw new Error('simTree need window')
  8. }
  9. factroy(window);
  10. })(typeof window !== 'undefined' ? window : this, function (window) {
  11. "use strict";
  12. var name = 'simTree';
  13. var version = '0.0.2';
  14. var document = window.document;
  15. var defaultConfig = {
  16. linkParent: false,
  17. response: {
  18. name: 'name',
  19. id: 'id',
  20. pid: 'pid',
  21. checked: 'checked',
  22. open: 'open',
  23. expand: 'expand',
  24. disabled: 'disabled'
  25. }
  26. };
  27. var err = function (msg) {
  28. throw new Error(msg);
  29. };
  30. var simTpl = function (tpl, data) {
  31. return tpl.replace(/\{\{(.+?)\}\}/g, function ($1, $2) {
  32. return data[$2] ? data[$2] : '';
  33. });
  34. };
  35. var Class = function (options) {
  36. if (typeof $ === 'undefined') {
  37. err(name + 'need jquery');
  38. }
  39. if (!$.isPlainObject(options)) return;
  40. if (!options.el) {
  41. err('你没有传el');
  42. }
  43. if (!(this instanceof Class)) {
  44. return new Class(options);
  45. }
  46. this.options = $.extend(true, {}, defaultConfig, options);
  47. this.init();
  48. };
  49. //初始化时候 用 checkId 存储选中的Id, 用openId 存储要展开的节点Id,
  50. var $prevA, checkId = [], openId = [];
  51. Class.prototype = {
  52. version: version,
  53. constructor: Class,
  54. /**
  55. * 绑定自定义事件
  56. *
  57. * @param type {string} 自定义事件名称
  58. * @param cb {function} 回调函数
  59. * @param isCover {boolean} 是否覆盖之前的回调
  60. * @return this {object} 实列对象
  61. */
  62. on: function (type, cb, isCover) {
  63. var isTriggered, args;
  64. this.handles[type] = this.handles[type] || [];
  65. isTriggered = this.handles[type].isTriggered;
  66. args = this.handles[type].args;
  67. if ($.isFunction(cb)) {
  68. if (isCover === true) {
  69. this.handles[type] = [cb];
  70. } else {
  71. this.handles[type].push(cb);
  72. }
  73. if (isTriggered) {
  74. cb.call(this, args);
  75. }
  76. }
  77. return this;
  78. },
  79. // 解除绑定的自定义事件
  80. off: function (type) {
  81. this.handles[type] = [];
  82. return this;
  83. },
  84. // 触发自定义事件
  85. trigger: function (type, args) {
  86. var i, len;
  87. this.handles[type] = this.handles[type] || [];
  88. i = 0;
  89. len = this.handles[type].length;
  90. // isTriggered 表示已经触发过
  91. this.handles[type].isTriggered = true;
  92. // args 参数
  93. this.handles[type].args = args;
  94. for (; i < len; i++) {
  95. this.handles[type][i].call(this, args);
  96. }
  97. },
  98. init: function () {
  99. var options = this.options;
  100. var data = options.data;
  101. this.handles = {};
  102. this.$el = $(options.el);
  103. this.data = data;
  104. this.event();
  105. this.render();
  106. },
  107. /**
  108. *
  109. */
  110. dataCallback: function () {
  111. var args = arguments;
  112. if (args.length === 1) {
  113. this.render(args[0]);
  114. } else {
  115. this.doRender(args[0], args[1]);
  116. }
  117. },
  118. /**
  119. * 把data解析成树型数据
  120. */
  121. parse: function (data) {
  122. var options = this.options;
  123. var response = options.response;
  124. var res = [];
  125. var map = {};
  126. var i = 0;
  127. var len = data.length;
  128. var id = response.id;
  129. var pid = response.pid;
  130. // 如果子节点异步加载 直接返回
  131. if (options.childNodeAsy) {
  132. return data;
  133. }
  134. for (; i < len; i++) {
  135. var item = data[i];
  136. var tempId = item[id];
  137. // 如果节点有children这个字段则判断数据本就是树型结构 直接返回原始数据
  138. if (item.children) {
  139. return data;
  140. }
  141. if (tempId) {
  142. map[tempId] = item
  143. }
  144. }
  145. for (i = 0; i < len; i++) {
  146. var item = data[i];
  147. var tempPid = item[pid];
  148. var parent = map[tempPid];
  149. if (tempPid && parent) {
  150. (parent.children || (parent.children = [])).push(item);
  151. } else {
  152. res.push(item);
  153. }
  154. }
  155. return res;
  156. },
  157. render: function (data) {
  158. var data = data || this.data;
  159. if ($.isFunction(data)) {
  160. data({}, this.dataCallback.bind(this))
  161. }
  162. if ($.isArray(data)) {
  163. data = this.parse(data);
  164. this.doRender(this.$el, data);
  165. }
  166. },
  167. doRender: function ($el, data, level) {
  168. var self = this;
  169. var options = this.options;
  170. var response = options.response;
  171. var len = data.length;
  172. var i = 0;
  173. var item;
  174. var id = response.id;
  175. var text = response.name;
  176. var level = level || 1;
  177. var tpl = '<i data-type="{{asy}}" class="sim-tree-spread {{spreadIcon}}"></i><a href="javascript:;"><i class="sim-tree-checkbox"></i>{{text}}</a>';
  178. var isRootEle = ($el === this.$el);
  179. var $outEl = $(document.createElement('ul'));
  180. var oLi, $li, hasChild, disabled, expand;
  181. var asy = options.childNodeAsy ? 'asy' : '';
  182. if (false && !options.check) { // 单选
  183. tpl = tpl.replace('<i class="sim-tree-checkbox"></i>', '');
  184. }
  185. for (; i < len; i++) {
  186. item = data[i];
  187. oLi = document.createElement('li');
  188. hasChild = !!item.children;
  189. disabled = item[response.disabled];
  190. expand = item[response.expand] || false;
  191. oLi.innerHTML = simTpl(tpl, {
  192. asy: asy,
  193. text: item[text],
  194. spreadIcon: hasChild ? (expand ? 'sim-icon-d' : 'sim-icon-r') : 'sim-hidden'
  195. });
  196. oLi.setAttribute('data-level', level);
  197. oLi.setAttribute('data-id', item[id]);
  198. disabled && oLi.setAttribute('class', 'disabled');
  199. $li = $(oLi);
  200. $li.data('data', item);
  201. if (expand) {
  202. $outEl.addClass("show");
  203. }
  204. $outEl.append($li);
  205. if (hasChild) {
  206. this.doRender($li, item.children, level + 1);
  207. }
  208. item[response.checked] && checkId.push(item[id]);
  209. item[response.open] && openId.push(item[id]);
  210. }
  211. len && $el.append($outEl);
  212. if (isRootEle) {
  213. $outEl.addClass('sim-tree');
  214. this.trigger('done', data);
  215. $.each(openId, function (index, id) {
  216. self.expandNode(id);
  217. });
  218. this.setSelected(checkId);
  219. } else {
  220. // 异步加载移除loading
  221. if (options.childNodeAsy) {
  222. this.hideLoading($el.find('.sim-tree-spread'));
  223. $outEl.addClass('show');
  224. }
  225. }
  226. },
  227. /**
  228. *
  229. */
  230. event: function () {
  231. var self = this;
  232. this.$el.off('click').on('click', function (e) {
  233. var $tar = $(e.target);
  234. if ($tar.hasClass('sim-tree-spread')) {
  235. self.spread.call(self, $tar);
  236. }
  237. if ($tar.hasClass('sim-tree-checkbox')) {
  238. $tar = $tar.parent();
  239. }
  240. if ($tar[0].tagName.toLowerCase() === 'a') {
  241. self.clickNode.call(self, $tar);
  242. }
  243. return false;
  244. });
  245. this.$el.on('selectstart', function () {
  246. return false;
  247. });
  248. if (this.options.done) {
  249. this.on('done', this.options.done);
  250. }
  251. if (this.options.onClick) {
  252. this.on('click', this.options.onClick);
  253. }
  254. if (this.options.onChange) {
  255. this.on('change', this.options.onChange);
  256. }
  257. if (this.options.onSearch) {
  258. this.on('search', this.options.onSearch);
  259. }
  260. },
  261. // 展开或收起
  262. spread: function ($el) {
  263. if ($el.hasClass('sim-icon-r')) {
  264. this.doSpread($el, true);
  265. } else {
  266. this.doSpread($el, false);
  267. }
  268. },
  269. // 添加loading
  270. showLoading: function ($el) {
  271. $el.addClass('sim-loading');
  272. },
  273. // 移除loading
  274. hideLoading: function ($el) {
  275. $el.removeClass('sim-loading');
  276. },
  277. /**
  278. * 展开或者收起
  279. * @param $el {jquery object} jquery对象
  280. * @param status {boolean} true 展开 false收起
  281. */
  282. doSpread: function ($el, status) {
  283. var $pli = $el.parent();
  284. var $ul = $pli.children('ul');
  285. var item = $pli.data('data');
  286. if (!item.children) return;
  287. if (status) {
  288. $el.removeClass('sim-icon-r').addClass('sim-icon-d');
  289. // 异步加载子节点
  290. if ($el.data('type') === 'asy' && $.isFunction(this.data)) {
  291. this.showLoading($el);
  292. this.data($pli.data('data'), this.dataCallback.bind(this, $pli));
  293. $el.data('type', '');
  294. }
  295. $ul.addClass('show');
  296. } else {
  297. $el.removeClass('sim-icon-d').addClass('sim-icon-r');
  298. $ul.removeClass('show');
  299. }
  300. },
  301. // 点击节点
  302. clickNode: function ($tar) {
  303. var self = this;
  304. var $pli = $tar.parent();
  305. var $li = this.$el.find('li');
  306. var len = $li.length;
  307. var i = 0;
  308. var list = [];
  309. var data, $childUl, $childCheck;
  310. var isChange = false;
  311. if ($pli.hasClass('disabled')) return;
  312. if (this.options.check) {
  313. isChange = true;
  314. this.doCheck($tar.find('.sim-tree-checkbox'));
  315. if (this.options.linkParent) {
  316. $childUl = $pli.children('ul');
  317. $childCheck = $childUl.find('.sim-tree-checkbox');
  318. $.each($childCheck, function () {
  319. self.doCheck($(this), $pli.data('checked'), true);
  320. });
  321. }
  322. for (; i < len; i++) {
  323. data = $li.eq(i).data();
  324. if (data['checked'] === true) {
  325. list.push(data.data);
  326. }
  327. }
  328. } else {
  329. //单选模式下仅保留当前选择
  330. $(".sim-tree-checkbox", this.$el).removeClass("checked");
  331. $(".sim-tree-checkbox", $tar).addClass("checked");
  332. if ($prevA) {
  333. $prevA.css('font-weight', 'normal');
  334. }
  335. $tar.css('font-weight', 'bold');
  336. $prevA = $tar;
  337. data = $pli.data('data');
  338. list = [data];
  339. if (this.sels) {
  340. isChange = !(this.sels[0] === data);
  341. } else {
  342. isChange = true;
  343. }
  344. }
  345. this.sels = list;
  346. this.trigger('click', list);
  347. isChange && this.trigger('change', list);
  348. },
  349. // 多选设置选中状态
  350. doCheck: function ($check, status, flag) {
  351. var self = this;
  352. var $li = $check.closest('li');
  353. var $childUl, $childUlCheck;
  354. var data = $li.data();
  355. if (typeof status === 'undefined') {
  356. status = !data.checked;
  357. }
  358. if (status === true) {
  359. $check.removeClass('sim-tree-semi').addClass('checked');
  360. } else if (status === false) {
  361. $check.removeClass('checked sim-tree-semi');
  362. } else if (status === 'semi') {
  363. $check.removeClass('checked').addClass('sim-tree-semi');
  364. }
  365. $li.data('checked', status);
  366. if (this.options.linkParent === true) {
  367. !flag && this.setParentCheck($li);
  368. }
  369. },
  370. setParentCheck: function ($li) {
  371. var $pul = $li.parent('ul');
  372. var $pli = $pul.parent('li');
  373. var $lis = $pul.children('li');
  374. var $plicheck = $pli.find('>a .sim-tree-checkbox');
  375. var checked = [];
  376. var maxLen = $lis.length;
  377. var isChecked, checkedLen;
  378. if (!$pli.length) return;
  379. if ($li.find('>a .sim-tree-checkbox').hasClass('sim-tree-semi')) {
  380. this.doCheck($plicheck, 'semi');
  381. return;
  382. }
  383. // 获取同级的选中状态
  384. $.each($lis, function () {
  385. isChecked = $(this).data('checked');
  386. isChecked === true && checked.push($(this));
  387. });
  388. checkedLen = checked.length;
  389. // 如果选中长度跟li长度一样 则父级选中
  390. if (maxLen === checkedLen) {
  391. this.doCheck($plicheck, true);
  392. }
  393. // 如果选中长度为0 则父级没选中
  394. if (!checkedLen) {
  395. this.doCheck($plicheck, false);
  396. }
  397. // 如果没有全选中 则父级设置成半选状态
  398. if (checkedLen >= 1 && checkedLen < maxLen) {
  399. this.doCheck($plicheck, 'semi');
  400. }
  401. },
  402. // 树搜索
  403. search: function (val) {
  404. if (!this.$el) return;
  405. var val = $.trim(val);
  406. var $li = this.$el.find('li');
  407. var i = 0;
  408. var len = $li.length;
  409. var text, $mLi, data;
  410. var res = [];
  411. var reg = new RegExp(val, 'i');
  412. $li.hide().children('.sim-tree-spread').addClass('sim-hidden');
  413. for (; i < len; i++) {
  414. $mLi = $li.eq(i);
  415. text = $mLi.children('a').text();
  416. data = $mLi.data('data');
  417. if (!val) {
  418. $mLi.show();
  419. if (data.children) {
  420. $mLi.children('.sim-tree-spread').removeClass('sim-hidden');
  421. }
  422. } else if (text.search(reg) !== -1) {
  423. if (parseInt($mLi.data('level')) !== 1) { // 不是顶级的需要展开父节点
  424. this.expandNode(data[this.options.response.pid]);
  425. }
  426. $mLi.parents('li').add($mLi).show();
  427. res.push($mLi);
  428. }
  429. }
  430. this.trigger('search', val);
  431. },
  432. // 展开某节点
  433. expandNode: function (id) {
  434. var $li = id.addClass ? id : this.$el.find('[data-id=' + id + ']');
  435. var data = $li.data('data');
  436. var pid = data[this.options.response.pid];
  437. var $spread = $li.children('.sim-tree-spread');
  438. var level = parseInt($li.data('level'));
  439. if (data.children && $spread.length) {
  440. $spread.removeClass('sim-hidden');
  441. this.doSpread($spread, true);
  442. }
  443. if (level !== 1) {
  444. this.expandNode(pid);
  445. }
  446. },
  447. // 设置选中
  448. setSelected: function (id) {
  449. var self = this;
  450. var aId = id;
  451. var aData = [];
  452. var res = [];
  453. if (typeof (aId) === 'string' || typeof (aId) === 'number') {
  454. aId = [aId];
  455. }
  456. if (!$.isArray(aId)) {
  457. return;
  458. }
  459. if (!this.options.check) { // 单选
  460. aId = [aId[0]];
  461. }
  462. $.each(aId, function (index, id) {
  463. var $li = self.$el.find('[data-id=' + id + ']');
  464. var $a = $li.children('a');
  465. var $check = $a.children('.sim-tree-checkbox');
  466. var data = $li.data('data');
  467. if (!$li.length) {
  468. return true;
  469. }
  470. if (!$check.length) { // 单选
  471. $a.css('font-weight', 'bold');
  472. } else { // 多选
  473. self.doCheck($check, true);
  474. }
  475. if (parseInt($li.data('level')) !== 1) { // 不是顶级的需要展开父节点
  476. self.expandNode(data[self.options.response.pid]);
  477. }
  478. aData.push(data);
  479. res.push($li[0]);
  480. });
  481. self.sels = aData;
  482. self.trigger('click', aData);
  483. },
  484. // 获取选中值
  485. getSelected: function () {
  486. return this.sels;
  487. },
  488. // 设置禁止选中
  489. disableNode: function (id) {
  490. var self = this;
  491. var aId = id;
  492. if (typeof (aId) === 'string' || typeof (aId) === 'number') {
  493. aId = [aId];
  494. }
  495. if (!$.isArray(aId)) {
  496. return;
  497. }
  498. $.each(aId, function (index, id) {
  499. var $li = self.$el.find('[data-id=' + id + ']');
  500. $li.addClass('disabled');
  501. });
  502. },
  503. // 树销毁
  504. destroy: function () {
  505. this.$el.html('');
  506. for (var key in this) {
  507. delete this[key]
  508. }
  509. delete this;
  510. },
  511. // 树刷新
  512. refresh: function (data) {
  513. this.$el.html('');
  514. this.render(data);
  515. }
  516. }
  517. window[name] = Class;
  518. $.fn[name] = function (opt) {
  519. opt = $.extend(true, {el: this}, opt);
  520. return Class(opt);
  521. };
  522. return Class;
  523. });