var $, Node, Position; $ = jQuery; Position = { getName: function(position) { return Position.strings[position - 1]; }, nameToIndex: function(name) { var i, j, ref; for (i = j = 1, ref = Position.strings.length; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) { if (Position.strings[i - 1] === name) { return i; } } return 0; } }; Position.BEFORE = 1; Position.AFTER = 2; Position.INSIDE = 3; Position.NONE = 4; Position.strings = ['before', 'after', 'inside', 'none']; Node = (function() { function Node(o, is_root, node_class) { if (is_root == null) { is_root = false; } if (node_class == null) { node_class = Node; } this.name = ''; this.setData(o); this.children = []; this.parent = null; if (is_root) { this.id_mapping = {}; this.tree = this; this.node_class = node_class; } } Node.prototype.setData = function(o) { /* Set the data of this node. setData(string): set the name of the node setdata(object): set attributes of the node Examples: setdata('node1') setData({ name: 'node1', id: 1}); setData({ name: 'node2', id: 2, color: 'green'}); * This is an internal function; it is not in the docs * Does not remove existing node values */ var key, setName, value; setName = (function(_this) { return function(name) { if (name !== null) { return _this.name = name; } }; })(this); if (typeof o !== 'object') { setName(o); } else { for (key in o) { value = o[key]; if (key === 'label') { setName(value); } else if (key !== 'children') { this[key] = value; } } } return null; }; Node.prototype.initFromData = function(data) { var addChildren, addNode; addNode = (function(_this) { return function(node_data) { _this.setData(node_data); if (node_data.children) { return addChildren(node_data.children); } }; })(this); addChildren = (function(_this) { return function(children_data) { var child, j, len, node; for (j = 0, len = children_data.length; j < len; j++) { child = children_data[j]; node = new _this.tree.node_class(''); node.initFromData(child); _this.addChild(node); } return null; }; })(this); addNode(data); return null; }; /* Create tree from data. Structure of data is: [ { label: 'node1', children: [ { label: 'child1' }, { label: 'child2' } ] }, { label: 'node2' } ] */ Node.prototype.loadFromData = function(data) { var j, len, node, o; this.removeChildren(); for (j = 0, len = data.length; j < len; j++) { o = data[j]; node = new this.tree.node_class(o); this.addChild(node); if (typeof o === 'object' && o.children) { node.loadFromData(o.children); } } return null; }; /* Add child. tree.addChild( new Node('child1') ); */ Node.prototype.addChild = function(node) { this.children.push(node); return node._setParent(this); }; /* Add child at position. Index starts at 0. tree.addChildAtPosition( new Node('abc'), 1 ); */ Node.prototype.addChildAtPosition = function(node, index) { this.children.splice(index, 0, node); return node._setParent(this); }; Node.prototype._setParent = function(parent) { this.parent = parent; this.tree = parent.tree; return this.tree.addNodeToIndex(this); }; /* Remove child. This also removes the children of the node. tree.removeChild(tree.children[0]); */ Node.prototype.removeChild = function(node) { node.removeChildren(); return this._removeChild(node); }; Node.prototype._removeChild = function(node) { this.children.splice(this.getChildIndex(node), 1); return this.tree.removeNodeFromIndex(node); }; /* Get child index. var index = getChildIndex(node); */ Node.prototype.getChildIndex = function(node) { return $.inArray(node, this.children); }; /* Does the tree have children? if (tree.hasChildren()) { // } */ Node.prototype.hasChildren = function() { return this.children.length !== 0; }; Node.prototype.isFolder = function() { return this.hasChildren() || this.load_on_demand; }; /* Iterate over all the nodes in the tree. Calls callback with (node, level). The callback must return true to continue the iteration on current node. tree.iterate( function(node, level) { console.log(node.name); // stop iteration after level 2 return (level <= 2); } ); */ Node.prototype.iterate = function(callback) { var _iterate; _iterate = function(node, level) { var child, j, len, ref, result; if (node.children) { ref = node.children; for (j = 0, len = ref.length; j < len; j++) { child = ref[j]; result = callback(child, level); if (result && child.hasChildren()) { _iterate(child, level + 1); } } return null; } }; _iterate(this, 0); return null; }; /* Move node relative to another node. Argument position: Position.BEFORE, Position.AFTER or Position.Inside // move node1 after node2 tree.moveNode(node1, node2, Position.AFTER); */ Node.prototype.moveNode = function(moved_node, target_node, position) { if (moved_node.isParentOf(target_node)) { return; } moved_node.parent._removeChild(moved_node); if (position === Position.AFTER) { return target_node.parent.addChildAtPosition(moved_node, target_node.parent.getChildIndex(target_node) + 1); } else if (position === Position.BEFORE) { return target_node.parent.addChildAtPosition(moved_node, target_node.parent.getChildIndex(target_node)); } else if (position === Position.INSIDE) { return target_node.addChildAtPosition(moved_node, 0); } }; /* Get the tree as data. */ Node.prototype.getData = function(include_parent) { var getDataFromNodes; if (include_parent == null) { include_parent = false; } getDataFromNodes = function(nodes) { var data, j, k, len, node, tmp_node, v; data = []; for (j = 0, len = nodes.length; j < len; j++) { node = nodes[j]; tmp_node = {}; for (k in node) { v = node[k]; if ((k !== 'parent' && k !== 'children' && k !== 'element' && k !== 'tree') && Object.prototype.hasOwnProperty.call(node, k)) { tmp_node[k] = v; } } if (node.hasChildren()) { tmp_node.children = getDataFromNodes(node.children); } data.push(tmp_node); } return data; }; if (include_parent) { return getDataFromNodes([this]); } else { return getDataFromNodes(this.children); } }; Node.prototype.getNodeByName = function(name) { return this.getNodeByCallback(function(node) { return node.name === name; }); }; Node.prototype.getNodeByCallback = function(callback) { var result; result = null; this.iterate(function(node) { if (callback(node)) { result = node; return false; } else { return true; } }); return result; }; Node.prototype.addAfter = function(node_info) { var child_index, node; if (!this.parent) { return null; } else { node = new this.tree.node_class(node_info); child_index = this.parent.getChildIndex(this); this.parent.addChildAtPosition(node, child_index + 1); if (typeof node_info === 'object' && node_info.children && node_info.children.length) { node.loadFromData(node_info.children); } return node; } }; Node.prototype.addBefore = function(node_info) { var child_index, node; if (!this.parent) { return null; } else { node = new this.tree.node_class(node_info); child_index = this.parent.getChildIndex(this); this.parent.addChildAtPosition(node, child_index); if (typeof node_info === 'object' && node_info.children && node_info.children.length) { node.loadFromData(node_info.children); } return node; } }; Node.prototype.addParent = function(node_info) { var child, j, len, new_parent, original_parent, ref; if (!this.parent) { return null; } else { new_parent = new this.tree.node_class(node_info); new_parent._setParent(this.tree); original_parent = this.parent; ref = original_parent.children; for (j = 0, len = ref.length; j < len; j++) { child = ref[j]; new_parent.addChild(child); } original_parent.children = []; original_parent.addChild(new_parent); return new_parent; } }; Node.prototype.remove = function() { if (this.parent) { this.parent.removeChild(this); return this.parent = null; } }; Node.prototype.append = function(node_info) { var node; node = new this.tree.node_class(node_info); this.addChild(node); if (typeof node_info === 'object' && node_info.children && node_info.children.length) { node.loadFromData(node_info.children); } return node; }; Node.prototype.prepend = function(node_info) { var node; node = new this.tree.node_class(node_info); this.addChildAtPosition(node, 0); if (typeof node_info === 'object' && node_info.children && node_info.children.length) { node.loadFromData(node_info.children); } return node; }; Node.prototype.isParentOf = function(node) { var parent; parent = node.parent; while (parent) { if (parent === this) { return true; } parent = parent.parent; } return false; }; Node.prototype.getLevel = function() { var level, node; level = 0; node = this; while (node.parent) { level += 1; node = node.parent; } return level; }; Node.prototype.getNodeById = function(node_id) { return this.id_mapping[node_id]; }; Node.prototype.addNodeToIndex = function(node) { if (node.id != null) { return this.id_mapping[node.id] = node; } }; Node.prototype.removeNodeFromIndex = function(node) { if (node.id != null) { return delete this.id_mapping[node.id]; } }; Node.prototype.removeChildren = function() { this.iterate((function(_this) { return function(child) { _this.tree.removeNodeFromIndex(child); return true; }; })(this)); return this.children = []; }; Node.prototype.getPreviousSibling = function() { var previous_index; if (!this.parent) { return null; } else { previous_index = this.parent.getChildIndex(this) - 1; if (previous_index >= 0) { return this.parent.children[previous_index]; } else { return null; } } }; Node.prototype.getNextSibling = function() { var next_index; if (!this.parent) { return null; } else { next_index = this.parent.getChildIndex(this) + 1; if (next_index < this.parent.children.length) { return this.parent.children[next_index]; } else { return null; } } }; Node.prototype.getNodesByProperty = function(key, value) { return this.filter(function(node) { return node[key] === value; }); }; Node.prototype.filter = function(f) { var result; result = []; this.iterate(function(node) { if (f(node)) { result.push(node); } return true; }); return result; }; Node.prototype.getNextNode = function(include_children) { var next_sibling; if (include_children == null) { include_children = true; } if (include_children && this.hasChildren() && this.is_open) { return this.children[0]; } else { if (!this.parent) { return null; } else { next_sibling = this.getNextSibling(); if (next_sibling) { return next_sibling; } else { return this.parent.getNextNode(false); } } } }; Node.prototype.getPreviousNode = function() { var previous_sibling; if (!this.parent) { return null; } else { previous_sibling = this.getPreviousSibling(); if (previous_sibling) { if (!previous_sibling.hasChildren() || !previous_sibling.is_open) { return previous_sibling; } else { return previous_sibling.getLastChild(); } } else { return this.getParent(); } } }; Node.prototype.getParent = function() { if (!this.parent) { return null; } else if (!this.parent.parent) { return null; } else { return this.parent; } }; Node.prototype.getLastChild = function() { var last_child; if (!this.hasChildren()) { return null; } else { last_child = this.children[this.children.length - 1]; if (!last_child.hasChildren() || !last_child.is_open) { return last_child; } else { return last_child.getLastChild(); } } }; return Node; })(); module.exports = { Node: Node, Position: Position };