Skip to content

Commit

Permalink
set parents before assigning value to child nodes - issue #121
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvaray committed Mar 22, 2014
1 parent 799a224 commit 1eaa568
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 21 deletions.
65 changes: 44 additions & 21 deletions backbone-associations.js
Expand Up @@ -108,6 +108,13 @@
// and prevent redundant event to be triggered in case of cyclic model graphs.
_proxyCalls:undefined,

// Override constructor to set parents
constructor: function (attributes, options) {
// Set parent's opportunistically.
options && options.__parents__ && (this.parents = [options.__parents__]);
BackboneModel.apply(this, arguments);
},

on: function (name, callback, context) {

var result = BackboneEvent.on.apply(this, arguments);
Expand Down Expand Up @@ -293,11 +300,16 @@

} else if (relation.type === Backbone.One) {

data = val instanceof BackboneModel ? val : new relatedModel(val, relationOptions);
var hasOwnProperty = (val instanceof BackboneModel) ?
val.attributes.hasOwnProperty(idKey) :
val.hasOwnProperty(idKey);
var newIdKey = (val instanceof BackboneModel) ?
val.attributes[idKey] :
val[idKey];

//Is the passed in data for the same key?
if (currVal && data.attributes.hasOwnProperty(idKey) &&
currVal.attributes[idKey] === data.attributes[idKey]) {
if (currVal && hasOwnProperty &&
currVal.attributes[idKey] === newIdKey) {
// Setting this flag will prevent events from firing immediately. That way clients
// will not get events until the entire object graph is updated.
currVal._deferEvents = true;
Expand All @@ -306,8 +318,16 @@
data = currVal;
} else {
newCtx = true;
}

if (val instanceof BackboneModel) {
data = val;
} else {
relationOptions.__parents__ = this;
data = new relatedModel(val, relationOptions);
delete relationOptions.__parents__;
}

}
} else {
throw new Error('type attribute must be specified and ' +
'have the values Backbone.One or Backbone.Many');
Expand All @@ -331,22 +351,8 @@

}
//Distinguish between the value of undefined versus a set no-op
if (attributes.hasOwnProperty(relationKey)) {
//Maintain reverse pointers - a.k.a parents
var updated = attributes[relationKey];
var original = this.attributes[relationKey];
// Set new parent for updated
if (updated) {
updated.parents = updated.parents || [];
(_.indexOf(updated.parents, this) == -1) && updated.parents.push(this);
}
// Remove `this` from the earlier set value's parents (if the new value is different).
if (original && original.parents.length > 0 && original != updated) {
original.parents = _.difference(original.parents, [this]);
// Don't bubble to this parent anymore
original._proxyCallback && original.off("all", original._proxyCallback, this);
}
}
if (attributes.hasOwnProperty(relationKey))
this._setupParents(attributes[relationKey], this.attributes[relationKey]);
}, this);
}
// Return results for `BackboneModel.set`.
Expand Down Expand Up @@ -456,10 +462,27 @@
});
},

//Maintain reverse pointers - a.k.a parents
_setupParents: function (updated, original) {
// Set new parent for updated
if (updated) {
updated.parents = updated.parents || [];
(_.indexOf(updated.parents, this) == -1) && updated.parents.push(this);
}
// Remove `this` from the earlier set value's parents (if the new value is different).
if (original && original.parents.length > 0 && original != updated) {
original.parents = _.difference(original.parents, [this]);
// Don't bubble to this parent anymore
original._proxyCallback && original.off("all", original._proxyCallback, this);
}
},

// Returns new `collection` (or derivatives) of type `options.model`.
_createCollection: function (type, options) {
options = _.defaults(options, {model: type.model});
return new type([], _.isFunction(options) ? options.call(this) : options);
var c = new type([], _.isFunction(options) ? options.call(this) : options);
c.parents = [this];
return c;
},

// Process all pending events after the entire object graph has been updated
Expand Down
59 changes: 59 additions & 0 deletions test/associated-model.js
Expand Up @@ -1696,6 +1696,65 @@ $(document).ready(function () {

});

test("Issue #121 - Traverse child upwards", 2, function () {

var Designation = Backbone.AssociatedModel.extend({
initialize: function () {
var parent = this.parents[0];
parent.on('change:state', function (model, value, options) {
this.set('name', value == 'NY' ? 'Associate' : 'Dy. Manager')
}, this);
}
});
var Product = Backbone.AssociatedModel.extend({
initialize: function () {
var parent = this.collection.parents[0];
parent.on('change:state', function (model, value, options) {
if (value == 'NY')
this.set('name', this.get('name') + '-' + this.get('calories'));

}, this);
}
});

var Store = Backbone.AssociatedModel.extend({

relations: [
{
type: Backbone.Many,
key: "products",
relatedModel: Product
},
{
type: Backbone.One,
key: "mgr_desig",
relatedModel: Designation
}
],
//Simulate a network call
sync: function (method, model, options) {
return options.success.call(this, {
state: "NY",
id: "1",
mgr_desig: {},
products: [
{id: "1", name: "Capn Crunch", calories: '20mg'}
]
}, options);
}

});

var s = new Store();
s.fetch();

equal(s.get('products').at(0).get('name'), 'Capn Crunch-20mg');
equal(s.get('mgr_desig').get('name'), 'Associate');


});


test("Issue #124", 7, function () {

Backbone.AssociatedModel = Backbone.Model;
Expand Down

0 comments on commit 1eaa568

Please sign in to comment.