UNPKG

forerunnerdb

Version:

A NoSQL document store database for browsers and Node.js.

448 lines (399 loc) 12 kB
"use strict"; // TODO: Remove the _update* methods because we are already mixing them // TODO: in now via Mixin.Updating and update autobind to extend the _update* // TODO: methods like we already do with collection var Shared, Collection, Db; Shared = require('./Shared'); /** * Creates a new Document instance. Documents allow you to create individual * objects that can have standard ForerunnerDB CRUD operations run against * them, as well as data-binding if the AutoBind module is included in your * project. * @name Document * @class Document * @constructor */ var FdbDocument = function () { this.init.apply(this, arguments); }; FdbDocument.prototype.init = function (name) { this._name = name; this._data = {}; }; Shared.addModule('Document', FdbDocument); Shared.mixin(FdbDocument.prototype, 'Mixin.Common'); Shared.mixin(FdbDocument.prototype, 'Mixin.Events'); Shared.mixin(FdbDocument.prototype, 'Mixin.ChainReactor'); Shared.mixin(FdbDocument.prototype, 'Mixin.Constants'); Shared.mixin(FdbDocument.prototype, 'Mixin.Triggers'); Shared.mixin(FdbDocument.prototype, 'Mixin.Matching'); Shared.mixin(FdbDocument.prototype, 'Mixin.Updating'); Collection = require('./Collection'); Db = Shared.modules.Db; /** * Gets / sets the current state. * @func state * @memberof Document * @param {String=} val The name of the state to set. * @returns {*} */ Shared.synthesize(FdbDocument.prototype, 'state'); /** * Gets / sets the db instance this class instance belongs to. * @func db * @memberof Document * @param {Db=} db The db instance. * @returns {*} */ Shared.synthesize(FdbDocument.prototype, 'db'); /** * Gets / sets the document name. * @func name * @memberof Document * @param {String=} val The name to assign * @returns {*} */ Shared.synthesize(FdbDocument.prototype, 'name'); /** * Sets the data for the document. * @func setData * @memberof Document * @param data * @param options * @returns {Document} */ FdbDocument.prototype.setData = function (data, options) { var i, $unset; if (data) { options = options || { $decouple: true }; if (options && options.$decouple === true) { data = this.decouple(data); } if (this._linked) { $unset = {}; // Remove keys that don't exist in the new data from the current object for (i in this._data) { if (i.substr(0, 6) !== 'jQuery' && this._data.hasOwnProperty(i)) { // Check if existing data has key if (data[i] === undefined) { // Add property name to those to unset $unset[i] = 1; } } } data.$unset = $unset; // Now update the object with new data this.updateObject(this._data, data, {}); } else { // Straight data assignment this._data = data; } this.deferEmit('change', {type: 'setData', data: this.decouple(this._data)}); } return this; }; /** * Gets the document's data returned as a single object. * @func find * @memberof Document * @param {Object} query The query object - currently unused, just * provide a blank object e.g. {} * @param {Object=} options An options object. * @returns {Object} The document's data object. */ FdbDocument.prototype.find = function (query, options) { var result; if (options && options.$decouple === false) { result = this._data; } else { result = this.decouple(this._data); } return result; }; /** * Modifies the document. This will update the document with the data held in 'update'. * @func update * @memberof Document * @param {Object} query The query that must be matched for a document to be * operated on. * @param {Object} update The object containing updated key/values. Any keys that * match keys on the existing document will be overwritten with this data. Any * keys that do not currently exist on the document will be added to the document. * @param {Object=} options An options object. * @returns {Array} The items that were updated. */ FdbDocument.prototype.update = function (query, update, options) { var result = this.updateObject(this._data, update, query, options); if (result) { this.deferEmit('change', {type: 'update', data: this.decouple(this._data)}); } }; /** * Internal method for document updating. * @func updateObject * @memberof Document * @param {Object} doc The document to update. * @param {Object} update The object with key/value pairs to update the document with. * @param {Object} query The query object that we need to match to perform an update. * @param {Object} options An options object. * @param {String} path The current recursive path. * @param {String} opType The type of update operation to perform, if none is specified * default is to set new data against matching fields. * @returns {Boolean} True if the document was updated with new / changed data or * false if it was not updated because the data was the same. * @private */ FdbDocument.prototype.updateObject = Collection.prototype.updateObject; /** * Determines if the passed key has an array positional mark (a dollar at the end * of its name). * @func _isPositionalKey * @memberof Document * @param {String} key The key to check. * @returns {Boolean} True if it is a positional or false if not. * @private */ FdbDocument.prototype._isPositionalKey = function (key) { return key.substr(key.length - 2, 2) === '.$'; }; /** * Updates a property on an object depending on if the collection is * currently running data-binding or not. * @func _updateProperty * @memberof Document * @param {Object} doc The object whose property is to be updated. * @param {String} prop The property to update. * @param {*} val The new value of the property. * @private */ FdbDocument.prototype._updateProperty = function (doc, prop, val) { if (this._linked) { window.jQuery.observable(doc).setProperty(prop, val); if (this.debug()) { console.log(this.logIdentifier() + ' Setting data-bound document property "' + prop + '"'); } } else { doc[prop] = val; if (this.debug()) { console.log(this.logIdentifier() + ' Setting non-data-bound document property "' + prop + '"'); } } }; /** * Increments a value for a property on a document by the passed number. * @func _updateIncrement * @memberof Document * @param {Object} doc The document to modify. * @param {String} prop The property to modify. * @param {Number} val The amount to increment by. * @private */ FdbDocument.prototype._updateIncrement = function (doc, prop, val) { if (this._linked) { window.jQuery.observable(doc).setProperty(prop, doc[prop] + val); } else { doc[prop] += val; } }; /** * Changes the index of an item in the passed array. * @func _updateSpliceMove * @memberof Document * @param {Array} arr The array to modify. * @param {Number} indexFrom The index to move the item from. * @param {Number} indexTo The index to move the item to. * @private */ FdbDocument.prototype._updateSpliceMove = function (arr, indexFrom, indexTo) { if (this._linked) { window.jQuery.observable(arr).move(indexFrom, indexTo); if (this.debug()) { console.log(this.logIdentifier() + ' Moving data-bound document array index from "' + indexFrom + '" to "' + indexTo + '"'); } } else { arr.splice(indexTo, 0, arr.splice(indexFrom, 1)[0]); if (this.debug()) { console.log(this.logIdentifier() + ' Moving non-data-bound document array index from "' + indexFrom + '" to "' + indexTo + '"'); } } }; /** * Inserts an item into the passed array at the specified index. * @func _updateSplicePush * @memberof Document * @param {Array} arr The array to insert into. * @param {Number} index The index to insert at. * @param {Object} doc The document to insert. * @private */ FdbDocument.prototype._updateSplicePush = function (arr, index, doc) { if (arr.length > index) { if (this._linked) { window.jQuery.observable(arr).insert(index, doc); } else { arr.splice(index, 0, doc); } } else { if (this._linked) { window.jQuery.observable(arr).insert(doc); } else { arr.push(doc); } } }; /** * Inserts an item at the end of an array. * @func _updatePush * @memberof Document * @param {Array} arr The array to insert the item into. * @param {Object} doc The document to insert. * @private */ FdbDocument.prototype._updatePush = function (arr, doc) { if (this._linked) { window.jQuery.observable(arr).insert(doc); } else { arr.push(doc); } }; /** * Removes an item from the passed array. * @func _updatePull * @memberof Document * @param {Array} arr The array to modify. * @param {Number} index The index of the item in the array to remove. * @private */ FdbDocument.prototype._updatePull = function (arr, index) { if (this._linked) { window.jQuery.observable(arr).remove(index); } else { arr.splice(index, 1); } }; /** * Multiplies a value for a property on a document by the passed number. * @func _updateMultiply * @memberof Document * @param {Object} doc The document to modify. * @param {String} prop The property to modify. * @param {Number} val The amount to multiply by. * @private */ FdbDocument.prototype._updateMultiply = function (doc, prop, val) { if (this._linked) { window.jQuery.observable(doc).setProperty(prop, doc[prop] * val); } else { doc[prop] *= val; } }; /** * Renames a property on a document to the passed property. * @func _updateRename * @memberof Document * @param {Object} doc The document to modify. * @param {String} prop The property to rename. * @param {Number} val The new property name. * @private */ FdbDocument.prototype._updateRename = function (doc, prop, val) { var existingVal = doc[prop]; if (this._linked) { window.jQuery.observable(doc).setProperty(val, existingVal); window.jQuery.observable(doc).removeProperty(prop); } else { doc[val] = existingVal; delete doc[prop]; } }; /** * Deletes a property on a document. * @func _updateUnset * @memberof Document * @param {Object} doc The document to modify. * @param {String} prop The property to delete. * @private */ FdbDocument.prototype._updateUnset = function (doc, prop) { if (this._linked) { window.jQuery.observable(doc).removeProperty(prop); } else { delete doc[prop]; } }; /** * Drops the document. * @func drop * @memberof Document * @returns {boolean} True if successful, false if not. */ FdbDocument.prototype.drop = function () { if (!this.isDropped()) { if (this._db && this._name) { if (this._db && this._db._document && this._db._document[this._name]) { this._state = 'dropped'; delete this._db._document[this._name]; delete this._data; this.emit('drop', this); return true; } } } else { return true; } return false; }; /** * Creates a new document instance. * @func document * @memberof Db * @param {String} documentName The name of the document to create. * @returns {*} */ Db.prototype.document = function (documentName) { if (documentName) { // Handle being passed an instance if (documentName instanceof FdbDocument) { if (documentName.state() !== 'droppped') { return documentName; } else { documentName = documentName.name(); } } this._document = this._document || {}; this._document[documentName] = this._document[documentName] || new FdbDocument(documentName).db(this); return this._document[documentName]; } else { // Return an object of document data return this._document; } }; /** * Returns an array of documents the DB currently has. * @func documents * @memberof Db * @returns {Array} An array of objects containing details of each document * the database is currently managing. */ Db.prototype.documents = function () { var arr = [], item, i; for (i in this._document) { if (this._document.hasOwnProperty(i)) { item = this._document[i]; arr.push({ name: i, linked: item.isLinked !== undefined ? item.isLinked() : false }); } } return arr; }; Shared.finishModule('Document'); module.exports = FdbDocument;