123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575 |
- import _toConsumableArray from 'babel-runtime/helpers/toConsumableArray';
- import _Object$keys from 'babel-runtime/core-js/object/keys';
- import _setImmediate from 'babel-runtime/core-js/set-immediate';
- import _typeof from 'babel-runtime/helpers/typeof';
- import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
- import _createClass from 'babel-runtime/helpers/createClass';
- /*
- Copyright 2013-2015 ASIAL CORPORATION
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- */
-
- import util from '../util';
- import platform from '../platform';
-
- export var LazyRepeatDelegate = function () {
- function LazyRepeatDelegate(userDelegate) {
- var templateElement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-
- _classCallCheck(this, LazyRepeatDelegate);
-
- if ((typeof userDelegate === 'undefined' ? 'undefined' : _typeof(userDelegate)) !== 'object' || userDelegate === null) {
- util.throw('"delegate" parameter must be an object');
- }
- this._userDelegate = userDelegate;
-
- if (!(templateElement instanceof Element) && templateElement !== null) {
- util.throw('"templateElement" parameter must be an instance of Element or null');
- }
- this._templateElement = templateElement;
- }
-
- _createClass(LazyRepeatDelegate, [{
- key: 'hasRenderFunction',
-
-
- /**
- * @return {Boolean}
- */
- value: function hasRenderFunction() {
- return this._userDelegate._render instanceof Function;
- }
-
- /**
- * @return {void}
- */
-
- }, {
- key: '_render',
- value: function _render() {
- this._userDelegate._render.apply(this._userDelegate, arguments);
- }
-
- /**
- * @param {Number} index
- * @param {Function} done A function that take item object as parameter.
- */
-
- }, {
- key: 'loadItemElement',
- value: function loadItemElement(index, done) {
- if (this._userDelegate.loadItemElement instanceof Function) {
- this._userDelegate.loadItemElement(index, done);
- } else {
- var element = this._userDelegate.createItemContent(index, this._templateElement);
- if (!(element instanceof Element)) {
- util.throw('"createItemContent" must return an instance of Element');
- }
-
- done({ element: element });
- }
- }
-
- /**
- * @return {Number}
- */
-
- }, {
- key: 'countItems',
- value: function countItems() {
- var count = this._userDelegate.countItems();
- if (typeof count !== 'number') {
- util.throw('"countItems" must return a number');
- }
- return count;
- }
-
- /**
- * @param {Number} index
- * @param {Object} item
- * @param {Element} item.element
- */
-
- }, {
- key: 'updateItem',
- value: function updateItem(index, item) {
- if (this._userDelegate.updateItemContent instanceof Function) {
- this._userDelegate.updateItemContent(index, item);
- }
- }
-
- /**
- * @return {Number}
- */
-
- }, {
- key: 'calculateItemHeight',
- value: function calculateItemHeight(index) {
- if (this._userDelegate.calculateItemHeight instanceof Function) {
- var height = this._userDelegate.calculateItemHeight(index);
-
- if (typeof height !== 'number') {
- util.throw('"calculateItemHeight" must return a number');
- }
-
- return height;
- }
-
- return 0;
- }
-
- /**
- * @param {Number} index
- * @param {Object} item
- */
-
- }, {
- key: 'destroyItem',
- value: function destroyItem(index, item) {
- if (this._userDelegate.destroyItem instanceof Function) {
- this._userDelegate.destroyItem(index, item);
- }
- }
-
- /**
- * @return {void}
- */
-
- }, {
- key: 'destroy',
- value: function destroy() {
- if (this._userDelegate.destroy instanceof Function) {
- this._userDelegate.destroy();
- }
-
- this._userDelegate = this._templateElement = null;
- }
- }, {
- key: 'itemHeight',
- get: function get() {
- return this._userDelegate.itemHeight;
- }
- }]);
-
- return LazyRepeatDelegate;
- }();
-
- /**
- * This class provide core functions for ons-lazy-repeat.
- */
- export var LazyRepeatProvider = function () {
-
- /**
- * @param {Element} wrapperElement
- * @param {LazyRepeatDelegate} delegate
- */
- function LazyRepeatProvider(wrapperElement, delegate) {
- _classCallCheck(this, LazyRepeatProvider);
-
- if (!(delegate instanceof LazyRepeatDelegate)) {
- util.throw('"delegate" parameter must be an instance of LazyRepeatDelegate');
- }
-
- this._wrapperElement = wrapperElement;
- this._delegate = delegate;
- this._insertIndex = this._wrapperElement.children[0] && this._wrapperElement.children[0].tagName === 'ONS-LAZY-REPEAT' ? 1 : 0;
-
- if (wrapperElement.tagName.toLowerCase() === 'ons-list') {
- wrapperElement.classList.add('lazy-list');
- }
-
- this._pageContent = this._findPageContentElement(wrapperElement);
-
- if (!this._pageContent) {
- util.throw('LazyRepeat must be descendant of a Page element');
- }
-
- this.lastScrollTop = this._pageContent.scrollTop;
- this.padding = 0;
- this._topPositions = [0];
- this._renderedItems = {};
-
- if (!this._delegate.itemHeight && !this._delegate.calculateItemHeight(0)) {
- this._unknownItemHeight = true;
- }
-
- this._addEventListeners();
- this._onChange();
- }
-
- _createClass(LazyRepeatProvider, [{
- key: '_findPageContentElement',
- value: function _findPageContentElement(wrapperElement) {
- var pageContent = util.findParent(wrapperElement, '.page__content');
-
- if (pageContent) {
- return pageContent;
- }
-
- var page = util.findParent(wrapperElement, 'ons-page');
- if (page) {
- var content = util.findChild(page, '.content');
- if (content) {
- return content;
- }
- }
-
- return null;
- }
- }, {
- key: '_checkItemHeight',
- value: function _checkItemHeight(callback) {
- var _this = this;
-
- this._delegate.loadItemElement(0, function (item) {
- if (!_this._unknownItemHeight) {
- util.throw('Invalid state');
- }
-
- _this._wrapperElement.appendChild(item.element);
-
- var done = function done() {
- _this._delegate.destroyItem(0, item);
- item.element && item.element.remove();
- delete _this._unknownItemHeight;
- callback();
- };
-
- _this._itemHeight = item.element.offsetHeight;
-
- if (_this._itemHeight > 0) {
- done();
- return;
- }
-
- // retry to measure offset height
- // dirty fix for angular2 directive
- _this._wrapperElement.style.visibility = 'hidden';
- item.element.style.visibility = 'hidden';
-
- _setImmediate(function () {
- _this._itemHeight = item.element.offsetHeight;
- if (_this._itemHeight == 0) {
- util.throw('Invalid state: "itemHeight" must be greater than zero');
- }
- _this._wrapperElement.style.visibility = '';
- done();
- });
- });
- }
- }, {
- key: '_countItems',
- value: function _countItems() {
- return this._delegate.countItems();
- }
- }, {
- key: '_getItemHeight',
- value: function _getItemHeight(i) {
- // Item is rendered
- if (this._renderedItems.hasOwnProperty(i)) {
- if (!this._renderedItems[i].hasOwnProperty('height')) {
- this._renderedItems[i].height = this._renderedItems[i].element.offsetHeight;
- }
- return this._renderedItems[i].height;
- }
-
- // Item is not rendered, scroll up
- if (this._topPositions[i + 1] && this._topPositions[i]) {
- return this._topPositions[i + 1] - this._topPositions[i];
- }
- // Item is not rendered, scroll down
- return this.staticItemHeight || this._delegate.calculateItemHeight(i);
- }
- }, {
- key: '_calculateRenderedHeight',
- value: function _calculateRenderedHeight() {
- var _this2 = this;
-
- return _Object$keys(this._renderedItems).reduce(function (a, b) {
- return a + _this2._getItemHeight(+b);
- }, 0);
- }
- }, {
- key: '_onChange',
- value: function _onChange() {
- this._render();
- }
- }, {
- key: '_lastItemRendered',
- value: function _lastItemRendered() {
- return Math.max.apply(Math, _toConsumableArray(_Object$keys(this._renderedItems)));
- }
- }, {
- key: '_firstItemRendered',
- value: function _firstItemRendered() {
- return Math.min.apply(Math, _toConsumableArray(_Object$keys(this._renderedItems)));
- }
- }, {
- key: 'refresh',
- value: function refresh() {
- var forceRender = { forceScrollDown: true };
- var firstItemIndex = this._firstItemRendered();
-
- if (util.isInteger(firstItemIndex)) {
- this._wrapperElement.style.height = this._topPositions[firstItemIndex] + this._calculateRenderedHeight() + 'px';
- this.padding = this._topPositions[firstItemIndex];
- forceRender.forceFirstIndex = firstItemIndex;
- }
-
- this._removeAllElements();
- this._render(forceRender);
- this._wrapperElement.style.height = 'inherit';
- }
- }, {
- key: '_render',
- value: function _render() {
- var _this3 = this;
-
- var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
- _ref$forceScrollDown = _ref.forceScrollDown,
- forceScrollDown = _ref$forceScrollDown === undefined ? false : _ref$forceScrollDown,
- forceFirstIndex = _ref.forceFirstIndex,
- forceLastIndex = _ref.forceLastIndex;
-
- if (this._unknownItemHeight) {
- return this._checkItemHeight(this._render.bind(this, arguments[0]));
- }
-
- var isScrollUp = !forceScrollDown && this.lastScrollTop > this._pageContent.scrollTop;
- this.lastScrollTop = this._pageContent.scrollTop;
- var keep = {};
-
- var offset = this._wrapperElement.getBoundingClientRect().top;
- var limit = 4 * window.innerHeight - offset;
- var count = this._countItems();
-
- var items = [];
- var start = forceFirstIndex || Math.max(0, this._calculateStartIndex(offset) - 30); // Recalculate for 0 or undefined
- var i = start;
-
- for (var top = this._topPositions[i]; i < count && top < limit; i++) {
- if (i >= this._topPositions.length) {
- // perf optimization
- this._topPositions.length += 100;
- }
-
- this._topPositions[i] = top;
- top += this._getItemHeight(i);
- }
-
- if (this._delegate.hasRenderFunction && this._delegate.hasRenderFunction()) {
- return this._delegate._render(start, i, function () {
- _this3.padding = _this3._topPositions[start];
- });
- }
-
- if (isScrollUp) {
- for (var j = i - 1; j >= start; j--) {
- keep[j] = true;
- this._renderElement(j, isScrollUp);
- }
- } else {
- var lastIndex = forceLastIndex || Math.max.apply(Math, [i - 1].concat(_toConsumableArray(_Object$keys(this._renderedItems)))); // Recalculate for 0 or undefined
- for (var _j = start; _j <= lastIndex; _j++) {
- keep[_j] = true;
- this._renderElement(_j, isScrollUp);
- }
- }
-
- _Object$keys(this._renderedItems).forEach(function (key) {
- return keep[key] || _this3._removeElement(key, isScrollUp);
- });
- }
-
- /**
- * @param {Number} index
- * @param {Boolean} isScrollUp
- */
-
- }, {
- key: '_renderElement',
- value: function _renderElement(index, isScrollUp) {
- var _this4 = this;
-
- var item = this._renderedItems[index];
- if (item) {
- this._delegate.updateItem(index, item); // update if it exists
- return;
- }
-
- this._delegate.loadItemElement(index, function (item) {
- if (isScrollUp) {
- _this4._wrapperElement.insertBefore(item.element, _this4._wrapperElement.children[_this4._insertIndex]);
- _this4.padding = _this4._topPositions[index];
- item.height = _this4._topPositions[index + 1] - _this4._topPositions[index];
- } else {
- _this4._wrapperElement.appendChild(item.element);
- }
-
- _this4._renderedItems[index] = item;
- });
- }
-
- /**
- * @param {Number} index
- * @param {Boolean} isScrollUp
- */
-
- }, {
- key: '_removeElement',
- value: function _removeElement(index) {
- var isScrollUp = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
-
- index = +index;
- var item = this._renderedItems[index];
- this._delegate.destroyItem(index, item);
-
- if (isScrollUp) {
- this._topPositions[index + 1] = undefined;
- } else {
- this.padding = this.padding + this._getItemHeight(index);
- }
-
- if (item.element.parentElement) {
- item.element.parentElement.removeChild(item.element);
- }
-
- delete this._renderedItems[index];
- }
- }, {
- key: '_removeAllElements',
- value: function _removeAllElements() {
- var _this5 = this;
-
- _Object$keys(this._renderedItems).forEach(function (key) {
- return _this5._removeElement(key);
- });
- }
- }, {
- key: '_recalculateTopPositions',
- value: function _recalculateTopPositions(start, end) {
- for (var i = start; i <= end; i++) {
- this._topPositions[i + 1] = this._topPositions[i] + this._getItemHeight(i);
- }
- }
- }, {
- key: '_calculateStartIndex',
- value: function _calculateStartIndex(current) {
- var firstItemIndex = this._firstItemRendered();
- var lastItemIndex = this._lastItemRendered();
-
- // Fix for Safari scroll and Angular 2
- this._recalculateTopPositions(firstItemIndex, lastItemIndex);
-
- var start = 0;
- var end = this._countItems() - 1;
-
- // Binary search for index at top of screen so we can speed up rendering.
- for (;;) {
- var middle = Math.floor((start + end) / 2);
- var value = current + this._topPositions[middle];
-
- if (end < start) {
- return 0;
- } else if (value <= 0 && value + this._getItemHeight(middle) > 0) {
- return middle;
- } else if (isNaN(value) || value >= 0) {
- end = middle - 1;
- } else {
- start = middle + 1;
- }
- }
- }
- }, {
- key: '_debounce',
- value: function _debounce(func, wait, immediate) {
- var timeout = void 0;
- return function () {
- var _this6 = this,
- _arguments = arguments;
-
- var callNow = immediate && !timeout;
- clearTimeout(timeout);
- if (callNow) {
- func.apply(this, arguments);
- } else {
- timeout = setTimeout(function () {
- timeout = null;
- func.apply(_this6, _arguments);
- }, wait);
- }
- };
- }
- }, {
- key: '_doubleFireOnTouchend',
- value: function _doubleFireOnTouchend() {
- this._render();
- this._debounce(this._render.bind(this), 100);
- }
- }, {
- key: '_addEventListeners',
- value: function _addEventListeners() {
- util.bindListeners(this, ['_onChange', '_doubleFireOnTouchend']);
-
- if (platform.isIOS()) {
- this._boundOnChange = this._debounce(this._boundOnChange, 30);
- }
-
- this._pageContent.addEventListener('scroll', this._boundOnChange, true);
-
- if (platform.isIOS()) {
- util.addEventListener(this._pageContent, 'touchmove', this._boundOnChange, { capture: true, passive: true });
- this._pageContent.addEventListener('touchend', this._boundDoubleFireOnTouchend, true);
- }
-
- window.document.addEventListener('resize', this._boundOnChange, true);
- }
- }, {
- key: '_removeEventListeners',
- value: function _removeEventListeners() {
- this._pageContent.removeEventListener('scroll', this._boundOnChange, true);
-
- if (platform.isIOS()) {
- util.removeEventListener(this._pageContent, 'touchmove', this._boundOnChange, { capture: true, passive: true });
- this._pageContent.removeEventListener('touchend', this._boundDoubleFireOnTouchend, true);
- }
-
- window.document.removeEventListener('resize', this._boundOnChange, true);
- }
- }, {
- key: 'destroy',
- value: function destroy() {
- this._removeAllElements();
- this._delegate.destroy();
- this._parentElement = this._delegate = this._renderedItems = null;
- this._removeEventListeners();
- }
- }, {
- key: 'padding',
- get: function get() {
- return parseInt(this._wrapperElement.style.paddingTop, 10);
- },
- set: function set(newValue) {
- this._wrapperElement.style.paddingTop = newValue + 'px';
- }
- }, {
- key: 'staticItemHeight',
- get: function get() {
- return this._delegate.itemHeight || this._itemHeight;
- }
- }]);
-
- return LazyRepeatProvider;
- }();
|