in my app I need that when I visit the root, it redirects to the view of the most recent model that in this case is always the firstObject in the collection.
App.Router.map(function() {
this.resource('threads', { path: '/' }, function() {
this.route('view', { path: ':thread_id' });
});
});
App.ThreadsRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
},
afterModel: function(threads) {
this.transitionTo('threads.view', threads.get('firstObject'));
}
});
This is working without problems, but wheter I directly go to the root url or the view one 2 identical requests to /threads are made. As soon I comment the afterModel section the redirection obviously doesn't work anymore but the requests are back to 1.
Any help is gladly accepted!
Since Threads/View are nested routes, the ThreadsRoute will be also called on the View route.
I think you should just call the ThreadsRoute -> ThreadsIndexRoute or separate model and afterModel hooks this way:
(Not tested code)
App.ThreadsRoute = Ember.Route.extend({
model: function() {
// console.log('in model);
return this.store.find('thread');
}
});
App.ThreadsIndexRoute = Ember.Route.extend({
afterModel: function(threads) {
// console.log('in afterModel);
this.transitionTo('threads.view', threads.get('firstObject'));
}
});
Your example is identical to this one:
App.Router.map(function() {
this.resource('threads', { path: '/' }, function() {
this.route('view', { path: ':thread_id' });
});
});
App.ThreadsRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
}
});
App.ThreadsIndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
}
});
If you check the inspector for which route you're in when you visit '/', you'll see that you're inside of
threads.index, having transitioned into each of them in turn, which is why you're seeing the call to find twice.
You can fix this by only having the model hook in ThreadsIndexRoute (e.g. rename your ThreadsRoute to ThreadsIndexRoute)
Related
I have a search results page, which is developed using EmberJS. I use Ember.Route to load data through from API. The search API which i was using is very slow. So we decided to load the data from a cache API. But the problem is that, the cache might have some old data. So we decided to re-load data in the already loaded page using search API.
My existing Route code would look like this
define(‘search’, ['jquery'], function($) {
var route = Ember.Route.extend({
model: function() {
return searchApiCall( someParameter );
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
return route;
});
In the above code, am just calling the search API and returning the json response. But, what i want is something as below.
define(‘search’, ['jquery'], function($) {
var route = Ember.Route.extend({
model: function() {
setTimeout(function(){
return searchApiCall( someParameter );
}, 10);
return cachedSearchResult( someParameter );
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
return route;
});
So what am looking in the above code is to pass the cached result at first and then somehow load the result from actual search api when its available.
Please let me know, whats the best way for this in EmberJS.
My approach would be to use the afterModel hook, something like this... although your version of Ember looks a bit old, so hopefully that is available.
const set = { Ember };
define(‘search’, ['jquery'], function($) {
var route = Ember.Route.extend({
model: function() {
return {
searchResults: cachedSearchResult( someParameter );
};
},
setupController: function(controller, model) {
controller.set('model', model);
},
afterModel: function(model) {
let searchResults = searchApiCall( someParameter );
set(model, 'searchResults', searchResults);
}
});
return route;
});
Ref to the question Trying to Migrate to Iron-Router from Router. I still dont understand how to migrate meteor router to iron-router.
I am using router in my meteor project. The router file is like followings:
Meteor.Router.add({
"/settings": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'site_settings';
},
"/new_page": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'new_page';
},
"/navigation": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'navigation';
},
"/login": function() {
return 'loginButtonsFullPage';
},
"/users": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'admin_users';
}
});
If someone knows how to use an iron-router to replace the return template in the right way. Much appreciate.
I meet a little bit complicated router function, and I have no idea how to solve it. the code is like:
"/": function() {
// Don't render until we have our data
if (!GroundDB.ready()) {
//if (!Offline.subscriptionLoaded('pages') || !Offline.subscriptionLoaded('settings')) {
return 'loadingpage';
} else {
var page_slug = utils.getSetting('indexPage');
var page = Pages.findOne({slug: page_slug});
if(!page) {
page = Pages.findOne();
// if pages dont have any public pages
if (!page) {
var isIndexPageInNav=Navigation.findOne({"location":"header_active","pages.slug":page_slug});
// if index page slug in navigation that means the user dont have right to view this slides or the index page not exist
if(isIndexPageInNav)
return 'loginButtonsFullPage';
else
return '404';
}
else {
page_slug = page.slug;
}
}
Session.set("page-slug", page_slug);
return page.template;
}
}
As you know the iron-router need give a template at the begining. but with router I can return dynamic templates. How does iron-router implement this idea.
Router.map(function() {
//site_settings being the name of the template
this.route('site_settings', {
path: '/settings',
action: function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
//if the conditional fails render a access_denied template
this.render('access_denied');
} else {
//else continue normally rendering, in this case the 'site_settings'
//template
this.render();
}
}
});
this.route('loginButtonsFullPage', {
path: '/login'
});
});
Note since you will be doing that if user is admin conditional a lot you can wrap that logic inside a controller and link it to all the relevant routes such as:
Router.map(function() {
this.route('site_settings', {
path: '/settings',
controller: 'AdminController'
});
this.route('new_page', {
path: '/new_page',
controller: 'AdminController'
});
this.route('navigation', {
path: '/navigation',
controller: 'AdminController'
});
//etc...
//don't need to add the controller for this one
//since all users have access
this.route('loginHuttonsFullPage', {
path: '/login'
});
});
AdminController = RouteController.extend({
action: function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
this.render('access_denied');
} else {
this.render();
}
}
});
A couple of other things you will want to check out in iron-router are layouts with {{> yield}} and waitOn which is indispensable.
The docs at https://github.com/EventedMind/iron-router will do a better job of explaining those concepts then I can here.
Here is my attempt at your more complicated route. It may not work right away because I may be misunderstanding what you are doing but the key things are to substitute the returns with this.render(template_name); waitOn instead of checking if something is ready(), adding all the required subscriptions to the waitOn and then finally adding all your logic to an action
//note: index is the name of the route, you do not actually need a template called index.
//in the previous examples where no template to render was returned then iron-router will
//look for a template with the same name as the route but in this route we will be providing
//it with a specific route name in all cases
this.route('index', {
path: '/',
//assuming GroundDB is a subscription e.g. GroundDB = Meteor.subscribe('groundDB');
//I don't know what your page and nav subscriptions are called but you should wait on them too.
//if you haven't assigned them to a variable do something like
//pageSubscription = Meteor.subscribe('pages');
waitOn: [GroundDB, pageSubscription, navigationSub],
//the template to load while the subscriptions in waitOn aren't ready.
//note: this can be defined globally if your loading template will be the same
//for all pages
loadingTemplate: 'loadingpage',
//here we evaluate the logic on which page to load assuming everything has loaded
action: function() {
var page_slug = utils.getSetting('indexPage');
var page = Pages.findOne({slug: page_slug});
if (!page) {
var isIndexPageInNav = Navigation.findOne({"location":"header_active","pages.slug":page_slug});
if(isIndexPageInNav)
this.render('loginButtonsFullPage');
else
this.render('404');
} else {
page_slug = page.slug;
}
Session.set("page-slug", page_slug);
this.render(page.template);
}
});
Is there a way to have multiple dynamic segments with a single resource? My use case is to avoid letting the user hit index routes.
Example:
this.resource('tracks', { path: 'albums/:album_id/tracks/:tracks_id' });
And I'd like to avoid the user from hitting the following routes:
albums/:album_id
albums/:album_id/tracks
albums/:album_id/tracks/:track_id
Routes:
this.resource('albums', { path: 'albums' }, function(){
this.resource('album', { path: '/:album_id' }, function() {
this.resource('tracks', { path: 'tracks' }, function(){
this.resource('track', { path: '/:track_id' });
});
});
});
Any help would be greatly appreciated.
Defining Your Routes
NOTE: If you define a resource using this.resource and do not supply a
function, then the implicit resource.index route is not created.
It would be better to use Ember's nested routes. Each route having its own dynamic segment.
App.Router.map(function () {
this.resource('albums', { path: '/albums' }, function () {
this.resource('album', { path: ':album_id' }, function () {
this.resource('tracks', { path: 'tracks' }, function () {
this.resource('track', { path: ':track_id' });
});
});
});
});
If you want to show the user the first track immediately after clicking an album, you could use a redirect.
App.AlbumRoute = Ember.Route.extend({
afterModel: function (album, transition) {
this.transitionTo('track', {album_id: album.id, track_id: album.tracks[0].id});
},
});
Check out the docs on redirection: http://emberjs.com/guides/routing/redirection/
Just for completeness sake, the index routes aren't necessary, they are just a freebie convenience if you define them, if you don't define them it won't go to them.
http://emberjs.jsbin.com/eMofowUQ/1/edit
And you can define multiple slugs in a single path and go directly to it, just note you'll only have a single model for that single resource, so you'll have to deal with that.
http://emberjs.jsbin.com/eMofowUQ/2/edit
A possible solution for us was to use the following:
App.AlbumsIndexRoute = Ember.Route.extend({
redirect: function(){
this.transitionTo('dashboard');
}
});
I got the following simple ember.js-setup, which works all great
App.Router.map(function() {
this.resource('tourdates', function() {
this.resource('tourdate', { path: ':tourdate_id' });
});
});
App.TourdatesRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
return tourdates.findBy('id', params.tourdate_id);
}
});
so, pretty simple, whenever i call index.html#/tourdates, i get the data via api. and when I click on a link in this view and call f.e. index.html#/tourdates/1 it just displays the view for its nested child.
This all breaks, when I directly call index.html#/tourdates/1 with the message
DEPRECATION: Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object (error on <Ember.Route:ember174>)
Error while loading route: ReferenceError {}
Uncaught ReferenceError: tourdates is not defined
Although he makes the ajax-call to the api and gets the data, he is not able to initialize the nested model
When your App.TourdatesRoute is loaded, all data from the json, will be rendered. And when you click to edit one of these loaded objects, using a link-to for example, ember is smart enough to get the already referenced object, instead of send a new request. So your url will change to: yourhost.com/tourdate/id.
When you direct call this url, it will call the App.TourdateRoute model method. Because doesn't have any pre loaded data. But in your case you have a:
tourdates.findBy('id', params.tourdate_id);
And I can't see in any place the declaration of tourdates.
I recommed you to change your TourdateRoute to TourdateIndexRoute so when transitioning to tourdates the ajax call is performed once:
App.TourdatesIndexRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
The TourdatesRoute is called both for TourdateRoute and TourdatesIndexRoute, because it's the parent route of both. So fetching all data in the TourdatesIndexRoute will ensure this is just called when transitioning to tourdates.
In your TourdateRoute you will load just the record needed. Something like this:
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
// retrieve just one data by id, from your endpoint
return $.getJSON('http://someapi.com/' + params.tourdate_id + '?jsoncallback=?').then(function(data) {
return data;
});
}
});
So a direct call to yourhost.com/tourdate/id will just loaded one record.
About your warning message, it happens because in some route you have:
App.MyRoute = Ember.Route.extend({
events: {
eventA: function() { ...},
eventB: function() { ...},
}
});
The events is deprecated and you need to use actions:
App.MyRoute = Ember.Route.extend({
actions: {
eventA: function() { ...},
eventB: function() { ...},
}
});
I hope it helps
I have a route at #/posts for posts that shows the first page of posts, and a pagination route at #/posts/:page_id that shows whatever page you request. My router looks like this:
App.Router.map(function(match) {
//...
this.route('posts');
this.resource('postsPage', { path: '/posts/:page_id' });
});
And those routes look like this:
App.PostsRoute = Em.Route.extend({
model: function(params) {
return App.store.find(App.Post, { 'page': params.page_id });
}
});
App.PostsPageRoute = Em.Route.extend({
renderTemplate: function() {
this.render('posts');
},
model: function(params) {
return App.store.find(App.Post, { 'page': params.page_id });
}
});
This worked fine for about a month after I implemented it, but it recently stopped working for anything but the #/posts route. Pages don't load, even though my REST API returns the right JSON.
Fiddle here
#/posts route here
#/posts/1 route here (note it doesn't load the content even though the JSON is sent)
By default, the postsPageRoute will setup the postsPage controller. You need to tell it to setup the posts controller.
App.PostsPageRoute = Em.Route.extend({
setupController: function(controller, model) {
this.controllerFor('posts').set('content', model)
},
renderTemplate: function() {
this.render('posts');
},
model: function(params) {
return App.store.find(App.Post, { 'page': params.page_id });
}
});