资讯专栏INFORMATION COLUMN

javascript 单选、复选树 ---

JessYanCoding / 395人阅读

摘要:项目开发中遇到一颗树单选多选项目中遇到这个功能,与其一个不如自己造个轮子。预览地址设计主要思路展现层树的显示用递归函数罗列出页面显示效果。插件里面维护一个获取当前选中状态的函数返回为单元的数组。为后面操纵数据状态例如是不是选中状态提供方便。

项目开发中遇到一颗树(单选、多选);

github L6zt

项目中遇到这个功能,与其copy一个不如自己造个轮子。预览地址
设计主要思路:
1.展现层树的显示 用递归函数罗列出页面显示效果。
2.不同的功能对应不用模式model来区分、对应这不同的展示效果(ui交互效果、初始化数据选择形式)
所以采用对应不同的模式分别写出(不同初始渲染状态函数和ui效果交互函数)。感觉这是这样处理比较合适。
3.插件里面维护一个获取当前选中状态的函数返回{id, name} 为单元的数组。
4.id 查询路径函数(findIdDir),所有初始化页面状态逻辑都基于它

主要函数代码:

// 搜寻回现id中具体的位置,为了定位数据点。为后面操纵数据状态(例如是不是选中状态)提供方便。
假如findIdDir返回数据 1-2 即该节点在 [{},{childs: [{},{id: "xxx", name: "我就是1-2节点"}]},{}]

</>复制代码

  1. const findIdDir = (data, id, dir) => {
  2. dir = dir ? dir : "";
  3. /*--||--||--||--*/
  4. for (let idx = 0, lg = data.length; idx < lg; idx++) {
  5. if (data[idx].id == id) {
  6. return dir === "" ? `${idx}` : `${dir}-${idx}`
  7. }
  8. if (data[idx].childs) {
  9. let curDir = `${dir ? `${dir}-` : ""}${idx}`;
  10. let result = findIdDir(data[idx].childs, id, curDir);
  11. if (result) {
  12. return result
  13. }
  14. }
  15. }
  16. return undefined
  17. };

// js 代码

</>复制代码

  1. /*
  2. * model ---- 0 1 2模式
  3. * 0 是单选模式
  4. * 1 是回连模式(选择父节点子节联动选中取消,子节点选中联调父节点是否选中) 多选模式
  5. * 2 特殊 不回联模式
  6. * onchange ----> 选中触发回调
  7. * idList: [] -----> 选中节点结合
  8. */
  9. console.log("init ---- statrt");
  10. (function() {
  11. const pluginName = "jctree";
  12. const noop = function() {};
  13. // 路径函数
  14. const findIdDir = (data, id, dir) => {
  15. dir = dir ? dir : "";
  16. /*--||--||--||--*/
  17. for (let idx = 0, lg = data.length; idx < lg; idx++) {
  18. if (data[idx].id == id) {
  19. return dir === "" ? `${idx}` : `${dir}-${idx}`
  20. }
  21. if (data[idx].childs) {
  22. let curDir = `${dir ? `${dir}-` : ""}${idx}`;
  23. let result = findIdDir(data[idx].childs, id, curDir);
  24. if (result) {
  25. return result
  26. }
  27. }
  28. }
  29. return undefined
  30. };
  31. const defaultOption = {
  32. model: 0,
  33. data: [],
  34. onchange: noop,
  35. idList: []
  36. }
  37. function JcTree(options) {
  38. this._options = defaultOption;
  39. if (!options.data || !Array.isArray(options.data)) {
  40. console.warn("树需要初始化数据data且data应为数组")
  41. };
  42. options.data = $.extend(true, [], options.data);
  43. $.extend(this._options, options || {});
  44. this.init();
  45. };
  46. // 渲染树
  47. JcTree.prototype.renderTree = function(data, open, index, dir) {
  48. index = undefined === index ? 0 : index;
  49. dir = undefined === dir ? "" : dir;
  50. const nextIndex = index + 1;
  51. const className = open ? "" : "hide";
  52. let htmlStr = `
    `;
  53. // data icon-check text-icon del-btn check.svg
  54. data && data.forEach((d, idx) => {
  55. let {
  56. id,
  57. name,
  58. childs,
  59. open,
  60. leafFlag,
  61. checked,
  62. hasChildSelect
  63. } = d;
  64. let curDir = dir === "" ? `${idx}` : `${dir}-${idx}`;
  65. let showToggleBtnFlag = leafFlag;
  66. htmlStr += `
    ` +
  67. (showToggleBtnFlag && childs && childs.length ? `${open ? "-" : "+"}` : "") +
  68. `` +
  69. `${name}` +
  70. `">(下级有选中节点)` +
  71. ``;
  72. if (childs && childs.length > 0) {
  73. htmlStr += this.renderTree(childs, open, nextIndex, curDir);
  74. }
  75. });
  76. return htmlStr += ``
  77. };
  78. // 初始化数据
  79. JcTree.prototype.initSingleData = function() {
  80. const {
  81. _options: {
  82. data,
  83. idList
  84. }
  85. } = this;
  86. if (idList.length === 0) {
  87. return
  88. };
  89. const dirList = idList.map(id => findIdDir(data, id));
  90. dirList.forEach(dir => {
  91. if (dir === undefined) return;
  92. const indexList = dir.split("-").filter(i => i !== "");
  93. let lg = indexList.length;
  94. let item = data;
  95. for (let i = 0; i < lg; i++) {
  96. let curIndex = indexList[i];
  97. if (i === lg - 1) {
  98. item[curIndex].checked = true
  99. } else {
  100. if (lg !== 1) {
  101. item[curIndex].open = true
  102. }
  103. }
  104. item = item[curIndex].childs
  105. }
  106. });
  107. };
  108. JcTree.prototype.initMulitData = function() {
  109. const {
  110. _options: {
  111. data,
  112. idList
  113. }
  114. } = this;
  115. const syncChildState = function(data) {
  116. if (data.childs) {
  117. data.childs.forEach(syncChildState);
  118. }
  119. data.open = true;
  120. data.checked = true;
  121. };
  122. if (idList.length === 0) {
  123. return
  124. };
  125. // 对id 路径进行排序·规则 --- 例如当 存在 ‘1-2-1’ 和 ‘1-2’ 两个对应的路径,
  126. // 此时表示 ‘1-2’ 以下的点都为选中的状态,此时 再出现 ‘1-2-2’,就不用关注这个点的状态,
  127. // 因为这个是在 ‘1-2’ 以下的所以是选中状态。
  128. let originDirList = idList.map(id => findIdDir(data, id)).filter(d => d !== undefined).sort();
  129. let dirList = [];
  130. // 打牌比较 如果 如果前面相同的话 拿出来
  131. while (originDirList.length) {
  132. let cur = originDirList.shift();
  133. dirList.push(cur)
  134. for (var i = 0; i < originDirList.length;) {
  135. if (originDirList[i].indexOf(cur) === 0) {
  136. originDirList.splice(i, 1)
  137. } else {
  138. i++
  139. }
  140. }
  141. };
  142. // 初始化父子节点 /0/
  143. let curItems = [];
  144. // 排序优化
  145. dirList.forEach(dir => {
  146. if (dir === undefined) return;
  147. const indexList = dir.split("-").filter(i => i !== "");
  148. let lg = indexList.length;
  149. let item = data;
  150. for (let i = 0; i < lg; i++) {
  151. let curIndex = indexList[i];
  152. if (i === lg - 1) {
  153. item[curIndex].checked = true;
  154. curItems.push(item[curIndex]);
  155. } else {
  156. if (lg !== 1) {
  157. item[curIndex].open = true
  158. item[curIndex].hasChildSelect = true
  159. }
  160. }
  161. item = item[curIndex].childs
  162. }
  163. });
  164. curItems.forEach(syncChildState);
  165. };
  166. JcTree.prototype.initMulitSpData = function() {
  167. const {
  168. _options: {
  169. data,
  170. idList
  171. }
  172. } = this;
  173. if (idList.length === 0) {
  174. return
  175. };
  176. // 打牌比较 如果 如果前面相同的话 拿出来
  177. let dirList = idList.map(id => findIdDir(data, id)).filter(d => d !== undefined).sort();
  178. // 初始化父子节点 /0/
  179. let curItems = [];
  180. // 排序优化
  181. dirList.forEach(dir => {
  182. if (dir === undefined) return;
  183. const indexList = dir.split("-").filter(i => i !== "");
  184. let lg = indexList.length;
  185. let item = data;
  186. for (let i = 0; i < lg; i++) {
  187. let curIndex = indexList[i];
  188. if (i === lg - 1) {
  189. item[curIndex].checked = true;
  190. curItems.push(item[curIndex]);
  191. } else {
  192. if (lg !== 1) {
  193. item[curIndex].open = true;
  194. item[curIndex].hasChildSelect = true
  195. }
  196. }
  197. item = item[curIndex].childs
  198. }
  199. });
  200. };
  201. JcTree.prototype.bindEventModelSingle = function() {
  202. const $root = this._options.$el;
  203. const _this = this;
  204. $root.on(`click.${pluginName}`, ".tree-handle-toggle", function() {
  205. let toggleText;
  206. const $curEl = $(this);
  207. const $parentNext = $curEl.parent(".tree-fh-item").next();
  208. $parentNext.toggleClass("hide");
  209. toggleText = $parentNext.hasClass("hide") ? "+" : "-";
  210. $curEl.text(toggleText);
  211. }).on(`click.${pluginName}`, "span.checkbox", function() {
  212. const $el = $(this);
  213. $el.toggleClass("active");
  214. const id = $el.data("id");
  215. let selectFlag = $el.hasClass("active");
  216. if (selectFlag) {
  217. $root.find("span.checkbox").removeClass("active")
  218. $el.addClass("active")
  219. _this._options.onchange(id);
  220. } else {
  221. $el.removeClass("active")
  222. }
  223. })
  224. };
  225. JcTree.prototype.bindEventModelMulit = function() {
  226. const $root = this._options.$el;
  227. const data = this._options.data;
  228. const _this = this;
  229. $root.on(`click.${pluginName}`, ".tree-handle-toggle", function() {
  230. let toggleText;
  231. const $curEl = $(this);
  232. const $parentNext = $curEl.parent(".tree-fh-item").next();
  233. $parentNext.toggleClass("hide");
  234. toggleText = $parentNext.hasClass("hide") ? "+" : "-";
  235. $curEl.text(toggleText);
  236. }).on(`click.${pluginName}`, "span.checkbox", function() {
  237. const $el = $(this);
  238. $el.toggleClass("active");
  239. const dir = $(this).data("dir").toString();
  240. const dirIndex = dir.split("-");
  241. let parentsDirs = [];
  242. let parentDir = "";
  243. const checkFlag = $el.hasClass("active");
  244. const $parent = $el.closest(".tree-fh-item");
  245. // 父级 对 下级效果
  246. const $childsParents = $parent.next(".tree-fh-node");
  247. checkFlag ? $childsParents.find("span.checkbox").addClass("active") : $childsParents.find("span.checkbox").removeClass("active")
  248. // 寻根节点
  249. dirIndex.forEach(d => {
  250. parentDir = parentDir === "" ? d : `${parentDir}-${d}`
  251. parentsDirs.push(parentDir)
  252. });
  253. // 找相应的父节点
  254. parentsDirs = parentsDirs.map(dir => `.tree-fh-item[data-dir="${dir}"]`).reverse();
  255. parentsDirs.shift();
  256. parentsDirs.forEach(function(selector) {
  257. const $el = $(selector, $root);
  258. const $next = $el.next();
  259. const findAllCheckboxs = $("span.checkbox", $next);
  260. let flag = true;
  261. findAllCheckboxs.each(function() {
  262. if (!$(this).hasClass("active")) {
  263. flag = false
  264. return false
  265. }
  266. });
  267. flag ? $el.find("span.checkbox").addClass("active") : $el.find("span.checkbox").removeClass("active");
  268. })
  269. _this._options.onchange(_this.getIdList());
  270. })
  271. };
  272. JcTree.prototype.bindEventModelMulitSp = function() {
  273. const $root = this._options.$el;
  274. const data = this._options.data;
  275. const _this = this;
  276. $root.on(`click.${pluginName}`, ".tree-handle-toggle", function() {
  277. let toggleText;
  278. const $curEl = $(this);
  279. const $parentNext = $curEl.parent(".tree-fh-item").next();
  280. $parentNext.toggleClass("hide");
  281. toggleText = $parentNext.hasClass("hide") ? "+" : "-";
  282. $curEl.text(toggleText);
  283. }).on(`click.${pluginName}`, "span.checkbox", function() {
  284. const $el = $(this);
  285. $el.toggleClass("active");
  286. const dir = $(this).data("dir").toString();
  287. const dirIndex = dir.split("-");
  288. let parentsDirs = [];
  289. let parentDir = "";
  290. const checkFlag = $el.hasClass("active");
  291. const $parent = $el.closest(".tree-fh-item");
  292. // 父级 对 下级效果
  293. // 寻根节点
  294. dirIndex.forEach(d => {
  295. parentDir = parentDir === "" ? d : `${parentDir}-${d}`
  296. parentsDirs.push(parentDir)
  297. });
  298. // 找相应的父节点
  299. parentsDirs = parentsDirs.map(dir => `.tree-fh-item[data-dir="${dir}"]`);
  300. parentsDirs.pop();
  301. parentsDirs.forEach(function(selector) {
  302. const $el = $(selector, $root);
  303. const $hasChildSelect = $el.find(".has-child-select");
  304. const $next = $el.next();
  305. const findAllCheckboxs = $("span.checkbox", $next);
  306. let flag = false;
  307. findAllCheckboxs.each(function() {
  308. if ($(this).hasClass("active")) {
  309. flag = true
  310. return false
  311. }
  312. });
  313. !flag ? $hasChildSelect.addClass("hide") : $hasChildSelect.removeClass("hide")
  314. })
  315. _this._options.onchange(_this.getIdList());
  316. })
  317. }
  318. //
  319. JcTree.prototype.getIdList = function() {
  320. const $root = this._options.$el;
  321. return $("span.active", $root).filter(".active").map((index, el) => {
  322. const $el = $(el);
  323. return {
  324. id: $el.data("id"),
  325. name: $el.data("name")
  326. }
  327. }).get();
  328. };
  329. // 初始化树
  330. JcTree.prototype.init = function() {
  331. switch (this._options.model) {
  332. case 0:
  333. {
  334. this.initSingleData();
  335. break;
  336. }
  337. case 1:
  338. {
  339. this.initMulitData();
  340. break;
  341. }
  342. case 2:
  343. {
  344. this.initMulitSpData();
  345. break;
  346. }
  347. }
  348. let result = this.renderTree(this._options.data, true);
  349. result = `
    ${result}
    `
  350. this._options.$el.html(result);
  351. switch (this._options.model) {
  352. case 0:
  353. {
  354. this.bindEventModelSingle();
  355. break;
  356. }
  357. case 1:
  358. {
  359. this.bindEventModelMulit();
  360. break;
  361. }
  362. case 2:
  363. {
  364. this.bindEventModelMulitSp();
  365. break;
  366. }
  367. }
  368. };
  369. $.fn.JcTree = function(options) {
  370. const $el = this;
  371. options = Object.assign({}, options, {
  372. $el
  373. });
  374. const jctree = new JcTree(options);
  375. const data = $el.data("jctree");
  376. if (data) this.off(`click.${pluginName}`);
  377. $el.data("jctree", jctree);
  378. return this
  379. }
  380. })();
  381. /**************************模拟树数据********************************/
  382. // 后端数据
  383. const ajaxData = {
  384. "flag":1,
  385. "data":{"root":{"id":1,"level":0,"data":{"id":1,"level":0,"parentId":0,"name":"全部","categoryId":1,"leafFlag":1},"parentId":0,"childs":[{"id":2,"level":1,"data":{"id":2,"level":1,"parentId":1,"name":"导入默认分类","categoryId":2,"leafFlag":1},"parentId":1,"childs":[]},{"id":3,"level":1,"data":{"id":3,"level":1,"parentId":1,"name":"测试1级","categoryId":3,"leafFlag":0},"parentId":1,"childs":[{"id":5,"level":2,"data":{"id":5,"level":2,"parentId":3,"name":"测试2级","categoryId":5,"leafFlag":0},"parentId":3,"childs":[{"id":7,"level":3,"data":{"id":7,"level":3,"parentId":5,"name":"测试3级","categoryId":7,"leafFlag":1},"parentId":5,"childs":[]},{"id":8,"level":3,"data":{"id":8,"level":3,"parentId":5,"name":"测试3级b","categoryId":8,"leafFlag":1},"parentId":5,"childs":[]}]},{"id":6,"level":2,"data":{"id":6,"level":2,"parentId":3,"name":"测试2级b","categoryId":6,"leafFlag":0},"parentId":3,"childs":[]}]},{"id":4,"level":1,"data":{"id":4,"level":1,"parentId":1,"name":"测试1级b","categoryId":4,"leafFlag":0},"parentId":1,"childs":[]}]},"rootFlag":-1,"count":8}
  386. };
  387. let data = [ajaxData.data.root];
  388. const transData = function (data, resultOut) {
  389. resultOut = resultOut ? resultOut : [];
  390. data.forEach((d, index) => {
  391. let leafMsg = {};
  392. leafMsg.id = d.id;
  393. leafMsg.childs = d.childs;
  394. leafMsg.name = d.data.name;
  395. leafMsg.leafFlag = d.data.leafFlag;
  396. leafMsg.level = d.level;
  397. if (d.childs) {
  398. const childs = leafMsg.childs = [];
  399. transData(d.childs, childs);
  400. }
  401. resultOut.push(leafMsg);
  402. });
  403. return resultOut
  404. };
  405. data = transData(data);
  406. data = [{
  407. "id": 1,
  408. "childs": [{
  409. "id": 2,
  410. "childs": [{
  411. id: 9,
  412. name: "测试",
  413. level: 0
  414. }],
  415. "name": "导入默认分类",
  416. "leafFlag": 1,
  417. "level": 1
  418. }, {
  419. "id": 3,
  420. "childs": [{
  421. "id": 5,
  422. "childs": [{
  423. "id": 7,
  424. "childs": [],
  425. "name": "测试3级",
  426. "leafFlag": 0,
  427. "level": 3
  428. }, {
  429. "id": 8,
  430. "childs": [],
  431. "name": "测试3级b",
  432. "leafFlag": 0,
  433. "level": 3
  434. }],
  435. "name": "测试2级",
  436. "leafFlag": 1,
  437. "level": 2
  438. }, {
  439. "id": 6,
  440. "childs": [],
  441. "name": "测试2级b",
  442. "leafFlag": 1,
  443. "level": 2
  444. }],
  445. "name": "测试1级",
  446. "leafFlag": 1,
  447. "level": 1
  448. }, {
  449. "id": 4,
  450. "childs": [{
  451. id: 13,
  452. name: "走吧",
  453. leafFlag: 1,
  454. childs: [{
  455. id: 113,
  456. name: "走吧",
  457. leafFlag: 1
  458. }, {
  459. id: 114,
  460. name: "哈哈",
  461. leafFlag: 1
  462. }, {
  463. id: 213,
  464. name: "jc",
  465. leafFlag: 1,
  466. childs : [
  467. {
  468. id: 313,
  469. name: "走吧",
  470. leafFlag: 1
  471. }, {
  472. id: 314,
  473. name: "哈哈",
  474. leafFlag: 1
  475. }, {
  476. id: 413,
  477. name: "jc",
  478. leafFlag: 1
  479. }, {
  480. id: 919,
  481. name: "xx",
  482. leafFlag: 1,
  483. childs: [
  484. {
  485. id: 818,
  486. name: "结束",
  487. leafFlag: 0,
  488. }
  489. ]
  490. }
  491. ]
  492. }]
  493. }, {
  494. id: 414,
  495. name: "哈哈",
  496. leafFlag: 1
  497. }, {
  498. id: 23,
  499. name: "jc",
  500. leafFlag: 1,
  501. childs : [
  502. {
  503. id: 33,
  504. name: "走吧",
  505. leafFlag: 1
  506. }, {
  507. id: 34,
  508. name: "哈哈",
  509. leafFlag: 1
  510. }, {
  511. id: 43,
  512. name: "jc",
  513. leafFlag: 1
  514. }, {
  515. id: 99,
  516. name: "xx",
  517. leafFlag: 1,
  518. childs: [
  519. {
  520. id: 88,
  521. name: "结束",
  522. leafFlag: 0,
  523. }
  524. ]
  525. }
  526. ]
  527. }],
  528. "name": "测试1级b",
  529. "leafFlag": 1,
  530. "level": 1
  531. }],
  532. "name": "全部",
  533. "leafFlag": 1,
  534. "level": 0
  535. }];
  536. data.forEach(d => {
  537. d.open = true;
  538. });
  539. $("#model-0").JcTree({
  540. data: data,
  541. model: 0,
  542. idList: [1]
  543. })
  544. $("#model-1").JcTree({
  545. data: data,
  546. model: 1,
  547. idList: [1]
  548. })
  549. $("#model-2").JcTree({
  550. data: data,
  551. model: 2,
  552. idList: [1]
  553. })
  554. console.log("init ---- end");

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/54770.html

相关文章

  • javascript 单选选树 ---

    摘要:项目开发中遇到一颗树单选多选项目中遇到这个功能,与其一个不如自己造个轮子。预览地址设计主要思路展现层树的显示用递归函数罗列出页面显示效果。插件里面维护一个获取当前选中状态的函数返回为单元的数组。为后面操纵数据状态例如是不是选中状态提供方便。 项目开发中遇到一颗树(单选、多选); github L6zt 项目中遇到这个功能,与其copy一个不如自己造个轮子。预览地址设计主要思路:1.展现...

    Riddler 评论0 收藏0
  • 单选框、复选框获取选中值及由ajax获取的data判断选中的项目

    摘要:单选框获取选中值将单选框选中的项,组成数组或者字符串答题者答案对比答案,算出分数答对的题目数量函数可计算某个字符串,并执行其中的的代码。 //单选框获取选中值 function getRadioRes(Name){ var rdsObj = document.getElementsByName(Name); var checkVal = null; ...

    CrazyCodes 评论0 收藏0
  • html初体验#2

    摘要:但在后端代码中容易识别成注释,慎用忘记加分号啦执行为协议,这里意思为不执行任何命令忘记加分号啦执行为协议,这里意思为不执行任何命令标签也用于跳转页面,但必须有按钮或者点击才能跳转完整样式网址同标签提交必须写属性才能被提交。 碎碎念 关于布局 css布局:横向、纵向 2019年新进展:css grid git bash 上安装 http server 目的在于不使用 file:/...

    Cobub 评论0 收藏0
  • html初体验#2

    摘要:但在后端代码中容易识别成注释,慎用忘记加分号啦执行为协议,这里意思为不执行任何命令忘记加分号啦执行为协议,这里意思为不执行任何命令标签也用于跳转页面,但必须有按钮或者点击才能跳转完整样式网址同标签提交必须写属性才能被提交。 碎碎念 关于布局 css布局:横向、纵向 2019年新进展:css grid git bash 上安装 http server 目的在于不使用 file:/...

    xiangzhihong 评论0 收藏0
  • vue2.0学习笔记(表单输入绑定)

    摘要:复选框当选中时当没有选中时这里的和特性并不会影响输入控件的特性,因为浏览器在提交表单时并不会包含未被选中的复选框。 1、基础用法 v-model指令可以实现表单元素和Model中数据的双向数据绑定。只能运用在表单元素中(input、radio、text、address、email、select、checkbox、textarea) 可以用 v-model 指令在表单 、 及 元素上...

    Seay 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<