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