UNPKG

backbone-virtualized-listview

Version:
1,427 lines (1,205 loc) 46.5 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("underscore"), require("jquery"), require("backbone"), require("fast-binary-indexed-tree")); else if(typeof define === 'function' && define.amd) define(["underscore", "jquery", "backbone", "fast-binary-indexed-tree"], factory); else if(typeof exports === 'object') exports["backbone-virtualized-listview"] = factory(require("underscore"), require("jquery"), require("backbone"), require("fast-binary-indexed-tree")); else root["backbone-virtualized-listview"] = factory(root["underscore"], root["jquery"], root["backbone"], root["fast-binary-indexed-tree"]); })(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; var _underscore = __webpack_require__(1); var _underscore2 = _interopRequireDefault(_underscore); var _jquery = __webpack_require__(2); var _jquery2 = _interopRequireDefault(_jquery); var _backbone = __webpack_require__(3); var _backbone2 = _interopRequireDefault(_backbone); var _fastBinaryIndexedTree = __webpack_require__(4); var _fastBinaryIndexedTree2 = _interopRequireDefault(_fastBinaryIndexedTree); var _defaultList = __webpack_require__(5); var _defaultList2 = _interopRequireDefault(_defaultList); var _defaultItem = __webpack_require__(8); var _defaultItem2 = _interopRequireDefault(_defaultItem); var _viewport = __webpack_require__(9); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // Helper function to created a scoped while loop var whileTrue = function whileTrue(func) { while (func()) {} }; var INVALIDATION_NONE = 0; var INVALIDATION_ITEMS = 0x1; var INVALIDATION_EVENTS = 0x2; var INVALIDATION_LIST = 0x4; var INVALIDATION_ALL = 0x7; var LIST_VIEW_EVENTS = ['willRedraw', 'didRedraw']; /** * The virtualized list view class. * * In addition to ordinary Backbone View options, the constructor also takes * * __virtualized__: whether or not the virtualization is enabled. * * __viewport__: the option locate the scrollable viewport. It can be * * * Omitted, auto detect the closest ancestor of the `$el` with 'overflowY' * style being 'auto' or 'scroll'. Use the window viewport if found none. * * A `string`, use it as a selector to select an __internal__ element as * the viewport. * * An `HTMLElement` or `jQuery`, use it as the viewport element. * * The `window`, use the window viewport. * * @param {Object} options The constructor options. * @param {boolean} [options.virtualized=true] * @param {string | HTMLElement | jQuery | window} [options.viewport] * */ var ListView = function (_Backbone$View) { _inherits(ListView, _Backbone$View); function ListView() { _classCallCheck(this, ListView); return _possibleConstructorReturn(this, (ListView.__proto__ || Object.getPrototypeOf(ListView)).apply(this, arguments)); } _createClass(ListView, [{ key: 'initialize', /** * Backbone view initializer * @see ListView */ value: function initialize() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$virtualized = _ref.virtualized, virtualized = _ref$virtualized === undefined ? true : _ref$virtualized, _ref$viewport = _ref.viewport, viewport = _ref$viewport === undefined ? null : _ref$viewport; this._props = { virtualized: virtualized, viewport: viewport }; this.options = { model: {}, listTemplate: _defaultList2.default, events: {}, items: [], itemTemplate: _defaultItem2.default, defaultItemHeight: 20 }; // States this._state = { indexFirst: 0, indexLast: 0, anchor: null, invalidation: INVALIDATION_NONE, removed: false, eventsListView: {} }; this._scheduleRedraw = _underscore2.default.noop; } }, { key: '_initViewport', value: function _initViewport() { var viewport = this._props.viewport; if (_underscore2.default.isString(viewport)) { return new _viewport.ElementViewport(this.$(viewport)); } else if (viewport instanceof _jquery2.default) { if (viewport.get(0) === window) { return new _viewport.WindowViewport(); } return new _viewport.ElementViewport(viewport); } else if (viewport instanceof HTMLElement) { return new _viewport.ElementViewport(viewport); } else if (viewport === window) { return new _viewport.WindowViewport(); } var $el = this.$el; while ($el.length > 0 && !$el.is(document)) { if (_underscore2.default.contains(['auto', 'scroll'], $el.css('overflowY'))) { return new _viewport.ElementViewport($el); } $el = $el.parent(); } return new _viewport.WindowViewport(); } }, { key: '_hookUpViewport', value: function _hookUpViewport() { var _this2 = this; this.viewport = this._initViewport(); if (this.virtualized) { (function () { var blockUntil = 0; var onViewportChange = function onViewportChange() { if (performance.now() > blockUntil) { _this2._scheduleRedraw(); } else if (!_this2._state.removed) { // If the scroll events are blocked, we shouldn't just swallow them. // Wait for 0.1 second and give another try. window.setTimeout(onViewportChange, 100); } }; _this2.viewport.on('change', onViewportChange); // // On keypress, we want to block the scroll events for 0.2 second to wait // for the animation to complete. Otherwise, the scroll would change the // geometry metrics and break the animation. The worst thing we may get is, // for 'HOME' and 'END' keys, the view doesn't scroll to the right position. // _this2.viewport.on('keypress', function () { blockUntil = performance.now() + 200; }); })(); } } /** * Whether or not the list view is virtualized */ }, { key: 'remove', /** * Remove the view and unregister the event listeners. */ value: function remove() { this._state.removed = true; if (this.viewport) { this.viewport.remove(); } _get(ListView.prototype.__proto__ || Object.getPrototypeOf(ListView.prototype), 'remove', this).call(this); } }, { key: '_applyPaddings', value: function _applyPaddings(_ref2) { var paddingTop = _ref2.paddingTop, paddingBottom = _ref2.paddingBottom; if (this.$topFiller && this.$bottomFiller) { this.$topFiller.height(paddingTop); this.$bottomFiller.height(paddingBottom); } } }, { key: '_processInvalidation', value: function _processInvalidation() { var _this3 = this; var _options = this.options, items = _options.items, events = _options.events, listTemplate = _options.listTemplate, model = _options.model; var invalidation = this._state.invalidation; var eventsDOM = _underscore2.default.omit(events, LIST_VIEW_EVENTS); var eventsListView = _underscore2.default.pick(events, LIST_VIEW_EVENTS); if (invalidation & INVALIDATION_EVENTS) { this.undelegateEvents(); _underscore2.default.each(this._state.eventsListView || {}, function (handler, event) { _this3.off(event, handler); }); } if (invalidation & INVALIDATION_LIST) { var isInternalViewport = _underscore2.default.isString(this._props.viewport); if (isInternalViewport && this.viewport) { this.viewport.remove(); this.viewport = null; } this.$el.html(listTemplate(model)); if (!this.viewport) { this._hookUpViewport(); } this.$topFiller = this.$('.top-filler'); this.$bottomFiller = this.$('.bottom-filler'); this._applyPaddings({ paddingTop: 0, paddingBottom: this.itemHeights.read(items.length) }); _underscore2.default.extend(this._state, { indexFirst: 0, indexLast: 0 }); } if (invalidation & INVALIDATION_EVENTS) { this.delegateEvents(eventsDOM); _underscore2.default.each(eventsListView, function (handler, event) { _this3.on(event, handler); }); this._state.eventsListView = eventsListView; } var invalidateItems = invalidation & INVALIDATION_ITEMS; _underscore2.default.extend(this._state, { invalidation: INVALIDATION_NONE }); return invalidateItems; } // Private API, redraw immediately }, { key: '_redraw', value: function _redraw() { var _this4 = this; var invalidateItems = this._processInvalidation(); var _options2 = this.options, items = _options2.items, itemTemplate = _options2.itemTemplate; var viewport = this.viewport, itemHeights = this.itemHeights, $topFiller = this.$topFiller, $bottomFiller = this.$bottomFiller, virtualized = this.virtualized; var _state = this._state, indexFirst = _state.indexFirst, indexLast = _state.indexLast, anchor = _state.anchor; if (!invalidateItems && items.length === 0) { return; } /** * The event indicates the list will start redraw. * @event ListView#willRedraw */ this.trigger('willRedraw'); whileTrue(function () { var isCompleted = true; var metricsViewport = viewport.getMetrics(); var visibleTop = metricsViewport.outer.top; var visibleBot = metricsViewport.outer.bottom; var listTopCur = _this4.$topFiller.get(0).getBoundingClientRect().top; var scrollRatio = metricsViewport.scroll.ratioY; var renderTop = false; var renderBot = false; whileTrue(function () { var listTop = anchor ? anchor.top - itemHeights.read(anchor.index) : listTopCur; var targetFirst = virtualized ? itemHeights.lowerBound(visibleTop - listTop) : 0; var targetLast = virtualized ? Math.min(itemHeights.upperBound(visibleBot - listTop) + 1, items.length) : items.length; var renderFirst = Math.max(targetFirst - 10, 0); var renderLast = Math.min(targetLast + 10, items.length); var renderMore = false; // Clean up if (targetFirst >= indexLast || targetLast <= indexFirst || invalidateItems) { $topFiller.nextUntil($bottomFiller).remove(); indexFirst = indexLast = targetFirst; if (targetFirst !== targetLast && items.length > 0) { renderMore = true; } if (!anchor) { var index = Math.round(targetFirst * (1 - scrollRatio) + targetLast * scrollRatio); var top = listTopCur + itemHeights.read(index); anchor = { index: index, top: top }; } invalidateItems = false; } else if (!anchor) { var _index = Math.round(indexFirst * (1 - scrollRatio) + indexLast * scrollRatio); var _top = listTopCur + itemHeights.read(_index); anchor = { index: _index, top: _top }; } // Render top if (targetFirst < indexFirst) { $topFiller.after(items.slice(renderFirst, indexFirst).map(itemTemplate)); $topFiller.nextUntil($bottomFiller).slice(0, indexFirst - renderFirst).each(function (offset, el) { itemHeights.writeSingle(renderFirst + offset, el.getBoundingClientRect().height); }); indexFirst = renderFirst; renderMore = renderTop = true; } else if (renderBot && !renderTop && renderFirst > indexFirst) { (function () { var removal = []; $topFiller.nextUntil($bottomFiller).slice(0, renderFirst - indexFirst).each(function (offset, el) { return removal.push(el); }); (0, _jquery2.default)(removal).remove(); indexFirst = renderFirst; renderMore = true; })(); } // Render bottom if (targetLast > indexLast) { $bottomFiller.before(items.slice(indexLast, renderLast).map(itemTemplate)); $topFiller.nextUntil($bottomFiller).slice(indexLast - indexFirst).each(function (offset, el) { itemHeights.writeSingle(indexLast + offset, el.getBoundingClientRect().height); }); indexLast = renderLast; renderMore = renderBot = true; } else if (renderTop && !renderBot && renderLast < indexLast) { (function () { var removal = []; $topFiller.nextUntil($bottomFiller).slice(renderLast - indexFirst).each(function (offset, el) { return removal.push(el); }); (0, _jquery2.default)(removal).remove(); indexLast = renderLast; renderMore = true; })(); } return renderMore; }); // Update the padding if (indexFirst !== _this4.indexFirst || indexLast !== _this4.indexLast) { _this4._applyPaddings({ paddingTop: itemHeights.read(indexFirst), paddingBottom: itemHeights.read(items.length) - itemHeights.read(indexLast) }); } // Adjust the scroll if it's changed significantly var listTop = anchor.top - itemHeights.read(anchor.index); var innerTop = listTop - (listTopCur - metricsViewport.inner.top); var scrollTop = Math.round(visibleTop - innerTop); var anchorNew = null; // Do a second scroll for a middle anchor after the item is rendered if (anchor.isMiddle) { var index = anchor.index; var itemTop = listTopCur + _this4.itemHeights.read(index); var itemBot = listTopCur + _this4.itemHeights.read(index + 1); anchorNew = { index: index, top: (visibleTop + visibleBot + itemTop - itemBot) / 2 }; isCompleted = false; } if (Math.abs(scrollTop - viewport.getMetrics().scroll.y) >= 1) { _this4.viewport.scrollTo({ y: scrollTop }); isCompleted = false; } anchor = anchorNew; return !isCompleted; }); // Write back the render state _underscore2.default.extend(this._state, { indexFirst: indexFirst, indexLast: indexLast, anchor: null }); /** * The event indicates the list view have completed redraw. * @event ListView#didRedraw */ this.trigger('didRedraw'); } /** * Get the item at certain index. * @param {number} index The index of the item. * @return {Object} */ }, { key: 'itemAt', value: function itemAt(index) { return _underscore2.default.first(this.options.items.slice(index, index + 1)); } /** * Get the rendered DOM element at certain index. * @param {number} index The index of the item. * @return {HTMLElement} */ }, { key: 'elementAt', value: function elementAt(index) { var _state2 = this._state, indexFirst = _state2.indexFirst, indexLast = _state2.indexLast; if (index < indexFirst || index >= indexLast || !this.$topFiller || !this.$bottomFiller) { return null; } return this.$topFiller.nextUntil(this.$bottomFiller).get(index - indexFirst); } /** * The index of the first rendered item. * @type {number} */ }, { key: 'set', /** * Set the list view options. The following options can be set * * __model__: The model object to render the skeleton of the list view. * * __listTemplate__: The template to render the skeleton of the list view. * * * By default, it would render a single `UL`. * * __Note__: It must contain the following elements with specified class * names as the first and last siblings of the list items. All list items * will be rendered in between. * * `'top-filler'`: The filler block on top. * * `'bottom-filler'`: The filler block at bottom. * * __events__: The events hash in form of `{ "event selector": callback }`. * * * Refer to {@link http://backbonejs.org/#View-events|Backbone.View~events} * * In addition to the DOM events, it can also handle the `'willRedraw'` and * `'didRedraw'` events of the list view. * * __Note__: The callback __MUST__ be a function. Member function names are * not supported. * * __items__: The model objects of the list items. * * __itemTemplate__: The template to render a list item. * * * By default, it would render a single `LI` filled with `item.text`. * * __Note__: list items __MUST NOT__ have outer margins, otherwise the layout * calculation will be inaccurate. * * __defaultItemHeight__: The estimated height of a single item. * * * It's not necessary to be accurate. But the accurater it is, the less the * scroll bar is adjusted overtime. * * Refer to {@link ListView} for detail. * * @param {Object} options The new options. * @param {Object} options.model * @param {ListView~cbListTemplate} [options.listTemplate] * @param {Object} options.events * @param {Object[]} [options.items=[]] * @param {ListView~cbItemTemplate} [options.itemTemplate] * @param {number} [options.defaultItemHeight=20] * @param {function} [callback] The callback to notify completion. * @return {ListView} The list view itself. */ value: function set() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _underscore2.default.noop; var isSet = function isSet(key) { return !_underscore2.default.isUndefined(options[key]); }; var itemHeightsCur = this._itemHeights; var invalidation = 0; _underscore2.default.extend(this.options, options); if (_underscore2.default.some(['model', 'listTemplate'], isSet)) { invalidation |= INVALIDATION_ALL; } else { if (_underscore2.default.some(['items', 'itemTemplate', 'defaultItemHeight'], isSet)) { if (isSet('defaultItemHeight') || this.itemHeights.maxVal !== this.length) { this._itemHeights = null; } invalidation |= INVALIDATION_ITEMS; } if (isSet('events')) { invalidation |= INVALIDATION_EVENTS; } } if (invalidation) { if (this.viewport && this.$topFiller && this.$topFiller.length > 0 && itemHeightsCur) { var visibleTop = this.viewport.getMetrics().outer.top; var listTopCur = this.$topFiller.get(0).getBoundingClientRect().top; var visibleFirst = itemHeightsCur.lowerBound(visibleTop - listTopCur); if (visibleFirst < this.length) { var el = this.elementAt(visibleFirst); if (el) { var elTop = el.getBoundingClientRect().top; this._state.anchor = { index: visibleFirst, top: elTop }; } } } this._invalidate(invalidation, callback); } else { callback(); } return this; } }, { key: '_invalidate', value: function _invalidate(invalidation, callback) { this._state.invalidation |= invalidation; this._scheduleRedraw(true); this.once('didRedraw', callback); } /** * Invalidate the already rendered items and schedule another redraw. * @param {function} [callback] The callback to notify completion. */ }, { key: 'invalidate', value: function invalidate() { var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _underscore2.default.noop; this._invalidate(INVALIDATION_ITEMS, callback); } /** * Scroll to a certain item. * @param {number} index The index of the item. * @param {string|number} [position='default'] The position of the item. * * The valid positions are * * `'default'`, if the item is above the viewport top, scroll it to the * top, if the item is below the viewport bottom, scroll it to the bottom, * otherwise, keep the viewport unchanged. * * `'top'`, scroll the item to top of the viewport. * * `'middle'`, scroll the item to the vertical center of the viewport. * * `'bottom'`, scroll the item to the bottom of the viewport. * * `{number}`, scroll the item to the given offset from the viewport top. * * @param {function} [callback] The callback to notify completion. * */ }, { key: 'scrollToItem', value: function scrollToItem() { if (!this.$topFiller || !this.$bottomFiller) { throw new Error('Cannot scroll before the view is rendered'); } var index = 0; var position = 'default'; var callback = _underscore2.default.noop; for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (args.length >= 3) { index = args[0]; position = args[1]; callback = args[2]; } else if (args.length === 2) { if (_underscore2.default.isFunction(args[1])) { index = args[0]; callback = args[1]; } else { index = args[0]; position = args[1]; } } else if (args.length === 1) { index = args[0]; } this._scrollToItem(index, position, callback); } }, { key: '_scrollToItem', value: function _scrollToItem(index, position, callback) { var metricsViewport = this.viewport.getMetrics(); var visibleTop = metricsViewport.outer.top; var visibleBot = metricsViewport.outer.bottom; var listTopCur = this.$topFiller.get(0).getBoundingClientRect().top; var itemTop = listTopCur + this.itemHeights.read(index); var itemBot = listTopCur + this.itemHeights.read(index + 1); var pos = position; if (pos === 'default') { if (itemTop < visibleTop) { pos = 'top'; } else if (itemBot > visibleBot) { pos = 'bottom'; } else { if (_underscore2.default.isFunction(callback)) { callback(); } return; } } if (pos === 'top') { this._state.anchor = { index: index, top: visibleTop }; } else if (pos === 'bottom') { this._state.anchor = { index: index + 1, top: visibleBot }; } else if (pos === 'middle') { this._state.anchor = { index: index, top: (visibleTop + visibleBot + itemTop - itemBot) / 2, isMiddle: true }; } else if (typeof pos === 'number') { this._state.anchor = { index: index, top: visibleTop + pos }; } else { throw new Error('Invalid position'); } this.once('didRedraw', callback); this._scheduleRedraw(); } /** * Render the list view. * @param {function} [callback] The callback to notify completion. */ }, { key: 'render', value: function render() { var _this5 = this; var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _underscore2.default.noop; var animationFrameId = null; var timeoutId = null; var redraw = function redraw() { animationFrameId = null; timeoutId = null; if (!_this5._state.removed) { _this5._redraw(); } }; this._scheduleRedraw = function () { var ignoreAnimationFrame = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!timeoutId) { if (ignoreAnimationFrame) { timeoutId = window.setTimeout(redraw, 0); if (animationFrameId) { window.cancelAnimationFrame(animationFrameId); animationFrameId = null; } } else if (!animationFrameId) { animationFrameId = window.requestAnimationFrame(redraw); } } }; // this._hookUpViewport(); this._invalidate(INVALIDATION_ALL, callback); return this; } }, { key: 'virtualized', get: function get() { return this._props.virtualized; } }, { key: 'indexFirst', get: function get() { return this._state.indexFirst; } /** * The index after the last rendered item. * @type {number} */ }, { key: 'indexLast', get: function get() { return this._state.indexLast; } /** * The total count of the items. * @type {number} */ }, { key: 'length', get: function get() { return this.options.items.length; } /** * The model object to render the skeleton of the list view. * @type {Object} */ }, { key: 'model', get: function get() { return this.options.model; } /** * The template to render the skeleton of the list view. * @callback ListView~cbListTemplate * @param {Object} model The model object of the list view. */ /** * The template to render the skeleton of the list view. * @type {ListView~cbListTemplate} */ }, { key: 'listTemplate', get: function get() { return this.options.listTemplate; } /** * The template to render a list item. * @callback ListView~cbItemTemplate * @param {Object} item The model object of the item */ /** * The template to render a list item. * @type {ListView~cbItemTemplate} */ }, { key: 'itemTemplate', get: function get() { return this.options.itemTemplate; } /** * The default list item height. * @type {number} */ }, { key: 'defaultItemHeight', get: function get() { return this.options.defaultItemHeight; } /** * @external BinaryIndexedTree * @see {@link https://microsoft.github.io/fast-binary-indexed-tree-js/BinaryIndexedTree.html} */ /** * The BinaryIndexedTree to get the heights and accumulated heights of items. * @type {external:BinaryIndexedTree} */ }, { key: 'itemHeights', get: function get() { if (!this._itemHeights) { var _options3 = this.options, defaultItemHeight = _options3.defaultItemHeight, items = _options3.items; this._itemHeights = new _fastBinaryIndexedTree2.default({ defaultFrequency: Math.max(defaultItemHeight, 1), maxVal: items.length }); } return this._itemHeights; } }]); return ListView; }(_backbone2.default.View); exports.default = ListView; /***/ }, /* 1 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_1__; /***/ }, /* 2 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_2__; /***/ }, /* 3 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_3__; /***/ }, /* 4 */ /***/ function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }, /* 5 */ /***/ function(module, exports, __webpack_require__) { var jade = __webpack_require__(6); module.exports = function template(locals) { var buf = []; var jade_mixins = {}; var jade_interp; buf.push("<ul class=\"list-container\"><div class=\"top-filler\"></div><div class=\"bottom-filler\"></div></ul>");;return buf.join(""); } /***/ }, /* 6 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; /** * Merge two attribute objects giving precedence * to values in object `b`. Classes are special-cased * allowing for arrays and merging/joining appropriately * resulting in a string. * * @param {Object} a * @param {Object} b * @return {Object} a * @api private */ exports.merge = function merge(a, b) { if (arguments.length === 1) { var attrs = a[0]; for (var i = 1; i < a.length; i++) { attrs = merge(attrs, a[i]); } return attrs; } var ac = a['class']; var bc = b['class']; if (ac || bc) { ac = ac || []; bc = bc || []; if (!Array.isArray(ac)) ac = [ac]; if (!Array.isArray(bc)) bc = [bc]; a['class'] = ac.concat(bc).filter(nulls); } for (var key in b) { if (key != 'class') { a[key] = b[key]; } } return a; }; /** * Filter null `val`s. * * @param {*} val * @return {Boolean} * @api private */ function nulls(val) { return val != null && val !== ''; } /** * join array as classes. * * @param {*} val * @return {String} */ exports.joinClasses = joinClasses; function joinClasses(val) { return (Array.isArray(val) ? val.map(joinClasses) : (val && typeof val === 'object') ? Object.keys(val).filter(function (key) { return val[key]; }) : [val]).filter(nulls).join(' '); } /** * Render the given classes. * * @param {Array} classes * @param {Array.<Boolean>} escaped * @return {String} */ exports.cls = function cls(classes, escaped) { var buf = []; for (var i = 0; i < classes.length; i++) { if (escaped && escaped[i]) { buf.push(exports.escape(joinClasses([classes[i]]))); } else { buf.push(joinClasses(classes[i])); } } var text = joinClasses(buf); if (text.length) { return ' class="' + text + '"'; } else { return ''; } }; exports.style = function (val) { if (val && typeof val === 'object') { return Object.keys(val).map(function (style) { return style + ':' + val[style]; }).join(';'); } else { return val; } }; /** * Render the given attribute. * * @param {String} key * @param {String} val * @param {Boolean} escaped * @param {Boolean} terse * @return {String} */ exports.attr = function attr(key, val, escaped, terse) { if (key === 'style') { val = exports.style(val); } if ('boolean' == typeof val || null == val) { if (val) { return ' ' + (terse ? key : key + '="' + key + '"'); } else { return ''; } } else if (0 == key.indexOf('data') && 'string' != typeof val) { if (JSON.stringify(val).indexOf('&') !== -1) { console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' + 'will be escaped to `&amp;`'); }; if (val && typeof val.toISOString === 'function') { console.warn('Jade will eliminate the double quotes around dates in ' + 'ISO form after 2.0.0'); } return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, '&apos;') + "'"; } else if (escaped) { if (val && typeof val.toISOString === 'function') { console.warn('Jade will stringify dates in ISO form after 2.0.0'); } return ' ' + key + '="' + exports.escape(val) + '"'; } else { if (val && typeof val.toISOString === 'function') { console.warn('Jade will stringify dates in ISO form after 2.0.0'); } return ' ' + key + '="' + val + '"'; } }; /** * Render the given attributes object. * * @param {Object} obj * @param {Object} escaped * @return {String} */ exports.attrs = function attrs(obj, terse){ var buf = []; var keys = Object.keys(obj); if (keys.length) { for (var i = 0; i < keys.length; ++i) { var key = keys[i] , val = obj[key]; if ('class' == key) { if (val = joinClasses(val)) { buf.push(' ' + key + '="' + val + '"'); } } else { buf.push(exports.attr(key, val, false, terse)); } } } return buf.join(''); }; /** * Escape the given string of `html`. * * @param {String} html * @return {String} * @api private */ var jade_encode_html_rules = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }; var jade_match_html = /[&<>"]/g; function jade_encode_char(c) { return jade_encode_html_rules[c] || c; } exports.escape = jade_escape; function jade_escape(html){ var result = String(html).replace(jade_match_html, jade_encode_char); if (result === '' + html) return html; else return result; }; /** * Re-throw the given `err` in context to the * the jade in `filename` at the given `lineno`. * * @param {Error} err * @param {String} filename * @param {String} lineno * @api private */ exports.rethrow = function rethrow(err, filename, lineno, str){ if (!(err instanceof Error)) throw err; if ((typeof window != 'undefined' || !filename) && !str) { err.message += ' on line ' + lineno; throw err; } try { str = str || __webpack_require__(7).readFileSync(filename, 'utf8') } catch (ex) { rethrow(err, null, lineno) } var context = 3 , lines = str.split('\n') , start = Math.max(lineno - context, 0) , end = Math.min(lines.length, lineno + context); // Error context var context = lines.slice(start, end).map(function(line, i){ var curr = i + start + 1; return (curr == lineno ? ' > ' : ' ') + curr + '| ' + line; }).join('\n'); // Alter exception message err.path = filename; err.message = (filename || 'Jade') + ':' + lineno + '\n' + context + '\n\n' + err.message; throw err; }; exports.DebugItem = function DebugItem(lineno, filename) { this.lineno = lineno; this.filename = filename; } /***/ }, /* 7 */ /***/ function(module, exports) { /* (ignored) */ /***/ }, /* 8 */ /***/ function(module, exports, __webpack_require__) { var jade = __webpack_require__(6); module.exports = function template(locals) { var buf = []; var jade_mixins = {}; var jade_interp; ;var locals_for_with = (locals || {});(function (text) { buf.push("<li>" + (jade.escape(null == (jade_interp = text) ? "" : jade_interp)) + "</li>");}.call(this,"text" in locals_for_with?locals_for_with.text:typeof text!=="undefined"?text:undefined));;return buf.join(""); } /***/ }, /* 9 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.ElementViewport = exports.WindowViewport = exports.Viewport = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _backbone = __webpack_require__(3); var _backbone2 = _interopRequireDefault(_backbone); var _jquery = __webpack_require__(2); var _jquery2 = _interopRequireDefault(_jquery); var _underscore = __webpack_require__(1); var _underscore2 = _interopRequireDefault(_underscore); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function getElementMetrics(el) { return _underscore2.default.pick(el.getBoundingClientRect(), ['left', 'top', 'right', 'bottom', 'width', 'height']); } function calculateRatio(scroll, scrollMax) { return scrollMax > 0 ? Math.min(Math.max(scroll / scrollMax, 0), 1) : 0; } var Viewport = exports.Viewport = function () { function Viewport($el) { var _this = this; _classCallCheck(this, Viewport); _underscore2.default.extend(this, _backbone2.default.Events); this.$el = $el; this.onScroll = function () { _this.trigger('scroll'); _this.trigger('change'); }; this.onResize = function () { _this.trigger('resize'); _this.trigger('change'); }; var keyCode = null; var timestamp = performance.now(); this.onKeydown = function (event) { // Consolidate the keydown events for the same key in 0.2 seconds if (keyCode !== event.keyCode || performance.now() > timestamp + 200) { keyCode = event.keyCode; timestamp = performance.now(); _this.trigger('keypress', keyCode); } }; this.onKeyup = function () { keyCode = null; }; this.$el.on('resize', this.onResize); this.$el.on('scroll', this.onScroll); (0, _jquery2.default)(document).on('keydown', this.onKeydown); (0, _jquery2.default)(document).on('keyup', this.onKeyup); this.scrollTo = function (scrollNew) { if (_underscore2.default.isNumber(scrollNew.x)) { _this.$el.scrollLeft(scrollNew.x); } if (_underscore2.default.isNumber(scrollNew.y)) { _this.$el.scrollTop(scrollNew.y); } }; } _createClass(Viewport, [{ key: 'remove', value: function remove() { this.$el.off('resize', this.onResize); this.$el.off('scroll', this.onScroll); (0, _jquery2.default)(document).off('keydown', this.onKeydown); (0, _jquery2.default)(document).off('keyup', this.onKeyup); } }, { key: 'getMetrics', value: function getMetrics() { throw new Error('Not implemented'); } }]); return Viewport; }(); var WindowViewport = exports.WindowViewport = function (_Viewport) { _inherits(WindowViewport, _Viewport); function WindowViewport() { _classCallCheck(this, WindowViewport); return _possibleConstructorReturn(this, (WindowViewport.__proto__ || Object.getPrototypeOf(WindowViewport)).call(this, (0, _jquery2.default)(window))); } _createClass(WindowViewport, [{ key: 'getMetrics', value: function getMetrics() { var inner = getElementMetrics(document.documentElement); inner.width = document.documentElement.scrollWidth; inner.height = document.documentElement.scrollHeight; inner.right = inner.left + inner.width; inner.bottom = inner.top + inner.height; var outer = { top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth, width: window.innerWidth, height: window.innerHeight }; var scroll = { x: window.pageXOffset, y: window.pageYOffset }; scroll.ratioX = calculateRatio(scroll.x, inner.width - outer.width); scroll.ratioY = calculateRatio(scroll.y, inner.height - outer.height); return { inner: inner, outer: outer, scroll: scroll }; } }]); return WindowViewport; }(Viewport); var SCROLLABLE = ['auto', 'scroll']; var ElementViewport = exports.ElementViewport = function (_Viewport2) { _inherits(ElementViewport, _Viewport2); function ElementViewport(el) { _classCallCheck(this, ElementViewport); var _this3 = _possibleConstructorReturn(this, (ElementViewport.__proto__ || Object.getPrototypeOf(ElementViewport)).call(this, (0, _jquery2.default)(el))); _this3.el = _this3.$el.get(0); _this3.$el.css('overflowX', function (s) { return _underscore2.default.contains(SCROLLABLE, s) ? s : 'auto'; }); _this3.$el.css('overflowY', function (s) { return _underscore2.default.contains(SCROLLABLE, s) ? s : 'auto'; }); return _this3; } _createClass(ElementViewport, [{ key: 'getMetrics', value: function getMetrics() { var outer = getElementMetrics(this.el); var scroll = { x: this.el.scrollLeft, y: this.el.scrollTop }; var inner = { left: outer.left - scroll.x, top: outer.top - scroll.y, width: this.el.scrollWidth, height: this.el.scrollHeight }; inner.right = inner.left + inner.width; inner.bottom = inner.top + inner.height; scroll.ratioX = calculateRatio(scroll.x, inner.width - outer.width); scroll.ratioY = calculateRatio(scroll.y, inner.height - outer.height); return { outer: outer, inner: inner, scroll: scroll }; } }]); return ElementViewport; }(Viewport); /***/ } /******/ ]) }); ; //# sourceMappingURL=backbone-virtualized-listview.js.map