3
* Copyright(c) 2006-2008, Ext JS, LLC.
6
* http://extjs.com/license
11
* @extends Ext.BoxComponent
12
* A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
13
* as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
14
* so that as the data in the store changes the view is automatically updated to reflect the changes. The view also
15
* provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
16
* mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
17
* config must be provided for the DataView to determine what nodes it will be working with.</b>
19
* <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.Panel}.</p>
21
var store = new Ext.data.JsonStore({
22
url: 'get-images.php',
26
{name:'size', type: 'float'},
27
{name:'lastmod', type:'date', dateFormat:'timestamp'}
32
var tpl = new Ext.XTemplate(
33
'<tpl for=".">',
34
'<div class="thumb-wrap" id="{name}">',
35
'<div class="thumb"><img src="{url}" title="{name}"></div>',
36
'<span class="x-editable">{shortName}</span></div>',
38
'<div class="x-clear"></div>'
41
var panel = new Ext.Panel({
48
title:'Simple DataView',
50
items: new Ext.DataView({
55
overClass:'x-view-over',
56
itemSelector:'div.thumb-wrap',
57
emptyText: 'No images to display'
60
panel.render(document.body);
63
* Create a new DataView
64
* @param {Object} config The config object
66
Ext.DataView = Ext.extend(Ext.BoxComponent, {
68
* @cfg {String/Array} tpl
69
* The HTML fragment or an array of fragments that will make up the template used by this DataView. This should
70
* be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
73
* @cfg {Ext.data.Store} store
74
* The {@link Ext.data.Store} to bind this DataView to.
77
* @cfg {String} itemSelector
78
* <b>This is a required setting</b>. A simple CSS selector (e.g. div.some-class or span:first-child) that will be
79
* used to determine what nodes this DataView will be working with.
82
* @cfg {Boolean} multiSelect
83
* True to allow selection of more than one item at a time, false to allow selection of only a single item
84
* at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
87
* @cfg {Boolean} singleSelect
88
* True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
89
* Note that if {@link #multiSelect} = true, this value will be ignored.
92
* @cfg {Boolean} simpleSelect
93
* True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
94
* false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
97
* @cfg {String} overClass
98
* A CSS class to apply to each item in the view on mouseover (defaults to undefined).
101
* @cfg {String} loadingText
102
* A string to display during data load operations (defaults to undefined). If specified, this text will be
103
* displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
104
* contents will continue to display normally until the new data is loaded and the contents are replaced.
107
* @cfg {String} selectedClass
108
* A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
110
selectedClass : "x-view-selected",
112
* @cfg {String} emptyText
113
* The text to display in the view when there is no data to display (defaults to '').
118
* @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
120
deferEmptyText: true,
122
* @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
131
initComponent : function(){
132
Ext.DataView.superclass.initComponent.call(this);
133
if(typeof this.tpl == "string"){
134
this.tpl = new Ext.XTemplate(this.tpl);
140
* Fires before a click is processed. Returns false to cancel the default action.
141
* @param {Ext.DataView} this
142
* @param {Number} index The index of the target node
143
* @param {HTMLElement} node The target node
144
* @param {Ext.EventObject} e The raw event object
149
* Fires when a template node is clicked.
150
* @param {Ext.DataView} this
151
* @param {Number} index The index of the target node
152
* @param {HTMLElement} node The target node
153
* @param {Ext.EventObject} e The raw event object
158
* Fires when the mouse enters a template node. trackOver:true or an overCls must be set to enable this event.
159
* @param {Ext.DataView} this
160
* @param {Number} index The index of the target node
161
* @param {HTMLElement} node The target node
162
* @param {Ext.EventObject} e The raw event object
167
* Fires when the mouse leaves a template node. trackOver:true or an overCls must be set to enable this event.
168
* @param {Ext.DataView} this
169
* @param {Number} index The index of the target node
170
* @param {HTMLElement} node The target node
171
* @param {Ext.EventObject} e The raw event object
175
* @event containerclick
176
* Fires when a click occurs and it is not on a template node.
177
* @param {Ext.DataView} this
178
* @param {Ext.EventObject} e The raw event object
183
* Fires when a template node is double clicked.
184
* @param {Ext.DataView} this
185
* @param {Number} index The index of the target node
186
* @param {HTMLElement} node The target node
187
* @param {Ext.EventObject} e The raw event object
192
* Fires when a template node is right clicked.
193
* @param {Ext.DataView} this
194
* @param {Number} index The index of the target node
195
* @param {HTMLElement} node The target node
196
* @param {Ext.EventObject} e The raw event object
200
* @event selectionchange
201
* Fires when the selected nodes change.
202
* @param {Ext.DataView} this
203
* @param {Array} selections Array of the selected nodes
208
* @event beforeselect
209
* Fires before a selection is made. If any handlers return false, the selection is cancelled.
210
* @param {Ext.DataView} this
211
* @param {HTMLElement} node The node to be selected
212
* @param {Array} selections Array of currently selected nodes
217
this.all = new Ext.CompositeElementLite();
218
this.selected = new Ext.CompositeElementLite();
222
onRender : function(){
224
this.el = document.createElement('div');
225
this.el.id = this.id;
227
Ext.DataView.superclass.onRender.apply(this, arguments);
231
afterRender : function(){
232
Ext.DataView.superclass.afterRender.call(this);
235
"click": this.onClick,
236
"dblclick": this.onDblClick,
237
"contextmenu": this.onContextMenu,
241
if(this.overClass || this.trackOver){
243
"mouseover": this.onMouseOver,
244
"mouseout": this.onMouseOut,
250
this.setStore(this.store, true);
255
* Refreshes the view by reloading the data from the store and re-rendering the template.
257
refresh : function(){
258
this.clearSelections(false, true);
260
var records = this.store.getRange();
261
if(records.length < 1){
262
if(!this.deferEmptyText || this.hasSkippedEmptyText){
263
this.el.update(this.emptyText);
265
this.hasSkippedEmptyText = true;
269
this.tpl.overwrite(this.el, this.collectData(records, 0));
270
this.all.fill(Ext.query(this.itemSelector, this.el.dom));
271
this.updateIndexes(0);
275
* Function which can be overridden to provide custom formatting for each Record that is used by this
276
* DataView's {@link #tpl template} to render each node.
277
* @param {Array/Object} data The raw data object that was used to create the Record.
278
* @param {Number} recordIndex the index number of the Record being prepared for rendering.
279
* @param {Record} record The Record being prepared for rendering.
280
* @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
281
* (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
283
prepareData : function(data){
288
* <p>Function which can be overridden which returns the data object passed to this
289
* DataView's {@link #tpl template} to render the whole DataView.</p>
290
* <p>This is usually an Array of data objects, each element of which is processed by an
291
* {@link Ext.XTemplate XTemplate} which uses <tt>'<tpl for=".">'</tt> to iterate over its supplied
292
* data object as an Array. However, <i>named</i> properties may be placed into the data object to
293
* provide non-repeating data such as headings, totals etc.</p>
294
* @param records {Array} An Array of {@link Ext.data.Record}s to be rendered into the DataView.
295
* @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
296
* contain <i>named</i> properties.
298
collectData : function(records, startIndex){
300
for(var i = 0, len = records.length; i < len; i++){
301
r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);
307
bufferRender : function(records){
308
var div = document.createElement('div');
309
this.tpl.overwrite(div, this.collectData(records));
310
return Ext.query(this.itemSelector, div);
314
onUpdate : function(ds, record){
315
var index = this.store.indexOf(record);
316
var sel = this.isSelected(index);
317
var original = this.all.elements[index];
318
var node = this.bufferRender([record], index)[0];
320
this.all.replaceElement(index, node, true);
322
this.selected.replaceElement(original, node);
323
this.all.item(index).addClass(this.selectedClass);
325
this.updateIndexes(index, index);
329
onAdd : function(ds, records, index){
330
if(this.all.getCount() == 0){
334
var nodes = this.bufferRender(records, index), n, a = this.all.elements;
335
if(index < this.all.getCount()){
336
n = this.all.item(index).insertSibling(nodes, 'before', true);
337
a.splice.apply(a, [index, 0].concat(nodes));
339
n = this.all.last().insertSibling(nodes, 'after', true);
340
a.push.apply(a, nodes);
342
this.updateIndexes(index);
346
onRemove : function(ds, record, index){
347
this.deselect(index);
348
this.all.removeElement(index, true);
349
this.updateIndexes(index);
353
* Refreshes an individual node's data from the store.
354
* @param {Number} index The item's data index in the store
356
refreshNode : function(index){
357
this.onUpdate(this.store, this.store.getAt(index));
361
updateIndexes : function(startIndex, endIndex){
362
var ns = this.all.elements;
363
startIndex = startIndex || 0;
364
endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
365
for(var i = startIndex; i <= endIndex; i++){
371
* Changes the data store bound to this view and refreshes it.
372
* @param {Store} store The store to bind to this view
374
setStore : function(store, initial){
375
if(!initial && this.store){
376
this.store.un("beforeload", this.onBeforeLoad, this);
377
this.store.un("datachanged", this.refresh, this);
378
this.store.un("add", this.onAdd, this);
379
this.store.un("remove", this.onRemove, this);
380
this.store.un("update", this.onUpdate, this);
381
this.store.un("clear", this.refresh, this);
384
store = Ext.StoreMgr.lookup(store);
385
store.on("beforeload", this.onBeforeLoad, this);
386
store.on("datachanged", this.refresh, this);
387
store.on("add", this.onAdd, this);
388
store.on("remove", this.onRemove, this);
389
store.on("update", this.onUpdate, this);
390
store.on("clear", this.refresh, this);
399
* Returns the template node the passed child belongs to, or null if it doesn't belong to one.
400
* @param {HTMLElement} node
401
* @return {HTMLElement} The template node
403
findItemFromChild : function(node){
404
return Ext.fly(node).findParent(this.itemSelector, this.el);
408
onClick : function(e){
409
var item = e.getTarget(this.itemSelector, this.el);
411
var index = this.indexOf(item);
412
if(this.onItemClick(item, index, e) !== false){
413
this.fireEvent("click", this, index, item, e);
416
if(this.fireEvent("containerclick", this, e) !== false){
417
this.clearSelections();
423
onContextMenu : function(e){
424
var item = e.getTarget(this.itemSelector, this.el);
426
this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
431
onDblClick : function(e){
432
var item = e.getTarget(this.itemSelector, this.el);
434
this.fireEvent("dblclick", this, this.indexOf(item), item, e);
439
onMouseOver : function(e){
440
var item = e.getTarget(this.itemSelector, this.el);
441
if(item && item !== this.lastItem){
442
this.lastItem = item;
443
Ext.fly(item).addClass(this.overClass);
444
this.fireEvent("mouseenter", this, this.indexOf(item), item, e);
449
onMouseOut : function(e){
451
if(!e.within(this.lastItem, true)){
452
Ext.fly(this.lastItem).removeClass(this.overClass);
453
this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);
454
delete this.lastItem;
460
onItemClick : function(item, index, e){
461
if(this.fireEvent("beforeclick", this, index, item, e) === false){
464
if(this.multiSelect){
465
this.doMultiSelection(item, index, e);
467
}else if(this.singleSelect){
468
this.doSingleSelection(item, index, e);
475
doSingleSelection : function(item, index, e){
476
if(e.ctrlKey && this.isSelected(index)){
477
this.deselect(index);
479
this.select(index, false);
484
doMultiSelection : function(item, index, e){
485
if(e.shiftKey && this.last !== false){
486
var last = this.last;
487
this.selectRange(last, index, e.ctrlKey);
488
this.last = last; // reset the last
490
if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){
491
this.deselect(index);
493
this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);
499
* Gets the number of selected nodes.
500
* @return {Number} The node count
502
getSelectionCount : function(){
503
return this.selected.getCount()
507
* Gets the currently selected nodes.
508
* @return {Array} An array of HTMLElements
510
getSelectedNodes : function(){
511
return this.selected.elements;
515
* Gets the indexes of the selected nodes.
516
* @return {Array} An array of numeric indexes
518
getSelectedIndexes : function(){
519
var indexes = [], s = this.selected.elements;
520
for(var i = 0, len = s.length; i < len; i++){
521
indexes.push(s[i].viewIndex);
527
* Gets an array of the selected records
528
* @return {Array} An array of {@link Ext.data.Record} objects
530
getSelectedRecords : function(){
531
var r = [], s = this.selected.elements;
532
for(var i = 0, len = s.length; i < len; i++){
533
r[r.length] = this.store.getAt(s[i].viewIndex);
539
* Gets an array of the records from an array of nodes
540
* @param {Array} nodes The nodes to evaluate
541
* @return {Array} records The {@link Ext.data.Record} objects
543
getRecords : function(nodes){
544
var r = [], s = nodes;
545
for(var i = 0, len = s.length; i < len; i++){
546
r[r.length] = this.store.getAt(s[i].viewIndex);
552
* Gets a record from a node
553
* @param {HTMLElement} node The node to evaluate
554
* @return {Record} record The {@link Ext.data.Record} object
556
getRecord : function(node){
557
return this.store.getAt(node.viewIndex);
561
* Clears all selections.
562
* @param {Boolean} suppressEvent (optional) True to skip firing of the selectionchange event
564
clearSelections : function(suppressEvent, skipUpdate){
565
if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){
567
this.selected.removeClass(this.selectedClass);
569
this.selected.clear();
572
this.fireEvent("selectionchange", this, this.selected.elements);
578
* Returns true if the passed node is selected, else false.
579
* @param {HTMLElement/Number} node The node or node index to check
580
* @return {Boolean} True if selected, else false
582
isSelected : function(node){
583
return this.selected.contains(this.getNode(node));
588
* @param {HTMLElement/Number} node The node to deselect
590
deselect : function(node){
591
if(this.isSelected(node)){
592
node = this.getNode(node);
593
this.selected.removeElement(node);
594
if(this.last == node.viewIndex){
597
Ext.fly(node).removeClass(this.selectedClass);
598
this.fireEvent("selectionchange", this, this.selected.elements);
603
* Selects a set of nodes.
604
* @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node,
605
* id of a template node or an array of any of those to select
606
* @param {Boolean} keepExisting (optional) true to keep existing selections
607
* @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
609
select : function(nodeInfo, keepExisting, suppressEvent){
610
if(Ext.isArray(nodeInfo)){
612
this.clearSelections(true);
614
for(var i = 0, len = nodeInfo.length; i < len; i++){
615
this.select(nodeInfo[i], true, true);
618
this.fireEvent("selectionchange", this, this.selected.elements);
621
var node = this.getNode(nodeInfo);
623
this.clearSelections(true);
625
if(node && !this.isSelected(node)){
626
if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){
627
Ext.fly(node).addClass(this.selectedClass);
628
this.selected.add(node);
629
this.last = node.viewIndex;
631
this.fireEvent("selectionchange", this, this.selected.elements);
639
* Selects a range of nodes. All nodes between start and end are selected.
640
* @param {Number} start The index of the first node in the range
641
* @param {Number} end The index of the last node in the range
642
* @param {Boolean} keepExisting (optional) True to retain existing selections
644
selectRange : function(start, end, keepExisting){
646
this.clearSelections(true);
648
this.select(this.getNodes(start, end), true);
652
* Gets a template node.
653
* @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
654
* @return {HTMLElement} The node or null if it wasn't found
656
getNode : function(nodeInfo){
657
if(typeof nodeInfo == "string"){
658
return document.getElementById(nodeInfo);
659
}else if(typeof nodeInfo == "number"){
660
return this.all.elements[nodeInfo];
666
* Gets a range nodes.
667
* @param {Number} start (optional) The index of the first node in the range
668
* @param {Number} end (optional) The index of the last node in the range
669
* @return {Array} An array of nodes
671
getNodes : function(start, end){
672
var ns = this.all.elements;
674
end = typeof end == "undefined" ? Math.max(ns.length - 1, 0) : end;
677
for(i = start; i <= end && ns[i]; i++){
681
for(i = start; i >= end && ns[i]; i--){
689
* Finds the index of the passed node.
690
* @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
691
* @return {Number} The index of the node or -1
693
indexOf : function(node){
694
node = this.getNode(node);
695
if(typeof node.viewIndex == "number"){
696
return node.viewIndex;
698
return this.all.indexOf(node);
702
onBeforeLoad : function(){
703
if(this.loadingText){
704
this.clearSelections(false, true);
705
this.el.update('<div class="loading-indicator">'+this.loadingText+'</div>');
710
onDestroy : function(){
711
Ext.DataView.superclass.onDestroy.call(this);
716
Ext.reg('dataview', Ext.DataView);
b'\\ No newline at end of file'