How can I make Iron:router re-render a template?
I have this html:
<head>
</head>
<body>
</body>
<template name="mainLayout">
list
find
{{> yield}}
</template>
<template name="listTemplate">
<p>list</p>
</template>
and this js:
Router.configure({
layoutTemplate: 'mainLayout'
});
Router.route('/list', {
name: 'list',
template: 'listTemplate'
});
Router.route('/find', {
name: 'find',
template: 'listTemplate',
data: function () {
return this.params.query;
}
});
if (Meteor.isClient) {
Template.listTemplate.rendered = function () {
if (this.data)
console.log('find ' + this.data.q);
else
console.log('list all');
};
}
When I click on the links to switch views (simulated here with console.log), the route does change, but the template is not re-rendered.
Is there a way to force iron:router to re-render?
Setting the router controller state did not work for me. The answer Antônio Augusto Morais gave in this related github issue worked. Using the Session to store the reactive var to trigger the autorun reactiveness. It's a hack, but it works.
## router.coffee
Router.route '/profile/:_id',
name: 'profile'
action: ->
Session.set 'profileId', #params._id
#render 'profile'
## profile.coffee
Template.profile.onCreated ->
#user = new ReactiveVar
template = #
#autorun ->
template.subscription = template.subscribe 'profile', Session.get 'profileId'
if template.subscription.ready()
template.user.set Meteor.users.findOne _id: Session.get 'profileId'
else
console.log 'Profile subscription is not ready'
Template.profile.helpers
user: -> Template.instance().user.get()
## profile.html
<template name="profile">
{{#if user}}
{{#with user.profile}}
<span class="first-name">{{firstName}}</span>
<span class="last-name">{{lastName}}</span>
{{/with}}
{{else}}
<span class="warning">User not found.</span>
{{/if}}
</template>
You can try something like this:
Router.configure({
layoutTemplate: 'mainLayout'
});
Router.route('/list', {
name: 'list',
template: 'listTemplate',
action: function() {
this.state.set('query', this.params.query);
}
});
Router.route('/find', {
name: 'find',
template: 'listTemplate',
data: function() {
return this.params.query;
},
action: function() {
this.state.set('query', this.params.query);
}
});
if (Meteor.isClient) {
Template.listTemplate.rendered = function() {
this.autorun(
function() {
if (this.state.get('query'))
console.log('find ' + this.data.q);
else
console.log('list all');
}
);
};
}
The rendered method isn't reactive, that's why you need an autorun.
The template "this.data" isn't reactive so you're gonna need a reactive var to do that, either a Session variable, a controller state, or some kind of reactive var.
You may need to add the reactive-var package depending on what approach you take.
Related
This is my router:
Router.route('/cource/:courcePath', {
name: 'Cource_page',
template: 'Cource_page',
data() {
return Cources.find({ courcePath: this.params.courcePath });
}
});
Template:
<template name="Cource_page">
<div class="container">
{{#each cources}}
<h1>{{courceTitle}}</h1>
{{/each}}
</div>
</template>
And helper:
Template.Cource_page.helpers({
cources() {
let courcePath = this.courcePath;
console.log(courcePath);
return Cources.find({ courcePath: courcePath });
}
});
When I go to some page (http://localhost:3000/cource/my-new-cource, for example), no data rendering and I get undefined in the console (the output of template helpers). What am I doing wrong?
You need to pass the data in the render directive
Router.route('/post/:_id', function () {
this.render('Post', {
data: function () {
return Posts.findOne({_id: this.params._id});
}
});
});
OR
you should subscribe to the data in the route:
Router.route('/cource/:courcePath', {
name: 'Cource_page',
template: 'Cource_page',
subscriptions: function() {
this.subscribe('courses', this.params.courcePath);
}
});
Check the guide for more info:
http://iron-meteor.github.io/iron-router/
When you provide the data context in your i-r route you don't need the helper. Try:
Router.route('/cource/:courcePath', {
name: 'Cource_page',
template: 'Cource_page',
data() {
return Cources.find({ courcePath: this.params.courcePath });
}
});
<template name="Cource_page">
<div class="container">
{{#each this}}
<h1>{{courceTitle}}</h1>
{{/each}}
</div>
</template>
I'm new with this framework. I want to use flow router as it is reactive. Before this I'm using iron router. Please someone show me how to change the code to flow router as flow router has no waitOn concept and onBeforeAction(replaced by triggersEnter). Your help and kindness really appreciated.
router.js
Router.route('/EditLokasi/:_id', {
name: 'EditLokasi',
template: 'EditLokasi',
onBeforeAction: function(){
var lokasiID = Lokasi.findOne({_id: this.params._id});
this.render('EditLokasi',{data: lokasiID});
},
waitOn: function(){
if(Meteor.userId()){
var userid = Meteor.user().username;
return [ Meteor.subscribe('profiles', userid), Meteor.subscribe('login') ];
}
}
});
EditLokasi.js
Template.EditLokasi.helpers({
lokasi: function(){
return Lokasi.find({data:lokasiID});
}
});
You can use subscriptions to subscribe and then, on the template, check if the data is ready through subReady
FlowRouter.route('/login', {
name: 'login',
subscriptions: function() {
var userid = Meteor.user().username;
this.register('subscribe-to-profile', Meteor.subscribe('profiles', userid));
// do it the same for other subscribe
},
action(params) {
var lokasiID = Lokasi.findOne({_id: this.params._id});
BlazeLayout.render('EditLokasi', {data: lokasiID});
}
});
And on your template
<template name="EditLokasi">
{{# if helper_checkSubReady}}
......
{{else}}
loading....
{{/if}}
</template>
helper_checkSubReady is a helper to check if the subscriptions are ready or not (check the link I mentioned above)
I show a list of items and for each item its belongs to a user. I list the item's name, description and the user who created it (by there name). I want to also have a link that goes to the user who created the item. Note: That I also link to an item's page.
Here is what I've done so far:
packages
aldeed:collection2
aldeed:simple-schema
iron:router
aldeed:autoform
useraccounts:iron-routing
accounts-password
accounts-base
useraccounts:core
zimme:active-route
todda00:friendly-slugs
reywood:publish-composite
client/items/listed_item.html
<template name="listedItem">
<a href="{{pathFor 'itemPage'}}">
{{name}}
</a>
<a href="{{pathFor 'profile'}}">
{{usernameFromId user}}
</a>
</template>
client/items/items.html
<template name="items">
<div class="items">
{{#each items}}
{{> listedItem}}
{{/each}}
</div>
</template>
client/subscriptions.js
Meteor.subscribe('items');
Meteor.subscribe('allUsernames');
Meteor.subscribe('users');
client/helpers.js
Template.items.helpers({
items: function() {
return Items.find();
},
});
Template.registerHelper("usernameFromId", function (userId) {
var user = Meteor.users.findOne({_id: this.userId});
return user.profile.name;
});
server/publications.js
Meteor.publish("items", function () {
return Items.find();
});
Meteor.publish("users", function () {
return Meteor.users.find({}, {
fields: { profile: 1 }
});
});
Meteor.publish("allUsernames", function () {
return Meteor.users.find({}, {
fields: { 'profile.name': 1, 'services.github.username': 1 }
});
});
lib/routes.js
Router.route('/items', function () {
this.render('items');
});
Router.route('/users/:_id', {
template: 'profile',
name: 'profile',
data: function() {
return Meteor.users.findOne({_id: this.params._id});
}
});
What this does though is show the items URL for the users URL, so it links to a user who doesn't exist. i.e. items/1234 = users/1234 when theres only users/1 in the system. How can I get it to link to the correct user ID?
use a link with the constructed URL
<template name="listedItem">
<a href="{{pathFor 'itemPage'}}">
{{name}}
</a>
<a href="http://yourdomain.com/users/{{user}}">
{{usernameFromId user}}
</a>
</template>
In this part:
Router.route('/users/:_id', {
template: 'profile',
name: 'profile',
data: function() {
return Meteor.users.findOne({_id: this.params._id});
}
});
you have referenced the route and created a route to a template with the name "profile" but I can't see any template with that name. Is it possible you just forgot to include it here? or that you really don't have that template?
Just making sure.
Try this:
Router.route('/users/:_id', {
template: 'profile',
name: 'profile'
}
In helper you can retrive that record id by this:
Router.current().params._id
I usually use this way:
In the template:
<div class="css-light-border">
{{title}}
</div><!-- / class light-border -->
In the router.js file:
Router.route('/document/:_id', function(){
Session.set('docid', this.params._id);
this.render('navbar', {to: "header"});
this.render('docItem', {to: "main"});
});
Basically here docid is the _id of the document which I want to display. Here is an example of how you pull from your database:
somehelper:function(){
var currentId = Session.get('docid');
var product = Products.findOne({_id:currentId});
return product;
I think it is an easy way because it uses Sessions.
I have db with posts and all of them have bollean flagged
I have one template and navigation like (Read, Dont Read).
Problem is that I see all posts (when I must see posts with flagged false or flagged true), and I dont understand why, I think problem in publish/subscribe
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
});
AllPostsController = RouteController.extend({
template: 'timeTable',
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
Router.map(function() {
this.route('timeTable', {path: '/',
controller: AllPostsController
});
this.route('readPosts', {path: '/read',
controller: readPostsController
});
this.route('dontreaderPosts', {
path: '/dontreader',
controller: dontreaderPostController
});
});
Meteor.publish('allPosts', function(){
return Posts.find({},{ sort: { createdAt: -1 }});
});
Meteor.publish('readPosts', function(){
return Posts.find({read:true},{ sort: { createdAt: -1 }});
});
Meteor.publish('dontreadPosts', function(){
return Posts.find({read:false},{ sort: { createdAt: -1 }});
});
If someone need more code, Just ask me
Anybody help
EDIT : David solved problem for regular tasks. Main problem that I have specific return Posts.find(...) in my tamplate helper.
<template name="timeTable">
{{#if posts_exist_week}}
{{> table posts=week}}
{{/if}}
{{#if posts_exist_month}}
{{> table posts=month}}
{{/if}}
</template>
<template name="table">
<table class="main-table table">
{{#each posts}}
{{> post}}
{{/each}}
</table>
</template>
You solved my problem if I did not have template timeTable (that show posts for last week and month) Because here it Template helper
Template.timeTable.helpers({
week: function() {
//...
return Posts.find({createdAt: {$gte: weekstart, $lt: yesterday}},{ sort: { createdAt: -1 }}); //return posts that was created in this week
},
month: function() {
//...
return Posts.find({createdAt: {$gte: monthstart, $lte: weekstart}},{ sort: { createdAt: -1 }});
}
});
And now you see that if I choose your decision (David) I will have 2 !!
return
first - in router
second - in template helper
I recreated this locally and found that extend causes the parent controller's waitOn to run. So whenever you go to the /read route it will actually activate both subscriptions and you'll end up with all of the documents on your client. A simple fix is to refactor your controllers like so:
PostController = RouteController.extend({
template: 'timeTable'
});
AllPostsController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
That being said, you don't want to build your app in a way that it breaks when extra subscriptions happen to be running. I would rewrite the controllers to select only the documents that pertain to them. For example:
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
},
data: {selector: {read: false}}
});
And now your helpers can use the selector like this:
Template.timeTable.helpers({
week: function() {
var selector = _.clone(this.selector || {});
selector.createdAt = {$gte: weekstart, $lt: yesterday};
return Posts.find(selector, {sort: {createdAt: -1}});
}
});
Also note that sorting in the publish functions may not be useful - see common mistakes.
I'm making a simple Meteor app that can redirect to a page when user click a link.
On 'redirect' template, I try get the value of property 'url' from the template instance. But I only get right value at the first time I click the link. When I press F5 to refresh 'redirect' page, I keep getting this error message:
Exception from Tracker afterFlush function: Cannot read property 'url' of null
TypeError: Cannot read property 'url' of null
at Template.redirect.rendered (http://localhost:3000/client/redirect.js?abbe5acdbab2c487f7aa42f0d68cf612f472683b:2:17)
at null.
This is where debug.js points to: (line 2)
if (allArgumentsOfTypeString)
console.log.apply(console, [Array.prototype.join.call(arguments, " ")]);
else
console.log.apply(console, arguments);
} else if (typeof Function.prototype.bind === "function") {
// IE9
var log = Function.prototype.bind.call(console.log, console);
log.apply(console, arguments);
} else {
// IE8
Function.prototype.call.call(console.log, console, Array.prototype.slice.call(arguments));
}
Can you tell me why I can't read the value of 'url' property from template data context in template rendered callback?
This is my code (for more details, you can visit my repo):
HTML:
<template name="layout">
{{>yield}}
</template>
<template name="home">
<div id="input">
<input type="text" id="url">
<input type="text" id="description">
<button id="add">Add</button>
</div>
<div id="output">
{{#each urls}}
{{>urlItem}}
{{/each}}
</div>
</template>
<template name="redirect">
<h3>Redirecting to new...{{url}}</h3>
</template>
<template name="urlItem">
<p><a href="{{pathFor 'redirect'}}">
<strong>{{url}}: </strong>
</a>{{des}}</p>
</template>
home.js
Template.home.helpers({
urls: function(){
return UrlCollection.find();
}
});
Template.home.events({
'click #add': function() {
var urlItem = {
url: $('#url').val(),
des: $('#description').val()
};
Meteor.call('urlInsert', urlItem);
}
});
redirect.js
Template.redirect.rendered = function() {
if ( this.data.url ) {
console.log('New location: '+ this.data.url);
} else {
console.log('No where');
}
}
Template.redirect.helpers({
url: function() {
return this.url;
}
});
router.js
Router.configure({
layoutTemplate: 'layout'
})
Router.route('/', {
name: 'home',
waitOn: function() {
Meteor.subscribe('getUrl');
}
});
Router.route('/redirect/:_id', {
name: 'redirect',
waitOn: function() {
Meteor.subscribe('getUrl', this.params._id);
},
data: function() {
return UrlCollection.findOne({_id: this.params._id});
}
});
publication.js
Meteor.publish('getUrl', function(_id) {
if ( _id ) {
return UrlCollection.find({_id: _id});
} else {
return UrlCollection.find();
}
});
Add this
Router.route('/redirect/:_id', {
name: 'redirect',
waitOn: function() {
Meteor.subscribe('getUrl', this.params._id);
},
data: function() {
if(this.ready()){
return UrlCollection.findOne({_id: this.params._id});
}else{
console.log("Not yet");
}
}
});
Tell me if works.
With help from my colleague, I can solve the problem.
My problem comes from wrong Meteor.subscribe syntax. In my code, I forget "return" in waitOn function. This will make Meteor do not know when the data is fully loaded.
Here is the right syntax:
waitOn: function() {
return Meteor.subscribe('getUrl', this.params._id);
}