microsoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemail
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
905 lines
33 KiB
905 lines
33 KiB
/** |
|
* The TreePanel provides tree-structured UI representation of tree-structured data. |
|
* A TreePanel must be bound to a {@link Ext.data.TreeStore}. |
|
* |
|
* TreePanels support multiple columns through the {@link #columns} configuration. |
|
* |
|
* By default a TreePanel contains a single column which uses the `text` Field of |
|
* the store's nodes. |
|
* |
|
* Simple TreePanel using inline data: |
|
* |
|
* @example |
|
* var store = Ext.create('Ext.data.TreeStore', { |
|
* root: { |
|
* expanded: true, |
|
* children: [ |
|
* { text: 'detention', leaf: true }, |
|
* { text: 'homework', expanded: true, children: [ |
|
* { text: 'book report', leaf: true }, |
|
* { text: 'algebra', leaf: true} |
|
* ] }, |
|
* { text: 'buy lottery tickets', leaf: true } |
|
* ] |
|
* } |
|
* }); |
|
* |
|
* Ext.create('Ext.tree.Panel', { |
|
* title: 'Simple Tree', |
|
* width: 200, |
|
* height: 150, |
|
* store: store, |
|
* rootVisible: false, |
|
* renderTo: Ext.getBody() |
|
* }); |
|
* |
|
* For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of |
|
* {@link Ext.data.NodeInterface NodeInterface} config options. |
|
* |
|
* Unless the TreeStore is configured with a {@link Ext.data.Model model} of your choosing, nodes in the {@link Ext.data.TreeStore} are by default, instances of {@link Ext.data.TreeModel}. |
|
* |
|
* # Heterogeneous node types. |
|
* |
|
* If the tree needs to use different data model classes at different levels there is much flexibility in how to specify this. |
|
* |
|
* ### Configuring the Reader. |
|
* If you configure the proxy's reader with a {@link Ext.data.reader.Reader#typeProperty typeProperty}, then the server is in control of which data model |
|
* types are created. A discriminator field is used in the raw data to decide which class to instantiate. |
|
* **If this is configured, then the data from the server is prioritized over other ways of determining node class**. |
|
* |
|
* @example |
|
* Ext.define('myApp.Territory', { |
|
* extend: 'Ext.data.TreeModel', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.define('myApp.Country', { |
|
* extend: 'Ext.data.TreeModel', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.define('myApp.City', { |
|
* extend: 'Ext.data.TreeModel', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.create('Ext.tree.Panel', { |
|
* renderTo: document.body, |
|
* height: 200, |
|
* width: 400, |
|
* title: 'Sales Areas - using typeProperty', |
|
* rootVisible: false, |
|
* store: { |
|
* // Child types use namespace of store's model by default |
|
* model: 'myApp.Territory', |
|
* proxy: { |
|
* type: 'memory', |
|
* reader: { |
|
* typeProperty: 'mtype' |
|
* } |
|
* }, |
|
* root: { |
|
* children: [{ |
|
* name: 'Europe, ME, Africa', |
|
* mtype: 'Territory', |
|
* children: [{ |
|
* name: 'UK of GB & NI', |
|
* mtype: 'Country', |
|
* children: [{ |
|
* name: 'London', |
|
* mtype: 'City', |
|
* leaf: true |
|
* }] |
|
* }] |
|
* }, { |
|
* name: 'North America', |
|
* mtype: 'Territory', |
|
* children: [{ |
|
* name: 'USA', |
|
* mtype: 'Country', |
|
* children: [{ |
|
* name: 'Redwood City', |
|
* mtype: 'City', |
|
* leaf: true |
|
* }] |
|
* }] |
|
* }] |
|
* } |
|
* } |
|
* }); |
|
* |
|
* ### Node being loaded decides. |
|
* You can declare your TreeModel subclasses with a {@link Ext.data.TreeModel#childType childType} which means that the node being loaded decides the |
|
* class to instantiate for all of its child nodes. |
|
* |
|
* It is important to note that if the root node is {@link Ext.tree.Panel#rootVisible hidden}, its type will default to the store's model type, and if left |
|
* as the default (`{@link Ext.data.TreeModel}`) this will have no knowledge of creation of special child node types. So be sure to specify a store model in this case: |
|
* |
|
* @example |
|
* Ext.define('myApp.TerritoryRoot', { |
|
* extend: 'Ext.data.TreeModel', |
|
* childType: 'myApp.Territory', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.define('myApp.Territory', { |
|
* extend: 'Ext.data.TreeModel', |
|
* childType: 'myApp.Country', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.define('myApp.Country', { |
|
* extend: 'Ext.data.TreeModel', |
|
* childType: 'myApp.City', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.define('myApp.City', { |
|
* extend: 'Ext.data.TreeModel', |
|
* fields: [{ |
|
* name: 'text', |
|
* mapping: 'name' |
|
* }] |
|
* }); |
|
* Ext.create('Ext.tree.Panel', { |
|
* renderTo: document.body, |
|
* height: 200, |
|
* width: 400, |
|
* title: 'Sales Areas', |
|
* rootVisible: false, |
|
* store: { |
|
* model: 'myApp.TerritoryRoot', // Needs to be this so it knows to create 'Country' child nodes |
|
* root: { |
|
* children: [{ |
|
* name: 'Europe, ME, Africa', |
|
* children: [{ |
|
* name: 'UK of GB & NI', |
|
* children: [{ |
|
* name: 'London', |
|
* leaf: true |
|
* }] |
|
* }] |
|
* }, { |
|
* name: 'North America', |
|
* children: [{ |
|
* name: 'USA', |
|
* children: [{ |
|
* name: 'Redwood City', |
|
* leaf: true |
|
* }] |
|
* }] |
|
* }] |
|
* } |
|
* } |
|
* }); |
|
* |
|
* # Data structure |
|
* |
|
* The {@link Ext.data.TreeStore TreeStore} maintains a {@link Ext.data.TreeStore#getRoot root node} and a hierarchical structure of {@link Ext.data.TreeModel node}s. |
|
* |
|
* The {@link Ext.tree.View UI} of the tree is driven by a {Ext.data.NodeStore NodeStore} which is a flattened view of *visible* nodes. |
|
* The NodeStore is dynamically updated to reflect the visibility state of nodes as nodes are added, removed or expanded. The UI |
|
* responds to mutation events fire by the NodeStore. |
|
* |
|
* Note that nodes have several more {@link Ext.data.Model#cfg-fields fields} in order to describe their state within the hierarchy. |
|
* |
|
* If you add store listeners to the {@link Ext.data.Store#event-update update} event, then you will receive notification when any of this state changes. |
|
* You should check the array of modified field names passed to the listener to decide whether the listener should take action or ignore the event. |
|
*/ |
|
Ext.define('Ext.tree.Panel', { |
|
extend: 'Ext.panel.Table', |
|
alias: 'widget.treepanel', |
|
alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'], |
|
requires: [ |
|
'Ext.tree.View', |
|
'Ext.selection.TreeModel', |
|
'Ext.tree.Column', |
|
'Ext.data.TreeStore', |
|
'Ext.tree.NavigationModel' |
|
], |
|
viewType: 'treeview', |
|
|
|
treeCls: Ext.baseCSSPrefix + 'tree-panel', |
|
|
|
/** |
|
* @cfg {Boolean} [rowLines=false] |
|
* Configure as true to separate rows with visible horizontal lines (depends on theme). |
|
*/ |
|
rowLines: false, |
|
|
|
/** |
|
* @cfg {Boolean} [lines=true] |
|
* False to disable tree lines. |
|
*/ |
|
lines: true, |
|
|
|
/** |
|
* @cfg {Boolean} [useArrows=false] |
|
* True to use Vista-style arrows in the tree. |
|
*/ |
|
useArrows: false, |
|
|
|
/** |
|
* @cfg {Boolean} [singleExpand=false] |
|
* True if only 1 node per branch may be expanded. |
|
*/ |
|
singleExpand: false, |
|
|
|
ddConfig: { |
|
enableDrag: true, |
|
enableDrop: true |
|
}, |
|
|
|
/** |
|
* @cfg {Boolean} animate |
|
* True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [rootVisible=true] |
|
* False to hide the root node. |
|
* |
|
* Note that trees *always* have a root node. If you do not specify a {@link #cfg-root} node, one will be created. |
|
* |
|
* If the root node is not visible, then in order for a tree to appear to the end user, the root node is autoloaded with its child nodes. |
|
*/ |
|
rootVisible: true, |
|
|
|
/** |
|
* @cfg {String} [displayField=text] |
|
* The field inside the model that will be used as the node's text. |
|
*/ |
|
displayField: 'text', |
|
|
|
/** |
|
* @cfg {Ext.data.Model/Ext.data.TreeModel/Object} root |
|
* Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded |
|
* data without having to specify a TreeStore and Model. A store and model will be created and root will be passed |
|
* to that store. For example: |
|
* |
|
* Ext.create('Ext.tree.Panel', { |
|
* title: 'Simple Tree', |
|
* root: { |
|
* text: "Root node", |
|
* expanded: true, |
|
* children: [ |
|
* { text: "Child 1", leaf: true }, |
|
* { text: "Child 2", leaf: true } |
|
* ] |
|
* }, |
|
* renderTo: Ext.getBody() |
|
* }); |
|
*/ |
|
root: null, |
|
|
|
// Required for the Lockable Mixin. These are the configurations which will be copied to the |
|
// normal and locked sub tablepanels |
|
normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'], |
|
lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'], |
|
|
|
isTree: true, |
|
|
|
/** |
|
* @cfg {Boolean} hideHeaders |
|
* True to hide the headers. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} folderSort |
|
* True to automatically prepend a leaf sorter to the store. |
|
*/ |
|
|
|
/** |
|
* @cfg {Ext.data.TreeStore} store (required) |
|
* The {@link Ext.data.TreeStore Store} the tree should use as its data source. |
|
*/ |
|
|
|
arrowCls: Ext.baseCSSPrefix + 'tree-arrows', |
|
linesCls: Ext.baseCSSPrefix + 'tree-lines', |
|
noLinesCls: Ext.baseCSSPrefix + 'tree-no-lines', |
|
autoWidthCls: Ext.baseCSSPrefix + 'autowidth-table', |
|
|
|
constructor: function(config) { |
|
config = config || {}; |
|
if (config.animate === undefined) { |
|
config.animate = Ext.isBoolean(this.animate) ? this.animate : Ext.enableFx; |
|
} |
|
this.enableAnimations = config.animate; |
|
delete config.animate; |
|
|
|
this.callParent([config]); |
|
}, |
|
|
|
initComponent: function() { |
|
var me = this, |
|
cls = [me.treeCls], |
|
store = me.store, |
|
view; |
|
|
|
if (me.useArrows) { |
|
cls.push(me.arrowCls); |
|
me.lines = false; |
|
} |
|
|
|
if (me.lines) { |
|
cls.push(me.linesCls); |
|
} else if (!me.useArrows) { |
|
cls.push(me.noLinesCls); |
|
} |
|
|
|
if (Ext.isString(store)) { |
|
store = me.store = Ext.StoreMgr.lookup(store); |
|
} else if (!store || !store.isStore) { |
|
store = Ext.apply({ |
|
type: 'tree', |
|
root: me.root, |
|
fields: me.fields, |
|
model: me.model, |
|
proxy: 'memory', |
|
folderSort: me.folderSort |
|
}, store); |
|
store = me.store = Ext.StoreMgr.lookup(store); |
|
} else if (me.root) { |
|
store = me.store = Ext.data.StoreManager.lookup(store); |
|
store.setRoot(me.root); |
|
if (me.folderSort !== undefined) { |
|
store.folderSort = me.folderSort; |
|
store.sort(); |
|
} |
|
} |
|
|
|
// Store must have the same idea about root visibility as us BEFORE callParent binds it. |
|
store.setRootVisible(me.rootVisible); |
|
|
|
// If there is no root node defined, then create one. |
|
if (!store.getRoot()) { |
|
store.setRoot({}); |
|
} |
|
|
|
me.viewConfig = Ext.apply({ |
|
rootVisible: me.rootVisible, |
|
animate: me.enableAnimations, |
|
singleExpand: me.singleExpand, |
|
node: store.getRoot(), |
|
hideHeaders: me.hideHeaders, |
|
navigationModel: 'tree' |
|
}, me.viewConfig); |
|
|
|
// If the user specifies the headers collection manually then don't inject our |
|
// own |
|
if (!me.columns) { |
|
if (me.initialConfig.hideHeaders === undefined) { |
|
me.hideHeaders = true; |
|
} |
|
me.addCls(me.autoWidthCls); |
|
me.columns = [{ |
|
xtype : 'treecolumn', |
|
text : 'Name', |
|
flex : 1, |
|
dataIndex: me.displayField |
|
}]; |
|
} |
|
|
|
if (me.cls) { |
|
cls.push(me.cls); |
|
} |
|
me.cls = cls.join(' '); |
|
|
|
me.callParent(); |
|
|
|
view = me.getView(); |
|
|
|
// Relay events from the TreeView. |
|
// An injected LockingView relays events from its locked side's View |
|
me.relayEvents(view, [ |
|
/** |
|
* @event checkchange |
|
* Fires when a node with a checkbox's checked property changes |
|
* @param {Ext.data.TreeModel} node The node who's checked property was changed |
|
* @param {Boolean} checked The node's new checked state |
|
*/ |
|
'checkchange', |
|
/** |
|
* @event afteritemexpand |
|
* @inheritdoc Ext.tree.View#afteritemexpand |
|
*/ |
|
'afteritemexpand', |
|
/** |
|
* @event afteritemcollapse |
|
* @inheritdoc Ext.tree.View#afteritemcollapse |
|
*/ |
|
'afteritemcollapse' |
|
]); |
|
}, |
|
|
|
// @private |
|
// Hook into the TreeStore. |
|
bindStore: function(store, initial) { |
|
var me = this, |
|
root = store.getRoot(), |
|
bufferedRenderer = me.bufferedRenderer; |
|
|
|
// Bind to store, and autocreate the BufferedRenderer. |
|
me.callParent(arguments); |
|
|
|
// If we're in a reconfigure (we already have a BufferedRenderer which is bound to our old store), |
|
// rebind the BufferedRenderer |
|
if (bufferedRenderer) { |
|
if (bufferedRenderer.store) { |
|
bufferedRenderer.bindStore(store); |
|
} |
|
} |
|
|
|
// The TreeStore needs to know about this TreePanel's singleExpand constraint so that |
|
// it can ensure the compliance of NodeInterface.expandAll. |
|
store.singleExpand = me.singleExpand; |
|
|
|
// Monitor the TreeStore for the root node being changed. Return a Destroyable object |
|
me.storeListeners = me.mon(store, { |
|
destroyable: true, |
|
rootchange: me.onRootChange, |
|
scope: me |
|
}); |
|
|
|
// Relay store events. relayEvents always returns a Destroyable object. |
|
me.storeRelayers = me.relayEvents(store, [ |
|
/** |
|
* @event beforeload |
|
* @inheritdoc Ext.data.TreeStore#beforeload |
|
*/ |
|
'beforeload', |
|
|
|
/** |
|
* @event load |
|
* @inheritdoc Ext.data.TreeStore#load |
|
*/ |
|
'load' |
|
]); |
|
|
|
// Relay store events with prefix. Return a Destroyable object |
|
me.rootRelayers = me.mon(root, { |
|
destroyable: true, |
|
|
|
/** |
|
* @event itemappend |
|
* @inheritdoc Ext.data.TreeStore#nodeappend |
|
*/ |
|
append: me.createRelayer('itemappend'), |
|
|
|
/** |
|
* @event itemremove |
|
* @inheritdoc Ext.data.TreeStore#noderemove |
|
*/ |
|
remove: me.createRelayer('itemremove'), |
|
|
|
/** |
|
* @event itemmove |
|
* @inheritdoc Ext.data.TreeStore#nodemove |
|
*/ |
|
move: me.createRelayer('itemmove', [0, 4]), |
|
|
|
/** |
|
* @event iteminsert |
|
* @inheritdoc Ext.data.TreeStore#nodeinsert |
|
*/ |
|
insert: me.createRelayer('iteminsert'), |
|
|
|
/** |
|
* @event beforeitemappend |
|
* @inheritdoc Ext.data.TreeStore#nodebeforeappend |
|
*/ |
|
beforeappend: me.createRelayer('beforeitemappend'), |
|
|
|
/** |
|
* @event beforeitemremove |
|
* @inheritdoc Ext.data.TreeStore#nodebeforeremove |
|
*/ |
|
beforeremove: me.createRelayer('beforeitemremove'), |
|
|
|
/** |
|
* @event beforeitemmove |
|
* @inheritdoc Ext.data.TreeStore#nodebeforemove |
|
*/ |
|
beforemove: me.createRelayer('beforeitemmove'), |
|
|
|
/** |
|
* @event beforeiteminsert |
|
* @inheritdoc Ext.data.TreeStore#nodebeforeinsert |
|
*/ |
|
beforeinsert: me.createRelayer('beforeiteminsert'), |
|
|
|
/** |
|
* @event itemexpand |
|
* @inheritdoc Ext.data.TreeStore#nodeexpand |
|
*/ |
|
expand: me.createRelayer('itemexpand', [0, 1]), |
|
|
|
/** |
|
* @event itemcollapse |
|
* @inheritdoc Ext.data.TreeStore#nodecollapse |
|
*/ |
|
collapse: me.createRelayer('itemcollapse', [0, 1]), |
|
|
|
/** |
|
* @event beforeitemexpand |
|
* @inheritdoc Ext.data.TreeStore#nodebeforeexpand |
|
*/ |
|
beforeexpand: me.createRelayer('beforeitemexpand', [0, 1]), |
|
|
|
/** |
|
* @event beforeitemcollapse |
|
* @inheritdoc Ext.data.TreeStore#nodebeforecollapse |
|
*/ |
|
beforecollapse: me.createRelayer('beforeitemcollapse', [0, 1]) |
|
}); |
|
|
|
// If rootVisible is false, we *might* need to expand the node. |
|
// If store is autoLoad, that will already have been kicked off. |
|
// If its already expanded, or in the process of loading, the TreeStore |
|
// has started that at the end of updateRoot |
|
if (!me.rootVisible && !store.autoLoad && !(root.isExpanded() || root.isLoading())) { |
|
// A hidden root must be expanded, unless it's overridden with autoLoad: false. |
|
// If it's loaded, set its expanded field (silently), and skip ahead to the onNodeExpand callback. |
|
if (root.isLoaded()) { |
|
root.data.expanded = true; |
|
store.onNodeExpand(root, root.childNodes); |
|
} |
|
// Root is not loaded; go through the expand mechanism to force a load |
|
// unless we were told explicitly not to load the store by setting |
|
// autoLoad: false. This is useful with Direct proxy in cases when |
|
// Direct API is loaded dynamically and may not be available at the time |
|
// when TreePanel is created. |
|
else if (store.autoLoad !== false) { |
|
root.data.expanded = false; |
|
root.expand(); |
|
} |
|
} |
|
|
|
// TreeStore must have an upward link to the TreePanel so that nodes can find their owning tree in NodeInterface.getOwnerTree |
|
store.ownerTree = me; |
|
|
|
if (!initial) { |
|
me.view.setRootNode(root); |
|
} |
|
}, |
|
|
|
// @private |
|
unbindStore: function() { |
|
var me = this, |
|
store = me.store; |
|
|
|
if (store) { |
|
me.callParent(); |
|
Ext.destroy(me.storeListeners, me.storeRelayers, me.rootRelayers); |
|
delete store.ownerTree; |
|
store.singleExpand = null; |
|
} |
|
}, |
|
|
|
/** |
|
* Sets root node of this tree. All trees *always* have a root node. It may be {@link #rootVisible hidden}. |
|
* |
|
* If the passed node has not already been loaded with child nodes, and has its expanded field set, this triggers the {@link #cfg-store} to load the child nodes of the root. |
|
* @param {Ext.data.TreeModel/Object} root |
|
* @return {Ext.data.TreeModel} The new root |
|
*/ |
|
setRootNode: function() { |
|
return this.store.setRoot.apply(this.store, arguments); |
|
}, |
|
|
|
/** |
|
* Returns the root node for this tree. |
|
* @return {Ext.data.TreeModel} |
|
*/ |
|
getRootNode: function() { |
|
return this.store.getRoot(); |
|
}, |
|
|
|
onRootChange: function(root) { |
|
this.view.setRootNode(root); |
|
}, |
|
|
|
/** |
|
* Retrieve an array of checked records. |
|
* @return {Ext.data.TreeModel[]} An array containing the checked records |
|
*/ |
|
getChecked: function() { |
|
return this.getView().getChecked(); |
|
}, |
|
|
|
isItemChecked: function(rec) { |
|
return rec.get('checked'); |
|
}, |
|
|
|
/** |
|
* Expands a record that is loaded in the tree. |
|
* @param {Ext.data.Model} record The record to expand |
|
* @param {Boolean} [deep] True to expand nodes all the way down the tree hierarchy. |
|
* @param {Function} [callback] The function to run after the expand is completed |
|
* @param {Object} [scope] The scope of the callback function. |
|
*/ |
|
expandNode: function(record, deep, callback, scope) { |
|
return this.getView().expand(record, deep, callback, scope || this); |
|
}, |
|
|
|
/** |
|
* Collapses a record that is loaded in the tree. |
|
* @param {Ext.data.Model} record The record to collapse |
|
* @param {Boolean} [deep] True to collapse nodes all the way up the tree hierarchy. |
|
* @param {Function} [callback] The function to run after the collapse is completed |
|
* @param {Object} [scope] The scope of the callback function. |
|
*/ |
|
collapseNode: function(record, deep, callback, scope) { |
|
return this.getView().collapse(record, deep, callback, scope || this); |
|
}, |
|
|
|
/** |
|
* Expand all nodes |
|
* @param {Function} [callback] A function to execute when the expand finishes. |
|
* @param {Object} [scope] The scope of the callback function |
|
*/ |
|
expandAll: function(callback, scope) { |
|
var me = this, |
|
root = me.getRootNode(); |
|
|
|
if (root) { |
|
Ext.suspendLayouts(); |
|
root.expand(true, callback, scope || me); |
|
Ext.resumeLayouts(true); |
|
} |
|
}, |
|
|
|
/** |
|
* Collapse all nodes |
|
* @param {Function} [callback] A function to execute when the collapse finishes. |
|
* @param {Object} [scope] The scope of the callback function |
|
*/ |
|
collapseAll: function(callback, scope) { |
|
var me = this, |
|
root = me.getRootNode(), |
|
view = me.getView(); |
|
|
|
if (root) { |
|
Ext.suspendLayouts(); |
|
scope = scope || me; |
|
if (view.rootVisible) { |
|
root.collapse(true, callback, scope); |
|
} else { |
|
root.collapseChildren(true, callback, scope); |
|
} |
|
Ext.resumeLayouts(true); |
|
} |
|
}, |
|
|
|
/** |
|
* Expand the tree to the path of a particular node. This is the way to expand a known path |
|
* when the intervening nodes are not yet loaded. |
|
* |
|
* The path may be an absolute path (beginning with a `'/'` character) from the root, eg: |
|
* |
|
* '/rootId/nodeA/nodeB/nodeC' |
|
* |
|
* Or, the path may be relative, starting from an **existing** node in the tree: |
|
* |
|
* 'nodeC/nodeD' |
|
* |
|
* @param {String} path The path to expand. The path may be absolute, including a leading separator and starting |
|
* from the root node id, or relative with no leading separator, starting from an *existing* |
|
* node in the tree. |
|
* @param {Object} [options] An object containing options to modify the operation. |
|
* @param {String} [options.field] The field to get the data from. Defaults to the model idProperty. |
|
* @param {String} [options.separator='/'] A separator to use. |
|
* @param {Boolean} [options.select] Pass as `true` to select the specified row. |
|
* @param {Boolean} [options.focus] Pass as `true` to focus the specified row. |
|
* @param {Function} [options.callback] A function to execute when the expand finishes. |
|
* @param {Boolean} options.callback.success `true` if the node expansion was successful. |
|
* @param {Ext.data.Model} options.callback.record If successful, the target record. |
|
* @param {HTMLElement} options.callback.node If successful, the record's view node. If unsuccessful, the |
|
* last view node encountered while expanding the path. |
|
* @param {Object} [options.scope] The scope (`this` reference) in which the callback function is executed. |
|
*/ |
|
expandPath: function(path, options) { |
|
var args = arguments, |
|
me = this, |
|
view = me.view, |
|
field = (options && options.field) || me.store.model.idProperty, |
|
select, |
|
doFocus, |
|
separator = (options && options.separator) || '/', |
|
callback, |
|
scope, |
|
current, |
|
index, |
|
keys, |
|
rooted, |
|
expander; |
|
|
|
// New option object API |
|
if (options && typeof options === 'object') { |
|
field = options.field || me.store.model.idProperty; |
|
separator = options.separator || '/'; |
|
callback = options.callback; |
|
scope = options.scope; |
|
select = options.select; |
|
doFocus = options.focus; |
|
} |
|
// Old multi argument API |
|
else { |
|
field = args[1] || me.store.model.idProperty; |
|
separator = args[2] || '/'; |
|
callback = args[3]; |
|
scope = args[4]; |
|
} |
|
|
|
if (Ext.isEmpty(path)) { |
|
return Ext.callback(callback, scope || me, [false, null]); |
|
} |
|
|
|
keys = path.split(separator); |
|
|
|
// If they began the path with '/', this indicates starting from the root ID. |
|
// otherwise, then can start at any *existing* node id. |
|
rooted = !keys[0]; |
|
if (rooted) { |
|
current = me.getRootNode(); |
|
index = 1; |
|
} |
|
// Not rooted, gather the first node in the path which MUST already exist. |
|
else { |
|
current = me.store.findNode(field, keys[0]); |
|
index = 0; |
|
} |
|
|
|
// Invalid root. Relative start could not be found, absolute start was not the rootNode. |
|
if (!current || (rooted && current.get(field) !== keys[1])) { |
|
return Ext.callback(callback, scope || me, [false, current]); |
|
} |
|
|
|
// The expand success callback passed to every expand call down the path. |
|
// Called in the scope of the node being expanded. |
|
expander = function(newChildren) { |
|
var node = this, |
|
len, i, value; |
|
|
|
// We've arrived at the end of the path. |
|
if (++index === keys.length) { |
|
if (select) { |
|
view.getSelectionModel().select(node); |
|
} |
|
if (doFocus) { |
|
view.getNavigationModel().setPosition(node, 0); |
|
} |
|
return Ext.callback(callback, scope || me, [true, node, view.getNode(node)]); |
|
} |
|
|
|
// Find the next child in the path if it's there and expand it. |
|
for (i = 0, len = newChildren ? newChildren.length : 0; i < len; i++) { |
|
// The ids paths may be numeric, so cast the value to a string for comparison |
|
node = newChildren[i]; |
|
value = node.get(field); |
|
if (value || value === 0) { |
|
value = value.toString(); |
|
} |
|
if (value === keys[index]) { |
|
return node.expand(false, expander); |
|
} |
|
} |
|
|
|
// If we get here, there's been a miss along the path, and the operation is a fail. |
|
node = this; |
|
Ext.callback(callback, scope || me, [false, node, view.getNode(node)]); |
|
}; |
|
current.expand(false, expander); |
|
}, |
|
|
|
/** |
|
* Expand the tree to the path of a particular node, then scroll it into view. |
|
* @param {String} path The path to bring into view. The path may be absolute, including a leading separator and starting |
|
* from the root node id, or relative with no leading separator, starting from an *existing* node in the tree. |
|
* @param {Object} [options] An object containing options to modify the operation. |
|
* @param {String} [options.field] The field to get the data from. Defaults to the model idProperty. |
|
* @param {String} [options.separator='/'] A separator to use. |
|
* @param {Boolean} [options.animate] Pass `true` to animate the row into view. |
|
* @param {Boolean} [options.highlight] Pass `true` to highlight the row with a glow animation when it is in view. |
|
* @param {Boolean} [options.select] Pass as `true` to select the specified row. |
|
* @param {Boolean} [options.focus] Pass as `true` to focus the specified row. |
|
* @param {Function} [options.callback] A function to execute when the expand finishes. |
|
* @param {Boolean} options.callback.success `true` if the node expansion was successful. |
|
* @param {Ext.data.Model} options.callback.record If successful, the target record. |
|
* @param {HTMLElement} options.callback.node If successful, the record's view node. If unsuccessful, the |
|
* last view node encountered while expanding the path. |
|
* @param {Object} [options.scope] The scope (`this` reference) in which the callback function is executed. |
|
*/ |
|
ensureVisible: function(path, options) { |
|
// They passed a record instance. Use the TablePanel's method. |
|
if (path.isEntity) { |
|
return this.callParent([path, options]); |
|
} |
|
|
|
var me = this, |
|
field = (options && options.field) || me.store.model.idProperty, |
|
separator = (options && options.separator) || '/', |
|
callback, |
|
scope, |
|
keys, |
|
rooted, |
|
last, |
|
node, |
|
parentNode, |
|
onLastExpanded = function(success, lastExpanded, lastExpandedHtmlNode, targetNode) { |
|
if (!targetNode && success && lastExpanded) { |
|
targetNode = lastExpanded.findChild(field, last); |
|
} |
|
// Once we have the node, we can use the TablePanel's ensureVisible method |
|
if (targetNode) { |
|
me.doEnsureVisible(targetNode, options); |
|
} else { |
|
Ext.callback(callback, scope || me, [false, lastExpanded]); |
|
} |
|
}; |
|
|
|
if (options) { |
|
callback = options.callback; |
|
scope = options.scope; |
|
} |
|
|
|
keys = path.split(separator); |
|
rooted = !keys[0]; |
|
last = keys.pop(); |
|
|
|
// If the path was "foo/bar" or "/foo/Bar" |
|
if (keys.length && !(rooted && keys.length === 1)) { |
|
me.expandPath(keys.join(separator), field, separator, onLastExpanded); |
|
} |
|
// If the path was "foo" or "/foo" |
|
else { |
|
node = me.store.findNode(field, last); |
|
if (node) { |
|
parentNode = node.parentNode; |
|
if (parentNode && !parentNode.isExpanded()) { |
|
parentNode.expand(); |
|
} |
|
// Pass the target node as the 4th parameter so the callback doesn't have to look it up |
|
onLastExpanded(true, null, null, node); |
|
} else { |
|
Ext.callback(callback, scope || me, [false, null]); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Expand the tree to the path of a particular node, then select it. |
|
* @param {String} path The path to expand. The path may be absolute, including a leading separator and |
|
* starting from the root node id, or relative with no leading separator, starting from |
|
* an *existing* node in the tree. |
|
* @param {String} [field] The field to get the data from. Defaults to the model idProperty. |
|
* @param {String} [separator='/'] A separator to use. |
|
* @param {Function} [callback] A function to execute when the select finishes. |
|
* @param {Boolean} callback.success `true` if the node expansion was successful. |
|
* @param {Ext.data.NodeInterface} callback.lastNode If successful, the target node. If unsuccessful, the |
|
* last tree node encountered while expanding the path. |
|
* @param {HTMLElement} callback.node If successful, the record's view node. |
|
* @param {Object} [scope] The scope of the callback function |
|
*/ |
|
selectPath: function(path, field, separator, callback, scope) { |
|
this.ensureVisible(path, { |
|
field: field, |
|
separator: separator, |
|
select: true, |
|
callback: callback, |
|
scope: scope |
|
}); |
|
} |
|
});
|
|
|