3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.view.AbstractView
17 * @extends Ext.Component
18 * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
20 Ext.define('Ext.view.AbstractView', {
21 extend: 'Ext.Component',
22 alternateClassName: 'Ext.view.AbstractView',
25 'Ext.data.StoreManager',
26 'Ext.CompositeElementLite',
28 'Ext.selection.DataViewModel'
32 getRecord: function(node) {
33 return this.getBoundView(node).getRecord(node);
36 getBoundView: function(node) {
37 return Ext.getCmp(node.boundView);
42 * @cfg {String/Array/Ext.XTemplate} tpl
44 * The HTML fragment or an array of fragments that will make up the template used by this DataView. This should
45 * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
48 * @cfg {Ext.data.Store} store
50 * The {@link Ext.data.Store} to bind this DataView to.
54 * @cfg {String} itemSelector
56 * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
57 * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
58 * working with. The itemSelector is used to map DOM nodes to records. As such, there should
59 * only be one root level element that matches the selector for each record.
63 * @cfg {String} itemCls
64 * Specifies the class to be assigned to each element in the view when used in conjunction with the
65 * {@link #itemTpl} configuration.
67 itemCls: Ext.baseCSSPrefix + 'dataview-item',
70 * @cfg {String/Array/Ext.XTemplate} itemTpl
71 * The inner portion of the item template to be rendered. Follows an XTemplate
72 * structure and will be placed inside of a tpl.
76 * @cfg {String} overItemCls
77 * A CSS class to apply to each item in the view on mouseover (defaults to undefined).
78 * Ensure {@link #trackOver} is set to `true` to make use of this.
82 * @cfg {String} loadingText
83 * A string to display during data load operations (defaults to undefined). If specified, this text will be
84 * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
85 * contents will continue to display normally until the new data is loaded and the contents are replaced.
87 loadingText: 'Loading...',
90 * @cfg {Boolean/Object} loadMask
91 * False to disable a load mask from displaying will the view is loading. This can also be a
92 * {@link Ext.LoadMask} configuration object. Defaults to <tt>true</tt>.
97 * @cfg {String} loadingCls
98 * The CSS class to apply to the loading message element (defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading")
102 * @cfg {Boolean} loadingUseMsg
103 * Whether or not to use the loading message.
110 * @cfg {Number} loadingHeight
111 * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
112 * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
113 * loading mask is applied and there are no other contents in the data view. Defaults to undefined.
117 * @cfg {String} selectedItemCls
118 * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
120 selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
123 * @cfg {String} emptyText
124 * The text to display in the view when there is no data to display (defaults to '').
125 * Note that when using local data the emptyText will not be displayed unless you set
126 * the {@link #deferEmptyText} option to false.
131 * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
133 deferEmptyText: true,
136 * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
141 * @cfg {Boolean} blockRefresh Set this to true to ignore datachanged events on the bound store. This is useful if
142 * you wish to provide custom transition animations via a plugin (defaults to false)
147 * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selection within the DataView. Defaults to <tt>false</tt>.
148 * This configuration will lock the selection model that the DataView uses.</p>
155 triggerEvent: 'itemclick',
156 triggerCtEvent: 'containerclick',
158 addCmpEvents: function() {
163 initComponent : function(){
165 isDef = Ext.isDefined,
166 itemTpl = me.itemTpl,
170 if (Ext.isArray(itemTpl)) {
172 itemTpl = itemTpl.join('');
173 } else if (Ext.isObject(itemTpl)) {
175 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
176 itemTpl = itemTpl.html;
179 if (!me.itemSelector) {
180 me.itemSelector = '.' + me.itemCls;
183 itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
184 me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
188 if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
190 sourceClass: 'Ext.view.View',
192 itemSelector: me.itemSelector,
193 msg: "DataView requires both tpl and itemSelector configurations to be defined."
199 if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
200 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
204 // backwards compat alias for overClass/selectedClass
205 // TODO: Consider support for overCls generation Ext.Component config
206 if (isDef(me.overCls) || isDef(me.overClass)) {
207 if (Ext.isDefined(Ext.global.console)) {
208 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
210 me.overItemCls = me.overCls || me.overClass;
215 if (me.overItemCls) {
219 if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
220 if (Ext.isDefined(Ext.global.console)) {
221 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
223 me.selectedItemCls = me.selectedCls || me.selectedClass;
224 delete me.selectedCls;
225 delete me.selectedClass;
231 * @event beforerefresh
232 * Fires before the view is refreshed
233 * @param {Ext.view.View} this The DataView object
238 * Fires when the view is refreshed
239 * @param {Ext.view.View} this The DataView object
244 * Fires when the node associated with an individual record is updated
245 * @param {Ext.data.Model} record The model instance
246 * @param {Number} index The index of the record/node
247 * @param {HTMLElement} node The node that has just been updated
252 * Fires when the nodes associated with an recordset have been added to the underlying store
253 * @param {Array[Ext.data.Model]} records The model instance
254 * @param {Number} index The index at which the set of record/nodes starts
255 * @param {Array[HTMLElement]} node The node that has just been updated
260 * Fires when the node associated with an individual record is removed
261 * @param {Ext.data.Model} record The model instance
262 * @param {Number} index The index of the record/node
270 me.store = Ext.data.StoreManager.lookup(me.store);
272 me.all = new Ext.CompositeElementLite();
275 onRender: function() {
280 msgCls: me.loadingCls,
281 useMsg: me.loadingUseMsg
284 me.callParent(arguments);
287 // either a config object
288 if (Ext.isObject(mask)) {
289 cfg = Ext.apply(cfg, mask);
291 // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
292 // If this DataView is floating, then mask this DataView.
293 // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
294 // LoadMask captures the element upon render.
295 me.loadMask = Ext.create('Ext.LoadMask', me.floating ? me : me.ownerCt || me, cfg);
298 beforeshow: me.onMaskBeforeShow,
304 onMaskBeforeShow: function(){
306 me.getSelectionModel().deselectAll();
308 if (me.loadingHeight) {
309 me.setCalculatedSize(undefined, me.loadingHeight);
313 onMaskHide: function(){
314 if (!this.destroying && this.loadingHeight) {
315 this.setHeight(this.height);
319 afterRender: function() {
320 this.callParent(arguments);
322 // Init the SelectionModel after any on('render') listeners have been added.
323 // Drag plugins create a DragDrop instance in a render listener, and that needs
324 // to see an itemmousedown event first.
325 this.getSelectionModel().bindComponent(this);
329 * Gets the selection model for this view.
330 * @return {Ext.selection.Model} The selection model
332 getSelectionModel: function(){
340 if (me.simpleSelect) {
342 } else if (me.multiSelect) {
346 Ext.applyIf(me.selModel, {
347 allowDeselect: me.allowDeselect,
351 if (!me.selModel.events) {
352 me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
355 if (!me.selModel.hasRelaySetup) {
356 me.relayEvents(me.selModel, [
357 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
359 me.selModel.hasRelaySetup = true;
362 // lock the selection model if user
363 // has disabled selection
364 if (me.disableSelection) {
365 me.selModel.locked = true;
372 * Refreshes the view by reloading the data from the store and re-rendering the template.
374 refresh: function() {
383 me.fireEvent('beforerefresh', me);
384 el = me.getTargetEl();
385 records = me.store.getRange();
388 if (records.length < 1) {
389 if (!me.deferEmptyText || me.hasSkippedEmptyText) {
390 el.update(me.emptyText);
394 me.tpl.overwrite(el, me.collectData(records, 0));
395 me.all.fill(Ext.query(me.getItemSelector(), el.dom));
399 me.selModel.refresh();
400 me.hasSkippedEmptyText = true;
401 me.fireEvent('refresh', me);
405 * Function which can be overridden to provide custom formatting for each Record that is used by this
406 * DataView's {@link #tpl template} to render each node.
407 * @param {Array/Object} data The raw data object that was used to create the Record.
408 * @param {Number} recordIndex the index number of the Record being prepared for rendering.
409 * @param {Record} record The Record being prepared for rendering.
410 * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
411 * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
413 prepareData: function(data, index, record) {
415 Ext.apply(data, record.getAssociatedData());
421 * <p>Function which can be overridden which returns the data object passed to this
422 * DataView's {@link #tpl template} to render the whole DataView.</p>
423 * <p>This is usually an Array of data objects, each element of which is processed by an
424 * {@link Ext.XTemplate XTemplate} which uses <tt>'<tpl for=".">'</tt> to iterate over its supplied
425 * data object as an Array. However, <i>named</i> properties may be placed into the data object to
426 * provide non-repeating data such as headings, totals etc.</p>
427 * @param {Array} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
428 * @param {Number} startIndex the index number of the Record being prepared for rendering.
429 * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
430 * contain <i>named</i> properties.
432 collectData : function(records, startIndex){
435 len = records.length,
440 r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
446 bufferRender : function(records, index){
447 var div = document.createElement('div');
448 this.tpl.overwrite(div, this.collectData(records, index));
449 return Ext.query(this.getItemSelector(), div);
453 onUpdate : function(ds, record){
455 index = me.store.indexOf(record),
459 node = me.bufferRender([record], index)[0];
461 me.all.replaceElement(index, node, true);
462 me.updateIndexes(index, index);
464 // Maintain selection after update
465 // TODO: Move to approriate event handler.
466 me.selModel.refresh();
467 me.fireEvent('itemupdate', record, index, node);
473 onAdd : function(ds, records, index) {
477 if (me.all.getCount() === 0) {
482 nodes = me.bufferRender(records, index);
483 me.doAdd(nodes, records, index);
485 me.selModel.refresh();
486 me.updateIndexes(index);
487 me.fireEvent('itemadd', records, index, nodes);
490 doAdd: function(nodes, records, index) {
493 if (index < all.getCount()) {
494 all.item(index).insertSibling(nodes, 'before', true);
497 all.last().insertSibling(nodes, 'after', true);
500 Ext.Array.insert(all.elements, index, nodes);
504 onRemove : function(ds, record, index) {
507 me.doRemove(record, index);
508 me.updateIndexes(index);
509 if (me.store.getCount() === 0){
512 me.fireEvent('itemremove', record, index);
515 doRemove: function(record, index) {
516 this.all.removeElement(index, true);
520 * Refreshes an individual node's data from the store.
521 * @param {Number} index The item's data index in the store
523 refreshNode : function(index){
524 this.onUpdate(this.store, this.store.getAt(index));
528 updateIndexes : function(startIndex, endIndex) {
529 var ns = this.all.elements,
530 records = this.store.getRange();
531 startIndex = startIndex || 0;
532 endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
533 for(var i = startIndex; i <= endIndex; i++){
535 ns[i].viewRecordId = records[i].internalId;
536 if (!ns[i].boundView) {
537 ns[i].boundView = this.id;
543 * Returns the store associated with this DataView.
544 * @return {Ext.data.Store} The store
546 getStore : function(){
551 * Changes the data store bound to this view and refreshes it.
552 * @param {Store} store The store to bind to this view
554 bindStore : function(store, initial) {
557 if (!initial && me.store) {
558 if (store !== me.store && me.store.autoDestroy) {
564 datachanged: me.onDataChanged,
573 me.loadMask.bindStore(null);
579 store = Ext.data.StoreManager.lookup(store);
582 datachanged: me.onDataChanged,
589 me.loadMask.bindStore(store);
594 // Bind the store to our selection model
595 me.getSelectionModel().bind(store);
597 if (store && (!initial || store.getCount())) {
604 * Calls this.refresh if this.blockRefresh is not true
606 onDataChanged: function() {
607 if (this.blockRefresh !== true) {
608 this.refresh.apply(this, arguments);
613 * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
614 * @param {HTMLElement} node
615 * @return {HTMLElement} The template node
617 findItemByChild: function(node){
618 return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
622 * Returns the template node by the Ext.EventObject or null if it is not found.
623 * @param {Ext.EventObject} e
625 findTargetByEvent: function(e) {
626 return e.getTarget(this.getItemSelector(), this.getTargetEl());
631 * Gets the currently selected nodes.
632 * @return {Array} An array of HTMLElements
634 getSelectedNodes: function(){
636 records = this.selModel.getSelection(),
640 for (; i < ln; i++) {
641 nodes.push(this.getNode(records[i]));
648 * Gets an array of the records from an array of nodes
649 * @param {Array} nodes The nodes to evaluate
650 * @return {Array} records The {@link Ext.data.Model} objects
652 getRecords: function(nodes) {
656 data = this.store.data;
658 for (; i < len; i++) {
659 records[records.length] = data.getByKey(nodes[i].viewRecordId);
666 * Gets a record from a node
667 * @param {Element/HTMLElement} node The node to evaluate
669 * @return {Record} record The {@link Ext.data.Model} object
671 getRecord: function(node){
672 return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
677 * Returns true if the passed node is selected, else false.
678 * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
679 * @return {Boolean} True if selected, else false
681 isSelected : function(node) {
682 // TODO: El/Idx/Record
683 var r = this.getRecord(node);
684 return this.selModel.isSelected(r);
688 * Selects a record instance by record instance or index.
689 * @param {Ext.data.Model/Index} records An array of records or an index
690 * @param {Boolean} keepExisting
691 * @param {Boolean} suppressEvent Set to false to not fire a select event
693 select: function(records, keepExisting, suppressEvent) {
694 this.selModel.select(records, keepExisting, suppressEvent);
698 * Deselects a record instance by record instance or index.
699 * @param {Ext.data.Model/Index} records An array of records or an index
700 * @param {Boolean} suppressEvent Set to false to not fire a deselect event
702 deselect: function(records, suppressEvent) {
703 this.selModel.deselect(records, suppressEvent);
707 * Gets a template node.
708 * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
709 * the id of a template node or the record associated with the node.
710 * @return {HTMLElement} The node or null if it wasn't found
712 getNode : function(nodeInfo) {
713 if (Ext.isString(nodeInfo)) {
714 return document.getElementById(nodeInfo);
715 } else if (Ext.isNumber(nodeInfo)) {
716 return this.all.elements[nodeInfo];
717 } else if (nodeInfo instanceof Ext.data.Model) {
718 return this.getNodeByRecord(nodeInfo);
726 getNodeByRecord: function(record) {
727 var ns = this.all.elements,
731 for (; i < ln; i++) {
732 if (ns[i].viewRecordId === record.internalId) {
741 * Gets a range nodes.
742 * @param {Number} start (optional) The index of the first node in the range
743 * @param {Number} end (optional) The index of the last node in the range
744 * @return {Array} An array of nodes
746 getNodes: function(start, end) {
747 var ns = this.all.elements,
752 end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
754 for (i = start; i <= end && ns[i]; i++) {
758 for (i = start; i >= end && ns[i]; i--) {
766 * Finds the index of the passed node.
767 * @param {HTMLElement/String/Number/Record} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
768 * or a record associated with a node.
769 * @return {Number} The index of the node or -1
771 indexOf: function(node) {
772 node = this.getNode(node);
773 if (Ext.isNumber(node.viewIndex)) {
774 return node.viewIndex;
776 return this.all.indexOf(node);
779 onDestroy : function() {
785 me.selModel.destroy();
788 // invoked by the selection model to maintain visual UI cues
789 onItemSelect: function(record) {
790 var node = this.getNode(record);
791 Ext.fly(node).addCls(this.selectedItemCls);
794 // invoked by the selection model to maintain visual UI cues
795 onItemDeselect: function(record) {
796 var node = this.getNode(record);
797 Ext.fly(node).removeCls(this.selectedItemCls);
800 getItemSelector: function() {
801 return this.itemSelector;
804 // all of this information is available directly
805 // from the SelectionModel itself, the only added methods
806 // to DataView regarding selection will perform some transformation/lookup
807 // between HTMLElement/Nodes to records and vice versa.
808 Ext.deprecate('extjs', '4.0', function() {
809 Ext.view.AbstractView.override({
811 * @cfg {Boolean} multiSelect
812 * True to allow selection of more than one item at a time, false to allow selection of only a single item
813 * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
816 * @cfg {Boolean} singleSelect
817 * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
818 * Note that if {@link #multiSelect} = true, this value will be ignored.
821 * @cfg {Boolean} simpleSelect
822 * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
823 * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
827 * Gets the number of selected nodes.
828 * @return {Number} The node count
830 getSelectionCount : function(){
831 if (Ext.global.console) {
832 Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
834 return this.selModel.getSelection().length;
838 * Gets an array of the selected records
839 * @return {Array} An array of {@link Ext.data.Model} objects
841 getSelectedRecords : function(){
842 if (Ext.global.console) {
843 Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
845 return this.selModel.getSelection();
848 select: function(records, keepExisting, supressEvents) {
849 if (Ext.global.console) {
850 Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
852 var sm = this.getSelectionModel();
853 return sm.select.apply(sm, arguments);
856 clearSelections: function() {
857 if (Ext.global.console) {
858 Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
860 var sm = this.getSelectionModel();
861 return sm.deselectAll();