Is it possible to wait until the page data is loaded before page rendering? I always see the notFound template for a few milliseconds before the data is loaded.
Here is my code:
this.route('gamePage', {
path: '/game/:slug/',
onBeforeAction: filter,
waitOn: function() { return [Meteor.subscribe('game', this.params.slug)]; },
data: function() {
var game = Games.findOne({slug: this.params.slug});
if (!game) {
this.render("notFound");
} else {
return game;
}
}
});
Any help would be greatly appreciated.
You can use the 'loading' hook to display a template of your choice while the subscriptions in waitOn are not yet ready.
Activate the hook:
Router.onBeforeAction("loading");
And set a loading template:
Router.configure({
loadingTemplate: "loading"
});
<template name="loading">
loading... <!-- or display an animated spinner -->
</template>
You can also set the loading template on a per-route level.
this.route("blah", {
path: "/blah",
loadingTemplate: "blahLoading"
});
Here is how I solved it:
if (!game && this.ready()) {
this.render("notFound");
} else {
return game;
}
Related
I'm facing an infinite loop issue and I can't see what is triggering it. It seems to happen while rendering the components.
I have three components, organised like this :
TimelineComponent
|--PostComponent
|--UserPopover
TimelineComponenet:
React.createClass({
mixins: [
Reflux.listenTo(TimelineStore, 'onChange'),
],
getInitialState: function() {
return {
posts: [],
}
},
componentWillMount: function(){
Actions.getPostsTimeline();
},
render: function(){
return (
<div className="timeline">
{this.renderPosts()}
</div>
);
},
renderPosts: function (){
return this.state.posts.map(function(post){
return (
<PostComponenet key={post.id} post={post} />
);
});
},
onChange: function(event, posts) {
this.setState({posts: posts});
}
});
PostComponent:
React.createClass({
...
render: function() {
return (
...
<UserPopover userId= {this.props.post.user_id}/>
...
);
}
});
UserPopover:
module.exports = React.createClass({
mixins: [
Reflux.listenTo(UsersStore, 'onChange'),
],
getInitialState: function() {
return {
user: null
};
},
componentWillMount: function(){
Actions.getUser(this.props.userId);
},
render: function() {
return (this.state.user? this.renderContent() : null);
},
renderContent: function(){
console.log(i++);
return (
<div>
<img src={this.state.user.thumbnail} />
<span>{this.state.user.name}</span>
<span>{this.state.user.last_name}</span>
...
</div>
);
},
onChange: function() {
this.setState({
user: UsersStore.findUser(this.props.userId)
});
}
});
Finally, there is also UsersStore**:
module.exports = Reflux.createStore({
listenables: [Actions],
users: [],
getUser: function(userId){
return Api.get(url/userId)
.then(function(json){
this.users.push(json);
this.triggerChange();
}.bind(this));
},
findUser: function(userId) {
var user = _.findWhere(this.users, {'id': userId});
if(user){
return user;
}else{
this.getUser(userId);
return [];
}
},
triggerChange: function() {
this.trigger('change', this.users);
}
});
Everything works properly except the UserPopover component.
For each PostComponent is rendering one UserPopOver which fetch the data in the willMount cycle.
The thing is, if you noticed I have this line of code console.log(i++); in the UserPopover component, that increments over and over
...
3820
3821
3822
3823
3824
3825
...
Clearl an infinite loop, but I really don't know where it comes from. If anyone could give me a hint I will be very gratefully.
PS: I already tried this approach in the UsersStore but then all the PostComponent have the same "user":
...
getUser: function(userId){
return Api.get(url/userId)
.then(function(json){
this.user = json;
this.triggerChange();
}.bind(this));
},
triggerChange: function() {
this.trigger('change', this.user);
}
...
And in the UserPopover
...
onChange: function(event, user) {
this.setState({
user: user
});
}
...
Because that your posts is fetch async, I believe that when your UserPopover component execute it's componentWillMount, the props.userId is undefined, and then you call UsersStore.findUser(this.props.userId), In UserStore, the getUser is called because it can't find user in local storage.
NOTE that every time the getUser's ajax finished, it trigger. So the UserPopover component execute onChange function, and call UsersStore.findUser again. That's a endless loop.
Please add a console.log(this.props.userId) in the UserPopover's componentWillMount to find out if it is like what i said above. I actually not 100% sure it.
That is a problem that all UserPopover instance share the same UserStore, I think we should rethink the structure of these components and stores. But I haven't thought out the best way yet.
You can do it like this:
TimelineComponent
|--PostComponent
|--UserPopover
UserPopover just listen for changes and update itself.
UserPopover listens for change at store, which holds which user's data should be in popover and on change updates itself. You can send also coordinates where to render. No need to create Popover for each Post.
I'm trying to wrap the plugin Justified Gallery in an Ember component. The main problem that I'm facing is that the list of photos in the gallery come from an API, so they're part of the model. What I have so far:
App.JustifiedGalleryComponent = Ember.Component.extend({
_init: function() {
this.$().justifiedGallery({
rowHeight: 150,
fixedHeight: false,
margins: 7
});
}.on('didInsertElement')
});
Template
{{#each photo in items}}
<div>
<img src={{photo.thumbUrl}} />
</div>
{{/each}}
But I can't get that to work, probably because the list of photo is inside an each loop, and when the plugin is applied the photos are still not in the DOM? What would be the approach for this problem?
Thanks!
EDIT:
Taking as a reference the component for Masonry I've got this almost sorted, but the first time that I navigate to the URL nothing shows, if I go to a second route (inside the ember app) and go back to the gallery then it displays fine and justified. This is my component now:
import Ember from 'ember';
var getOptions = function (keys) {
var properties = this.getProperties(keys);
Object.keys(properties).forEach(function (key) {
if (properties[key] === "null") {
properties[key] = null;
}
if (properties[key] === undefined) {
delete properties[key];
}
});
return properties;
};
export default Ember.Component.extend({
classNames: ['justified-grid'],
options: null,
items: null,
setup: Ember.on('didInsertElement', function() {
this.set('options', getOptions.call(this, [
'rowHeight',
'fixedHeight',
'margins'
]));
this.justifyGrid();
}),
justifyGrid: Ember.observer('items.#each', function() {
var _this = this;
imagesLoaded(this.$(), function() {
_this.$().justifiedGallery(_this.get('options'));
_this.set('gridInitialized', true);
});
})
});
The problem wasn't in the component. It was that my model is loading the photos using async (Ember Data). For this reason, in the router, after setting the model, I had to force Ember Data to load my photos:
afterModel: function(model) {
return Em.RSVP.all([model.get('photos')]);
}
My problem is that I have two similar paths and in first one router waits for my subscriptions and renders whole template, but the second one is rendering right away with no loading and data passed is causing errors(since there is no collection subscribed yet).
I paste my code here, the second one is different because of template and data passed but the rest is practically the same.
I'm just starting with iron-routing, maybe someone can tell me where is mistake?
Router.map(function() {
this.route('/', {
onBeforeAction: function() {
if (Meteor.user()) {
if (Meteor.user().firstLogin)
this.render("firstLogin");
else
Router.go('/news');
} else {
this.render("start");
}
},
waitOn: function() {
return Meteor.subscribe('allUsers');
},
onAfterAction: function() {
document.title = "someTitle";
},
loadingTemplate: "loading",
});
this.route('users',{
path:'/user/:_id',
layoutTemplate: 'secondLayout',
yieldTemplates: {
'template1': {to: 'center' },
'template2': {to: 'top' },
'template3': {to: 'left' },
'template4': {to: 'right' },
},
waitOn: function(){
return Meteor.subscribe("allUsers");
},
data: function(){
return Meteor.users.findOne({_id:String(this.params._id)});
},
loadingTemplate: "loading",
});
});
You are using iron-router in the lagacy. If you're just starting it. I recommend you use the new api. In that case, you can use this.ready() to check the subscription is finished or not
Following is the example from the official guide
Router.route('/post/:_id', function () {
// add the subscription handle to our waitlist
this.wait(Meteor.subscribe('item', this.params._id));
// this.ready() is true if all items in the wait list are ready
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
});
So I have the following backbone route:
Nightbird.Routers.Errors = Nightbird.Routers.Core.extend({
routes: {
'server_error': 'serverError',
},
initialize: function(){
console.log('dasddasd');
},
serverError: function() {
console.log('asdasdasd');
var serverErrorView = new Nightbird.Views.ServerError();
serverErrorView.render();
}
});
it does come into this class because the initialize function is being called, when this route loads I see: dasddasd in the console, but I do no see asdasdasd
The url is localhost:9000/#server_error
Can some one explain what I am doing wrong? I am not sure what else I am suppose to provide for further information so please ask for any additional details.
Additional
The following is how the app gets registered:
window.Nightbird = {
Models: {},
Collections: {},
Views: {},
Routers: {},
blogId: 0,
initialize: function() {
if (window.Development === undefined && window.Production === undefined) {
throw 'Production class (Production.config.js) cannot be missing. App Cannot load.';
}
if (window.Development !== undefined) {
this.blogId = window.Development.BLOG_ID;
} else {
this.blogId = window.Production.BLOG_ID;
}
new Nightbird.Routers.Posts();
new Nightbird.Routers.Errors();
if (!Backbone.History.started) {
Backbone.history.start();
} else {
Backbone.history.stop();
Backbone.history.start();
}
}
}
This class extends:
Nightbird.Routers.Core = Backbone.Router.extend({
serverError: function(){
Backbone.history.navigate("server_error", {trigger: true});
}
});
Why such a simple abstraction, because this way any issue getting or posting or what have you can redirect you to a server error route.
Then in my index.html I do:
<!DOCTYPE html>
<html>
<body>
<div id="manage">
</div>
</body>
<script src="js/compiled.js"></script>
<script>
Nightbird.initialize();
</script>
</html>
I guess that the problem is the way that you're instantiating the Backbone Router
Try to create the Backbone Router inheriting from Backbone.Router.
When you check if Backbone.History.started is true, it probably is not. so it will go to else statement, and there at that moment Backbone.History.star() is undefined. So it is never starting the Backbone.History
Hope it helps.
I'm having a issue where transitioning is not occurring on a page reload/refresh. When I start the application and click on the links, everything works perfectly, but when I reload the route - I get an empty page (blank). This is happening for me on the MovieIndexRoute below.
// Router
MediaUi.Router.map(function () {
this.resource('movies', { path: '/'}, function() {
this.resource('movie', { path: 'movie/:id' }, function() {
this.route('edit', { path: '/edit' });
});
});
});
// Movies Route
MediaUi.MoviesRoute = Ember.Route.extend({
model: function() {
var media;
media = MediaUi.Media.find();
return media;
}
});
// Movie Route
MediaUi.MovieRoute = Ember.Route.extend({
serialize: function(model) {
return { id: model.get('_id') };
}
});
// Movie Index Route
MediaUi.MovieIndexRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('movie');
}
});
You can also access the repo here: https://github.com/alvincrespo/media-ui/tree/nested-resources on the nested-resources branch.
I've also added the following screenshot, showing the page and console.
Any help with this would be greatly appreciated. Thank You!