linuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacos
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.
906 lines
33 KiB
906 lines
33 KiB
9 years ago
|
/**
|
||
|
* 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
|
||
|
});
|
||
|
}
|
||
|
});
|