UNPKG

@transmutable/bink

Version:

A sleek and reactive framework for web pages

218 lines (195 loc) 5.67 kB
import DataObject from './DataObject.js' /** Holds a map of <string, value> pairs, sometimes fetched from or sent to a back-end server. @example <caption>Directly loading data</caption> this.model = new DataModel({ id: 42, something: 'different' }) this.model.get('id') // returns 42 this.model.get('something') // returns 'different' this.model.get('bogus', 'default') // returns 'default' @example <caption>Fetching data from a service</caption> * class ExampleModel extends DataModel { * get url() { return '/api/example' } * } * this.model = new ExampleModel() * this.model.fetch().then(() => { ... }).catch(err => { ... }) */ const DataModel = class extends DataObject { /** @param {Object} [data={}] @param {Object} [options={}] @param {Object} [options.fieldDataObjects=null] a map of dataField (string) to DataObject (class), used to create sub-objects in this Model's data */ constructor(data = {}, options = {}) { super(options) if (typeof this.options.fieldDataObjects === 'undefined') { this.options.fieldDataObjects = {} } /** @type {Object<string,*>} */ this._data = {} /** @type {DataCollection|null} */ this.collection = null // set or unset by a DataCollection that contains this model this.setBatch(data) } /** See {@link DataObject.cleanup} for details. */ cleanup() { super.cleanup() // TODO - need to clean up any sub-DataObjects this._data = null } /** @param {string} dataField @return {boolean} true if this model contains a field with the name of `dataField` */ has(dataField) { return typeof this._data[dataField] !== 'undefined' } /** Find a value held within this DataModel. @param {string} dataField @param {*} [defaultValue=null] a value to return if the field value is `undefined`, `null`, or the empty string @return {*} may be native types or, if mapped by options.fieldDataObjects, another DataObject */ get(dataField, defaultValue = null) { if ( typeof this._data[dataField] === 'undefined' || this._data[dataField] === null || this._data[dataField] === '' ) { return defaultValue } return this._data[dataField] } /** Return the first value that this `DataModel.has` or 'undefined' if none are found @param {string[]} dataFields @return {*} may be undefined, native types, or (if mapped by options.fieldDataObjects) another DataObject */ getFirst(...dataFields) { for (let i = 0; i < dataFields.length; i += 1) { if (this.has(dataFields[i])) { return this.get(dataFields[i]) } } return undefined } /** Set a key/value pair @param {string} dataField @param {*} value - the new value of the field */ set(dataField, value) { const batch = {} batch[dataField] = value return this.setBatch(batch) } /** Set a group of values. The 'values' parameter should be an object that works in for(key in values) loops like a dictionary: {} If a key is in options.fieldDataObjects then the value will be used to contruct a DataObject and that will be the saved value. */ setBatch(values) { const changes = {} let changed = false for (const key in values) { const result = this._set(key, values[key]) if (result !== DataObject._NO_CHANGE) { changed = true changes[key] = result this.trigger(`changed:${key}`, this, key, result) } } if (changed) { this.trigger('changed', this, changes) } return changes } /** Add a value to a field, creating the value if necessary @param {string} dataField @param {int} [amount=1] @return {*} the new value of the field */ increment(dataField, amount = 1) { const currentVal = dataField in this._data ? this._data[dataField] : 0 this.set(dataField, currentVal + amount) return this.get(dataField) } _set(dataField, data) { // _set does not fire any events, so you probably want to use set or setBatch if (data instanceof DataObject) { if (this._data[dataField] instanceof DataObject) { this._data[dataField].reset(data._data) } else { this._data[dataField] = data } } else if (this.options.fieldDataObjects[dataField]) { if (this._data[dataField]) { this._data[dataField].reset(data) } else { this._data[dataField] = new this.options.fieldDataObjects[dataField](data) } } else { if (this._data[dataField] === data) { return DataObject._NO_CHANGE } if (this._data[dataField] instanceof DataObject) { this._data[dataField].reset(data) } else { this._data[dataField] = data } } return this._data[dataField] } /** @return {string} The model data as a JSON-formatted string */ stringify() { return JSON.stringify(this._data) } /** Calls the DELETE method on the service resource @return {Promise} */ delete() { return new Promise((resolve, reject) => { super .delete() .then((...params) => { if (this.collection !== null) { this.collection.remove(this) } resolve(...params) }) .catch((...params) => { reject(...params) }) }) } /** @param {Object} [data={}] */ reset(data = {}) { for (const key in this._data) { if (typeof data[key] === 'undefined') { this._data[key] = null } } this.setBatch(data) this.trigger('reset', this) } /** @param {*} obj - the value to compare to `this` or `this.get('id')` */ equals(obj) { if (obj === null || typeof obj === 'undefined') return false if (this === obj) return true if (typeof obj !== typeof this) return false if (obj.get('id') === this.get('id')) return true return false } } DataModel.SavingEvent = Symbol('do-saving') DataModel.SavedEvent = Symbol('do-saved') export default DataModel export { DataModel }