Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

lazy-repeat.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. import _toConsumableArray from 'babel-runtime/helpers/toConsumableArray';
  2. import _Object$keys from 'babel-runtime/core-js/object/keys';
  3. import _setImmediate from 'babel-runtime/core-js/set-immediate';
  4. import _typeof from 'babel-runtime/helpers/typeof';
  5. import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
  6. import _createClass from 'babel-runtime/helpers/createClass';
  7. /*
  8. Copyright 2013-2015 ASIAL CORPORATION
  9. Licensed under the Apache License, Version 2.0 (the "License");
  10. you may not use this file except in compliance with the License.
  11. You may obtain a copy of the License at
  12. http://www.apache.org/licenses/LICENSE-2.0
  13. Unless required by applicable law or agreed to in writing, software
  14. distributed under the License is distributed on an "AS IS" BASIS,
  15. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. See the License for the specific language governing permissions and
  17. limitations under the License.
  18. */
  19. import util from '../util';
  20. import platform from '../platform';
  21. export var LazyRepeatDelegate = function () {
  22. function LazyRepeatDelegate(userDelegate) {
  23. var templateElement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
  24. _classCallCheck(this, LazyRepeatDelegate);
  25. if ((typeof userDelegate === 'undefined' ? 'undefined' : _typeof(userDelegate)) !== 'object' || userDelegate === null) {
  26. util.throw('"delegate" parameter must be an object');
  27. }
  28. this._userDelegate = userDelegate;
  29. if (!(templateElement instanceof Element) && templateElement !== null) {
  30. util.throw('"templateElement" parameter must be an instance of Element or null');
  31. }
  32. this._templateElement = templateElement;
  33. }
  34. _createClass(LazyRepeatDelegate, [{
  35. key: 'hasRenderFunction',
  36. /**
  37. * @return {Boolean}
  38. */
  39. value: function hasRenderFunction() {
  40. return this._userDelegate._render instanceof Function;
  41. }
  42. /**
  43. * @return {void}
  44. */
  45. }, {
  46. key: '_render',
  47. value: function _render() {
  48. this._userDelegate._render.apply(this._userDelegate, arguments);
  49. }
  50. /**
  51. * @param {Number} index
  52. * @param {Function} done A function that take item object as parameter.
  53. */
  54. }, {
  55. key: 'loadItemElement',
  56. value: function loadItemElement(index, done) {
  57. if (this._userDelegate.loadItemElement instanceof Function) {
  58. this._userDelegate.loadItemElement(index, done);
  59. } else {
  60. var element = this._userDelegate.createItemContent(index, this._templateElement);
  61. if (!(element instanceof Element)) {
  62. util.throw('"createItemContent" must return an instance of Element');
  63. }
  64. done({ element: element });
  65. }
  66. }
  67. /**
  68. * @return {Number}
  69. */
  70. }, {
  71. key: 'countItems',
  72. value: function countItems() {
  73. var count = this._userDelegate.countItems();
  74. if (typeof count !== 'number') {
  75. util.throw('"countItems" must return a number');
  76. }
  77. return count;
  78. }
  79. /**
  80. * @param {Number} index
  81. * @param {Object} item
  82. * @param {Element} item.element
  83. */
  84. }, {
  85. key: 'updateItem',
  86. value: function updateItem(index, item) {
  87. if (this._userDelegate.updateItemContent instanceof Function) {
  88. this._userDelegate.updateItemContent(index, item);
  89. }
  90. }
  91. /**
  92. * @return {Number}
  93. */
  94. }, {
  95. key: 'calculateItemHeight',
  96. value: function calculateItemHeight(index) {
  97. if (this._userDelegate.calculateItemHeight instanceof Function) {
  98. var height = this._userDelegate.calculateItemHeight(index);
  99. if (typeof height !== 'number') {
  100. util.throw('"calculateItemHeight" must return a number');
  101. }
  102. return height;
  103. }
  104. return 0;
  105. }
  106. /**
  107. * @param {Number} index
  108. * @param {Object} item
  109. */
  110. }, {
  111. key: 'destroyItem',
  112. value: function destroyItem(index, item) {
  113. if (this._userDelegate.destroyItem instanceof Function) {
  114. this._userDelegate.destroyItem(index, item);
  115. }
  116. }
  117. /**
  118. * @return {void}
  119. */
  120. }, {
  121. key: 'destroy',
  122. value: function destroy() {
  123. if (this._userDelegate.destroy instanceof Function) {
  124. this._userDelegate.destroy();
  125. }
  126. this._userDelegate = this._templateElement = null;
  127. }
  128. }, {
  129. key: 'itemHeight',
  130. get: function get() {
  131. return this._userDelegate.itemHeight;
  132. }
  133. }]);
  134. return LazyRepeatDelegate;
  135. }();
  136. /**
  137. * This class provide core functions for ons-lazy-repeat.
  138. */
  139. export var LazyRepeatProvider = function () {
  140. /**
  141. * @param {Element} wrapperElement
  142. * @param {LazyRepeatDelegate} delegate
  143. */
  144. function LazyRepeatProvider(wrapperElement, delegate) {
  145. _classCallCheck(this, LazyRepeatProvider);
  146. if (!(delegate instanceof LazyRepeatDelegate)) {
  147. util.throw('"delegate" parameter must be an instance of LazyRepeatDelegate');
  148. }
  149. this._wrapperElement = wrapperElement;
  150. this._delegate = delegate;
  151. this._insertIndex = this._wrapperElement.children[0] && this._wrapperElement.children[0].tagName === 'ONS-LAZY-REPEAT' ? 1 : 0;
  152. if (wrapperElement.tagName.toLowerCase() === 'ons-list') {
  153. wrapperElement.classList.add('lazy-list');
  154. }
  155. this._pageContent = this._findPageContentElement(wrapperElement);
  156. if (!this._pageContent) {
  157. util.throw('LazyRepeat must be descendant of a Page element');
  158. }
  159. this.lastScrollTop = this._pageContent.scrollTop;
  160. this.padding = 0;
  161. this._topPositions = [0];
  162. this._renderedItems = {};
  163. if (!this._delegate.itemHeight && !this._delegate.calculateItemHeight(0)) {
  164. this._unknownItemHeight = true;
  165. }
  166. this._addEventListeners();
  167. this._onChange();
  168. }
  169. _createClass(LazyRepeatProvider, [{
  170. key: '_findPageContentElement',
  171. value: function _findPageContentElement(wrapperElement) {
  172. var pageContent = util.findParent(wrapperElement, '.page__content');
  173. if (pageContent) {
  174. return pageContent;
  175. }
  176. var page = util.findParent(wrapperElement, 'ons-page');
  177. if (page) {
  178. var content = util.findChild(page, '.content');
  179. if (content) {
  180. return content;
  181. }
  182. }
  183. return null;
  184. }
  185. }, {
  186. key: '_checkItemHeight',
  187. value: function _checkItemHeight(callback) {
  188. var _this = this;
  189. this._delegate.loadItemElement(0, function (item) {
  190. if (!_this._unknownItemHeight) {
  191. util.throw('Invalid state');
  192. }
  193. _this._wrapperElement.appendChild(item.element);
  194. var done = function done() {
  195. _this._delegate.destroyItem(0, item);
  196. item.element && item.element.remove();
  197. delete _this._unknownItemHeight;
  198. callback();
  199. };
  200. _this._itemHeight = item.element.offsetHeight;
  201. if (_this._itemHeight > 0) {
  202. done();
  203. return;
  204. }
  205. // retry to measure offset height
  206. // dirty fix for angular2 directive
  207. _this._wrapperElement.style.visibility = 'hidden';
  208. item.element.style.visibility = 'hidden';
  209. _setImmediate(function () {
  210. _this._itemHeight = item.element.offsetHeight;
  211. if (_this._itemHeight == 0) {
  212. util.throw('Invalid state: "itemHeight" must be greater than zero');
  213. }
  214. _this._wrapperElement.style.visibility = '';
  215. done();
  216. });
  217. });
  218. }
  219. }, {
  220. key: '_countItems',
  221. value: function _countItems() {
  222. return this._delegate.countItems();
  223. }
  224. }, {
  225. key: '_getItemHeight',
  226. value: function _getItemHeight(i) {
  227. // Item is rendered
  228. if (this._renderedItems.hasOwnProperty(i)) {
  229. if (!this._renderedItems[i].hasOwnProperty('height')) {
  230. this._renderedItems[i].height = this._renderedItems[i].element.offsetHeight;
  231. }
  232. return this._renderedItems[i].height;
  233. }
  234. // Item is not rendered, scroll up
  235. if (this._topPositions[i + 1] && this._topPositions[i]) {
  236. return this._topPositions[i + 1] - this._topPositions[i];
  237. }
  238. // Item is not rendered, scroll down
  239. return this.staticItemHeight || this._delegate.calculateItemHeight(i);
  240. }
  241. }, {
  242. key: '_calculateRenderedHeight',
  243. value: function _calculateRenderedHeight() {
  244. var _this2 = this;
  245. return _Object$keys(this._renderedItems).reduce(function (a, b) {
  246. return a + _this2._getItemHeight(+b);
  247. }, 0);
  248. }
  249. }, {
  250. key: '_onChange',
  251. value: function _onChange() {
  252. this._render();
  253. }
  254. }, {
  255. key: '_lastItemRendered',
  256. value: function _lastItemRendered() {
  257. return Math.max.apply(Math, _toConsumableArray(_Object$keys(this._renderedItems)));
  258. }
  259. }, {
  260. key: '_firstItemRendered',
  261. value: function _firstItemRendered() {
  262. return Math.min.apply(Math, _toConsumableArray(_Object$keys(this._renderedItems)));
  263. }
  264. }, {
  265. key: 'refresh',
  266. value: function refresh() {
  267. var forceRender = { forceScrollDown: true };
  268. var firstItemIndex = this._firstItemRendered();
  269. if (util.isInteger(firstItemIndex)) {
  270. this._wrapperElement.style.height = this._topPositions[firstItemIndex] + this._calculateRenderedHeight() + 'px';
  271. this.padding = this._topPositions[firstItemIndex];
  272. forceRender.forceFirstIndex = firstItemIndex;
  273. }
  274. this._removeAllElements();
  275. this._render(forceRender);
  276. this._wrapperElement.style.height = 'inherit';
  277. }
  278. }, {
  279. key: '_render',
  280. value: function _render() {
  281. var _this3 = this;
  282. var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
  283. _ref$forceScrollDown = _ref.forceScrollDown,
  284. forceScrollDown = _ref$forceScrollDown === undefined ? false : _ref$forceScrollDown,
  285. forceFirstIndex = _ref.forceFirstIndex,
  286. forceLastIndex = _ref.forceLastIndex;
  287. if (this._unknownItemHeight) {
  288. return this._checkItemHeight(this._render.bind(this, arguments[0]));
  289. }
  290. var isScrollUp = !forceScrollDown && this.lastScrollTop > this._pageContent.scrollTop;
  291. this.lastScrollTop = this._pageContent.scrollTop;
  292. var keep = {};
  293. var offset = this._wrapperElement.getBoundingClientRect().top;
  294. var limit = 4 * window.innerHeight - offset;
  295. var count = this._countItems();
  296. var items = [];
  297. var start = forceFirstIndex || Math.max(0, this._calculateStartIndex(offset) - 30); // Recalculate for 0 or undefined
  298. var i = start;
  299. for (var top = this._topPositions[i]; i < count && top < limit; i++) {
  300. if (i >= this._topPositions.length) {
  301. // perf optimization
  302. this._topPositions.length += 100;
  303. }
  304. this._topPositions[i] = top;
  305. top += this._getItemHeight(i);
  306. }
  307. if (this._delegate.hasRenderFunction && this._delegate.hasRenderFunction()) {
  308. return this._delegate._render(start, i, function () {
  309. _this3.padding = _this3._topPositions[start];
  310. });
  311. }
  312. if (isScrollUp) {
  313. for (var j = i - 1; j >= start; j--) {
  314. keep[j] = true;
  315. this._renderElement(j, isScrollUp);
  316. }
  317. } else {
  318. var lastIndex = forceLastIndex || Math.max.apply(Math, [i - 1].concat(_toConsumableArray(_Object$keys(this._renderedItems)))); // Recalculate for 0 or undefined
  319. for (var _j = start; _j <= lastIndex; _j++) {
  320. keep[_j] = true;
  321. this._renderElement(_j, isScrollUp);
  322. }
  323. }
  324. _Object$keys(this._renderedItems).forEach(function (key) {
  325. return keep[key] || _this3._removeElement(key, isScrollUp);
  326. });
  327. }
  328. /**
  329. * @param {Number} index
  330. * @param {Boolean} isScrollUp
  331. */
  332. }, {
  333. key: '_renderElement',
  334. value: function _renderElement(index, isScrollUp) {
  335. var _this4 = this;
  336. var item = this._renderedItems[index];
  337. if (item) {
  338. this._delegate.updateItem(index, item); // update if it exists
  339. return;
  340. }
  341. this._delegate.loadItemElement(index, function (item) {
  342. if (isScrollUp) {
  343. _this4._wrapperElement.insertBefore(item.element, _this4._wrapperElement.children[_this4._insertIndex]);
  344. _this4.padding = _this4._topPositions[index];
  345. item.height = _this4._topPositions[index + 1] - _this4._topPositions[index];
  346. } else {
  347. _this4._wrapperElement.appendChild(item.element);
  348. }
  349. _this4._renderedItems[index] = item;
  350. });
  351. }
  352. /**
  353. * @param {Number} index
  354. * @param {Boolean} isScrollUp
  355. */
  356. }, {
  357. key: '_removeElement',
  358. value: function _removeElement(index) {
  359. var isScrollUp = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
  360. index = +index;
  361. var item = this._renderedItems[index];
  362. this._delegate.destroyItem(index, item);
  363. if (isScrollUp) {
  364. this._topPositions[index + 1] = undefined;
  365. } else {
  366. this.padding = this.padding + this._getItemHeight(index);
  367. }
  368. if (item.element.parentElement) {
  369. item.element.parentElement.removeChild(item.element);
  370. }
  371. delete this._renderedItems[index];
  372. }
  373. }, {
  374. key: '_removeAllElements',
  375. value: function _removeAllElements() {
  376. var _this5 = this;
  377. _Object$keys(this._renderedItems).forEach(function (key) {
  378. return _this5._removeElement(key);
  379. });
  380. }
  381. }, {
  382. key: '_recalculateTopPositions',
  383. value: function _recalculateTopPositions(start, end) {
  384. for (var i = start; i <= end; i++) {
  385. this._topPositions[i + 1] = this._topPositions[i] + this._getItemHeight(i);
  386. }
  387. }
  388. }, {
  389. key: '_calculateStartIndex',
  390. value: function _calculateStartIndex(current) {
  391. var firstItemIndex = this._firstItemRendered();
  392. var lastItemIndex = this._lastItemRendered();
  393. // Fix for Safari scroll and Angular 2
  394. this._recalculateTopPositions(firstItemIndex, lastItemIndex);
  395. var start = 0;
  396. var end = this._countItems() - 1;
  397. // Binary search for index at top of screen so we can speed up rendering.
  398. for (;;) {
  399. var middle = Math.floor((start + end) / 2);
  400. var value = current + this._topPositions[middle];
  401. if (end < start) {
  402. return 0;
  403. } else if (value <= 0 && value + this._getItemHeight(middle) > 0) {
  404. return middle;
  405. } else if (isNaN(value) || value >= 0) {
  406. end = middle - 1;
  407. } else {
  408. start = middle + 1;
  409. }
  410. }
  411. }
  412. }, {
  413. key: '_debounce',
  414. value: function _debounce(func, wait, immediate) {
  415. var timeout = void 0;
  416. return function () {
  417. var _this6 = this,
  418. _arguments = arguments;
  419. var callNow = immediate && !timeout;
  420. clearTimeout(timeout);
  421. if (callNow) {
  422. func.apply(this, arguments);
  423. } else {
  424. timeout = setTimeout(function () {
  425. timeout = null;
  426. func.apply(_this6, _arguments);
  427. }, wait);
  428. }
  429. };
  430. }
  431. }, {
  432. key: '_doubleFireOnTouchend',
  433. value: function _doubleFireOnTouchend() {
  434. this._render();
  435. this._debounce(this._render.bind(this), 100);
  436. }
  437. }, {
  438. key: '_addEventListeners',
  439. value: function _addEventListeners() {
  440. util.bindListeners(this, ['_onChange', '_doubleFireOnTouchend']);
  441. if (platform.isIOS()) {
  442. this._boundOnChange = this._debounce(this._boundOnChange, 30);
  443. }
  444. this._pageContent.addEventListener('scroll', this._boundOnChange, true);
  445. if (platform.isIOS()) {
  446. util.addEventListener(this._pageContent, 'touchmove', this._boundOnChange, { capture: true, passive: true });
  447. this._pageContent.addEventListener('touchend', this._boundDoubleFireOnTouchend, true);
  448. }
  449. window.document.addEventListener('resize', this._boundOnChange, true);
  450. }
  451. }, {
  452. key: '_removeEventListeners',
  453. value: function _removeEventListeners() {
  454. this._pageContent.removeEventListener('scroll', this._boundOnChange, true);
  455. if (platform.isIOS()) {
  456. util.removeEventListener(this._pageContent, 'touchmove', this._boundOnChange, { capture: true, passive: true });
  457. this._pageContent.removeEventListener('touchend', this._boundDoubleFireOnTouchend, true);
  458. }
  459. window.document.removeEventListener('resize', this._boundOnChange, true);
  460. }
  461. }, {
  462. key: 'destroy',
  463. value: function destroy() {
  464. this._removeAllElements();
  465. this._delegate.destroy();
  466. this._parentElement = this._delegate = this._renderedItems = null;
  467. this._removeEventListeners();
  468. }
  469. }, {
  470. key: 'padding',
  471. get: function get() {
  472. return parseInt(this._wrapperElement.style.paddingTop, 10);
  473. },
  474. set: function set(newValue) {
  475. this._wrapperElement.style.paddingTop = newValue + 'px';
  476. }
  477. }, {
  478. key: 'staticItemHeight',
  479. get: function get() {
  480. return this._delegate.itemHeight || this._itemHeight;
  481. }
  482. }]);
  483. return LazyRepeatProvider;
  484. }();