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