github L6zthtml
項目中遇到這個功能,與其copy一個不如本身造個輪子。預覽地址
設計主要思路:
1.展示層樹的顯示 用遞歸函數羅列出頁面顯示效果。
2.不一樣的功能對應不用模式model來區分、對應這不一樣的展現效果(ui交互效果、初始化數據選擇形式)
因此採用對應不一樣的模式分別寫出(不一樣初始渲染狀態函數和ui效果交互函數)。感受這是這樣處理比較合適。
3.插件裏面維護一個獲取當前選中狀態的函數返回{id, name} 爲單元的數組。
4.id 查詢路徑函數(findIdDir),全部初始化頁面狀態邏輯都基於它node
// 搜尋回現id中具體的位置,爲了定位數據點。爲後面操縱數據狀態(例如是否是選中狀態)提供方便。
假如findIdDir返回數據 1-2
即該節點在 [{},{childs: [{},{id: 'xxx', name: '我就是1-2節點'}]},{}]git
const findIdDir = (data, id, dir) => { dir = dir ? dir : ''; /*--||--||--||--*/ for (let idx = 0, lg = data.length; idx < lg; idx++) { if (data[idx].id == id) { return dir === '' ? `${idx}` : `${dir}-${idx}` } if (data[idx].childs) { let curDir = `${dir ? `${dir}-` : ''}${idx}`; let result = findIdDir(data[idx].childs, id, curDir); if (result) { return result } } } return undefined };
// js 代碼github
/* * model ---- 0 1 2模式 * 0 是單選模式 * 1 是回連模式(選擇父節點子節聯動選中取消,子節點選中聯調父節點是否選中) 多選模式 * 2 特殊 不回聯模式 * onchange ----> 選中觸發回調 * idList: [] -----> 選中節點結合 */ console.log('init ---- statrt'); (function() { const pluginName = 'jctree'; const noop = function() {}; // 路徑函數 const findIdDir = (data, id, dir) => { dir = dir ? dir : ''; /*--||--||--||--*/ for (let idx = 0, lg = data.length; idx < lg; idx++) { if (data[idx].id == id) { return dir === '' ? `${idx}` : `${dir}-${idx}` } if (data[idx].childs) { let curDir = `${dir ? `${dir}-` : ''}${idx}`; let result = findIdDir(data[idx].childs, id, curDir); if (result) { return result } } } return undefined }; const defaultOption = { model: 0, data: [], onchange: noop, idList: [] } function JcTree(options) { this._options = defaultOption; if (!options.data || !Array.isArray(options.data)) { console.warn('樹須要初始化數據data且data應爲數組') }; options.data = $.extend(true, [], options.data); $.extend(this._options, options || {}); this.init(); }; // 渲染樹 JcTree.prototype.renderTree = function(data, open, index, dir) { index = undefined === index ? 0 : index; dir = undefined === dir ? '' : dir; const nextIndex = index + 1; const className = open ? '' : 'hide'; let htmlStr = `<div class="tree-fh-node ${className}">`; // data icon-check text-icon del-btn check.svg data && data.forEach((d, idx) => { let { id, name, childs, open, leafFlag, checked, hasChildSelect } = d; let curDir = dir === '' ? `${idx}` : `${dir}-${idx}`; let showToggleBtnFlag = leafFlag; htmlStr += `<div class="tree-fh-item" data-id="${id}" data-index="${index}" data-dir="${curDir}">` + (showToggleBtnFlag && childs && childs.length ? `<span class="tree-handle-toggle" data-id="${id}" data-index="${index}" data-dir="${curDir}">${open ? '-' : '+'}</span>` : '<span class="tree-handle-toggle-none"></span>') + `<label class="checbox-container"><span data-id="${id}" data-dir="${curDir}" data-name="${name}" class="checkbox ${checked? 'active' : ''}"><i class="icon-check text-icon"></i></span></label>` + `<span class="tree-node-name">${name}</span>` + `<span class="has-child-select ${hasChildSelect ? '' : 'hide'} ?>">(下級有選中節點)</span>` + `</div>`; if (childs && childs.length > 0) { htmlStr += this.renderTree(childs, open, nextIndex, curDir); } }); return htmlStr += `</div>` }; // 初始化數據 JcTree.prototype.initSingleData = function() { const { _options: { data, idList } } = this; if (idList.length === 0) { return }; const dirList = idList.map(id => findIdDir(data, id)); dirList.forEach(dir => { if (dir === undefined) return; const indexList = dir.split('-').filter(i => i !== ''); let lg = indexList.length; let item = data; for (let i = 0; i < lg; i++) { let curIndex = indexList[i]; if (i === lg - 1) { item[curIndex].checked = true } else { if (lg !== 1) { item[curIndex].open = true } } item = item[curIndex].childs } }); }; JcTree.prototype.initMulitData = function() { const { _options: { data, idList } } = this; const syncChildState = function(data) { if (data.childs) { data.childs.forEach(syncChildState); } data.open = true; data.checked = true; }; if (idList.length === 0) { return }; // 對id 路徑進行排序·規則 --- 例如當 存在 ‘1-2-1’ 和 ‘1-2’ 兩個對應的路徑, // 此時表示 ‘1-2’ 如下的點都爲選中的狀態,此時 再出現 ‘1-2-2’,就不用關注這個點的狀態, // 由於這個是在 ‘1-2’ 如下的因此是選中狀態。 let originDirList = idList.map(id => findIdDir(data, id)).filter(d => d !== undefined).sort(); let dirList = []; // 打牌比較 若是 若是前面相同的話 拿出來 while (originDirList.length) { let cur = originDirList.shift(); dirList.push(cur) for (var i = 0; i < originDirList.length;) { if (originDirList[i].indexOf(cur) === 0) { originDirList.splice(i, 1) } else { i++ } } }; // 初始化父子節點 /0/ let curItems = []; // 排序優化 dirList.forEach(dir => { if (dir === undefined) return; const indexList = dir.split('-').filter(i => i !== ''); let lg = indexList.length; let item = data; for (let i = 0; i < lg; i++) { let curIndex = indexList[i]; if (i === lg - 1) { item[curIndex].checked = true; curItems.push(item[curIndex]); } else { if (lg !== 1) { item[curIndex].open = true item[curIndex].hasChildSelect = true } } item = item[curIndex].childs } }); curItems.forEach(syncChildState); }; JcTree.prototype.initMulitSpData = function() { const { _options: { data, idList } } = this; if (idList.length === 0) { return }; // 打牌比較 若是 若是前面相同的話 拿出來 let dirList = idList.map(id => findIdDir(data, id)).filter(d => d !== undefined).sort(); // 初始化父子節點 /0/ let curItems = []; // 排序優化 dirList.forEach(dir => { if (dir === undefined) return; const indexList = dir.split('-').filter(i => i !== ''); let lg = indexList.length; let item = data; for (let i = 0; i < lg; i++) { let curIndex = indexList[i]; if (i === lg - 1) { item[curIndex].checked = true; curItems.push(item[curIndex]); } else { if (lg !== 1) { item[curIndex].open = true; item[curIndex].hasChildSelect = true } } item = item[curIndex].childs } }); }; JcTree.prototype.bindEventModelSingle = function() { const $root = this._options.$el; const _this = this; $root.on(`click.${pluginName}`, '.tree-handle-toggle', function() { let toggleText; const $curEl = $(this); const $parentNext = $curEl.parent('.tree-fh-item').next(); $parentNext.toggleClass('hide'); toggleText = $parentNext.hasClass('hide') ? '+' : '-'; $curEl.text(toggleText); }).on(`click.${pluginName}`, 'span.checkbox', function() { const $el = $(this); $el.toggleClass('active'); const id = $el.data('id'); let selectFlag = $el.hasClass('active'); if (selectFlag) { $root.find('span.checkbox').removeClass('active') $el.addClass('active') _this._options.onchange(id); } else { $el.removeClass('active') } }) }; JcTree.prototype.bindEventModelMulit = function() { const $root = this._options.$el; const data = this._options.data; const _this = this; $root.on(`click.${pluginName}`, '.tree-handle-toggle', function() { let toggleText; const $curEl = $(this); const $parentNext = $curEl.parent('.tree-fh-item').next(); $parentNext.toggleClass('hide'); toggleText = $parentNext.hasClass('hide') ? '+' : '-'; $curEl.text(toggleText); }).on(`click.${pluginName}`, 'span.checkbox', function() { const $el = $(this); $el.toggleClass('active'); const dir = $(this).data('dir').toString(); const dirIndex = dir.split('-'); let parentsDirs = []; let parentDir = ''; const checkFlag = $el.hasClass('active'); const $parent = $el.closest('.tree-fh-item'); // 父級 對 下級效果 const $childsParents = $parent.next('.tree-fh-node'); checkFlag ? $childsParents.find('span.checkbox').addClass('active') : $childsParents.find('span.checkbox').removeClass('active') // 尋根節點 dirIndex.forEach(d => { parentDir = parentDir === '' ? d : `${parentDir}-${d}` parentsDirs.push(parentDir) }); // 找相應的父節點 parentsDirs = parentsDirs.map(dir => `.tree-fh-item[data-dir="${dir}"]`).reverse(); parentsDirs.shift(); parentsDirs.forEach(function(selector) { const $el = $(selector, $root); const $next = $el.next(); const findAllCheckboxs = $('span.checkbox', $next); let flag = true; findAllCheckboxs.each(function() { if (!$(this).hasClass('active')) { flag = false return false } }); flag ? $el.find('span.checkbox').addClass('active') : $el.find('span.checkbox').removeClass('active'); }) _this._options.onchange(_this.getIdList()); }) }; JcTree.prototype.bindEventModelMulitSp = function() { const $root = this._options.$el; const data = this._options.data; const _this = this; $root.on(`click.${pluginName}`, '.tree-handle-toggle', function() { let toggleText; const $curEl = $(this); const $parentNext = $curEl.parent('.tree-fh-item').next(); $parentNext.toggleClass('hide'); toggleText = $parentNext.hasClass('hide') ? '+' : '-'; $curEl.text(toggleText); }).on(`click.${pluginName}`, 'span.checkbox', function() { const $el = $(this); $el.toggleClass('active'); const dir = $(this).data('dir').toString(); const dirIndex = dir.split('-'); let parentsDirs = []; let parentDir = ''; const checkFlag = $el.hasClass('active'); const $parent = $el.closest('.tree-fh-item'); // 父級 對 下級效果 // 尋根節點 dirIndex.forEach(d => { parentDir = parentDir === '' ? d : `${parentDir}-${d}` parentsDirs.push(parentDir) }); // 找相應的父節點 parentsDirs = parentsDirs.map(dir => `.tree-fh-item[data-dir="${dir}"]`); parentsDirs.pop(); parentsDirs.forEach(function(selector) { const $el = $(selector, $root); const $hasChildSelect = $el.find('.has-child-select'); const $next = $el.next(); const findAllCheckboxs = $('span.checkbox', $next); let flag = false; findAllCheckboxs.each(function() { if ($(this).hasClass('active')) { flag = true return false } }); !flag ? $hasChildSelect.addClass('hide') : $hasChildSelect.removeClass('hide') }) _this._options.onchange(_this.getIdList()); }) } // JcTree.prototype.getIdList = function() { const $root = this._options.$el; return $('span.active', $root).filter('.active').map((index, el) => { const $el = $(el); return { id: $el.data('id'), name: $el.data('name') } }).get(); }; // 初始化樹 JcTree.prototype.init = function() { switch (this._options.model) { case 0: { this.initSingleData(); break; } case 1: { this.initMulitData(); break; } case 2: { this.initMulitSpData(); break; } } let result = this.renderTree(this._options.data, true); result = `<div class="tree-root-warp">${result} </div>` this._options.$el.html(result); switch (this._options.model) { case 0: { this.bindEventModelSingle(); break; } case 1: { this.bindEventModelMulit(); break; } case 2: { this.bindEventModelMulitSp(); break; } } }; $.fn.JcTree = function(options) { const $el = this; options = Object.assign({}, options, { $el }); const jctree = new JcTree(options); const data = $el.data('jctree'); if (data) this.off(`click.${pluginName}`); $el.data('jctree', jctree); return this } })(); /**************************模擬樹數據********************************/ // 後端數據 const ajaxData = { "flag":1, "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} }; let data = [ajaxData.data.root]; const transData = function (data, resultOut) { resultOut = resultOut ? resultOut : []; data.forEach((d, index) => { let leafMsg = {}; leafMsg.id = d.id; leafMsg.childs = d.childs; leafMsg.name = d.data.name; leafMsg.leafFlag = d.data.leafFlag; leafMsg.level = d.level; if (d.childs) { const childs = leafMsg.childs = []; transData(d.childs, childs); } resultOut.push(leafMsg); }); return resultOut }; data = transData(data); data = [{ "id": 1, "childs": [{ "id": 2, "childs": [{ id: 9, name: '測試', level: 0 }], "name": "導入默認分類", "leafFlag": 1, "level": 1 }, { "id": 3, "childs": [{ "id": 5, "childs": [{ "id": 7, "childs": [], "name": "測試3級", "leafFlag": 0, "level": 3 }, { "id": 8, "childs": [], "name": "測試3級b", "leafFlag": 0, "level": 3 }], "name": "測試2級", "leafFlag": 1, "level": 2 }, { "id": 6, "childs": [], "name": "測試2級b", "leafFlag": 1, "level": 2 }], "name": "測試1級", "leafFlag": 1, "level": 1 }, { "id": 4, "childs": [{ id: 13, name: '走吧', leafFlag: 1, childs: [{ id: 113, name: '走吧', leafFlag: 1 }, { id: 114, name: '哈哈', leafFlag: 1 }, { id: 213, name: 'jc', leafFlag: 1, childs : [ { id: 313, name: '走吧', leafFlag: 1 }, { id: 314, name: '哈哈', leafFlag: 1 }, { id: 413, name: 'jc', leafFlag: 1 }, { id: 919, name: 'xx', leafFlag: 1, childs: [ { id: 818, name: '結束', leafFlag: 0, } ] } ] }] }, { id: 414, name: '哈哈', leafFlag: 1 }, { id: 23, name: 'jc', leafFlag: 1, childs : [ { id: 33, name: '走吧', leafFlag: 1 }, { id: 34, name: '哈哈', leafFlag: 1 }, { id: 43, name: 'jc', leafFlag: 1 }, { id: 99, name: 'xx', leafFlag: 1, childs: [ { id: 88, name: '結束', leafFlag: 0, } ] } ] }], "name": "測試1級b", "leafFlag": 1, "level": 1 }], "name": "所有", "leafFlag": 1, "level": 0 }]; data.forEach(d => { d.open = true; }); $('#model-0').JcTree({ data: data, model: 0, idList: [1] }) $('#model-1').JcTree({ data: data, model: 1, idList: [1] }) $('#model-2').JcTree({ data: data, model: 2, idList: [1] }) console.log('init ---- end');