Backbone.js model property constants - is this good practice? - backbone.js

I have trouble keeping track of the properties of a Model instance. For example I have declarations such as:
MyModel = Backbone.Model.extend({});
Then in my views, I'm often doing something like:
var someVal = this.model.get('someProperty');
I was beginning the task of moving a View's strings into constants by encapsulating them in an object and passing it as a second parameter to a View's constructor, for example:
SchemaOptionsView = Backbone.View.extend(
{ /* Body of View here as usual */},
{
TEMPLATE: '#View-Template',
INPUT_REQUIRED:'required'
});
When I got to thinking, I could solve two problems by doing this with my Models:
MyModel = Backbone.Model.extend(
{ /* Body of Model here as usual */},
{
PROPERTY_1 = 'p1',
PROPERTY_2 = 'p2'
});
I could then use the constant to retrieve the property value:
var someVal = this.model.get(MyModel.PROPERTY_1);
This would allow me to clearly see the properties a Model instance contains, and also moves a string value into a constant in an appropriate place.
I have not seen this recommended or in any examples. Have I missed something? What do people think about this idea?

This is solving a non problem. Your models should have well defined attributes - we use the defaults hash to enumerate all the available attributes of our models. Not only does this provide a catalog of the attributes that a model possesses but it also provides a way to override a non-validated input.

Related

Backbone JS - Combination of collection, views and models

I had the following setup now in three different projects, so I thought maybe it's worth asking here. There's a chance I'm not the only person wondering about this.
So there it comes:
I have a list of objects
The list items have a special function like draggable or onClick-Events
My question: How do I build this scenario with Backbone?
I guess I create a model and a view for my list objects. I use a collection for keeping together my objects.
Now I wonder: Do I instantiate the object view in the model constructor and instantiate the models in the collection constructor? Or is there a better way to achieve what I want?
Neither; use a mediator to instantiate the views and inject them with their model/collection.
Marionette.js has a nice implementation for this sort of mediating object, called Controller - I suggest you check it out.
Further more, you don't have to explicitly instantiate the collection models - just declare their type in the collection's prototype, e.g.:
var MyModel = Backbone.Model.extend({
// do some shit
});
var MyCollection = Backbone.Collection.extend({
model: MyModel // pass the type for the collection to instantiate
});
Taking our mediator/controller approach further, this is how it could be done (with Marionette.js):
var MyController = Marionette.Controller.extend({
initialize: function () {
this.myCollection = new MyCollection();
this.myCollectionView = new MyCollectionView({
collection: this.myCollection
});
}
});
This, of course, is just a skeleton code, meant to roughly demonstrate the MVC approach, but it's a good starting point.

Backbone.js: Why can't I retrieve the model in this case?

I'm trying to retrieve the model, because I need to access certain attributes, but I cannot. I'm inside a view, which extends another one. This is how the view looks like:
var ImageGridControlView = GridControlView.extend({
//.... stuff ....
alert(this.model.get('property')) //This gives me an error in console and nothing is alerted.
});
In the class that the above code extends, the model can be retrieved, like this:
var GridControlView = ControlView.extend({
//.... stuff ....
alert(this.model.get('property')) //This shows the property value correctly
});
Why can't the model be retrieved from ImageGridControlView? What is the persistence of models across different classes, children, etc? I'm new to Backbone, and the official documentation only covers models superficially.
Your snippets are too small to actually find the problem, so we have to just guess. Most likely candidates are:
you are accessing this.model inside a function triggered by an event binding that is not properly bound, so this isn't actually your view instance. It could be a the source DOM element of the window object instead.
You aren't passing a model property in the options object provided to the view's constructor

Backbone Models - extracting names at runtime

I need to be able extract a backbone model's name at runtime from a backbone View to write generic View and then Template code. That is: I need to get the "ClassName" from the model that is passed to the View and then take the first three characters from it and pass it on to the template.
Is there a simple way to do it?
Thanks.
Bharat
By ClassName, do you mean the name by which you refer to your model, such as User in new User();?
If so, unfortunately this is not possible, because the name you give to the model class is simply a variable name and not part of the model declaration. Consider:
var Foo = Backbone.Model.extend({});
var Bar = Foo;
Is the ClassName Foo or Bar?
Instead, simply give each of your models a name property:
var Foo = Backbone.Model.extend({
name:"Foo"
});
This way you can easily get the model name, either by modelInstance.name or ModelClass.prototype.name.

backbone.js model.clear with no defaults

When I create a new backbone.js model, the attributes from "defaults" are NOT ignored, they become the initial values for my model.
Why are they ignored when I call "clear" on a model.
Is there any reason behind it?
Why should I want a state, where all properties of my model are 0 or ""(spaces) when I have "defaults"?
Can anybody give me a real world example for that?
For instance, in one of my models there is a property called "status". The initial value is "x", but the user can change it to "b" or "u". ""(space) is no valid value. Therefore in the model, I have
defaults: {
"status": "x"
}
In the detail component, where the user can edit the values of a model, he has a button called "erase". When he does that, the model gets the initial state. In this case I will never need a function to set all properties to zero or spaces.
It would have been easy for the backbone.js author to check in the "clear" method, if the model has "defaults", and if so, delete all values and then get the defaults.
Thanks alot in advance
Wolfgang
Had a similar need. Because authors of frameworks don't use them by themselves, they can't foreseen all real world use cases. Better to ask directly https://github.com/documentcloud/backbone
Anyway, it's javascript
Backbone.Model.prototype.resetToDefaults = function() {
this.clear();
this.set(this.defaults);
};
If you don't like it overwrite it!
var Model = Backbone.Model.extend({
clear: function(options) {
Backbone.Model.prototype.clear.apply(this, arguments); // or something like this
return this.set(_.clone(this.defaults));
}
});
and now on you can use your custom model as a basis for all your models
var MyModel = Model.extend({...});
If doing this quick thingy bothers you, you can post this to backbone.js' git issues.
Hope this helps!

How to access a calculated field of a backbone model from handlebars template?

I would like to access the calculated fields I have implemented in the model (backbone.js) from the template.
Do I need always to define a helper to do it?
I think the problem has to do with the way I pass the model to the template.
If I pass this.model.toJSON() I have access to the properties but not to the functions I have defined in it.
If I pass this.model directly I can access the function but not the properties of the backbone model.
Always pass this.model.toJSON() to your templates.
What you need to do to get your calculated values, is override your toJSON method on your model.
MyModel = Backbone.Model.extend({
myValue: function(){
return "this is a calculated value";
},
toJSON: function(){
// get the standard json for the object
var json = Backbone.Model.prototype.toJSON.apply(this, arguments);
// get the calculated value
json.myValue = this.myValue();
// send it all back
return json;
}
})
And now you have access to myValue from the the JSON that is returned by toJSON, which means you have access to it in the view.
The other option, as you mentioned, is to build helper methods and register them with Handlebars. Unless you have some functionality that changes based on how the template is being rendered, and/or what data is being passed to the template, I wouldn't bother with that.
Here is another possibility: (from the model initialize)
initialize: function() {
this.on("change", function () {
this.set({ calculatedColumn: this.get("otherColumn") }, { silent: true });
});
},
Computed properties in Backbone
I have had the same issue. #DerickBailey is right, of course, that overriding toJSON does the job. But it also leaks into the communication with the server (see muu's comment on his answer).
So eventually, I have built a Backbone plugin to specifically handle data export to templates, and do so with a minimum of fuss: Backbone.Marionette.Export. It also deals with nested structures, takes care of circular references etc. See the docs.
Here's how it works. Include the plugin file into your project and declare
MyModel = Backbone.Model.extend({
foo: function () {
return "I am a calculated value";
},
exportable: "foo" // <-- this is the one line you have to add
});
If you are a Marionette user, you are already done at this point. foo shows up in your templates as if it were a model attribute.
In plain Backbone views, just call myModel.export() or myCollection.export() instead of their toJSON counterparts when you render.
For methods taking arguments, there is an onExport handler. Examples, again, are in the docs.
The best way to do it is to add this to your model:
function initialize() {
this.set("calculatedColumn", function () { return this.otherColumn; });
}
A backbone model normally stores the actual data values internally in "model.attributes". That is why when you pass your model directly to the template, it only has functions added directly to model and not any data. And if you use model.toJSON() it is normally implemented in backbone as _.clone(model.attributes) (see backbone.js). So you have the data and not the functions added directly to the model. That is why the above works - you set the function on model.attributes, not on the model object itself. Do not reference model.attributes directly, use model.get("calculatedColumn") and model.set("calculatedColumn", ...).
So model.get("calculatedColumn") returns a function. If you go {{calculatedColumn}} in handlebars (assuming you're using handlebars), it shows the value returned by the function. But calculatedColumn will not be sent to the server because backbone does a JSON.stringify to model.toJSON in sync (in backbone.js) and JSON.stringify ignores functions. If you want JSON.stringify to not ignore the function (so the function is turned into a data value whenever toJSON is run on the model - during view rendering and model sync-ing), override model.toJSON just as #Derick Bailey described.
Also, you can derive your own BaseModel from Backbone.Model and override .toJSON and derive all your models from BaseModel if you need to. Then you would need a generic version of .toJSON that could be applied to any model.

Resources