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