commit extjs-2.2.1
[extjs.git] / source / widgets / DataView.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 /**\r
10  * @class Ext.DataView\r
11  * @extends Ext.BoxComponent\r
12  * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}\r
13  * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}\r
14  * so that as the data in the store changes the view is automatically updated to reflect the changes.  The view also\r
15  * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,\r
16  * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}\r
17  * config must be provided for the DataView to determine what nodes it will be working with.</b>\r
18  *\r
19  * <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.Panel}.</p>\r
20  * <pre><code>\r
21 var store = new Ext.data.JsonStore({\r
22     url: 'get-images.php',\r
23     root: 'images',\r
24     fields: [\r
25         'name', 'url',\r
26         {name:'size', type: 'float'},\r
27         {name:'lastmod', type:'date', dateFormat:'timestamp'}\r
28     ]\r
29 });\r
30 store.load();\r
31 \r
32 var tpl = new Ext.XTemplate(\r
33     '&lt;tpl for="."&gt;',\r
34         '&lt;div class="thumb-wrap" id="{name}"&gt;',\r
35         '&lt;div class="thumb"&gt;&lt;img src="{url}" title="{name}"&gt;&lt;/div&gt;',\r
36         '&lt;span class="x-editable"&gt;{shortName}&lt;/span&gt;&lt;/div&gt;',\r
37     '&lt;/tpl&gt;',\r
38     '&lt;div class="x-clear"&gt;&lt;/div&gt;'\r
39 );\r
40 \r
41 var panel = new Ext.Panel({\r
42     id:'images-view',\r
43     frame:true,\r
44     width:535,\r
45     autoHeight:true,\r
46     collapsible:true,\r
47     layout:'fit',\r
48     title:'Simple DataView',\r
49 \r
50     items: new Ext.DataView({\r
51         store: store,\r
52         tpl: tpl,\r
53         autoHeight:true,\r
54         multiSelect: true,\r
55         overClass:'x-view-over',\r
56         itemSelector:'div.thumb-wrap',\r
57         emptyText: 'No images to display'\r
58     })\r
59 });\r
60 panel.render(document.body);\r
61 </code></pre>\r
62  * @constructor\r
63  * Create a new DataView\r
64  * @param {Object} config The config object\r
65  */\r
66 Ext.DataView = Ext.extend(Ext.BoxComponent, {\r
67     /**\r
68      * @cfg {String/Array} tpl\r
69      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should\r
70      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.\r
71      */\r
72     /**\r
73      * @cfg {Ext.data.Store} store\r
74      * The {@link Ext.data.Store} to bind this DataView to.\r
75      */\r
76     /**\r
77      * @cfg {String} itemSelector\r
78      * <b>This is a required setting</b>. A simple CSS selector (e.g. div.some-class or span:first-child) that will be \r
79      * used to determine what nodes this DataView will be working with.\r
80      */\r
81     /**\r
82      * @cfg {Boolean} multiSelect\r
83      * True to allow selection of more than one item at a time, false to allow selection of only a single item\r
84      * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).\r
85      */\r
86     /**\r
87      * @cfg {Boolean} singleSelect\r
88      * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).\r
89      * Note that if {@link #multiSelect} = true, this value will be ignored.\r
90      */\r
91     /**\r
92      * @cfg {Boolean} simpleSelect\r
93      * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,\r
94      * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).\r
95      */\r
96     /**\r
97      * @cfg {String} overClass\r
98      * A CSS class to apply to each item in the view on mouseover (defaults to undefined).\r
99      */\r
100     /**\r
101      * @cfg {String} loadingText\r
102      * A string to display during data load operations (defaults to undefined).  If specified, this text will be\r
103      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's\r
104      * contents will continue to display normally until the new data is loaded and the contents are replaced.\r
105      */\r
106     /**\r
107      * @cfg {String} selectedClass\r
108      * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').\r
109      */\r
110     selectedClass : "x-view-selected",\r
111     /**\r
112      * @cfg {String} emptyText\r
113      * The text to display in the view when there is no data to display (defaults to '').\r
114      */\r
115     emptyText : "",\r
116 \r
117     /**\r
118      * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load\r
119      */\r
120     deferEmptyText: true,\r
121     /**\r
122      * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events\r
123      */\r
124     trackOver: false,\r
125 \r
126     //private\r
127     last: false,\r
128 \r
129 \r
130     // private\r
131     initComponent : function(){\r
132         Ext.DataView.superclass.initComponent.call(this);\r
133         if(typeof this.tpl == "string"){\r
134             this.tpl = new Ext.XTemplate(this.tpl);\r
135         }\r
136 \r
137         this.addEvents(\r
138             /**\r
139              * @event beforeclick\r
140              * Fires before a click is processed. Returns false to cancel the default action.\r
141              * @param {Ext.DataView} this\r
142              * @param {Number} index The index of the target node\r
143              * @param {HTMLElement} node The target node\r
144              * @param {Ext.EventObject} e The raw event object\r
145              */\r
146             "beforeclick",\r
147             /**\r
148              * @event click\r
149              * Fires when a template node is clicked.\r
150              * @param {Ext.DataView} this\r
151              * @param {Number} index The index of the target node\r
152              * @param {HTMLElement} node The target node\r
153              * @param {Ext.EventObject} e The raw event object\r
154              */\r
155             "click",\r
156             /**\r
157              * @event mouseenter\r
158              * Fires when the mouse enters a template node. trackOver:true or an overCls must be set to enable this event.\r
159              * @param {Ext.DataView} this\r
160              * @param {Number} index The index of the target node\r
161              * @param {HTMLElement} node The target node\r
162              * @param {Ext.EventObject} e The raw event object\r
163              */\r
164             "mouseenter",\r
165             /**\r
166              * @event mouseleave\r
167              * Fires when the mouse leaves a template node. trackOver:true or an overCls must be set to enable this event.\r
168              * @param {Ext.DataView} this\r
169              * @param {Number} index The index of the target node\r
170              * @param {HTMLElement} node The target node\r
171              * @param {Ext.EventObject} e The raw event object\r
172              */\r
173             "mouseleave",\r
174             /**\r
175              * @event containerclick\r
176              * Fires when a click occurs and it is not on a template node.\r
177              * @param {Ext.DataView} this\r
178              * @param {Ext.EventObject} e The raw event object\r
179              */\r
180             "containerclick",\r
181             /**\r
182              * @event dblclick\r
183              * Fires when a template node is double clicked.\r
184              * @param {Ext.DataView} this\r
185              * @param {Number} index The index of the target node\r
186              * @param {HTMLElement} node The target node\r
187              * @param {Ext.EventObject} e The raw event object\r
188              */\r
189             "dblclick",\r
190             /**\r
191              * @event contextmenu\r
192              * Fires when a template node is right clicked.\r
193              * @param {Ext.DataView} this\r
194              * @param {Number} index The index of the target node\r
195              * @param {HTMLElement} node The target node\r
196              * @param {Ext.EventObject} e The raw event object\r
197              */\r
198             "contextmenu",\r
199             /**\r
200              * @event selectionchange\r
201              * Fires when the selected nodes change.\r
202              * @param {Ext.DataView} this\r
203              * @param {Array} selections Array of the selected nodes\r
204              */\r
205             "selectionchange",\r
206 \r
207             /**\r
208              * @event beforeselect\r
209              * Fires before a selection is made. If any handlers return false, the selection is cancelled.\r
210              * @param {Ext.DataView} this\r
211              * @param {HTMLElement} node The node to be selected\r
212              * @param {Array} selections Array of currently selected nodes\r
213              */\r
214             "beforeselect"\r
215         );\r
216 \r
217         this.all = new Ext.CompositeElementLite();\r
218         this.selected = new Ext.CompositeElementLite();\r
219     },\r
220 \r
221     // private\r
222     onRender : function(){\r
223         if(!this.el){\r
224             this.el = document.createElement('div');\r
225             this.el.id = this.id;\r
226         }\r
227         Ext.DataView.superclass.onRender.apply(this, arguments);\r
228     },\r
229 \r
230     // private\r
231     afterRender : function(){\r
232         Ext.DataView.superclass.afterRender.call(this);\r
233 \r
234         this.el.on({\r
235             "click": this.onClick,\r
236             "dblclick": this.onDblClick,\r
237             "contextmenu": this.onContextMenu,\r
238             scope:this\r
239         });\r
240 \r
241         if(this.overClass || this.trackOver){\r
242             this.el.on({\r
243                 "mouseover": this.onMouseOver,\r
244                 "mouseout": this.onMouseOut,\r
245                 scope:this\r
246             });\r
247         }\r
248 \r
249         if(this.store){\r
250             this.setStore(this.store, true);\r
251         }\r
252     },\r
253 \r
254     /**\r
255      * Refreshes the view by reloading the data from the store and re-rendering the template.\r
256      */\r
257     refresh : function(){\r
258         this.clearSelections(false, true);\r
259         this.el.update("");\r
260         var records = this.store.getRange();\r
261         if(records.length < 1){\r
262             if(!this.deferEmptyText || this.hasSkippedEmptyText){\r
263                 this.el.update(this.emptyText);\r
264             }\r
265             this.hasSkippedEmptyText = true;\r
266             this.all.clear();\r
267             return;\r
268         }\r
269         this.tpl.overwrite(this.el, this.collectData(records, 0));\r
270         this.all.fill(Ext.query(this.itemSelector, this.el.dom));\r
271         this.updateIndexes(0);\r
272     },\r
273 \r
274     /**\r
275      * Function which can be overridden to provide custom formatting for each Record that is used by this\r
276      * DataView's {@link #tpl template} to render each node.\r
277      * @param {Array/Object} data The raw data object that was used to create the Record.\r
278      * @param {Number} recordIndex the index number of the Record being prepared for rendering.\r
279      * @param {Record} record The Record being prepared for rendering.\r
280      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.\r
281      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))\r
282      */\r
283     prepareData : function(data){\r
284         return data;\r
285     },\r
286 \r
287     /**\r
288      * <p>Function which can be overridden which returns the data object passed to this\r
289      * DataView's {@link #tpl template} to render the whole DataView.</p>\r
290      * <p>This is usually an Array of data objects, each element of which is processed by an\r
291      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied\r
292      * data object as an Array. However, <i>named</i> properties may be placed into the data object to\r
293      * provide non-repeating data such as headings, totals etc.</p>\r
294      * @param records {Array} An Array of {@link Ext.data.Record}s to be rendered into the DataView.\r
295      * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also\r
296      * contain <i>named</i> properties.\r
297      */\r
298     collectData : function(records, startIndex){\r
299         var r = [];\r
300         for(var i = 0, len = records.length; i < len; i++){\r
301             r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);\r
302         }\r
303         return r;\r
304     },\r
305 \r
306     // private\r
307     bufferRender : function(records){\r
308         var div = document.createElement('div');\r
309         this.tpl.overwrite(div, this.collectData(records));\r
310         return Ext.query(this.itemSelector, div);\r
311     },\r
312 \r
313     // private\r
314     onUpdate : function(ds, record){\r
315         var index = this.store.indexOf(record);\r
316         var sel = this.isSelected(index);\r
317         var original = this.all.elements[index];\r
318         var node = this.bufferRender([record], index)[0];\r
319 \r
320         this.all.replaceElement(index, node, true);\r
321         if(sel){\r
322             this.selected.replaceElement(original, node);\r
323             this.all.item(index).addClass(this.selectedClass);\r
324         }\r
325         this.updateIndexes(index, index);\r
326     },\r
327 \r
328     // private\r
329     onAdd : function(ds, records, index){\r
330         if(this.all.getCount() == 0){\r
331             this.refresh();\r
332             return;\r
333         }\r
334         var nodes = this.bufferRender(records, index), n, a = this.all.elements;\r
335         if(index < this.all.getCount()){\r
336             n = this.all.item(index).insertSibling(nodes, 'before', true);\r
337             a.splice.apply(a, [index, 0].concat(nodes));\r
338         }else{\r
339             n = this.all.last().insertSibling(nodes, 'after', true);\r
340             a.push.apply(a, nodes);\r
341         }\r
342         this.updateIndexes(index);\r
343     },\r
344 \r
345     // private\r
346     onRemove : function(ds, record, index){\r
347         this.deselect(index);\r
348         this.all.removeElement(index, true);\r
349         this.updateIndexes(index);\r
350     },\r
351 \r
352     /**\r
353      * Refreshes an individual node's data from the store.\r
354      * @param {Number} index The item's data index in the store\r
355      */\r
356     refreshNode : function(index){\r
357         this.onUpdate(this.store, this.store.getAt(index));\r
358     },\r
359 \r
360     // private\r
361     updateIndexes : function(startIndex, endIndex){\r
362         var ns = this.all.elements;\r
363         startIndex = startIndex || 0;\r
364         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));\r
365         for(var i = startIndex; i <= endIndex; i++){\r
366             ns[i].viewIndex = i;\r
367         }\r
368     },\r
369     \r
370     /**\r
371      * Returns the store associated with this DataView.\r
372      * @return {Ext.data.Store} The store\r
373      */\r
374     getStore : function(){\r
375         return this.store;\r
376     },\r
377 \r
378     /**\r
379      * Changes the data store bound to this view and refreshes it.\r
380      * @param {Store} store The store to bind to this view\r
381      */\r
382     setStore : function(store, initial){\r
383         if(!initial && this.store){\r
384             this.store.un("beforeload", this.onBeforeLoad, this);\r
385             this.store.un("datachanged", this.refresh, this);\r
386             this.store.un("add", this.onAdd, this);\r
387             this.store.un("remove", this.onRemove, this);\r
388             this.store.un("update", this.onUpdate, this);\r
389             this.store.un("clear", this.refresh, this);\r
390         }\r
391         if(store){\r
392             store = Ext.StoreMgr.lookup(store);\r
393             store.on("beforeload", this.onBeforeLoad, this);\r
394             store.on("datachanged", this.refresh, this);\r
395             store.on("add", this.onAdd, this);\r
396             store.on("remove", this.onRemove, this);\r
397             store.on("update", this.onUpdate, this);\r
398             store.on("clear", this.refresh, this);\r
399         }\r
400         this.store = store;\r
401         if(store){\r
402             this.refresh();\r
403         }\r
404     },\r
405 \r
406     /**\r
407      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.\r
408      * @param {HTMLElement} node\r
409      * @return {HTMLElement} The template node\r
410      */\r
411     findItemFromChild : function(node){\r
412         return Ext.fly(node).findParent(this.itemSelector, this.el);\r
413     },\r
414 \r
415     // private\r
416     onClick : function(e){\r
417         var item = e.getTarget(this.itemSelector, this.el);\r
418         if(item){\r
419             var index = this.indexOf(item);\r
420             if(this.onItemClick(item, index, e) !== false){\r
421                 this.fireEvent("click", this, index, item, e);\r
422             }\r
423         }else{\r
424             if(this.fireEvent("containerclick", this, e) !== false){\r
425                 this.clearSelections();\r
426             }\r
427         }\r
428     },\r
429 \r
430     // private\r
431     onContextMenu : function(e){\r
432         var item = e.getTarget(this.itemSelector, this.el);\r
433         if(item){\r
434             this.fireEvent("contextmenu", this, this.indexOf(item), item, e);\r
435         }\r
436     },\r
437 \r
438     // private\r
439     onDblClick : function(e){\r
440         var item = e.getTarget(this.itemSelector, this.el);\r
441         if(item){\r
442             this.fireEvent("dblclick", this, this.indexOf(item), item, e);\r
443         }\r
444     },\r
445 \r
446     // private\r
447     onMouseOver : function(e){\r
448         var item = e.getTarget(this.itemSelector, this.el);\r
449         if(item && item !== this.lastItem){\r
450             this.lastItem = item;\r
451             Ext.fly(item).addClass(this.overClass);\r
452             this.fireEvent("mouseenter", this, this.indexOf(item), item, e);\r
453         }\r
454     },\r
455 \r
456     // private\r
457     onMouseOut : function(e){\r
458         if(this.lastItem){\r
459             if(!e.within(this.lastItem, true, true)){\r
460                 Ext.fly(this.lastItem).removeClass(this.overClass);\r
461                 this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);\r
462                 delete this.lastItem;\r
463             }\r
464         }\r
465     },\r
466 \r
467     // private\r
468     onItemClick : function(item, index, e){\r
469         if(this.fireEvent("beforeclick", this, index, item, e) === false){\r
470             return false;\r
471         }\r
472         if(this.multiSelect){\r
473             this.doMultiSelection(item, index, e);\r
474             e.preventDefault();\r
475         }else if(this.singleSelect){\r
476             this.doSingleSelection(item, index, e);\r
477             e.preventDefault();\r
478         }\r
479         return true;\r
480     },\r
481 \r
482     // private\r
483     doSingleSelection : function(item, index, e){\r
484         if(e.ctrlKey && this.isSelected(index)){\r
485             this.deselect(index);\r
486         }else{\r
487             this.select(index, false);\r
488         }\r
489     },\r
490 \r
491     // private\r
492     doMultiSelection : function(item, index, e){\r
493         if(e.shiftKey && this.last !== false){\r
494             var last = this.last;\r
495             this.selectRange(last, index, e.ctrlKey);\r
496             this.last = last; // reset the last\r
497         }else{\r
498             if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){\r
499                 this.deselect(index);\r
500             }else{\r
501                 this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);\r
502             }\r
503         }\r
504     },\r
505 \r
506     /**\r
507      * Gets the number of selected nodes.\r
508      * @return {Number} The node count\r
509      */\r
510     getSelectionCount : function(){\r
511         return this.selected.getCount()\r
512     },\r
513 \r
514     /**\r
515      * Gets the currently selected nodes.\r
516      * @return {Array} An array of HTMLElements\r
517      */\r
518     getSelectedNodes : function(){\r
519         return this.selected.elements;\r
520     },\r
521 \r
522     /**\r
523      * Gets the indexes of the selected nodes.\r
524      * @return {Array} An array of numeric indexes\r
525      */\r
526     getSelectedIndexes : function(){\r
527         var indexes = [], s = this.selected.elements;\r
528         for(var i = 0, len = s.length; i < len; i++){\r
529             indexes.push(s[i].viewIndex);\r
530         }\r
531         return indexes;\r
532     },\r
533 \r
534     /**\r
535      * Gets an array of the selected records\r
536      * @return {Array} An array of {@link Ext.data.Record} objects\r
537      */\r
538     getSelectedRecords : function(){\r
539         var r = [], s = this.selected.elements;\r
540         for(var i = 0, len = s.length; i < len; i++){\r
541             r[r.length] = this.store.getAt(s[i].viewIndex);\r
542         }\r
543         return r;\r
544     },\r
545 \r
546     /**\r
547      * Gets an array of the records from an array of nodes\r
548      * @param {Array} nodes The nodes to evaluate\r
549      * @return {Array} records The {@link Ext.data.Record} objects\r
550      */\r
551     getRecords : function(nodes){\r
552         var r = [], s = nodes;\r
553         for(var i = 0, len = s.length; i < len; i++){\r
554             r[r.length] = this.store.getAt(s[i].viewIndex);\r
555         }\r
556         return r;\r
557     },\r
558 \r
559     /**\r
560      * Gets a record from a node\r
561      * @param {HTMLElement} node The node to evaluate\r
562      * @return {Record} record The {@link Ext.data.Record} object\r
563      */\r
564     getRecord : function(node){\r
565         return this.store.getAt(node.viewIndex);\r
566     },\r
567 \r
568     /**\r
569      * Clears all selections.\r
570      * @param {Boolean} suppressEvent (optional) True to skip firing of the selectionchange event\r
571      */\r
572     clearSelections : function(suppressEvent, skipUpdate){\r
573         if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){\r
574             if(!skipUpdate){\r
575                 this.selected.removeClass(this.selectedClass);\r
576             }\r
577             this.selected.clear();\r
578             this.last = false;\r
579             if(!suppressEvent){\r
580                 this.fireEvent("selectionchange", this, this.selected.elements);\r
581             }\r
582         }\r
583     },\r
584 \r
585     /**\r
586      * Returns true if the passed node is selected, else false.\r
587      * @param {HTMLElement/Number} node The node or node index to check\r
588      * @return {Boolean} True if selected, else false\r
589      */\r
590     isSelected : function(node){\r
591         return this.selected.contains(this.getNode(node));\r
592     },\r
593 \r
594     /**\r
595      * Deselects a node.\r
596      * @param {HTMLElement/Number} node The node to deselect\r
597      */\r
598     deselect : function(node){\r
599         if(this.isSelected(node)){\r
600             node = this.getNode(node);\r
601             this.selected.removeElement(node);\r
602             if(this.last == node.viewIndex){\r
603                 this.last = false;\r
604             }\r
605             Ext.fly(node).removeClass(this.selectedClass);\r
606             this.fireEvent("selectionchange", this, this.selected.elements);\r
607         }\r
608     },\r
609 \r
610     /**\r
611      * Selects a set of nodes.\r
612      * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node,\r
613      * id of a template node or an array of any of those to select\r
614      * @param {Boolean} keepExisting (optional) true to keep existing selections\r
615      * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent\r
616      */\r
617     select : function(nodeInfo, keepExisting, suppressEvent){\r
618         if(Ext.isArray(nodeInfo)){\r
619             if(!keepExisting){\r
620                 this.clearSelections(true);\r
621             }\r
622             for(var i = 0, len = nodeInfo.length; i < len; i++){\r
623                 this.select(nodeInfo[i], true, true);\r
624             }\r
625                 if(!suppressEvent){\r
626                     this.fireEvent("selectionchange", this, this.selected.elements);\r
627                 }\r
628         } else{\r
629             var node = this.getNode(nodeInfo);\r
630             if(!keepExisting){\r
631                 this.clearSelections(true);\r
632             }\r
633             if(node && !this.isSelected(node)){\r
634                 if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){\r
635                     Ext.fly(node).addClass(this.selectedClass);\r
636                     this.selected.add(node);\r
637                     this.last = node.viewIndex;\r
638                     if(!suppressEvent){\r
639                         this.fireEvent("selectionchange", this, this.selected.elements);\r
640                     }\r
641                 }\r
642             }\r
643         }\r
644     },\r
645 \r
646     /**\r
647      * Selects a range of nodes. All nodes between start and end are selected.\r
648      * @param {Number} start The index of the first node in the range\r
649      * @param {Number} end The index of the last node in the range\r
650      * @param {Boolean} keepExisting (optional) True to retain existing selections\r
651      */\r
652     selectRange : function(start, end, keepExisting){\r
653         if(!keepExisting){\r
654             this.clearSelections(true);\r
655         }\r
656         this.select(this.getNodes(start, end), true);\r
657     },\r
658 \r
659     /**\r
660      * Gets a template node.\r
661      * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node\r
662      * @return {HTMLElement} The node or null if it wasn't found\r
663      */\r
664     getNode : function(nodeInfo){\r
665         if(typeof nodeInfo == "string"){\r
666             return document.getElementById(nodeInfo);\r
667         }else if(typeof nodeInfo == "number"){\r
668             return this.all.elements[nodeInfo];\r
669         }\r
670         return nodeInfo;\r
671     },\r
672 \r
673     /**\r
674      * Gets a range nodes.\r
675      * @param {Number} start (optional) The index of the first node in the range\r
676      * @param {Number} end (optional) The index of the last node in the range\r
677      * @return {Array} An array of nodes\r
678      */\r
679     getNodes : function(start, end){\r
680         var ns = this.all.elements;\r
681         start = start || 0;\r
682         end = typeof end == "undefined" ? Math.max(ns.length - 1, 0) : end;\r
683         var nodes = [], i;\r
684         if(start <= end){\r
685             for(i = start; i <= end && ns[i]; i++){\r
686                 nodes.push(ns[i]);\r
687             }\r
688         } else{\r
689             for(i = start; i >= end && ns[i]; i--){\r
690                 nodes.push(ns[i]);\r
691             }\r
692         }\r
693         return nodes;\r
694     },\r
695 \r
696     /**\r
697      * Finds the index of the passed node.\r
698      * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node\r
699      * @return {Number} The index of the node or -1\r
700      */\r
701     indexOf : function(node){\r
702         node = this.getNode(node);\r
703         if(typeof node.viewIndex == "number"){\r
704             return node.viewIndex;\r
705         }\r
706         return this.all.indexOf(node);\r
707     },\r
708 \r
709     // private\r
710     onBeforeLoad : function(){\r
711         if(this.loadingText){\r
712             this.clearSelections(false, true);\r
713             this.el.update('<div class="loading-indicator">'+this.loadingText+'</div>');\r
714             this.all.clear();\r
715         }\r
716     },\r
717 \r
718     onDestroy : function(){\r
719         Ext.DataView.superclass.onDestroy.call(this);\r
720         this.setStore(null);\r
721     }\r
722 });\r
723 \r
724 Ext.reg('dataview', Ext.DataView);