Animated.js 13 KB

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