Coffeescript AngularJS service, can't access member functions using # notation - javascript

I've removed some stuff for legibility.
I've written the User service using the CoffeeScript class system, but it is giving me problems when it attempts to access its own member functions.
User = (...) ->
'ngInject'
new class User
constructor: ->
#setRole()
login: (params) ->
params.grant_type = 'password'
request = {...}
$http(request).then (response) ->
$window.localStorage.token = response.data.access_token
payload = jwtHelper.decodeToken(response.data.access_token)
$rootScope.$broadcast EVENTS.AUTH.LOGIN
#setRole() # PROBLEM LINE
, (response) ->
if response.status == 400
$rootScope.$broadcast EVENTS.ERROR.BAD_REQUEST
...
When I attempt to invoke #setRole(), the browser informs me that setRole() doesn't exist:
TypeError: Cannot read property 'setRole' of undefined
This compiles to this:
User = [..., function(...) {
'ngInject';
return new (User = (function() {
function User() {
this.setRole();
console.log("User service loaded");
}
User.prototype.login = function(params) {
var request;
params.grant_type = 'password';
request = {...}
};
return $http(request).then(function(response) {
var payload;
$window.localStorage.token = response.data.access_token;
payload = jwtHelper.decodeToken(response.data.access_token);
$rootScope.$broadcast(EVENTS.AUTH.LOGIN);
return this.setRole(); # WRONG this, I PRESUME
}, function(response) {
if (response.status === 400) {
return $rootScope.$broadcast(EVENTS.ERROR.BAD_REQUEST);
}
});
};
...
My question is: why am I not able to invoke User.setRole() within my own service, using # notation? Is there a workaround for this? I presume it has something to do with the this pointer not corresponding to the User instance.

The problem is that in the function you pass to then, this changes. To keep it the same, use the fat arrow syntax (=>):
$http(request).then (response) =>

Related

set property of js module with nested callback

Im trying to set a modules variable/property from a nested function (basically a xhr callback (Api.get()) inside that module (in the init() function), but it does not work and I can not figure out why.
//posts
var Posts = (function() {
//array of all posts objects
var data = null;
//initialize the posts module
var init = function(callback) {
if(data === null) {
//load all posts
loadAll(function(response){
// data = JSON.parse(response)
var posts = JSON.parse(response)
//create array
data = posts;
// call callback
console.log(data)
callback()
})
}
}
// return all posts from api as json
var loadAll = function(callback) {
Api.get('/api/posts/get', function(response) {
callback(response)
})
}
//public interface
return {
data: data,
init: init,
loadAll: loadAll
}
})();
After calling Posts.init() I log Posts.data to the console, but it is still null. However, console.log(data) inside the init() method logs the expected array of objects im trying to assign to Posts.data. It seems that data inside the callback is another variable than Posts.data. Can someone please explain why and if possible, provide a solution for setting the modules data property inside Api.get()?
You need to have a reference to the return object so you can alter its data property after you've returned the object. One way to do this would be to create an object with the methods and data and return that object. Then you can refer to its data property internally with this.data:
// Fake API
let Api = {
get(url, cb) {
cb('["testdata"]')
}
}
//posts
var Posts = (function() {
//array of all posts objects
return {
data: null,
init(callback) {
if (this.data === null) {
//load all posts
this.loadAll((response) => { // arrow function needed here for correct `this` binding
var posts = JSON.parse(response)
//create array
this.data = posts; // add data
callback()
})
}
},
loadAll(callback) {
Api.get('/api/posts/get', function(response) {
callback(response)
})
}
}
})();
console.log("initial posts data: ", Posts.data)
Posts.init(() => console.log("After init():", Posts.data))
If you do it this way, you don't actually need the IEFE unless you plan on making multiple objects. You can just use Posts = {/* rest of the data and methods */}. This would also work well as a class instead of a plain object.

Calling function from a function in javascript es6

I'm using es6 javascript with babel and trying to make an ajax call using xhr using two function but getting an error Uncaught TypeError: this.post is not a function
What is the correct syntax to make a call to a function from another function defined in the same class in es6 javascript?
Thanks for your answer this is my code
import alt from '../../alt';
import cookie from 'react-cookie';
class LoginActions {
constructor(){
this.generateActions(
'updatePassword',
'updateName',
'loginSuccess',
'loginFail',
'remember'
);
}
// Generic get request
post(url, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
callback(null, xhr.responseText);
} else {
callback(xhr.statusText);
}
}
};
xhr.send(data);
}
// Get actual content
login(name, password, remember) {
var data = "name="+name+"&password="+password+"&remember="+remember;
this.post('api/login', data, function(err, data) {
if (!err) {
this.actions.loginSuccess(data.message);
} else {
this.actions.loginFail(JSON.parse(data.message));
}
}).bind(this);
}
}
export default alt.createActions(LoginActions);
Edit1: This is how I call login function / also passed data to xhr request above
handleSubmit(event){
event.preventDefault();
var name = this.state.name;
var password = this.state.password;
var remember = this.state.remember;
LoginActions.login(name, password, remember);
}
Your methods login() and post() are instance methods, not static methods. So you have to create an instance of your LoginActions object with new in order to properly call those methods on that object.
Or if you don't actually need an instance with instance data, then make all the methods static and refer to them as LoginActions.post() and LoginActions.login(), not using this.
Instead, you're trying to mix and match. You're calling LoginActions.login() which is a static type call and then inside of login(), you're trying to reference this which assumes an instance.
Give these things a try:
As #jfriend00 suggested, create an instance of LoginAction class and call login method on that:
var loginAction = new LoginActions();
loginAction.login(name, password, remember);
define generateActions method in LoginActions class.
this.post('api/login', data, function(err, data) {
if (!err) {
this.actions.loginSuccess(data.message);
} else {
this.actions.loginFail(JSON.parse(data.message));
}
}).bind(this);
Here, you seem to be trying to bind this to the callback. But actually you are binding this to the return value of post method. To bind this to the callback,
this.post('api/login', data, function(err, data) {
if (!err) {
this.actions.loginSuccess(data.message);
} else {
this.actions.loginFail(JSON.parse(data.message));
}
}.bind(this));
Notice function(){}.bind instead of the post(function(){}).bind

Passing data from service to controller

I have created a service with the purpose of accessing an API. I need to return data to my controller but am unsure how to do this as I completely new to sails.
My Service:
// API call to get the ID of contact within Get Response with email address
getContact: function(options) {
// establish connection to API
var api = new getResponse(apiKey, apiUrl);
var contact = api.getContactsByEmail(options.email, null, null, function (response) {
JSON.stringify(response);
console.log(JSON.stringify(response));
});
return contact;
},
I know the API call is working as when I log the response I get the correct response:
{"success":true,"data":{"error":null,"id":1,"result":{"sds":{"ip":null,"name":"Full Name","origin":"api","cycle_day":0,"email":"email#email.com","campaign":"id","created_on":"date","changed_on":null}}}}
My Controller:
index: function(req, res) {
var contact = GetresponseService.getContact({email: 'email#email.com'});
console.log(contact);
return res.send(contact);
}
I want to retrieve the ID value but when I log the value of contact I get undefined. I think my problem is related to the scope but not sure.
Can anyone point me in the right direction?
Because you are directly assigning a value from api.getContactsByEmail() which does not return a value.
By the nature of node.js, the function api.getContactsByEmail() gives you callback with the response. You have to get the value from within the anonymous callback function like this:
// API call to get the ID of contact within Get Response with email address
getContact: function(options) {
// establish connection to API
var api = new getResponse(apiKey, apiUrl);
var contact = "";
api.getContactsByEmail(options.email, null, null, function (response) {
contact = response;
JSON.stringify(response);
console.log(JSON.stringify(response));
return contact;
});
}
more ...
In my opinion, its better to return a callback instead of direct return of value.
// API call to get the ID of contact within Get Response with email address
getContact: function(options, callback) {
// establish connection to API
var api = new getResponse(apiKey, apiUrl);
var contact = "";
api.getContactsByEmail(options.email, null, null, function (response) {
contact = response;
JSON.stringify(response);
console.log(JSON.stringify(response));
if(typeof(callback) == "function")
callback(contact);
else
return contact; // return contact if there is callback func.
});
}
You can use it like:
index: function(req, res) {
var contact;
GetresponseService.getContact({email: 'email#email.com'}, function(contactResult) {
contact = contactResult;
console.log(contact);
return res.send(contact);
});
}
Everything looks like it should work, however I think you're running into an issue with this piece
var contact = api.getContactsByEmail(options.email, null, null, function (response) {
JSON.stringify(response);
console.log(JSON.stringify(response));
});
api.getContactsByEmail is asynchronous I assume, so this declarative statement won't work.
Instead declare contact and return it inside the callback, something like:
api.getContactsByEmail(options.email, null, null, function (response) {
JSON.stringify(response);
console.log(JSON.stringify(response));
var contact = response.contacts; //or however you access the contact(s) from the response variable
//You should also be watching for errors in your callbacks, but that's a different topic
return contact;
});
Read up on asynchronous calls in javascript and make sure you have a solid grasp on when data is accessible when using them.

MVC SPA + Knockout

I have just recently be tasked with creating an SPA. So, I created a new project and selected SPA and found that it loaded all the files I needed including this knockout.js.
I am new to knockout.js so I watched a few videos and I get the idea, but the SPA project just doesn't seem to compute to me as it isn't a Single Page Application because you have to go to a new URL to login, register, authorise, manage account, etc (you get the idea).
So, looking at the code for the index page I can see a view model for the homeView. It looks like this:
function HomeViewModel(app, dataModel) {
var self = this;
self.myHometown = ko.observable("");
Sammy(function () {
this.get('#home', function () {
// Make a call to the protected Web API by passing in a Bearer Authorization Header
$.ajax({
method: 'get',
url: app.dataModel.userInfoUrl,
contentType: "application/json; charset=utf-8",
headers: {
'Authorization': 'Bearer ' + app.dataModel.getAccessToken()
},
success: function (data) {
self.myHometown('Your Hometown is : ' + data.hometown);
}
});
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
return self;
}
app.addViewModel({
name: "Home",
bindingMemberName: "home",
factory: HomeViewModel
});
and the HTML looks like this:
<!-- ko with: home -->
<!-- removed HTML to make it concise -->
<!-- /ko -->
now, from the look of this (correct me if I am wrong) the with handle states that if there is a variable called home, then display it (I assume this is what the bindingMembername is).
So, seeing that I can guess that if I added another partial page and included it. I could created a view model like this:
function DrawViewModel(app, dataModel) {
var self = this;
Sammy(function () {
this.get('#draw', function () {
app.home = null;
});
});
return self;
}
app.addViewModel({
name: "Draw",
bindingMemberName: "draw",
factory: DrawViewModel
});
so, in theory because this sets the app.home to null whenever anyone navigates to #draw, then the home partial will not be displayed, similarly I could added app.draw = null to the sammy route for the homeViewModel to hide the draw partial.
My issue with this, is that it will get massively complicated the more viewModels I create. So, is there something I am missing? Is there an easier way of doing this?
My ultimate goal is to move all the pages to be SPA (including the login/register pages).
Cheers in advance,
/r3plica
Well, after a bit of messing around I found out how to do this.
Basically I rewrote the AddView method and made it look like this:
// Other operations
self.addViewModel = function (options) {
var viewItem = new options.factory(self, dataModel),
navigator;
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem;
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
are you can see, if check to see if the current held view is different to the one I am adding. If it is, then I return null (which is how I get it to hide any views I am not using).
To answer my question further, I needed a way of working out how to direct to the login page if the user was not logged in.
Again in app.viewmodel.js I added a few observable properties:
// UI state
self.user = ko.observable(null);
self.loggedIn = ko.computed(function () {
return self.user() !== null;
});
and in my new login.viewmodel.js I added this function:
// Operations
self.login = function () {
self.loggingIn(true);
dataModel.login({
grant_type: "password",
username: self.userName(),
password: self.password()
}).done(function (data) {
if (data.userName && data.access_token) {
app.navigateToLoggedIn(data.userName, data.access_token, self.rememberMe());
} else {
//self.errors.push("An unknown error occurred.");
}
}).fail(function (jqXHR, textStatus, error) {
dataModel.displayError(jqXHR);
}).always(function () {
self.loggingIn(false);
});
};
the important bit here is the app.naviateToLoggedIn method. This is located in the app.viewmodel.js and looks like this:
// UI operations
self.navigateToLoggedIn = function (userName, accessToken, persistent) {
if (accessToken) {
dataModel.setAccessToken(accessToken, persistent)
}
self.user(new UserViewModel(self, userName, dataModel));
self.navigateToHome();
};
the userViewModel is dead simple:
function UserViewModel(app, name, dataModel) {
var self = this;
// Data
self.name = ko.observable(name);
// Operations
self.logOff = function () {
dataModel.logout().done(function () {
app.navigateToLoggedOff();
}).fail(function (jqHXR) {
dataModel.displayError(jqHXR);
});
};
return self;
}
and finally, to get our initial load right, in the home.viewmodel.js js file, I have this sammy declaration:
Sammy(function () {
this.get('#home', function () {
if (app.loggedIn()) {
app.navigateToHome();
} else {
window.location.hash = "login";
}
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});

Uncaught TypeError: undefined is not a function for request() in Titanium

I have a problem with 1 of my functions.
in app.js ia have th following:
//Including all functions
Ti.include('functions.js');
//Including the login screen
Ti.include('login.js');
//Including the register screen
Ti.include('register.js');
So all the functions are above the other files that could call a function.
In login.js I have the following code:
//'login' is the type
var request = request('login', loginUsernameInput.value, md5(loginPasswordInput.value));
Ti.API.info('request: ' + request);
if(request == true) {
alert('You are loggedin');
} else {
alert('Something went wrong');
}
The request function looks like this:
function request(type, username, password) {
//Database connection
var db = Ti.Network.createHTTPClient();
db.open('POST', 'http://myip/mobile_app/');
Ti.API.info('type: ' + type);
Ti.API.info('username: ' + username);
Ti.API.info('password: ' + password);
//If variables has been send
db.onload = function() {
var answer = this.responseText;
Ti.API.info('type answer: ' + typeof this.responseText);
if(answer == 'true') {
Ti.API.info('TEST');
return true;
} else {
return false;
}
};
//Variables to send
db.send({
type: type,
username: username,
password: md5(password)
});
//If there is an error
db.onerror = function(e) {
Ti.API.info('error: ' + JSON.stringify(e));
};
}
I know that this.responseText returns true and that the function md5() works aswell.
I know this because I also tested login.js when the code is like:
Ti.API.info('request: ' + request('login', loginUsernameInput.value, md5(loginPasswordInput.value)));
if(request('login', loginUsernameInput.value, md5(loginPasswordInput.value)) == true) {
alert('You are loggedin');
} else {
alert('Something went wrong');
}
The above also returns that function request() is undefined
So as soon as try to login I get the following error:
So my question is how can resolve the error?
I think the issue is that you redefine variable request to the return value of function request. You request function does not return anything. Therefore it returns undefined.
Now that you set request to undefined. You cannot use request() anymore, because you overwrote it.
Attempting to call an undefined value as a function, results in undefined is not a function
See demonstration: http://repl.it/UXE/1
Indeed, #Gabs00 is right when he says that you're redefining your request variable.
But the real question is how could you have avoid this problem ?
And the answer is in the way you're coding. By using Ti.include, you're not using the best practices recommended by Titanium.
Instead, you should do something like this :
In a file models/User.js :
exports.request = function(type, username, password) {
// Your code here
};
Then, instead of writing (in your app.js file) :
Ti.include('functions.js');
var request = request('login', loginUsernameInput.value, md5(loginPasswordInput.value));
You'll have :
var User = require('models/User');
User.request('login', loginUsernameInput.value, md5(loginPasswordInput.value));
But even with this code, it's sot satisfying enough...
What you should probably try is to declare as many functions as your requests. Even if your code is common for your 2 requests for now, I can assure you that it won't be that way very long.
Thus, there is the final code I recommend :
In your models/User.js file :
function request(type, username, password) {
// Your code
};
exports.login = function(username, password) {
request('login', username, password);
};
exports.register = function(username, password) {
request('register', username, password);
};
The cool thing with this code is that your request method is totally protected: the only code which can access this function is the one written in your models/User.js file. For the others, it's like this function doesn't even exist.
In you app.js file :
var User = require('models/User');
User.login(loginUsernameInput.value, md5(loginPasswordInput.value));
I think it's more readable this way, don't you?
If you want more information about this pattern, you should probably read these articles:
CommonJS modules
Titanium best practices
As you'll see, you should reuse this pattern for your windows too (your Ti.include('login.js'); and Ti.include('register.js');)
This question got answered here: http://developer.appcelerator.com/question/175412/function-not-returning-anything#280011
Edit:
In User.js:
function request(type, username, password, callback) {
db.onload = function() {
Ti.API.info('type antwoord: ' + typeof this.responseText);
callback( this.responseText );
};
}
exports.login = function(username, password, callback) {
request('login', username, password, callback);
};
and in login.js:
// last parameter is the callback function
User.login(loginUsernameInput.value, md5(loginPasswordInput.value), function(response){
Ti.API.info('login response: ' + response)
});
now i get true or false as response.

Categories