Below is a reduced Ember controller. jQuery is used to make a remote call, and action needs to be taken in the controller in the callback.
In the callback, this refers to the GenericController correctly, I can read a value with this.get('someKey') but this.set('someKey', '') will not set the value. This works when the this.transitionTo method is removed. Any help on how to get the set to work with a transition present would be super helpful.
App.GenericController = Ember.Controller.extend({
someAction: function() {
var jqxhr = jQuery.getJSON(this._endpoint, {someKey: this.get('someKey')});
jqxhr.done(this._someActionComplete.bind(this));
},
_endpoint: '/some/generic.json',
_someActionComplete: function(json, textStatus, jqxhr) {
this.set('someKey', '');
this.transitionToRoute('reference', reference);
}
});
It' all about async.
When you invoke your method this correctly references the controller as you already noticed, this is why you can use this.get('someValue'), but by the time the ayncronous call returns (wenn done is invoked for example) this no longer references your controller anymore but the object which invoked the done function, so you have to safe the correct reference to this before you make the request, this way you can use it to pass it to the bind function:
App.GenericController = Ember.Controller.extend({
someAction: function() {
var _this = this;
var jqxhr = jQuery.getJSON(this._endpoint, {someKey: this.get('someKey')});
jqxhr.done(_this._someActionComplete(_this));
},
_endpoint: '/some/generic.json',
_someActionComplete: function(json, textStatus, jqxhr) {
this.set('someKey', '');
}
});
Update in response to your last comment
I don't know what you are doing differently, but take a look at this simple Demo that it works as expected. Click the button "Some action" to execute the request.
Hope it helps.
Because this in jqxhr.done does not reference the GenericController, it references the object that invoked done. This is how I solve it:
App.GenericController = Ember.Controller.extend({
someAction: function() {
var self = this;
var jqxhr = jQuery.getJSON(this._endpoint, {someKey: this.get('someKey')});
jqxhr.done(self._someActionComplete.bind(self));
}
}
Now you still have a reference to this you can use to call other functions, including set, on the controller.
Related
I have a component which adds some functionality to a <select> tag. I want to initialise some javascript after the <select> has fully rendered including all <option> tags. The data used to populate the <option> tags is an array of objects provided from an ajax request.
I'm using ember-data and finding this works when the data is provided from the store, meaning it is an instance of DS.RecordArray which has helpful properties like isLoaded. However, when the data is provided from a jQuery ajax call and is just plain JSON, it appears as though the component tries to render everything before the promise returned by jQuery is fulfilled.
I feel the issue is with how I'm handling promises as the issue seems to be related to things initialising before they should (ie promises have resolved properly). I tried wrapping the ajax call in an RSVP.Promise object but not luck, (I'm using Ember-CLI). Below is a simplified version of what I have so far. Any help would be appreciated.
// My route
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
var hash = Ember.RSVP.hash({
// myOptions: $.getJSON('/api/options')
myOptions: new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: '/api/options',
type: 'get',
dataType: 'json',
success: function(result) {
Ember.run(null, resolve, result);
},
error: function(result) {
Ember.run(null, reject, result);
}
});
})
});
return hash.then(function(result) {
controller.set('optionsForSelect', result.myOptions);
});
}
});
// My component
export default Ember.Select.extend({
didInsertElement: function() {
// Is called before ajax request has finished
this.$().mySelectPlugin({
});
}
});
// Handlebars template
{{my-select-plugin content=optionsForSelect optionValuPath="content.id" optionLabelPath="content.name"}}
To me, this seems like something that should be handled in the controller, not the route. Here's what I did for a similar situation.
App.LessonsShowIndexController = Ember.ObjectController.extend({
author: function() {
return this.get('model').get('author');
}.property('author'),
fullName: function() {
return this.get('author').get('firstName') + ' ' + this.get('author').get('lastName');
}.property('author.isFulfilled'),
});
Using the isFulfilled property allows the controller to wait for the promise to resolve before using the data. In your case, you could have a property that returns a promise and another that waits for it to be fulfilled.
In the case below videoEnded function is successfully called, however how do I get a reference to the actual Ember component it self?
export default Ember.Component.extend({
videoEnded: function(){
var self = this;
alert('how do i get a reference to the actual ember component object here ?')
}
didInsertElement: function() {
var self = this;
var options = {};
self._soundjs = soundjs('soundOne', options, function(){
});
self._soundjs.on('ended',self.videoEnded);
}
The following solved my issue. videEnded callback was losing the components context. The following code resolved the issue, self passed to video ended refers to the component context which can then be used, to send actions to the context controller.
self._soundjs.on('ended',function() { self.videoEnded.apply(self, arguments); });
Credit: #teddyzeenny
I'm trying to load the current user into the data store but am having some difficulty. The server uses PassportJS and visiting /api/users/me returns a JSON object similar to this:
{"user":{"_id":"53a4d9c4b18d19631d766980","email":"ashtonwar#gmail.com",
"last_name":"War","first_name":"Ashton","location":"Reading, England",
"birthday":"12/24/1993","gender":"male","fb_id":"615454635195582","__v":0}}
My store is just defined by App.store = DS.Store.create();
The controller to retrieve the current user is:
App.UsersCurrentController = Ember.ObjectController.extend({
content: null,
retrieveCurrentUser: function() {
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
App.store.createRecord('user', data.user);
var currentUser = App.store.find(data.user._id);
controller.set('content', currentUser);
});
}.call()
});
It is called by my application controller:
App.ApplicationController = Ember.Controller.extend({
needs: "UsersCurrent",
user: Ember.computed.alias("controllers.UsersCurrent")
});
I suspect the line App.store.createRecord('user', data.user); is causing issues but I don't have any idea how to fix it.
The console logs TypeError: this.container is undefined while the Ember debugger shows every promise is fulfilled and the users.current controller has no content. Thankyou for any help you can provide.
Are you defining the store on the App namespace, because Ember Data doesn't do that by default. Either way, you're failing to define the type you want to find after you create the record.
var currentUser = controller.store.find('user', data.user._id);
createRecord returns the record, so there is no point in finding it afterward
var currentUser = controller.store.createRecord('user', data.user);
Also in your example, you are trying to call the function immediately on the type, and not on the instance. You should add that as a method to run on init.
App.UsersCurrentController = Ember.ObjectController.extend({
retrieveCurrentUser: function() {
console.log('hello')
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
var user = controller.store.createRecord('user', data.user);
controller.set('model', user);
});
}.on('init')
});
http://emberjs.jsbin.com/OxIDiVU/693/edit
This might be a silly question, but I can't find out anything about it anywhere...
I create a method in one of my controller to verify if the user session is still good, and I'm using this method in almost every page of my app in my beforeModel. But the thing is that I don't want to copy/paste the code every time in every route, this will be dirty and I really don't like it.
Lets say I have this controller :
App.LoginController = Ember.ObjectController.extend({
...
isSession: function() {
var session = this;
Ember.$
.get(host + '/session', function(data) {
console.log('DEBUG: Session OK');
})
.fail(function() {
console.log('DEBUG: Session FAIL');
session.transitionToRoute('login');
});
}
});
How can I call it in this router :
App.HomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
//Here
},
model: function() {
return this.store.all('login');
}
});
I've tried this this.get('loginController').isSession(); but I receive this error Error while loading route: TypeError: Cannot call method 'isSession' of undefined
Thanks for the help !
[edit]
I don't have much to show but this :
My map
App.Router.map(function() {
this.route('login', { path: '/' });
this.route('home');
this.resource('enquiries', function() {
this.route('enquiry', { path: '/:enquiry_id' }, function() {
this.route('update');
});
});
});
Most likely I only Have a LoginController and my HomeRoute. (its the beginning of the app)
I don't need to create a Route for my Login because I have an action helper in my login template and I'm redirected to my Home template after that.
You need to use controllerFor() method in order to call method on controller from router. If method is an action you need to use send() method, like this.controllerFor('login').send('isSession')
App.HomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
transition.abort();
this.controllerFor('login').isSession()
}
});
If you don't need a return value from isSession you might consider making it an action on a top-level route. The router.send method in the docs has a pretty good example of how you declare actions as well as how you call them. Note that send is also a method you can call on a controller. Actions bubble up from a controller, to the parent route, and then all the way up the route hierarchy, as shown here
I'm overriding the deserialize method so I can load an object from the backend corresponding with the id. However, the way I get this object is asynchronous. Deserialize does not wait for my callback and returns automatically.
Example:
show: Em.Route.extend({
route: "/:id",
deserialize: function(router, post) {
var postController = router.get('postController ');
postController.findById(post.id, function(post) {
return post
});
}
The call to the backend is made but deserialize returns automatically. Is there a way to work with asynchronous call in deserialize?
Thank you
Luke Melia did an NYC Ember meetup lightning talk on this very thing, using Promises via jQuery Deferred objects. Basically, Ember detects if you return a Promise (by seeing if the object has a then method), and puts the route in a loading state (which you have to declare as a sibling of the state with the deserialize). On ajax.done, you can resolve the promise, which puts the router fully in the state with the data fully loaded.
With the new router, you're not really supposed to use the support for async transitions that is present in the vanilla statemanager, but you can use the automatic loading states to achieve the same kind of thing.
Here is what i have found out, this would work in older versions of ember:
In the enter function of the state/route you can try to load the data. The enter function receives as second argument at transition object which have 2 methods. One is 'async' which tells the transition that it cant continue until the other method 'resume' has bean called.
So in the example:
...
enter: function (r, t) {
t.async();
r.get('postController').loadResources(t.resume);
},
...
For newer versions of Ember you should use some kind of proxy for the data you load.
{
PostController: Ember.ObjectController.extend({
find: function (id) {
var ajax = Em.$.ajax(); //call the ajax thing
ajax.done(Em.$.proxy(function (data) {
this.set('content', data);
}, this));
return this;
}
})
...
Router: Em.Router.extend({
show: Em.Route.extend({
deserialize: function(router, post) {
var postController = router.get('postController');
postController.find(post.id);
return postController;
}
});
})
}