Animated.js 12 KB

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