Create lazy-loading relations between Backbone.js models and collections with minimal effort. Perfect for REST APIs
Let's define and create a model with a collection
var Employees = Backbone.ChildCollection.extend({
// url path will be appended to the url of the parent model
urlPath: 'employees'
})
var Company = Backbone.Model.extend({
urlRoot: '/api/company'
name: 'company', // see below how this is used
// setup all child collections with a key
collections: {
'employees': Employees
}
});
// instantiate a new model
var myCompany = new Company({id: 1, name: 'My Company'});
The url for child collections is created automatically using the URL from parent models
console.log( myCompany.get('employees').url() ) // = /api/company/1/employees
The collections have a reference to the parent model
var employeeColl = myCompany.get('employees');
console.log( employeeColl.parentModel == myCompany ) // true
Here's some more use cases.
// create employee models – POST: /api/company/1/employees
myCompany.get('employees').create([
{id: 1, name: 'John Doe'},
{id: 2, name: 'Jane Doe'}
])
// there's more than one to access children
var firstEmployee = myCompany.get('employees').first()
//var firstEmployee = myCompany.get('employees.first')
//var firstEmployee = myCompany.get('employees.at0')
//var firstEmployee = myCompany.get('employees.1') // ID
// child models inside the child collections can traverse to the parent model
firstEmployee.collection.parentModel == myCompany // true
// of if you give the parent model a `name`, you can do this
firstEmployee.get('company') == myCompany // true
// Setup a computer model with a link to a single employee model
var Computer = Backbone.Model.extend({
models: {
'employee': {id: 'employee_id', coll: myCompany.get('employees')}
}
});
var computer = new Computer({'employee_id':'1'})
console.log( computer.get('employee') ) // John Doe Model
console.log( computer.get('employee').get('name') ) // "John Doe"
Note: a model can have both collections and models defined.
Model attributes and collection keys should not conflict unless you are wanting to preload the collection with data. If they keys are the same, the child collection with be returned and not the model attribute. However, if the model attribute is an array it will be added to the child collection.
var jsonModelData = {
id: 1,
name: 'My Company',
// this matches the child collection key,
// so when `get(employees)` is used, this data will be converted into a real collection
employees: [
{name: 'Bob'},
{name: 'Jill'}
]
}
// using the example from above...
var myCompany = new Company(jsonModelData);
// returns collection with two employees, Bob and Jill
myCompany.get('employees');
You start by putting a list of collections
on your model. Multiple structures are supported.
collections: {
'employees': 'employees', // urlPath will be set with generic ChildCollection
'employees': EmployeesColl,
'employees': {
collection: EmployeesColl,
... // any other options listed here will be passed to collection on init
},
'employees': function(){ return EmployeesColl },
'employees': function(){ return {
collection: EmployeesColl,
}}
}
As of v0.12.0, child collections can be nested under a group name. This can be helpful when your collections grow in number and you'd like to organize.
// group-coll.js
module.exports = {
'one': Coll1
'two': Coll2
}
// model.js
collections: {
'employees': EmployeesColl,
'group': require('./group-coll')
}
// using
model.get('employees')
model.get('group/one')
model.get('group/two')
Sometimes you may want to translate a related ID to a real Model. To do this, setup models
. Like Collections, you can specify a hash or a function that returns a hash
/* assuming your model looks like:
{
id: 1,
employee_id: 1,
more: 'attrs'
}
*/
models: {
'employee': {
id: 'employee_id', // the attribute on the model,
// where to lookup the id
coll: EmployeesColl // instance
coll: 'EmployeesColl' // string name of instance on this model or global window (useful when instance not defined on load)
coll: function(id, key){ // you can also choose to lookup the model with a custom function instead
return MyLookupCollection.findASpecialModel(id)
},
// optional
fetch: true // if `id` isn't found, it will be fetched from server
}
}
// later: `.get('employee')` == model
Models will also work by having the entire model attributes rather than just and ID. Like so...
/* assuming your model looks like:
{
id: 1,
employee: {
id: 1,
name: 'John Doe',
more: 'attrs'
},
more: 'attrs'
}
*/
models: {
'employee': Employee // will turn the attrs into a real Model
}
// later: `.get('employee')` == model
[Collection/Model].parentModel
– a reference to the parent model of this collection/model
Model.refColl
- a reference to the collection they were fetched from. Only set when a child model is setup with a coll
option
Collection.urlPath
– the path to be appended to the URL of the parent model.
[Collection/Model].hasFetched
(BOOL) – Is set to true
after a fetch
happens.
[Collection/Model].isFetching
(BOOL) – Will be set to true
while the fetch
method is happening.
Collection.fetchOnce()
– Fetches collection if it has not been fetched yet. (Tests for hasFetched
)
Collection.timeSinceLastFetch()
- Time in miliseconds since last fetched
Collection.stale
- if set, fetch
will only make request once data is given ms
stale.
Collection.fetch({stale:10000})
- Overrides .stale
option to signify when the collection data becomes stale. A fetch
request will not follow through until the data is stale.
Model.needsFetching
Dot notation is supported when getting child collections and models.
// collections can return a specific model by providing an index
myCompany.get('employees.at0.name')
// aka
myCompany.get('employees').at(0).get('name')
// `first` and `last` are also supported
myCompany.get('employees.first.name')
myCompany.get('employees.last.name')
// as is model ID
myCompany.get('employees.1.name')
A benefit of using dot notation is if a nested item does not exist a fatal error will not occur.
- Add support for module.exports
- child model lookup via
coll
property can be a custom function.
- ready for commonjs loaders such as Webpack
- child
collections
can be grouped under a common key – helpful for organized modular loading - child models have reference to collection they fetched from via
refColl
id
is not longer required, they key name will be used to find the ID on the model attributes- if model
info.coll
is a string, it will also check for it on the window
- dot notation logic changed: index retrieval must be prefixed with "at". ex:
at0
. If not, a normalget()
will happen - access to parentModel via
name
will traverse all the way to the top (previously limited to first parentModel) - getOrFetch accepts
silent
option - model info defined as a function will be called in context of model
- model info.coll can be a string which will retrieved with dot notation
MIT © Kevin Jantzer - Blackstone Publishing