text: /*
  
  This file is part of Ext JS 4
  
  Copyright (c) 2011 Sencha Inc
  
  Contact:  http://www.sencha.com/contact
  
  GNU General Public License Usage
  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.
  
  If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
  
  */
  /**
   * @class Ext.ux.DataViewTransition
   * @extends Object
   * @author Ed Spencer (http://sencha.com)
   * Transition plugin for DataViews
   */
  Ext.ux.DataViewTransition = Ext.extend(Object, {
  
      /**
       * @property defaults
       * @type Object
       * Default configuration options for all DataViewTransition instances
       */
      defaults: {
          duration  : 750,
          idProperty: 'id'
      },
      
      /**
       * Creates the plugin instance, applies defaults
       * @constructor
       * @param {Object} config Optional config object
       */
      constructor: function(config) {
          Ext.apply(this, config || {}, this.defaults);
      },
  
      /**
       * Initializes the transition plugin. Overrides the dataview's default refresh function
       * @param {Ext.view.View} dataview The dataview
       */
      init: function(dataview) {
          /**
           * @property dataview
           * @type Ext.view.View
           * Reference to the DataView this instance is bound to
           */
          this.dataview = dataview;
          
          var idProperty = this.idProperty;
          dataview.blockRefresh = true;
          dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
              this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
                  element.id = element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, dataview.store.getAt(index).get(idProperty));
              }, this);
          }, dataview);
          
          /**
           * @property dataviewID
           * @type String
           * The string ID of the DataView component. This is used internally when animating child objects
           */
          this.dataviewID = dataview.id;
          
          /**
           * @property cachedStoreData
           * @type Object
           * A cache of existing store data, keyed by id. This is used to determine
           * whether any items were added or removed from the store on data change
           */
          this.cachedStoreData = {};
          
          //var store = dataview.store;
          
          //catch the store data with the snapshot immediately
          this.cacheStoreData(dataview.store.snapshot);
          
          dataview.store.on('datachanged', function(store) {
              var parentEl = dataview.getTargetEl(),
                  calcItem = store.getAt(0),
                  added    = this.getAdded(store),
                  removed  = this.getRemoved(store),
                  previous = this.getRemaining(store),
                  existing = Ext.apply({}, previous, added);
              
              //hide old items
              Ext.each(removed, function(item) {
                  Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
                      remove  : false,
                      duration: duration,
                      opacity : 0,
                      useDisplay: true
                  });
              }, this);
              
              //store is empty
              if (calcItem == undefined) {
                  this.cacheStoreData(store);
                  return;
              }
              
              var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty));
              
              //calculate the number of rows and columns we have
              var itemCount   = store.getCount(),
                  itemWidth   = el.getMargin('lr') + el.getWidth(),
                  itemHeight  = el.getMargin('bt') + el.getHeight(),
                  dvWidth     = parentEl.getWidth(),
                  columns     = Math.floor(dvWidth / itemWidth),
                  rows        = Math.ceil(itemCount / columns),
                  currentRows = Math.ceil(this.getExistingCount() / columns);
              
              //make sure the correct styles are applied to the parent element
              parentEl.applyStyles({
                  display : 'block',
                  position: 'relative'
              });
              
              //stores the current top and left values for each element (discovered below)
              var oldPositions = {},
                  newPositions = {},
                  elCache      = {};
              
              //find current positions of each element and save a reference in the elCache
              Ext.iterate(previous, function(id, item) {
                  var id = item.get(this.idProperty),
                      el = elCache[id] = Ext.get(this.dataviewID + '-' + id);
                  
                  oldPositions[id] = {
                      top : el.getTop()  - parentEl.getTop()  - el.getMargin('t') - parentEl.getPadding('t'),
                      left: el.getLeft() - parentEl.getLeft() - el.getMargin('l') - parentEl.getPadding('l')
                  };
              }, this);
              
              //set absolute positioning on all DataView items. We need to set position, left and 
              //top at the same time to avoid any flickering
              Ext.iterate(previous, function(id, item) {
                  var oldPos = oldPositions[id],
                      el     = elCache[id];
                      
                  if (el.getStyle('position') != 'absolute') {
                      elCache[id].applyStyles({
                          position: 'absolute',
                          left    : oldPos.left + "px",
                          top     : oldPos.top + "px",
  
                          //we set the width here to make ListViews work correctly. This is not needed for DataViews
                          width   : el.getWidth(!Ext.isIE || Ext.isStrict),
                          height  : el.getHeight(!Ext.isIE || Ext.isStrict)
                      });
                  }
              });
              
              //get new positions
              var index = 0;
              Ext.iterate(store.data.items, function(item) {
                  var id = item.get(idProperty),
                      el = elCache[id];
                  
                  var column = index % columns,
                      row    = Math.floor(index / columns),
                      top    = row    * itemHeight,
                      left   = column * itemWidth;
                  
                  newPositions[id] = {
                      top : top,
                      left: left
                  };
                  
                  index ++;
              }, this);
              
              //do the movements
              var startTime  = new Date(),
                  duration   = this.duration,
                  dataviewID = this.dataviewID;
              
              var doAnimate = function() {
                  var elapsed  = new Date() - startTime,
                      fraction = elapsed / duration;
                  
                  if (fraction >= 1) {
                      for (var id in newPositions) {
                          Ext.fly(dataviewID + '-' + id).applyStyles({
                              top : newPositions[id].top + "px",
                              left: newPositions[id].left + "px"
                          });
                      }
                      
                      Ext.TaskManager.stop(task);
                  } else {
                      //move each item
                      for (var id in newPositions) {
                          if (!previous[id]) continue;
                          
                          var oldPos  = oldPositions[id],
                              newPos  = newPositions[id],
                              oldTop  = oldPos.top,
                              newTop  = newPos.top,
                              oldLeft = oldPos.left,
                              newLeft = newPos.left,
                              diffTop = fraction * Math.abs(oldTop  - newTop),
                              diffLeft= fraction * Math.abs(oldLeft - newLeft),
                              midTop  = oldTop  > newTop  ? oldTop  - diffTop  : oldTop  + diffTop,
                              midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
                          
                          Ext.fly(dataviewID + '-' + id).applyStyles({
                              top : midTop + "px",
                              left: midLeft + "px"
                          });
                      }
                  }
              };
              
              var task = {
                  run     : doAnimate,
                  interval: 20,
                  scope   : this
              };
              
              Ext.TaskManager.start(task);
              
              //<debug>
              var count = 0;
              for (var k in added) {
                  count++;
              }
              if (Ext.global.console) {
                  Ext.global.console.log('added:', count);
              }
              //</debug>
              
              //show new items
              Ext.iterate(added, function(id, item) {
                  Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
                      top    : newPositions[item.get(this.idProperty)].top + "px",
                      left   : newPositions[item.get(this.idProperty)].left + "px"
                  });
                  
                  Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
                      remove  : false,
                      duration: duration,
                      opacity : 1
                  });
              }, this);
              
              this.cacheStoreData(store);
          }, this);
      },
      
      /**
       * Caches the records from a store locally for comparison later
       * @param {Ext.data.Store} store The store to cache data from
       */
      cacheStoreData: function(store) {
          this.cachedStoreData = {};
          
          store.each(function(record) {
               this.cachedStoreData[record.get(this.idProperty)] = record;
          }, this);
      },
      
      /**
       * Returns all records that were already in the DataView
       * @return {Object} All existing records
       */
      getExisting: function() {
          return this.cachedStoreData;
      },
      
      /**
       * Returns the total number of items that are currently visible in the DataView
       * @return {Number} The number of existing items
       */
      getExistingCount: function() {
          var count = 0,
              items = this.getExisting();
          
          for (var k in items) count++;
          
          return count;
      },
      
      /**
       * Returns all records in the given store that were not already present
       * @param {Ext.data.Store} store The updated store instance
       * @return {Object} Object of records not already present in the dataview in format {id: record}
       */
      getAdded: function(store) {
          var added = {};
          
          store.each(function(record) {
              if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
                  added[record.get(this.idProperty)] = record;
              }
          }, this);
          
          return added;
      },
      
      /**
       * Returns all records that are present in the DataView but not the new store
       * @param {Ext.data.Store} store The updated store instance
       * @return {Array} Array of records that used to be present
       */
      getRemoved: function(store) {
          var removed = [];
          
          for (var id in this.cachedStoreData) {
              if (store.findExact(this.idProperty, Number(id)) == -1) {
                  removed.push(this.cachedStoreData[id]);
              }
          }
          
          return removed;
      },
      
      /**
       * Returns all records that are already present and are still present in the new store
       * @param {Ext.data.Store} store The updated store instance
       * @return {Object} Object of records that are still present from last time in format {id: record}
       */
      getRemaining: function(store) {
          var remaining = {};
  
          store.each(function(record) {
              if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
                  remaining[record.get(this.idProperty)] = record;
              }
          }, this);
  
          return remaining;
      }
  });
  
  