Upgrade to ExtJS 3.2.2 - Released 06/02/2010
[extjs.git] / examples / ux / DataViewTransition.js
1 /*!
2  * Ext JS Library 3.2.2
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.ux.DataViewTransition
9  * @extends Object
10  * @author Ed Spencer (http://extjs.com)
11  * Transition plugin for DataViews
12  */
13 Ext.ux.DataViewTransition = Ext.extend(Object, {
14
15     /**
16      * @property defaults
17      * @type Object
18      * Default configuration options for all DataViewTransition instances
19      */
20     defaults: {
21         duration  : 750,
22         idProperty: 'id'
23     },
24     
25     /**
26      * Creates the plugin instance, applies defaults
27      * @constructor
28      * @param {Object} config Optional config object
29      */
30     constructor: function(config) {
31         Ext.apply(this, config || {}, this.defaults);
32     },
33
34     /**
35      * Initializes the transition plugin. Overrides the dataview's default refresh function
36      * @param {Ext.DataView} dataview The dataview
37      */
38     init: function(dataview) {
39         /**
40          * @property dataview
41          * @type Ext.DataView
42          * Reference to the DataView this instance is bound to
43          */
44         this.dataview = dataview;
45         
46         var idProperty = this.idProperty;
47         dataview.blockRefresh = true;
48         dataview.updateIndexes = dataview.updateIndexes.createSequence(function() {
49             this.getTemplateTarget().select(this.itemSelector).each(function(element, composite, index) {
50                 element.id = element.dom.id = String.format("{0}-{1}", dataview.id, store.getAt(index).get(idProperty));
51             }, this);
52         }, dataview);
53         
54         /**
55          * @property dataviewID
56          * @type String
57          * The string ID of the DataView component. This is used internally when animating child objects
58          */
59         this.dataviewID = dataview.id;
60         
61         /**
62          * @property cachedStoreData
63          * @type Object
64          * A cache of existing store data, keyed by id. This is used to determine
65          * whether any items were added or removed from the store on data change
66          */
67         this.cachedStoreData = {};
68         
69         var store = dataview.store;
70         
71         //cache the data immediately, and again on any load operation
72         this.cacheStoreData(store);
73         store.on('load', this.cacheStoreData, this);
74         
75         store.on('datachanged', function(store) {
76             var parentEl = dataview.getTemplateTarget(),
77                 calcItem = store.getAt(0),
78                 added    = this.getAdded(store),
79                 removed  = this.getRemoved(store),
80                 previous = this.getRemaining(store),
81                 existing = Ext.apply({}, previous, added);
82             
83             //hide old items
84             Ext.each(removed, function(item) {
85                 Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).fadeOut({
86                     remove  : false,
87                     duration: duration / 1000,
88                     useDisplay: true
89                 });
90             }, this);
91             
92             //store is empty
93             if (calcItem == undefined) {
94                 this.cacheStoreData(store);
95                 return;
96             }
97             
98             var el = parentEl.child("#" + this.dataviewID + "-" + calcItem.get(this.idProperty));
99             
100             //calculate the number of rows and columns we have
101             var itemCount   = store.getCount(),
102                 itemWidth   = el.getMargins('lr') + el.getWidth(),
103                 itemHeight  = el.getMargins('bt') + el.getHeight(),
104                 dvWidth     = parentEl.getWidth(),
105                 columns     = Math.floor(dvWidth / itemWidth),
106                 rows        = Math.ceil(itemCount / columns),
107                 currentRows = Math.ceil(this.getExistingCount() / columns);
108             
109             //make sure the correct styles are applied to the parent element
110             parentEl.applyStyles({
111                 display : 'block',
112                 position: 'relative'
113                 // ,
114                 // height  : Ext.max([rows, currentRows]) * itemHeight,
115                 // width   : columns * itemWidth
116             });
117             
118             //stores the current top and left values for each element (discovered below)
119             var oldPositions = {},
120                 newPositions = {},
121                 elCache      = {};
122             
123             //find current positions of each element and save a reference in the elCache
124             Ext.iterate(previous, function(id, item) {
125                 var id = item.get(this.idProperty),
126                     el = elCache[id] = parentEl.child('#' + this.dataviewID + '-' + id);
127                 
128                 oldPositions[id] = {
129                     top : el.getTop()  - parentEl.getTop()  - el.getMargins('t') - parentEl.getPadding('t'),
130                     left: el.getLeft() - parentEl.getLeft() - el.getMargins('l') - parentEl.getPadding('l')
131                 };
132             }, this);
133             
134             //set absolute positioning on all DataView items. We need to set position, left and 
135             //top at the same time to avoid any flickering
136             Ext.iterate(previous, function(id, item) {
137                 var oldPos = oldPositions[id],
138                     el     = elCache[id];
139                     
140                 if (el.getStyle('position') != 'absolute') {
141                     elCache[id].applyStyles({
142                         position: 'absolute',
143                         left    : oldPos.left + "px",
144                         top     : oldPos.top + "px",
145
146                         //we set the width here to make ListViews work correctly. This is not needed for DataViews
147                         width   : el.getWidth(!Ext.isIE || Ext.isStrict),
148                         height  : el.getHeight(!Ext.isIE || Ext.isStrict)
149                     });
150                 }
151             });
152             
153             //get new positions
154             var index = 0;
155             Ext.iterate(store.data.items, function(item) {
156                 var id = item.get(idProperty),
157                     el = elCache[id];
158                 
159                 var column = index % columns,
160                     row    = Math.floor(index / columns),
161                     top    = row    * itemHeight,
162                     left   = column * itemWidth;
163                 
164                 newPositions[id] = {
165                     top : top,
166                     left: left
167                 };
168                 
169                 index ++;
170             }, this);
171             
172             //do the movements
173             var startTime  = new Date(),
174                 duration   = this.duration,
175                 dataviewID = this.dataviewID;
176             
177             var doAnimate = function() {
178                 var elapsed  = new Date() - startTime,
179                     fraction = elapsed / duration;
180                 
181                 if (fraction >= 1) {
182                     for (var id in newPositions) {
183                         Ext.fly(dataviewID + '-' + id).applyStyles({
184                             top : newPositions[id].top + "px",
185                             left: newPositions[id].left + "px"
186                         });
187                     }
188                     
189                     Ext.TaskMgr.stop(task);
190                 } else {
191                     //move each item
192                     for (var id in newPositions) {
193                         if (!previous[id]) continue;
194                         
195                         var oldPos  = oldPositions[id],
196                             newPos  = newPositions[id],
197                             oldTop  = oldPos.top,
198                             newTop  = newPos.top,
199                             oldLeft = oldPos.left,
200                             newLeft = newPos.left,
201                             diffTop = fraction * Math.abs(oldTop  - newTop),
202                             diffLeft= fraction * Math.abs(oldLeft - newLeft),
203                             midTop  = oldTop  > newTop  ? oldTop  - diffTop  : oldTop  + diffTop,
204                             midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
205                         
206                         Ext.fly(dataviewID + '-' + id).applyStyles({
207                             top : midTop + "px",
208                             left: midLeft + "px"
209                         });
210                     }
211                 }
212             };
213             
214             var task = {
215                 run     : doAnimate,
216                 interval: 20,
217                 scope   : this
218             };
219             
220             Ext.TaskMgr.start(task);
221             
222             //show new items
223             Ext.iterate(added, function(id, item) {
224                 Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
225                     top : newPositions[id].top + "px",
226                     left: newPositions[id].left + "px"
227                 }).fadeIn({
228                     remove  : false,
229                     duration: duration / 1000
230                 });
231             }, this);
232             
233             this.cacheStoreData(store);
234         }, this);
235     },
236     
237     /**
238      * Caches the records from a store locally for comparison later
239      * @param {Ext.data.Store} store The store to cache data from
240      */
241     cacheStoreData: function(store) {
242         this.cachedStoreData = {};
243         
244         store.each(function(record) {
245              this.cachedStoreData[record.get(this.idProperty)] = record;
246         }, this);
247     },
248     
249     /**
250      * Returns all records that were already in the DataView
251      * @return {Object} All existing records
252      */
253     getExisting: function() {
254         return this.cachedStoreData;
255     },
256     
257     /**
258      * Returns the total number of items that are currently visible in the DataView
259      * @return {Number} The number of existing items
260      */
261     getExistingCount: function() {
262         var count = 0,
263             items = this.getExisting();
264         
265         for (var k in items) count++;
266         
267         return count;
268     },
269     
270     /**
271      * Returns all records in the given store that were not already present
272      * @param {Ext.data.Store} store The updated store instance
273      * @return {Object} Object of records not already present in the dataview in format {id: record}
274      */
275     getAdded: function(store) {
276         var added = {};
277         
278         store.each(function(record) {
279             if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
280                 added[record.get(this.idProperty)] = record;
281             }
282         }, this);
283         
284         return added;
285     },
286     
287     /**
288      * Returns all records that are present in the DataView but not the new store
289      * @param {Ext.data.Store} store The updated store instance
290      * @return {Array} Array of records that used to be present
291      */
292     getRemoved: function(store) {
293         var removed = [];
294         
295         for (var id in this.cachedStoreData) {
296             if (store.findExact(this.idProperty, Number(id)) == -1) removed.push(this.cachedStoreData[id]);
297         }
298         
299         return removed;
300     },
301     
302     /**
303      * Returns all records that are already present and are still present in the new store
304      * @param {Ext.data.Store} store The updated store instance
305      * @return {Object} Object of records that are still present from last time in format {id: record}
306      */
307     getRemaining: function(store) {
308       var remaining = {};
309       
310       store.each(function(record) {
311           if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
312               remaining[record.get(this.idProperty)] = record;
313           }
314       }, this);
315       
316       return remaining;
317     }
318 });