Backbone model - collection property not empty on new model creation - backbone.js

I have a model with a backbone collection as an attribute. When I create a new model and save it along with its collection to the database it all works fine.
However, when I create a new model, its collection contains the previous models values. All the other properties are blank as expected.
I've defined my model defaults like this:
defaults: {
InsertionOrderNumber: null,
ClientID: null,
CampaignName: null,
FromDate: null,
ToDate: null,
TotalBudget: null,
ManagementFee: null,
AgencyCommission: null,
SourceDocuments: new Uploads() //this is the collection
}
For some reason it seems like the same instance of the collection is being used for all new models of that type.
Any idea why this is happening?
Thanks in advance =)

As is, new Uploads() is evaluated when you define your model, not when you create a new instance. Use a function to create new collections in the defaults:
var M = Backbone.Model.extend({
defaults: function() {
return {
InsertionOrderNumber: null,
ClientID: null,
CampaignName: null,
FromDate: null,
ToDate: null,
TotalBudget: null,
ManagementFee: null,
AgencyCommission: null,
SourceDocuments: new Uploads()
};
}
});
And a demo http://jsfiddle.net/nikoshr/gzTgt/

Related

Right way to initialize backbone collection

Having a model, it presumaly would be nice to use it in a collection. However I am a bit puzzled with the collections and colleciton initialization.
May be I am missing the idea of the collection itself. So probably the first question would be can we have a collection and then a have a instances of the collection?
Secondly, how do we create an instance of the collection?
So here is a snippet of my code:
Model:
CarInsuranceApp.Models.Row = Backbone.Model.extend({
defaults: {
id: null,
....
higher_excess: null,
}
});
Collection
CarInsuranceApp.Collections.Table = Backbone.Collection.extend({
model: CarInsuranceApp.Models.Row,
// my collection methods
filter: function() {
...
Initialize collection
CarInsuranceApp.results = new CarInsuranceApp.Collections.Table( myArraOfObjects );
This gives back an output of:
child {models: Array[19], length: 19, _byId: Object, _idAttr: "id", _events: Object…}
So the question would be what is this thing child? Also an instance of the backbone.collection doesn't respond to methods, such as where, is there any way to call super methods?

backbone.js - Overridden parse() does not set model attributes

I have a model which links to two sub-models like so:
var SubModel = Backbone.Model.extend({
defaults: {
headline: null,
image_url: null,
url: null
}
});
var MainModel = Backbone.Model.extend({
defaults: {
subModelA: null,
subModelB: null,
title: null
},
urlRoot: function() {
if (this.isNew()) {
return '/mainmodel/new';
}
return '/mainmodel';
},
initialize: function() {
this.fetch();
},
parse: function(data) {
var response = {};
response.subModelA = new SubModel(data.subModelA);
response.subModelB = new SubModel(data.subModelB);
response.title = data.title;
return response;
}
});
The issue I'm currently having is that calling var mainModelInstance = new MainModel() does correctly fetch from /mainmodel/new but mainModelInstance.attributes is always a blank object {}.
var mainModelInstance = new MainModel();
mainModelInstance.attributes; // Returns {}
Here is a sample of the server's response to /mainmodel/new:
{
"title": "Politics",
"subModelA": {
"headline": "Investigators: Iran tried to smuggle suicide belts, missiles by boat into Yemen",
"url": "http://dailycaller.com/2013/02/09/yemen-minister-says-weapons-came-from-iran/",
"image_url": "http://cdn01.dailycaller.com/wp-content/uploads/2013/02/54c7d52e1a384db489ab9ea568afddb0-e1360455589316.jpg"
},
"subModelB": {
"headline": "Review: Who needs Windows RT? Acer's Iconia W510 runs the real thing",
"url": "http://arstechnica.com/gadgets/2013/02/review-who-needs-windows-rt-acers-iconia-w510-runs-the-real-thing/",
"image_url": "http://cdn.arstechnica.net/wp-content/uploads/2013/02/w510-main-640x388.jpg"
}
}
It seems as though the model's attributes are not being updated via parse. Why aren't the model's attributes being updated?
Your code might as well be working, but you are not testing it correctly
You are calling this.fetch in yout initialize method.
calling model.fetch is an asynchronous call and when you are trying to evaluate mainModelInstance.attributes, the http request call is not yet completed.
You should test this with:
var mainModelInstance = new MainModel();
mainModelInstance.on('change', function() {
console.log(mainModelInstance.toJSON());
});
or even better, dont auto fetch on initialize (its not a best practice anyway)
and use the jQuery promise pattern:
var mainModelInstance = new MainModel();
mainModelInstance.fetch().done(function () {
console.log(mainModelInstance.toJSON());
});
This is just a work in progress answer, feel free to discuss in the comments.
I would change your MainModel definition like so:
subModelA: new SubModelA(),
subModelB: new SubModelB(),
parse: function(data){
this.subModelA.set(data.subModelA);
this.subModelB.set(data.subModelB);
return data; // we keep two copies of the data, in mainModel and submodels.
}
So assuming your server responds exactly like in your answer
var model = new MainModel();
model.get('title'); // Politics
model.subModelA.get('headline'); // Investigators: Iran tr...
Then, you may have to override the save method depending on how you wish to persist things back to the server — this might work.
save: function(key, val, options) {
this.set({
subModelA: this.subModelA.toJSON(),
subModelB: this.subModelB.toJSON()
});
Backbone.Model.prototype.save.apply(this, arguments);
}
First, default attributes values must be put into the defaults option, like so:
var SubModel = Backbone.Model.extend({
defaults: {
headline: null,
image_url: null,
url: null
}
});
Then, you'll have value to save to the server when creating a new instance. That will also fill up your mainModelInstance.attributes hash.
For the parsing problem, have you logged (console.log) in what you get back from the server?

How are 'change' events handled in backbone models?

I'm trying to build a model that dynamically updates Session variables in a Meteor project. I know that plain JSON should not be stored within backbone models, so I have a Special model set up like so:
initialize : function () {
// Log the changed properties
this.on('change', function (model, options) {
for ( var i in options.changes)
this.display(i);
Session.set('NewSpecial', model);
});
},
//Attributes
defaults: {
"Product" : null,
"ShortDescription" : null,
"Category" : "food",
"Price" : new PriceModel,
"Date" : new DateModel,
"Uses" : 0,
"Tags" : [],
"Contributor" : null
},
With "Price" and "Date" being stored in their own models:
//Price model for use within Special
var PriceModel = Backbone.Model.extend({
defaults : {
"Regular" : null,
"Special" : null,
"PercentOff" : null
}
});
//Date model for use within Special
var DateModel = Backbone.Model.extend({
defaults : {
"StartTime" : null,
"EndTime" : null,
"HumanTimeRange" : null
}
});
As shown, when the attributes of the Special model change, it should call display for the attribute that changed, and then set the Session var to the model. If my DateModel or PriceModel change however, it doesn't appear to trigger a change event on the Special model. Should each "DateModel" and "PriceModel" have their own this.on('change', ...) methods that call Special.set(attribute, thisModel) methods? Or is there a different way to go about this?
I see a couple problems.
First of all, your defaults:
defaults: {
"Product" : null,
"ShortDescription" : null,
"Category" : "food",
"Price" : new PriceModel,
"Date" : new DateModel,
"Uses" : 0,
"Tags" : [],
"Contributor" : null
}
That will end up with one PriceModel, one DateModel, and one tags array being shared by all instances of that model. A defaults object is shallow copied and merged into the model's attributes, none of the values in defaults are cloned or duplicated, they're just copied over as-is. If you want distinced Price, Date, and Tags values then use a function for defaults:
defaults: function() {
return {
"Product" : null,
"ShortDescription" : null,
"Category" : "food",
"Price" : new PriceModel,
"Date" : new DateModel,
"Uses" : 0,
"Tags" : [],
"Contributor" : null
};
}
The second problem is that set has a fairly simplistic view of what change means. If you have a look at the source for set, you'll see this:
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val;
if (!silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
if (!changing) delete this._changes[attr];
}
The _.isEqual won't recognize that something has changed inside your Price or Date or that you've added or removed something from Tags. If you do things like this:
p = new PriceModel(...);
m.set('Price', p)
then m will noticed that Price has changed but if you:
p = m.get('Price');
p.set(...);
m.set('Price', p);
then m won't recognize that Price has changed; your model won't automatically bind to events on Price so it won't notice the p.set(...) call and it won't recognize m.set('Price', p) as a change since that's little more than a fancy way of saying p = p.
You can solve part of this change problem by not giving set a Tags array that came from get; make a copy, change the copy, and then hand the updated copy to set. The half can be handled by binding to "change" events on the contained Price and Date models and forwarding them similar to how collections do it, something like this:
initialize: function() {
this.attributes.Price.on(
'all',
function(ev, model, opts) { this.trigger(ev, model, opts) },
this
);
//...
}
You'd want to provide your own set implementation in case someone did a set('Price', some_new_object) and you need to rebind your forwarder.

Qooxdoo - Color Table cell based on two column values?

Is there a way to color a table cell based on two column values?
I see this in the documentation:
addNumericCondition(String condition, Integer value1, String align, String color, String style, String weight, String target)
which works great for one condition, but if you start adding more conditions -- the future conditions override it...Here's what I'm doing:
var defaultRenderer = new qx.ui.table.cellrenderer.Conditional("left", "", "", "");
defaultRenderer.addNumericCondition("==", true, null, "#000000", null, null, "Verified");
defaultRenderer.addNumericCondition("==", false, null, "#000000", null, null, "Verified");
var nawasdNotVerified = new qx.ui.table.cellrenderer.Conditional("left", "", "", "");
nawasdNotVerified.addNumericCondition("==", true, null, "#FF0000", null, null, "NAWASed");
nawasdNotVerified.addNumericCondition("==", false, null, "#FF0000", null, null, "NAWASed");
table.getTableColumnModel().setDataCellRenderer( 0, defaultRenderer);
table.getTableColumnModel().setDataCellRenderer( 0, nawasdNotVerified );
);
Thanks for the help!
The default conditional renderer does not offer such a feature. But it could be extended easily. Overriding the method _getCellStyle of the Conditional renderer gives you access to the cellInfo object which contains the raw data of the current row (cellInfo.rowData). Having that, you can decide based on whatever data you like in that row how to style your cell.

Backbone.js - Using new() in Model defaults - circular reference

Taking the following Model:
MyModel= Backbone.Model.extend({
defaults : {
myNestedModel:undefined,
},
initialize: function() {
this.set({myNestedModel: new MyNestedModel());
}
});
It has a single property named 'myNestedModel' which has the following definition:
MyNestedModel= Backbone.Model.extend({
defaults : {
myModel:undefined,
}
});
It too has a single Property name 'myModel'. Now if I create an instance of MyModel:
aModel = new MyModel();
The nested model will have been set in MyModel's initialize method. I then use JSON.stringify in a two step process:
// Use Backbone.js framework to get an object that we can use JSON.stringfy on
var modelAsJson = aModel.toJSON();
// Now actually do stringify
var modelAsJsonString = JSON.stringify(modelAsJson);
This works fine and I get the JSON representation of MyModel and it's property of MyNestedModel. The problem occurs when I use defaults, for example:
MyModel= Backbone.Model.extend({
defaults : {
new MyNestedModel(),
}
});
This causes a problem with JSON.stringify since it doesn't support circular references. I assume the circular reference is being created because all instances of MyModel share the same instance of MyNestedModel. Whereas the initialize method creates a new nested model for each instance.
Questions:
Is my understanding of defaults:{} being the 'cause' of the
problem correct?
From a question I posted recently I got the
impression I should be using defaults for all properties. If that is
the case, how should I be using defaults in the scenario presented
in this post/question?
Can someone clarify the use of defaults:{}
with regards to when the value applies, when it's overridden and
whether instances share the same default 'instances'?
Defaults is used only for attributes inside your model ( the data in the model ), and whenever you create your model it takes the values from defaults and sets the attributes. e.g.
User = Backbone.Model.extend({
defaults : {
rating : 0
}
})
User1 = new User({ name : 'jack', email : 'jack#gmail.com' });
User2 = new User({ name : 'john', email : 'john#gmail.com' });
User1.set({ rating : 2 });
Now your two models when called with toJSON will print
{
rating: 2,
name: 'jack',
email: 'jack#gmail.com'
}
{
rating: 0,
name: 'john',
email: 'john#gmail.com'
}
Since defaults is an object, every value you place there is evaluated immediately so :
defaults : {
rating : defaultRating()
}
will call defaultRating() - not everytime when you initialize the model, but immediately ( in the extend method )
You should use defaults for models where you need some data to exist on the creating of the model ( e.g. new myModel() )
In your example you have the following mistakes :
1.set a value without a property
defaults : {
PROPERTY : new Model()
}
2.you don't need such an option for your defaults - you should place there only attributes ( data ) for the model
Defaults applies always as long as it is not replaced by a new defaults in an extended model e.g.
var Model = Backbone.Model.extend({ defaults : { alpha : 'beta' } });
var myModel = Model.extend({ defaults : { beta : 'gama' } });
now your myModel when initialized will have
{ beta : 'gama' } // alpha : 'beta' will not be set as value, because it is replaced

Resources