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