dtx-backbone-associations
Version:
Create object hierarchies with Backbone models. Respond to hierarchy changes using regular Backbone events
1,395 lines (1,169 loc) • 129 kB
JavaScript
$(document).ready(function () {
if (!window.console) {
window.console = {};
var names = [ 'log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml',
'group', 'groupEnd', 'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd' ];
for (var i = 0; i < names.length; ++i) {
window.console[ names[i] ] = function () {
};
}
}
var attr
, child1, child2, child3, child4
, dept1
, emp
, loc1, loc2
, node1, node2, node3
, parent1
, project1, project2
;
var Location = Backbone.AssociatedModel.extend({
defaults:{
id:-1,
add1:"",
add2:null,
zip:"",
state:""
},
urlRoot:'/location'
});
//location store
var locations = new Backbone.Collection([
new Location({id:3, add1:"loc3", state:"AL"}),
new Location({id:4, add1:"loc4", state:"VA"}),
new Location({id:5, add1:"loc5", state:"CA"}),
new Location({id:6, add1:"loc6", state:"IN"}),
new Location({id:7, add1:"loc7", state:"NY"}),
new Location({id:8, add1:"loc8", state:"NY"})
]);
var map2locs = function (ids) {
if (ids instanceof Backbone.Collection) return ids;
if (_.isArray(ids) && ids.length > 0) {
if (_.isObject(ids[0])) //dummy logic to check whether array has ids or objects
return ids;
} else {
if (_.isObject(ids))
return ids;
}
ids = _.isArray(ids) ? ids.slice() : [ids];
var result = [];
_.each(ids, function (id) {
if (!id) return;
var mapped = _.find(locations.models, function (m) {
if (m.get('id') == id) return m;
});
result.push(mapped || id);
});
return result;
};
var Project = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.Many,
key:'locations',
relatedModel:Location,
map:map2locs
}
],
defaults:{
name:"",
number:0,
locations:[]
},
urlRoot:'/project'
});
var Department = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.Many,
key:'controls',
relatedModel:Project
},
{
type:Backbone.Many,
key:'locations',
relatedModel:Location,
map:map2locs
}
],
defaults:{
name:'',
locations:[],
number:-1,
controls:[]
},
urlRoot:'/department'
});
Dependent = Backbone.AssociatedModel.extend({
validate:function (attr) {
return (attr.sex && attr.sex != "M" && attr.sex != "F") ? "invalid sex value" : undefined;
},
defaults:{
fname:'',
lname:'',
sex:'F', //{F,M}
age:0,
relationship:'S' //Values {C=Child, P=Parents}
},
urlRoot:'/dependent'
});
//dept store
var store = new Backbone.Collection([new Department({number:99, name:"sales"}), new Department({number:100, name:"admin"})]);
var map2dept = function (id) {
if (id instanceof Department) return id;
if (_.isObject(id)) return id;
var found = _.find(store.models, function (m) {
return m.get('number') == id
});
return found ? found : id;
};
Employee = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'works_for',
relatedModel:Department,
map:map2dept
},
{
type:Backbone.Many,
key:'dependents',
relatedModel:Dependent
},
{
type:Backbone.One,
key:'manager',
relatedModel:'Employee'
}
],
validate:function (attr) {
return (attr.sex && attr.sex != "M" && attr.sex != "F") ? "invalid sex value" : undefined;
},
defaults:{
sex:'M', //{F,M}
age:0,
fname:"",
lname:"",
works_for:{},
dependents:[],
manager:null
},
urlRoot:'/employee'
});
Node = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'parent',
relatedModel:'Node'
},
{
type:Backbone.Many,
key:'children',
relatedModel:'Node'
}
],
defaults:{
name:''
}
});
module("Backbone.AssociatedModel", {
setup:function () {
emp = new Employee({
fname:"John",
lname:"Smith",
age:21,
sex:"M"
});
child1 = new Dependent({
fname:"Jane",
lname:"Smith",
sex:"F",
relationship:"C"
});
child2 = new Dependent({
fname:"Barbara",
lname:"Ruth",
sex:"F",
relationship:"C"
});
child3 = new Dependent({
fname:"Gregory",
lname:"Smith",
sex:"M",
relationship:"C"
});
child4 = new Dependent({
fname:"Jane",
lname:"Doe",
sex:"F",
relationship:"C"
});
parent1 = new Dependent({
fname:"Edgar",
lname:"Smith",
sex:"M",
relationship:"P"
});
loc1 = new Location({
add1:"P.O Box 3899",
zip:"94404",
state:"CA",
id:"1"
});
loc2 = new Location({
add1:"P.O Box 4899",
zip:"95502",
state:"CA",
id:"2"
});
project1 = new Project({
name:"Project X",
number:"2"
});
project2 = new Project({
name:"Project Y",
number:"2"
});
project2.get("locations").add(loc2);
project1.get("locations").add(loc1);
dept1 = new Department({
name:"R&D",
number:"23"
});
dept1.set({locations:[loc1, loc2]});
dept1.set({controls:[project1, project2]});
emp.set({"works_for":dept1});
emp.set({"dependents":[child1, parent1]});
}
});
test("initialize", 1, function () {
equal(emp.get('fname'), 'John', 'name should be John');
});
test("VERSION", 1, function () {
ok(Backbone.Associations.VERSION, "Backbone.Associations.VERSION exists");
});
test("SEPARATOR", 13, function () {
// Change separator to `~`
Backbone.Associations.SEPARATOR = "~";
equal(Backbone.Associations.SEPARATOR, "~", "Backbone.Associations.SEPERATOR should be `~`");
equal(emp.get('dependents[0]~fname'), emp.get('dependents').at(0).get('fname'));
equal(emp.get('works_for~controls[0]~locations[0]~zip'), 94404);
emp.once('change:works_for~controls[0]~locations[0]', function () {
ok(true, "Fired emp change:works_for~controls[0]~locations[0]...");
});
emp.once('change:works_for~controls[*]~locations[*]', function () {
ok(true, "Fired emp change:works_for~controls[*]~locations[*]...");
});
emp.once('change:works_for~controls[*]~locations[*]~zip', function () {
ok(true, "Fired emp change:works_for~controls[*]~locations[*]~zip...");
});
emp.get('works_for').get("locations").at(0).set('zip', 94403); // 3
// Change separator to `->`
Backbone.Associations.SEPARATOR = "->";
equal(Backbone.Associations.SEPARATOR, "->", "Backbone.Associations.SEPERATOR should be `->`");
emp.get('works_for').on('change:locations[*]', function () {
ok(true, "Fired emp.works_for change:locations[*]...");
});
emp.get('works_for').on('change:locations[0]->zip', function () {
ok(true, "Fired emp.works_for change:locations[0].zip...");
});
emp.get('works_for').on('change:controls[0]->locations[0]->zip', function () {
ok(true, "Fired emp.works_for change:controls[0]->locations[0]->zip...");
});
emp.get('works_for').get("locations").at(0).set('zip', 94405);
equal(emp.get('works_for->controls[0]->locations[0]->zip'), 94405);
equal(emp.get('dependents[0]->fname'), emp.get('dependents').at(0).get('fname'));
// Change separator to `.`
Backbone.Associations.SEPARATOR = ".";
equal(Backbone.Associations.SEPARATOR, ".", "Backbone.Associations.SEPERATOR should be `.`");
});
test("primitive attribute set operation", 2, function () {
emp.set({'age':22});
equal(emp.get("age"), 22, "emp's should be 22 years");
loc1.set({'zip':'95502'});
ok(dept1.get('locations').at(0) == loc1, "dept1's first location should be same as loc1");
});
test("nested get", 11, function () {
equal(emp.get('works_for.name'), emp.get('works_for').get('name'), 'result should be same as `get` chain');
equal(emp.get('dependents[0].fname'), emp.get('dependents').at(0).get('fname'), 'index can be defined to get model from collection, like `get("dependent[0].fname")`');
equal(emp.get('dependents[1].fname'), emp.get('dependents').at(1).get('fname'));
equal(emp.get('works_for.controls[0].locations[0].zip'), 94404);
equal(emp.get('works_for.controls[0].locations[0].zip'), emp.get('works_for').get('controls').at(0).get('locations').at(0).get('zip'));
deepEqual(emp.get('works_for.locations[1]').toJSON(), emp.get('works_for').get('locations').at(1).toJSON());
equal(emp.get('dependents[1000].fname'), undefined, "result should be `undefined` if indexed model in not present in collection");
equal(emp.get('works_for.unknown'), undefined, "result should be `undefined` if attribute is not available");
equal(emp.get("1"), undefined);
equal(emp.get('dependents[1]."1"'), undefined);
equal(emp.get('works_for.""'), undefined);
});
test("nested set", 6, function () {
equal(emp.get('works_for.name'), 'R&D');
emp.set('works_for.name', 'Marketing');
equal(emp.get('works_for.name'), 'Marketing');
emp.set('works_for.locations[0].zip', 94403);
equal(emp.get('works_for.locations[0].zip'), 94403, "nested `set` for model in collection should be same as normal `set`");
emp.set('dependents[0].sex', 'X', {validate:true});//validate test
notEqual(emp.get('dependents[0].sex'), 'X', "validate test should be passed in nested `set` while `validate:true` is passed");
emp.set({
'designation':'Senior Manager',
'works_for.controls[0].locations[0].zip':90909,
'dependents[1000].fname':'outofindex',
'dependents[1].fname':'John'
});
equal(emp.get('dependents[1].fname'), 'John');
emp.on('change:works_for.name', function () {
ok(false);
});
emp.set({
'works_for.name':'Marketing'
}, {silent:true});
emp.set({
'wrongpath.path2.works_for.name':'mip'
});
ok(true);
});
test("function can also be passed as value of attribute on set", 2, function () {
var dept2 = function () {
return {
name:"Marketing",
number:"24"
};
};
equal(emp.get("works_for").get("name"), "R&D", "department name should be R&D");
emp.set({"works_for":dept2});
equal(emp.get("works_for").get("name"), "Marketing", "department name should be set to Marketing");
});
test("unset", 3, function () {
emp.get('works_for').unset('locations');
equal(emp.get('works_for').get('locations'), void 0, "locations should be void");
emp.unset('works_for.locations');
equal(emp.get('works_for.locations'), void 0, "locations should be void");
emp.unset('dependents');
equal(emp.get('dependents'), void 0, "`dependents` should be unset");
});
test("setDefaults", 2, function () {
emp.get("works_for").set({'number':5});
equal(emp.get("works_for").get('number'), 5, "number has new value");
emp.set({"works_for":dept1.defaults});
equal(emp.get("works_for").get('number'), -1, "number has default value");
});
test("invalid relations", 5, function () {
var em1 = Backbone.AssociatedModel.extend({
relations:[
{
type1:Backbone.One, //no type specified
key:'parent',
relatedModel:'Node'
}
]
});
try {
var emi1 = new em1;
emi1.set('parent', {id:1});
} catch (e) {
equal(e.message === "type attribute must be specified and have the values Backbone.One or Backbone.Many", true)
}
var em2 = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.Oxne, //wrong value of type
key:'parent',
relatedModel:'Node'
}
]
});
try {
var em2i = new em2;
em2i.set('parent', {id:1});
} catch (e) {
equal(e.message === "type attribute must be specified and have the values Backbone.One or Backbone.Many", true)
}
var em3 = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'parent',
collectionType:em2//no RelatedModel specified
}
]
});
try {
var em3i = new em3;
em3i.set('parent', {id:1});
} catch (e) {
equal(e.message === "specify a relatedModel for Backbone.One type", true)
}
var em4 = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.Many,
key: 'parent',
collectionType: em2//RelatedModel specified
}
]
});
try {
var em4i = new em4;
em4i.set('parent', {id: 1});
} catch (e) {
equal(e.message === "type is of Backbone.Model. Specify derivatives of Backbone.Collection", true)
}
var Owner = Backbone.OriginalModel.extend();
var House = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'owner',
relatedModel:Owner
}
]
});
var owner = new Owner;
try {
new House({owner:owner});
} catch (e) {
equal(e.message === "specify an AssociatedModel or Backbone.Model for Backbone.One type", true)
}
});
test("escape", 1, function () {
emp.get('works_for').get("locations").at(0).set({'add1':'<a>New Address</a>'});
equal(_.escape(emp.get('works_for').get("locations").at(0).get("add1")), '<a>New Address</a>', "City should be in HTML-escaped version");
});
test("has", 5, function () {
ok(emp.get("works_for").get("locations").at(0).has('add2') == false, "Add2 is undefined in department address");
strictEqual(emp.has("works_for.locations[0].add2"), false);
emp.get("works_for").get("locations").at(0).set({'add2':'Add2 value'});
strictEqual(emp.has("works_for.locations[0].add2"), true);
strictEqual(emp.get("works_for.locations").at(0).has('add2'), true, "Add2 is defined in department address");
strictEqual(emp.has('add2'), false, "Add2 is undefined in patient");
});
test("validate", 2, function () {
emp.get('dependents').at(0).set({sex:"X"}, {validate:true});
equal(emp.get('dependents').at(0).get('sex'), 'F', "sex validation prevents values other than M & F");
emp.get('dependents').at(0).set({sex:"M"}, {validate:true});
equal(emp.get('dependents').at(0).get('sex'), 'M', "sex validation allows legal values - M & F");
});
test("clear", 2, function () {
emp.clear();
equal(emp.get('works_for'), void 0, "Deparment should be set to undefined");
equal(emp.get('dependents'), void 0, "Dependents should be undefined");
});
test("clone", 7, function () {
var emp2 = emp.clone();
equal(emp.get('fname'), 'John');
equal(emp.get('works_for').get('name'), 'R&D');
equal(emp2.get('fname'), emp.get('fname'), "fname should be the same on the clone.");
equal(emp2.get('works_for').get('name'), emp.get('works_for').get('name'), "name of department should be the same on the clone.");
deepEqual(emp.toJSON(), emp2.toJSON(), "emp should be the same on the clone");
emp.set({
works_for:{
name:'Marketing',
number:'24'
}
});
equal(emp.get('works_for').get('name'), 'Marketing');
equal(emp2.get('works_for').get('name'), 'R&D', "Changing a parent attribute does not change the clone.");
});
test("change, hasChanged, changedAttributes, previous, previousAttributes", 8, function () {
emp.on('change', function () {
ok(emp.hasChanged('works_for'), "emp->change, employee has changed");
});
emp.on('change:works_for', function () {
equal(true, emp.hasChanged());
equal(true, emp.hasChanged('works_for'));
equal(emp.get('works_for').hasChanged(), false, '`hasChanged` for `works_for` returns false as it is new object');
equal(emp.hasChanged('works_for'), true);
equal(emp.get('works_for').changedAttributes(), false, 'changedAttributes for `works_for` returns false as it is new object');
equal(emp.previous('works_for').get('name'), 'R&D');
equal(emp.previousAttributes().works_for.get('name'), 'R&D', 'previousAttributes is correct');
});
emp.set('works_for', {name:'Marketing', number:'24'});
});
test("change : second model of nested collection", 1, function () {
dept1.on('change:locations[0]', function () {
ok(false, 'First model in nested collection should not change');
});
dept1.on('change:locations[1]', function () {
ok(true, 'Second model in nested collection should change');
});
loc2.set('zip', '97008');
});
test("change : all attributes get updated in an atomic operation", 8, function () {
emp.on('change', function () {
equal(emp.get('works_for').get('name'), 'Marketing');
equal(emp.get('works_for').get('number'), '24');
equal(emp.previous('works_for').get('name'), 'R&D');
equal(emp.previous('works_for').get('number'), '23');
});
emp.on('change:works_for', function () {
equal(emp.get('works_for').get('name'), 'Marketing');
equal(emp.get('works_for').get('number'), '24');
equal(emp.previous('works_for').get('name'), 'R&D');
equal(emp.previous('works_for').get('number'), '23');
});
emp.set('works_for', {name:'Marketing', number:'24'});
});
test("change : all attributes get updated in an atomic operation for AssociatedModel properties ", 14, function () {
emp.on('change', function () {
equal(emp.get('lname'), 'Bond');
equal(emp.get('fname'), 'James');
equal(emp.previous('fname'), 'John');
equal(emp.previous('lname'), 'Smith');
equal(emp.get('works_for').get('number'), '24');
equal(emp.previous('works_for').get('name'), 'R&D');
equal(emp.previous('works_for').get('number'), 23);
});
emp.on('change:works_for', function () {
equal(emp.get('lname'), 'Bond');
equal(emp.get('fname'), 'James');
equal(emp.previous('fname'), 'John');
equal(emp.previous('lname'), 'Smith');
equal(emp.get('works_for').get('number'), '24');
equal(emp.previous('works_for').get('name'), 'R&D');
equal(emp.previous('works_for').get('number'), 23);
});
emp.set({works_for:{name:'Marketing', number:'24'}, fname:"James", lname:"Bond"});
});
test("child `change`", 17, function () {
emp.on('change', function () {
ok(true, "Fired emp change...");
});
emp.on('change:works_for', function () {
ok(true, "Fired emp change:works_for...");
});
emp.on('change:works_for.name', function () {
equal(true, emp.get("works_for").hasChanged());
equal(true, emp.hasChanged());
equal(true, emp.hasChanged("works_for"));
var changed = emp.changedAttributes();
deepEqual(changed['works_for'].toJSON(), emp.get("works_for").toJSON());
equal(emp.get("works_for").previousAttributes()["name"], "R&D");
equal(emp.get("works_for").previous("name"), "R&D");
var diff = emp.get('works_for').toJSON();
diff.locations[0].zip = 94405;
changed = emp.get('works_for').changedAttributes(diff);
equal(changed.locations[0].zip, 94405);
ok(true, "Fired emp change:works_for.name...");
});
emp.get('works_for').on('change', function () {
ok(true, "Fired works_for change...");
});
emp.get('works_for').on('change:name', function () {
ok(true, "Fired works_for dept:name change...");
});
emp.set({'works_for.name':'Marketing'});//4+7
emp.set('works_for', {name:"Marketing", number:29});//2
emp.set('works_for', undefined);//2
emp.set('works_for', dept1);//2
emp.set('works_for', dept1);//0
});
test("child `change in collection`", 18, function () {
emp.get('works_for').get('locations').at(0).on('change:zip', function () {
ok(true, "Fired works_for locations[0]:zip change...");
});
emp.get('works_for').get('locations').at(0).on('change', function () {
equal(true, emp.get('works_for').hasChanged());
equal(true, emp.hasChanged());
var changed = emp.get('works_for').changedAttributes();
equal(changed['locations'].at(0).changed['zip'], 94403);
equal(changed['controls'].at(0).changed['locations'].at(0).changed['zip'], 94403);
ok(true, "Fired works_for locations0 change...");
});
emp.get('works_for').on('change:locations[*]', function () {
ok(true, "Fired emp.works_for change:locations[*]...");
});
emp.get('works_for').on('change:locations[0].zip', function () {
ok(true, "Fired emp.works_for change:locations[0].zip...");
});
emp.get('works_for').on('change:locations[0]', function () {
ok(true, "Fired emp.works_for change:locations[0]...");
});
emp.get('works_for').on('change:controls[0].locations[0].zip', function () {
ok(true, "Fired emp.works_for change:controls[0].locations[0].zip...");
});
emp.get('works_for').on('change:controls[0].locations[0]', function () {
ok(true, "Fired emp.works_for change:controls[0].locations[0]...");
});
emp.get('works_for').on('change:controls.locations', function () {
ok(true, "Fired emp.works_for change:controls.locations...");
});
emp.on('change:works_for.locations[0].zip', function () {
ok(true, "Fired emp change:works_for.locations[0].zip...");
});
emp.on('change:works_for.locations[0]', function () {
ok(true, "Fired emp change:works_for.locations[0]...");
});
emp.on('change:works_for.locations[*]', function () {
ok(true, "Fired emp change:works_for.locations[*]...");
});
emp.on('change:works_for.controls[0].locations[0].zip', function () {
ok(true, "Fired emp change:works_for.controls[0].locations[0].zip...");
});
emp.on('change:works_for.controls[0].locations[0]', function () {
ok(true, "Fired emp change:works_for.controls[0].locations[0]...");
});
emp.on('change:works_for.controls[*].locations[*]', function () {
ok(true, "Fired emp change:works_for.controls[*].locations[*]...");
});
emp.on('change:works_for.controls[*].locations[*].zip', function () {
ok(true, "Fired emp change:works_for.controls[*].locations[*].zip...");
});
emp.get('works_for').get("locations").at(0).set('zip', 94403);
});
test("collection `*` change", 12, function () {
emp.on('change:works_for.controls[0].locations[0]', function () {
ok(true, "Fired emp change:works_for.controls[0].locations[0]");
});
emp.on('change:works_for.controls[*].locations[*]', function () {
ok(true, "Fired emp change:works_for.controls[*].locations[*]");
});
emp.on('change:works_for.controls[*].locations[*].zip', function () {
ok(true, "Fired emp change:works_for.controls[*].locations[*].zip");
});
emp.on('change:works_for.controls[1].locations', function () {
ok(true, "Fired emp change:works_for.controls[1].locations");
});
emp.on('change:works_for.controls[*].locations', function () {
ok(true, "Fired emp change:works_for.controls[*].locations");
});
emp.get('works_for.controls[0].locations[0]').set('zip', 94406); //3
emp.get('works_for.controls[1].locations[0]').set('add1', 'new changed address'); //1
emp.set('works_for.controls[1].locations', undefined); // 2
emp.set('works_for.controls[1].locations', [loc1]); // 2
emp.on('change:works_for.locations[0]', function () {
ok(false, "emp change:works_for.locations[0] should not be fired");
});
emp.on('change:works_for.locations[1]', function () {
ok(true, "Fired emp change:works_for.locations[1]");
});
emp.on('change:works_for.locations[*]', function () {
ok(true, "Fired emp change:works_for.locations[*]");
});
emp.get('works_for.locations[1]').set('zip', 94407); //2
emp.get('works_for').on('change:locations', function () {
ok(true, "Fired emp.works_for change:locations...");
});
emp.set('works_for.locations', undefined); //1
emp.set('works_for.locations', [loc1]); //1
});
test("collection `*` add", 5, function () {
emp.on('add:works_for.controls[0].locations', function () {
ok(true, "Fired emp add:works_for.controls[0].locations...");
});
emp.on('add:works_for.controls[1].locations', function () {
ok(true, "Fired emp add:works_for.controls[1].locations...");
});
emp.on('add:works_for.controls[*].locations', function () {
ok(true, "Fired emp add:works_for.controls[*].locations...");
});
emp.on('add:works_for.locations', function () {
ok(true, "add:works_for.locations");
});
emp.on('add:works_for.locations[*]', function () {
ok(false, "emp add:works_for.location[*] should not be fired.");
});
emp.get('works_for.controls[0].locations').add(loc2); //2
emp.get('works_for.controls[1].locations').add({ //2
id:3,
add1:"loc3"
});
emp.get('works_for.locations').add({ //1
id:4,
add1:"loc4"
});
});
test("collection `*` remove", 5, function () {
emp.get('works_for.controls[0].locations').add(loc2);
emp.get('works_for.locations').add(loc2);
emp.on('remove:works_for.controls[0].locations', function () {
ok(true, "Fired emp remove:works_for.controls[0].locations...");
});
emp.on('remove:works_for.controls[1].locations', function () {
ok(true, "Fired emp remove:works_for.controls[1].locations...");
});
emp.on('remove:works_for.controls[*].locations', function () {
ok(true, "Fired emp remove:works_for.controls[*].locations...");
});
emp.on('remove:works_for.locations', function () {
ok(true, "Fired remove:works_for.locations");
});
emp.on('remove:works_for.locations[*]', function () {
ok(false, "emp remove:works_for.location[*] should not be fired.");
});
emp.get('works_for.controls[0].locations').remove(loc2); //2
emp.get('works_for.controls[1].locations').remove(loc2); //2
emp.get('works_for.locations').remove(loc2); //1
});
test("collection `*` reset", 3, function () {
emp.on('reset:works_for.controls[0].locations', function () {
ok(true, "Fired emp reset:works_for.controls[0].locations");
});
emp.on('reset:works_for.controls[*].locations', function () {
ok(true, "Fired emp reset:works_for.controls[*].locations");
});
emp.on('reset:works_for.locations', function () {
ok(true, "Fired reset:works_for.locations");
});
emp.get('works_for.controls[0].locations').reset();
emp.get('works_for.locations').reset();
});
test("collection `*` destroy", function () {
emp.on("destroy:works_for.controls[0].locations", function () {
ok(true, "Fired emp destroy:works_for.controls[0].locations");
});
emp.on("destroy:works_for.controls[*].locations", function () {
ok(true, "Fired emp destroy:works_for.controls[*].locations");
});
var loc = emp.get('works_for.controls[0].locations[0]');
loc.sync = function (method, model, options) {
return options.success.call(this, null, options);
};
loc.destroy();
});
test("collection `*` sort", function () {
Backbone.Associations.EVENTS_NC = true;
emp.on('change:works_for.controls[0].locations', function () {
ok(true, "Fired emp change:works_for.controls[0].locations");
});
emp.on('change:works_for.controls[*].locations', function () {
ok(true, "Fired emp change:works_for.controls[*].locations");
});
emp.get('works_for').on('change:controls[0]', function () {
ok(true, "Fired emp change:works_for.controls[0]");
});
emp.on('nested-change', function () {
ok(true, "Fired emp nested-change");
});
emp.set('works_for.controls[0].locations', locations); //4
// check length and add comparator
var locCol = emp.get('works_for.controls[0].locations');
equal(locCol.length, 6, "location collection's length should be 6.");
locCol.comparator = function (l) {
return l.get("state");
};
emp.on('sort:works_for.controls[0].locations', function () {
ok(true, "Fired emp sort:works_for.controls[0].locations");
});
emp.on('sort:works_for.controls[*].locations', function () {
ok(true, "Fired emp sort:works_for.controls[*].locations");
});
emp.on('sort:works_for.controls.locations', function () {
ok(false, "emp sort:works_for.controls.locations should not be fired");
});
emp.get('works_for.controls[0].locations').sort();
});
test("child `nested-change`", 9, function () {
Backbone.Associations.EVENTS_NC = true;
emp.get('works_for').get('locations').on('change', function () {
ok(true, "Regular backbone change event from collections...");
});
emp.get('works_for').on('nested-change', function () {
if (arguments[0] == "controls[0].locations[0]")
ok(true, "Fired emp.works_for change:controls[0].locations[0]...");
if (arguments[0] == "locations[0]")
ok(true, "Fired emp.works_for change:locations[0]...");
equal(arguments[2].dummy, true);
});
emp.on('nested-change', function () {
if (arguments[0] == "works_for.controls[0].locations[0]")
ok(true, "Fired emp change:works_for.controls[0].locations[0]...");
if (arguments[0] == "works_for.locations[0]")
ok(true, "Fired emp change:works_for.locations[0]...");
equal(arguments[2].dummy, true);
});
emp.get('works_for').get("locations").at(0).set('zip', 94403, {dummy:true});
});
test("Set closure scope correctly - while setting BB Collection & Model instances directly", 5, function () {
var Location = Backbone.AssociatedModel.extend({
defaults:{
name:"",
zip:""
}
});
var Group = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'meetup',
relatedModel:Location
}
],
defaults:{
meetup:undefined,
name:""
}
});
var loc1 = new Location({name:"CA", zip:"94404", id:1});
var loc2 = new Location({name:"OH", zip:"92201", id:2});
var cb = function () {
ok(true)
};
var g1 = new Group({name:"AO", meetup:loc1});
g1.on("change:meetup", cb);
var g2 = new Group({name:"SO", meetup:loc2});
g2.on("change:meetup", cb);
g1.set('meetup', loc2); //1
g2.set('meetup.zip', '93303'); //2 : Should fire change for both g1 and g2
g1.set('meetup', undefined); //1
g2.set('meetup.zip', '93304'); //1 : Should fire change for g2 only
});
test("Fetch collection repeatedly: Issue#47", 3, function () {
var User = Backbone.AssociatedModel.extend({
defaults:{
name:"",
email:""
}
});
var Container = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.Many,
key:'xxx',
relatedModel:User
}
],
defaults:{
'xxx':[],
name:""
}
});
var ContainerCollection = Backbone.Collection.extend({
model:Container,
url:"fc",
sync:function (resp, model, options) {
options.success(
[
{
"id":425,
"name":"I am so cool",
"xxx":[
{
"id":418,
"email":"art@garfunkel.com"
}
]
}
]
);
}
});
var f = new ContainerCollection();
f.on('add:xxx', function () {
ok(true)
});
f.fetch();
f.at(0).get('xxx').add({name:"n1"}); //Fire add:xxx
f.at(0).get('xxx').add({name:"n4"}); //Fire add:xxx
f.fetch();
f.at(0).get('xxx').add({name:"n2"}); //Fire add:xxx
});
test("Polymorphic Associate : Issue#23", 4, function () {
var Models = {};
var findPolyMorphicType = Models.findPolyMorphicType = function (relation, attributes) {
var key = relation.key + '_type';
return Models[attributes[key] || this.get(key)];
};
Models.Job = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'organizable',
relatedModel:findPolyMorphicType
}
]
});
Models.Comment = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'commentable',
relatedModel:findPolyMorphicType
}
]
});
Models.Company = Backbone.AssociatedModel.extend({
});
Models.Department = Backbone.AssociatedModel.extend({
});
Models.Article = Backbone.AssociatedModel.extend({
});
Models.StatusUpdate = Backbone.AssociatedModel.extend({
});
var cjob = new Models.Job({organizable_type:'Company', name:"J1", organizable:{name:"Google"}});
var djob = new Models.Job({organizable_type:'Department', name:"J2", organizable:{name:"Google Reader"}});
var articleComment = new Models.Comment({
commentable_type:'Article',
name:"c1",
commentable:{body:"Wonderful post!"}
});
var statusComment = new Models.Comment({
commentable_type:'StatusUpdate',
name:"c2",
commentable:{body:"Why are you updating your status with pointless crap?"}
});
equal(cjob.get('organizable') instanceof Models.Company, true);
equal(djob.get('organizable') instanceof Models.Department, true);
equal(articleComment.get('commentable') instanceof Models.Article, true);
equal(statusComment.get('commentable') instanceof Models.StatusUpdate, true);
});
test("Fetch collection with reset: Issue#45", 5, function () {
var Product = Backbone.AssociatedModel.extend({
defaults:{
name:undefined, // String
productId:undefined // String
}
});
var Category = Backbone.AssociatedModel.extend();
var Brand = Backbone.AssociatedModel.extend();
var Vendor = Backbone.AssociatedModel.extend();
var Tag = Backbone.AssociatedModel.extend();
var SearchFacet = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.Many,
key:'categories',
relatedModel:Category
},
{
type:Backbone.Many,
key:'brands',
relatedModel:Brand
},
{
type:Backbone.Many,
key:'vendors',
relatedModel:Vendor
},
{
type:Backbone.Many,
key:'tags',
relatedModel:Tag
}
]
});
var counter = 1;
var SearchResult = Backbone.AssociatedModel.extend({
relations:[
{
type:Backbone.One,
key:'searchFacet',
relatedModel:SearchFacet
},
{
type:Backbone.Many,
key:'products',
relatedModel:Product
}
],
sync:function (method, model, options) {
return options.success.call(this, {
id:1,
products:[
{
id:counter,
name:'product' + counter,
productId:'productId' + counter++
},
{
id:counter,
name:'product' + counter,
productId:'productId' + counter++
},
{
id:counter,
name:'product' + counter,
productId:'productId' + counter++
},
{
id:counter,
name:'product' + counter,
productId:'productId' + counter++
}
],
searchFacet:{
id:'sf1',
categories:[
{
name:'category' + counter++
}
],
brands:[
{
name:'brand' + counter++
}
],
tags:[
{
name:'tag' + counter++
}
],
vendors:[
{
name:'vendor' + counter++
}
]
}
});
},
urlRoot:'/search'
});
var searchResult = new SearchResult();
searchResult.on("reset:products", function (e) {
ok(false, 'searchResult reset:products should not fired.'); //0
});
searchResult.on("reset:searchFacet.tags", function (e) {
ok(false, 'searchResult reset:searchFacet.tags should not fired.'); //0
});
searchResult.fetch();
equal(searchResult.get('products').length, 4, "searchResult.products.length should be 4."); //1
searchResult.fetch();
equal(searchResult.get('products').length, 4, "searchResult.products.length should be 4."); //1
searchResult.off();
searchResult.on("reset:products", function (e) {
ok(true, 'searchResult reset:products fired.'); //1
});
searchResult.on("reset:searchFacet.tags", function (e) {
ok(true, 'searchResult reset:searchFacet.tags fired.'); //1
});
searchResult.fetch({reset:true});
equal(searchResult.get('products').length, 4, "searchResult.products.length should be 4."); //1
});
test("IdAttribute: Issue#80", 2, function () {
var User = Backbone.AssociatedModel.extend({
idAttribute: "_id"
});
var Container = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'owner',
relatedModel: User
}
],
defaults: {
users: []
}
});
var home = new Container();
var u = new User({_id: 1, name: "Chip Lay"});
home.set('owner', u);
var u1 = new User({_id: 1, name: "Chip Lay"});
home.set('owner', u1);
equal(home.get('owner') == u, true); //Uses the same id to refer the same object
User = Backbone.AssociatedModel.extend();
Container = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'owner',
relatedModel: User
}
],
defaults: {
users: []
}
});
home = new Container();
u = new User({id: 1, name: "Chip Lay"});
home.set('owner', u);
u1 = new User({id: 1, name: "Chip Lay"});
home.set('owner', u1);
equal(home.get('owner') == u, true); //Uses the same id to refer the same object
});
test("Global scope support: Issue#98", 3, function () {
var scope1 = {};
var scope2 = {};
Backbone.Associations.scopes.push(scope1);
Backbone.Associations.scopes.push(scope2);
scope1.User = Backbone.AssociatedModel.extend({
defaults: {omid: "s1.u"}
});
scope2.User = Backbone.AssociatedModel.extend({
defaults: {omid: "s2.u"}
});
var Container = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'owner',
relatedModel: 'User'
}
],
defaults: {
users: []
}
});
var home = new Container();
home.set('owner', {id: 1, name: "Chip Lay"});
equal(home.get('owner').get('omid') == 's1.u', true);
Backbone.Associations.scopes = [];
Backbone.Associations.scopes.push(scope2);
Backbone.Associations.scopes.push(scope1);
home.set('owner', {id: 2, name: "Chip Lay"});
equal(home.get('owner').get('omid') == 's2.u', true);
//scope1 takes precedence over scope 2
home.relations[0].scope = scope1;
home.set('owner', {id: 3, name: "Chip Lay"});
equal(home.get('owner').get('omid') == 's1.u', true);
});
test("Remote Key: Issue#78", 11, function () {
var Location = Backbone.AssociatedModel.extend({});
var Job = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'location',
isTransient: true,
relatedModel: Location
}
]
});
var User = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'job',
remoteKey: 'job_attributes',
relatedModel: Job
}
]
});
var MultiContainer = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.Many,
key: 'owner',
remoteKey : 'owner_attributes',
relatedModel: User
}
]
});
var home = new MultiContainer();
var u = new User({name: "Chip Lay", job: new Job({name: 'dream_job', location: new Location({name: 'loc1'})})});
var u1 = new User({name: "John Sr"});
home.set('owner', [u,u1]);
equal(home.toJSON()['owner_attributes'].length,2);
equal(home.toJSON()['owner'],undefined);
var UniContainer = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'owner',
remoteKey : 'owner_attributes',
relatedModel: User
}
]
});
var home2 = new UniContainer();
home2.set('owner', u);
equal(home2.toJSON()['owner_attributes']['name'],'Chip Lay');
equal(home2.toJSON()['owner_attributes']['job'], undefined);
equal(home2.toJSON()['owner_attributes']['job_attributes']['name'], 'dream_job');
equal(home2.toJSON()['owner_attributes']['job_attributes']['location'], undefined);
equal(home2.toJSON()['owner'],undefined);
var home3 = new UniContainer();
equal(home3.toJSON()['owner_attributes'],undefined);
equal(home3.toJSON()['owner'],undefined);
var home4 = new MultiContainer();
equal(home4.toJSON()['owner_attri