/** * @class Ext.ux.DataViewTransition * Transition plugin for DataViews *//* eslint-disable vars-on-top, one-var */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); // 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) { // eslint-disable-line eqeqeq this.cacheStoreData(store); return; } var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty)), // calculate the number of rows and columns we have itemWidth = el.getMargin('lr') + el.getWidth(), itemHeight = el.getMargin('bt') + el.getHeight(), dvWidth = parentEl.getWidth(), columns = Math.floor(dvWidth / itemWidth); // 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) { // eslint-disable-next-line no-redeclare var id = item.get(this.idProperty), el = elCache[id] = Ext.get(this.dataviewID + '-' + id); oldPositions[id] = { top: el.getY() - parentEl.getY() - el.getMargin('t') - parentEl.getPadding('t'), left: el.getX() - parentEl.getX() - 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), 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, doAnimate = function() { var elapsed = new Date() - startTime, fraction = elapsed / duration, id; if (fraction >= 1) { for (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 (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" }); } } }, task = { run: doAnimate, interval: 20, scope: this }; Ext.TaskManager.start(task); //<debug> var count = 0; for (var k in added) { // eslint-disable-line no-unused-vars count++; } if (Ext.global.console && Ext.global.console.log) { 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) { // eslint-disable-line no-unused-vars 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) { // eslint-disable-next-line eqeqeq 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) { /* eslint-disable-next-line eqeqeq */ if (this.cachedStoreData[record.get(this.idProperty)] != undefined) { remaining[record.get(this.idProperty)] = record; } }, this); return remaining; }});