Subscription in router - meteor

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

Related

How to handle subscription in Meteor / Iron Router

whats the bestway to handle subscription based data. For example, you have a game where you have to create the character first before you can do any other things. Currently I thougth I can try to handle it with a onBeforeAction filter. So I have a global controller for every route that needs a the character.
DefaultController = LayoutController.extend({
onBeforeAction : function() {
var currentCharacter = Character.getCurrent.call({});
if(currentCharacter === undefined) {
this.render('CharacterCreate');
} else {
this.next();
}
},
waitOn() {
this.subscribe('characters.owned');
}
});
You have a route like this:
Router.route('/game', { controller: 'DefaultController' });
The problem is until the the collection is loaded the game template will shown. Is there a better approach like this? And another problem when a route needs a character it throws an exception until the subscription is loaded.
Just use a loading hook while the subscriptions are being loaded.
loading(){
this.render('myLoadingTemplate');
}
The loading hook is run automatically while waiting for subscriptions to be ready.
You might find my post on building a clean router.js file useful.

How can I be updated of an attribute change in Meteor?

I have a template that subscribes to a document. Everything works fine in the DOM and Blaze updates as soon as an attribute used in the template helpers is changed.
I also have some custom logic that doesn't appears in the DOM and depends on the document attributes. How can I call a function to change that logic when an attribute is updated?
I'm looking for something like this.data.attr.onChanged where this would refer to the template and this.data is the data send to the template, as usual; or a Meteor function that is rerun on change where I could put my callback in.
I hoped that template.onRendered would be recalled, but that's not the case.
I've read a lot about reactive variables, but could not find how they could be useful here.
[edit] the change is coming from the server that is communicating with another service
I've tried Tracker.autorun like this:
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
console.log("tracker", self.data.item.socketId);
});
});
And the corresponding route is:
Router.route('editItem', {
path: '/edit/:_id',
waitOn: function () {
var sub = Meteor.subscribe('item', this.params._id);
return [sub];
},
data: function () {
return {item: Items.findOne(this.params._id)};
},
action: function () {
if (this.ready())
this.render();
}
});
At some point, the property socketId gets removed from the corresponding document by the server and I'm sure of that since I've checked in the shell, but the tracker doesn't rerun.
Use Template.currentData().item.socketId instead of self.data.item.socketId, this will give you reactivity.
And in templates generally, use self.autorun instead of Tracker.autorun (unlike Tracker.autorun, this will ensure that the autorun is stopped when the template is destroyed). Likewise, if you want to subscribe in a template, use self.subscribe instead of Meteor.subscribe.
Code to see if Template.currentData() works for you:
Template.editItem.onRendered(function() {
var self = this;
self.autorun(function () {
console.log("tracker", Template.currentData().item.socketId);
});
});
I'm not sure if I got you right, you just want to observe your html inputs and apply the new value to your helper method(s) on change?!
If so, you could use session variables to store your temporary UI state:
// observe your input
Template.yourTemplate.events({
"change #inputA": function (event) {
if(event.target.value != "") {
Session.set("valueA", event.target.value);
}
}
}
// apply the changed value on your helper function
Template.yourTemplate.helpers({
getSomeData: function() {
var a = Session.get("valueA");
// do something with a ..
}
}
In meteor's official todo app tutorial this concept is also used.
If you need to re-run something which is not part of DOM/helper, you can use Tracker.autorun. According to meteor docs, Run a function now and rerun it later whenever its dependencies change.
here's the docs link
Try moving the subscription into Tracker.autorun
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
Meteor.subscribe('item', this.params._id);
console.log("tracker", self.data.item.socketId);
});
});
Of course you can't use this.params there so you can store this as a Session variable

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

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'] });

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.

Can I mount another route handler through __meteor_bootstrap__.app?

I'm building my first meteor app and need to be able to create a new route handler to handle an oauth callback. I've looked through server.js and found that the connect.app context is available under meteor_bootstrap. Although this doesn't seem to work:
if (Meteor.is_server) {
Meteor.startup(function () {
var app = __meteor_bootstrap__.app;
app.use('/callback',function (req,res) {
res.writeHead(404);
res.end();
return;
});
});
}
Thoughts?
The problem with this solution is that your middleware is put at the bottom of the stack. Therefore the catch-all meteor handler will always run before your "/callback"-handler.
One very hacky way to get around this (until the meteor releases their proper routing support) is to splice in your handler att the top of the stack:
__meteor_bootstrap__.app.stack.splice (0, 0, {
route: '/hello',
handle: function (req,res, next) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end("hello world");
return;
}.future ()
});
You can achieve this with the Meteor Router smart package:
Meteor.Router.add({
'/callback': 404
})
Some of the answers are leading to routing being a no-go on the server right now without being hacky. It's a known issue, and sounds like routing is a hot item on the todo list.

Resources