DataViewTransition.js 12 KB

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