UNPKG

meteor-sdk

Version:

The aim of this library is to simplify the process of working with meteor server over DDP protocol using external JS environments

360 lines (359 loc) 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ddpReactiveCollection = void 0; const ddpReducer_js_1 = require("./ddpReducer.js"); const ddpReactiveDocument_js_1 = require("./ddpReactiveDocument.js"); const ddpOnChange_js_1 = require("./ddpOnChange.js"); /** * A reactive collection class. * @constructor * @param {ddpCollection} ddpCollection - Instance of @see ddpCollection class. * @param {Object} [settings={skip:0,limit:Infinity,sort:false}] - Object for declarative reactive collection slicing. * @param {Function} [filter=undefined] - Filter function. */ class ddpReactiveCollection { constructor(ddpCollectionInstance, settings, filter) { this._length = { result: 0 }; this._data = []; this._rawData = []; this._reducers = []; this._tickers = []; this._ones = []; this._first = {}; this._skip = settings && typeof settings.skip === 'number' ? settings.skip : 0; this._limit = settings && typeof settings.limit === 'number' ? settings.limit : Infinity; this._sort = settings && typeof settings.sort === 'function' ? settings.sort : false; this._syncFunc = function (skip, limit, sort) { const options = {}; if (typeof skip === 'number') options.skip = skip; if (typeof limit === 'number') options.limit = limit; if (sort) { options.sort = sort; } return ddpCollectionInstance.fetch.call(ddpCollectionInstance, options); }; // @ts-ignore // @ts-ignore this._changeHandler = ddpCollectionInstance.onChange(({ prev, next, predicatePassed }) => { if (prev && next) { if (predicatePassed[0] == 0 && predicatePassed[1] == 1) { // prev falling, next passing filter, adding new element with sort this._smartUpdate(next); } else if (predicatePassed[0] == 1 && predicatePassed[1] == 0) { // prev passing, next falling filter, removing old element let i = this._rawData.findIndex((obj) => { return obj._id == prev._id; }); this._removeItem(i); } else if (predicatePassed[0] == 1 && predicatePassed[1] == 1) { // both passing, should delete previous and add new let i = this._rawData.findIndex((obj) => { return obj._id == prev._id; }); this._smartUpdate(next, i); } } else if (!prev && next) { // element was added and is passing the filter // adding new element with sort this._smartUpdate(next); } else if (prev && !next) { // element was removed and is passing the filter, so it was in newCollection // removing old element let i = this._rawData.findIndex((obj) => { return obj._id == prev._id; }); this._removeItem(i); } this._length.result = this._data.length; this._reducers.forEach((reducer) => { reducer.doReduce(); }); if (this._data[0] !== this._first) { this._updateReactiveObjects(); } this._first = this._data[0]; this._tickers.forEach((ticker) => { ticker(this.data()); }); //@ts-ignore }, filter ? filter : (_) => 1); this.started = false; this.start(); } /** * Removes document from the local collection copies. * @private * @param {number} i - Document index in this._rawData array. */ _removeItem(i) { this._rawData.splice(i, 1); if (i >= this._skip && i < this._skip + this._limit) { this._data.splice(i - this._skip, 1); if (this._rawData.length >= this._skip + this._limit) { this._data.push(this._rawData[this._skip + this._limit - 1]); } } else if (i < this._skip) { this._data.shift(); if (this._rawData.length >= this._skip + this._limit) { this._data.push(this._rawData[this._skip + this._limit - 1]); } } } /** * Adds document to local the collection this._rawData according to used sorting if specified. * @private * @param {Object} newEl - Document to be added to the local collection. * @param j * @return {boolean} - The first element in the collection was changed */ _smartUpdate(newEl, j) { let placement; if (!this._rawData.length) { placement = this._rawData.push(newEl) - 1; if (placement >= this._skip && placement < this._skip + this._limit) { this._data.push(newEl); } return; } if (this._sort) { for (let i = 0; i < this._rawData.length; i++) { if (this._sort(newEl, this._rawData[i]) < 1) { placement = i; if (i == j) { // new position is the the same this._rawData[i] = newEl; if (j >= this._skip && j < this._skip + this._limit) { this._data[j - this._skip] = newEl; } } else { // new position is different // removing old element and adding new this._removeItem(j); this._rawData.splice(i, 0, newEl); if (i >= this._skip && i < this._skip + this._limit) { this._data.splice(i - this._skip, 0, newEl); this._data.splice(this._limit); } } break; } if (i == this._rawData.length - 1) { placement = this._rawData.push(newEl) - 1; if (placement >= this._skip && placement < this._skip + this._limit) { this._data.push(newEl); } break; } } } else { // no sorting, trying to change existing if (typeof j === 'number') { placement = j; this._rawData[j] = newEl; if (j >= this._skip && j < this._skip + this._limit) { this._data[j - this._skip] = newEl; } } else { placement = this._rawData.push(newEl) - 1; if (placement >= this._skip && placement < this._skip + this._limit) { this._data.push(newEl); } } } } /** * Adds reducer. * @private * @param {ddpReducer} reducer - A ddpReducer object that needs to be updated on changes. */ _activateReducer(reducer) { this._reducers.push(reducer); } /** * Adds reactive object. * @private * @param {ddpReactiveDocument} o - A ddpReactiveDocument object that needs to be updated on changes. */ _activateReactiveObject(o) { this._ones.push(o); } /** * Removes reducer. * @private * @param {ddpReducer} reducer - A ddpReducer object that does not need to be updated on changes. */ _deactivateReducer(reducer) { let i = this._reducers.indexOf(reducer); if (i > -1) { this._reducers.splice(i, 1); } } /** * Removes reactive object. * @private * @param {ddpReactiveDocument} o - A ddpReducer object that does not need to be updated on changes. */ _deactivateReactiveObject(o) { let i = this._ones.indexOf(o); if (i > -1) { this._ones.splice(i, 1); } } /** * Sends new object state for every associated reactive object. * @public */ _updateReactiveObjects() { this._ones.forEach((ro) => { ro._update(this.data()[0]); }); } /** * Updates ddpReactiveCollection settings. * @public * @param {Object} [settings={skip:0,limit:Infinity,sort:false}] - Object for declarative reactive collection slicing. * @return {this} */ settings(settings) { let skip, limit, sort; if (settings) { skip = settings.skip; limit = settings.limit; sort = settings.sort; } this._skip = skip !== undefined ? skip : this._skip; this._limit = limit !== undefined ? limit : this._limit; this._sort = sort !== undefined ? sort : this._sort; this._data.splice(0, this._data.length, ...this._syncFunc(this._skip, this._limit, this._sort)); this._updateReactiveObjects(); return this; } /** * Updates the skip parameter only. * @public * @param {number} n - A number of documents to skip. * @return {this} */ skip(n) { return this.settings({ skip: n }); } /** * Updates the limit parameter only. * @public * @param {number} n - A number of documents to observe. * @return {this} */ limit(n) { return this.settings({ limit: n }); } /** * Stops reactivity. Also stops associated reactive objects. * @public */ stop() { if (this.started) { this._changeHandler.stop(); this.started = false; } } /** * Starts reactivity. This method is being called on instance creation. * Also starts every associated reactive object. * @public */ start() { if (!this.started) { this._rawData.splice(0, this._rawData.length, ...this._syncFunc(0, 0, this._sort)); this._data.splice(0, this._data.length, ...this._syncFunc(this._skip, this._limit, this._sort)); this._updateReactiveObjects(); this._changeHandler.start(); this.started = true; } } /** * Sorts local collection according to specified function. * Specified function form {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}. * @public * @param {Function} f - A function used for sorting. * @return {this} */ sort(f) { this._sort = f; if (this._sort) { this._rawData.splice(0, this._rawData.length, ...this._syncFunc(0, 0, this._sort)); this._data.splice(0, this._data.length, ...this._syncFunc(this._skip, this._limit, this._sort)); this._updateReactiveObjects(); } return this; } /** * Returns reactive local collection with applied sorting, skip and limit. * This returned array is being mutated within this class instance. * @public * @return {Array} - Local collection with applied sorting, skip and limit. */ data() { return this._data; } /** * Runs a function every time a change occurs. * @param {Function} f - Function which recieves new collection at each change. * @public */ onChange(f) { const self = this; return (0, ddpOnChange_js_1.ddpOnChange)(f, self, '_tickers'); } /** * Maps reactive local collection to another reactive array. * Specified function form {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map}. * @public * @param {Function} f - Function that produces an element of the new Array. * @return {ddpReducer} - Object that allows to get reactive mapped data @see ddpReducer. */ // @ts-ignore map(f) { return new ddpReducer_js_1.ddpReducer(this, function (accumulator, el, i, a) { return accumulator.concat(f(el, i, a)); }, []); } /** * Reduces reactive local collection. * Specified function form {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce}. * @public * @param {Function} f - Function to execute on each element in the array. * @param {*} initialValue - Value to use as the first argument to the first call of the function. * @return {ddpReducer} - Object that allows to get reactive object based on reduced reactive local collection @see ddpReducer. */ reduce(f, initialValue) { return new ddpReducer_js_1.ddpReducer(this, f, initialValue); } /** * Reactive length of the local collection. * @public * @return {Object} - Object with reactive length of the local collection. {result} */ count() { return this._length; } /** * Returns a reactive object which fields are always the same as the first object in the collection. * @public * @param {Object} [settings={preserve:false}] - Settings for reactive object. Use {preserve:true} if you want to keep object on remove. * @return {ddpReactiveDocument} - Object that allows to get reactive object based on reduced reactive local collection @see ddpReactiveDocument. */ one(settings) { return new ddpReactiveDocument_js_1.ddpReactiveDocument(this, settings); } } exports.ddpReactiveCollection = ddpReactiveCollection;