iron:router Meteor how to change route (save state in history) without changing the url - meteor

I am developing a meteor applicaton . For routing I am using iron:router .
I am changing some templates by changing a session variable.
Is there any way that without changing the url the user gets an entry in the browser history, that with a browser back the session variable changes back?
My Problem is: Some beta testers tested the app and tried to close some overlays they opened with the browser back button.

I'm not sure I understand your question 100%. But it sounds like you want to set a session variable to a specific value based off of a specific route to control the state of an overlay?
If that's the case, your best bet would be to use an onBeforeAction hook.
Here's how you could do that with a Route Controller:
PostController = RouteController.extend({
waitOn: function () {},
data: function () {},
onBeforeAction () {
Session.set('someSession', 'someValue');
},
action: function () {
this.render();
}
});
If you don't want to use a Route Controller, you can also just add a hook function and specify a route for which the hook should run on.
Edit
Router.onBeforeAction(function () {
Session.set('showOverlay', false);
this.next();
});
You can also specify routes that you don't want this before hook on:
Router.onBeforeAction(function () {
Session.set('showOverlay', false);
this.next();
}, { except: ['admin'] });

Related

My router doesn't work as expected

this is the router code
Router.route('screens', {
path: '/screenshots/:_id',
template: 'screens',
onBeforeAction: function(){
Session.set( "currentRoute", "screens" );
Session.set("screenshots", this.params._id);
this.next();
}
});
this is the helper for screenshots template
Template.screens.helpers({
ss: function () {
var screenshots = Session.get("screenshots");
return Products.findOne({ _id: screenshots});
}
});
and am calling it here
<h4>Click to view the Screenshots
When i click to view the screenshots URL, the URL should be this /screenshots/:_id based on my router configuration, but what i see in the browser is /screenshots/ without the _id and the page shows 404 - NOT FOUND.
Is it possible to create nested routes?
because before i click on the link that executes the above route. i will be in this route
Router.route('itemDetails', {
path: '/item/:_id',
template: 'itemDetails',
onBeforeAction: function(){
Session.set( "currentRoute", "itemDetails" );
Session.set("itemId", this.params._id);
this.next();
}
});
and this route works fine i can see the item _id, is it possible to create another route inside it that has for example this path /item/:_id/screenshots?
I have the _id stored in Session.get("itemId"). Is it possible to call it in the path of the route somehow?
I tried '/item' + '/screenshots' + '/' + Session.get("itemId") but didn't work
or there is other way to solve it?
The problem is not with the code in the question, the 404 page is occurring due to it not being passed an id into the path, the browser says /screenshots/ and not /screenshots/randomId because it is only being passed that from the link.
As per additions to the question and chat with Behrouz: Because the value is stored in session we can use
Template.registerHelper('session',function(input){
return Session.get(input);
});
to register a global template helper called session which can be called with {{session session_var_name}} and create the link as below:
<h4>Click to view the Screenshots

Meteor Iron Router not working on certain links

I have a route like this:
Router.route('/box', function () {
this.render('boxCanvasTpl');
},{
name: 'box',
layoutTemplate: 'appWrapperLoggedInTpl',
waitOn: function() {
console.log("Box route ran ok.");
return [
Meteor.subscribe('item_ownership_pub', function() {
console.log("subscription 'item_ownership_pub' is ready.");
}),
Meteor.subscribe('my_items', function() {
console.log("subscription 'my_items' is ready.");
})
];
}
});
... and I am clicking a link in a Template like this:
My Link
I receive the 'Box route ran ok.' message, but some reason the page does not navigate to the given URL. I have added console.log code in the funciton that is run when the 'boxCanvasTpl' is rendered, but these aren't showing in the browser console. It seems that something inbetween is stopping the templkate from re-rendering, but can't put my finger on it - any ideas?
There are some properties of Iron Router that you need to be aware of.
Say that the user is currently already on /boxes and there is a box template that renders for that path. If you:
click on a link Click Me
or
click on a link Click Me
Iron Router will NOT re-render the template because it already exists on the page. It will also NOT re-render the template if the box template happens to be a partial template that is already rendered on the page that you're on and also exists on the page that you want to navigate to.
Since it doesn't re-render, any code you have inside Template.box.onRendered will also not run again.
This behavior is most common in your layout, header, and footer templates. For many users, these templates are used for all of a website's pages, regardless of path. Because the layout, header, and footer template is rendered on a person's first visit to the site, they won't be re-rendered ever again if the user decides to navigate to other parts of the site using the same templates, so the code inside Template.layout/header/footer.onRendered won't fire.
Also note - even if a reactive Spacebars helper changes the physical look of the layout / header / footer, it doesn't qualify as an actual render, so reactive updates to the template do not trigger the onRendered callback.
The lack of re-rendering is what gives Meteor that "snappy" feel.
EDIT
Try to code in a reactive, event-driven style. Try not to think too much in a render / re-render sense.
You go to /box
You click on a link for /box?box=2342
Get your params or query in Iron Router
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#route-parameters
In Iron Router use the data from the params or query to set the data context for the template.
Grab stuff from the data context as needed inside of the template's .onRendered, .events, and .helpers callbacks.
Set Session vars as necessary and use them in helpers to give reactive changes to the page without having to re-render a template. Also use events to trigger updates to the session vars to, again, trigger reactive changes to the page.
Try this:
afterwards, go to /test?BUNNIES=lalalala
check out the console logs
test.html
<template name="test">
{{myData}}
</template>
test.js
Template.test.helpers({
myData: function() {
console.log("data context accessed from test.helpers: ", this);
console.log("this.BUNNIES accessed from test.helpers: ", this.BUNNIES);
return this.BUNNIES;
}
});
Template.test.onRendered(function() {
console.log("data context accessed from test.onRendered: ", this.data);
});
Template.test.events({
'click': function(){
console.log("data accessed from test.events: ", this);
}
});
router.js
Router.route('/test', function() {
console.log("routed!");
this.render('test');
}, {
name: 'test',
data: function(){
//here I am setting the data context
// for /test?BUNNIES=1234
var query = this.params.query;
console.log("query: ", query);
return query;
},
waitOn: function() {
console.log("waitOn is running (should see this message once for each subscription)");
return [
Meteor.subscribe('item_ownership_pub'),
Meteor.subscribe('my_items')
];
}
});
way cleaner way of writing router
Router.route('/test', {
waitOn: function() {
console.log("waitOn is running (should see this message once for each subscription");
return [
Meteor.subscribe('item_ownership_pub'),
Meteor.subscribe('my_items')
];
},
data: function(){
var query = this.params.query;
console.log("query: ", query);
return query;
},
action: function(){
console.log("this will re-render if url params changed");
this.render();
}
})

Subscription in router

I want to subscribe to data on specific pages, so I put subscribe() inside router.js. I am not sure if I should enclose it inside Meteor.isClient() block. Should I? When will I ever do routing for the server-side?
Router.route('/courses/:_id', function () {
if (Meteor.isClient) {
Meteor.subscribe("comments");
}
this.render('CourseDetail', { .. });
});
Instead of putting the if (Meteor.isClient){} check inside of your router.js file, you can simply remove that check and put the file inside of the top-level client folder in your application directory. This way, you do not have to worry about your routes being processed on the server at all. In making that change, you can structure your route definition above in one of two ways:
Router.route('/courses/:id', function() {
this.wait(Meteor.subscribe('comments')); // Either this one
this.subscribe('comments').wait(); // or this one. DO NOT DO BOTH.
if(this.ready()) {
this.render();
} else {
this.render('CourseDetail');
}
});
or:
Router.route('/courses/:id', {
subscriptions: function() {
this.subscribe('comments');
},
action: function() {
this.render('CourseDetail');
}
});
Notice that the first option passes a function as the second parameter to the Router.route() function while the second option passes an object as the second parameter to the Router.route() function. Both options are perfectly valid. For information on the first option, check this out; for information on the second option, check this out.
As for when you would ever do server-side routing, this is usually done if you are setting up an HTTP request/response part of your application for external applications to access your server. Unless this is the case, you would most likely never need to worry about setting up such a thing. In the case of doing this, however, you would define your routes and put them in the top-level server folder in your application directory. for information on server-side routing, check this out.
No need to use the Meteor wrapper. Iron router has its own syntax for saying where you want your route to run.
Here is an example.
Router.route('/item', function () { var req = this.request; var res = this.response; res.end('hello from the server\n'); }, {where: 'server'});
Here is the docs site.
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
You should define the subscription in the onBeforeAction option of the iron router route.
Router.route('/courses/:_id', function () {
onBeforeAction: function () {
Meteor.subscribe("comments");
},
action: function (){
this.render('CourseDetail', { .. });
}
});
Source

Iron Router onBeforeAction isn't being called

I have a /user route set up, which is supposed to render the login template if the current user isn't logged in. The entire router has a waitOn that waits for the currentUser subscription to finish. The problem is that when I go to /user it simply renders the dataNotFound template instead.
Here's the snippets of code that are relevant to this situation. I've been careful to show you them in the order they're defined in my lib/router.js file.
Router.plugin('dataNotFound', {notFoundTemplate: 'notFound'});
Router.onBeforeAction(function () {
console.log(Meteor.userId())
if (!Meteor.userId()) this.render('login');
else this.next();
}, { only: ['user'] });
Router.configure({
waitOn: function () { return Meteor.subscribe('currentUser'); }
});
Router.route('/user', {
name: 'user',
template: 'userView',
data: function () { return Meteor.user(); }
});
That console.log above doesn't even ever fire. It seems to me that since it should be a reactive function that even if initially the dataNotFound is rendered, then soon after that the onBeforeAction should be fired and render the login template, right?
It's very bizarre that your console log doesn't even fire. I have a few ideas, but first want to address the last piece of your question.
The dataNotFound plugin is triggered when the data function fires on your rout. This means it is bypassing your onBeforeAction hook altogether, and not that it isn't getting there.
One thing I can think of that might be worth trying would be wrapping the 'user' route action in a if ( this.ready() ) statement:
edit:
Router.route('user', {
// Your other stuff
action: function() {
if this.ready() {
this.render();
},
The reason I suggest this is just that you are using a waitOn statement, but I'm not 100% sure how that works if you don't have a this.ready() somewhere in your route, because that's what (again, from my reading of the documentation, have not fiddled around with it) tells the router what to wait before executing. Possibly it's not waiting at all right now.
I had a problem with onBeforeAction hook after upgrading from meteor 0.9.1 to 1.
It didnt get fired when it should. Like after I log out, I enter address manually and instead of login page I can see the actual site waiting for data that never comes. onRun hook solved it, but as docs says it gets fired only once.
Router.onRun(function () {
if (!Meteor.userId()) {
this.render('login');
} else {
this.next();
}
}, { only: ['user'] });
Try swapping out Meteor.userId() with Meteor.user() in your if statement. See this link for reference on how to handle checking for the user in a before filter: http://www.manuel-schoebel.com/blog/meteorjs-iron-router-filters-before-and-after-hooks.
I found the issue here.
According to this post, thanks to Steeve Cannon. The problem is on waitOn function.
Probably when you logginOut the subscribe to currentUser will fail, and this will make your app in infinite waiting. onBeforeAction runs after waitOn
You will need to check all variables in you Publish to insure waitOn complete successfully.
Put an if statement inside the waitOn function.
waitOn: function () {
if(Meteor.userId())
return Meteor.subscribe('currentUser');
}
Refer this comment to know why this is happening: https://github.com/iron-meteor/iron-router/issues/1010#issuecomment-72210587

How to test Meteor router or Iron router with laika

I'm using laika for testing and the meteor-router package for routing. I want to do tests that navigate to some page, fill a form, submit it and check for a success message, but I'm stuck on the navigation part. This was my first attempt:
var assert = require('assert');
suite('Router', function() {
test('navigate', function(done, server, client) {
client.eval(function() {
Meteor.Router.to('test');
var title = $('h1').text();
emit('title', title);
})
.once('title', function(title) {
assert.equal(title, 'Test');
done();
});
});
});
This doesn't work because Meteor.Router.to doesn't have a callback and I don't know how to execute the next line when the new page is loaded.
I tried also with something like this
var page = require('webpage').create();
page.open('http://localhost:3000/test', function () {
...
}
but I got the error Error: Cannot find module 'webpage'
Edit
I'm moving to iron router, so any answer with that also will be helpful.
I had the same problem. I needed to navigate to some page before running my tests. I'm using iron router as well. I figured you can't just execute Router.go('foo') and that's it. You need to wait until the actual routing took place. Fortunately the router exposes a method Router.current() which is a reactive data source that will change as soon as your page is ready. So, in order to navigate to a specific route before running my tests, I firstly run the following code block:
// route to /some/path
client.evalSync(function() {
// react on route change
Deps.autorun(function() {
if (Router.current().path == '/some/path') {
emit('return');
this.stop();
}
});
Router.go('/some/path');
});
Since this is within an evalSync()everything that follows this block will be executed after the routing has finished.
Hope this helps.
Laika now includes a waitForDOM() function you can set up to wait for a specific DOM element to appear, which in this case would be an element in the page you're loading.
client.eval(function() {
Router.go( 'test' );
waitForDOM( 'h1', function() {
var title = $('h1').text();
emit( 'title', title );
});
});
The first parameter is a jQuery selector.

Resources