3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.tree.View
17 * @extends Ext.view.Table
19 Ext.define('Ext.tree.View', {
20 extend: 'Ext.view.Table',
21 alias: 'widget.treeview',
23 loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
24 expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
26 expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
27 checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
28 expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
33 * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
38 * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
42 collapseDuration: 250,
44 toggleOnDblClick: true,
46 initComponent: function() {
49 if (me.initialConfig.animate === undefined) {
50 me.animate = Ext.enableFx;
53 me.store = Ext.create('Ext.data.NodeStore', {
55 rootVisible: me.rootVisible,
57 beforeexpand: me.onBeforeExpand,
59 beforecollapse: me.onBeforeCollapse,
60 collapse: me.onCollapse,
66 me.setRootNode(me.node);
69 me.callParent(arguments);
73 this.store.removeAll();
76 setRootNode: function(node) {
78 me.store.setNode(node);
80 if (!me.rootVisible) {
85 onRender: function() {
87 opts = {delegate: me.expanderSelector},
90 me.callParent(arguments);
95 delegate: me.expanderSelector,
96 mouseover: me.onExpanderMouseOver,
97 mouseout: me.onExpanderMouseOut
101 delegate: me.checkboxSelector,
102 click: me.onCheckboxChange
106 onCheckboxChange: function(e, t) {
107 var item = e.getTarget(this.getItemSelector(), this.getTargetEl()),
111 record = this.getRecord(item);
112 value = !record.get('checked');
113 record.set('checked', value);
114 this.fireEvent('checkchange', record, value);
118 getChecked: function() {
120 this.node.cascadeBy(function(rec){
121 if (rec.get('checked')) {
128 isItemChecked: function(rec){
129 return rec.get('checked');
132 createAnimWrap: function(record, index) {
134 headerCt = this.panel.headerCt,
135 headers = headerCt.getGridColumns(),
136 i = 0, len = headers.length, item,
137 node = this.getNode(record),
140 for (; i < len; i++) {
142 thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
145 nodeEl = Ext.get(node);
146 tmpEl = nodeEl.insertSibling({
149 '<td colspan="' + headerCt.getColumnCount() + '">',
150 '<div class="' + Ext.baseCSSPrefix + 'tree-animator-wrap' + '">',
151 '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
166 animateEl: tmpEl.down('div'),
167 targetEl: tmpEl.down('tbody')
171 getAnimWrap: function(parent) {
176 // We are checking to see which parent is having the animation wrap
178 if (parent.animWrap) {
179 return parent.animWrap;
181 parent = parent.parentNode;
186 doAdd: function(nodes, records, index) {
187 // If we are adding records which have a parent that is currently expanding
188 // lets add them to the animation wrap
191 parent = record.parentNode,
194 animWrap = me.getAnimWrap(parent),
195 targetEl, children, len;
197 if (!animWrap || !animWrap.expanding) {
199 return me.callParent(arguments);
202 // We need the parent that has the animWrap, not the nodes parent
203 parent = animWrap.record;
205 // If there is an anim wrap we do our special magic logic
206 targetEl = animWrap.targetEl;
207 children = targetEl.dom.childNodes;
209 // We subtract 1 from the childrens length because we have a tr in there with the th'es
210 len = children.length - 1;
212 // The relative index is the index in the full flat collection minus the index of the wraps parent
213 relativeIndex = index - me.indexOf(parent) - 1;
215 // If we are adding records to the wrap that have a higher relative index then there are currently children
216 // it means we have to append the nodes to the wrap
217 if (!len || relativeIndex >= len) {
218 targetEl.appendChild(nodes);
220 // If there are already more children then the relative index it means we are adding child nodes of
221 // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
223 // +1 because of the tr with th'es that is already there
224 Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
227 // We also have to update the CompositeElementLite collection of the DataView
228 Ext.Array.insert(a, index, nodes);
230 // If we were in an animation we need to now change the animation
231 // because the targetEl just got higher.
232 if (animWrap.isAnimating) {
237 doRemove: function(record, index) {
238 // If we are adding records which have a parent that is currently expanding
239 // lets add them to the animation wrap
241 parent = record.parentNode,
243 animWrap = me.getAnimWrap(record),
244 node = all.item(index).dom;
246 if (!animWrap || !animWrap.collapsing) {
248 return me.callParent(arguments);
251 animWrap.targetEl.appendChild(node);
252 all.removeElement(index);
255 onBeforeExpand: function(parent, records, index) {
259 if (!me.rendered || !me.animate) {
263 if (me.getNode(parent)) {
264 animWrap = me.getAnimWrap(parent);
266 animWrap = parent.animWrap = me.createAnimWrap(parent);
267 animWrap.animateEl.setHeight(0);
269 else if (animWrap.collapsing) {
270 // If we expand this node while it is still expanding then we
271 // have to remove the nodes from the animWrap.
272 animWrap.targetEl.select(me.itemSelector).remove();
274 animWrap.expanding = true;
275 animWrap.collapsing = false;
279 onExpand: function(parent) {
281 queue = me.animQueue,
288 if (me.singleExpand) {
289 me.ensureSingleExpand(parent);
292 animWrap = me.getAnimWrap(parent);
299 animateEl = animWrap.animateEl;
300 targetEl = animWrap.targetEl;
302 animateEl.stopAnimation();
303 // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
305 animateEl.slideIn('t', {
306 duration: me.expandDuration,
309 lastframe: function() {
310 // Move all the nodes out of the anim wrap to their proper location
311 animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
312 animWrap.el.remove();
314 delete animWrap.record.animWrap;
320 animWrap.isAnimating = true;
323 resetScrollers: function(){
324 var panel = this.panel;
326 panel.determineScrollbars();
327 panel.invalidateScroller();
330 onBeforeCollapse: function(parent, records, index) {
334 if (!me.rendered || !me.animate) {
338 if (me.getNode(parent)) {
339 animWrap = me.getAnimWrap(parent);
341 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
343 else if (animWrap.expanding) {
344 // If we collapse this node while it is still expanding then we
345 // have to remove the nodes from the animWrap.
346 animWrap.targetEl.select(this.itemSelector).remove();
348 animWrap.expanding = false;
349 animWrap.collapsing = true;
353 onCollapse: function(parent) {
355 queue = me.animQueue,
357 animWrap = me.getAnimWrap(parent),
365 animateEl = animWrap.animateEl;
366 targetEl = animWrap.targetEl;
370 // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
371 animateEl.stopAnimation();
372 animateEl.slideOut('t', {
373 duration: me.collapseDuration,
376 lastframe: function() {
377 animWrap.el.remove();
378 delete animWrap.record.animWrap;
384 animWrap.isAnimating = true;
388 * Checks if a node is currently undergoing animation
390 * @param {Ext.data.Model} node The node
391 * @return {Boolean} True if the node is animating
393 isAnimating: function(node) {
394 return !!this.animQueue[node.getId()];
397 collectData: function(records) {
398 var data = this.callParent(arguments),
404 for (; i < len; i++) {
407 if (record.get('qtip')) {
408 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
409 if (record.get('qtitle')) {
410 row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
413 if (record.isExpanded()) {
414 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
416 if (record.isLoading()) {
417 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
425 * Expand a record that is loaded in the view.
426 * @param {Ext.data.Model} record The record to expand
427 * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
428 * @param {Function} callback (optional) The function to run after the expand is completed
429 * @param {Object} scope (optional) The scope of the callback function.
431 expand: function(record, deep, callback, scope) {
432 return record.expand(deep, callback, scope);
436 * Collapse a record that is loaded in the view.
437 * @param {Ext.data.Model} record The record to collapse
438 * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
439 * @param {Function} callback (optional) The function to run after the collapse is completed
440 * @param {Object} scope (optional) The scope of the callback function.
442 collapse: function(record, deep, callback, scope) {
443 return record.collapse(deep, callback, scope);
447 * Toggle a record between expanded and collapsed.
448 * @param {Ext.data.Record} recordInstance
450 toggle: function(record) {
451 this[record.isExpanded() ? 'collapse' : 'expand'](record);
454 onItemDblClick: function(record, item, index) {
455 this.callParent(arguments);
456 if (this.toggleOnDblClick) {
461 onBeforeItemMouseDown: function(record, item, index, e) {
462 if (e.getTarget(this.expanderSelector, item)) {
465 return this.callParent(arguments);
468 onItemClick: function(record, item, index, e) {
469 if (e.getTarget(this.expanderSelector, item)) {
473 return this.callParent(arguments);
476 onExpanderMouseOver: function(e, t) {
477 e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
480 onExpanderMouseOut: function(e, t) {
481 e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
485 * Gets the base TreeStore from the bound TreePanel.
487 getTreeStore: function() {
488 return this.panel.store;
491 ensureSingleExpand: function(node) {
492 var parent = node.parentNode;
494 parent.eachChild(function(child) {
495 if (child !== node && child.isExpanded()) {