Animated.js 12 KB

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