passing _this into an ajax call - javascript

So I'm not sure what the best way to do this is, currently I have an ajax call that looks some like this
var _this = this;
_.each(this.models, function (model) {
model.fetch({
success: function(model, response) {
if (_.has(model.attributes, "settings")) {
_this.populateSettings(_this);
}
}
In the populateSettings method, I need certain attributes on this Backbone view. I wasn't sure how to get those attributes except to pass in each specific attribute, or pass in a reference to this backbone view and in populateSettings, use that reference to populate the view. Is there a better way to do this? Thanks.

One option would be to listen for sync events on model in your view, so that whenever model successfully syncs with the data from the server, your view could run populateSettings.
This could look something like this:
model.on('sync', this.populateSettings, this)
Note the third argument, this. Passing this as the third argument sets the context in which the event handler will run. From the Backbone.js docs:
To supply a context value for this when the callback is invoked, pass the optional third argument: model.on('change', this.render, this)
If you used an event listener like this, you could remove the success: option altogether. And you may also find the change or change:[attribute] events useful as well.

Related

Knockout subscribe scope

Is there any possibility to change the scope of the subscribe in Knockout?
I have something like this:
element =
{
type: ko.observable()
name: ko.observable()
content: ko.observable()
}
element.type.subscribe(this._typeChanged.bind(element))
Basically I want to have an access to the object which property I am subscribed to. Binding like in my code does nto work since it binds to the whole VeiwModel and not the object.
Maybe the knockout handle that when you subscribe an observable you can pass 2 parameters the first is the callback and the second is the scope/context, try something like this:
element.type.subscribe(this._typeChanged, element)
The subscribe function accepts three parameters: callback is the function that is called whenever the notification happens, target (optional) defines the value of this in the callback function, and event (optional; default is "change") is the name of the event to receive notification for.
Ref. http://knockoutjs.com/documentation/observables.html
The problem is the way in which you're creating your view model. The view model shuld be self-contained, including the functions that operate on it. It should be something like this:
var ViewModel = function() {
var self = this;
self.type = ko.observable();
self.name = ko.observable();
self.content = ko.observable();
self.type.subscribe(function(newVal) {
// here you have access to all the viewmodel properties through self
});
return self;
};
This is a constructor using the var self=this; pattern.To use the view model you need to instantiate it, i.e. var vm = new ViewModel(). (You can omit the new).
Of course, you can also define a function, and bind it to self, or receive a callback in the constructor, and bind it to self. In that case, the function implementation will have the view model accesible via this, and not self, which will be undefined inside the function body.
var doSomethignWithVm = function(newVal) {
// acces viewmodel via this
// you can also use newVal
};
You modify the constructor to receive this as a callback:
var ViewModel = function(doSomethingCallback) {
self.type.subscribe(callback.bind(self));
};
This pattern doesn't make much sense because your callback is supposed to have knowledge of your view model. In that case it makes more sense to include the subscription functionality directly inside the model.
EDIT
Note: as I've mentioned in a comment to Joel Ramos Michaliszen's answer, both of this codes are equivalent:
self.type.subscribe(callback.bind(self));
self.type.subscribe(callback.bind, self);
You can check that by seeing the source code of subscribable in knockout's gitbhub, in the file knockout/src/subscribales/subscribable.js. If you look for subscribe implementation you'll see this:
subscribe: function (callback, callbackTarget, event) {
// ...
boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
I.e. if you provide a second argument, it's used tob bind the function passed in the firt argument to it.
Although I get that I may have the wrong approach top this I am also in a stage where I will not be able to do any breaking changes to the app.
I figured out that I could use lodash to help me with this.
I ended up using partial function to append the element as a parameter in the subscribe callback:
element.type.subscribe(_.partial(this.typeChanged, element))
or in coffeescript
element.type.subscribe $_.partial #typeChanged, element
Now the chartTypeChanged has 2 parameters on the input instead of one.

model.on('change', function()) not working properly

I am using backboneJS model.on ('change:attribute',functionName(someParameter)) to listen to change in model's attribute and call a funcion with given parameter. But the problem I am facing is the function is being called initially even when there is no change in the model. After that,even when the model changes, the function is not called. I did some trials and found out that without the parameter, if I called ('change:attribute',functionName),
the events fired properly. I can not understand what the problem is. Can anyone help as I think I am missing something very basic here. And a way to approach such problem would be much appreciated. Thanks.
The .on() method expects you to pass the callback function or method that will be called to handle the event. But in your first example you tried to pass a result of that callback.
So inside it will execute yourCallback.call(...) or yourCallback.apply(...). Obviously it could not execute the .call() method of non-function value.
But you can wrap the method call in anonymous function though if you really need it. For example if you need to use that someParameter value:
var MyView = Backbone.View.extend({
// ...
myMethod: function(someParameter) {
this.model.on('change:attribute', function() {
functionName(someParameter);
});
}
});

Globally listen for Backbone.js Model.save

I'd like to append Backbone's native Model.save() method with a custom logging method that logs success and errors. I know that on a per model basis I can call something like:
myModel.save().success(function() {
// do something cool
});
But rather than adjusting every call to various models' save events, I'd like to simply listen for the save event on any model. One way that I think I want to avoid is actually modifying the Backbone.Model.prototype.save method (although if someone has an elegant way to do this I'm open to it).
Any thoughts on how to create such an event listener?
If all your models/collections are using the default Backbone.sync method, you could create a new sync method to do the logging.
var originalSync = Backbone.sync;
var loggingSync = function(method, model, options) {
// call original Backbone.sync
var promise = originalSync(method, model, options);
promise.done(function() {
// if method is 'update' or 'create', log success
});
promise.fail(function() {
// if method is 'update' or 'create', log failure
});
return promise;
};
Backbone.sync = loggingSync;
By default, Model.sync and Collection.sync both proxy through to Backbone.sync, so if you are using default sync, this change would take care of it.
var GlobalModel = Backbone.Model.extend({
save: function() {
}
});
myModel = GlobalModel.extend({
//..your model
})
If all you want to do is log AJAX success/failures, there's actually a much better way of doing it that doesn't even involve Backbone at all: $.ajaxComplete.
jQuery has this wonderful function called ajaxComplete that takes a function argument, and then runs that function every time a $.ajax call completes (failure or success). By using this method (ie. "binding an event handler to the ajaxComplete pseudo-event") you:
A) don't have to muck with Backbone internals
B) are guaranteed to catch EVERY AJAX request (even those made without Backbone)
C) keep this logging code completely separate from the rest of your code
There's probably some other upsides I'm missing, but you get the idea.
If on the other hand you truly do care only about save and not just about AJAX requests, then ... well you could still use $.ajaxComplete and just do some filtering inside it to ignore non-saves. But you could also make your own Model base class (as #salexch suggested) or worse you could muck with Backbone's internals, eg. by replacing Backbone.Model.prototype.save or Backbone.sync (as #Paul Hoenecke suggested), but then you're setting yourself up for trouble with future Backbone upgrades or 3rd party Backbone libraries you might want to use.
If you're interested, here's the doc page for ajaxComplete:
http://api.jquery.com/ajaxComplete/
Oh, and there's also some similarly named $.ajaxSomething methods (eg. ajaxError) for targeting only failures/successes/request starts/etc.

Why does Backbone.js model's 'on()' take 'this' as last parameter if it's almost always going to be this?

I'm just getting into Backbone, and one thing that I don't understand is why the 'on()' method for models always takes three arguments--event, handler, and context.
It seems that almost always 'this' is used for context and I haven't seen any other usage. Even if there were, since I haven't seen one yet it must be pretty rare.
So my question is: When does one use a context other than 'this', and why is Backbone designed this way? By the way, I do understand why you need to provide context, it's just that I wonder why the method syntax specifies that I use three arguments instead of making the last argument optional--which seems to be always 'this' and feels redundant. I'm sure I'm missing something. Please someone help me understand. Thank you!
[EDIT] Why can't one do something like:
model.on = function(event, callback){
model.on_with_three_args.call(this, event, callback, this);
});
model.on_with_three_args = function(event, callback){
/* whatever the on() is supposed to do */
});
Suppose we're in a view that's based on a model and we want to bind to the model's change event:
this.model.on('change', this.render);
The on call sees two things:
The event name, a simple string.
The handler, a function.
on has no way of knowing what this means in this.render, it just sees a function; on won't even know the difference between the call above and this:
this.model.on('change', function() { ... });
If your function needs a particular context then you have two choices:
Create a bound function using _.bind, _.bindAll, Function.bind, $.proxy, CoffeeScripts =>, the var _this = this closure trick, or any of the ways of creating or simulating a bound function.
Tell it which context you want by saying:
this.model.on('change', this.render, this);
There's no way to unroll the call stack to see which this you want so you have to be explicit about it.
Backbone will call the callback like this:
node.callback.apply(node.context || this, ...);
where node.callback is the callback function and node.context is the third argument (if any) given to on. If you don't specify the context then you'll get whatever this happens to be when trigger is called; in the example above, this would end up being the model.
So the third argument to on actually is optional but the default value isn't terribly useful and there is no way to choose a better default, the information you need to choose a sensible context simply isn't accessible in JavaScript. This is why you see so much _.bindAll(this, ...) boilerplate in Backbone views.
If you tried something like this:
model.on = function(event, callback){
model.on_with_three_args.call(this, event, callback, this);
});
then this in that context would usually be model so you'd really be saying:
model.on = function(event, callback){
model.on_with_three_args.call(model, event, callback, model);
});
or
model.on = function(event, callback){
model.on_with_three_args(event, callback, model);
});
and there's little point to any of that. The value of this inside on has little if anything to do with the value of this in the code that calls on. this in JavaScript is not a variable, it is a keyword which refers to the current calling context.

Getting a reference to a model in an event callback

I'm not sure if I'm doing this right, first time playing with Backbone.js.
I have two views with two models and I want to use the event aggregator method to fire events between the two.
The aggregator declaration:
Backbone.View.prototype.eventAggregator = _.extend({}, Backbone.Events);
So in one view I have a line like this that will fire the removeRow method.
this.eventAggregator.trigger("removeRow", this.row);
In another view
MyView = Backbone.View.extend({
initialize: function() {
this.eventAggregator.bind("removeRow", this.removeRow);
this.model.get("rows").each(function(row) {
// Do stuff
});
},
removeRow: function(row) {
// row is passed in fine
// this.model is undefined
this.model.get("rows").remove(row);
}
});
I think I understand why this.model is undefined, but what can I do to maintain a reference so that I can use this.model in the callback? I thought about passing the model to the first view and then passing it back in the trigger call, but that seems to make the entire point of an event aggregator pointless. If I have the model I can just call the .remove method directly and have lost the benefit of my first view being unaware of the model. Any suggestions?
I think you have binding problem.
You have two ways to assure that this will be the View instance:
1. Using bindAll
In your View.initialize() you can add this line:
_.bindAll( this, "removeRow" )
Interesting post of #DerickBailey about this matter
2. Using the optional third argument in your bind declaration
Like this:
this.eventAggregator.bind("removeRow", this.removeRow, this);
Backbone documentation about this matter
Supply your View object as third parameter of the bind method:
this.eventAggregator.bind("removeRow", this.removeRow, this);
The third parameter is the context of calling your callback. See the docs.
Also, you can use .on() instead of .bind() which is shorter...
You need to bind this so scope isn't lost. The blog link on the other answer uses underscore's bindAll
initialize: function() {
_.bindAll(this, 'removeRow');
this.eventAggregator.bind("removeRow", this.removeRow);
this.model.get("rows").each(function(row) {
// Do stuff
});
},

Categories

Resources