Knockout subscribe scope - javascript

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.

Related

SAP OpenUI5 - Call function inside attachRequestCompleted

I have a question about attachRequestCompleted in SAP Open UI5.
My code Looks like this:
test : function (oEvent) {
model = new sap.ui.model.json.JSONModel();
// Load JSON in model
model.loadData("http://localhost:8080/getJSON");
model.attachRequestCompleted( function(){
console.log(model.getData());
this.makeSomething()
});
},
I want to call my function makeSomething after the model is loaded but it's not possible.
I tried to call it after the function like this. The function gets called but the model isn't loaded.
test : function (oEvent) {
model = new sap.ui.model.json.JSONModel();
// Load JSON in model
model.loadData("http://localhost:8080/getJSON");
model.attachRequestCompleted( function(){
console.log(model.getData());
}, this.checkElement());
},
Is this even possible?
The this keyword in JavaScript is tricky. As W3schools states here:
In JavaScript, the thing called this, is the object that "owns" the JavaScript code.
The value of this, when used in a function, is the object that "owns" the function.
The value of this, when used in an object, is the object itself.
The this keyword in an object constructor does not have a value. It is only a substitute for the new object.
The value of this will become the new object when the constructor is used to create an object.
In your case, if you call this inside your test method, this will refer to the current controller. You can use this inside your method to access other methods of the controller.
However, if you use this inside your callback method, this - the owner of the code - is no longer the controller. It is your callback method. this.makeSomething() does not exist.
The common way around this is to create a variable usually called that, to which you give the value of this while this has the value you want to access later on. You can then access it from your callback method; in the callback method, the that variable will not have changed, whereas this will be different.
A code sample is worth a thousand words. See my changes below.
test : function (oEvent) {
var that = this;
model = new sap.ui.model.json.JSONModel();
// Load JSON in model
model.loadData("http://localhost:8080/getJSON");
model.attachRequestCompleted( function(){
console.log(model.getData());
that.makeSomething();
});
},
When using UI5, I usually create a variable at the same level as my controller methods called _globalThis. In the onInit method, I then assign it the value of this and can then access that same variable from every one of my callback methods.

how does BackboneJS view's function binding works?

I am reading through BackboneJS View .
SearchView = Backbone.View.extend({
initialize: function(){
alert("Alerts suck.");
}
});
// The initialize function is always called when instantiating a Backbone View.
// Consider it the constructor of the class.
var search_view = new SearchView();
Is every function inside a View object called on instantiation or is it only the initialize function alone??
Is initialize more like a callback function on success of instantiating a view? what exactly is it meant for?
I went through google. But found most results having buzz words that i couldn't understand. can someone put it straight away simple? assuming I have no knowledge about underscorejs?
Only the initialize function is called on instantiation. You can regard it as a constructor of sorts.
Even in the documentation, the title of the initialize function is constructor/initialize.
... If the view defines an initialize function, it will be called when the view is first created.
It would make no sense at all if every function was called on instantiation. Imagine a case where you have some destructive logic in one of the functions of your class (which is very likely), you wouldn't want that function called right away.
Any other functions that you want to execute the moment the object is instantiated can simply be called from within the initialize function.
initialize: function(){
// alert("Alerts are not too cool (no offence).");
console.log( "Consoles are cool" );
another_init_func();
more_init_stuff();
be_awesome();
...
}

call vs passing object in javascript

ive been using call a bit recently in some tutorials I've been following but am more used to passing the object. I was wondering is it best to use call. What's the most efficient way?
var editor = function(someParam){
//do something with this
}
var testFunction function(someParam){
editor.call(this, someParam);
}
or
var editor = function(obj, someParam){
var self = obj;
}
var testFunction function(someParam){
editor(this, someParam);
}
I would stick with passing the object to the function as long as you can structure the function that way.
I see call more like a utility if the function cannot be changed anymore and you really have to inject a different this context.
Also, consider using the prototype approach if your function is bound very close to some object that you want to call the function on.
Also, call will be less performant than passing the object to the function, as the injection of a different this has some overhead.
In some cases call is very useful, for example when adding event handler:
window.addEventListener('load', function(){
var cb = document.getElementById('myCheckBox');
cb.addEventListener('change', onchange);
onchange.call(cb); // sets `cb` as `this` in `onchange` function
});
function onchange(){
// I'm using 'this' for current element
if(this.checked)
alert('checked');
}
In this case onchange will be called on window load and every time checkbox checked state changes

passing _this into an ajax call

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.

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