UNPKG

inspire-tree

Version:

Inspired JavaScript Tree UI Component

1,673 lines (1,550 loc) 190 kB
/* Inspire Tree * @version 7.3.0 * https://github.com/helion3/inspire-tree * @copyright Copyright 2015 Helion3, and other contributors * @license Licensed under MIT * see https://github.com/helion3/inspire-tree/blob/master/LICENSE */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('lodash/map.js'), require('lodash/castArray.js'), require('lodash/defaultsDeep.js'), require('lodash/difference.js'), require('lodash/each.js'), require('lodash/get.js'), require('lodash/includes.js'), require('lodash/isArray.js'), require('lodash/isArrayLike.js'), require('lodash/isBoolean.js'), require('lodash/isEmpty.js'), require('lodash/isFunction.js'), require('lodash/isObject.js'), require('lodash/isRegExp.js'), require('lodash/isString.js'), require('lodash/noop.js'), require('lodash/tail.js'), require('lodash/sortBy.js'), require('lodash/assign.js'), require('lodash/cloneDeep.js'), require('lodash/find.js'), require('lodash/findIndex.js'), require('lodash/findLast.js'), require('lodash/indexOf.js'), require('lodash/pull.js'), require('lodash/slice.js'), require('lodash/invoke.js'), require('lodash/isArrayLikeObject.js'), require('lodash/remove.js'), require('lodash/sortedIndexBy.js')) : typeof define === 'function' && define.amd ? define(['lodash/map.js', 'lodash/castArray.js', 'lodash/defaultsDeep.js', 'lodash/difference.js', 'lodash/each.js', 'lodash/get.js', 'lodash/includes.js', 'lodash/isArray.js', 'lodash/isArrayLike.js', 'lodash/isBoolean.js', 'lodash/isEmpty.js', 'lodash/isFunction.js', 'lodash/isObject.js', 'lodash/isRegExp.js', 'lodash/isString.js', 'lodash/noop.js', 'lodash/tail.js', 'lodash/sortBy.js', 'lodash/assign.js', 'lodash/cloneDeep.js', 'lodash/find.js', 'lodash/findIndex.js', 'lodash/findLast.js', 'lodash/indexOf.js', 'lodash/pull.js', 'lodash/slice.js', 'lodash/invoke.js', 'lodash/isArrayLikeObject.js', 'lodash/remove.js', 'lodash/sortedIndexBy.js'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.InspireTree = factory(global._.map, global._.castArray, global._.defaultsDeep, global._.difference, global._.each, global._.get, global._.includes, global._.isArray, global._.isArrayLike, global._.isBoolean, global._.isEmpty, global._.isFunction, global._.isObject, global._.isRegExp, global._.isString, global._.noop, global._.tail, global._.sortBy, global._.assign, global._.cloneDeep, global._.find, global._.findIndex, global._.findLast, global._.indexOf, global._.pull, global._.slice, global._.invoke, global._.isArrayLikeObject, global._.remove, global._.sortedIndexBy)); })(this, (function (_map, castArray, defaultsDeep, difference, each, get, includes, isArray, isArrayLike, isBoolean, isEmpty, isFunction, isObject, isRegExp, isString, noop, tail, _sortBy, _assign, cloneDeep, find, findIndex, findLast, indexOf, pull, slice, invoke, isArrayLikeObject, _remove, sortedIndexBy) { 'use strict'; function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _get() { return _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = _superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); } function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); } function _possibleConstructorReturn(t, e) { if (e && ("object" == typeof e || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _superPropBase(t, o) { for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t));); return t; } function _superPropGet(t, o, e, r) { var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } /** * Reset a node's state to the tree default. * * @private * @param {TreeNode} node Node object. * @returns {TreeNode} Node object. */ function resetState(node) { each(node._tree.defaultState, function (val, prop) { node.state(prop, val); }); return node; } /** * Stores repetitive state change logic for most state methods. * * @private * @param {string} prop State property name. * @param {boolean} value New state value. * @param {string} verb Verb used for events. * @param {TreeNode} node Node object. * @param {string} deep Optional name of state method to call recursively. * @return {TreeNode} Node object. */ function baseStateChange(prop, value, verb, node, deep) { if (node.state(prop) !== value) { node.context().batch(); if (node._tree.config.nodes.resetStateOnRestore && verb === 'restored') { resetState(node); } // indeterminate may never be true if checked is if (value && prop === 'checked') { node.state('indeterminate', false); } node.state(prop, value); node._tree.emit('node.' + verb, node, false); if (deep && node.hasChildren()) { node.children.recurseDown(function (child) { baseStateChange(prop, value, verb, child); }); } // This node's "renderability" has changed, so we should // trigger a re-cache in the parent context. if (prop === 'hidden' || prop === 'removed') { node.context().indicesDirty = true; node.context().calculateRenderablePositions(); } node.markDirty(); node.context().end(); } return node; } function _extendableBuiltin(cls) { function ExtendableBuiltin() { cls.apply(this, arguments); } ExtendableBuiltin.prototype = Object.create(cls.prototype, { constructor: { value: cls, enumerable: false, writable: true, configurable: true } }); if (Object.setPrototypeOf) { Object.setPrototypeOf(ExtendableBuiltin, cls); } else { ExtendableBuiltin.__proto__ = cls; } return ExtendableBuiltin; } /** * Base function to invoke given method(s) on tree nodes. * * @private * @param {TreeNode} nodes Array of node objects. * @param {string|array} methods Method names. * @param {array|Arguments} args Array of arguments to proxy. * @param {boolean} deep Invoke deeply. * @return {TreeNodes} Array of node objects. */ function baseInvoke(nodes, methods, args, deep) { methods = castArray(methods); nodes._tree.batch(); nodes[deep ? 'recurseDown' : 'each'](function (node) { each(methods, function (method) { if (isFunction(node[method])) { node[method].apply(node, args); } }); }); nodes._tree.end(); return nodes; } /** * Creates a predicate function. * * @private * @param {string|function} predicate Property name or custom function. * @return {function} Predicate function. */ function getPredicateFunction(predicate) { var fn = predicate; if (isString(predicate)) { fn = function fn(node) { return isFunction(node[predicate]) ? node[predicate]() : node[predicate]; }; } return fn; } /** * Base function to filter nodes by state value. * * @private * @param {string} state State property * @param {boolean} full Return a non-flat hierarchy * @return {TreeNodes} Array of matching nodes. */ function baseStatePredicate(state, full) { if (full) { return this.extract(state); } // Cache a state predicate function var fn = getPredicateFunction(state); return this.flatten(function (node) { // Never include removed nodes unless specifically requested if (state !== 'removed' && node.removed()) { return false; } return fn(node); }); } /** * An Array-like collection of TreeNodes. * * Note: Due to issue in many javascript environments, * native objects are problematic to extend correctly * so we mimic it, not actually extend it. * * @param {InspireTree} tree Context tree. * @param {array} array Array of TreeNode objects. * @param {object} opts Configuration object. * @return {TreeNodes} Collection of TreeNode */ var TreeNodes = /*#__PURE__*/function (_extendableBuiltin2) { function TreeNodes(tree, array, opts) { var _this; _classCallCheck(this, TreeNodes); _this = _callSuper(this, TreeNodes); if (isFunction(get(tree, 'isTree')) && !tree.isTree(tree)) { throw new TypeError('Invalid tree instance.'); } Object.defineProperty(_this, '_tree', { value: tree, writable: true }); _this.length = 0; _this.batching = 0; // A custom dirty flag to indicate when an index-altering // change has occured. Avoids re-caching when unnecessary. _this.indicesDirty = false; _this.config = defaultsDeep({}, opts, { calculateRenderablePositions: false }); // Init pagination _this._pagination = { limit: tree.config.pagination.limit, total: 0 }; if (isArray(array) || array instanceof TreeNodes) { each(array, function (node) { if (node instanceof TreeNode) { _this.push(node.clone()); } else { _this.addNode(node); } }); } return _this; } /** * Adds a new node. If a sort method is configured, * the node will be added in the appropriate order. * * @param {object} object Node * @return {TreeNode} Node object. */ _inherits(TreeNodes, _extendableBuiltin2); return _createClass(TreeNodes, [{ key: "addNode", value: function addNode(object) { // Base insertion index var index = this.length; // If tree is sorted, insert in correct position if (this._tree.config.sort) { index = sortedIndexBy(this, object, this._tree.config.sort); } return this.insertAt(index, object); } /** * Release pending data changes to any listeners. * * Will skip rendering as long as any calls * to `batch` have yet to be resolved, * * @private * @return {void} */ }, { key: "applyChanges", value: function applyChanges() { if (this.batching === 0) { this.calculateRenderablePositions(); this._tree.emit('changes.applied', this.context()); } } /** * Batch multiple changes for listeners (i.e. DOM) * * @private * @return {void} */ }, { key: "batch", value: function batch() { if (this.batching < 0) { this.batching = 0; } this.batching++; } /** * Query for all available nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "available", value: function available(full) { return baseStatePredicate.call(this, 'available', full); } /** * Blur nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "blur", value: function blur() { return this.invoke('blur'); } /** * Blur (deeply) all nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "blurDeep", value: function blurDeep() { return this.invokeDeep('blur'); } /** * Calculate and cache the first/last renderable nodes. * * Primarily useful for rendering engines, since hidden DOM * nodes may still be present and CSS :first/:last selectors * would fail. * * @param {boolean} force Force recalculation. * @return {void} * @private */ }, { key: "calculateRenderablePositions", value: function calculateRenderablePositions(force) { if (!force && (!this.indicesDirty || this.batching > 0 || !this.config.calculateRenderablePositions)) { return; } var first; var last; this.each(function (node) { if (node.renderable()) { // Cache first node if none yet first = first || node; // Always update last node on match last = node; } }); if (this.firstRenderableNode && this.firstRenderableNode !== first) { this.firstRenderableNode.markDirty(); } if (first && first !== this.firstRenderableNode) { first.markDirty(); } if (this.lastRenderableNode && this.lastRenderableNode !== last) { this.lastRenderableNode.markDirty(); } if (last && last !== this.lastRenderableNode) { last.markDirty(); } this.firstRenderableNode = first; this.lastRenderableNode = last; this.indicesDirty = false; // If we have a parent, force it to recalculate as well if (this._context && this._context.context() instanceof TreeNodes) { this._context.context().calculateRenderablePositions(true); } } /** * Query for all checked nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "checked", value: function checked(full) { return baseStatePredicate.call(this, 'checked', full); } /** * Clean nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "clean", value: function clean() { return this.invoke('clean'); } /** * Clones (deeply) the array of nodes. * * Note: Cloning will *not* clone the context pointer. * * @return {TreeNodes} Array of cloned nodes. */ }, { key: "clone", value: function clone() { return new TreeNodes(this._tree, this); } /** * Collapse nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "collapse", value: function collapse() { return this.invoke('collapse'); } /** * Query for all collapsed nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "collapsed", value: function collapsed(full) { return baseStatePredicate.call(this, 'collapsed', full); } /** * Collapse (deeply) all children. * * @return {TreeNodes} Array of node objects. */ }, { key: "collapseDeep", value: function collapseDeep() { return this.invokeDeep('collapse'); } /** * Concat multiple TreeNodes arrays. * * @param {TreeNodes} nodes Array of nodes. * @return {TreeNodes} Resulting node array. */ }, { key: "concat", value: function concat(nodes) { var newNodes = new TreeNodes(this._tree); newNodes._context = this._context; var pusher = function pusher(node) { if (node instanceof TreeNode) { newNodes.push(node); } }; each(this, pusher); each(nodes, pusher); // Copy pagination limit newNodes._pagination.limit = this._pagination.limit; return newNodes; } /** * Get the context of this collection. If a collection * of children, context is the parent node. Otherwise * the context is the tree itself. * * @return {TreeNode|object} Node object or tree instance. */ }, { key: "context", value: function context() { return this._context || this._tree; } /** * Copy nodes to another tree instance. * * @param {object} dest Destination Inspire Tree. * @param {boolean} hierarchy Include necessary ancestors to match hierarchy. * @param {boolean} includeState Include itree.state object. * @return {object} Methods to perform action on copied nodes. */ }, { key: "copy", value: function copy(dest, hierarchy, includeState) { var newNodes = new TreeNodes(this._tree); each(this, function (node) { newNodes.push(node.copy(dest, hierarchy, includeState)); }); return newNodes; } /** * Return deepest nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "deepest", value: function deepest() { var matches = new TreeNodes(this._tree); this.recurseDown(function (node) { if (!node.children) { matches.push(node); } }); return matches; } /** * Deselect nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "deselect", value: function deselect() { return this.invoke('deselect'); } /** * Deselect (deeply) all nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "deselectDeep", value: function deselectDeep() { return this.invokeDeep('deselect'); } /** * Iterate each TreeNode. * * @param {function} iteratee Iteratee invoke for each node. * @return {TreeNodes} Array of node objects. */ }, { key: "each", value: function each$1(iteratee) { each(this, iteratee); return this; } /** * Query for all editable nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "editable", value: function editable(full) { return baseStatePredicate.call(this, 'editable', full); } /** * Query for all nodes in editing mode. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "editing", value: function editing(full) { return baseStatePredicate.call(this, 'editing', full); } /** * Release the current batch. * * @private * @return {void} */ }, { key: "end", value: function end() { this.batching--; if (this.batching === 0) { this.applyChanges(); } } /** * Expand nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "expand", value: function expand() { return this.invoke('expand'); } /** * Query for all expanded nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "expanded", value: function expanded(full) { return baseStatePredicate.call(this, 'expanded', full); } /** * Expand (deeply) all nodes. * * @return {Promise<TreeNodes>} Promise resolved when all children have loaded and expanded. */ }, { key: "expandDeep", value: function expandDeep() { var _this2 = this; return new Promise(function (resolve) { var waitCount = 0; var done = function done() { if (--waitCount === 0) { resolve(_this2); } }; _this2.recurseDown(function (node) { waitCount++; // Ignore nodes without children if (node.children) { node.expand()["catch"](done).then(function () { // Manually trigger expansion on newly loaded children node.children.expandDeep()["catch"](done).then(done); }); } else { done(); } }); }); } /** * Expand parents. * * @return {TreeNodes} Array of node objects. */ }, { key: "expandParents", value: function expandParents() { return this.invoke('expandParents'); } /** * Clone a hierarchy of all nodes matching a predicate. * * Because it filters deeply, we must clone all nodes so that we * don't affect the actual node array. * * @param {string|function} predicate State flag or custom function. * @return {TreeNodes} Array of node objects. */ }, { key: "extract", value: function extract(predicate) { var flat = this.flatten(predicate); var matches = new TreeNodes(this._tree); each(flat, function (node) { return matches.addNode(node.copyHierarchy()); }); return matches; } /** * Filter all nodes matching the given predicate. * * @param {string|function} predicate State flag or custom function. * @return {TreeNodes} Array of node objects. */ }, { key: "filterBy", value: function filterBy(predicate) { var fn = getPredicateFunction(predicate); var matches = new TreeNodes(this._tree); each(this, function (node) { if (fn(node)) { matches.push(node); } }); return matches; } /** * Returns the first node matching predicate. * * @param {function} predicate Predicate function, accepts a single node and returns a boolean. * @return {TreeNode} First matching TreeNode, or undefined. */ }, { key: "find", value: function find(predicate) { var match; this.recurseDown(function (node) { if (predicate(node)) { match = node; return false; } }); return match; } /** * Returns the first shallow node matching predicate. * * @param {function} predicate Predicate function, accepts a single node and returns a boolean. * @return {TreeNode} First matching TreeNode, or undefined. */ }, { key: "first", value: function first(predicate) { for (var i = 0, l = this.length; i < l; i++) { if (predicate(this[i])) { return this[i]; } } } /** * Flatten and get only node(s) matching the expected state or predicate function. * * @param {string|function} predicate State property or custom function. * @return {TreeNodes} Flat array of matching nodes. */ }, { key: "flatten", value: function flatten(predicate) { var flat = new TreeNodes(this._tree); var fn = getPredicateFunction(predicate); this.recurseDown(function (node) { if (fn(node)) { flat.push(node); } }); return flat; } /** * Query for all focused nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "focused", value: function focused(full) { return baseStatePredicate.call(this, 'focused', full); } /** * Iterate each TreeNode. * * @param {function} iteratee Iteratee invoke for each node. * @return {TreeNodes} Array of node objects. */ }, { key: "forEach", value: function forEach(iteratee) { return this.each(iteratee); } /** * Get a specific node by its index, or undefined if it doesn't exist. * * @param {int} index Numeric index of requested node. * @return {TreeNode} Node object. Undefined if invalid index. */ }, { key: "get", value: function get(index) { return this[index]; } /** * Query for all hidden nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "hidden", value: function hidden(full) { return baseStatePredicate.call(this, 'hidden', full); } /** * Hide nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "hide", value: function hide() { return this.invoke('hide'); } /** * Hide (deeply) all nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "hideDeep", value: function hideDeep() { return this.invokeDeep('hide'); } /** * Query for all indeterminate nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "indeterminate", value: function indeterminate(full) { return baseStatePredicate.call(this, 'indeterminate', full); } /** * Insert a new node at a given position. * * @param {integer} index Index at which to insert the node. * @param {object} object Raw node object or TreeNode. * @return {TreeNode} Node object. */ }, { key: "insertAt", value: function insertAt(index, object) { // If node has a pre-existing ID if (object.id) { // Is it already in the tree? var existingNode = this.node(object.id); if (existingNode) { existingNode.restore().show(); // Merge children if (isArrayLike(object.children)) { // Setup existing node's children property if needed if (!isArrayLike(existingNode.children)) { existingNode.children = new TreeNodes(this._tree); existingNode.children._context = existingNode; } // Copy each child (using addNode, which uses insertAt) each(object.children, function (child) { existingNode.children.addNode(child); }); } // Merge truthy children else if (object.children && isBoolean(existingNode.children)) { existingNode.children = object.children; } existingNode.markDirty(); this.applyChanges(); // Node merged, return it. return existingNode; } } // Node is new, insert at given location. var node = this._tree.constructor.isTreeNode(object) ? object : objectToNode(this._tree, object); // Grab remaining nodes this.splice(index, 0, node); // Refresh parent state and mark dirty if (this._context) { node.itree.parent = this._context; this._context.refreshIndeterminateState().markDirty(); } // Event this._tree.emit('node.added', node); // Always mark this node as dirty node.markDirty(); // If pushing this node anywhere but the end, other nodes may change. if (this.length - 1 !== index) { this.invoke('markDirty'); } this.applyChanges(); return node; } /** * Invoke method(s) on each node. * * @param {string|array} methods Method name(s). * @param {array|Arguments} args Array of arguments to proxy. * @return {TreeNodes} Array of node objects. */ }, { key: "invoke", value: function invoke(methods, args) { return baseInvoke(this, methods, args); } /** * Invoke method(s) deeply. * * @param {string|array} methods Method name(s). * @param {array|Arguments} args Array of arguments to proxy. * @return {TreeNodes} Array of node objects. */ }, { key: "invokeDeep", value: function invokeDeep(methods, args) { if (!isArrayLikeObject(args) || arguments.length > 2) { args = tail(arguments); } return baseInvoke(this, methods, args, true); } /** * Returns the last shallow node matching predicate. * * @param {function} predicate Predicate function, accepts a single node and returns a boolean. * @return {TreeNode} Last matching shallow TreeNode, or undefined. */ }, { key: "last", value: function last(predicate) { for (var i = this.length - 1; i >= 0; i--) { if (predicate(this[i])) { return this[i]; } } } /** * Query for all nodes currently loading children. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "loading", value: function loading(full) { return baseStatePredicate.call(this, 'loading', full); } /** * Loads additional nodes for this context. * * @param {Event} event Click or scroll event if DOM interaction triggered this call. * @return {Promise<TreeNodes>} Resolves with request results. */ }, { key: "loadMore", value: function loadMore(event) { var _this3 = this; // Never refire if node is loading if (this._loading) { return Promise.reject(new Error('Pending loadMore call must complete before being invoked again.')); } var promise; // If no records remain, immediately resolve if (this._pagination.limit === this._pagination.total) { return Promise.resolve(); } // Set loading flag, prevents repeat requests this._loading = true; this.batch(); // Mark this context as dirty since we'll update text/tree nodes invoke(this._context, 'markDirty'); // Increment the pagination this._pagination.limit += this._tree.config.pagination.limit; // Emit an event this._tree.emit('node.paginated', this._context || this._tree, this.pagination, event); if (this._tree.config.deferredLoading) { if (this._context) { promise = this._context.loadChildren(); } else { promise = this._tree.load(this._tree.config.data); } } else { this._loading = false; promise = Promise.resolve(); } this.end(); // Clear the loading flag if (this._tree.config.deferredLoading) { promise.then(function () { _this3._loading = false; _this3.applyChanges(); })["catch"](function () { _this3._loading = false; _this3.applyChanges(); }); } return promise; } /** * Query for all nodes matched in the last search. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "matched", value: function matched(full) { return baseStatePredicate.call(this, 'matched', full); } /** * Move node at a given index to a new index. * * @param {int} index Current index. * @param {int} newIndex New index. * @param {TreeNodes} target Target TreeNodes array. Defaults to this. * @return {TreeNode} Node object. */ }, { key: "move", value: function move(index, newIndex) { var target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this; var oldNode = this[index].remove(); var node = target.insertAt(newIndex, oldNode); this._tree.emit('node.moved', node, this, index, target, newIndex); return node; } /** * Get a node. * * @param {string|number} id ID of node. * @return {TreeNode} Node object. */ }, { key: "node", value: function node(id) { var match; this.recurseDown(function (node) { if (node.id === id) { match = node; return false; } }); return match; } /** * Get all nodes in a tree, or nodes for an array of IDs. * * @param {array} refs Array of ID references. * @return {TreeNodes} Array of node objects. * @example * * const all = tree.nodes() * const some = tree.nodes([1, 2, 3]) */ }, { key: "nodes", value: function nodes(refs) { var results; if (isArray(refs)) { results = new TreeNodes(this._tree); this.recurseDown(function (node) { if (refs.indexOf(node.id) > -1) { results.push(node); } }); } return isArray(refs) ? results : this; } /** * Get the pagination. * * @return {object} Pagination configuration object. */ }, { key: "pagination", value: function pagination() { return this._pagination; } /** * Removes the last node. * * @return {TreeNode} Last tree node. */ }, { key: "pop", value: function pop() { var result = _superPropGet(TreeNodes, "pop", this, 3)([]); this.indicesDirty = true; this.calculateRenderablePositions(); return result; } /** * Push a new TreeNode onto the collection. * * @param {TreeNode} node Node objext. * @returns {number} The new length. */ }, { key: "push", value: function push(node) { var result = _superPropGet(TreeNodes, "push", this, 3)([node]); this.indicesDirty = true; this.calculateRenderablePositions(); return result; } /** * Iterate down all nodes and any children. * * Return false to stop execution. * * @param {function} iteratee Iteratee function. * @return {TreeNodes} Resulting nodes. */ }, { key: "recurseDown", value: function recurseDown$1(iteratee) { recurseDown(this, iteratee); return this; } /** * Remove a node. * * @param {TreeNode} node Node object. * @return {TreeNodes} Resulting nodes. */ }, { key: "remove", value: function remove(node) { _remove(this, { id: node.id }); invoke(this._context, 'markDirty'); this.indicesDirty = true; this.applyChanges(); return this; } /** * Query for all soft-removed nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "removed", value: function removed(full) { return baseStatePredicate.call(this, 'removed', full); } /** * Restore nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "restore", value: function restore() { return this.invoke('restore'); } /** * Restore (deeply) all nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "restoreDeep", value: function restoreDeep() { return this.invokeDeep('restore'); } /** * Select nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "select", value: function select() { return this.invoke('select'); } /** * Query for all selectable nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "selectable", value: function selectable(full) { return baseStatePredicate.call(this, 'selectable', full); } /** * Select (deeply) all nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "selectDeep", value: function selectDeep() { return this.invokeDeep('select'); } /** * Query for all selected nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "selected", value: function selected(full) { return baseStatePredicate.call(this, 'selected', full); } /** * Removes the first node. * * @param {TreeNode} node Node object. * @return {TreeNode} Node object. */ }, { key: "shift", value: function shift(node) { var result = _superPropGet(TreeNodes, "shift", this, 3)([node]); this.indicesDirty = true; this.calculateRenderablePositions(); return result; } /** * Show nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "show", value: function show() { return this.invoke('show'); } /** * Show (deeply) all nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "showDeep", value: function showDeep() { return this.invokeDeep('show'); } /** * Soft-remove nodes. * * @return {TreeNodes} Array of node objects. */ }, { key: "softRemove", value: function softRemove() { return this.invoke('softRemove'); } /** * Sorts all TreeNode objects in this collection. * * If no custom sorter given, the configured "sort" value will be used. * * @param {string|function} sorter Sort function or property name. * @return {TreeNodes} Array of node objects. */ }, { key: "sortBy", value: function sortBy(sorter) { var _this4 = this; sorter = sorter || this._tree.config.sort; // Only apply sort if one provided if (sorter) { this.batch(); var sorted = _sortBy(this, sorter); this.length = 0; each(sorted, function (node) { _this4.push(node); }); this.indicesDirty = true; this.end(); } return this; } /** * Sorts (deeply) all nodes in this collection. * * @param {function} comparator [description] * @return {TreeNodes} Array of node objects. */ }, { key: "sortDeep", value: function sortDeep(comparator) { this.sort(comparator); this.each(function (node) { if (node.hasChildren()) { node.children.sortDeep(comparator); } }); return this; } /** * Changes array contents by removing existing nodes and/or adding new nodes. * * @param {number} start Start index. * @param {number} deleteCount Number of nodes to delete. * @param {TreeNode} ...nodes One or more nodes. * @return {array} Array of deleted elements. */ }, { key: "splice", value: function splice() { var result = _superPropGet(TreeNodes, "splice", this, 1).apply(this, arguments); this.indicesDirty = true; this.calculateRenderablePositions(); return result; } /** * Set nodes' state values. * * @param {string} name Property name. * @param {boolean} newVal New value, if setting. * @return {TreeNodes} Array of node objects. */ }, { key: "state", value: function state() { return this.invoke('state', arguments); } /** * Set (deeply) nodes' state values. * * @param {string} name Property name. * @param {boolean} newVal New value, if setting. * @return {TreeNodes} Array of node objects. */ }, { key: "stateDeep", value: function stateDeep() { return this.invokeDeep('state', arguments); } /** * Swaps two node positions. * * @param {TreeNode} node1 Node 1. * @param {TreeNode} node2 Node 2. * @return {TreeNodes} Array of node objects. */ }, { key: "swap", value: function swap(node1, node2) { this._tree.batch(); var n1Context = node1.context(); var n2Context = node2.context(); // Cache. Note: n2Index is only usable once var n1Index = n1Context.indexOf(node1); var n2Index = n2Context.indexOf(node2); // If contexts match, we can simply re-assign them if (n1Context === n2Context) { this[n1Index] = node2; this[n2Index] = node1; // Emit move events for each node this._tree.emit('node.moved', node1, n1Context, n1Index, n2Context, n2Index); this._tree.emit('node.moved', node2, n2Context, n2Index, n1Context, n1Index); } else { // Otherwise, we have to move between contexts // Move node 1 to node 2's index n1Context.move(n1Index, n2Context.indexOf(node2), n2Context); // Move node 2 to node 1s original index n2Context.move(n2Context.indexOf(node2), n1Index, n1Context); } this.indicesDirty = true; this._tree.end(); this._tree.emit('node.swapped', node1, n1Context, n1Index, node2, n2Context, n2Index); return this; } /** * Get the tree instance. * * @return {InspireTree} Tree instance. */ }, { key: "tree", value: function tree() { return this._tree; } /** * Get a native node Array. * * @return {array} Array of node objects. */ }, { key: "toArray", value: function toArray() { var array = []; each(this, function (node) { array.push(node.toObject()); }); return array; } /** * Adds a node to beginning of the collection. * * @param {TreeNode} node Node object. * @return {number} New length of collection. */ }, { key: "unshift", value: function unshift(node) { var result = _superPropGet(TreeNodes, "unshift", this, 3)([node]); this.indicesDirty = true; this.calculateRenderablePositions(); return result; } /** * Query for all visible nodes. * * @param {boolean} full Retain full hiearchy. * @return {TreeNodes} Array of node objects. */ }, { key: "visible", value: function visible(full) { return baseStatePredicate.call(this, 'visible', full); } }]); }(_extendableBuiltin(Array)); /** * Base recursion function for a collection or node. * * Returns false if execution should cease. * * @private * @param {TreeNode|TreeNodes} obj Node or collection. * @param {function} iteratee Iteratee function * @return {boolean} Cease iteration. */ function recurseDown(obj, iteratee) { var res; if (obj instanceof TreeNodes) { each(obj, function (node) { res = recurseDown(node, iteratee); return res; }); } else if (obj instanceof TreeNode) { res = iteratee(obj); // Recurse children if (res !== false && obj.hasChildren()) { res = recurseDown(obj.children, iteratee); } } return res; } /** * Resolve promise-like objects consistently. * * @private * @param {object} promise Promise-like object. * @returns {Promise} Promise */ function standardizePromise(promise) { return new Promise(function (resolve, reject) { if (!isObject(promise)) { return reject(new Error('Invalid Promise')); } if (isFunction(promise.then)) { promise.then(resolve); } // jQuery promises use "error" if (isFunction(promise.error)) { promise.error(reject); } else if (isFunction(promise["catch"])) { promise["catch"](reject); } }); } /** * Helper method to clone an ITree config object. * * Rejects non-clonable properties like ref. * * @private * @param {object} itree ITree configuration object * @param {array} excludeKeys Keys to exclude, if any * @return {object} Cloned ITree. */ function cloneItree(itree, excludeKeys) { var clone = {}; excludeKeys = castArray(excludeKeys); excludeKeys.push('ref'); each(itree, function (v, k) { if (!includes(excludeKeys, k)) { clone[k] = cloneDeep(v); } }); if (!excludeKeys.includes('parent')) { Object.defineProperty(clone, 'parent', { value: itree.parent, writable: true }); } return clone; } /** * Get or set a state value. * * This is a base method and will not invoke related changes, for example * setting selected=false will not trigger any deselection logic. * * @private * @param {TreeNode} node Tree node. * @param {string} property Property name. * @param {boolean} val New value, if setting. * @return {boolean} Current value on read, old value on set. */ function baseState(node, property, val) { var currentVal = node.itree.state[property]; if (typeof val !== 'undefined' && currentVal !== val) { // Update values node.itree.state[property] = val; if (property !== 'rendered') { node.markDirty(); } // Emit an event node._tree.emit('node.state.changed', node, property, currentVal, val); } return currentVal; } /** * Represents a singe node object within the tree. * * @param {TreeNode} source TreeNode to copy. * @return {TreeNode} Tree node object. */ var TreeNode = /*#__PURE__*/function () { function TreeNode(tree, source, excludeKeys) { var _this = this; _classCallCheck(this, TreeNode); Object.defineProperty(this, '_tree', { value: tree, writable: true }); if (source instanceof TreeNode) { excludeKeys = castArray(excludeKeys); excludeKeys.push('_tree'); // Iterate manually for better perf each(source, function (value, key) { // Skip properties if (!includes(excludeKeys, key)) { if (isObject(value)) { if (value instanceof TreeNodes) { _this[key] = value.clone(); } else if (key === 'itree') { _this[key] = cloneItree(value); } else { _this[key] = cloneDeep(value); } } else { // Copy pr