Should I and how to transitionToRoute in a promise callback - javascript

In an Ember controller, I want to call my API to create a user and on success, transitionToRoute. This is what I currently want to work:
import ajax from "ic-ajax";
import Ember from "ember";
export default Ember.Controller.extend({
actions: {
createAndLoginUser: function() {
var user = { "user": this.getProperties("email", "name") };
ajax({ url: "api/users", type: "POST", data: user })
.then(transitionToHome);
}
}
});
var transitionToHome = function() {
this.transitionToRoute("home")
}
But when I place a debugger in the method, this is no longer the controller object and is out of scope to call transitionToRoute.
I've only ever written hacky javascript, but I'm trying to learn core concepts and some frameworks. Is this the right way to use a promise? And is this the right place in Ember to put a transition?

Your problem has nothing to do with Ember or transitions; it's all about this handling. The simplest solution is just
.then(transitionToHome.bind(this));
Putting transition method inside the controller
You could also consider putting transitionToHome inside the controller as a method. Then, you could call it as in the following:
export default Ember.Controller.extend({
transitionToHome: function() { this.transitionToRoute("home"); },
actions: {
createAndLoginUser: function() {
var self = this;
var user = { "user": this.getProperties("email", "name") };
ajax({ url: "api/users", type: "POST", data: user })
.then(function() { self.transitionToHome(); });
}
}
});
This might be a simpler approach which is more readable and doesn't require reasoning about this and using bind (but does require using self).
Moving the transition to the route?
Outside the scope of your question, but some would say that route-related logic (including transitioning) logically belongs in the route, not the controller. If you agree, you could refactor the above to do a send
createAndLoginUser: function() {
var user = { "user": this.getProperties("email", "name") };
var self = this;
ajax({ url: "api/users", type: "POST", data: user })
.then(function() { self.send("goHome"); });
}
}
and then implement the goHome action in your route. Or, you could implement goHome in a higher-level route, even the application route, thus making it available from any controller or lower-level route.
Moving the AJAX logic to a service?
Others might say that the ajax logic does not really belong here in the controller, and should rightfully be in a services layer, so
// services/user.js
export function createUser(data) {
return ajax({ url: "api/users", type: "POST", data: { user: data } });
}
// controller
import { createUser } from 'app/services/user';
export default Ember.Controller.extend({
createAndLoginUser: function() {
var data = this.getProperties("email", "name"));
var self = this;
createUser(data) . then(function() { self.send("goHome"); });
}
};
Using ES6 syntax, we can avoid the self, simplifying a bit in the process:
createAndLoginUser: function() {
var data = this.getProperties("email", "name"));
var goHome = () => this.send("goHome");
createUser(data) . then(goHome);
}
Now this code is starting to look more semantic and readable.

An alternative solution is to simply use ES6 arrow syntax to maintain the context of this:
import ajax from "ic-ajax";
import Ember from "ember";
export default Ember.Controller.extend({
actions: {
createAndLoginUser() {
let user = { "user": this.getProperties("email", "name") };
ajax({ url: "api/users", type: "POST", data: user })
.then(() => {
this.transitionToRoute("home");
});
}
}
});

Related

Ember 2.0 router does not load model data?

I have in my router.js:
Router.map(function() {
this.route('portfolio', function() {
this.route('company', { path:'/company/:id' });
});
}
And in my routes/portfolio/company.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
var companyId = params.id;
return new Ember.RSVP.hash({
company: Ember.$.ajax({ url: '/api/company/'+companyId, dataType: "json", type: 'GET' })
}).then(function(message) {
return message;
}, function(error) {
console.log( error );
});
}
});
My route and template is loading fine, when I navigate to app/portfolio/company/1, but for some reason when I navigate to that route, Ember wont load the model (no error, but the {{model}} variable does not get populated in template). Only when I refresh the page, Ember loads the model?! I am a bit confused now...
Edit: added missing param and added better description
I think in your template or in controller you are using model like so
model.company replace it with model, and remove extraneous RSVP.hash
because Ember.$.ajax already returns promise which model hooks can handle
so in ES6 (ember-cli supports it) your model hook should look like this
model({ id }) {
return Ember.$.ajax('/api/company/' + id);
}
with above things everything should work, what was happening I think you were passing just model to {{link-to}} while your controller or template expecting model.company so was breaking things

Ember Simple Auth transition after login

I have login code on my application route, as per examples in the docs, but the call to authenticate does not seem to return a promise. The response I get in 'then' is undefined. Therefore the transition does not work. I have to manually refresh the page, and then the top redirect is called.
import Ember from 'ember';
// Make 'session' available throughout the application
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
redirect: function () {
this.transitionTo('orders');
},
actions: {
authenticate: function () {
var data = {
identification: this.controller.get('identification'),
password: this.controller.get('password')
};
this.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', data).then(
function(response) {
console.log(response); // undefined
this.transitionTo('orders'); // can't call on undefined
}
);
},
}
});
My issue was 'this' inside the function call was the wrong object. Solved by using var _this = this;
I'll post the full working code.;
import Ember from 'ember';
// Make 'session' available throughout the application
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
redirect: function () {
this.transitionTo('orders');
},
actions: {
authenticate: function () {
var data = {
identification: this.controller.get('identification'),
password: this.controller.get('password')
};
var _this = this;
this.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', data).then(
function(response) {
console.log(_this.get('session')); // this correctly gets the session
_this.transitionTo('orders');
}
);
},
}
});
The promise returned by the session's authenticate method doesn't resolve with a value. You can access data that the authenticator resolves with via the session's secure property, e.g. this.get('session.secure.token)'.

React/reflux how to do proper async calls

I recently started to learn ReactJS, but I'm getting confused for async calls.
Lets say I have a Login page with user/pass fields and login button. Component looks like:
var Login = React.createClass({
getInitialState: function() {
return {
isLoggedIn: AuthStore.isLoggedIn()
};
},
onLoginChange: function(loginState) {
this.setState({
isLoggedIn: loginState
});
},
componentWillMount: function() {
this.subscribe = AuthStore.listen(this.onLoginChange);
},
componentWillUnmount: function() {
this.subscribe();
},
login: function(event) {
event.preventDefault();
var username = React.findDOMNode(this.refs.email).value;
var password = React.findDOMNode(this.refs.password).value;
AuthService.login(username, password).error(function(error) {
console.log(error);
});
},
render: function() {
return (
<form role="form">
<input type="text" ref="email" className="form-control" id="username" placeholder="Username" />
<input type="password" className="form-control" id="password" ref="password" placeholder="Password" />
<button type="submit" className="btn btn-default" onClick={this.login}>Submit</button>
</form>
);
}
});
AuthService looks like:
module.exports = {
login: function(email, password) {
return JQuery.post('/api/auth/local/', {
email: email,
password: password
}).success(this.sync.bind(this));
},
sync: function(obj) {
this.syncUser(obj.token);
},
syncUser: function(jwt) {
return JQuery.ajax({
url: '/api/users/me',
type: "GET",
headers: {
Authorization: 'Bearer ' + jwt
},
dataType: "json"
}).success(function(data) {
AuthActions.syncUserData(data, jwt);
});
}
};
Actions:
var AuthActions = Reflux.createActions([
'loginSuccess',
'logoutSuccess',
'syncUserData'
]);
module.exports = AuthActions;
And store:
var AuthStore = Reflux.createStore({
listenables: [AuthActions],
init: function() {
this.user = null;
this.jwt = null;
},
onSyncUserData: function(user, jwt) {
console.log(user, jwt);
this.user = user;
this.jwt = jwt;
localStorage.setItem(TOKEN_KEY, jwt);
this.trigger(user);
},
isLoggedIn: function() {
return !!this.user;
},
getUser: function() {
return this.user;
},
getToken: function() {
return this.jwt;
}
});
So when I click the login button the flow is the following:
Component -> AuthService -> AuthActions -> AuthStore
I'm directly calling AuthService with AuthService.login.
My question is I'm I doing it right?
Should I use action preEmit and do:
var ProductAPI = require('./ProductAPI')
var ProductActions = Reflux.createActions({
'load',
'loadComplete',
'loadError'
})
ProductActions.load.preEmit = function () {
ProductAPI.load()
.then(ProductActions.loadComplete)
.catch(ProductActions.loadError)
}
The problem is the preEmit is that it makes the callback to component more complex. I would like to learn the right way and find where to place the backend calls with ReactJS/Reflux stack.
I am using Reflux as well and I use a different approach for async calls.
In vanilla Flux, the async calls are put in the actions.
But in Reflux, the async code works best in stores (at least in my humble opinion):
So, in your case in particular, I would create an Action called 'login' which will be triggered by the component and handled by a store which will start the login process. Once the handshaking ends, the store will set a new state in the component that lets it know the user is logged in. In the meantime (while this.state.currentUser == null, for example) the component may display a loading indicator.
For Reflux you should really take a look at https://github.com/spoike/refluxjs#asynchronous-actions.
The short version of what is described over there is:
Do not use the PreEmit hook
Do use asynchronous actions
var MyActions = Reflux.createActions({
"doThis" : { asyncResult: true },
"doThat" : { asyncResult: true }
});
This will not only create the 'makeRequest' action, but also the 'doThis.completed', 'doThat.completed', 'doThis.failed' and 'doThat.failed' actions.
(Optionally, but preferred) use promises to call the actions
MyActions.doThis.triggerPromise(myParam)
.then(function() {
// do something
...
// call the 'completed' child
MyActions.doThis.completed()
}.bind(this))
.catch(function(error) {
// call failed action child
MyActions.doThis.failed(error);
});
We recently rewrote all our actions and 'preEmit' hooks to this pattern and do like the results and resulting code.
I also found async with reflux kinda confusing. With raw flux from facebook, i would do something like this:
var ItemActions = {
createItem: function (data) {
$.post("/projects/" + data.project_id + "/items.json", { item: { title: data.title, project_id: data.project_id } }).done(function (itemResData) {
AppDispatcher.handleViewAction({
actionType: ItemConstants.ITEM_CREATE,
item: itemResData
});
}).fail(function (jqXHR) {
AppDispatcher.handleViewAction({
actionType: ItemConstants.ITEM_CREATE_FAIL,
errors: jqXHR.responseJSON.errors
});
});
}
};
So the action does the ajax request, and invokes the dispatcher when done. I wasn't big on the preEmit pattern either, so i would just use the handler on the store instead:
var Actions = Reflux.createActions([
"fetchData"
]);
var Store = Reflux.createStore({
listenables: [Actions],
init() {
this.listenTo(Actions.fetchData, this.fetchData);
},
fetchData() {
$.get("http://api.com/thedata.json")
.done((data) => {
// do stuff
});
}
});
I'm not big on doing it from the store, but given how reflux abstracts the actions away, and will consistently fire the listenTo callback, i'm fine with it. A bit easier to reason how i also set call back data into the store. Still keeps it unidirectional.

AngularJS Services (Update/Save)

New to AngularJS and trying to get a grasp of the framework, and trying to build a basic CRUD app. I can't seem to figure out what is needed to Update an existing record. Here is my service:
angular.module('appServices', ['ngResource']).
factory('App', function ($resource) {
var Item = $resource('App/:AppId', {
//Default parameters
AppId: '#id'
}, {
//Actions
query: {
method: 'GET',
isArray: true
},
getById: {
method: 'PUT'
},
update: {
method: 'POST'
}
});
return Item;
});
I can run a basic Get all query, and getById to populate an edit form, but that's where I'm stuck. Here is example code for getById
$scope.apps = App.query();
$scope.getEdit = function(AppId) {
App.getById({id:AppId}, function(app) {
$scope.original = app;
$scope.app = new App(app);
});
};
$scope.save = function() {
//What type of information should go here?
//Do I need to make changes to the appServices?
};
I guess, I'm just not sure what's next concerning Updating existing information, or how the "app" object gets passed to the API, can anyone point me in the right direction, or show me a quick update method?
This is a really messy way of handling save operations in angular. For one - you should not be using PUT operations for retrieval requests and secondly - all of this is already built-in to angular. See below.
var Item = $resource( 'App/Details/:AppId', { AppId: '#id' } );
var item = Item.get({ id: 1 }, function( data ) {
data.setAnothervalue = 'fake value';
data.$save();
);
What I'm doing here is retrieving an "Item" and then immediately saving it with new data once it's returned.
Angular JS provides a stack of defaults already, including query, save, remove/delete, get.etc. And for most RESTful APIs, you really shouldn't need to add much, if anything at all. See the resource docs for more information, particularly the information on defaults: http://docs.angularjs.org/api/ngResource.$resource
Additionally, once you get a handle on that - you may want to use $save for both create/update operations, but using POST/PUT (RESTful conventions). If you do, see my article that I wrote about not too long ago: http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/
After doing a bit more research, and reviewing Daniel's link (thanks). I got it working.
Controller method:
$scope.save = function() {
$scope.app.update();
};
Service Factory:
var Item = $resource('App/Details/:AppId', {
//Default parameters
AppId: '#id'
}, {
//Actions
query: {
method: 'GET',
isArray: true
},
getById: {
method: 'PUT'
},
update: {
method: 'POST'
}
});
Item.prototype.update = function (cb) {
console.log(this.AppId);
return Item.update({ AppId: this.AppId },
angular.extend({}, this, { AppId: undefined }), cb);
};
return Item;

javascript code architecture question

I'm about to make a web app which will have a pretty heavy client end. I'm not sure about the way to organize my javascript code, but here is a basic idea :
// the namespace for the application
var app = {};
// ajax middle layer
app.products = {
add : function(){
// send ajax request
// if response is successful
// do some ui manipulation
app.ui.products.add( json.data );
},
remove : function(){},
...
};
app.categories = {
add : function(){},
....
};
// the ui interface which will be called based on ajax responses
app.ui = {};
app.ui.products = {
add : function( product_obj ){
$('#products').append( "<div id='"+product_obj.id+"'>"+product_obj.title+"</div>" );
}
};
app.ui.categories = {};
Anybody got similar experiences to tell me the pros and cons of this approach? What's your way of designing client side javascript code architecture? Thanks.
[update] : This web app, as you see from the above, deals with products CRUD, categories CRUD only in a ajax fashion. I'm only showing an snippet here, so you guys know what I'm trying to achieve and what my question is. Again, I'm asking for inputs for my approach to organize the code of this app.
That is similar to the way I do my JavaScript projects. Here are some tricks I have used:
Create one file for each singleton object. In your code, store ajax, middle layer and ui interface in separate files
Create a global singleton object for the 3 layers usually in the project; GUI, Backend and App
Never use pure ajax from anywhere outside the Backend object. Store the URL to the serverside page in the Backend object and create one function that uses that URL to contact the server.
Have a JSON class on the server that can report errors and exceptions to the client. In the Backend object, check if the returned JSON object contains an error, and call the serverError function in the GUI class to present the error to the user (or developer).
Here is an example of a Backend object:
var Backend = {};
Backend.url = "/ajax/myApp.php";
Backend.postJSON = function(data, callback){
var json = JSON.stringify(data);
$.ajax({
type: "POST",
url: Backend.url,
data: "json="+json,
dataType: "json",
success: function(response){
if(response){
if(response.task){
return callback(response);
}else if(response.error){
return Backend.error(response);
}
}
return Backend.error(response);
},
error: function(response){
Backend.error({error:"network error", message:response.responseText});
},
});
};
Backend.error = function(error){
if(error.message){
Client.showError(error.message, error.file, error.line, error.trace);
}
};
This can be improved by storing the ajax object somewher in the Backend object, but it's not necessary.
When you build something non trivial, encapsulation is important to make things maintainable in long run. For example, JS UI is not just simple JS methods. A UI components consists of css, template, logic, localization, assets(images, etc).
It is same for a product module, it may need its own settings, event bus, routing. It is important to do some basic architectural code in integrating your chosen set of libraries. This had been a challenge for me when I started large scale JS development. I compiled some best practices in to a reference architecture at http://boilerplatejs.org for someone to use the experience I gained.
For client-side ajax handling I have a URL object that contains all my urls and than I have an ajax object that handles the ajax. This is not a centric approach.In my case I have I have different urls handling different tasks. I also pass a callback function to be executed into the ajax object as well.
var controller_wrapper = {
controller: {
domain: "MYDOMAIN.com",
assets: "/assets",
prefix: "",
api: {
domainer: "http://domai.nr/api/json/info",
tk_check: "https://api.domainshare.tk/availability_check"
},
"perpage": "/listings/ajax",
"save_image": "/members/saveImage",
"update": "/members/update",
"check_domain": "/registrar/domaincheck",
"add_domain": "/registrar/domainadd",
"delete_listing": "/members/deactivateProfile",
"save_listing": "/members/saveProfile",
"get_images": "/images/get",
"delete_image": "/images/delete",
"load_listing": "/members/getProfile",
"load_listings": "/members/getListings",
"loggedin": "/members/loggedin",
"login": "/members/login",
"add_listing": "/members/add",
"remove": "/members/remove",
"get": "/members/get",
"add_comment": "/members/addComment",
"load_status": "/api/loadStatus"
}
}
var common = {
pager: 1,
page: 0,
data: {
saved: {},
save: function (k, v) {
this.saved[k] = v;
}
},
ajax: {
callback: '',
type: 'POST',
url: '',
dataType: '',
data: {},
add: function (k, val) {
this.data[k] = val;
},
clear: function () {
this.data = {};
},
send: function () {
var ret;
$.ajax({
type: this.type,
url: this.url,
data: this.data,
dataType: this.dataType !== '' ? this.dataType : "json",
success: function (msg) {
if (common.ajax.callback !== '') {
ret = msg;
common.ajax.callback(ret);
} else {
ret = msg;
return ret;
}
return;
},
error: function (response) {
console.log(response);
alert("Error");
}
})
}
}
var callback = function (results) {
console.log(results
}
common.ajax.callback = callback;
common.ajax.type = "jsonp";
common.ajax.type = "POST";
common.ajax.url = controller_wrapper.controller.perpage;
common.ajax.add("offset", common.page);
common.ajax.add("type", $("select[name='query[type]']").val());
common.ajax.add("max", $("select[name='query[max]']").val());
common.ajax.add("min", $("select[name='query[min]']").val());
common.ajax.add("bedrooms", $("select[name='query[bedrooms]']").val());
common.ajax.add("sort", $("select[name='query[sort]']").val());
common.ajax.send();

Categories

Resources