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');
}
});
Related
I have a page in Vue/Nuxt that needs to refresh a list of items every few seconds. This is an SPA that does an Axios fetch to a server to get updated information. At the moment, I have something like this:
methods: {
doRefresh() {
setTimeout(function() {
// trigger server fetch here
doRefresh();
}, 5000);
}
}
It works, unless the other code in doRefresh throws an error, in which case the refreshing stops, or somehow the code gets called twice, and I get two timers going at the same time.
An alternative is call setInterval() only once. The trouble with that is that it keeps going even after I leave the page. I could store the reference returned by the setInterval(), and then stop it in a destroyed() hook. But again, an error might prevent that from happening.
Is there a safe and reliable way to run a timer on a Vue page, and destroy it when the user leaves the page?
This approach together with try-catch is a way to go, have a look at this snippet:
https://codepen.io/alexbrohshtut/pen/YzXjNeB
<div id="app">
<wrapper/>
</div>
Vue.component("interval-component", {
template: `
<div> {{lastRefreshed}}
<button #click="init">Start</button></div>`,
data() {
return {
timeoutId: undefined,
lastRefreshed: undefined
};
},
methods: {
doJob() {
if (Math.random() > 0.9) throw new Error();
this.lastRefreshed = new Date();
console.log("Job done");
},
init() {
if (this.timeoutId) return;
this.run();
},
run() {
console.log("cycle started");
const vm = this;
this.timeoutId = setTimeout(function() {
try {
vm.doJob();
} catch (e) {
console.log(e);
} finally {
vm.run();
}
}, 2000);
}
},
destroyed() {
clearTimeout(this.timeoutId);
console.log("Destroyed");
}
});
Vue.component("wrapper", {
template: `<div> <button #click="create" v-if="destroyed"> Create</button>
<button v-else #click="destroy">Destroy</button>
<interval-component v-if="!destroyed" /></div>`,
data() {
return {
destroyed: true
};
},
methods: {
destroy() {
this.destroyed = true;
},
create() {
this.destroyed = false;
}
}
});
new Vue({
el: "#app"
});
I have been pretty much beginner at this part of javascript and I would appreciate any ideas how could be solved this problem.
I use requirejs to define my own modules where I also use backbone.js.
Let say I have the main module where I initialize my Backbone view which is rendered without any problem. Also, the click event where is calling method createSchemeForm creates the form correctly. The problem raises up in a situation when I call cancel method by click and the modules which are defined for Backbone view (e.g. "unicorn/sla/dom/helper"...) are undefined but when I called method createSchemeForm at the beginning the modules were executed without any problem.
Thank you in advance for any suggestions.
Backbone view
define("unicorn/sla/view/scheme", [
"unicorn/sla/dom/helper",
"unicorn/soy/utils",
"unicorn/sla/utils"
], function (DOMHelper, soyUtils, jsUtils) {
return Backbone.View.extend({
el: 'body',
inputData: {},
btnSaveScheme: 'btn-save-sla-scheme',
btnCancel: 'btn-cancel-sla-scheme',
btnCreate: 'btn-create-sla-scheme',
btnContainer: '#sla-scheme-buttons-container',
schemeContent: '#sla-scheme-content-section',
btnSpinner: '.button-spinner',
events: {
'click #btn-create-sla-scheme' : "createSchemeForm",
'click #btn-cancel-sla-scheme' : "cancel"
},
initialize: function(){
console.log("The scheme view is initialized...");
this.render();
},
createSchemeForm: function () {
this.spin();
DOMHelper.clearSchemeContent();
DOMHelper.clearButtonsContainer();
//Get button
$btnSave = soyUtils.getButton({isPrimary: 'true', id: this.btnSaveScheme, label: 'Save'});
$btnCancel = soyUtils.getButton({isPrimary: 'false', id: this.btnCancel, label: 'Cancel'});
//Append new created buttons
DOMHelper.addContent(this.btnContainer, AJS.format("{0}{1}", $btnSave, $btnCancel));
//Call service to get entry data for scheme creation form
AJS.$.ajax({
url: AJS.format('{0}={1}',AJS.I18n.getText('rest-url-project-scheme-input-data'), jsUtils.getProjectKey()) ,
type: "post",
async: false,
context: this,
global: false,
}).done(function (data) {
this.inputData = data;
$slaSchemeForm = soyUtils.getSchemeCreateForm({slaScheme : data, helpText: AJS.I18n.getText("sla-time-target-tooltip-text")});
DOMHelper.addContent(this.schemeContent, $slaSchemeForm);
jsUtils.scroll(this.schemeContent, 'slow');
}).fail(function () {
jsUtils.callFlag('error', AJS.I18n.getText("message-title-error"), AJS.I18n.getText("sla-error-load-scheme-input-data"));
}).always(function () {
this.stopSpin();
});
},
spin: function () {
AJS.$('.button-spinner').spin();
},
stopSpin: function () {
AJS.$('.button-spinner').spinStop();
},
cancel: function () {
jsUtils.clearButtonsContainer();
jsUtils.clearSchemeContent();
$btnCreateScheme = soyUtils.getButton({isPrimary: 'false', id: this.btnCreate, label: 'Create SLA Scheme'});
DOMHelper.addContent(this.btnContainer, $btnCreateScheme);
DOMHelper.addContent(this.schemeContent, soyUtils.getSchemesTable(new Array())); // TODO - get current data from server instead of empty array
}
});
});
Main module where is Backbone view initialize
define("unicorn/sla/project/batch", [
"unicorn/sla/utils",
"unicorn/sla/data/operations",
"unicorn/sla/data/validator",
"unicorn/sla/dom/helper",
"unicorn/sla/model/confirm/message",
"unicorn/sla/view/scheme",
"exports"
], function (jsUtils, operations, validator, DOMHelper, ConfirmMessage, SchemeView, exports) {
//Load project batch
exports.onReady = function () {
$schemeView = new SchemeView();
$schemeView.render();
}
});
AJS.$(function () {
AJS.$(document).ready(function () {
require("unicorn/sla/project/batch").onReady();
});
});
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.
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;
}
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!