README
Getting Started
Version 1.0.0 is here. Highlights:
- New .has type specs syntax
- Huge performance improvement over vanilla backbonejs. Model updates are 4x faster in most browsers (20x faster in Chrome and nodejs).
What it is
NestedTypes is state-of-the-art backbonejs-compatible model framework.
Complex attribute types
- Cross-browser handling of Date.
- Nested models and collections.
- One-to-many and many-to-many model relationships.
It's achieved using attribute type annotations, which feels in much like statically typed programming language. Yet, this annotations are vanilla JavaScript, no transpiler step is required.
Safety
NestedTypes check types on every model update and perform dynamic type casts to ensure that attributes will always hold values of proper type.
As result, NestedTypes models are extremely reliable. It's impossible to break client-server protocol with inaccurate attribute assignment. If something will go really wrong, it will warn you with a messages in the console.
Performance
NestedTypes uses attribute type information for sophisticated optimizations targeting modern JS JIT engines.
Compared to backbonejs, model updates are about 20 times faster in Chrome/nodejs, and 4 times faster in other browsers.
Easy to use and learn
NestedTypes was originally designed with an idea to make backbonejs more friendly for newbiews.
What we do, is taking intuitive newbie approach to backbonejs, and turn it from the mistake to legal way of doing things.
var User = Nested.Model.extend({
urlRoot : '/api/users',
defaults : {
// Primitive types
login : "", // String
email : String.value( null ), // null, but String
loginCount : Number.has.toJSON( false ) // 0, not serialized
active : Boolean.value( true ), // true
created : Date, // new Date()
settings : Settings, // new Settings()
// collection of models, received as an array of model ids
roles : Role.Collection.subsetOf( rolesCollection ),
// reference to model, received as model id.
office : Office.from( officeCollection )
}
});
var collection = new User.Collection();
collection.fetch().done( function(){
var user = collection.first();
console.log( user.name ); // native properties
console.log( user.office.name );
console.log( user.roles.first().name );
});
Types are being checked in run-time on assignment, but instead of throwing exceptions it tries to cast values to defined types.
user.login = 1;
console.assert( user.login === "1" );
user.active = undefined;
console.assert( user.active === false );
user.loginCount = "hjkhjkhfjkhjkfd";
console.assert( _.isNan( user.loginCount ) );
user.settings = { timeZone : 180 }; // same as user.settings.set({ timeZone : 180 })
console.assert( user.settings instanceof Settings );
Installation & Requirements
CommonJS (node.js, browserify):
var Nested = require( 'nestedtypes' );
CommonJS/AMD (RequireJS). 'backbone' and 'underscore' modules must be defined in config paths.
require([ 'nestedtypes' ], function( Nested ){ ... });
Browser's script tag
<script src="underscore.js" type="text/javascript"></script>
<script src="backbone.js" type="text/javascript"></script>
<script src="nestedtypes.js" type="text/javascript"></script>
<script> var Model = Nested.Model; ... </script>
Supported JS environments
NestedTypes requires modern JS environment with support for native properties.
It's tested in IE 9+
, Chrome
, Safari
, Firefox
, which currently gives you about 95%
of all browsers being used for accessing the web.
node.js
and io.js
are also supported.
Packaging and dependencies
NestedTypes itself is packaged as UMD (Universal Module Definition) module, and should load dependencies properly in any environment.
NestedTypes require underscore
and backbone
libraries. They either must be included globally with <script>
tag or, if CommonJS
/AMD
loaders are used, be accessible by their standard module names.
bower
bower install backbone.nested-types
npm
npm install backbone.nested-types
Manual
Copy nestedtypes.js
file to desired location.
Object.extend
Overview
NestedTypes core functionality relies on improved Object.extend
function, which is also available as separate module
without any side dependencies. It compatible with Backbone's extend
, while providing some
additional capabilities important for NestedTypes and its applications, such as:
- Native properties
- Forward declarations
You can attach it to your Constructor function like this:
Object.extend.attach( MyConstructor1, MyConstructor2, ... );
Object.extend
can also be used directly to create classes.
When used as a part of NestedTypes,
all Object.extend
classes also implements Backbone.Events
, thus your custom objects are capable of sending and receiving backbone events.
You can add your own methods to all classes like this:
Object.extend.Class.prototype.myMethod = function(){...}
Defining classes
var MyClass = Object.extend({
a : 1,
inc : function(){ return this.a++; },
initialize : function( x ){
this.a = x;
}
},{
factory : function( x ){
return new MyClass( x );
}
});
When executed directly,
Object.extend( protoProps, staticProps )
creates constructor function and extends
its prototype with protoProps
properties, also attaching staticProps
to the constructor
itself. Constructor will call optional initialize
method.
Inheritance
var Subclass = MyClass.extend({
b : 2,
initialize : function( a, b ){
Subclass.__super__.initialize.apply( this, arguments );
this.b = b;
}
}
Every constructor created with Object.extend
may be further extended with extend
method.
Correct prototype chain will be built and attached to subclass constructor. Every subclass
constructor has __super__
property pointing to the prototype of the base class.
Overriding constructor
var Subclass = MyClass.extend({
b : 2,
constructor : function( a, b ){
MyClass.apply( this, arguments );
this.b = b;
}
}
You may override constructor instead of dealing with initialize
function.
Native Properties
var Class = Object.extend({
properties: {
readOnly : function(){ return 'hello!'; },
readWrite : {
get : function(){ return this._value2; },
set : function( value ){
this._value2 = value;
}
}
}
});
Native properties can be defined with properties
spec.
For read-only properties, it's enough to supply get function as spec.
Otherwise, properties specs format is the same as accepted by standard Object.defineProperties
function.
You can access native properties as if it would be regular object member variable.
var x = c.readOnly
c.readWrite = 1;
Forward declarations
var A = Object.extend(),
B = Object.extend( function(){ this.b = 'b'; } );
A.define({
bType : B
});
B.define({
aType : A
});
Classes can be created with an Object.extend()
, and defined later using
MyClass.define( protoProps, staticProps )
function. It can be helpful
to resolve circular dependencies.
define
cannot be used to override constructor. It can be achieved by passing
constructor function to extend
, as it is done for B
in the example.
Console Warnings
var A = Object.extend({
a : function(){}
});
var B = A.extend({
a : 0 // Warning about type error
});
If you try to override base class function with non-function value, Object.extend
will notify you about that with a warning to the console. Cause usually it's a mistake.
In this case, you'll see in the console following message:
[Type Warning] Base class method overriden with value in Object.extend({ a : 0 }); Object = >...
function warning( Ctor, name, value ){
throw new TypeError( 'Whoops...' );
}
Object.extend.error.overrideMethodWithValue = warning;
You may override default warning handling assigning our own function to Object.extend.error.overrideMethodWithValue
.
Nested.Model
Overview
In NestedTypes model definition's defaults
section is the specification of model's attributes.
attributes
keyword may be used instead of defaults
.
In defaults
or attributes
, you may specify attribute default value, its type, and different options of
attribute behavior. Refer to corresponding sections of the manual for details.
In NestedTypes, attribute declaration is mandatory. When you try to set an attribute which doesn't have default value, you'll got an error in the console.
model.defaults( [ attrs ], [ options ] )
var UserInfo = Nested.Model.extend({
defaults : {
name : 'test'
}
});
var DetailedUserInfo = UserInfo.extend({
attributes : { // <- alternative syntax for 'defaults'
login : '',
roles : [ 'user' ]
}
});
var user = new DetailedUserInfo();
In Backbone, 'name' attr is not inherited and would be undefined. In NestedTypes it's inherited, and you can access it directly.
console.assert( user.name === 'test' );
user.name = 'admin';
In Backbone all models will share the same instance of [ 'user' ] array. Bug. In NestedTypes, user.roles is deep copied on creation. Good practice.
user.roles.push( 'admin' );
NestedTypes automatically creates defaults
function for every model
from model attribute's spec. Base model attributes will be inherited.
Following statement can be used to return every model to its original state:
`model.set( model.defaults() )`
defaults
function accepts optional attrs
argument with attribute values hash
and fills missing attributes with default values.
default values deep cloning
When new model is being created, NestedTypes will deep clone
all items (including objects and arrays) from defaults
object.
Correct defaults inheritance
When extending some existing model definition, NestedTypes will property
merge base model's defaults
.
model.attrName
NestedTypes creates native property for every attribute.
model.attr = val;
has the same effect as model.set( 'attr', val );
val = model.attr;
has the same effect as val = model.get( 'attr' );
You still might need to use model.set
in cases when you want to set multiple attributes
at once, or to pass some options.
model.id
In NestedTypes, model.id
is assignable property, linked to model.attributes[ model.idAttribute ]
.
model.id = 5
has the same effect as model.set( model.idAttribute, 5 )
model.properties
var M = Nested.Model.extend({
defaults : {
a : 1
},
properties : {
b : function(){ return this.a + 1; }
}
});
var m = new M();
console.log( m.b ); // 2
Custom native properties specification. Most typical use case is calculated properties.
model.properties
is the part of Object.extend
functionality. Refer to Object.extend
manual section for details.
model.set()
Set model attributes. In NestedTypes, this operation is type safe. It's guaranteed that model attribute will always hold null or value of specified type.
- Values are converted to proper types. For existing nested models and collections
deep update
may be invoked. Refer toAttribute Types
manual section for details. - Set hooks are being executed for changing attributes. Refer to
Attribute Options
section for details. - Events are being registered for changing attributes.
replace:attr
events are fired, - Attribute values are being set, firing regular change events.
On attempt to set an attribute which is not defined, warning message will be printed to console.
In NestedTypes, you can assign individual model attributes directly, and it's faster than using set
:
model.attr = val;
model.get( 'attr' )
Get attribute value by name. Returned value can be modified with get hook
in attribute definition.
In NestedTypes, you can access model attributes directly, and it's faster than get
:
val = model.attr;
Deep clone
`model.deepClone()` or `model.clone({ deep : true })`
Deeply clone model with all nested models, collections, and other complex types.
Deep get and set
`x = model.deepGet( 'attr1.attr2.modelId.attr3.objId' )`
Get attribute by dot-separated path. Model attribute name, model.id or model.cid (for collection attribute), index (for array), or object property name ( for plain objects) may be used as an elements of the path.
If some model in the middle of path doesn't exists, it will return undefined
.
`x = model.deepSet( 'attr1.attr2.modelId.attr3.objId', x )`
Set model value by dot-separated path. If model attribute in the middle of path equals to null, empty model will be created.
Model.Collection
var UserInfo = Nested.Model.extend({
urlBase : '/api/user/',
defaults : {
login : '',
roles : [ 'user' ]
},
collection : {
initialize : function(){
this.fetch();
}
}
});
var collection = new UserInfo.Collection();
Every model definition has its own correct Collection
type extending base Model.Collection
, which can be
accessed instantly without declaration. Collection.model
and Collection.url
properties are taken from model.
`var collection = new AnyModel.Collection();`
You could customize collection definition providing the spec in Model.collection
, which then will be passed to BaseModel.Collection.extend
.
Model.define()
var Tree = Nested.Mode.extend();
Tree.define({
defaults : {
branches : Tree.Collection
}
});
Forward declarations makes possible type-accurate recursive and mutually recursive model definitions.
Model.define
is the part of Object.extend
functionality. Refer to Object.extend
manual section for details.
Serialization
model.toJSON
var M = Nested.Model.extend({
defaults : {
// Attribute-level toJSON.
a : String.has.toJSON( false ),
b : 5
},
// Model-level toJSON.
toJSON : function(){
// Call NestedTypes serialization algorithm.
var json = Nested.Model.prototype.toJSON.apply( this, arguments );
// Do some json transformations...
return json;
}
});
All nested attributes will be serialized automatically.
You can control serialization of any attribute with toJSON
attribute option. Most typical use case is to exclude attribute from those which are being sent to the server.
model.parse
var M = Nested.Model.extend({
defaults : {
// Attribute-level parse transform.
a : AbstractModel.has.parse( AbstractModel.factory )
},
// Model-level parse transform.
parse : function( resp ){
// Do some resp transformations...
// (!) Call attribute-level parse transform (!)
return this._parse( resp );
}
});
All nested attributes will be parsed automatically.
You can customize parsing of any attribute with parse
attribute option. Most typical use case is to create proper model subclass for abstract model attribute.
You may need to override model-level parse
function in order to change attribute names or top-level format.
Attribute Types
Generic Constructor types
var A = Nested.Model.extend({
defaults : {
obj1 : Ctor, // = new Ctor()
obj2 : Ctor.value( null ), // = null
obj3 : Ctor.value( something ), // = new Ctor( something )
}
});
var a = A();
a.obj2 = "dsds"; // a.obj2 = new Ctor( "dsds" );
console.assert( a.obj2 instanceof Ctor );
Type spec format
Type specs may be used instead of init values in Model.defaults
. They looks like this:
name : Constructor
or name : Constructor.value( x )
where Constructor
is JS constructor function, and x
is null
or value passed
as constructor's argument.
When value is not given, typed attribute is initialized invoking new Constructor()
.
Type casting rules
When typed attribute is assigned with the value...
- ...which is
null
, attribute value will be set tonull
. - ...which is an instance of
Constructor
, attribute's value will be replaced with a given one. - in other case, NestedTypes will try to convert value to the
Constructor
type, typically invokingnew Constructor( value )
. Procedure might be more complex for some selected types, such as nested models and collections.
Serialization
Constructor types are being serialized with JSON.stringify()
method. You may override toJSON
for your type
to customize serialization. I.e.
`this.name.toJSON()`
will be invoked to produce JSON, if this method exists.
When receiving data from server, standard type cast logic is used to convert JSON response to Constructor object. I.e.
`this.name = new Constructor( jsonResponse )`
will be invoked.
Date type
var A = Nested.Model.extend({
defaults : {
created : Date, // = new Date()
updated : Date.value( null ), // = null
a : Date.value( 327943789 ), // = new Date( 327943789 )
b : Date.value( "2012-12-12 12:12" ) // = new Date( "2012-12-12 12:12" )
}
});
var a = A();
a.updated = '2012-12-12 12:12';
console.assert( a.updated instanceof Date );
a.updated = '/Date(32323232323)/';
console.assert( a.updated instanceof Date );
Type spec format
To create attribute of Date
type, pass Date
constructor instead of default value.
`time : Date` or `time : Date.value( x )`
When default value is given, it will be converted to Date using type casting rules.
Type casting rules
Number
is treated as milliseconds from 1970 timestamps, as returned by Date.getTime().String
is treated as one of the following date-time formats (will be detected automatically):- UTC ISO time string.
- Local date-time string.
- Microsoft
/Date(msecs)/
time string.
null
sets attribute tonull
bypassing type conversion logic.- Other values will be converted to
Invalid Date
.
Serialization
Date
attributes are serialized to UTC ISO date string by default. You may customize date serialization format
providing attribute's toJSON
option. Following option will serialize time
to milliseconds.
`time : Date.has.toJSON( function( date ){ return date.getTime(); })`
You can prevent attribute from being serialized, using:
`time : Date.has.toJSON( false )`
Date
attributes are being parsed from JSON using type casting rules.
Primitive types
var A = Nested.Model.extend({
defaults : {
// Original backbone behaviour - no type, value is 3232
untyped : Nested.value( 3232 )
// defaults with primitive types are always 'typed'
number : 5, // same as Number.value( 5 )
integer : Integer.value( 6 ),
string : 'something', // same as String.value( 'something' )
string1 : '', // same as String
boolean : true, // same as Boolean.value( true )
initWithNull : String.value( null ), // Type is String, default value is null
}
});
var a = A();
a.boolean = "hello";
console.assert( a.boolean === true );
a.number = "5";
console.assert( a.number === 5 );
a.number = "hjhjfd";
console.assert( _.isNaN( a.number ) );
a.integer = 1.5423;
console.assert( a.integer === 2 );
a.string = 5;
console.assert( a.string === "5" );
a.boolean = 0;
console.assert( a.boolean === false );
Type spec format
Primitive types (Boolean, Number, String) are special in a sense that they are inferred from their values. In most cases special type annotation syntax is not really required. For example:
n : 5
is the same asn : Number.value( 5 )
b : true
is the same asb : Boolean.value( true )
s : 'hi'
is the same ass : String.value( 'hi' )
x : null
is not the same. No type will be being inferred fromnull
value.
Integer type
NestedTypes adds global Integer
type, to be used in type annotations. Integer
type is not being inferred from default values, and needs to be specified explicitly.
Type casting rules
null
will set attribute tonull
for all primitive types.Number
attribute:- Number( x ) will be invoked to parse numbers.
- Attribute will be set to
NaN
if conversion will fail.
Integer
attribute:- Same as
Number
, but values also converted to integer usingMath.round
.
- Same as
String
attribute:- Primitive types will be converted to their string representation.
- For objects,
x.toString()
method will be invoked. - Conversion to string never fails.
Boolean
attribute:- Will be always converted to
true
orfalse
using standard JS type cast logic.
- Will be always converted to
Serialization
Primitives are serialized to JSON directly. You can disable serialization of particular attribute with an option:
`x : Integer.value( 5 ).toJSON( false )`
Untyped attributes
Type spec format
To define untyped attribute, use either of these options:
u : null
,u : []
, oru : {}
.- Any
u : x
where typeof x === 'object'. u : Nested.value( x )
for value of any type, including primitives.
Type casting rules
None
Serialization
When serialized, value.toJSON
function will be invoked if it exists for particular value.
JSON responses are assigned to untyped attributes as is.
Models and Collections
var User = Nested.Model.extend({
defaults : {
name : String,
created : Date,
group : Group,
permissions : Permission.Collection
}
});
var a = new User(),
b = a.deepClone();
Type spec format
To define nested model or collection, annotate attribute with Model or Collection type:
`a : MyModel` or `b : MyModel.Collection` or `c : SomeCollection`
Inline nested Models and Collections definitions
Inline nested definitions
var M = Nested.Model.extend({
defaults :{
// define model extending base Nested.Model
nestedModel : Nested.defaults({
a : 1,
//define model extending specified model
b : MyModel.defaults({
// define collection of nested models
items : Nested.Collection.defaults({
a : 1,
b : 2
})
})
})
}
})
Simple models and collections can be defined with special shortened syntax.
It's useful in case of deeply nested JS objects, when you previously preferred plain objects and arrays in place of models and collections. Now you could easily convert them to nested types, enjoying nested changes detection and 'deep update' features.
Type casting
Deep update example:
var user = new User();
// Following assignment...
user.group = { name: "Admin" };
// ...is the same as this:
user.group.set({ name: "Admin" });
// Following assignment...
user.permissions = [{ id: 5, type: 'full' }];
// ...is the same as this:
user.permissions.set( [{ id: 5, type: 'full' }] );
// Following assignment...
user.group = {
nestedModel : {
deeplyNestedModel : { attr : 'value' },
attr : 5
}
};
// ...is the same as this, but fire single 'change' event
user.group.nestedModel.deeplyNestedModel.attr = 'value';
user.group.nestedModel.attr = 'value';
When Model
or Collection
attribute is assigned with the value...
- ...which is
null
, attribute value will be set tonull
. - ...which is an instance of specified
Model
/Collection
, attribute's value will be replaced with a given one. - otherwise, if value has incompatible type, and current attribute value...
- ...is
null
, new model or collection will be created taking value as constructor argument. - ...is existing model or collection, update will be delegated to its
set
method performingdeep update
.
- ...is
Serialization
Nested models and collections are serialized as nested JSON. When JSON response is received, they are being constructed or updated according to type case rules.
Change events bubbling
Event bubbling:
var M = Nested.Model.extend({
defaults: {
bubbleChanges : ModelOrCollection,
dontBubble : ModelOrCollection.has.triggerWhanChanged( false )
}),
bubbleCustomEvents : ModelOrCollection.has
.triggerWhanChanged( 'event1 event2 whatever' )
}
});
Change events will be bubbled from nested models and collections.
change
andchange:attribute
events for any changes in nested models and collections. Multiplechange
events from submodels during bulk updates are carefully joined together, which make it suitable to subscribe View.render to the top model'schange
.replace:attribute
event when model or collection is replaced with new object. You might need it to subscribe for events from submodels.- It's possible to control event bubbling for every attribute. You can completely disable it, or override the list of events which would be counted as change:
Model id references
var User = Nested.Model.extend({
defaults : {
name : String,
roles : Role.Collection.subsetOf( roles ) // <- serialized as array of model ids
location : Location.from( locations ) // <- serialized as model id
}
});
var user = new User({ id: 0 });
user.fetch();
Server response: "{ id: 0, name : 'john', roles : [ 1, 2, 3 ], location : 6 }"
//ref attributes behaves like normal collections and models.
assert( user.roles instanceof Collection );
assert( user.roles.first() instanceof Role );
assert( user.location.name === "Boston" );
Sometimes it is suitable to serialize model references as an id or an array of ids.
NestedTypes provides special attribute data types to transparently handle this situation, as if you would work with normal nested models and collections.
Model.from
Model.from
represent reference to the model from existing collection, which is serialized as model id.
`ref : Model.from( masterCollection )`
Attribute may be assigned with model id or model itself. On `get, attribute behaves as Model type. Model id will be resolved to model on first attribute read attempt.
If master collection is empty and thus reference cannot be resolved, it will defer id resolution and get
will return null
. If master collection is not empty, id will be resolved to model from this collection, or null
if corresponding model doesn't exists.
Attribute counts as changed only when different model or id is assigned.
Collection.subsetOf
Collection.subsetOf
is a collection of models taken from other 'master' collection. On first access, it will resolve model ids to real models using master collection for lookups.
If master collection is empty and thus references cannot be resolved, it will defer id resolution and just return empty collection. If master collection is not empty, it will filter out ids of non-existent models.
Collection.subsetOf
supports some additional methods:
- addAll() - add all models from master collection.
- removeAll() - same as reset().
- toggle( modelOrId ) - toggle specific model in set.
- justOne( modelOrId ) - reset subset to contain just specified model.
change
events won't be bubbled from models in Collection.subsetOf. Other collection's events will.
Master Collection
Master collection reference may be:
- direct reference to collection object
string
, designating reference to the current model's member relative to 'this'.function
, which returns reference to collection and executed in the context of the model.
Attribute options
Type.has
var M = Nested.Model.extend({
defaults : {
attr : Date.has
.value( null )
.toJSON( false )
}
});
Attribute options spec gives you to customize different aspects of attribute behavior, such as:
- attribute serialization control
- nested changes detection
- attribute's get and set
.value
is an example of attribute option. In order to get access to other options you need to use keyword .has
. Options specs are chainable, you can specify any sequence of options separated by dot.
.value( value )
var M = Nested.Model.extend({
defaults : {
a : Type.has.value( value ),
b : Type.value( value )
}
});
Attribute's default value. On model construction, value
will be casted to Type
applying usual type casting rules.
.toJSON( function( value, name ) | false )
var M = Nested.Model.extend({
defaults : {
a : Type.has.toJSON( function( value, name ){
return value.text;
}),
b : Type.has.toJSON( false )
}
});
When attribute will be serialized as a part of model, given function will be used instead of attribute's toJSON.
Function accepts attribute's name
and its current value
, and will be executed in the context of the model, holding an attribute.
Passing false
option will prevent attribute's serialization.
.parse( function( value, name ) )
var M = Nested.Model.extend({
defaults : {
a : Type.has.parse( function( value ){
return Type.factory( value );
})
}
});
Attribute-specific parse
logic, will be executed after model's parse
method.
Function accepts attribute's name
and response value
, and will be executed in the context of the model, holding an attribute.
This option is useful to parse abstract model attributes, or handle non-standard format of specific attributes.
.get( function( value, name ) )
var M = Nested.Model.extend({
defaults : {
a : Type.has.get( function( value, name ){
return value;
})
}
});
Called during model.get( 'a' )
or model.a
in the context of the model, allowing you to modify value which will be returned without altering attribute itself.
Get hook function accepts attribute's name
and its current value
, and returns modified value.
Multiple get hooks are chainable, and will be applied in specified order.
.set( function( value, name ) )
var M = Nested.Model.extend({
defaults : {
a : Type.has.set( function( value, name ){
return value;
})
}
});
Called during attribute's update in the context of the model after type cast but before an actual set, allowing you to modify set value.
Set hook function accepts attribute's name
and value
to be set, and returns modified value, or undefined
to cancel attribute update.
Multiple set hooks are chainable, and will be applied in specified order.
Returned value will be casted to attribute's type applying standard convertion rules. So, it's guaranteed that attribute's value will always hold the correct type.
.events( eventsMap )
var M = Nested.Model.extend({
defaults : {
a : Type.has.events({
'isReady isNotReady' : function(){
this.trigger( 'imwatchingyou' );
}
}),
}
});
Automatically manage events subscription for nested attribute, capable of sending events. Event handlers will be called in the context of of the parent model.
.triggerWhenChanged( String | false )
var M = Nested.Model.extend({
defaults : {
a : ModelA.has.triggerWhenChanged( 'change myEvent' ),
b : ModelB.has.triggerWhenChanged( false ),
}
});
Override default list of events used for nested changes detection of selected attribute.
Pass false
option to disable nested changes detection for this attribute.
Nested.attribute([ optionsHash ])
var M = Nested.Model.extend({
defaults : {
a : Nested.attribute({
value : null,
toJSON : false
}),
b : Nested.attribute()
.value( null )
.toJSON( false )
}
});
Nested.attribute
function returns attribute spec as it appears after .has
, optionally accepting set of options as a hash.
Nested.store
There's a global store for the collections, which might be useful in case of bi-directional relationships. It's available as a member of Model (this.store), and globally as Nested.store.
Initialization
Nested.store = {
roles : Role.Collection,
locations : Locations.Collection
};
var User = Nested.Model.extend({
defaults : {
name : String,
roles : Collection.subsetOf( 'store.roles' ); // this.store.roles
location : Location.from( 'store.locations' }); // this.store.locations
}
});
Store needs to be initialized with a hash of collections and models type specs. It can be initialized several times.
Format of the spec object is the same as in Model.defaults
.
Lazy loading
On first access to every member of the store, it will fetch data from the server automatically. You need to take care of update events.
Nested.store.fetch( 'attr1', ...)
Update all store members, which are currently loaded:
`Nested.store.fetch()`
Fetches store elements with given names:
`Nested.store.fetch( 'name1', 'name2', ... )`
Returns aggregate promise for xhr objects.
Nested.clear( 'attr1', ... )
Clear all store collection elements:
Nested.store.clear()
Clear selected store collections:
Nested.store.clear( 'name1', 'name2', ... )
Returns store to allow chained calls.
Nested.errors
NestedTypes detect four error types in the runtime, which will be logged to console using console.error.
Method overriden with value
When you override function with non-function value in the subclass, it usually means an error.
This message also warn you on the situation when you made model attribute or property name the same as some base class method.
[Type Warning] Base class method overriden with value in Object.extend({ url : [object Object] }); Object = ...
Wrong model.set argument
First argument of Model.set must be either string, or literal object representing attribute hash.
Other situation means serious error. Something goes really wrong.
[Type Error] Attribute hash is not an object in Model.set( "http://0.0.0.0/" ); this = ...
Wrong collection.set argument
First argument of Collection.set must be either an Array, literal object, or compatible Model.
Other situation means serious error. Something goes really wrong.
[Type Error] Wrong argument type in Collection.set( "dsds" ); this = ...
Attribute has ho default value
Attempt to set an attribute which is not declared in model defaults
.
[Type Error] Attribute has no default value in Model.set( "a", 0 ); this =...