How to change meteor router paths/slugs without reloading the template? - meteor

I have two router paths, one is /topic/:postId and one is /topic/:postId/:commentId. Both paths are pointing to the template "topic".
The first path opens the template with a list of all the post's comments. Whenever I select a comment, I filter some contents on the page based on the comment and I want the route to change to the second path (allowing users to copy the link to this filtered page directly), but I want meteor to stay on the same template and not reload the page.
In short, how can I change the URL/slugs without reloading the page?
Edit: These are my routes pointing to the same template and I'd like to jump between them without (noticeable) reload. I left out waitOn and multiple data lines for simplicity' sake:
Router.route("/topic/:postId", {
name: "Topic",
data: function () {
return {
content: "topic",
post: this.params.postId
};
}
});
Router.route("/topic/:postId/:commentId", {
name: "Comment",
data: function () {
return {
content: "topic",
post: this.params.postId,
comment: this.params.commentId
};
}
});

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 router force rerender

I have the following routs
/product/123
/product/456
the template renders the product data and allows the user to drag things around
when I make a redirect to another product like that
Router.go("product",{_id:"456"});
the template updates the data but does not re-render the html. that means that what the user dragged stays in place. this is good for some cases but not for mine
the only solution that worked for me was to redirect to another page that sets clear template and it redirects to the product page
my router function:
Router.route('product/:_id/', {
name:"product",
data:function(){
var data = {product: Products.findOne({_id:objectId(this.params._id)})}
return data;
},
waitOn:function(){
return Meteor.subscribe('Products',this.params._id);
},
yieldTemplates: {'product': {to: 'mainArea'}},
});
I need a way to tell the router or template to reset the html
One solution is to set up an autorun in the template's onRendered function that looks for changes to the URL parameters and the resets the template as needed. Something like this:
Template.myTemplate.onRendered(function() {
var controller = Router.current()
this.autorun(function() {
var params = controller.getParams() // Reactive
// Clear up your drag interface here
});
});
By accessing controller.getParams() (the route controllers reactive parameter list) the outrun will be called when you move between routes on the same template.

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();
}
})

Angular ui-router setup for this use case?

I want to redraw a line in a line chart without reloading it (neither template nor controller) completely when navigating from country/5 to country/7. Can this be done with ui-router?
State
country/:id
Template with directive - country.html
<lineChart data="scope.someData">
Controller
onStateParamsChange => fetch data, set scope.someData
As of today, there is no official support for what you're looking for, which in UI Router parlance is considered 'dynamic parameters'. However, if you check out this experimental branch and help us out by testing and providing feedback, it will get merged to master sooner.
Set up your route/state like so:
$stateProvider.state("country", {
url: "/country/{id:int}",
params: { id: { dynamic: true } }
/* other state configuration here */
});
Then, in your controller, you can observe changes to id like so:
$stateParams.$observe("id", function(val) {
// val is the updated value of $stateParams.id
// Here's where you can do your logic to fetch new data & update $scope
});

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