I'm working with the latest release of Ember JS (RC1), and I have an architectural problem :
I have a very simple use case : a list of users, and a form to add users.
My Router:
App.Router.map(function () {
this.resource('users', function () {
this.route('new');
});
});
My Routes:
App.UsersRoute = Em.Route.extend({
model:function () {
return App.User.findAll();
}
});
My Controller:
App.UsersNewController = Em.ObjectController.extend({
saveUser:function () {
//'content' contains the user
App.User.save(this.content);
// here i want to reload the list of users, but it doesn't work
// The application goes correctly to the url /users
// But doesn't call the 'model' function
this.transitionToRoute('users');
}
});
As I say in the above comment, when I create a new User, I'd like to redirect to the list of users (that part works) AND reload the user list by calling the 'model' method of the route (that part doesn't).
I could write a method in UsersController to reload the list, but then I would have duplication between UsersRoute and UsersController.
Can someone help me on this problem ?
Thanks
P.S. : here a fiddle http://jsfiddle.net/vsxXj/
Ember Documentation on the model hook:
"A hook you can implement to convert the URL into the model for this
route."
So i do not think that this hook is right for this case. I think you should use the setupController hook for this case. Try this:
App.UsersRoute = Em.Route.extend({
setupController(controller){
controller.set("content", App.User.findAll());
}
});
Related
I am wondering the appropriate way to access a route model from a different non nested route controller.
If I have my routes set up like this: (this works however, not sure if its proper)
App.Router.map(function() {
this.route('admin');
this.route('page1');
}
And the Page 1 route has a model like this:
App.page1Model = {content:'Content of simple model'};
App.Page1Route = Ember.Route.extend({
model(){
return App.page1Model;
});
Then the admin controller wants to access the page1 route, I can only do it like this:
App.AdminController = Ember.Controller.extend({
page1Model:App.page1Model,
Now do stuff with page1Model.....
});
Ive tried to use Ember.inject.controller() however that only works for me when my routes are nested and I want to access Parent controller from child. Is there a way to use that syntax to get what I want, or is there a better way than what im doing?
Thanks
There's an inherent problem with what you're asking for: when the user is on the admin page, they're not on the page1 page, so there's no page1 context. Some questions you might want to ask:
what happens if the user goes to /admin having never gone to /page1?
what happens if the user goes to /page1 then /page2 then /admin?
I can think of two Ember-esque ways of doing what you want:
A Page1ModelService. Here, you create an Ember.Service that holds an instance of Page1Model. You inject the service into route:page1 and route:admin and let them each pull off the instance. Whether they can change which instance of the model is showing is up to you.
Return a Page1Model instance in the model hook for route:application. This route sits above both route:page1 and route:admin, so they can both look up the model as follows:
// route:application
model() {
return App.Page1Model.create();
}
// route:page1
model() {
return this.modelFor('application');
}
I was able to achieve my goal through using registers and injection. Can someone please take a look and let me know if this is 'proper' through Ember standards or if there is a better way ( #James A. Rosen :) )?
OH! If there is a better way to attach the model to the page1 route, please let me know. This worked though I am not sure if i like the .model after create().
JSBIN: http://jsbin.com/tikezoyube/1/edit?html,js,output
JS of that:
var App = Ember.Application.create();
var page1Model = {title:'Old Title'};
var page1ModelFactory = Ember.Object.extend({
model : page1Model
});
App.Router.map(function(){
this.route('page1');
this.route('admin');
});
App.register('model:page1', page1ModelFactory);
App.inject('controller:admin','page1Model','model:page1');
App.Page1Route = Ember.Route.extend({
model(){ return page1ModelFactory.create().model; }
});
App.AdminController = Ember.Controller.extend({
actions:{
updateTitle:function(){
console.log(this.get('page1Model').model.title);
this.get('page1Model').set('model.title','THE NEW TITLE!');
console.log(this.get('page1Model').model.title);
this.transitionToRoute('page1');
}
}
});
Thanks!
I am accessing the query parameters in the route using the below code:
export default Ember.Route.extend({
afterModel: function(params, transition){
this.set('clientId', transition.queryParams.clientId);
},
setupController: function(controller) {
controller.set('clientId', this.get('clientId'));
}
});
The reason I am not using a controller is because I am feeding this data straight in to a component and I am of the understanding that in newer versions of Ember controllers will be phased out.
This is currently working however if I change any of the properties in the URL it doesn't update in the app unless I refresh the page or exit the route and re-enter it.
How can I "re-run" the route afterModel and update the properties which are passed to the component?
If the only option is to use a controller then I can implement this until a better solution comes along.
Try adding this to your route:
queryParams: {
'clientId' : {
refreshModel: true,
replace : true,
},
}
See here for more information.
EDIT: Here is the github repo. And you can test the site here.
On the homepage, just open the browser console and you will notice that WaitOn and data are being run twice. When there is no WaitOn, then the data just runs once.
I have setup my pages by extending RouteController and further extending these controllers. For example:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldTemplates: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
// Authentication
onBeforeAction: function() {
if(_.isNull(Meteor.user())){
Router.go(Router.path('login'));
} else {
this.next();
}
}
});
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
console.log("from controller waitOn");
return Meteor.subscribe('userProfileVerification');
},
data: function() {
// If current user has verified email
console.log("from controller data start");
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
console.log("from controller data end");
return data;
}
});
On observing the console in the client, it seems the hooks are being run 2-3 times. And I also get an error on one of the times because the data is not available. The following is the console on just requesting the page once:
from controller waitOn
profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:44 from controller data start
debug.js:41 Exception from Tracker recompute function: Cannot read property 'phoneNumber' of undefined
TypeError: Cannot read property 'phoneNumber' of undefined
at ProfileController.extend.data (http://localhost:3000/lib/router/profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:46:62)
at bindData [as _data] (http://localhost:3000/packages/iron_controller.js?b02790701804563eafedb2e68c602154983ade06:226:50)
at DynamicTemplate.data (http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:219:50)
at http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:252:25
at null.<anonymous> (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2445:26)
at http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1808:16
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2043:12)
at viewAutorun (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1807:18)
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:296:36)
at Tracker.Computation._recompute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:310:14)
from controller data start
from controller data end
from controller waitOn
from controller data start
from controller data end
Have I not used the controllers properly?
Without being able to see the rest of the code that you have defined that uses these route controllers (such as templates or route definitions), I cannot accurately speak to the reason for the data function being called multiple times. I suspect that you may be using the ProfileVerificationsController with multiple routes, in which case the data definition for this controller would be executed multiple times, one for each route that uses the controller. Since the data definition is reactive, as you browse through your application and data changes, this might be resulting in the code defined to be rerun.
As for your controller definitions, I would suggest making a few modifications to make the code more robust and bulletproof. First, the ProfileController definition:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldRegions: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
onBeforeAction: function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
this.redirect('login'); // Could do this as well
this.render('login'); // And possibly this is necessary
} else {
this.next();
}
}
});
Notice the first thing that I changed, yieldTemplates to yieldRegions. This typo would prevent the regions from your templates using this route controller to be properly filled with the desired subtemplates. Second, in the onBeforeAction definition, I would suggest checking not only whether or not the Meteor.user() object is null using Underscore, but also checking for whether or not it is undefined as well. The modification that I made will allow you to check both states of the Meteor.user() object. Finally, not so much a typo correction as an alternative suggestion for directing the user to the login route, you could use the this.redirect() and this.render() functions instead of the Router.go() function. For additional information on all available options that can be defined for a route/route controller, check this out.
Now for the ProfileVerificationsController definition:
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
return Meteor.subscribe('userProfileVerification');
},
data: function() {
if(this.ready()) {
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
return data;
}
}
});
Notice the one thing that I changed, which is to wrap all of your code defined in the data option for your controller with a if(this.ready()){}. This is critical when using the waitOn option because the waitOn option adds one or more subscription handles to a wait list for the route and the this.ready() check returns true only when all of the handles in the wait list are ready. Making sure to use this check will prevent any cases of data unexpectedly not being loaded yet when you are building up your data context for the route. For additional information on defining subscriptions for your routes/route controllers, check this out.
As a final suggestion, for your onBeforeAction option definition in your ProfileController, I would suggest moving this out into its own global hook like so:
Router.onBeforeAction(function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
} else {
this.next();
}
});
Defining this check in the global hook ensures that you don't have to worry about adding your ProfileController to all of your routes just to make sure that this check is run for all of them. The check will be run for every route every time that one is accessed. Just a suggestion, though, as you may have reasons for not doing this. I just wanted to suggest it since I make sure to do it for every Meteor app that I develop for additional security.
In my application I want to read the parameters user is entering and then I want to use that parameter. http://responsive.beta.postify.com/X I want to read that X value. But first how do I ensure that the router expects a parameter?
My router is like this
Cards.Router.map(function ()
{
this.resource('cards', {path: '/'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
I want that parameter whenever the app loads. That is going to be my clientID which I would be using to fetch data from server depending upon the client.
Any thoughts on it?
When I do something like this
Cards.Router.map(function ()
{
this.resource('cards', {path: ':clientID'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
and in my browser if I put like this http://responsive.beta.postify.com/#/26 then its working but if I do like http://responsive.beta.postify.com/26 then it is not working.
To answer your question directly, to use a parameter in a route you would do something like this:
this.resource('cards', { path: '/:user_id' });
Then in your route
App.CardsRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.user_id);
}
});
This is how you can get a parameter in a certain route. Now as far as your application goes, using the code I posted above should get you that parameter as long as they access the root ('/') of your application on first load and have the user_id in the url.
I would suggest a different strategy maybe for getting the client_id and storing it for later user in your application. For example, in my application I have an Ember.Application.initializer({}) where I store the client_id. All depends on your server configuration and how your app is built, but I would definitely try and get the client_id a different way if you can!
Good luck.
in Ember.js I have route with model. Could you help me, when I'm on route playlist how to reload this route (or set new data to model) called by callback from another JS function? I've been looking to documentation so long, but no help for me.
App.PlaylistRoute = Ember.Route.extend({
setupController: function(controller, model) {
$.getJSON('api/playlist.php?' + Math.random().toString(36), function (data) {
controller.set('model', data);
});
}
});
Thanks a lot!
It seems the solution in the answer won't work for current route.
I had a same issue and tried the solution here and it worked.
http://discuss.emberjs.com/t/refresh-current-view-page-after-language-change/4291/5#post_5
In your route.
actions: {
sessionChanged: function() {
this.refresh();
}
}
and in your controller.
observeSession: function() {
this.send("sessionChanged");
}.observes("session.isAuthenticated"),
There are two ways of doing it.
One is write an action in playlist route and call this.refresh() inside it
For more information you can visit Ember Guide refresh method for route.
The other way is in your controller depending on the situation when you need to reload your route use
this.get('target.target.router').refresh();
any of the two would help you in refreshing your route.
A small note of refresh method below from ember guides:
Refresh the model on this route and any child routes, firing the beforeModel, model, and afterModel hooks in a similar fashion to how routes are entered when transitioning in from other route. The current route params (e.g. article_id) will be passed in to the respective model hooks, and if a different model is returned, setupController and associated route hooks will re-fire as well.
From a controller use transitionToRoute:
this.transitionToRoute('playlist', newModel);
From a route use transitionTo:
this.transitionTo('playlist', newModel);
For example, imagine you have an action on your controller
App.PlaylistController = Ember.ArrayController.extend({
actions: {
grabNewModel: function(){
//get some new model
this.transitionToRoute('playlist', newModel);
}
}
});
This answer appears first on google when searching how to refresh a route with the current accepted answer being out of date. If you really need to refresh a route and perform the model hook action again from an action in a controller then use the following:
In the route
#action
refreshModel() {
this.refresh();
}
In order to call this action in the controller use
this.send('refreshModel');
For example:
#action
performUpdate(event) {
event.preventDefault();
// Perform necessary action
this.send('refreshModel');
}
Note: This will send the action to the corresponding route for the controller it is called from, and update that route only and any child routes.