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