Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
backbone.nested-types
Advanced tools
backbone.js extension adding type annotations to model attributes, easiest possible way of dealing with nested models and collections, and native properties for attributes. Providing you with a more or less complete, simple, and powerful object system for
In case you're precisely know what you're looking for, it's backbone.js extension adding type annotations, type coercion and type checks (yes, checks) to model attributes, easiest possible way of dealing with nested models and collections, and native properties for attributes. Providing you with a more or less complete, simple, and powerful object system for JavaScript.
In case if you don't, here is a brief outline of problems we're solving with this little thing. There are two major goals behind:
Simplify maping of complex server-side entities to client's models. It should:
parse
, toJSON
, and initialize
. It's not trivial thing to do it right.Simplify usage of models as contexts for template rendering. What we've done for that:
Seriously reduce backbone.js painfullness and shorten learning curve for backbone newbies.
These issues are addressed in many different backbone plugins, but this one is defferent.
We solve these problems encouraging you to type less, than you used to. Type specs in model's 'defaults' section do all the magic. So, if your attribute is a date, just write in defaults, that it's Date. That's it.
No, you don't need a compiler, or learn some non-standard syntax. It's still old good vanilla JS you're get used to.
Forget the 'get'. For any model attributes mentioned in 'defaults', use
model.first = model.second;
model.deep.nesting = some.thing.from.another.model;
instead of
model.set( 'first', model.get( 'second' ) );
model.deep.set( 'nesting', some.get( 'thing' ).get( 'from' ).get( 'another' ).get( 'model );
Great for accessing nested models from templates.
Also, you can define calculated native properties for models and collections like this:
var MyModel = Model.extend({
defaults : {
otherModel_id : 0,
yetAnotherModel_id : 2
},
__otherModelsCollection : null,
properties : {
otherModel : function(){
return this.__otherModelsCollection.get( this.otherModel_id );
},
yetAnotherModel : {
get : function(){
return this.__otherModelsCollection.get( this.yetAnotherModel_id );
},
set : function( model ){
this.yetAnotherModel_id = model.id;
}
}
}
});
// This could be done from some other place...
MyModel.prototype.__otherModelsCollection = ...
Great for implementing collection joins which looks like nested models. Also, since custom properties definition override default properties, it's well suitable for setting hooks on attribute's modification. In case you'll need such a weird stuff, of course.
You could use constructor functions as default value.
var User = Model.extend({
defaults : {
name : String,
created : Date,
loginCount: Number
}
});
New object will be created automatically for any typed attribute, no need to override initialize
.
When typed attribute assigned with value of different type, constructor will be invoked to convert it to defined type.
var user = new User();
assert( user.created instanceof Date );
user.created = "2012-12-12 12:12"; // string will be converted to Date
assert( user.created instanceof Date );
user.loginCount = "jhkhjhjhj";
assert( user.loginCount === NaN );
It means, that you don't need to override Model.parse and Model.initialize when you receive time and date from the server. It will be parsed and serialized to JSON as ISO date automatically.
It's important to note that defaults specs are mandatory with this plugin. Default implementation of Model.validate method will write an error in console if you attempt to save model with attributes which are not declared.
Also, defaults spec must be an object, at this time you can't use function as in plain Backbone. Support for them will be added later.
Semantic of type annonation designed to be both intuitive and protective, in order to have minimal learning curve and help to overcome typical backbone.js mistakes usually done by newbies. Howewer, it's not as simple behind the scene. Here's more formal and detailed description of logic behind defaults:
var M = Model.extend({
defaults : {
num : 1,
str : "",
arr : [ 1, 2, 3 ],
obj : { a: 1, b: 2 },
date : Date,
shared : new Date()
}
});
// creation of default values will behave exactly as the following code in plain backbone:
var _tmp = new Date();
var M = Model.extend({
defaults : function(){
return {
num : 1,
str : "",
arr : [ 1, 2, 3 ],
obj : { a: 1, b: 2 },
date : new Date(),
shared : _tmp
}
}
});
var M = Model.extend({
defaults : {
date1 : Model.Attribute( Date, null ),
date2 : Model.Attribute( Date, '2012-12-12 12:12' )
}
});
// will behave as:
var M = Model.extend({
defaults : function(){
return {
date1 : null,
date2 : new Date( '2012-12-12 12:12' )
}
}
});
var M = Model.extend({
defaults : {
date1 : Model.Attribute({
type : Date,
value : null,
get : function(){
var time = this.attributes.date1;
return time ? time : new Date( '1900-01-01 00:00' );
})
}
}
});
To use nested models and collections, just annotate attributes with Model or Collection type.
var User = Model.extend({
defaults : {
name : String,
created : Date,
group : GroupModel,
permissions : PermissionCollection
}
});
No need to override initialize
, parse
, and toJSON
, nested JSON will be parsed and generated automatically. You still can override parse to transform JSON received from the server, but there is no need to create new Model/Collection instances, because of the modified 'set' behaviour.
If attribute is defined as Model or Collection, new value is an object or array (for example, JSON received form the server), and its current value is not null, it will be delegated to 'set' method of existig nested model or collection (!). If current value is null, new instance of model/collection will be created. I.e. this code:
var user = new User();
user.group = {
name: "Admin"
};
user.permissions = [{ id: 5, type: 'full' }];
is equivalent of:
user.group.set({
name: "Admin"
};
user.permissions.set( [{ id: 5, type: 'full' }] );
'set' method for models and collection is strictly type checked. You'll got error in the console on every attempt to set values with incompatible type.
This mechanics of 'set' allows you to work with JSON from in case of deeply nested models and collections without the need to override 'parse'. This code (considering that nested attributes defined as models):
user.group = {
nestedModel : {
deeplyNestedModel : {
attr : 'value'
},
attr : 5
}
};
is almost equivalent of:
user.group.nestedModel.deeplyNestedModel.set( 'attr', 'value' );
user.group.nestedModel.set( 'attr', 'value' );
but it will fire just single change
event.
Change events will be bubbled from nested models and collections.
change
and change:attribute
events for any changes in nested models and collections. Multiple change
events from submodels during bulk updates are carefully joined together, which make it suitable to subscribe View.render to the top model's change
.replace:attribute
event when model or collection is replaced with new object. You might need it to subscribe for events from submodels.When you have many-to-many relationships, it is suitable to transfer such a relationships from server as arrays of model ids. NestedTypes gives you special attribute data type to handle such a situation.
var User = Model.extend({
defaults : {
name : String,
roles : RolesCollection.RefsTo( rolesCollection ) // <- subclass of existing RolesCollection
}
});
var user = new User({ id: 0 });
user.fetch(); // <- and you'll receive from server "{ id: 0, name : 'john', roles : [ 1, 2, 3 ] }"
...
// however, user.roles behaves like collection of Roles.
assert( user.roles instanceof Collection );
assert( user.roles.first() instanceof Role );
Collection.RefsTo is a collection of models. It overrides toJSON and parse to accept array of model ids. Also, it override its 'get' property in upper model, to resolve ids to real models from the given master collection on first attribute read attempt. If master collection is empty and thus references cannot be resolved, it will defer id resolution and just return collection of dummy models with ids. However, if master collection is not empty, it will filter out ids of non-existent models.
This semantic is required to deal with collections in asynchronous JS world. Also, there are 'lazy' option for passing reference to master collection:
var User = Model.extend({
defaults : {
name : String,
roles : Collection.RefsTo( function(){
return this.collection.rolesCollection; // <- collection of Roles is the direct member of UsersCollection.
})
}
});
Note, that 'change' events won't be bubbled from models in Collection.RefsTo. Collection's events will.
Type checks and type coercions performed on every model 'set' call and assignment to the model's property. Failed type checks will be logged with console.error as "[Type Error...]..." message.
There are two type cercion rules:
And there are two very fast runtime type checks in Model.set, which in combination with coercion rules listed above effectively isolating significant amount of type errors:
Also, there is a protection inside 'extend' from occasionally overriding base Model/Collection/Class members with attributes and properties. Required, since plugin creates native properties for attributes.
model.nestedModel = other.nestedModel.deepClone(); // will create a copy of nested objects tree
var Base = Model.extend({
defaults: {
a : 1
}
});
var Derived = Base.extend({
defaults: {
b : 1
}
});
var instance = new Derived();
assert( instance.b === 1 );
assert( instance.a === 1 );
var myClass = Class.extend({
a : 1,
initialize : function( options ){
this.a = options.a
this.listenTo( options.nowhere, 'something', function(){
this.trigger( 'heardsomething', this );
});
},
properties : {
b : function(){ return this.a + 1; }
}
});
P.S.: There's an opinion that classes are not needed in JS since you can do fancy mixins with prototypes. What I would say? C'mon, we're really old-school guys here. :) We're get used to situation when class looks like a class, not as a random excerpt from Linux kernel or something :)
Native properties support is required. It means all modern browsers, IE from version 9.
You need a single file (nestedtypes.js) and backbone.js itself. It should work in browser with plain script tag, require.js or other AMD loader. Also, it's available as npm package for node.js (https://www.npmjs.org/package/backbone.nested-types).
Module exports three variables - Class, Model, Collection. You need to use them instead of backbone's. In browser environment with plain script tag import it will export these things under NestedTypes namespace - see an example in sources.
And yes, you could expect this extension to be stable and bug free enough to use. It is being developed as a part of commercial product. Volicon rely on this extension as a core part of architecture for next gen products. And we're actually interested in your bug reports. :)
FAQs
backbone.js extension adding type annotations to model attributes, easiest possible way of dealing with nested models and collections, and native properties for attributes. Providing you with a more or less complete, simple, and powerful object system for
The npm package backbone.nested-types receives a total of 0 weekly downloads. As such, backbone.nested-types popularity was classified as not popular.
We found that backbone.nested-types demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.