UNPKG

comindware.ui

Version:

Comindware Core UI provides the basic components like editors, lists, dropdowns, popups that we so desperately need while creating Marionette-based single-page applications.

198 lines (168 loc) 7.39 kB
/** * Developer: Stepan Burguchev * Date: 7/18/2014 * Copyright: 2009-2016 Comindware® * All Rights Reserved * Published under the MIT license */ 'use strict'; import 'lib'; /** * @name SlidingWindowCollection * @memberof module:core.collections * @class Коллекция-обертка, отображающая указанный интервал родительской Backbone-коллекции (скользящее окно). * @constructor * @extends Backbone.Collection * @param {Object} options Объект опций. * @param {Number} [options.position=0] Изначальное значении позиции окна. * @param {Number} [options.windowSize=0] Изначальное значение размера окна (количество элементов). * */ const SlidingWindowCollection = Backbone.Collection.extend(/** @lends module:core.collections.SlidingWindowCollection.prototype */ { constructor(collection, options) //noinspection JSHint { options = options || {}; this.parentCollection = collection; this.innerCollection = new Backbone.Collection(); //noinspection JSUnresolvedVariable,JSHint options.close_with && this.__bindLifecycle(options.close_with, 'close'); //noinspection JSUnresolvedVariable,JSHint options.destroy_with && this.__bindLifecycle(options.destroy_with, 'destroy'); this.state = { position: options.position || 0, windowSize: options.windowSize || 0 }; this.__rebuildModels(); this.listenTo(collection, 'add', this.__onAdd); this.listenTo(collection, 'remove', this.__onRemove); this.listenTo(collection, 'reset', this.__onReset); this.listenTo(collection, 'sort', this.__onSort); _.each([ 'add', 'remove', 'reset', 'sort' ], function(eventName) { this.listenTo(this.innerCollection, eventName, function() { const args = _.toArray(arguments); args.unshift(eventName); this.trigger.apply(this, args); }); }, this); this.initialize.apply(this, arguments); }, __rebuildModels(options) { options = options || {}; const newModels = this.parentCollection.chain().rest(this.state.position).first(this.state.windowSize).value(); this.innerCollection.reset(newModels, _.extend(options, { silent: true })); this.models = this.innerCollection.models; this.length = this.innerCollection.length; this.trigger('reset', this, _.clone(options)); if (this.models.length !== newModels.length) { throw new Error('SlidingWindowCollection size mismatch: does parent collection have models with duplicated id?'); } }, __buildModelsInternal(list) { for (let i = 0, len = list.length; i < len; i++) { const model = list.at(i); this.models.push(model); model.collection = this; this._byId[model.cid] = model; if (model.id) { this._byId[model.id] = model; } //noinspection JSHint !model.collapsed && model.children && this.__buildModelsInternal(model.children); } this.length = this.models.length; }, __bindLifecycle(view, methodName) { view.on(methodName, _.bind(this.stopListening, this)); }, __onSort(collection, options) { this.__rebuildModels(options); }, __onAdd(model, collection, options) { this.__rebuildModels(options); }, __onRemove(model, collection, options) { this.__rebuildModels(options); }, __onReset(collection, options) { this.__rebuildModels(options); }, sort(options) { this.parentCollection.sort(options); }, /** * Обновить размер скользящего окна * @param {Number} newWindowSize Новый размер скользящего окна * */ updateWindowSize(newWindowSize) { if (this.state.windowSize !== newWindowSize) { this.state.windowSize = newWindowSize; this.__rebuildModels(); } }, /** * Обновить позицию скользящего окна * @param {Number} newPosition Новая позиция скользящего окна * */ updatePosition(newPosition) { if (this.state.windowSize === undefined) { throw 'updatePosition() has been called before setting window size'; } newPosition = this.__normalizePosition(newPosition); if (newPosition === this.state.position) { return newPosition; } const actualWindowSize = this.innerCollection.length; const delta = newPosition - this.state.position; let oldValues; let newValues; if (Math.abs(delta) < actualWindowSize) { // update collection via add/remove if (delta > 0) { oldValues = this.innerCollection.first(delta); this.length -= oldValues.length; this.innerCollection.remove(oldValues); newValues = this.parentCollection.chain().rest(this.state.position + actualWindowSize).first(delta).value(); this.length += newValues.length; this.innerCollection.add(newValues); } else { if (this.length >= this.state.windowSize) { oldValues = this.innerCollection.last(-delta); this.length -= oldValues.length; this.innerCollection.remove(oldValues); } newValues = this.parentCollection.chain().rest(newPosition).first(-delta).value(); this.length += newValues.length; this.innerCollection.add(newValues, { at: 0 }); } this.state.position = newPosition; } else { this.state.position = newPosition; this.__rebuildModels(); } return newPosition; }, __normalizePosition(position) { const maxPos = Math.max(0, this.parentCollection.length - 1); return Math.max(0, Math.min(maxPos, position)); } }); // methods that alter data should proxy to the parent collection _.each(['add', 'remove', 'set', 'reset', 'push', 'pop', 'unshift', 'shift', 'slice', 'sync', 'fetch'], methodName => { SlidingWindowCollection.prototype[methodName] = function() { return this.parentCollection[methodName].apply(this.parentCollection, _.toArray(arguments)); }; }); // methods that retrieves data should proxy to the inner collection _.each(['each', 'at', 'get', 'filter', 'map'], methodName => { SlidingWindowCollection.prototype[methodName] = function() { return this.innerCollection[methodName].apply(this.innerCollection, _.toArray(arguments)); }; }); _.extend(SlidingWindowCollection.prototype, Backbone.Events); export default SlidingWindowCollection;