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 * Used as a view by {@link Ext.tree.Panel TreePanel}.
18 Ext.define('Ext.tree.View', {
19 extend: 'Ext.view.Table',
20 alias: 'widget.treeview',
22 loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
23 expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
25 expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
26 checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
27 expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
29 // Class to add to the node wrap element used to hold nodes when a parent is being
30 // collapsed or expanded. During the animation, UI interaction is forbidden by testing
31 // for an ancestor node with this class.
32 nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
37 * @cfg {Boolean} rootVisible
38 * False to hide the root node.
43 * @cfg {Boolean} animate
44 * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
48 collapseDuration: 250,
50 toggleOnDblClick: true,
52 initComponent: function() {
55 if (me.initialConfig.animate === undefined) {
56 me.animate = Ext.enableFx;
59 me.store = Ext.create('Ext.data.NodeStore', {
61 rootVisible: me.rootVisible,
63 beforeexpand: me.onBeforeExpand,
65 beforecollapse: me.onBeforeCollapse,
66 collapse: me.onCollapse,
72 me.setRootNode(me.node);
75 me.callParent(arguments);
78 processUIEvent: function(e) {
79 // If the clicked node is part of an animation, ignore the click.
80 // This is because during a collapse animation, the associated Records
81 // will already have been removed from the Store, and the event is not processable.
82 if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
85 return this.callParent(arguments);
89 this.store.removeAll();
92 setRootNode: function(node) {
94 me.store.setNode(node);
96 if (!me.rootVisible) {
101 onRender: function() {
105 me.callParent(arguments);
110 delegate: me.expanderSelector,
111 mouseover: me.onExpanderMouseOver,
112 mouseout: me.onExpanderMouseOut
116 delegate: me.checkboxSelector,
117 click: me.onCheckboxChange
121 onCheckboxChange: function(e, t) {
123 item = e.getTarget(me.getItemSelector(), me.getTargetEl());
126 me.onCheckChange(me.getRecord(item));
130 onCheckChange: function(record){
131 var checked = record.get('checked');
132 if (Ext.isBoolean(checked)) {
134 record.set('checked', checked);
135 this.fireEvent('checkchange', record, checked);
139 getChecked: function() {
141 this.node.cascadeBy(function(rec){
142 if (rec.get('checked')) {
149 isItemChecked: function(rec){
150 return rec.get('checked');
153 createAnimWrap: function(record, index) {
155 headerCt = this.panel.headerCt,
156 headers = headerCt.getGridColumns(),
157 i = 0, len = headers.length, item,
158 node = this.getNode(record),
161 for (; i < len; i++) {
163 thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
166 nodeEl = Ext.get(node);
167 tmpEl = nodeEl.insertSibling({
170 '<td colspan="' + headerCt.getColumnCount() + '">',
171 '<div class="' + this.nodeAnimWrapCls + '">',
172 '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
187 animateEl: tmpEl.down('div'),
188 targetEl: tmpEl.down('tbody')
192 getAnimWrap: function(parent) {
197 // We are checking to see which parent is having the animation wrap
199 if (parent.animWrap) {
200 return parent.animWrap;
202 parent = parent.parentNode;
207 doAdd: function(nodes, records, index) {
208 // If we are adding records which have a parent that is currently expanding
209 // lets add them to the animation wrap
212 parent = record.parentNode,
215 animWrap = me.getAnimWrap(parent),
216 targetEl, children, len;
218 if (!animWrap || !animWrap.expanding) {
220 return me.callParent(arguments);
223 // We need the parent that has the animWrap, not the nodes parent
224 parent = animWrap.record;
226 // If there is an anim wrap we do our special magic logic
227 targetEl = animWrap.targetEl;
228 children = targetEl.dom.childNodes;
230 // We subtract 1 from the childrens length because we have a tr in there with the th'es
231 len = children.length - 1;
233 // The relative index is the index in the full flat collection minus the index of the wraps parent
234 relativeIndex = index - me.indexOf(parent) - 1;
236 // If we are adding records to the wrap that have a higher relative index then there are currently children
237 // it means we have to append the nodes to the wrap
238 if (!len || relativeIndex >= len) {
239 targetEl.appendChild(nodes);
241 // If there are already more children then the relative index it means we are adding child nodes of
242 // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
244 // +1 because of the tr with th'es that is already there
245 Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
248 // We also have to update the CompositeElementLite collection of the DataView
249 Ext.Array.insert(a, index, nodes);
251 // If we were in an animation we need to now change the animation
252 // because the targetEl just got higher.
253 if (animWrap.isAnimating) {
258 beginBulkUpdate: function(){
259 this.bulkUpdate = true;
260 this.ownerCt.changingScrollbars = true;
263 endBulkUpdate: function(){
265 ownerCt = me.ownerCt;
267 me.bulkUpdate = false;
268 me.ownerCt.changingScrollbars = true;
272 onRemove : function(ds, record, index) {
274 bulk = me.bulkUpdate;
276 me.doRemove(record, index);
278 me.updateIndexes(index);
280 if (me.store.getCount() === 0){
284 me.fireEvent('itemremove', record, index);
288 doRemove: function(record, index) {
289 // If we are adding records which have a parent that is currently expanding
290 // lets add them to the animation wrap
292 parent = record.parentNode,
294 animWrap = me.getAnimWrap(record),
295 node = all.item(index).dom;
297 if (!animWrap || !animWrap.collapsing) {
299 return me.callParent(arguments);
302 animWrap.targetEl.appendChild(node);
303 all.removeElement(index);
306 onBeforeExpand: function(parent, records, index) {
310 if (!me.rendered || !me.animate) {
314 if (me.getNode(parent)) {
315 animWrap = me.getAnimWrap(parent);
317 animWrap = parent.animWrap = me.createAnimWrap(parent);
318 animWrap.animateEl.setHeight(0);
320 else if (animWrap.collapsing) {
321 // If we expand this node while it is still expanding then we
322 // have to remove the nodes from the animWrap.
323 animWrap.targetEl.select(me.itemSelector).remove();
325 animWrap.expanding = true;
326 animWrap.collapsing = false;
330 onExpand: function(parent) {
332 queue = me.animQueue,
339 if (me.singleExpand) {
340 me.ensureSingleExpand(parent);
343 animWrap = me.getAnimWrap(parent);
350 animateEl = animWrap.animateEl;
351 targetEl = animWrap.targetEl;
353 animateEl.stopAnimation();
354 // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
356 animateEl.slideIn('t', {
357 duration: me.expandDuration,
360 lastframe: function() {
361 // Move all the nodes out of the anim wrap to their proper location
362 animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
363 animWrap.el.remove();
365 delete animWrap.record.animWrap;
371 animWrap.isAnimating = true;
374 resetScrollers: function(){
375 if (!this.bulkUpdate) {
376 var panel = this.panel;
378 panel.determineScrollbars();
379 panel.invalidateScroller();
383 onBeforeCollapse: function(parent, records, index) {
387 if (!me.rendered || !me.animate) {
391 if (me.getNode(parent)) {
392 animWrap = me.getAnimWrap(parent);
394 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
396 else if (animWrap.expanding) {
397 // If we collapse this node while it is still expanding then we
398 // have to remove the nodes from the animWrap.
399 animWrap.targetEl.select(this.itemSelector).remove();
401 animWrap.expanding = false;
402 animWrap.collapsing = true;
406 onCollapse: function(parent) {
408 queue = me.animQueue,
410 animWrap = me.getAnimWrap(parent),
418 animateEl = animWrap.animateEl;
419 targetEl = animWrap.targetEl;
423 // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
424 animateEl.stopAnimation();
425 animateEl.slideOut('t', {
426 duration: me.collapseDuration,
429 lastframe: function() {
430 animWrap.el.remove();
431 delete animWrap.record.animWrap;
437 animWrap.isAnimating = true;
441 * Checks if a node is currently undergoing animation
443 * @param {Ext.data.Model} node The node
444 * @return {Boolean} True if the node is animating
446 isAnimating: function(node) {
447 return !!this.animQueue[node.getId()];
450 collectData: function(records) {
451 var data = this.callParent(arguments),
457 for (; i < len; i++) {
460 if (record.get('qtip')) {
461 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
462 if (record.get('qtitle')) {
463 row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
466 if (record.isExpanded()) {
467 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
469 if (record.isLoading()) {
470 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
478 * Expands a record that is loaded in the view.
479 * @param {Ext.data.Model} record The record to expand
480 * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
481 * @param {Function} callback (optional) The function to run after the expand is completed
482 * @param {Object} scope (optional) The scope of the callback function.
484 expand: function(record, deep, callback, scope) {
485 return record.expand(deep, callback, scope);
489 * Collapses a record that is loaded in the view.
490 * @param {Ext.data.Model} record The record to collapse
491 * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
492 * @param {Function} callback (optional) The function to run after the collapse is completed
493 * @param {Object} scope (optional) The scope of the callback function.
495 collapse: function(record, deep, callback, scope) {
496 return record.collapse(deep, callback, scope);
500 * Toggles a record between expanded and collapsed.
501 * @param {Ext.data.Model} recordInstance
503 toggle: function(record) {
504 this[record.isExpanded() ? 'collapse' : 'expand'](record);
507 onItemDblClick: function(record, item, index) {
508 this.callParent(arguments);
509 if (this.toggleOnDblClick) {
514 onBeforeItemMouseDown: function(record, item, index, e) {
515 if (e.getTarget(this.expanderSelector, item)) {
518 return this.callParent(arguments);
521 onItemClick: function(record, item, index, e) {
522 if (e.getTarget(this.expanderSelector, item)) {
526 return this.callParent(arguments);
529 onExpanderMouseOver: function(e, t) {
530 e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
533 onExpanderMouseOut: function(e, t) {
534 e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
538 * Gets the base TreeStore from the bound TreePanel.
540 getTreeStore: function() {
541 return this.panel.store;
544 ensureSingleExpand: function(node) {
545 var parent = node.parentNode;
547 parent.eachChild(function(child) {
548 if (child !== node && child.isExpanded()) {