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