Meteor full page refresh on load more button ( iron-router ) - javascript

My Meteor app seems to be refreshing on a new route, I added iron-router-progress as I'm following the Discover Meteor.
Heres the URL - http://thusstyles.meteor.com/
Github URL - github.com/ThusStyles/diccoverMeteor
Discover Meteor URL - http://meteor-book-chapter12-5.meteor.com/
Mine seems to jump to the top when the load more button is clicked.

This is because you use waitOn instead of subscriptions. waitOn will try to render the loading template, while subscriptions will still subscribe but won't trigger the loading template, thus not cause the flickering.
However, this will also render the page before the subscriptions are ready. Therefore you need to keep keep track of the ready-state for the subscriptions. See example for Discover Meteor. Note that data is returning this.postsSub.ready, which is set under subscriptions.
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
postsLimit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.postsLimit()};
},
subscriptions: function() {
this.postsSub = Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().count() === this.postsLimit();
var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increm
ent});
return {
posts: this.posts(),
ready: this.postsSub.ready,
nextPath: hasMore ? nextPath : null
};
}
});
See full example on GitHub:
https://github.com/DiscoverMeteor/Microscope/commit/chapter12-5

Related

Meteor Iron Router WaitOn Subscription

I am really struggling with waiting on a subscription to load for a specific route before returning the data to the template. I can see on from the publish on the server that a document is found, but on the client there is no document.
If I do a find().count() on the publish, it shows 1 document found, which is correct, but when I do the count on the subscription, it shows 0 documents.
I have tried a number of different methods, like using subscriptions:function() instead of waitOn:function(), but nothing works.
Collections.js lib:
SinglePackage = new Mongo.Collection("SinglePackage");
SinglePackage.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
}
});
Publications.js server:
Meteor.publish("SinglePackage", function(pack_id) {
return Packages.find({shortId: pack_id});
});
Iron Router:
Router.route('/package/:id', {
name: 'package.show',
template: 'Package_page',
layoutTemplate: 'Landing_layout',
waitOn: function() {
return Meteor.subscribe('SinglePackage', this.params.id);
},
data: function() {
return SinglePackage.find();
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Am I doing something very wrong, or is this just a complicated thing to achieve? One would think that waitOn would make the rest of the function wait until the subscription is ready.
Any help would be highly appreciated.
It appears that the data function is running before the subscription is ready. Even if the data function did run after the subscription was ready, it wouldn't be a reactive data source rendering the pub/sub here pointless. Here's a great article on reactive data sources.
Referring to the example from the Iron Router Docs for subscriptions, you would do something like this:
Router.route('/package/:id', {
subscriptions: function() {
// returning a subscription handle or an array of subscription handles
// adds them to the wait list.
return Meteor.subscribe('SinglePackage', this.params.id);
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Then in your template.js:
Template.Package_page.helpers({
singlePackage() {
// This is now a reactive data source and will automatically update whenever SinglePackage changes in Mongo.
return Package.find().fetch();
}
});
In your template.html you can now use singlePackage:
<template name="Package_page">
{#with singlePackage} <!-- Use #each if you're singlePackage is an array -->
ID: {_id}
{/with}
</template>

Meteor.user() is undefined in client-side controller

In the client-side of a METEORJS application, i have a controller that display some users.
I have a problem with Meteor.user() function, the error is : Meteor.user(...) is undefined.
Here is my code :
this.AdminUsersController = RouteController.extend({
template: "Admin",
yieldTemplates: {
'AdminUsers': { to: 'AdminSubcontent'}
},
onBeforeAction: function() {
var permissions = Meteor.user().profile.permissions;
if (permissions && permissions.indexOf('Users') != -1)
this.next();
else this.redirect('/admin/dashboard');
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("Admin"); this.render("loading", { to: "AdminSubcontent" });}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("users")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
var data = {
params: this.params || {},
users: Users.find({labo_id: Meteor.user().profile.labo_id}, {sort: {createdAt:-1}})
};
return data;
},
onAfterAction: function() {
}});
It's in the data function.
I try to retrieve all users that are connected to the logged in user and got the same labo_id field...
I don't know why it give me that, because in the onBeforeAction function, i can access to Meteor.user(), and specially his profile...
Someone know what can i do to make it run ?
Thanks for your future answers :)
This is a timing issue. Meteor.user() does not necessarily return data, if it hasn't been loaded yet. Meteor.userId() however will return the _id of the user record (if they are logged in). If you can change your query to rely on that _id, then it can work.
Depending on which router you are using, you can add a resolve: entry to ensure that the route waits for the user record to be loaded before activating it, and then your query will work.

call method in Iron-Router RouteController from Template.tmpl.helpers() not working

I'm trying to push new data on to my clients array each time the loadMoreClients method is called. The publication is expecting this back_to parameter and knows how to handle it. My problem is that I can't seem to call these methods from my Template helpers .
I logged Iron and Iron.controller to the console and both of those exist and are showing me what I expected to see. I just can't seem to find current docs or examples of how to access Iron.controller() methods/properties from my Template helpers
Here is my RouteController code:
ClientController = ApplicationController.extend({
action : function(){
this.render(Router.current().route.getName())
},
data : function(){
if( this.params._id ){
return Clients.findOne({ _id:this.params._id })
}
},
waitOn : function(){
return [
Meteor.subscribe('directory'),
Meteor.subscribe('clients')
]
},
loadMoreClients : function(){
this.months_back += 3
this.back_to = moment().subtract(this.months_back,'months').startOf('day')
this.clients.push(Meteor.subscribe('clients', {back_to:this.back_to, skip:this.clients.length}))
},
loadAllClients : function(){
this.clients.push(Meteor.subscribe('clients', {back_to:this.start_of_time, skip:this.clients.length}))
},
// we'll use these properties to 'load more' client data
clients : [],
back_to : moment().subtract(3,'months').startOf('day'),
months_back : 3,
start_of_time : moment(new Date(0))
})
Here is my helpers code:
Template.client_list.helpers({
clients : function(){
var clients = []
Iron.controller().clients.forEach(function(client){
// ... some stuff here...
clients.push(client)
})
return clients
},
earliestClientLoaded : function(){
var controller = Iron.controller()
return controller.clients[controller.clients.length - 1].createdAt
}
})
Template.client_list.events({
'click .btn-load-more' : function(e){
e.preventDefault()
Iron.controller().loadMoreClients()
},
'click .btn-load-all' : function(e){
e.preventDefault()
Iron.controller().loadAllClients()
}
})
I'm getting undefined function errors on my Iron.controller() calls to loadMoreClients and loadAllClients methods.
What am I doing wrong here?
I have changed the approach to this problem by simply updating the subscription to subscribe to posts earlier than the currently loaded date.
It is working, although seems like there should be a better way than having to waitOn the subscription when I want to load more.

ember.js discard created model if not saving

I have followed the 3 parts of the tutorial here and everything is working fine, with one exception. When I go to the "Add the book" page and then navigate to "List books" without saving, the list of books is extended by an empty item. I suspect that the reason is the following code, where the new model is created before saving and not removed if not saving the form. Any ideas how to fix this?
Embertest.BooksNewRoute = Ember.Route.extend({
model: function() {
return this.get('store').createRecord('book');
},
actions: {
create: function() {
var newBook = this.get('currentModel');
newBook.save();
this.transitionTo('books');
}
}
});
From http://emberjs.com/api/classes/Ember.Route.html#method_deactivate
Add a deactivate function on your route. This is called before exiting this current route. So you can roll back the record you just created in the model like this:
model: function(params) {
return this.get('store').createRecord('book');
},
deactivate: function() {
this.currentModel.rollback();
},
I solved this by attaching an action to destroy the record to the willTransition event as below. Would anyone comment if this is the right approach?
Embertest.BooksNewRoute = Ember.Route.extend({
model: function() {
return this.get('store').createRecord('book');
},
actions: {
willTransition: function() {
if (this.currentModel.get('isNew')) {
this.get('currentModel').deleteRecord();
};
},
create: function() {
var newBook = this.get('currentModel');
newBook.save();
this.transitionTo('books');
}
}
});

Waiting for models to load before rendering app in Ember.js

I have a number of different application-level models — i.e., current user, current account, etc. — that I want to load before rendering my application. How and where should this be done? This question/answer helped a lot, but it doesn't cover the async aspect.
The following code accomplishes what I want, but loading the models in beforeModel (to take advantage of it waiting for the promise to resolve) doesn't seem right. Should I even be loading these models in ApplicationRoute?
App.ApplicationController = Ember.Controller.extend({
currentAccount: null
});
App.ApplicationRoute = Ember.Route.extend({
beforeModel: function () {
var self = this;
return App.Account.find(...).then(function (account) {
self.controllerFor('application').set('currentAccount', account);
});
}
});
Thanks for your help!
The trick is to return a promise from the route's model method.
This will cause the router to transition into App.LoadingRoute route, until the promise resolves (which can be used for loading indication bars/wheels etc.)
When the promise resolves, the App.LoadingRoute will be deactivated, and the original route's setupController method will be called.
This works for ember-data promises, JQuery's $.ajax promises and ember-model's fetch promises.
Just make sure you return the actual model after resolving the promise.
This can also be a good place to handle errors if the promise is rejected - but I'll leave that to some other question.
As for where you should load your models - that is dependent on your app's usage.
Usually you would load a model where the URL indicates you need that model - a rule of thumb would be the indication of a model ID in the URL.
This of course changes if you need to prefetch some data.
And now for some code:
App.SomeRoute = Ember.Route.extend({
model: function(params){
return App.SomeModel.fetch(params.model_id).then(function(modelData){
// it is better to return the actual model here, and not the promise itself
return App.SomeModel.find(params.model_id);
});
},
setupController: function(controller, model){
controller.set("model", model);
// do some controller setup here - can be omitted if no setup is needed
// this will run only after the promise has been resolved.
}
});
App.LoadingRoute = Ember.Route.extend({
activate: function(){
this._super();
// add some loading indication here
},
deactivate: function(){
this._super();
// remove loading indication
}
}
Hope this helps.
You want to preload data/models to initialize your application, and feel beforeModel is incorrect?
Sounds like you need an application initializer!
Your friend in this instance:
App.deferReadiness(); // halt progress of application until all instances of this call (ie: multiple initializers) are matched by an instance the following call:
App.advanceReadiness(); // consider this to be equivalent to a promise resolve call.
1) From you looking up the user directly, modifying where mentioned to suit your app setup:
Ember.Application.initializer({
name: 'loadUser',
after: 'store',
initialize: function(container, app) {
// modify this following to suit how you're determining the account
var url = 'user/' + currentAccount;
// tell the app to pause loading until advanceReadiness is declared
app.deferReadiness();
// load from JSON
Ember.$.getJSON('url').then(function(json) {
var store = container.lookup('store:main');
store.load(app.User, json);
// tell app to start progressing again
app.advanceReadiness();
});
}
});
2) Through meta tag:
Ember.Application.initializer({
name: 'currentUser'
after: 'store',
initialize: function(container, app) {
app.deferReadiness();
$(function() {
// Look up an attribute in a meta tag
var store = container.lookup('store:main'),
attributes = $('meta[name="current-user"]').attr('content');
if (attributes) {
var obj = store.load(app.User, JSON.parse(attributes)),
user = App.User.find(obj.id),
controller = container.lookup('controller:currentUser').set('content', user);
container.typeInjection('controller', 'currentUser', 'controller:currentUser');
}
app.advanceReadiness();
});
}
});
3) Through Session data:
Ember.Application.initializer({
name : 'currentUser',
after : 'session',
initialize: function(container, app) {
var controller = container.lookup('controller:currentUser');
container.typeInjection('controller', 'currentUser', 'controller:currentUser');
}
});
I managed to get this work by using nested Promises and the afterModel method in the ApplicationRoute.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
// load the reservation (a globally needed model)
return App.Reservation.fetch().then(function(reservations) {
return reservations.get('firstObject');
});
},
afterModel: function() {
// Load all other globally needed models
var self = this;
return App.Gender.fetch().then(function(genders) {
self.controllerFor('application').set('genders', genders);
return App.FilterAttribute.fetch().then(function(filterAttributes) {
self.controllerFor('application').set('filterAttributes', filterAttributes);
//return App.SomeOtherModel...
});
});
},
setupController: function(controller, model) {
controller.set('reservation', model);
}
});
Works just perfectly :-) The application remains in the LoadingRoute until all records are loaded.
Note that I am using Ember Model, but this should make no difference, it just have to return a Promise.

Categories

Resources