infinite.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. define(function(require, exports, module) {
  2. "use strict";
  3. var Util = require('../util'),
  4. Base = require('../base');
  5. var transform = Util.prefixStyle("transform");
  6. var transition = Util.prefixStyle("transition");
  7. /**
  8. * An infinity dom-recycled list plugin for xscroll.
  9. * @constructor
  10. * @param {object} cfg
  11. * @param {string} cfg.transition recomposition cell with a transition
  12. * @param {string} cfg.infiniteElements dom-selector for reused elements
  13. * @param {function} cfg.renderHook render function for cell by per col or per row duration scrolling
  14. * @extends {Base}
  15. */
  16. var Infinite = function(cfg) {
  17. Infinite.superclass.constructor.call(this, cfg);
  18. this.userConfig = Util.mix({
  19. transition: 'all 0.5s ease'
  20. }, cfg);
  21. }
  22. Util.extend(Infinite, Base, {
  23. /**
  24. * a pluginId
  25. * @memberOf Infinite
  26. * @type {string}
  27. */
  28. pluginId: "infinite",
  29. /**
  30. * store the visible elements inside of view.
  31. * @memberOf Infinite
  32. * @type {object}
  33. */
  34. visibleElements: {},
  35. /**
  36. * store all elements data.
  37. * @memberOf Infinite
  38. * @type {object}
  39. */
  40. sections: {},
  41. /**
  42. * plugin initializer
  43. * @memberOf Infinite
  44. * @override Base
  45. * @return {Infinite}
  46. */
  47. pluginInitializer: function(xscroll) {
  48. var self = this;
  49. self.xscroll = xscroll;
  50. self.isY = !!(xscroll.userConfig.zoomType == "y");
  51. self._ = {
  52. _top:self.isY ? "_top" : "_left",
  53. _height:self.isY ? "_height" : "_width",
  54. top:self.isY ? "top" : "left",
  55. height:self.isY ? "height" : "width",
  56. width:self.isY ? "width" : "height",
  57. y:self.isY ? "y" : "x",
  58. translate:self.isY ? "translateY" : "translateX",
  59. containerHeight:self.isY ? "containerHeight" : "containerWidth",
  60. scrollTop:self.isY ? "scrollTop" : "scrollLeft",
  61. }
  62. self._initInfinite();
  63. xscroll.on("afterrender", function() {
  64. self.render();
  65. self._bindEvt();
  66. });
  67. return self;
  68. },
  69. /**
  70. * detroy the plugin
  71. * @memberOf Infinite
  72. * @override Base
  73. * @return {Infinite}
  74. */
  75. pluginDestructor: function() {
  76. var self = this;
  77. var _ = self._;
  78. for (var i = 0; i < self.infiniteLength; i++) {
  79. self.infiniteElements[i].style[_.top] = "auto";
  80. self.infiniteElements[i].style[transform] = "none";
  81. self.infiniteElements[i].style.visibility = "hidden";
  82. }
  83. self.xscroll && self.xscroll.off("scroll", self._updateByScroll, self);
  84. self.xscroll && self.xscroll.off("tap panstart pan panend", self._cellEventsHandler, self);
  85. return self;
  86. },
  87. _initInfinite: function() {
  88. var self = this;
  89. var xscroll = self.xscroll;
  90. var _ = self._;
  91. self.sections = {};
  92. self.infiniteElements = xscroll.renderTo.querySelectorAll(self.userConfig.infiniteElements);
  93. self.infiniteLength = self.infiniteElements.length;
  94. self.infiniteElementsCache = (function() {
  95. var tmp = []
  96. for (var i = 0; i < self.infiniteLength; i++) {
  97. tmp.push({});
  98. self.infiniteElements[i].style.position = "absolute";
  99. self.infiniteElements[i].style[_.top] = 0;
  100. self.infiniteElements[i].style.visibility = "hidden";
  101. self.infiniteElements[i].style.display = "block";
  102. Util.addClass(self.infiniteElements[i], "_xs_infinite_elements_");
  103. }
  104. return tmp;
  105. })();
  106. self.elementsPos = {};
  107. return self;
  108. },
  109. _renderUnRecycledEl: function() {
  110. var self = this;
  111. var _ = self._;
  112. var translateZ = self.userConfig.gpuAcceleration ? " translateZ(0) " : "";
  113. for (var i in self.__serializedData) {
  114. var unrecycledEl = self.__serializedData[i];
  115. if (self.__serializedData[i]['recycled'] === false) {
  116. var el = unrecycledEl.id && document.getElementById(unrecycledEl.id.replace("#", "")) || document.createElement("div");
  117. var randomId = Util.guid("xs-row-");
  118. el.id = unrecycledEl.id || randomId;
  119. unrecycledEl.id = el.id;
  120. self.xscroll.content.appendChild(el);
  121. for (var attrName in unrecycledEl.style) {
  122. if (attrName != _.height && attrName != "display" && attrName != "position") {
  123. el.style[attrName] = unrecycledEl.style[attrName];
  124. }
  125. }
  126. el.style[_.top] = 0;
  127. el.style.position = "absolute";
  128. el.style.display = "block";
  129. el.style[_.height] = unrecycledEl[_._height] + "px";
  130. el.style[transform] = _.translate + "(" + unrecycledEl[_._top] + "px) " + translateZ;
  131. Util.addClass(el, unrecycledEl.className);
  132. self.userConfig.renderHook.call(self, el, unrecycledEl);
  133. }
  134. }
  135. },
  136. /**
  137. * render or update the scroll contents
  138. * @memberOf Infinite
  139. * @return {Infinite}
  140. */
  141. render: function() {
  142. var self = this;
  143. var _ = self._;
  144. var xscroll = self.xscroll;
  145. var offset = self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft();
  146. self.visibleElements = self.getVisibleElements(offset);
  147. self.__serializedData = self._computeDomPositions();
  148. xscroll.sticky && xscroll.sticky.render(true); //force render
  149. xscroll.fixed && xscroll.fixed.render();
  150. var size = xscroll[_.height];
  151. var containerSize = self._containerSize;
  152. if (containerSize < size) {
  153. containerSize = size;
  154. }
  155. xscroll[_.containerHeight] = containerSize;
  156. xscroll.container.style[_.height] = containerSize + "px";
  157. xscroll.content.style[_.height] = containerSize + "px";
  158. self._renderUnRecycledEl();
  159. self._updateByScroll();
  160. self._updateByRender(offset);
  161. self.xscroll.boundryCheck();
  162. return self;
  163. },
  164. _getChangedRows: function(newElementsPos) {
  165. var self = this;
  166. var changedRows = {};
  167. for (var i in self.elementsPos) {
  168. if (!newElementsPos.hasOwnProperty(i)) {
  169. changedRows[i] = "delete";
  170. }
  171. }
  172. for (var i in newElementsPos) {
  173. if (newElementsPos[i].recycled && !self.elementsPos.hasOwnProperty(i)) {
  174. changedRows[i] = "add";
  175. }
  176. }
  177. self.elementsPos = newElementsPos;
  178. return changedRows;
  179. },
  180. _updateByScroll: function(e) {
  181. var self = this;
  182. var xscroll = self.xscroll;
  183. var _ = self._;
  184. var _pos = e && e[_.scrollTop];
  185. var pos = _pos === undefined ? (self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft()) : _pos;
  186. var elementsPos = self.getVisibleElements(pos);
  187. var changedRows = self.changedRows = self._getChangedRows(elementsPos);
  188. try{
  189. for (var i in changedRows) {
  190. if (changedRows[i] == "delete") {
  191. self._pushEl(i);
  192. }
  193. if (changedRows[i] == "add") {
  194. var elObj = self._popEl(elementsPos[i][self.guid]);
  195. var index = elObj.index;
  196. var el = elObj.el;
  197. if (el) {
  198. self.infiniteElementsCache[index].guid = elementsPos[i].guid;
  199. self.__serializedData[elementsPos[i].guid].__infiniteIndex = index;
  200. self._renderData(el, elementsPos[i]);
  201. self._renderStyle(el, elementsPos[i]);
  202. }
  203. }
  204. }
  205. }catch(e){
  206. console.warn('Not enough infiniteElements setted!');
  207. }
  208. return self;
  209. },
  210. _updateByRender: function(pos) {
  211. var self = this;
  212. var _ = self._;
  213. var xscroll = self.xscroll;
  214. var pos = pos === undefined ? (self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft()) : pos;
  215. var prevElementsPos = self.visibleElements;
  216. var newElementsPos = self.getVisibleElements(pos);
  217. var prevEl, newEl;
  218. //repaint
  219. for (var i in newElementsPos) {
  220. newEl = newElementsPos[i];
  221. for (var j in prevElementsPos) {
  222. prevEl = prevElementsPos[j];
  223. if (prevEl.guid === newEl.guid) {
  224. if (newEl.style != prevEl.style || newEl[_._top] != prevEl[_._top] || newEl[_._height] != prevEl[_._height]) {
  225. self._renderStyle(self.infiniteElements[newEl.__infiniteIndex], newEl, true);
  226. }
  227. if (JSON.stringify(newEl.data) != JSON.stringify(prevEl.data)) {
  228. self._renderData(self.infiniteElements[newEl.__infiniteIndex], newEl);
  229. }
  230. } else {
  231. // paint
  232. if (self.__serializedData[newEl.guid].recycled && self.__serializedData[newEl.guid].__infiniteIndex === undefined) {
  233. var elObj = self._popEl();
  234. self.__serializedData[newEl.guid].__infiniteIndex = elObj.index;
  235. self._renderData(elObj.el, newEl);
  236. self._renderStyle(elObj.el, newEl);
  237. }
  238. }
  239. }
  240. }
  241. self.visibleElements = newElementsPos;
  242. },
  243. /**
  244. * get all element posInfo such as top,height,template,html
  245. * @return {Array}
  246. **/
  247. _computeDomPositions: function() {
  248. var self = this;
  249. var _ = self._;
  250. var pos = 0,
  251. size = 0,
  252. sections = self.sections,
  253. section;
  254. var data = [];
  255. var serializedData = {};
  256. for (var i in sections) {
  257. for (var j = 0, len = sections[i].length; j < len; j++) {
  258. section = sections[i][j];
  259. section.sectionId = i;
  260. section.index = j;
  261. data.push(section);
  262. }
  263. }
  264. //f = v/itemSize*1000 < 60 => v = 0.06 * itemSize
  265. self.userConfig.maxSpeed = 0.06 * 50;
  266. for (var i = 0, l = data.length; i < l; i++) {
  267. var item = data[i];
  268. size = item.style && item.style[_.height] >= 0 && item.style.position != "fixed" ? item.style[_.height] : 0;
  269. item.guid = item.guid || Util.guid();
  270. item[_._top] = pos;
  271. item[_._height] = size;
  272. item.recycled = item.recycled === false ? false : true;
  273. pos += size;
  274. serializedData[item.guid] = item;
  275. }
  276. self._containerSize = pos;
  277. return serializedData;
  278. },
  279. /**
  280. * get all elements inside of the view.
  281. * @memberOf Infinite
  282. * @param {number} pos scrollLeft or scrollTop
  283. * @return {object} visibleElements
  284. */
  285. getVisibleElements: function(pos) {
  286. var self = this;
  287. var xscroll = self.xscroll;
  288. var _ = self._;
  289. var pos = pos === undefined ? (self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft()) : pos;
  290. var threshold = self.userConfig.threshold >= 0 ? self.userConfig.threshold : xscroll[_.height] / 3;
  291. var tmp = {},
  292. item;
  293. var data = self.__serializedData;
  294. for (var i in data) {
  295. item = data[i];
  296. if (item[_._top] >= pos - threshold && item[_._top] <= pos + xscroll[_.height] + threshold) {
  297. tmp[item.guid] = item;
  298. }
  299. }
  300. return JSON.parse(JSON.stringify(tmp));
  301. },
  302. _popEl: function() {
  303. var self = this;
  304. for (var i = 0; i < self.infiniteLength; i++) {
  305. if (!self.infiniteElementsCache[i]._visible) {
  306. self.infiniteElementsCache[i]._visible = true;
  307. return {
  308. index: i,
  309. el: self.infiniteElements[i]
  310. }
  311. }
  312. }
  313. },
  314. _pushEl: function(guid) {
  315. var self = this;
  316. for (var i = 0; i < self.infiniteLength; i++) {
  317. if (self.infiniteElementsCache[i].guid == guid) {
  318. self.infiniteElementsCache[i]._visible = false;
  319. self.infiniteElements[i].style.visibility = "hidden";
  320. self.infiniteElementsCache[i].guid = null;
  321. }
  322. }
  323. },
  324. _renderData: function(el, elementObj) {
  325. var self = this;
  326. if (!el || !elementObj || elementObj.style.position == "fixed") return;
  327. self.userConfig.renderHook.call(self, el, elementObj);
  328. },
  329. _renderStyle: function(el, elementObj, useTransition) {
  330. var self = this;
  331. var _ = self._;
  332. if (!el) return;
  333. var translateZ = self.xscroll.userConfig.gpuAcceleration ? " translateZ(0) " : "";
  334. //update style
  335. for (var attrName in elementObj.style) {
  336. if (attrName != _.height && attrName != "display" && attrName != "position") {
  337. el.style[attrName] = elementObj.style[attrName];
  338. }
  339. }
  340. el.setAttribute("xs-index", elementObj.index);
  341. el.setAttribute("xs-sectionid", elementObj.sectionId);
  342. el.setAttribute("xs-guid", elementObj.guid);
  343. el.style.visibility = "visible";
  344. el.style[_.height] = elementObj[_._height] + "px";
  345. el.style[transform] = _.translate + "(" + elementObj[_._top] + "px) " + translateZ;
  346. el.style[transition] = useTransition ? self.userConfig.transition : "none";
  347. },
  348. getCell: function(e) {
  349. var self = this,
  350. cell;
  351. var el = Util.findParentEl(e.target, "._xs_infinite_elements_", self.xscroll.renderTo);
  352. if(!el){
  353. el = Util.findParentEl(e.target, ".xs-sticky-handler", self.xscroll.renderTo);
  354. }
  355. var guid = el && el.getAttribute("xs-guid");
  356. if (undefined === guid) return;
  357. return {
  358. data:self.__serializedData[guid],
  359. el:el
  360. };
  361. },
  362. _bindEvt: function() {
  363. var self = this;
  364. if (self._isEvtBinded) return;
  365. self._isEvtBinded = true;
  366. self.xscroll.renderTo.addEventListener("webkitTransitionEnd", function(e) {
  367. if (e.target.className.match(/xs-row/)) {
  368. e.target.style.webkitTransition = "";
  369. }
  370. });
  371. self.xscroll.on("scroll", self._updateByScroll, self);
  372. self.xscroll.on("tap panstart pan panend", self._cellEventsHandler, self);
  373. return self;
  374. },
  375. _cellEventsHandler: function(e) {
  376. var self = this;
  377. var cell = self.getCell(e);
  378. e.cell = cell.data;
  379. e.cellEl = cell.el;
  380. e.cell && self[e.type].call(self, e);
  381. },
  382. /**
  383. * tap event
  384. * @memberOf Infinite
  385. * @param {object} e events data include cell object
  386. * @event
  387. */
  388. tap: function(e) {
  389. this.trigger("tap", e);
  390. return this;
  391. },
  392. /**
  393. * panstart event
  394. * @memberOf Infinite
  395. * @param {object} e events data include cell object
  396. * @event
  397. */
  398. panstart: function(e) {
  399. this.trigger("panstart", e);
  400. return this;
  401. },
  402. /**
  403. * pan event
  404. * @memberOf Infinite
  405. * @param {object} e events data include cell object
  406. * @event
  407. */
  408. pan: function(e) {
  409. this.trigger("pan", e);
  410. return this;
  411. },
  412. /**
  413. * panend event
  414. * @memberOf Infinite
  415. * @param {object} e events data include cell object
  416. * @event
  417. */
  418. panend: function(e) {
  419. this.trigger("panend", e);
  420. return this;
  421. },
  422. /**
  423. * insert data before a position
  424. * @memberOf Infinite
  425. * @param {string} sectionId sectionId of the target cell
  426. * @param {number} index index of the target cell
  427. * @param {object} data data to insert
  428. * @return {Infinite}
  429. */
  430. insertBefore: function(sectionId, index, data) {
  431. var self = this;
  432. if (sectionId === undefined || index === undefined || data === undefined) return self;
  433. if (!self.sections[sectionId]) {
  434. self.sections[sectionId] = [];
  435. }
  436. self.sections[sectionId].splice(index, 0, data);
  437. return self;
  438. },
  439. /**
  440. * insert data after a position
  441. * @memberOf Infinite
  442. * @param {string} sectionId sectionId of the target cell
  443. * @param {number} index index of the target cell
  444. * @param {object} data data to insert
  445. * @return {Infinite}
  446. */
  447. insertAfter: function(sectionId, index, data) {
  448. var self = this;
  449. if (sectionId === undefined || index === undefined || data === undefined) return self;
  450. if (!self.sections[sectionId]) {
  451. self.sections[sectionId] = [];
  452. }
  453. self.sections[sectionId].splice(Number(index) + 1, 0, data);
  454. return self;
  455. },
  456. /**
  457. * append data after a section
  458. * @memberOf Infinite
  459. * @param {string} sectionId sectionId for the append cell
  460. * @param {object} data data to append
  461. * @return {Infinite}
  462. */
  463. append: function(sectionId, data) {
  464. var self = this;
  465. if (!self.sections[sectionId]) {
  466. self.sections[sectionId] = [];
  467. }
  468. self.sections[sectionId] = self.sections[sectionId].concat(data);
  469. return self;
  470. },
  471. /**
  472. * remove some data by sectionId,from,number
  473. * @memberOf Infinite
  474. * @param {string} sectionId sectionId for the append cell
  475. * @param {number} from removed index from
  476. * @param {number} number removed data number
  477. * @return {Infinite}
  478. */
  479. remove: function(sectionId, from, number) {
  480. var self = this;
  481. var number = number || 1;
  482. if (undefined === sectionId || !self.sections[sectionId]) return self;
  483. //remove a section
  484. if (undefined === from) {
  485. self.sections[sectionId] = null;
  486. return self;
  487. }
  488. //remove some data in section
  489. if (self.sections[sectionId] && self.sections[sectionId][from]) {
  490. self.sections[sectionId].splice(from, number);
  491. return self;
  492. }
  493. return self;
  494. },
  495. /**
  496. * replace some data by sectionId and index
  497. * @memberOf Infinite
  498. * @param {string} sectionId sectionId to replace
  499. * @param {number} index removed index from
  500. * @param {object} data new data to replace
  501. * @return {Infinite}
  502. */
  503. replace: function(sectionId, index, data) {
  504. var self = this;
  505. if (undefined === sectionId || !self.sections[sectionId]) return self;
  506. self.sections[sectionId][index] = data;
  507. return self;
  508. },
  509. /**
  510. * get data by sectionId and index
  511. * @memberOf Infinite
  512. * @param {string} sectionId sectionId
  513. * @param {number} index index in the section
  514. * @return {object} data data
  515. */
  516. get: function(sectionId, index) {
  517. if (undefined === sectionId) return;
  518. if (undefined === index) return this.sections[sectionId];
  519. return this.sections[sectionId][index];
  520. }
  521. });
  522. if (typeof module == 'object' && module.exports) {
  523. module.exports = Infinite;
  524. }
  525. /** ignored by jsdoc **/
  526. else if (window.XScroll && window.XScroll.Plugins) {
  527. return XScroll.Plugins.Infinite = Infinite;
  528. }
  529. });