UNPKG

promised-models

Version:

promise based, typed attributes, nested models and collections

524 lines (402 loc) 11 kB
# Promised Models ## Key features * promise based * typed attributes * nested models and collections * async calculations and validation ## Install $npm install --save promised-models ## Usage ```js var Model = require('promises-models'), FashionModel = new Model.inherit({ attributes: { name: Model.attributeTypes.String } }), model = new FashionModel({ name: 'Kate' }); model.get('name'); // 'Kate' ``` ## Api reference (in progress) ### Model sync methods #### inherit `Model.inherit(properties, [classPorperties])` Creates you own model class by extending `Model`. You can define attributes, instance/class method and properties. Inheritance is built over [inherit](https://www.npmjs.com/package/inherit). ```js var CountedModels = Model.inherit({ __constructor: function () { this.__base.apply(this, arguments); //super this.attributes.index.set(this.__self._count); //static properties this.__self._count ++; }, getIndex: function () { return this.get('index'); } }, { _count: 0, getCount: function () { return this._count; } }); ``` #### attributeTypes `Model.attributeTypes` Namespace for predefined types of attributes. Supported types: * `String` * `Number` * `Boolean` * `List` — for storing arrays * `Model` — for nested models * `ModelsList` — for nested collections * `Object` — serializable objects You can extend default attribute types or create your own ```js var DateAttribute = Model.attributeTypes.Number.inherit({ //.. }), FashionModel = Model.inherit({ attributes: { name: Model.attributeTypes.String, birthDate: DateAttribute } }); ``` **Note:** `models.attributes` will be replaced in constructor with attribute instances. ```js var model = new FashionModel(); model.attributes.birthDate instanceof DateAttribute; //true ``` #### set `model.set(attributeName, value)` Set current value of attribute. ```js var model = new FashionModel(); model.set('name', 'Kate'); model.attributes.name.set('Kate'); model.set({ name: 'Kate', birthDate: new Date(1974, 1, 16) }); ``` **Note:** setting `null` is equivalent to call `.unset()` #### get `model.get(attributeName)` Get current value of attribute. ```js var model = new FashionModel({ name: 'Kate', birthDate: new Date(1974, 1, 16) }) model.get('name'); //Kate model.attributes.name.get(); //Kate model.get('some'); //throws error as unknown attribute ``` #### toJSON `model.toJSON()` Return shallow copy of model data. **Note:** You can create internal attributes, which wouldn't be included to returned object. ```js var FashionModel = new Model.inherit({ attributes: { name: Model.attributeTypes.String.inherit({ internal: true; }), sename: Model.attributeTypes.String.inherit({ internal: true; }), fullName: Model.attributeTypes.String } }), model = new FashionModel({ name: 'Kate', sename: 'Moss', fullName: 'Kate Moss' }); model.toJSON(); // {fullName: 'Kate Moss'} model.get('name'); // Kate ``` **Note:** Returned object supposed to be serializable via `JSON.parse()`. Due to this reason `NaN` and `Infinity` are serialized in this way: ``` NaN -> null Infinity -> 'Infinity' ``` #### isChanged `model.isChanged([branch])` Has model changed since init or last commit/save/fetch. ```js var FashionModel = Model.inherit({ attributes: { name: Model.attributeTypes.String, weight: Model.attributeTypes.Number.inherit({ default: 50 }) } }), model = new FashionModel({ name: 'Kate', weight: 55 }); model.isChanged(); //false model.set('weight', 56); model.isChanged(); //true ``` #### commit `model.commit([branch])` Cache current model state ```js var model = new FashionModel(); model.set({ name: 'Kate', weight: 55 }); model.isChanged();//true model.commit(); model.isChanged();//false ``` #### revert `model.revert([branch])` Revert model state to last cashed one ```js var model = new FashionModel({ name: 'Kate', weight: 55 }); model.set('weight', 56); model.revert(); model.get('weight'); //55 model.isChanged(); //false ``` **Note:** You can create your own cache by passing branch param. ```js var RENDERED = 'RENDERED'; model.on('change', function () { if (model.isChanged(RENDERED)) { View.render(); model.commit(RENDERED); } }); ``` #### on `model.on([attributes], events, cb, [ctx])` Add event handler for one or multiple model events. List of events: * `change` – some of attributes have been changed * `change:attributeName` – `attributeName` have been changed * `destruct` – model was destructed * `calculate` – async calculations started ```js model.on('change', this.changeHandler, this) .on('change:weight change:name', this.changeHandler, this); ``` #### un `model.un([attributes], events, cb, [ctx])` Unsubscribe event handler from events. ```js //subscribe model.on('weight name', 'change', this.changeHandler, this); //unsubscribe model.un('change:weight change:name', this.changeHandler, this); ``` #### destruct `model.destruct()` Remove all events handlers from model and removes model from collections #### isSet `model.isSet(attributeName)` Returns `true` if attribute was set via constructor or set ```js var model = new FashionModel(); model.isSet('name'); //false model.set('name', 'Kate'); model.isSet('name'); //true ``` #### unset `model.unset(attributeName)` Set attribute to default value and `model.isSet() === 'false'` ```js var model = new FashionModel(); model.set('name', 'Kate'); model.unset('name'); model.isSet('name'); //false model.get('name'); //empty string (default value) ``` ### Model async methods #### validate `model.validate()` Validate model attributes. ```js var FashionModel = Model.inherit({ attributes: { name: Model.attributeTypes.String.inherit({ validate: function () { return $.get('/validateName', { name: this.get() }).then(function () { return true; //valid }, function () { return false; //invalid }); } }) } }), model = new FashionModel(); model.validate().fail(function (err) { if (err instanceof Model.ValidationError) { console.log('Invalid attributes:' + err.attributes.join()); } else { return err; } }).done(); ``` #### ready `model.ready()` Fulfils when all calculations over model finished. ```js var FashionModel = Model.inherit({ attributes: { name: Model.attributeTypes.String, ratingIndex: Model.attributeTypes.Number.inherit({ calculate: function () { return $.get('/rating', { annualFee: this.model.get('annualFee') }); } }), annualFee: Model.attributeTypes.Number } }), model = new FashionModel(); model.set('annualFee', 1000000); model.ready().then(function () { model.get('ratingIndex'); }).done(); ``` #### fetch `model.fetch()` Fetch data associated with model from storage. ```js var FashionModel = Model.inherit({ attributes: { name: Model.attributeTypes.String }, storage: Model.Storage.inherit({ find: function (model) { return $.get('/models', { id: model.id }); } }) }), model = new FashionModel(id); model.fetch().then(function () { model.get('name'); }).done(); ``` #### save `model.save()` ```js var FashionModel = Model.inherit({ attributes: { name: Model.attributeTypes.String, weight: Model.attributeTypes.Number }, storage: Model.Storage.inherit({ insert: function (model) { return $.post('/models', model.toJSON()).then(function (result) { return result.id; }); }, update: function (model) { return $.put('/models', model.toJSON()); } }) }), model = new FashionModel(); model.set({ name: 'Kate', weight: 55 }); model.save().then(function () { //create model.id; //storage id model.set('weight', 56); return model.save(); //update }).done() ``` #### remove `model.remove()` Removes model from storage. ### Model additional methods and properties * `model.isNew()` * `model.isReady()` * `model.trigger(event)` * `model.calculate()` * `model.CHANGE_BRANCH` * `model.CALCULATIONS_BRANCH` These methods provided for advanced model extending. Consult source for details. ### Model static methods and properties #### Storage `Model.Storage` Abstract class for model storage ```js var FashionModel = Model.inherit({ attributes: { //.. }, storage: Model.Storage.inherit({ //.. }) }); ``` #### Class storage `Model.storage` Storage class ```js var SuperModel = FashionModel.inherit({ storage: FashionModel.storage.inherit({ //extend storage from FashionModel //.. }) }); ``` #### Attribute `Model.Attribute` Base class for model attribute ```js var CustomAttribute = Model.attribute.inherit({ //.. }) ``` #### Class attributes `Model.attributes` Model class attributes ```js var SuperModel = FashionModel.inherit({ attributes: { name: FashionModel.attributes.name, weight: FashionModel.attributes.weight.inherit({ default: 50 }) } }); ``` #### `Model.on([attributes], events, cb, [ctx])` Bind event on all models of class ```js FashionModel.on('change', this.changeHandler, this); ``` #### `Model.un([attributes], events, cb, [ctx])` Unbind event on all models of class ### List Array like object returned for fields types `List` and `ModelsList` ``` var Podium = Model.inherit({ attributes: { models: Model.attributeTypes.ModelsList(FashionModel) } }), podium = new Podium(data), list = podium.get('models'), //instanceof List model = list.get(0); //instanceof Model ``` #### Mutating methods List inerits Array mutating methods: `pop`, `push`, `reverse`, `shift`, `sort`, `splice`, `unshift` ``` podium.get('models').push(new FashionModel()); ``` #### `list.get(index)` Get list item by index ``` podium.get('models').get(0);// instanceof Model ``` #### `list.length()` Returns length of list #### `list.toArray()` Returns shallow copy of Array, wich stores List items ``` podium.get('models').forEach(function (model) { model; // instanceof Model }); ``` #### ValidationError `Model.ValidationError` Error class for validation fail report ## run tests $ npm test