Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / view / View.js
1 /**
2  * @class Ext.view.View
3  * @extends Ext.view.AbstractView
4  *
5  * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
6  * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
7  * so that as the data in the store changes the view is automatically updated to reflect the changes.  The view also
8  * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
9  * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
10  * config must be provided for the DataView to determine what nodes it will be working with.</b>
11  *
12  * The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
13  *
14  * {@img Ext.DataView/Ext.DataView.png Ext.DataView component}
15  *
16  *     Ext.regModel('Image', {
17  *         Fields: [
18  *             {name:'src', type:'string'},
19  *             {name:'caption', type:'string'}
20  *         ]
21  *     });
22  *     
23  *     Ext.create('Ext.data.Store', {
24  *         id:'imagesStore',
25  *         model: 'Image',
26  *         data: [
27  *             {src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts'},
28  *             {src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data'},
29  *             {src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme'},
30  *             {src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned'}            
31  *         ]
32  *     });
33  *     
34  *     var imageTpl = new Ext.XTemplate(
35  *         '&lt;tpl for="."&gt;',
36  *             '&lt;div style="thumb-wrap"&gt;',
37  *               '&lt;img src="{src}" /&gt;',
38  *               '&lt;br/&gt;&lt;span&gt;{caption}&lt;/span&gt;',
39  *             '&lt;/div&gt;',
40  *         '&lt;/tpl&gt;'
41  *     );
42  *     
43  *     Ext.create('Ext.DataView', {
44  *         store: Ext.data.StoreManager.lookup('imagesStore'),
45  *         tpl: imageTpl,
46  *         itemSelector: 'div.thumb-wrap',
47  *         emptyText: 'No images available',
48  *         renderTo: Ext.getBody()
49  *     });
50  *
51  * @xtype dataview
52  */
53 Ext.define('Ext.view.View', {
54     extend: 'Ext.view.AbstractView',
55     alternateClassName: 'Ext.view.View',
56     alias: 'widget.dataview',
57
58     inheritableStatics: {
59         EventMap: {
60             mousedown: 'MouseDown',
61             mouseup: 'MouseUp',
62             click: 'Click',
63             dblclick: 'DblClick',
64             contextmenu: 'ContextMenu',
65             mouseover: 'MouseOver',
66             mouseout: 'MouseOut',
67             mouseenter: 'MouseEnter',
68             mouseleave: 'MouseLeave',
69             keydown: 'KeyDown'
70         }
71     },
72
73     addCmpEvents: function() {
74         this.addEvents(
75             /**
76              * @event beforeitemmousedown
77              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
78              * @param {Ext.view.View} this
79              * @param {Ext.data.Model} record The record that belongs to the item
80              * @param {HTMLElement} item The item's element
81              * @param {Number} index The item's index
82              * @param {Ext.EventObject} e The raw event object
83              */
84             'beforeitemmousedown',
85             /**
86              * @event beforeitemmouseup
87              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
88              * @param {Ext.view.View} this
89              * @param {Ext.data.Model} record The record that belongs to the item
90              * @param {HTMLElement} item The item's element
91              * @param {Number} index The item's index
92              * @param {Ext.EventObject} e The raw event object
93              */
94             'beforeitemmouseup',
95             /**
96              * @event beforeitemmouseenter
97              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
98              * @param {Ext.view.View} this
99              * @param {Ext.data.Model} record The record that belongs to the item
100              * @param {HTMLElement} item The item's element
101              * @param {Number} index The item's index
102              * @param {Ext.EventObject} e The raw event object
103              */
104             'beforeitemmouseenter',
105             /**
106              * @event beforeitemmouseleave
107              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
108              * @param {Ext.view.View} this
109              * @param {Ext.data.Model} record The record that belongs to the item
110              * @param {HTMLElement} item The item's element
111              * @param {Number} index The item's index
112              * @param {Ext.EventObject} e The raw event object
113              */
114             'beforeitemmouseleave',
115             /**
116              * @event beforeitemclick
117              * Fires before the click event on an item is processed. Returns false to cancel the default action.
118              * @param {Ext.view.View} this
119              * @param {Ext.data.Model} record The record that belongs to the item
120              * @param {HTMLElement} item The item's element
121              * @param {Number} index The item's index
122              * @param {Ext.EventObject} e The raw event object
123              */
124             'beforeitemclick',
125             /**
126              * @event beforeitemdblclick
127              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
128              * @param {Ext.view.View} this
129              * @param {Ext.data.Model} record The record that belongs to the item
130              * @param {HTMLElement} item The item's element
131              * @param {Number} index The item's index
132              * @param {Ext.EventObject} e The raw event object
133              */
134             'beforeitemdblclick',
135             /**
136              * @event beforeitemcontextmenu
137              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
138              * @param {Ext.view.View} this
139              * @param {Ext.data.Model} record The record that belongs to the item
140              * @param {HTMLElement} item The item's element
141              * @param {Number} index The item's index
142              * @param {Ext.EventObject} e The raw event object
143              */
144             'beforeitemcontextmenu',
145             /**
146              * @event beforeitemkeydown
147              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
148              * @param {Ext.view.View} this
149              * @param {Ext.data.Model} record The record that belongs to the item
150              * @param {HTMLElement} item The item's element
151              * @param {Number} index The item's index
152              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
153              */
154             'beforeitemkeydown',
155             /**
156              * @event itemmousedown
157              * Fires when there is a mouse down on an item
158              * @param {Ext.view.View} this
159              * @param {Ext.data.Model} record The record that belongs to the item
160              * @param {HTMLElement} item The item's element
161              * @param {Number} index The item's index
162              * @param {Ext.EventObject} e The raw event object
163              */
164             'itemmousedown',
165             /**
166              * @event itemmouseup
167              * Fires when there is a mouse up on an item
168              * @param {Ext.view.View} this
169              * @param {Ext.data.Model} record The record that belongs to the item
170              * @param {HTMLElement} item The item's element
171              * @param {Number} index The item's index
172              * @param {Ext.EventObject} e The raw event object
173              */
174             'itemmouseup',
175             /**
176              * @event itemmouseenter
177              * Fires when the mouse enters an item.
178              * @param {Ext.view.View} this
179              * @param {Ext.data.Model} record The record that belongs to the item
180              * @param {HTMLElement} item The item's element
181              * @param {Number} index The item's index
182              * @param {Ext.EventObject} e The raw event object
183              */
184             'itemmouseenter',
185             /**
186              * @event itemmouseleave
187              * Fires when the mouse leaves an item.
188              * @param {Ext.view.View} this
189              * @param {Ext.data.Model} record The record that belongs to the item
190              * @param {HTMLElement} item The item's element
191              * @param {Number} index The item's index
192              * @param {Ext.EventObject} e The raw event object
193              */
194             'itemmouseleave',
195             /**
196              * @event itemclick
197              * Fires when an item is clicked.
198              * @param {Ext.view.View} this
199              * @param {Ext.data.Model} record The record that belongs to the item
200              * @param {HTMLElement} item The item's element
201              * @param {Number} index The item's index
202              * @param {Ext.EventObject} e The raw event object
203              */
204             'itemclick',
205             /**
206              * @event itemdblclick
207              * Fires when an item is double clicked.
208              * @param {Ext.view.View} this
209              * @param {Ext.data.Model} record The record that belongs to the item
210              * @param {HTMLElement} item The item's element
211              * @param {Number} index The item's index
212              * @param {Ext.EventObject} e The raw event object
213              */
214             'itemdblclick',
215             /**
216              * @event itemcontextmenu
217              * Fires when an item is right clicked.
218              * @param {Ext.view.View} this
219              * @param {Ext.data.Model} record The record that belongs to the item
220              * @param {HTMLElement} item The item's element
221              * @param {Number} index The item's index
222              * @param {Ext.EventObject} e The raw event object
223              */
224             'itemcontextmenu',
225             /**
226              * @event itemkeydown
227              * Fires when a key is pressed while an item is currently selected.
228              * @param {Ext.view.View} this
229              * @param {Ext.data.Model} record The record that belongs to the item
230              * @param {HTMLElement} item The item's element
231              * @param {Number} index The item's index
232              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
233              */
234             'itemkeydown',
235             /**
236              * @event beforecontainermousedown
237              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
238              * @param {Ext.view.View} this
239              * @param {Ext.EventObject} e The raw event object
240              */
241             'beforecontainermousedown',
242             /**
243              * @event beforecontainermouseup
244              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
245              * @param {Ext.view.View} this
246              * @param {Ext.EventObject} e The raw event object
247              */
248             'beforecontainermouseup',
249             /**
250              * @event beforecontainermouseover
251              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
252              * @param {Ext.view.View} this
253              * @param {Ext.EventObject} e The raw event object
254              */
255             'beforecontainermouseover',
256             /**
257              * @event beforecontainermouseout
258              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
259              * @param {Ext.view.View} this
260              * @param {Ext.EventObject} e The raw event object
261              */
262             'beforecontainermouseout',
263             /**
264              * @event beforecontainerclick
265              * Fires before the click event on the container is processed. Returns false to cancel the default action.
266              * @param {Ext.view.View} this
267              * @param {Ext.EventObject} e The raw event object
268              */
269             'beforecontainerclick',
270             /**
271              * @event beforecontainerdblclick
272              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
273              * @param {Ext.view.View} this
274              * @param {Ext.EventObject} e The raw event object
275              */
276             'beforecontainerdblclick',
277             /**
278              * @event beforecontainercontextmenu
279              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
280              * @param {Ext.view.View} this
281              * @param {Ext.EventObject} e The raw event object
282              */
283             'beforecontainercontextmenu',
284             /**
285              * @event beforecontainerkeydown
286              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
287              * @param {Ext.view.View} this
288              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
289              */
290             'beforecontainerkeydown',
291             /**
292              * @event containermouseup
293              * Fires when there is a mouse up on the container
294              * @param {Ext.view.View} this
295              * @param {Ext.EventObject} e The raw event object
296              */
297             'containermouseup',
298             /**
299              * @event containermouseover
300              * Fires when you move the mouse over the container.
301              * @param {Ext.view.View} this
302              * @param {Ext.EventObject} e The raw event object
303              */
304             'containermouseover',
305             /**
306              * @event containermouseout
307              * Fires when you move the mouse out of the container.
308              * @param {Ext.view.View} this
309              * @param {Ext.EventObject} e The raw event object
310              */
311             'containermouseout',
312             /**
313              * @event containerclick
314              * Fires when the container is clicked.
315              * @param {Ext.view.View} this
316              * @param {Ext.EventObject} e The raw event object
317              */
318             'containerclick',
319             /**
320              * @event containerdblclick
321              * Fires when the container is double clicked.
322              * @param {Ext.view.View} this
323              * @param {Ext.EventObject} e The raw event object
324              */
325             'containerdblclick',
326             /**
327              * @event containercontextmenu
328              * Fires when the container is right clicked.
329              * @param {Ext.view.View} this
330              * @param {Ext.EventObject} e The raw event object
331              */
332             'containercontextmenu',
333             /**
334              * @event containerkeydown
335              * Fires when a key is pressed while the container is focused, and no item is currently selected.
336              * @param {Ext.view.View} this
337              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
338              */
339             'containerkeydown',
340
341             /**
342              * @event selectionchange
343              * Fires when the selected nodes change. Relayed event from the underlying selection model.
344              * @param {Ext.view.View} this
345              * @param {Array} selections Array of the selected nodes
346              */
347             'selectionchange',
348             /**
349              * @event beforeselect
350              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
351              * @param {Ext.view.View} this
352              * @param {HTMLElement} node The node to be selected
353              * @param {Array} selections Array of currently selected nodes
354              */
355             'beforeselect'
356         );
357     },
358     // private
359     afterRender: function(){
360         var me = this,
361             listeners;
362
363         me.callParent();
364
365         listeners = {
366             scope: me,
367             click: me.handleEvent,
368             mousedown: me.handleEvent,
369             mouseup: me.handleEvent,
370             dblclick: me.handleEvent,
371             contextmenu: me.handleEvent,
372             mouseover: me.handleEvent,
373             mouseout: me.handleEvent,
374             keydown: me.handleEvent
375         };
376
377         me.mon(me.getTargetEl(), listeners);
378
379         if (me.store) {
380             me.bindStore(me.store, true);
381         }
382     },
383
384     handleEvent: function(e) {
385         if (this.processUIEvent(e) !== false) {
386             this.processSpecialEvent(e);
387         }
388     },
389
390     // Private template method
391     processItemEvent: Ext.emptyFn,
392     processContainerEvent: Ext.emptyFn,
393     processSpecialEvent: Ext.emptyFn,
394
395     /*
396      * Returns true if this mouseover/out event is still over the overItem.
397      */
398     stillOverItem: function (event, overItem) {
399         var nowOver;
400
401         // There is this weird bug when you hover over the border of a cell it is saying
402         // the target is the table.
403         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
404         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
405         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
406         // this hard exception.
407         if (overItem && typeof(overItem.offsetParent) === "object") {
408             // mouseout : relatedTarget == nowOver, target == wasOver
409             // mouseover: relatedTarget == wasOver, target == nowOver
410             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
411             return Ext.fly(overItem).contains(nowOver);
412         }
413
414         return false;
415     },
416
417     processUIEvent: function(e) {
418         var me = this,
419             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
420             map = this.statics().EventMap,
421             index, record,
422             type = e.type,
423             overItem = me.mouseOverItem,
424             newType;
425
426         if (!item) {
427             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
428                 item = overItem;
429             }
430
431             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
432             if (type == 'keydown') {
433                 record = me.getSelectionModel().getLastSelected();
434                 if (record) {
435                     item = me.getNode(record);
436                 }
437             }
438         }
439
440         if (item) {
441             index = me.indexOf(item);
442             if (!record) {
443                 record = me.getRecord(item);
444             }
445
446             if (me.processItemEvent(record, item, index, e) === false) {
447                 return false;
448             }
449
450             newType = me.isNewItemEvent(item, e);
451             if (newType === false) {
452                 return false;
453             }
454
455             if (
456                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
457                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
458                 (me['onItem' + map[newType]](record, item, index, e) === false)
459             ) {
460                 return false;
461             }
462
463             me.fireEvent('item' + newType, me, record, item, index, e);
464         }
465         else {
466             if (
467                 (me.processContainerEvent(e) === false) ||
468                 (me['onBeforeContainer' + map[type]](e) === false) ||
469                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
470                 (me['onContainer' + map[type]](e) === false)
471             ) {
472                 return false;
473             }
474
475             me.fireEvent('container' + type, me, e);
476         }
477
478         return true;
479     },
480
481     isNewItemEvent: function (item, e) {
482         var me = this,
483             overItem = me.mouseOverItem,
484             type = e.type;
485
486         switch (type) {
487             case 'mouseover':
488                 if (item === overItem) {
489                     return false;
490                 }
491                 me.mouseOverItem = item;
492                 return 'mouseenter';
493
494             case 'mouseout':
495                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
496                 if (me.stillOverItem(e, overItem)) {
497                     return false;
498                 }
499                 me.mouseOverItem = null;
500                 return 'mouseleave';
501         }
502         return type;
503     },
504
505     // private
506     onItemMouseEnter: function(record, item, index, e) {
507         if (this.trackOver) {
508             this.highlightItem(item);
509         }
510     },
511
512     // private
513     onItemMouseLeave : function(record, item, index, e) {
514         if (this.trackOver) {
515             this.clearHighlight();
516         }
517     },
518
519     // @private, template methods
520     onItemMouseDown: Ext.emptyFn,
521     onItemMouseUp: Ext.emptyFn,
522     onItemClick: Ext.emptyFn,
523     onItemDblClick: Ext.emptyFn,
524     onItemContextMenu: Ext.emptyFn,
525     onItemKeyDown: Ext.emptyFn,
526     onBeforeItemMouseDown: Ext.emptyFn,
527     onBeforeItemMouseUp: Ext.emptyFn,
528     onBeforeItemMouseEnter: Ext.emptyFn,
529     onBeforeItemMouseLeave: Ext.emptyFn,
530     onBeforeItemClick: Ext.emptyFn,
531     onBeforeItemDblClick: Ext.emptyFn,
532     onBeforeItemContextMenu: Ext.emptyFn,
533     onBeforeItemKeyDown: Ext.emptyFn,
534
535     // @private, template methods
536     onContainerMouseDown: Ext.emptyFn,
537     onContainerMouseUp: Ext.emptyFn,
538     onContainerMouseOver: Ext.emptyFn,
539     onContainerMouseOut: Ext.emptyFn,
540     onContainerClick: Ext.emptyFn,
541     onContainerDblClick: Ext.emptyFn,
542     onContainerContextMenu: Ext.emptyFn,
543     onContainerKeyDown: Ext.emptyFn,
544     onBeforeContainerMouseDown: Ext.emptyFn,
545     onBeforeContainerMouseUp: Ext.emptyFn,
546     onBeforeContainerMouseOver: Ext.emptyFn,
547     onBeforeContainerMouseOut: Ext.emptyFn,
548     onBeforeContainerClick: Ext.emptyFn,
549     onBeforeContainerDblClick: Ext.emptyFn,
550     onBeforeContainerContextMenu: Ext.emptyFn,
551     onBeforeContainerKeyDown: Ext.emptyFn,
552
553     /**
554      * Highlight a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
555      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
556      * handle stepping through the list via keyboard navigation.
557      * @param {HTMLElement} item The item to highlight
558      */
559     highlightItem: function(item) {
560         var me = this;
561         me.clearHighlight();
562         me.highlightedItem = item;
563         Ext.fly(item).addCls(me.overItemCls);
564     },
565
566     /**
567      * Un-highlight the currently highlighted item, if any.
568      */
569     clearHighlight: function() {
570         var me = this,
571             highlighted = me.highlightedItem;
572
573         if (highlighted) {
574             Ext.fly(highlighted).removeCls(me.overItemCls);
575             delete me.highlightedItem;
576         }
577     },
578
579     refresh: function() {
580         this.clearHighlight();
581         this.callParent(arguments);
582     }
583 });