// https://github.com/justinchmura/js-treeview/blob/1.1.5/src/treeview.js (function (define) { 'use strict'; (function (root, factory) { if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.TreeView = factory(); } }(window, function () { return (function () { /** List of events supported by the tree view */ var events = [ 'expand', 'expandAll', 'collapse', 'collapseAll', 'select' ]; /** * A utilite function to check to see if something is a DOM object * @param {object} Object to test * @returns {boolean} If the object is a DOM object */ function isDOMElement(obj) { try { return obj instanceof HTMLElement; } catch (e) { // Some browsers don't support using the HTMLElement so some extra // checks are needed. return typeof obj === 'object' && obj.nodeType === 1 && typeof obj.style === 'object' && typeof obj.ownerDocument === 'object'; } } /** * A forEach that will work with a NodeList and generic Arrays * @param {array|NodeList} arr The array to iterate over * @param {function} callback Function that executes for each element. First parameter is element, second is index * @param {object} The context to execute callback with */ function forEach(arr, callback, scope) { var i, len = arr.length; for (i = 0; i < len; i += 1) { callback.call(scope, arr[i], i); } } /** * Emit an event from the tree view * @param {string} name The name of the event to emit */ function emit(instance, name) { var args = [].slice.call(arguments, 2); if (events.indexOf(name) > -1) { if (instance.handlers[name] && instance.handlers[name] instanceof Array) { forEach(instance.handlers[name], function (handle) { window.setTimeout(function () { handle.callback.apply(handle.context, args); }, 0); }); } } else { throw new Error(name + ' event cannot be found on TreeView.'); } } /** * Renders the tree view in the DOM */ function render(self) { var container = isDOMElement(self.node) ? self.node : document.getElementById(self.node); var leaves = [], click; var renderLeaf = function (item) { var leaf = document.createElement('div'); var content = document.createElement('div'); var text = document.createElement('div'); var expando = document.createElement('div'); leaf.setAttribute('class', 'tree-leaf'); content.setAttribute('class', 'tree-leaf-content'); content.setAttribute('data-item', JSON.stringify(item)); text.setAttribute('class', 'tree-leaf-text'); text.textContent = item.name; expando.setAttribute('class', 'tree-expando ' + (item.expanded ? 'expanded' : '')); expando.textContent = item.expanded ? '-' : '+'; content.appendChild(expando); content.appendChild(text); leaf.appendChild(content); if (item.children && item.children.length > 0) { var children = document.createElement('div'); children.setAttribute('class', 'tree-child-leaves'); forEach(item.children, function (child) { var childLeaf = renderLeaf(child); children.appendChild(childLeaf); }); if (!item.expanded) { children.classList.add('hidden'); } leaf.appendChild(children); } else { expando.classList.add('hidden'); } return leaf; }; forEach(self.data, function (item) { leaves.push(renderLeaf.call(self, item)); }); container.innerHTML = leaves.map(function (leaf) { return leaf.outerHTML; }).join(''); click = function (e) { var parent = (e.target || e.currentTarget).parentNode; var data = JSON.parse(parent.getAttribute('data-item')); var leaves = parent.parentNode.querySelector('.tree-child-leaves'); if (leaves) { if (leaves.classList.contains('hidden')) { self.expand(parent, leaves); } else { self.collapse(parent, leaves); } } else { emit(self, 'select', { target: e, data: data }); } }; forEach(container.querySelectorAll('.tree-leaf-text'), function (node) { node.onclick = click; }); forEach(container.querySelectorAll('.tree-expando'), function (node) { node.onclick = click; }); } /** * @constructor * @property {object} handlers The attached event handlers * @property {object} data The JSON object that represents the tree structure * @property {DOMElement} node The DOM element to render the tree in */ function TreeView(data, node) { this.handlers = {}; this.node = node; this.data = data; render(this); } /** * Expands a leaflet by the expando or the leaf text * @param {DOMElement} node The parent node that contains the leaves * @param {DOMElement} leaves The leaves wrapper element */ TreeView.prototype.expand = function (node, leaves, skipEmit) { var expando = node.querySelector('.tree-expando'); expando.textContent = '-'; leaves.classList.remove('hidden'); if (skipEmit) { return; } emit(this, 'expand', { target: node, leaves: leaves }); }; TreeView.prototype.expandAll = function () { var self = this; var nodes = document.getElementById(self.node).querySelectorAll('.tree-expando'); forEach(nodes, function (node) { var parent = node.parentNode; var leaves = parent.parentNode.querySelector('.tree-child-leaves'); if (parent && leaves && parent.hasAttribute('data-item')) { self.expand(parent, leaves, true); } }); emit(this, 'expandAll', {}); }; /** * Collapses a leaflet by the expando or the leaf text * @param {DOMElement} node The parent node that contains the leaves * @param {DOMElement} leaves The leaves wrapper element */ TreeView.prototype.collapse = function (node, leaves, skipEmit) { var expando = node.querySelector('.tree-expando'); expando.textContent = '+'; leaves.classList.add('hidden'); if (skipEmit) { return; } emit(this, 'collapse', { target: node, leaves: leaves }); }; /** */ TreeView.prototype.collapseAll = function () { var self = this; var nodes = document.getElementById(self.node).querySelectorAll('.tree-expando'); forEach(nodes, function (node) { var parent = node.parentNode; var leaves = parent.parentNode.querySelector('.tree-child-leaves'); if (parent && leaves && parent.hasAttribute('data-item')) { self.collapse(parent, leaves, true); } }); emit(this, 'collapseAll', {}); }; /** * Attach an event handler to the tree view * @param {string} name Name of the event to attach * @param {function} callback The callback to execute on the event * @param {object} scope The context to call the callback with */ TreeView.prototype.on = function (name, callback, scope) { if (events.indexOf(name) > -1) { if (!this.handlers[name]) { this.handlers[name] = []; } this.handlers[name].push({ callback: callback, context: scope }); } else { throw new Error(name + ' is not supported by TreeView.'); } }; /** * Deattach an event handler from the tree view * @param {string} name Name of the event to deattach * @param {function} callback The function to deattach */ TreeView.prototype.off = function (name, callback) { var index, found = false; if (this.handlers[name] instanceof Array) { this.handlers[name].forEach(function (handle, i) { index = i; if (handle.callback === callback && !found) { found = true; } }); if (found) { this.handlers[name].splice(index, 1); } } }; return TreeView; }()); })); }(window.define));