extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,149 lines (1,026 loc) • 110 kB
JavaScript
/**
* 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