AngularJS, callback and variables scopes - javascript

I m actually facing a problem with javascript in general. In fact, I need to update a list after calling a callback in two different files.
This is my description of the callback :
this.modify = function(){
var self = this;
var success = function(){
self.user = self.userEdit;
};
var err = function(data){
alert(data);
};
UserService.put(this.userEdit, success, err);
}
}
And this is the function which calls the callback :
UserService.put = function (data, succ, err) {
var user = {login:data.login,nom:data.nom,prenom:data.prenom,password:data.password};
$http({
url: __ADRS_SRV__ + "user/"+data._id,
method: "PUT",
data:user,
isArray: true
}).success(function(data){
succ();
}).error(function(error){
err(error);
});
}
In fact,
var success = function(){
self.user = self.userEdit;
};
doesn't seem to work properly, when I log self.user in the callback call, I got an undefined...
Do you have an idea to bypass this ?
Thanks for advance

You have to remember the this as self before declaring the success function:
var self = this;
var success = function(){
self.user = self.userEdit;
};
Or an alternative would be just using this, but bind the function with this variable:
var success = function() {
this.user = this.userEdit;
}.bind(this);
Hope this helps.

Related

How to call properties like methods in JavaScript

I downloaded the code from Git and here is the link https://github.com/MikeWasson/LocalAccountsApp
In this visual studio solution we have JavaScript file called app.js which has the following code.
function ViewModel() {
var self = this;
var tokenKey = 'accessToken';
self.result = ko.observable();
self.user = ko.observable();
self.registerEmail = ko.observable();
self.registerPassword = ko.observable();
self.registerPassword2 = ko.observable();
self.loginEmail = ko.observable();
self.loginPassword = ko.observable();
function showError(jqXHR) {
self.result(jqXHR.status + ': ' + jqXHR.statusText);
//self.loginPassword();
}
self.callApi = function () {
self.result('');
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: 'GET',
url: '/api/values',
headers: headers
}).done(function (data) {
self.result(data);
}).fail(showError);
}
self.register = function () {
self.result('');
var data = {
Email: self.registerEmail(),
Password: self.registerPassword(),
ConfirmPassword: self.registerPassword2()
};
$.ajax({
type: 'POST',
url: '/api/Account/Register',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data)
}).done(function (data) {
self.result("Done!");
}).fail(showError);
}
self.login = function () {
self.result('');
var loginData = {
grant_type: 'password',
username: self.loginEmail(),
password: self.loginPassword()
};
$.ajax({
type: 'POST',
url: '/Token',
data: loginData
}).done(function (data) {
self.user(data.userName);
// Cache the access token in session storage.
sessionStorage.setItem(tokenKey, data.access_token);
}).fail(showError);
}
self.logout = function () {
self.user('');
sessionStorage.removeItem(tokenKey)
}
}
var app = new ViewModel();
ko.applyBindings(app);
What I'm not able to understand that how come properties like
self.result, self.registerEmail, self.registerPassword/Password2, self.loginEmail, self.loginPassword
became methods. Because when I type "self" followed by a dot the intellisense gives all the above mentioned "self" properties as methods. Like
self.result(), self.registerPassword()
And obviously "ko" comes from Knockout.js library.
But when I try to imitate the same in my simple JavaScript code. It fails and I know it's not right to do it here
var SomeObj = {
ok : function()
{
var x = 10, y= 10, z;
z = x+ y;
return z;
}
}
function ViewModel()
{
var self = this;
self.result = SomeObj.ok();
function xyz()
{
self.result();
alert(5700);
}
}
var v = new ViewModel();
alert(v.result());
But how come object "self" has declared "result" as property and used it later like a method in app.js where as when I try
alert(v.result());
in the alert statement It gives me an exception but if I do
alert(self.result());
in app.js it gives me back "undefined" but why not an exception the way I get in my code.
The problem you're seeing here is that by using the parenthesis, you're invoking the SomeObj.ok method, rather than assigning it as a function. To store the method as a function pointer you should do the following:
self.result = SomeObj.ok;
Following this you'll be able to use self.result() to execute the assigned method which will return the result of the assigned SomeObj.ok method.
Check the definition of observable here:
It defines a function which returns a function, which also provides logic for reads and writes based on whether a parameter is sent. If your ok function returned a function, rather than the result, you could access it the same way as observable.
var SomeObj = {
ok : function() {
return function() {
var x = 10, y= 10, z;
z = x+ y;
return z;
}
}
}
Now, the assignment self.result = SomeObj.ok(); would prime self.result with a function, and as such calling self.result() will invoke the function assigned.

Javascript functions order

I'm making a jquery library to use an application with the json rpc protocol but I'm stuck with a little problem.
This is the fiddle that shows the code (obviously it can't work): https://jsfiddle.net/L9qkkxLe/3/.
;(function($) {
$.lib = function(options) {
var outputHTML = [],
plugin = this;
var APIcall = function(api_method, api_params) {
request = {};
request.id = Math.floor((Math.random() * 100) + 1);
request.jsonrpc = '2.0';
request.method = api_method;
request.params = (api_params) ? api_params : [];
$.ajax({
type: "POST",
url: "http://localhost:8898/jsonrpc",
data: JSON.stringify(request),
timeout: 3000,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', window.btoa(options.username + ":" + options.password));
},
success: function(data) {
handleData(data, api_method);
},
error: function(jqXHR, textStatus, errorThrown) {
log("Connection time out: can't reach it. Try changing the settings.");
isConnected = "false";
},
dataType: "json"
});
}
var handleData = function(data, method) {
if (method == "getgenres") {
outputHTML = data.result.genres; //I need data.result.genres to return in getgenres function
}
}
var log = function(msg) {
if (options.debug == true) console.log(msg);
}
plugin.getgenres = function() {
APIcall("getgenres");
return outputHTML; //This is sadly empty.
}
};
}(jQuery));
var init = new $.lib();
console.log(init.getgenres());
I need that the getgenres function returns data.result.genres but actually it returns an empty array because getgenres is called for first and only after the handleData function gives to outputHTML the value that I need.
You are performing an asynchronous AJAX request, which means you can't actually get back the data immediately. There are two ways to solve your issue: making it synchronous (easy but ill advised) or using a callback (a little bit more complex but generally accepted):
In your getgenres function, you could accept one more parameter: callback
plugin.getgenres = function(callback) {
/* Dont forget APIcall already took two parameters in, so callback has to be the third in line! */
APIcall("getgenres", false, callback);
}
Now modify your APIcall function to accept your callback:
var APIcall = function(api_method, api_params, callback) { ... }
And call the callback from the successful completion call - instead of having a handler method in between wrapped in a function, you can simply pass the anonymous function. So instead of success: function(data){ handle(data); }, just use:
success: callback
The anonymous function that we will pass to it will receive as its first parameter the data you were passing to the handler. Now you can do the following:
var myGenres = [];
var init = new $.lib();
init.getgenres(function(data){
/* Now your data is actually loaded and available here. */
myGenres = data;
console.log(myGenres);
});
I would like to point out that there are many better ways to handle this, including turning this into a Constructor (More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) instead of the strange amalgamation of functions and variables you have now, as well as using JS Promises (here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to make this easier. But the basic gist should be here.
Update (potential implementation)
Because I mentioned that this could be done in a way that I think is clearer to read and use. I do not know all use cases for this, but from the provided example I would change the code to something looking like the following. Please also note I am not an expert on jQuery plugins, so I am avoiding plugging into jQuery and just using it as an easy AJAX call.
function getAjax(){
if(!window.jQuery || !window.$) throw("jQuery is required for this plugin to function.");
this.data = [];
this.request = '';
return this;
}
getAjax.prototype = {
createRequest : function(method, parameters){
this.request = {};
this.request.id = Math.floor((Math.random() * 100) + 1);
this.request.jsonrpc = '2.0';
this.request.method = method;
this.request.params = parameters || [];
return this;
},
callRequest : function(options, callback, error){
var self = this;
// We could also `throw` here as you need to set up a request before calling it.
if(!this.request) return this;
else {
$.ajax({
// We will allow passing a type and url using the options and use sensible defaults.
type: options.type || "POST",
url: options.url || "http://localhost:8898/jsonrpc",
// Here we use the request we made earlier.
data: JSON.stringify(this.request),
timeout: options.timeout || 3000,
beforeSend: function(xhr){
xhr.setRequestHeader(
'Authorization',
window.btoa( options.username + ":" + options.password)
);
},
// We will also store all the made request in this object. That could be useful later, but it's not necessary. After that, we call the callback.
success: function(data){
var store = {request:self.request, data: data};
self.data.push(store);
// Call the callback and bind `this` to it so we can use `this` to access potentially pther data. Also, pass the results as arguments.
callback(data, self.request.id).bind(self);
},
// Error function!
error: error,
dataType: options.dataType || "json"
});
}
return this;
}
}
// Example use
new getAjax().createRequest('getgenres').callRequest({
username: 'myusername',
password: 'mypassword'
}, function(data, id){
// Success! Do with your data what you want.
console.log(data);
}, function(e){
// Error!
alert('An error has occurred: ' + e.statusText);
console.log(e);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
What I do in those occasions is this:
You are supplying a method. So put a reference to the a callback function. In this case plugin.getGenresFinalize. When handleData is called it will fire that callBack function. This way you can pass multiple methods to the api call for different types of data.
plugin.getgenres = function() {
APIcall(this.getgenresFinalize);
}
plugin.getgenresFinalize = function(data) {
console.log(data);
}
var handleData = function(data, method) {
method(data);
}

jQuery object, inconsistent global variables and AJAX call

I'm looking to get what I thought would be a simple script to run an AJAX call and keep various values stored to an object, but I cannot get the globals to remain consistent the way I would expect.
I've gone around in circles trying what I think is everything. As soon as I put the AJAX call in I can't get it to play nicely with the global variables. The process value is always false that way and the content never loads in.
ExtContent = function(){
var self = this;
this.init = function() {
self.output = null;
self.process = false;
};
this.request = function(url){
$.ajax({
type : 'GET',
timeout : 10000,
dataType : 'html',
url : url,
passself : self,
success : function(response){
this.passself.setoutput(response);
},
error : function(req,response){
if(response==='error'){
self.error=req.statusText;
}
}
});
};
this.setoutput = function(data){
this.output = data;
this.process = true;
};
this.returnprocess = function(){
return self.process;
};
this.returnoutput = function(){
return self.output;
};
self.init();
};
<div id="holder"></div>
loadcontent = new ExtContent();
loadcontent.request('/test.html');
if(loadcontent.returnprocess()){
$('#holder').before(loadcontent.returnoutput());
}else{
$('#holder').before('FAILED');
}
I can't get the process to be true and the content to be stored in output.
Thanks.
Despite wrapping everything as a class/object, the jQuery $.ajax call is still an asynchronous operation. basically "You have ordered a pizza, then try to eat it before it arrives".
i.e. this orders it:
loadcontent.request('/test.html');
and this tries to eat it immediately:
if(loadcontent.returnprocess()){
The call to setoutput (i.e. the "Pizza delivery") happens long after these operations complete.
You need to add event handler properties to your class, or use deferreds+promises to wait for the data to arrive.
To use promises, just return the $.ajax result from request:
this.request = function(url){
return $.ajax({
type : 'GET',
timeout : 10000,
dataType : 'html',
url : url,
passself : self,
success : function(response){
this.passself.setoutput(response);
},
error : function(req,response){
if(response==='error'){
self.error=req.statusText;
}
}
});
};
and use it like this:
loadcontent.request('/test.html').done(function(){
if(loadcontent.returnprocess()){
$('#holder').before(loadcontent.returnoutput());
}else{
$('#holder').before('FAILED');
}
});
Or if you setup the return values correctly inside request:
loadcontent.request('/test.html').done(function(){
$('#holder').before(loadcontent.returnoutput();
}).fail(function(){
$('#holder').before('FAILED');
});
Maybe this can help you
this.setoutput = function(data){
// 'this' here, is refering 'setoutput' function, not ExtContent,
// so ExtContent.process != ExtContent.setoutput.process
// this.output = data;
// this.process = true;
self.output = data;
self.process = true;
};

Angular js and callback when getting a from WebService

I m trying to use a controller callback function inside my service, when it successes an $http post request. Here's my code, the exact description is a bit below.
my controller :
function UserAccountCtrl (UserService, $rootScope, listUsers) {
$rootScope.title = 'Comptes utilisateurs';
this.users = listUsers.data;
this.isShown = false;
this.isModification = false;
this.deleteEntry = function(entry){
this.users.splice(this.users.indexOf(entry), 1);
UserService.delete(entry);
};
this.show = function(){
this.isShown = true;
};
this.hide = function(){
this.isShown = false;
};
this.save = function(){
var success = function(data){
this.users.push(data);
};
var err = function(data){
alert(data);
};
UserService.post(this.user, success, err);
this.hide();
};
}
My service function :
UserService.post = function (data,succ,err) {
$http({
url: __ADRS_SRV__ + "user",
method: "POST",
data:data,
isArray: true
}).success(function(data){
succ(data);
}).error(function(error){
err(error);
});
}
The functionnement is simple : when I post a new user, the WebService inserts it in mongo, and returns the fully new object generated. I can get the new object from console.log or with an alert, and it works fine.
But I cant push the new item in my array. I have the error :
Uncaught TypeError: undefined is not a function
At the exact line where I need to push the new item.
So, does anybody have an idea ?
Thanks for advance
this in this.users.push(data); is not the same this as outside of the function and therefore does not have a users array to push the new data to. (See MDN this for more info on this in javascript)
I would actually not use this at all and attach everything to the $scope object as required. This would get around your issue as $scope will be the same no matter what the context.
function UserAccountCtrl ($scope, UserService, $rootScope, listUsers) {
$scope.users = listUsers.data;
$scope.save = function(){
var success = function(data){
$scope.users.push(data);
};
var err = function(data){
alert(data);
};
UserService.post($scope.user, success, err);
$scope.hide();
};
// do the same with other functions
}
See 'this' vs $scope in AngularJS controllers for more details.
Can you try if this works?
this.save = function(){
var self = this;
var success = function(data){
self.users.push(data);
};
var err = function(data){
alert(data);
};
UserService.post(this.user, success, err);
this.hide();
};

jQuery AJAX issue? Or JS OOP scope issue?

I am trying to create a database handler class in javascript. I would like to call the class by simply using:
var databaseHandler = new DatabaseHandler();
result = databaseHandler.getResult("SELECT * FROM login");
I have created the class and used a callback for the ajax function (so as to wait for the ajax result to be returned). But all I am still receiving "undefined" as my result. If I use console.log(a) inside of the onComplete function, I get an array of the intended results.
(function(window){
//Database class
function DatabaseHandler(){
//Query
this.query = function(query, whenDone){
request = $.ajax({
url: "../optiMizeDashboards/php/DatabaseQuery.php",
type: "POST",
data: {query : query},
dataType: "JSON"
});
request.done(function(output) {
whenDone(output);
});
request.fail(function(jqXHR, textStatus) {
console.log(textStatus);
});
};
//Get result
this.getResult = function(query){
this.query(query, this.onComplete);
};
//Ajax callback
this.onComplete = function(a){
return a;
};
}
//Make available to global scope
window.DatabaseHandler = DatabaseHandler;
}(window))
My question is: Is this something to do with the variable scope, or the way that ajax works? I have read all the answers explaining that ajax is ASYNC and I thought I had handled that by using a callback function "onComplete"
Any help on this topic would be greatly appreciated!
You will not be able to return result immediately from calling getResult because underlying jQuery POST request is Asynchronous, instead you need to be passing a callback function which eventually will receive a result from server.
something like that:
(function(window){
//Database class
function DatabaseHandler(){
//Query
this.query = function(query, whenDone){
request = $.ajax({
url: "../optiMizeDashboards/php/DatabaseQuery.php",
type: "POST",
data: {query : query},
dataType: "JSON"
});
request.done(function(output) {
whenDone(output);
});
request.fail(function(jqXHR, textStatus) {
console.log(textStatus);
});
};
//Get result
this.getResult = function(query, callback){
this.query(query, callback);
};
}
//Make available to global scope
window.DatabaseHandler = DatabaseHandler;
}(window))
// then use it like so
var databaseHandler = new DatabaseHandler();
result = databaseHandler.getResult("SELECT * FROM login", function(data) {
//do something with data
});
PS: exposing direct SQL access to the databse on the client is very dangerous though, and I would not recommend doing that

Categories