Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / view / AbstractView.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.view.AbstractView
17  * @extends Ext.Component
18  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
19  * @private
20  */
21 Ext.define('Ext.view.AbstractView', {
22     extend: 'Ext.Component',
23     alternateClassName: 'Ext.view.AbstractView',
24     requires: [
25         'Ext.LoadMask',
26         'Ext.data.StoreManager',
27         'Ext.CompositeElementLite',
28         'Ext.DomQuery',
29         'Ext.selection.DataViewModel'
30     ],
31
32     inheritableStatics: {
33         getRecord: function(node) {
34             return this.getBoundView(node).getRecord(node);
35         },
36
37         getBoundView: function(node) {
38             return Ext.getCmp(node.boundView);
39         }
40     },
41
42     /**
43      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
44      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
45      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
46      */
47     /**
48      * @cfg {Ext.data.Store} store (required)
49      * The {@link Ext.data.Store} to bind this DataView to.
50      */
51
52     /**
53      * @cfg {Boolean} deferInitialRefresh
54      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
55      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
56      * by the expensive update of the view structure.</p>
57      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
58      * <i>selection</i> may not take place at render time. To access a View's item elements as soon as possible, use the {@link #viewready} event.
59      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
60      */
61     deferInitialRefresh: true,
62
63     /**
64      * @cfg {String} itemSelector (required)
65      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
66      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
67      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
68      * only be one root level element that matches the selector for each record.
69      */
70
71     /**
72      * @cfg {String} itemCls
73      * Specifies the class to be assigned to each element in the view when used in conjunction with the
74      * {@link #itemTpl} configuration.
75      */
76     itemCls: Ext.baseCSSPrefix + 'dataview-item',
77
78     /**
79      * @cfg {String/String[]/Ext.XTemplate} itemTpl
80      * The inner portion of the item template to be rendered. Follows an XTemplate
81      * structure and will be placed inside of a tpl.
82      */
83
84     /**
85      * @cfg {String} overItemCls
86      * A CSS class to apply to each item in the view on mouseover.
87      * Ensure {@link #trackOver} is set to `true` to make use of this.
88      */
89
90     /**
91      * @cfg {String} loadingText
92      * A string to display during data load operations.  If specified, this text will be
93      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
94      * contents will continue to display normally until the new data is loaded and the contents are replaced.
95      */
96     loadingText: 'Loading...',
97
98     /**
99      * @cfg {Boolean/Object} loadMask
100      * False to disable a load mask from displaying will the view is loading. This can also be a
101      * {@link Ext.LoadMask} configuration object.
102      */
103     loadMask: true,
104
105     /**
106      * @cfg {String} loadingCls
107      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
108      */
109
110     /**
111      * @cfg {Boolean} loadingUseMsg
112      * Whether or not to use the loading message.
113      * @private
114      */
115     loadingUseMsg: true,
116
117
118     /**
119      * @cfg {Number} loadingHeight
120      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
121      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
122      * loading mask is applied and there are no other contents in the data view.
123      */
124
125     /**
126      * @cfg {String} [selectedItemCls='x-view-selected']
127      * A CSS class to apply to each selected item in the view.
128      */
129     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
130
131     /**
132      * @cfg {String} emptyText
133      * The text to display in the view when there is no data to display.
134      * Note that when using local data the emptyText will not be displayed unless you set
135      * the {@link #deferEmptyText} option to false.
136      */
137     emptyText: "",
138
139     /**
140      * @cfg {Boolean} deferEmptyText
141      * True to defer emptyText being applied until the store's first load.
142      */
143     deferEmptyText: true,
144
145     /**
146      * @cfg {Boolean} trackOver
147      * True to enable mouseenter and mouseleave events
148      */
149     trackOver: false,
150
151     /**
152      * @cfg {Boolean} blockRefresh
153      * Set this to true to ignore datachanged events on the bound store. This is useful if
154      * you wish to provide custom transition animations via a plugin
155      */
156     blockRefresh: false,
157
158     /**
159      * @cfg {Boolean} disableSelection
160      * True to disable selection within the DataView. This configuration will lock the selection model
161      * that the DataView uses.
162      */
163
164
165     //private
166     last: false,
167
168     triggerEvent: 'itemclick',
169     triggerCtEvent: 'containerclick',
170
171     addCmpEvents: function() {
172
173     },
174
175     // private
176     initComponent : function(){
177         var me = this,
178             isDef = Ext.isDefined,
179             itemTpl = me.itemTpl,
180             memberFn = {};
181
182         if (itemTpl) {
183             if (Ext.isArray(itemTpl)) {
184                 // string array
185                 itemTpl = itemTpl.join('');
186             } else if (Ext.isObject(itemTpl)) {
187                 // tpl instance
188                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
189                 itemTpl = itemTpl.html;
190             }
191
192             if (!me.itemSelector) {
193                 me.itemSelector = '.' + me.itemCls;
194             }
195
196             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
197             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
198         }
199
200         //<debug>
201         if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
202             Ext.Error.raise({
203                 sourceClass: 'Ext.view.View',
204                 tpl: me.tpl,
205                 itemSelector: me.itemSelector,
206                 msg: "DataView requires both tpl and itemSelector configurations to be defined."
207             });
208         }
209         //</debug>
210
211         me.callParent();
212         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
213             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
214         }
215
216         //<debug>
217         // backwards compat alias for overClass/selectedClass
218         // TODO: Consider support for overCls generation Ext.Component config
219         if (isDef(me.overCls) || isDef(me.overClass)) {
220             if (Ext.isDefined(Ext.global.console)) {
221                 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
222             }
223             me.overItemCls = me.overCls || me.overClass;
224             delete me.overCls;
225             delete me.overClass;
226         }
227
228         if (me.overItemCls) {
229             me.trackOver = true;
230         }
231
232         if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
233             if (Ext.isDefined(Ext.global.console)) {
234                 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
235             }
236             me.selectedItemCls = me.selectedCls || me.selectedClass;
237             delete me.selectedCls;
238             delete me.selectedClass;
239         }
240         //</debug>
241
242         me.addEvents(
243             /**
244              * @event beforerefresh
245              * Fires before the view is refreshed
246              * @param {Ext.view.View} this The DataView object
247              */
248             'beforerefresh',
249             /**
250              * @event refresh
251              * Fires when the view is refreshed
252              * @param {Ext.view.View} this The DataView object
253              */
254             'refresh',
255             /**
256              * @event viewready
257              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
258              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
259              * for selection until this event fires.
260              * @param {Ext.view.View} this
261              */
262             'viewready',
263             /**
264              * @event itemupdate
265              * Fires when the node associated with an individual record is updated
266              * @param {Ext.data.Model} record The model instance
267              * @param {Number} index The index of the record/node
268              * @param {HTMLElement} node The node that has just been updated
269              */
270             'itemupdate',
271             /**
272              * @event itemadd
273              * Fires when the nodes associated with an recordset have been added to the underlying store
274              * @param {Ext.data.Model[]} records The model instance
275              * @param {Number} index The index at which the set of record/nodes starts
276              * @param {HTMLElement[]} node The node that has just been updated
277              */
278             'itemadd',
279             /**
280              * @event itemremove
281              * Fires when the node associated with an individual record is removed
282              * @param {Ext.data.Model} record The model instance
283              * @param {Number} index The index of the record/node
284              */
285             'itemremove'
286         );
287
288         me.addCmpEvents();
289
290         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
291         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
292         me.all = new Ext.CompositeElementLite();
293     },
294
295     onRender: function() {
296         var me = this,
297             mask = me.loadMask,
298             cfg = {
299                 msg: me.loadingText,
300                 msgCls: me.loadingCls,
301                 useMsg: me.loadingUseMsg
302             };
303
304         me.callParent(arguments);
305
306         if (mask) {
307             // either a config object
308             if (Ext.isObject(mask)) {
309                 cfg = Ext.apply(cfg, mask);
310             }
311             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
312             // If this DataView is floating, then mask this DataView.
313             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
314             // LoadMask captures the element upon render.
315             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
316             me.loadMask.on({
317                 scope: me,
318                 beforeshow: me.onMaskBeforeShow,
319                 hide: me.onMaskHide
320             });
321         }
322     },
323
324     onMaskBeforeShow: function(){
325         var loadingHeight = this.loadingHeight;
326         
327         this.getSelectionModel().deselectAll();
328         if (loadingHeight) {
329             this.setCalculatedSize(undefined, loadingHeight);
330         }
331     },
332
333     onMaskHide: function(){
334         var me = this;
335         
336         if (!me.destroying && me.loadingHeight) {
337             me.setHeight(me.height);
338         }
339     },
340
341     afterRender: function() {
342         this.callParent(arguments);
343
344         // Init the SelectionModel after any on('render') listeners have been added.
345         // Drag plugins create a DragDrop instance in a render listener, and that needs
346         // to see an itemmousedown event first.
347         this.getSelectionModel().bindComponent(this);
348     },
349
350     /**
351      * Gets the selection model for this view.
352      * @return {Ext.selection.Model} The selection model
353      */
354     getSelectionModel: function(){
355         var me = this,
356             mode = 'SINGLE';
357
358         if (!me.selModel) {
359             me.selModel = {};
360         }
361
362         if (me.simpleSelect) {
363             mode = 'SIMPLE';
364         } else if (me.multiSelect) {
365             mode = 'MULTI';
366         }
367
368         Ext.applyIf(me.selModel, {
369             allowDeselect: me.allowDeselect,
370             mode: mode
371         });
372
373         if (!me.selModel.events) {
374             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
375         }
376
377         if (!me.selModel.hasRelaySetup) {
378             me.relayEvents(me.selModel, [
379                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
380             ]);
381             me.selModel.hasRelaySetup = true;
382         }
383
384         // lock the selection model if user
385         // has disabled selection
386         if (me.disableSelection) {
387             me.selModel.locked = true;
388         }
389
390         return me.selModel;
391     },
392
393     /**
394      * Refreshes the view by reloading the data from the store and re-rendering the template.
395      */
396     refresh: function() {
397         var me = this,
398             el,
399             records;
400
401         if (!me.rendered || me.isDestroyed) {
402             return;
403         }
404
405         me.fireEvent('beforerefresh', me);
406         el = me.getTargetEl();
407         records = me.store.getRange();
408
409         el.update('');
410         if (records.length < 1) {
411             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
412                 el.update(me.emptyText);
413             }
414             me.all.clear();
415         } else {
416             me.tpl.overwrite(el, me.collectData(records, 0));
417             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
418             me.updateIndexes(0);
419         }
420
421         me.selModel.refresh();
422         me.hasSkippedEmptyText = true;
423         me.fireEvent('refresh', me);
424
425         // Upon first refresh, fire the viewready event.
426         // Reconfiguring the grid "renews" this event.
427         if (!me.viewReady) {
428             // Fire an event when deferred content becomes available.
429             // This supports grid Panel's deferRowRender capability
430             me.viewReady = true;
431             me.fireEvent('viewready', me);
432         }
433     },
434
435     /**
436      * Function which can be overridden to provide custom formatting for each Record that is used by this
437      * DataView's {@link #tpl template} to render each node.
438      * @param {Object/Object[]} data The raw data object that was used to create the Record.
439      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
440      * @param {Ext.data.Model} record The Record being prepared for rendering.
441      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
442      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
443      */
444     prepareData: function(data, index, record) {
445         if (record) {
446             Ext.apply(data, record.getAssociatedData());
447         }
448         return data;
449     },
450
451     /**
452      * <p>Function which can be overridden which returns the data object passed to this
453      * DataView's {@link #tpl template} to render the whole DataView.</p>
454      * <p>This is usually an Array of data objects, each element of which is processed by an
455      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
456      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
457      * provide non-repeating data such as headings, totals etc.</p>
458      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
459      * @param {Number} startIndex the index number of the Record being prepared for rendering.
460      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
461      * contain <i>named</i> properties.
462      */
463     collectData : function(records, startIndex){
464         var r = [],
465             i = 0,
466             len = records.length,
467             record;
468
469         for(; i < len; i++){
470             record = records[i];
471             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
472         }
473         return r;
474     },
475
476     // private
477     bufferRender : function(records, index){
478         var div = document.createElement('div');
479         this.tpl.overwrite(div, this.collectData(records, index));
480         return Ext.query(this.getItemSelector(), div);
481     },
482
483     // private
484     onUpdate : function(ds, record){
485         var me = this,
486             index = me.store.indexOf(record),
487             node;
488
489         if (index > -1){
490             node = me.bufferRender([record], index)[0];
491             // ensure the node actually exists in the DOM
492             if (me.getNode(record)) {
493                 me.all.replaceElement(index, node, true);
494                 me.updateIndexes(index, index);
495                 // Maintain selection after update
496                 // TODO: Move to approriate event handler.
497                 me.selModel.refresh();
498                 me.fireEvent('itemupdate', record, index, node);
499             }
500         }
501
502     },
503
504     // private
505     onAdd : function(ds, records, index) {
506         var me = this,
507             nodes;
508
509         if (me.all.getCount() === 0) {
510             me.refresh();
511             return;
512         }
513
514         nodes = me.bufferRender(records, index);
515         me.doAdd(nodes, records, index);
516
517         me.selModel.refresh();
518         me.updateIndexes(index);
519         me.fireEvent('itemadd', records, index, nodes);
520     },
521
522     doAdd: function(nodes, records, index) {
523         var all = this.all;
524
525         if (index < all.getCount()) {
526             all.item(index).insertSibling(nodes, 'before', true);
527         } else {
528             all.last().insertSibling(nodes, 'after', true);
529         }
530
531         Ext.Array.insert(all.elements, index, nodes);
532     },
533
534     // private
535     onRemove : function(ds, record, index) {
536         var me = this;
537
538         me.doRemove(record, index);
539         me.updateIndexes(index);
540         if (me.store.getCount() === 0){
541             me.refresh();
542         }
543         me.fireEvent('itemremove', record, index);
544     },
545
546     doRemove: function(record, index) {
547         this.all.removeElement(index, true);
548     },
549
550     /**
551      * Refreshes an individual node's data from the store.
552      * @param {Number} index The item's data index in the store
553      */
554     refreshNode : function(index){
555         this.onUpdate(this.store, this.store.getAt(index));
556     },
557
558     // private
559     updateIndexes : function(startIndex, endIndex) {
560         var ns = this.all.elements,
561             records = this.store.getRange(),
562             i;
563             
564         startIndex = startIndex || 0;
565         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
566         for(i = startIndex; i <= endIndex; i++){
567             ns[i].viewIndex = i;
568             ns[i].viewRecordId = records[i].internalId;
569             if (!ns[i].boundView) {
570                 ns[i].boundView = this.id;
571             }
572         }
573     },
574
575     /**
576      * Returns the store associated with this DataView.
577      * @return {Ext.data.Store} The store
578      */
579     getStore : function(){
580         return this.store;
581     },
582
583     /**
584      * Changes the data store bound to this view and refreshes it.
585      * @param {Ext.data.Store} store The store to bind to this view
586      */
587     bindStore : function(store, initial) {
588         var me = this,
589             maskStore;
590
591         if (!initial && me.store) {
592             if (store !== me.store && me.store.autoDestroy) {
593                 me.store.destroyStore();
594             }
595             else {
596                 me.mun(me.store, {
597                     scope: me,
598                     datachanged: me.onDataChanged,
599                     add: me.onAdd,
600                     remove: me.onRemove,
601                     update: me.onUpdate,
602                     clear: me.refresh
603                 });
604             }
605             if (!store) {
606                 // Ensure we have an instantiated LoadMask before we unbind it.
607                 if (me.loadMask && me.loadMask.bindStore) {
608                     me.loadMask.bindStore(null);
609                 }
610                 me.store = null;
611             }
612         }
613         if (store) {
614             store = Ext.data.StoreManager.lookup(store);
615             me.mon(store, {
616                 scope: me,
617                 datachanged: me.onDataChanged,
618                 add: me.onAdd,
619                 remove: me.onRemove,
620                 update: me.onUpdate,
621                 clear: me.refresh
622             });
623             // Ensure we have an instantiated LoadMask before we bind it.
624             if (me.loadMask && me.loadMask.bindStore) {
625                 // View's store is a NodeStore, use owning TreePanel's Store
626                 if (Ext.Array.contains(store.alias, 'store.node')) {
627                     maskStore = this.ownerCt.store;
628                 } else {
629                     maskStore = store;
630                 }
631                 me.loadMask.bindStore(maskStore);
632             }
633         }
634
635         // Flag to say that initial refresh has not been performed.
636         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
637         me.viewReady = false;
638
639         me.store = store;
640         // Bind the store to our selection model
641         me.getSelectionModel().bind(store);
642
643         /*
644          * This code used to have checks for:
645          * if (store && (!initial || store.getCount() || me.emptyText)) {
646          * Instead, just trigger a refresh and let the view itself figure out
647          * what needs to happen. It can cause incorrect display if our store
648          * has no data.
649          */
650         if (store) {
651             if (initial && me.deferInitialRefresh) {
652                 Ext.Function.defer(function () {
653                     if (!me.isDestroyed) {
654                         me.refresh(true);
655                     }
656                 }, 1);
657             } else {
658                 me.refresh(true);
659             }
660         }
661     },
662
663     /**
664      * @private
665      * Calls this.refresh if this.blockRefresh is not true
666      */
667     onDataChanged: function() {
668         if (this.blockRefresh !== true) {
669             this.refresh.apply(this, arguments);
670         }
671     },
672
673     /**
674      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
675      * @param {HTMLElement} node
676      * @return {HTMLElement} The template node
677      */
678     findItemByChild: function(node){
679         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
680     },
681
682     /**
683      * Returns the template node by the Ext.EventObject or null if it is not found.
684      * @param {Ext.EventObject} e
685      */
686     findTargetByEvent: function(e) {
687         return e.getTarget(this.getItemSelector(), this.getTargetEl());
688     },
689
690
691     /**
692      * Gets the currently selected nodes.
693      * @return {HTMLElement[]} An array of HTMLElements
694      */
695     getSelectedNodes: function(){
696         var nodes   = [],
697             records = this.selModel.getSelection(),
698             ln = records.length,
699             i  = 0;
700
701         for (; i < ln; i++) {
702             nodes.push(this.getNode(records[i]));
703         }
704
705         return nodes;
706     },
707
708     /**
709      * Gets an array of the records from an array of nodes
710      * @param {HTMLElement[]} nodes The nodes to evaluate
711      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
712      */
713     getRecords: function(nodes) {
714         var records = [],
715             i = 0,
716             len = nodes.length,
717             data = this.store.data;
718
719         for (; i < len; i++) {
720             records[records.length] = data.getByKey(nodes[i].viewRecordId);
721         }
722
723         return records;
724     },
725
726     /**
727      * Gets a record from a node
728      * @param {Ext.Element/HTMLElement} node The node to evaluate
729      *
730      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
731      */
732     getRecord: function(node){
733         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
734     },
735
736
737     /**
738      * Returns true if the passed node is selected, else false.
739      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
740      * @return {Boolean} True if selected, else false
741      */
742     isSelected : function(node) {
743         // TODO: El/Idx/Record
744         var r = this.getRecord(node);
745         return this.selModel.isSelected(r);
746     },
747
748     /**
749      * Selects a record instance by record instance or index.
750      * @param {Ext.data.Model[]/Number} records An array of records or an index
751      * @param {Boolean} [keepExisting] True to keep existing selections
752      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
753      */
754     select: function(records, keepExisting, suppressEvent) {
755         this.selModel.select(records, keepExisting, suppressEvent);
756     },
757
758     /**
759      * Deselects a record instance by record instance or index.
760      * @param {Ext.data.Model[]/Number} records An array of records or an index
761      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
762      */
763     deselect: function(records, suppressEvent) {
764         this.selModel.deselect(records, suppressEvent);
765     },
766
767     /**
768      * Gets a template node.
769      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
770      * the id of a template node or the record associated with the node.
771      * @return {HTMLElement} The node or null if it wasn't found
772      */
773     getNode : function(nodeInfo) {
774         if (!this.rendered) {
775             return null;
776         }
777         if (Ext.isString(nodeInfo)) {
778             return document.getElementById(nodeInfo);
779         }
780         if (Ext.isNumber(nodeInfo)) {
781             return this.all.elements[nodeInfo];
782         }
783         if (nodeInfo instanceof Ext.data.Model) {
784             return this.getNodeByRecord(nodeInfo);
785         }
786         return nodeInfo; // already an HTMLElement
787     },
788
789     /**
790      * @private
791      */
792     getNodeByRecord: function(record) {
793         var ns = this.all.elements,
794             ln = ns.length,
795             i = 0;
796
797         for (; i < ln; i++) {
798             if (ns[i].viewRecordId === record.internalId) {
799                 return ns[i];
800             }
801         }
802
803         return null;
804     },
805
806     /**
807      * Gets a range nodes.
808      * @param {Number} start (optional) The index of the first node in the range
809      * @param {Number} end (optional) The index of the last node in the range
810      * @return {HTMLElement[]} An array of nodes
811      */
812     getNodes: function(start, end) {
813         var ns = this.all.elements,
814             nodes = [],
815             i;
816
817         start = start || 0;
818         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
819         if (start <= end) {
820             for (i = start; i <= end && ns[i]; i++) {
821                 nodes.push(ns[i]);
822             }
823         } else {
824             for (i = start; i >= end && ns[i]; i--) {
825                 nodes.push(ns[i]);
826             }
827         }
828         return nodes;
829     },
830
831     /**
832      * Finds the index of the passed node.
833      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
834      * or a record associated with a node.
835      * @return {Number} The index of the node or -1
836      */
837     indexOf: function(node) {
838         node = this.getNode(node);
839         if (Ext.isNumber(node.viewIndex)) {
840             return node.viewIndex;
841         }
842         return this.all.indexOf(node);
843     },
844
845     onDestroy : function() {
846         var me = this;
847
848         me.all.clear();
849         me.callParent();
850         me.bindStore(null);
851         me.selModel.destroy();
852     },
853
854     // invoked by the selection model to maintain visual UI cues
855     onItemSelect: function(record) {
856         var node = this.getNode(record);
857         
858         if (node) {
859             Ext.fly(node).addCls(this.selectedItemCls);
860         }
861     },
862
863     // invoked by the selection model to maintain visual UI cues
864     onItemDeselect: function(record) {
865         var node = this.getNode(record);
866         
867         if (node) {
868             Ext.fly(node).removeCls(this.selectedItemCls);
869         }
870     },
871
872     getItemSelector: function() {
873         return this.itemSelector;
874     }
875 }, function() {
876     // all of this information is available directly
877     // from the SelectionModel itself, the only added methods
878     // to DataView regarding selection will perform some transformation/lookup
879     // between HTMLElement/Nodes to records and vice versa.
880     Ext.deprecate('extjs', '4.0', function() {
881         Ext.view.AbstractView.override({
882             /**
883              * @cfg {Boolean} [multiSelect=false]
884              * True to allow selection of more than one item at a time, false to allow selection of only a single item
885              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
886              */
887             /**
888              * @cfg {Boolean} [singleSelect=false]
889              * True to allow selection of exactly one item at a time, false to allow no selection at all.
890              * Note that if {@link #multiSelect} = true, this value will be ignored.
891              */
892             /**
893              * @cfg {Boolean} [simpleSelect=false]
894              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
895              * false to force the user to hold Ctrl or Shift to select more than on item.
896              */
897
898             /**
899              * Gets the number of selected nodes.
900              * @return {Number} The node count
901              */
902             getSelectionCount : function(){
903                 if (Ext.global.console) {
904                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
905                 }
906                 return this.selModel.getSelection().length;
907             },
908
909             /**
910              * Gets an array of the selected records
911              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
912              */
913             getSelectedRecords : function(){
914                 if (Ext.global.console) {
915                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
916                 }
917                 return this.selModel.getSelection();
918             },
919
920             select: function(records, keepExisting, supressEvents) {
921                 if (Ext.global.console) {
922                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
923                 }
924                 var sm = this.getSelectionModel();
925                 return sm.select.apply(sm, arguments);
926             },
927
928             clearSelections: function() {
929                 if (Ext.global.console) {
930                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
931                 }
932                 var sm = this.getSelectionModel();
933                 return sm.deselectAll();
934             }
935         });
936     });
937 });
938