How to get a pagination route to load properly in Ember.js? - javascript

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 });
}
});

Related

How reload the data in the page using EmberJS

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;
});

How to migrate from meteor router to IR (Iron Router)?

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);
}
});

Ember.js afterModel hook triggers two identical requests

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)

EmberJS: initialize nested model with parent model loaded by ajax

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

ember linkTo sending empty params in route's model hook

I have a blog site where going to the url /posts should list all posts. Then clicking a link for /posts/:post_id should display the details of that post. However, when clicking the link, it seems that the linkTo is not passing the :post_id properly. Here is my code:
Router:
App.Router.map(function() {
this.resource("posts", function() {
this.resource("post", { path: "/:post_id" }, function() {
//other routes
});
});
this.route("posts.index", { path: "/" });//Links root url to posts index template
});
Routes:
/****** Posts ******/
App.PostsRoute = Ember.Route.extend({
});
App.PostsIndexRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
}
});
/****** Post *******/
App.PostRoute = Ember.Route.extend({
});
App.PostIndexRoute = Ember.Route.extend({
});
Controllers:
/****** Posts ******/
App.PostsController = Ember.ArrayController.extend({
});
App.PostsIndexController = Ember.ArrayController.extend({
});
/****** Post ******/
App.PostController = Ember.ObjectController.extend({
});
App.PostIndexController = Ember.ObjectController.extend({
});
Posts/Index Template:
{{#each post in controller}}
<tr>
<td>{{post.name}}</td>
<td>{{post.title}}</td>
<td>{{post.content}}</td>
<td>{{#linkTo post.index post}}View{{/linkTo}}</td>
</tr>
{{/each}}
Every time I click the {{linkTo post.index post}}, it takes me to the correct url, renders the correct templates (post/index), but nothing displays. I tried putting a model hook in the PostIndexRoute like this:
App.PostIndexRoute = Ember.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
}
});
But params = {} It has no data in it. Can someone point me in the right direction? what am I doing wrong?
Since you are passing the post object you need to {{linkTo}} a route that accepts a dynamic URL segment. In your example posts.index doesn't accept a parameter.
<td>{{#linkTo post post}}View{{/linkTo}}</td>
Here is an example of a way to structure your code to better handle the routing: JSBin example
You should also check out the Ember.js routing guides as an example of how to structure routes.

Categories