Backbone.js: Pass params to listenTo - backbone.js

How can I retrieve the params when using listenTo? For example, I have the following function in a collection:
add: function(models, options, firstTime) {
//stuff done
}
and I have this function in the view:
initialize: function() {
this.listenTo(this.collection, 'add', this.addAll);
}
The listener is working correctly --addAll in the view is being called when add is triggered in the collection--, but I don't know how can I retrieve the parameters that add used. Is this possible to do? I want to listenTo(), and retrieve the arguments and the values used for the listened function.
How can I do it?

You can't get access directly to the argument passed to add unless you wrap the function or some similar solution.
Though, if you only want to access the changed attributes, then just check the model.change property

Related

Which arguments are passed to handler bound to Backbone.Model change event?

I can't seem to find any documentation for this and playing around with the code hasn't helped me understand this completely.
I have Backbone model and in my my I bind a handler to the model change event:
var myModel = new ModelA();
var myView = new ViewA({
model: myModel
})
//in my view I have
this.listenTo(this.model, "change", this.handleChange);
Can someone please explain to me what arguments are passed to this.handleChange? I see
there are 2 arguments, model & value, but what are they exactly?
What happens when I bind to a specific attribute, like so:
this.listenTo(this.model, "change:attr", this.handleChange);
When I unset an attribute from the model using myModel.unset("attr");, what are the values passed to handleChange? I see that in some cases value is undefined and sometimes it has 1 attribute unset = true
Any help or point to relevant documentation will be appreciated.
From the code:
Event change:attr:
this.trigger('change:' + changes[i], this, current[changes[i]], options);
Event change:
this.trigger('change', this, options);
So in the first case, the arguments are: the model, the value, and the options used (both external and internal options (for example, unset: true for your unset call)). In the second case, as it doesn't concern a particular attribute, the arguments are: the model and the options.

Backbonejs and Custom Model Event

I am trying to create a custom event for my model but apparently the custom event get triggered no matter what unless I use "anonymous" function definition as a callback
Here is the pseudo code of my app structure
//Router
initialize: ->
this.user = new User()
this.view = new View({model:this.user})
this.view.render()
//View
initialize: ->
//This event binding get triggered no matter what
//this.model.on("custom:event", this.triggerMe(), this)
//This works properly. Only triggered when I call model.trigger("custom:event")
this.model.on("custom:event", function(){console.log("I WORK!!");}))
triggerMe: ->
//I GET TRIGGER NO MATTER WHAT
you are invoking a function here:
this.triggerMe()
it should be this.triggerMe
this.model.on("custom:event", this.triggerMe, this)
Adding () or .call() or .apply() is invoking a function not a reference to it.
By passing this.triggerMe() you automatically execute the triggerMe function (because you add parentheses, and by so invocating it).
What you need to do, is to pass a reference to the function. Like so:
this.model.on("custom:event", this.triggerMe, this)

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.

Backbone.js - passing 2 models to 1 view

I'm trying to pass 2 models to the view, but it seems it is not working. Here is my example: http://jsfiddle.net/kahhor/jp4B6/14/ As you can see second alert is showing undefined...
May be I have wrong approach. What I'm trying to do is: in View1 bind event 'change' to Model1... Than by clicking button in View2, call function in Model1 which changes value, and automatically render View1, since it was binded to change event.
But don't forget that View2 has also its own Model2, which I created outside the view and than passed it like new View2({model:Model2});.
It might looked confusing at first, but I think it is simple thing that backbone can do. I just don't know how to do it :)
Thanks,
you can access custom parameters (options) from
window.PopupView = new PopupView({ model: LeftNotificationM, model2: PopupM});
like this:
window.PopupView = Backbone.View.extend({
// code left out
initialize: function () {
this.model.bind('change:notification_num', this.render);
alert(this.model);
// your model2 option:
alert(this.options.model2);
},
// code left out
});
Conclusion: "unrecognized options" of a view can be found in this.options
I just found this question and Sled's answer via Google and it helped me a lot - but just to update this 2 year old question and perhaps save some other Googlers the headache:
Backbone Views no longer automatically attach options passed to the
constructor as this.options, but you can do it yourself if you prefer.
Vitaliy's Answer on a similar question shows you how:
initialize : function (options) {
this.options = options || {};
}
Add this patch and pass any options directly to view.
Backbone.View.prototype._configureWithoutThis = Backbone.View.prototype._configure;
Backbone.View.prototype._configure = function(options) {
this._configureWithoutThis(options);
_.extend(this, this.options);
}

Is it possible to dynamically define a comparator function for a collection?

I have a basic collection :
myCollection = Backbone.Collection.extend({
model: myModel
url: '/theurl',
initialize: function(){
this.fetch();
})
})
When initialized, the collection receives items ordered by date.
I would like to be able to dynamically reorder the collection, using another model attribute (name, rating, etc.).
In the view associated with the collection, I tried to bind a click event to a callback like this :
reorderByName: function(){
myCollection.comparator = function(item){
return item.get('name');
});
this.render();
})
Unfortunately, that does not seem to work. Any suggestions as to how I could do it ?
Thanks.
It looks like you've only done half of what you need to do. You've given the collection its comparator, but you've not told it to resort itself. So you need to add something like this statement right before you render:
myCollection.sort();
Note that this will trigger a reset event which would be received by any object that you've bound this event to. If you wish to suppress triggering that event, you could make the call this way:
myCollection.sort({silent: true});
Hope this helps.
I found this while looking for a solution to my own issues with comparator. I thought I would add, in case anyone else finds this question, that if the models in your collections have undefined values for the property by which your comparator is sorting, the sort won't happen. Be sure to use defaults on your model if they're not required and you're gonna sort by 'em. ;)

Resources