SAP OpenUI5 - Call function inside attachRequestCompleted - javascript

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.

Related

Callbacks usage with instanced objects in Javascript

I was facing a problem with callbacks in Javascript. I solved my problem using what I would call an ugly property of Javascript (so to say, something that would logically be forbiden and never work in other languages than Javascript). So my question: Is there an ELEGANT way, to do the same thing.
I will so begin with the beginning. My goal was to wrap, in some manner, the Web Audio API. In the architecture, I implemented a class, lets call it AudioRessource, which is destined to be an interface (abstraction) in some manner of the AudioBuffer object of the Web Audio API.
This class (AudioRessource) have a prototype member function that must simply take an url as argument to automatically load audio data, decode it, handle errors, etc and finally hold the resulting AudioBuffer object in a "pseudo-private" member:
function AudioRessource()
{
this._aBuffer = null; // future reference to `AudioBuffer` object
this._loadStatus = 2;
};
AudioRessource.prototype.loadData = function(url) {
/* deal here with async functions to
provides audio data loading automation */
}
The main problem here, is that this will be an object instance (of AudioRessource) which will create the callback functions, using only local references, and must be able to pass the final AudioBuffer object to itself.
To load the raw audio data, this is pretty simple, I use the XMLHttpRequest object, with an extra property set as member of the XMLHttpRequest object, like this:
AudioRessource.prototype.loadData = function(url) {
let req = new XMLHttpRequest();
req.extraProperty = this; // reference to `AudioRessource` instance
req.onload = function(){
// retrive instance reference within the callback
this.extraProperty._loadStatus = 0;
}
req.onerror = function(){
// retrive instance reference within the callback
this.extraProperty._loadStatus = -1;
}
req.open('GET', url, true);
req.send(null);
this._loadStatus = 1;
}
The big problem appear when we have to decode the coded raw audio data into PCM data, that is, an Web Audio API AudioBuffer object instance. Indeed, the Web Audio API provides only one function to achieve this, and this function is asynchronous, and takes a callback that simply recieve the resulting buffer as argument: how to "catch" this resulting buffer to assign it to the proper AudioRessource instance (the one who lauched the process) ? This work that way:
AudioCtx.decodeAudioData(rawData,
function(result){
// do something with result },
function(error){
// do something with error });
My first naive approach, was to think like we were in C/C++ : I simply put an AudioRessource instance function "pointer" (reference) as callback, this way, the AudioRessource instance will directly recieve the buffer:
// where 'this' is an `AudioRessource` instance
AudioCtx.decodeAudioData(rawData,
this._handleDecodeSuccess,
this._handleDecodeError);
However, this does not work, because in javascript, this is not a "function pointer" that is passed into the decodeAudioData, but if I well undstand, an literal expression, that is, the "ASCII content" of the function... So the 'this' reference is lost !
I spent some time to try understand how this kind of asynchronous function is attended to work, since to me, coming from C/C++, this is simply an heresy: The function does not take any extra argument, no way to pass any external reference... "What is that thing ?". Then I finaly decided to try the "Illogical Javascript logic" way... And I found the solution :
// Create local variable which stores reference to 'this'
let thisInstReference = this;
// Use the local variable to write our callback
AudioCtx.decodeAudioData(rawData,
function(resut){
thisInstReference._aBuffer = result;
thisInstReference._loadStatus = 0;
},
function(resut){
thisInstReference._loadStatus = -3;
});
To be honnest, to me, this is simply freaking. First of all, I even don't understand what realy happen: HOW a local variable (to a object instance's member function), that stores a reference to an object instance (this), can be used "as this" in a callback function ? I do not even understand how a language can allow this kind of thing. Secondly, to me, this not a "proper way" to code something: this code is simply illogical, dirty, this works but this appear as an ugly hack that takes advantage of Javascript misdesign.
So here is my question: How to achieve this, in a elegant way ?
Your problem is simply due the the nature of how this works in javascript. The value of this is not bound at compile time nor at runtime but instead very late at call time.
In the following code:
AudioCtx.decodeAudioData(rawData,
this._handleDecodeSuccess,
this._handleDecodeError);
.. the value of this inside _handleDecodeSuccess and _handleDecodeError is not determined at object creation time but instead at the time they are called. And it is the decodeAudioData method that will eventually call them when decoding is complete. This causes the value of this to become something else (depending on how the functions are called).
The modern solution is to statically bind this to the functions:
AudioCtx.decodeAudioData(rawData,
this._handleDecodeSuccess.bind(this),
this._handleDecodeError.bind(this));
Note: the .bind() method creates a new function that wraps your function with this permanently bound to the argument you pass to it.
The traditional solution is to capture this inside a closure like what you have done.

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.

calling functions using lookup table in Backbone View isn't passing "this" (the view) to the function

I have a function in a Backbone View that gets called from an event. Different functions in the View get called based on a var called selected, so I made a lookup table to route the values of selected to functions in my view:
var cases = {
'application' : this.choose_application,
'device' : this.choose_device,
'log' : this.choose_log,
'process' : this.choose_process,
'server' : this.choose_server,
'network' : this.choose_network
}
Then I call the function:
var func = cases[selected];
func();
When a function from the lookup table gets called (this.choose_log, for example), this in the choose_log function equals the Window object rather than the View.
As expected, if instead of using the lookup table, I simply call this.choose_log(), this in the choose_log function equals the View.
How can I pass make this equal the View within all of the functions in my lookup table? And more importantly, why is this happening?
How can I pass make this equal the View within all of the functions in my lookup table?
Use cases[selected].call(this); - see docs for call.
And more importantly, why is this happening?
Because the value of the this keyword depends on how the function is called. In the "usual" case it does work as expected because you call the function as a method on the View instance.

How to use JSONP with Object Oriented Javascript

I am new to JSONP and had implemented cross domain functionality for my application and everything is working fine. Now i want to change my javascript code to apply object orientation.
My api is
http://localhost:8080/myApplication/getComments?callback=displayComments
CrossDomain.prototype.displayComments = function(data) {
// code to display the comments
}
Now I am getting an error in firebug given below
ReferenceError: displayComments is not defined
I changed the api to
http://localhost:8080/myApplication/getComments?callback=this.displayComments
and found that the function is appended inline to the callback like this
http://localhost:8080/myApplication/getComments?callback=callback=function (jsonData)
{
//code to display the comments
}
this time another error in firebug
SyntaxError: function statement requires a name
I have a doubt whether to use JSONP in object oriented javascript or not.
Please help.
Thanks in advance.
There's no point in defining the function on the prototype of a function unless you are going to create instances of that function, so start by doing that.
var myCrossDomain = new CrossDomain();
Then you have to call the method on the object, not as a global (it isn't a global, so you can't do that anyway)
var uri = "http://localhost:8080/myApplication/getComments?callback=" +
encodeURIComponent("myCrossDomain.displayComments");
In response to edits and comments:
Yes i am creating an instance of this in another js file
Then reference it as shown above.
I changed the api to
http://localhost:8080/myApplication/getComments?callback=this.displayComments
It's JSON-P. It runs by adding a new script element. Everything gets called in the global context. That is going to call this.displayComments which will be the same as window.displayComments.
If you want to call your method directly, then you need to specify the global variable holding the instance explicitly.
If you don't want to call it directly then you can use the more traditional approach of generating a new, anonymous function which has access to said object through a closure, assigning that function to a global variable (with a unique name) and using that name as your callback argument.
and found that the function is appended inline to the callback like this
http://localhost:8080/myApplication/getComments?callback=callback=function (jsonData)
You haven't shown us the code that creates the URI so we can't tell why that might be the case.

Javascript Object Inheritence

I'm creating a control for Google maps v2. While creating my control I've found a design challenge and want to find an appropriate solution. Here's the goods.
A custom Google control inherits from GControl;
myControl.prototype = new GControl();
Next I need to overload the initializer so here it is.
myControl.prototype.initilize = function (map) {
//do some work and return stuff
};
Now within my custom controls initlize function I create a couple elements which, using the GEvent class, I subscribe to various events. To make my callback functions managable, I included them into the controls prototype.
myControl.prototype.onEvent = function(e){
//do some work;
//modify the members of the current myControl instance
};
Within my callback function "onEvent" I want to modify members within my control. What is the best way to access my control from the function? The keyword "this" cannot be used because that is a reference to the element that was clicked, in my case a div. And I can't access the members through the prototype because I need a specific instance of the object. The only viable solution I've considered is to create my control globally in one of my scripts. Is this the best method?
The easiest thing that I can think, it to define your onEvent method within your constructor, there you will have quick access to the current object instance, and you will not have to modify your public API:
function MyControl () {
var instance = this; // store a reference to 'this'
this.onEvent = function (e) {
// use the instance variable
instance.otherMethod();
};
}
Note that in this approach, the onEvent property will exist physically in your object instances (obj.hasOwnProperty('onEvent') = true).
Edit: You can simply use the GEvent.bind function:
GEvent.bind(map, "click", myObj, myObj.onEvent);
The above bind method will enforce the context, so the this keyword inside myObj.onEvent will point to the myObj object instance when the event is triggered, it will work with your code without problems.
I'm not familiar with how you subscribe to events using GEvent, so I'll make that part up. Do this:
myControl.prototype.onEvent = function(e, instance) {
// do some work
// modify members of 'instance'
};
function wrap(handler, instance) {
return function(e) {
handler(e, instance);
}
}
GEvent.Register('Event', wrap(instance.onEvent, instance));

Categories

Resources