ecmamodel
v1.0.1
Published
ES6/ES7 Model and Collection with validation,error handling and sync them with your existing API over a RESTful JSON interface.
Downloads
2
Maintainers
Readme
ecmamodel
ES6/ES7 Model and Collection with validation,error handling and sync them with your existing API over a RESTful JSON interface.
ecmamodel is a open source package for the node.js and browser.
Installation
$ npm install --save ecmamodel
Requirements
ecmamodel requires a Javascript environment with ES6 classes and decorators. Babel is included as a dependency to make the library compatible with environments that do not support these features directly.
Note.
On old browsers we are recommending to use babel-polyfill and isomorphic-fetch
Usage
Defining Model
Your all models should be extended from Model class.
Model fields should be defined by @Field decorator. Field annotation takes optional properties which describe field
import {Model,Field} from 'ecmamodel';
class User extends Model {
@Field({
type:String,
trim:true,
enum:['John','Smith']
lowercase:true,
required:true,
set:function(key,value,target){},
minLength:2,
validators:[]
})
name:String;
}
Type of field should be defined with type properties available in Field annotation.
Available Field Types With Options
####String
lowercase
: {Boolean} calls .toLowerCase() method on the valueuppercase
: {Boolean} calls .toUpperCase() method on the valuetrim
: {Boolean} calls .trim() method on the valuematch
: {RegExp} creates a RegExp based validator.enum
: {Array} Creates an enum validator. If the value which is being set to the field is not in the Array, validation will fail.minLength
: {Number} Creates an min length validator. If the value is shorter than the minimum allowed length, validation will fail.maxLength
: {Number} Creates an max length validator. If the value is longer than the maximum allowed length, validation will fail.
####Number
min
: {Number} creates a validator which checks that the value which is being set is not less than the value specified.max
: {Number} creates a validator which checks that the value which is being set is not greater than the value specified.
Date
min
: {Date} creates a validator which checks that the value is before minimum allowed value.max
: {Date} creates a validator which checks that the value is after maximum allowed value.
Boolean
- no custom options
Array
- no custom options
Object
- no custom options
Additional options
default
. will populate the field when it is created.required
:{Boolean} validates if the value exists.validators
:{Array} define custom validators with custom errors message.set
:{Function} define field setter
for example.
@Field({
type:Number,
set:function(key,value,target){ return value.toLowerCase() },
})
name:String;
ob.name = 'JOHN' // will return 'john'.
Type Casting
Each property in our Model will be casted to its associated type For example, we've defined a age as a String which will be cast to the Number and so on.
@Field({
type:Number
})
age:Number;
ob.age = '15' // automatically cast values to Number (15)
Custom Validators With Error Handling
Your validators should be extended from Validator class. and errors with ValidationError class
import {Validator} from 'ecmamodel';
import {ValidationError} from 'ecmamodel';
export class CustomValidationError extends ValidationError {
constructor(options){
super(
`custom error message`,
options
);
}
}
export class CustomValidator extends Validator{
validate(){
//to do someting
//filed value will be this.value
//model instance will be this.model
//filed key will be this.filed
//and options which passed from constructor will be this.kind
throw new CustomValidationError(this);
}
}
Validator supports synchronous and asynchronous validations.
import {Validator} from 'ecmamodel';
export class CustomValidator extends Validator{
validate(){
return new Promise((resolve,reject)=>{
//to do someting
reject(new CustomValidationError(this))
})
}
}
And it's should be defined within validators
option. for example
class User extends Model {
@Field({
type:String,
validators:[
new CustomValidator(/** your custom options **/)
]
})
name:String;
}
Validation work when validate() function is called
var user = new User();
user.validate(errors=>{
console.info(errors)
});
OR
var user = new User();
user.validate()
.then(success=>{})
.catch(errors=>{
console.info(errors)
});
Defining Model Id (unique identifier)
default id field is (id). but you can override it by using @Id decorator. For example.
import {Id,Field} from 'ecmamodel';
class User extends Model {
@Id
@Field({
type:String
})
_id:String
}
You can have only one @Id field in each model
Defining Models Collection
Your Models collection should be extended from Collection class. Collection constructor requires Model's class.
import {Collection} from 'ecmamodel';
class UserCollection extends Collection{
constructor(){
super(User);
}
get url(){
return '/users'
}
}
// we can add,remove and get model from collection
var collection = new UserCollection();
collection.add({id:1,name:"John"});
var id = 1;
var user = collection.get(id);
collection.remove(user);
//also we can reset,cleanup,clear callection data
collection.reset([{id:1,name:"John"},{id:2,name:"Smith"}])
collection.clear();
collection.cleanup((model)=>{return model.id == 1})
API Integration
ecmamodel is pre-configured to sync with a RESTful APIs. Simply create a new Model with the url of your resource endpoint: You should override Model's and Collection's url getters. Getters should return endpoint urls
class User extends Model {
get url(){
return '/users'
}
}
var user = new User();
user.name = "John";
user.save()
.then(success=>{})
.catch(errors=>{})
The Collection and Model components together form a direct mapping of REST resources using the following methods:
GET /users/ .... collection.fetch();
POST /users/ .... model.save();//if it's new
GET /books/1 ... model.fetch();
PUT /books/1 ... model.save();
DEL /books/1 ... model.destroy();
After API's success response model and collection call 'parse()' method so you can transform data
class User extends Model {
get url(){
return '/users'
}
parse: function(data) {
return data.user;
}
}
var user = new User();
user.name = "John";
user.save();
Events
ecmamodel Model and Collection have custom named events.
Available Events
collection events
create
when a model is added to a collection.remove
when a model is removed from a collection.change
when model's fields are changedreset
when the collection's entire content has been reset (removed all the objects).
model events
change
when a model's fields have changed.change:[field]
when a specific field has been updated.validate
when validate function is calleddestroy
when a model is destroyed.sync
when a model or collection has been successfully synced with the server.error
when a model's or collection's request to the server has failed.response
when a model or collection receives response to its request from the server.
Examples
model: create,update and validate
import 'babel-polyfill';
import {Model,Field} from 'ecmamodel';
class User extends Model {
@Field({
type:String,
trim:true,
enum:['john','smith'],
lowercase:true,
required:true,
minLength:4,
maxLength:30,
default:"John"
})
name:String;
@Field({
type:Number,
min:10,
max:20
})
age:Number;
}
const user = new User();
user.on('change',(model,newObject,oldObject)=>{
console.info(model,newObject,oldObject);
});
user.on('change:name',(model,newValue,oldValue)=>{
console.info(model,newValue,oldValue);
});
user.name = 'Smith';
user.on('validate',(model,errors)=>{
console.info(errors)
});
user.name = 'wrong name';
user.validate(e=>{
console.info(e)
});
//or
user.validate()
.then(r=>{})
.catch(e=>{
console.info(e)
})
collection: create and remove item
import 'babel-polyfill';
import {Collection} from 'ecmamodel';
class UserCollection extends Collection {
constructor(){
super(User);//specify the model class
}
}
const collection = new UserCollection();
collection.on('create',(item,collection)=>{})
collection.on('renove',(item,index,collection)=>{})
collection.add({
name:"Smith"
})
//or
let model = new User({name:"Smith",id:"1"});
collection.add(model)
//get item
var user:User = collection.get("1");
//or
var user:User = collection.get(model);
//remove item
collection.remove(user);
//or
collection.remove({id:"1"})
sync with server
import 'babel-polyfill';
import 'isomorphic-fetch';//include for old browsers support
import {Model,Field} from 'ecmamodel';
class User extends Model {
@Field({
type:String,
trim:true,
enum:['john','smith'],
lowercase:true,
required:true,
minLength:4,
maxLength:30,
default:"John"
})
name:String;
@Field({
type:Number,
min:10,
max:20
})
age:Number;
get url(){
return '/users';
}
}
const user = new User({name:"smith",age:15});
user.on('sync',(model,response)=>{});
user.on('response',(model,response)=>{});
user.on('save',(model)=>{});
user.save()
.then(data=>{})
.catch(error=>{})
//will send 'POST' request to "POST /users" with user data
user.fetch({query:{name:"smith"}}) //will send 'GET' request to "GET /users" with query string
//with options patch user.fetch({patch:true}) will send PATCH request
//or
user.fetch({method:"POST"}) // default is 'GET'
They also works on Collections
collection.fetch({method:"POST",query:{name:'smith'}})
Model Available methods
- get(key) - get Model by field name
- set(key,value,silent) - set new value for a specified field; if 'silent' is true, changes won't be triggered
- toJSON()
- validate(cb:Function)
- save(options={validate:true})
- getId()
- parse(res)
- get url() (server endpoint) should be overridden
- get isNew
Collection Available methods
- get(id)
- add(object)
- remove(object)
- clear()
- cleanup(cb:Function)
- reset(data:Array)
- fetch(options)
- parse(res)
- map(cb:Function)
- filter(cb:Function)
- each(cb:Function)
- sort(cb:Function)
- toObject()
- toArray()
- toJSON()
- get url() (server endpoint) should be overridden
- length
UTILS
ecmamodel utility classes are located in 'ecmamodel/utils'
import {
Emitter,
Bound,
Cached,
Objects,
Types
} from 'ecmamodel/utils'