UNPKG

extjs-gpl

Version:

GPL licensed version of Sencha Ext JS

1,149 lines (1,026 loc) 110 kB
/** * This class is used as a set of methods that are applied to the prototype of a * {@link Ext.data.Model Model} to decorate it with a Node API. This means that models * used in conjunction with a tree will have all of the tree related methods available * on the model. In general, this class will not be used directly by the developer. * * This class also creates extra {@link Ext.data.Field fields} on the model, if they do * not exist, to help maintain the tree state and UI. These fields are documented as * config options. * * The data fields used to render a tree node are: {@link #text}, {@link #leaf}, * {@link #children}, and {@link #expanded}. Once a node is loaded to the tree store * you can use {@link Ext.data.Model#get get()} to fetch the value of a given field * name (provided there is not a convenience accessor on the Node for that field). * * @example * Ext.tip.QuickTipManager.init(); // not required when using Ext.application() * * var root = { * expanded: true, * children: [{ * text: "Leaf node (<i>no folder/arrow icon</i>)", * leaf: true, * qtitle: 'Sample Tip Title', * qtip: 'Tip body' * }, { * text: "Parent node expanded", * expanded: true, * children: [{ * text: "Expanded leaf node 1", * leaf: true * }, { * text: "Expanded leaf node 2", * leaf: true * }] * }, { * text: "Parent node collapsed", * children: [{ * text: "Collapsed leaf node 1", * leaf: true * }, { * text: "Collapsed leaf node 2", * leaf: true * }] * }] * }; * * var tree = Ext.create('Ext.tree.Panel', { * title: 'TreePanel', * width: 260, * height: 200, * root: root, * rootVisible: false, * renderTo: document.body, * bbar: ['The first node ', { * text: 'is a leaf?', * handler: function () { * var firstChild = tree.getRootNode().getChildAt(0); * Ext.Msg.alert('Is Leaf?', firstChild.isLeaf()); * } * }, { * text: 'has text?', * handler: function () { * var firstChild = tree.getRootNode().getChildAt(0); * Ext.Msg.alert('Has Text:', firstChild.get('text')); * } * }] * }); * * The following configs have methods used to set the value / state of the node at * runtime: * * **{@link #children} / {@link #leaf}** * * - {@link #appendChild} * - {@link #hasChildNodes} * - {@link #insertBefore} * - {@link #insertChild} * - {@link #method-remove} * - {@link #removeAll} * - {@link #removeChild} * - {@link #replaceChild} * * **{@link #expanded}** * * - {@link #method-expand} * - {@link #expandChildren} * - {@link #method-collapse} * - {@link #collapseChildren} * * The remaining configs may be set using {@link Ext.data.Model#method-set set()}. * * node.set('text', 'Changed Text'); // example showing how to change the node label * * The {@link #qtip}, {@link #qtitle}, and {@link #qshowDelay} use QuickTips and * requires initializing {@link Ext.tip.QuickTipManager} unless the application is * created using {@link Ext#method-application}. * * Ext.tip.QuickTipManager.init(); * * For additional information and examples see the description for * {@link Ext.tree.Panel}. */ Ext.define('Ext.data.NodeInterface', { requires: [ 'Ext.data.field.Boolean', 'Ext.data.field.Integer', 'Ext.data.field.String', 'Ext.data.writer.Json', 'Ext.mixin.Observable' ], /** * @cfg {Boolean} [expanded=false] * True if the node is expanded. * * When the tree is asynchronously remote loaded, expanding a collapsed node loads * the children of that node (if the node has not already been loaded previously). * * See also: {@link #isExpanded}. */ /** * @cfg {Boolean} [expandable=true] * False to prevent expanding/collapsing of this node. * * See also: {@link #isExpandable}. */ /** * @cfg {Boolean} [checked=null] * Set to true or false to show a checkbox alongside this node. * * To fetch an array of checked nodes use {@link Ext.tree.Panel#method-getChecked * getChecked()}. */ /** * @cfg {Boolean} [leaf=false] * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be * rendered for this node. * * See also: {@link #isLeaf}. */ /** * @cfg {String} cls * CSS class to apply to this node. */ /** * @cfg {String} iconCls * @inheritdoc Ext.panel.Header#iconCls * @localdoc Use {@link #icon} to set the icon src path directly. */ /** * @cfg {String} icon * @inheritdoc Ext.panel.Header#icon */ /** * @cfg {String} glyph * @cfg {Number/String} glyph * * A numeric unicode character code to use as the icon. The default font-family * for glyphs can be set globally using * {@link Ext.app.Application#glyphFontFamily glyphFontFamily} application * config or the {@link Ext#setGlyphFontFamily Ext.setGlyphFontFamily()} method. * It is initially set to `'Pictos'`. * * The following shows how to set the glyph using the font icons provided in the * SDK (assuming the font-family has been configured globally): * * // assumes the glyphFontFamily is "Pictos" * glyph: 'x48' // the "home" icon (H character) * * // assumes the glyphFontFamily is "Pictos" * glyph: 72 // The "home" icon (H character) * * // assumes the glyphFontFamily is "Pictos" * glyph: 'H' // the "home" icon * * Alternatively, this config option accepts a string with the charCode and * font-family separated by the `@` symbol. * * // using Font Awesome * glyph: 'xf015@FontAwesome' // the "home" icon * * // using Pictos * glyph: 'H@Pictos' // the "home" icon * * Depending on the theme you're using, you may need include the font icon * packages in your application in order to use the icons included in the * SDK. For more information see: * * - [Font Awesome icons](http://fortawesome.github.io/Font-Awesome/cheatsheet/) * - [Pictos icons](http://docs.sencha.com/extjs/6.2/core_concepts/font_ext.html) * - [Theming Guide](http://docs.sencha.com/extjs/6.2/core_concepts/theming.html) * @since 6.2.0 */ /** * @cfg {Boolean} [allowDrop=true] * Set to false to deny dropping on this node. * * Applicable when using the {@link Ext.tree.plugin.TreeViewDragDrop * TreeViewDragDrop} plugin. */ /** * @cfg {Boolean} [allowDrag=true] * Set to false to deny dragging of this node. * * Applicable when using the {@link Ext.tree.plugin.TreeViewDragDrop * TreeViewDragDrop} plugin. */ /** * @cfg {String} href * A URL for a link that's created when this config is specified. * * See also {@link #hrefTarget}. */ /** * @cfg {String} hrefTarget * Target for link. Only applicable when {@link #href} is also specified. */ /** * @cfg {String} qtip * Tooltip text to show on this node. * * See also {@link #qtitle}. * See also {@link #qshowDelay}. */ /** * @cfg {String} qtitle * Tooltip title. * * See also {@link #qtip}. * See also {@link #qshowDelay}. */ /** * @cfg {Number} qshowDelay * Tooltip showDelay. * * See also {@link #qtip}. * See also {@link #qtitle}. */ /** * @cfg {String} text * The text to show on node label (_html tags are accepted_). * The default text for the root node is `ROOT`. All other nodes default to ''. * * **Note:** By default the node label is `text`, but can be set using the tree's * {@link Ext.tree.Panel#cfg-displayField displayField} config. */ /** * @cfg {Ext.data.NodeInterface[]} children * Array of child nodes. * * **Note:** By default the child nodes root is `children`, but can be set using the * reader {@link Ext.data.reader.Reader#cfg-rootProperty rootProperty} config on the * {@link Ext.data.TreeStore TreeStore's} {@link Ext.data.TreeStore#cfg-proxy proxy}. */ /** * @cfg {Boolean} [loaded=false] * @private * True if the node has finished loading. * * See {@link #isLoaded}. */ /** * @cfg {Boolean} [loading=false] * @private * True if the node is currently loading. * * See {@link #isLoading}. */ /** * @cfg {Boolean} root * @private * True if this is the root node. * * See {@link #isRoot}. */ /** * @cfg {Boolean} isLast * @private * True if this is the last node. * * See {@link #method-isLast}. */ /** * @cfg {Boolean} isFirst * @private * True if this is the first node. * * See {@link #method-isFirst}. */ /** * @cfg {String} parentId * @private * ID of parent node. * * See {@link #parentNode}. */ /** * @cfg {Number} index * @private * The position of the node inside its parent. When parent has 4 children and the node is third amongst them, * index will be 2. * * See {@link #indexOf} and {@link #indexOfId}. */ /** * @cfg {Number} depth * @private * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on... * * See {@link #getDepth}. */ /** * @property {Ext.data.NodeInterface} nextSibling * A reference to this node's next sibling node. `null` if this node does not have a next sibling. */ /** * @property {Ext.data.NodeInterface} previousSibling * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling. */ /** * @property {Ext.data.NodeInterface} parentNode * A reference to this node's parent node. `null` if this node is the root node. */ /** * @property {Ext.data.NodeInterface} lastChild * A reference to this node's last child node. `null` if this node has no children. */ /** * @property {Ext.data.NodeInterface} firstChild * A reference to this node's first child node. `null` if this node has no children. */ /** * @property {Ext.data.NodeInterface[]} childNodes * An array of this nodes children. Array will be empty if this node has no children. */ /** * @method onRegisterTreeNode * Implement this method in a tree record subclass if it needs to track whenever it is registered * with a {@link Ext.data.TreeStore TreeStore}. * @param {Ext.data.TreeStore} treeStore The TreeStore to which the node is being registered. * @template */ /** * @method onUnregisterTreeNode * Implement this method in a tree record subclass if it needs to track whenever it is unregistered * from a {@link Ext.data.TreeStore TreeStore}. * @param {Ext.data.TreeStore} treeStore The TreeStore from which the node is being unregistered. * @template */ statics: { /** * This method allows you to decorate a Model's class to implement the NodeInterface. * This adds a set of methods, new events, new properties and new fields on every Record. * @param {Ext.Class/Ext.data.Model} model The Model class or an instance of the Model class you want to * decorate the prototype of. * @static */ decorate: function (modelClass) { var model = Ext.data.schema.Schema.lookupEntity(modelClass), proto = model.prototype, idName, idField, idType; if (!model.prototype.isObservable) { model.mixin(Ext.mixin.Observable.prototype.mixinId, Ext.mixin.Observable); } if (proto.isNode) { // if (already decorated) return; } idName = proto.idProperty; idField = model.getField(idName); idType = idField.type; model.override(this.getPrototypeBody()); model.addFields([ { name : 'parentId', type : idType, defaultValue : null, allowNull : idField.allowNull }, { name : 'index', type : 'int', defaultValue : -1, persist : false , convert: null }, { name : 'depth', type : 'int', defaultValue : 0, persist : false , convert: null }, { name : 'expanded', type : 'bool', defaultValue : false, persist : false , convert: null }, { name : 'expandable', type : 'bool', defaultValue : true, persist : false , convert: null }, { name : 'checked', type : 'auto', defaultValue : null, persist : false , convert: null }, { name : 'leaf', type : 'bool', defaultValue : false }, { name : 'cls', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'iconCls', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'icon', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'glyph', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'root', type : 'boolean', defaultValue : false, persist : false , convert: null }, { name : 'isLast', type : 'boolean', defaultValue : false, persist : false , convert: null }, { name : 'isFirst', type : 'boolean', defaultValue : false, persist : false , convert: null }, { name : 'allowDrop', type : 'boolean', defaultValue : true, persist : false , convert: null }, { name : 'allowDrag', type : 'boolean', defaultValue : true, persist : false , convert: null }, { name : 'loaded', type : 'boolean', defaultValue : false, persist : false , convert: null }, { name : 'loading', type : 'boolean', defaultValue : false, persist : false , convert: null }, { name : 'href', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'hrefTarget', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'qtip', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'qtitle', type : 'string', defaultValue : '', persist : false , convert: null }, { name : 'qshowDelay', type : 'int', defaultValue : 0, persist : false , convert: null }, { name : 'children', type : 'auto', defaultValue : null, persist : false , convert: null }, { name : 'visible', type : 'boolean', defaultValue : true, persist : false }, { name : 'text', type : 'string', persist : false } ]); }, getPrototypeBody: function() { var bubbledEvents = { idchanged : true, append : true, remove : true, move : true, insert : true, beforeappend : true, beforeremove : true, beforemove : true, beforeinsert : true, expand : true, collapse : true, beforeexpand : true, beforecollapse: true, sort : true }, silently = { silent: true }; // bulkUpdate usage: // This is used in 3 contexts: // a) When registering nodes. When bulk updating, we don't want to descend down the tree // recursively making calls to register which is redundant. We do need to call it for each node // because they need to be findable via id as soon as append events fire, so we only do the minimum needed. // b) When setting a data property on the model. We only need to go through set (and the subsequent event chain) // so that the UI can update. If we're doing a bulk update, the UI will update regardless. // c) triggerUIUpdate. This is because we know "something has changed", but not exactly what, so we allow the UI to redraw itself. // It has no purpose as far as data goes, so skip it when we can return { /** * @property {Boolean} isNode * `true` in this class to identify an object as an instantiated Node, or subclass thereof. */ isNode: true, firstChild: null, lastChild: null, parentNode: null, previousSibling: null, nextSibling: null, constructor: function() { var me = this; me.mixins.observable.constructor.call(me); me.callParent(arguments); me.childNodes = []; // These events are fired on this node, and programmatically bubble // up the parentNode axis, ending up walking off the top and firing // on the owning Ext.data.TreeStore /** * @event append * Fires when a new child node is appended * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} node The newly appended node * @param {Number} index The index of the newly appended node */ /** * @event remove * Fires when a child node is removed * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} node The removed node * @param {Boolean} isMove `true` if the child node is being removed so it can be moved to another position in the tree. * @param {Object} context An object providing information about where the removed node came from. It contains the following properties: * @param {Ext.data.NodeInterface} context.parentNode The node from which the removed node was removed. * @param {Ext.data.NodeInterface} context.previousSibling The removed node's former previous sibling. * @param {Ext.data.NodeInterface} context.nextSibling The removed node's former next sibling. * (a side effect of calling {@link Ext.data.NodeInterface#appendChild appendChild} or * {@link Ext.data.NodeInterface#insertBefore insertBefore} with a node that already has a parentNode) */ /** * @event move * Fires when this node is moved to a new location in the tree * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} oldParent The old parent of this node * @param {Ext.data.NodeInterface} newParent The new parent of this node * @param {Number} index The index it was moved to */ /** * @event insert * Fires when a new child node is inserted. * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} node The child node inserted * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before */ /** * @event beforeappend * Fires before a new child is appended, return false to cancel the append. * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} node The child node to be appended */ /** * @event beforeremove * Fires before a child is removed, return false to cancel the remove. * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} node The child node to be removed * @param {Boolean} isMove `true` if the child node is being removed so it can be moved to another position in the tree. * (a side effect of calling {@link Ext.data.NodeInterface#appendChild appendChild} or * {@link Ext.data.NodeInterface#insertBefore insertBefore} with a node that already has a parentNode) */ /** * @event beforemove * Fires before this node is moved to a new location in the tree. Return false to cancel the move. * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} oldParent The parent of this node * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to * @param {Number} index The index it is being moved to */ /** * @event beforeinsert * Fires before a new child is inserted, return false to cancel the insert. * @param {Ext.data.NodeInterface} this This node * @param {Ext.data.NodeInterface} node The child node to be inserted * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before */ /** * @event expand * Fires when this node is expanded. * @param {Ext.data.NodeInterface} this The expanding node */ /** * @event collapse * Fires when this node is collapsed. * @param {Ext.data.NodeInterface} this The collapsing node */ /** * @event beforeexpand * Fires before this node is expanded. * @param {Ext.data.NodeInterface} this The expanding node */ /** * @event beforecollapse * Fires before this node is collapsed. * @param {Ext.data.NodeInterface} this The collapsing node */ /** * @event sort * Fires when this node's childNodes are sorted. * @param {Ext.data.NodeInterface} this This node. * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node. */ return me; }, /** * Ensures that the passed object is an instance of a Record with the NodeInterface applied * @return {Ext.data.NodeInterface} */ createNode: function (node) { var me = this, childType = me.childType, store, storeReader, nodeProxy, nodeReader, reader, typeProperty, T = me.self; // Passed node's internal data object if (!node.isModel) { // Check this node type's childType configuration if (childType) { T = me.schema.getEntity(childType); } // See if the reader has a typeProperty and use it if possible else { store = me.getTreeStore(); storeReader = store && store.getProxy().getReader(); nodeProxy = me.getProxy(); nodeReader = nodeProxy ? nodeProxy.getReader() : null; // If the node's proxy's reader was configured with a special typeProperty (property name which defines the child type name) use that. reader = !storeReader || (nodeReader && nodeReader.initialConfig.typeProperty) ? nodeReader : storeReader; if (reader) { typeProperty = reader.getTypeProperty(); if (typeProperty) { T = reader.getChildType(me.schema, node, typeProperty); } } } node = new T(node); } // The node may already decorated, but may not have been // so when the model constructor was called. If not, // setup defaults here if (!node.childNodes) { node.firstChild = node.lastChild = node.parentNode = node.previousSibling = node.nextSibling = null; node.childNodes = []; } return node; }, /** * Returns true if this node is a leaf * @return {Boolean} */ isLeaf: function() { return this.get('leaf') === true; }, /** * Sets the first child of this node * @private * @param {Ext.data.NodeInterface} node */ setFirstChild: function(node) { this.firstChild = node; }, /** * Sets the last child of this node * @private * @param {Ext.data.NodeInterface} node */ setLastChild: function(node) { this.lastChild = node; }, /** * Updates general data of this node like isFirst, isLast, depth. This * method is internally called after a node is moved. This shouldn't * have to be called by the developer unless they are creating custom * Tree plugins. * @protected * @param {Boolean} commit * @param {Object} info The info to update. May contain any of the following * @param {Object} info.isFirst * @param {Object} info.isLast * @param {Object} info.index * @param {Object} info.depth * @param {Object} info.parentId * @return {String}[]} The names of any persistent fields that were modified. */ updateInfo: function(commit, info) { var me = this, phantom = me.phantom, result; commit = { silent: true, commit: commit }; // The only way child data can be influenced is if this node has changed level in this update. if (info.depth !== me.data.depth) { var childInfo = { depth: me.data.depth + 1 }, children = me.childNodes, childCount = children.length, i; for (i = 0; i < childCount; i++) { children[i].set(childInfo); } } result = me.set(info, commit); // Restore phantom flag which might get cleared by a commit. me.phantom = phantom; return result; }, /** * Returns true if this node is the last child of its parent * @return {Boolean} */ isLast: function() { return this.get('isLast'); }, /** * Returns true if this node is the first child of its parent * @return {Boolean} */ isFirst: function() { return this.get('isFirst'); }, /** * Returns true if this node has one or more child nodes, else false. * @return {Boolean} */ hasChildNodes: function() { return !this.isLeaf() && this.childNodes.length > 0; }, /** * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt> * node attribute is explicitly specified as true, otherwise returns false. * @return {Boolean} */ isExpandable: function() { var me = this; if (me.get('expandable')) { return !(me.isLeaf() || (me.isLoaded() && !me.phantom && !me.hasChildNodes())); } return false; }, triggerUIUpdate: function() { // This isn't ideal, however none of the underlying fields have changed // but we still need to update the UI // callJoined calls both the Stores we are joined to, and any TreeStore of which we may be a descendant. this.callJoined('afterEdit', []); }, /** * Inserts node(s) as the last child node of this node. * * If the node was previously a child node of another parent node, it will be removed from that node first. * * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]/Object} node The node or Array of nodes to append * @param {Boolean} [suppressEvents=false] True to suppress firing of * events. * @param {Boolean} [commit=false] * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed */ appendChild: function(node, suppressEvents, commit) { var me = this, i, ln, index, oldParent, previousSibling, childInfo = { isLast: true, parentId: me.getId(), depth: (me.data.depth||0) + 1 }, result, treeStore = me.getTreeStore(), halfCheckedValue = treeStore && treeStore.triStateCheckbox ? 1 : false, bulkUpdate = treeStore && treeStore.bulkUpdate, meChecked, nodeChecked, modifiedFields; // Coalesce all layouts caused by node append Ext.suspendLayouts(); // if passed an array do them one by one if (Ext.isArray(node)) { ln = node.length; result = new Array(ln); // Suspend view updating and data syncing during update me.callTreeStore('beginFill'); for (i = 0; i < ln; i++) { result[i] = me.appendChild(node[i], suppressEvents, commit); } // Resume view updating and data syncing after appending all new children. // This will fire the add event to any views (if its the top level append) me.callTreeStore('endFill', [result]); } else { // Make sure it is a record node = me.createNode(node); if (suppressEvents !== true && me.fireBubbledEvent('beforeappend', [me, node]) === false) { Ext.resumeLayouts(true); return false; } index = me.childNodes.length; oldParent = node.parentNode; // it's a move, make sure we move it cleanly if (oldParent) { if (suppressEvents !== true && node.fireBubbledEvent('beforemove', [node, oldParent, me, index]) === false) { Ext.resumeLayouts(true); return false; } // Return false if a beforeremove listener vetoed the remove if (oldParent.removeChild(node, false, suppressEvents, oldParent.getTreeStore() === treeStore) === false) { Ext.resumeLayouts(true); return false; } } // Coalesce sync operations across this operation // Node field setting (loaded, expanded) and node addition both trigger a sync if autoSync is set. treeStore && treeStore.beginUpdate(); index = me.childNodes.length; if (index === 0) { me.setFirstChild(node); } me.childNodes[index] = node; node.parentNode = me; node.nextSibling = null; me.setLastChild(node); previousSibling = me.childNodes[index - 1]; if (previousSibling) { node.previousSibling = previousSibling; previousSibling.nextSibling = node; previousSibling.updateInfo(commit, { isLast: false }); // No need to trigger a ui update if we're doing a bulk update if (!bulkUpdate) { previousSibling.triggerUIUpdate(); } } else { node.previousSibling = null; } // Update the new child's info passing in info we already know childInfo.isFirst = index === 0; childInfo.index = index; // Integrate the new node into its new position. // It's not in the store yet, so we might need to // inform the store of significant field changes later. modifiedFields = node.updateInfo(commit, childInfo); // We stop being a leaf as soon as a node is appended if (me.isLeaf()) { me.set('leaf', false); } // As soon as we append a child to this node, we are loaded if (!me.isLoaded()) { if (bulkUpdate) { me.data.loaded = true; } else { me.set('loaded', true); } } else if (me.childNodes.length === 1 && !bulkUpdate) { me.triggerUIUpdate(); } // Ensure connectors are correct by updating the UI on all intervening nodes (descendants) between last sibling and new node. if (index && me.childNodes[index - 1].isExpanded() && !bulkUpdate) { me.childNodes[index - 1].cascade(me.triggerUIUpdate); } // We register the subtree before we proceed so relayed events // (like nodeappend) from our TreeStore (if we have one) will be // able to use getNodeById. The node also needs to be added since // we're passing it in the events below. If we're not bulk updating, it // means we're just appending a node (with possible children), so do it // deeply here to ensure everything is captured. if (treeStore) { treeStore.registerNode(me, !bulkUpdate); if (bulkUpdate) { treeStore.registerNode(node); } } // This node MUST fire its events first, so that if the TreeStore's // onNodeAppend loads and appends local children, the events are still in order; // This node appended this child first, before the descendant cascade. if (suppressEvents !== true) { me.fireBubbledEvent('append', [me, node, index]); if (oldParent) { node.fireBubbledEvent('move', [node, oldParent, me, index]); } } // Inform the TreeStore so that the node can be inserted // and registered. me.callTreeStore('onNodeAppend', [node, index]); // Now that the store contains the new node, we cam inform it of field changes. if (modifiedFields) { node.callJoined('afterEdit', [modifiedFields]); } result = node; // Coalesce sync operations across this operation // Node field setting (loaded, expanded) and node addition both trigger a sync if autoSync is set. if (treeStore) { treeStore.endUpdate(); } } // Flush layouts caused by updating of the UI Ext.resumeLayouts(true); return result; }, /** * Returns the tree this node is in. * @return {Ext.tree.Panel} The tree panel which owns this node. * @deprecated 6.2.0 */ getOwnerTree: function() { var store = this.getTreeStore(); if (store) { return store.ownerTree; } }, /** * Returns the {@link Ext.data.TreeStore} which owns this node. * @return {Ext.data.TreeStore} The TreeStore which owns this node. */ getTreeStore: function() { var root = this; while (root && !root.treeStore) { root = root.parentNode; } return root && root.treeStore; }, /** * Removes a child node from this node. * @param {Ext.data.NodeInterface} node The node to remove * @param {Boolean} [erase=false] True to erase the record using the * configured proxy. * @return {Ext.data.NodeInterface} The removed node */ removeChild: function(node, erase, suppressEvents, isMove) { var me = this, index = me.indexOf(node), i, childCount, previousSibling, treeStore = me.getTreeStore(), bulkUpdate = treeStore && treeStore.bulkUpdate, removeContext, removeRange = []; if (index === -1 || (suppressEvents !== true && me.fireBubbledEvent('beforeremove', [me, node, !!isMove]) === false)) { return false; } // Coalesce all layouts caused by node removal Ext.suspendLayouts(); // Coalesce sync operations across this operation treeStore && treeStore.beginUpdate(); // remove it from childNodes collection Ext.Array.erase(me.childNodes, index, 1); // update child refs if (me.firstChild === node) { me.setFirstChild(node.nextSibling); } if (me.lastChild === node) { me.setLastChild(node.previousSibling); } // Update previous sibling to point to its new next. previousSibling = node.previousSibling; if (previousSibling) { node.previousSibling.nextSibling = node.nextSibling; } // Update the next sibling to point to its new previous if (node.nextSibling) { node.nextSibling.previousSibling = node.previousSibling; // And if it's the new first child, let it know if (index === 0) { node.nextSibling.updateInfo(false, { isFirst: true }); } // Update subsequent siblings' index values for (i = index, childCount = me.childNodes.length; i < childCount; i++) { me.childNodes[i].updateInfo(false, { index: i }); } } // If the removed node had no next sibling, but had a previous, // update the previous sibling so it knows it's the last else if (previousSibling) { previousSibling.updateInfo(false, { isLast: true }); // We're removing the last child. // Ensure connectors are correct by updating the UI on all intervening nodes (descendants) between previous sibling and new node. if (!bulkUpdate) { if (previousSibling.isExpanded()) { previousSibling.cascade(me.triggerUIUpdate); } // No intervening descendant nodes, just update the previous sibling else { previousSibling.triggerUIUpdate(); } } } // If this node suddenly doesn't have child nodes anymore, update // myself if (!me.childNodes.length && !bulkUpdate) { me.triggerUIUpdate(); } // Flush layouts caused by updating the UI Ext.resumeLayouts(true); if (suppressEvents !== true) { // Context argument to events. removeContext = { parentNode: node.parentNode, previousSibling: node.previousSibling, nextSibling: node.nextSibling }; // Inform the TreeStore so that descendant nodes can be removed. me.callTreeStore('beforeNodeRemove', [[node], !!isMove, removeRange]); node.previousSibling = node.nextSibling = node.parentNode = null; me.fireBubbledEvent('remove', [me, node, !!isMove, removeContext]); // Inform the TreeStore so that the node unregistered and unjoined. me.callTreeStore('onNodeRemove', [[node], !!isMove, removeRange]); } // Update removed node's pointers *after* firing event so that listeners // can tell where the removal took place if (erase) { node.erase(true); } else { node.clear(); } // Must clear the parentNode silently upon remove from the TreeStore. // Any subsequent append to any node will trigger dirtiness // (It may be added to a different node of the same ID, e.g. "root"). // lastParentId still needed for TreeStore's clearRemovedOnLoad functionality to be able to link // nodes in the removed array to nodes under the reloading node's tree. // to be able to if (!isMove) { node.set({ parentId: null, lastParentId: me.getId() }, silently); } // Coalesce sync operations across this operation if (treeStore) { treeStore.endUpdate(); } return node; }, /** * Creates a copy (clone) of this Node. * @param {String} [id] A new id, defaults to this Node's id. * @param {Ext.data.session.Session} [session] The session to which the new record belongs. * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node. * False to copy without child Nodes. * @return {Ext.data.NodeInterface} A copy of this Node. */ copy: function(newId, session, deep) { var me = this, result, args = [newId], len = me.childNodes ? me.childNodes.length : 0, i; // Historical API of NodeInterface#copy was (newId, deep) // We must keep that working if a Session is passed. // Second argument is Session in superclass copy method. if (session && session.isSession) { args.push(session); } else if (arguments.length < 3) { deep = session; } result = me.callParent(args); // Move child nodes across to the copy if required if (deep) { for (i = 0; i < len; i++) { result.appendChild(me.childNodes[i].copy(undefined, true)); } } return result; }, /** * Clears the node. * @private * @param {Boolean} [erase=false] True to erase the node using the configured * proxy. * @param {Boolean} [resetChildren=false] True to reset child nodes */ clear: function(erase, resetChildren) { var me = this, data; // clear any references from the node me.parentNode = me.previousSibling = me.nextSibling = null; if (erase) { me.firstChild = me.lastChild = me.child