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
Related
I'm having an issue which makes me think I might be misinterpreting the use of the ExtJS class architecture a bit. Herewith an example of a simplified version which demonstrates the issue
Ext.define('Person', {
name : 'Default',
inventory : [],
addInventoryItem : function(item) {
Ext.Array.push(this.inventory, item);
},
inventoryList : function() {
console.log("Inventory: " + JSON.stringify(this.inventory));
},
setName : function(name) {
this.name = "name";
}
});
Ext.define('Student', {
extend : 'Person'
});
alex = Ext.create('Student');
alex.setName("Alex");
alex.addInventoryItem("Knifes");
alex.inventoryList();
david = Ext.create('Student');
david.setName("David");
david.addInventoryItem("Forks");
david.inventoryList();
Output:
Inventory: ["Knifes"]
Inventory: ["Knifes","Forks"]
Expected Output:
Inventory: ["Knifes"]
Inventory: ["Forks"]
I would've expected the actual result in the event of overriding the class, but fail to understand why my second instance affects the superclass, and in turn reflects changes made by my first instance.
The problem is that there is a single inventory array shared between all instances of your class. The array is created once at the time the class is defined, then each instance just gets a reference to that one array.
Basically you need to give each Person instance its own inventory array on creation, like this:
Ext.define('Person', {
...
constructor: function() {
this.inventory = [];
},
...
});
What is the idiomatic way to create a new Instance of an associated Model inside a Controller?
Motivation:
If I change the Class of my Model the code below doesn't get updated and I have to find all the breakages and fix them manually.
If I could use the the association in the controller, it gets updated automatically, but I can't find any documentation on the magical incantation to create a new instance from the controller reference.
Here is the relevant parts of my controller:
Ext.define('AdminApp.controller.SelectFilesController', {
extend: 'Ext.app.Controller',
models: [
'File'
],
// lots of stuff snipped for brevity
onFilefieldChange: function(filefield, value, eOpts) {
Ext.each(Ext.getDom('select-upload-button-fileInputEl').files, function(f) {
var fm = new AdminApp.model.File({
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
}
});
// lots more stuff snipped for brevity
}
What I want to know:
I found this in the documentation:
It’s important to note that the getters for both views and models
return a reference to the class (requiring you to instantiate your own
instances), while the getters for stores and controllers return actual
instances.
I can't find any examples of what the code to instantiate an instance should look like.
After much trial and error and just plain educated guessing ...
I figured out what the syntax should be.
First you need to create a reference to the class of the Model.
By convention this is the name of the model prefixed with get and suffixed with Model.
var fmc = this.getFileModel();
I had to do this outside the Ext.each() function that visits each item because the this reference pointed to an HTML5 File object instead of my Controller.
Then you simply use a regular Ext.create(fmc, { /* config */ } to get a new instance like so.
var fm = Ext.create(fmc, {
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
So the correct version of the onFilefieldChange function in the question should look like:
onFilefieldChange: function(filefield, value, eOpts) {
var fmc = this.getFileModel();
Ext.each(Ext.getDom('select-upload-button-fileInputEl').files, function(f) {
var fm = Ext.create(fmc,{
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
}
});
}
I am trying to search a collection for a model attribute and then grab and return the entire model ?
var myModel = Backbone.Model.extend({
defaults: {
a: '',
b: '',
c: '',
d: '',
e: ''
}
});
My collection has around 100 of myModels.
I am trying to search through the collection by a, find it and then return the entire myModel of a so I can access the other attributes ?
If I understand your question correctly, you want to use the where method on Backbone collections, here in the docs:
http://backbonejs.org/#Collection-where
So, given an instance of MyCollection called myCollection that has MyModels in it, you can say:
var foundModels = myCollection.where({a:'some value'});
and foundModels will contain an array of the models you seek
BTW, if you are doing a more complex search, use the filter method instead, passing a function as the first argument that returns true on the desired match:
var modelsWhoseAStartsWithA = myCollection.filter(function(anyModel) {
var startsWithA = new RegExp(/^[aA]/);
return startsWithA.test(anyModel.get('a'));
});
I'm trying to set an array value in a backbone.js model initialize function. In the line that starts with 'this.set...' I get a 'unexpected string' error. Is it not possible to set array values this way?
Thanks!
var bGenericItem = Backbone.Model.extend({
defaults: {
attrArray: new Array({'item_id': '', 'type': '', 'name':''})
},
initialize: function(){
// Set the id to cid for now
this.set({ attrArray["item_id"]: this.cid });
}
});
What you're trying to do doesn't make any sense. Your defaults is an array which holds a single object:
defaults: {
attrArray: [
{ item_id: '', type: '', name: '' }
]
},
You'd use an array if you wanted to hold a list of attribute objects. But, if you had a list of attribute objects, which one's item_id would you expect attrArray['item_id'] to refer to? Are you assuming that attrArray will always be initialized to the default value and that no one would ever send an attrArray in as part of your model's initial data? If so, you'd want something more like this:
// Use a function so that each instance gets its own array,
// otherwise the default array will be attached to the prototype
// and shared by all instances.
defaults: function() {
return {
attrArray: [
{ item_id: '', type: '', name: '' }
]
};
},
initialize: function() {
// get will return a reference to the array (not a copy!) so
// we can modify it in-place.
this.get('attrArray')[0]['item_id'] = this.cid;
}
Note that you'll run into some issues with array attributes that require special handling:
get('attrArray') will return a reference to the array that is inside the model so modifying that return value will change the model.
Things like a = m.get('attrArray'); a.push({ ... }); m.set('attrArray', a) won't work the way you expect them to, the set won't notice that the array has changed (because it hasn't, a == a is true after all) so you won't get "change" events unless you clone the attrArray somewhere between get and set.
There are several problems with your code
1: The defaults setting is an object literal which means the value that you assign to it is set as soon as it's defined. You need to set your defaults to a function, instead of a literal value. This will ensure each model instance gets it's own copy of the default values, instead of sharing a copy across every model instance.
2: You should also not use new Array, just use an array literal syntax []. But you're not really using an array in this code, so I removed the array wrapper for now.
3: You can't access attrArray directly. You must get it from the model's attributes and then update it
var bGenericItem = Backbone.Model.extend({
defaults: function(){
return {
attrArray: {'item_id': '', 'type': '', 'name':''}
};
},
initialize: function(){
// Set the id to cid for now
var arr = this.get("attrArray");
arr["item_id"] = this.cid;
}
});
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.