DataViewTransition.js 12 KB

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