/**
* @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;
}
});