Iron Router: How to Provide Same Data to Multiple Routes - meteor

It's evident how to provide route-specific data, i.e. through the use of a controller:
PostController = RouteController.extend({
layoutTemplate: 'PostLayout',
template: 'Post',
waitOn: function () { return Meteor.subscribe('post', this.params._id); },
data: function () { return Posts.findOne({_id: this.params._id}) },
action: function () {
this.render();
}
});
But how does one provide data for the application in general? In the case that every route needs to be subscribed to the same subset of information, so that the pub/sub doesn't need to be re-done on every route change. Thanks!

It sounds to me like you are looking for a completely general publication/subscription scheme so that you do not have to define the waitOn/data option combination for every single route or route controller that you define. In that case, you can simply publish a given set of data on the server like so:
Meteor.publish('someData', function() {
return SomeDataCollection.find({});
});
and subscribe to that set of data on the client like so:
Meteor.subscribe('someData');
With this publication/subscription pair setup, you will have access to the data provided in all routes. You just have to make sure that you check for non-existent data in your code to handle the first load of any given template when the data has not been loaded on the client yet. In this manner, you would never have to actually define a the waitOn and data options for any route or route controller.
If you would like to utilize Iron Router in a different way than through route controllers, you also have the option of waiting on one/many subscriptions globally by using the Router.configure({}); function. To use the example above:
Router.configure({
waitOn: function() {
return Meteor.subscribe('someData');
}
});
For information about this route option and all of the other options that you have available at a global level, check this out.

Related

Implementing a simple search with Meteor and Iron Router

In the next phase of my Meteor journey (read: learning the ropes!), I'd like to implement a simple search based on user inputed values, then redirect to a route specific to the record returned from the server.
At the moment, I'm picking up the inputed values via this code:
Template.home.events 'submit form': (event, template) ->
event.preventDefault()
console.log 'form submitted!'
countryFirst = event.target.firstCountrySearch.value
countrySecond = event.target.secondCountrySearch.value
Session.set 'countryPairSearchInputs', [countryFirst, countrySecond]
countryPairSearchInputs = Session.get 'countryPairSearchInputs'
console.log(countryPairSearchInputs)
return Router.go('explore')
Happily, the console log returns the desired countryPairSearchInputs variable - an array of two ids. In my routes.coffee file I then have the following:
#route "explore",
path: "/explore/:_id"
waitOn: ->
Meteor.subscribe 'countryPairsSearch'
On the server side, I have:
Meteor.publish 'countryPairsSearch', getCountryPairsSearch
And finally, I have a search.coffee file in my /lib directory that defines the getCountryPairsSearch function:
#getCountryPairsSearch = ->
CountryPairs.findOne $and: [
{ country_a_id: $in: Session.get('countryPairSearchInputs') }
{ country_b_id: $in: Session.get('countryPairSearchInputs') }
]
With regards to the search function itself, I have a CountryPairs collection where each record has two ids (country_a_id and country_b_id) - the aim here is to allow users to input two countries, with the corresponding CountryPair then being returning.
I'm currently struggling to tie all the pieces together - the console output on searching is currently:
Uncaught Error: Missing required parameters on path "/explore/:_id". The missing params are: ["_id"]. The params object passed in was: undefined.
Any help would be greatly appreciated - as you can probably tell I'm new to Meteor and am still getting used to the pub/sub methodology!
Edited: mixed up client/server for the publish method when I first posted - the danger of late-night posting!
First, seems you're expecting an :id parameter on your 'explore' route.
If I understand you're case, you're not expecting any params here, so you can just delete ':id' from your route:
#route "explore",
path: "/explore/"
waitOn: ->
Meteor.subscribe 'countryPairsSearch'
or either add a params to your Router.go call:
Router.go('explore', {_id: yourIdVar});
Secondly, you're trying to use a client function: Session.get() server-side. Try to update the publication using a parameter ; or using a method.call.
client-side
Meteor.subscribe 'countryPairsSearch' countryA countryB
not sure about the coffeescript syntax, check http://docs.meteor.com/#/full/meteor_subscribe
and server-side
#getCountryPairsSearch = (countryA, countryB) ->
CountryPairs.findOne $and: [
{ country_a_id: $in: countryA }
{ country_b_id: $in: countryB }
]

Force iron-router to go to current route

I have created a route like:
this.route('design.create', {
path: '/design',
template: 'Design',
I have wroten some waitOn, data, onBeforeAction, onStop related and executed when the route is loaded.
I have a "strange" use case:
Where I'm already in the route design.create I would like re-init it (execute again waitOn, data, onBeforeAction, onStop).
A simple: Router.go('design.create') not works...I guess because I'm already on the same route.
Seems that:
Router.current()._computation.invalidate()
Do what I want...but it's not very elegant call a private method
Set a dependency by using a session variable in the onBeforeAction and change the session variable when you what the current route to get rerun:
...
onBeforeAction: function() {
Session.get('dependOnMe')
// the acutal onBeforeAction-code goes here
}
...
...
// somewhere else
Session.set('dependOnMe', new Date())
// will trigger the onBeforeAction again
...

How to use reactive vars on server side

I want to use a reactive counter var on server side. But I can not guess how to do it without using collections.
I expect {{count}} will be updated after server count var changed without refresh a page or how to send a client that the count was changed?
<body>
{{> test }}
</body>
<template name="test">
{{count}}
</template>
client:
Meteor.call('count', function(err, result) {
console.log(result)
Session.set('count', result)
})
Template.test.helpers({
count: function () {
return Session.get('count')
}
});
server:
var count=0
Meteor.startup(function () {
Meteor.setInterval(function() {
count++
}, 1000)
});
Meteor.methods({
count: function() {
return count
}
})
My code on MeteorPad
I want to see what I expect:
Client:
Meteor.subscribe('count')
Template.test.helpers({
count: function () {
return Counter.findOne().count
}
});
Common:
Counter = new Mongo.Collection('count')
Server:
Meteor.publish('count', function() {
return Counter.find()
})
Meteor.startup(function () {
if(Counter.find().count() === 0) {
Counter.insert({count: 0})
}
Meteor.setInterval(function() {
Counter.update({}, {$inc: {count: 1}})
}, 1000)
});
Example on meteorpad
It depends on how you plan to scale your application. If you plan to scale to multiple server instances then you cannot rely on the servers sharing information automatically. In this case it would be best to create a collection named something like "ApplicationState". Then every instance of your application can use a consistent state, and you can make use of the built in subscriptions.
If you plan to only use a single server instance then you should checkout the documentation on Tracker: http://manual.meteor.com/#tracker. This allows you to defined custom dependencies on data. I have not had the chance to play with it yet, but I am pretty sure that you can create something similar to subscriptions: http://manual.meteor.com/#deps-creatingreactivevalues
Your code does not work as the way you want it because regular plain vars are not reactive data sources. The docs has a list of reactive data sources here http://docs.meteor.com/#/full/reactivity - So for your example to work you'd want to use Session or ReactiveVar - but these only works on the client so they wont help you here.
Your best bet is to create a manual publish function and subscribe to it from the client (or as other people has proposed, use a Collection - which would work across multiple servers as well). Hope that helps!

Proper way to .findOne() in Iron:router

Why is it that .findOne() does not work when executed inside the Router?
It always returns undefined.
Yet .find() works without any problems. Also tested .findOne() by manually entering the condition and the ._id manually.
Is .findOne() not designed to work inside the Router?
It's working properly in my application. I implemented it like this:
Router.map(function() {
this.route('training', {
path: '/training/:id',
data: function() {
return Trainings.findOne({id: this.params.id});
},
notFoundTemplate: 'notFound',
title: "Training"
});
});
it works fine!
maybe you're trying to pass the result to an iterator? it's not a cursor.
try a find().fetch() and use the result in the same way.
if it's a data/timing issue you could also guard with a ready() function.
you don't need to if you're using it reactively but this gives you a bit more explicit knowhow when things happen, yet without using a waitOn.
in coffeescript:
#---------routes ---------
#route 'routeName',
path: '/path/to/:cname'
onBeforeAction: ->
Meteor.subscribe('Things', {
cname: #params.cname
})
this.next()
data: ->
if #ready()
data = {
params: #params
}
data.lesson = Things.findOne({cname: #params.cname})
return data

Meteor removes collection on new route

I have a simple list and details view using two collections.
When I navigate back to the list view Meteor removes the single document added to the details collection and undoes the change to the other collection.
I want this data to remain so the client doesn't have to keep reloading it...
Both the 'league' and the 'standings' subscriptions are 'undone' on navigation back to the the root. The league and leagues route both use the 'weeks' Mongo collection. When navigating to a league detail I add to the single document. Navigation to the detail works fine ... its when I navigate back to the list that I loose the collection data.
I need all this data 'cached' and am obviously not going about it correctly....
Router.map(function () {
this.route('leagueList', {
path: '/'
});
this.route('league', {
path: '/league/:league',
template: 'standings',
waitOn: function () {
console.log(this.params.league);
return [Meteor.subscribe('league', this.params.league),
Meteor.subscribe('standings', this.params.league) ];
},
data: function () {
return {theLeague: Leagues.findOne({league: this.params.league}),
theStandings: Standings.findOne()};
}
});
});
Server:
Meteor.publish('leagues', function(){
console.log('all league names sent');
return Leagues.find({}, {fields: {weeks: 0}});
});
Meteor.publish('league', function(theLeague){
console.log('sending weeks detail for: ' + theLeague);
return Leagues.find({league: theLeague});
});
Meteor.publish('standings', function(theLeague){
console.log('standings: ' + theLeague);
var file = Leagues.findOne({league: theLeague}).weeks[0].file;
return Standings.find({file: file});
});
client:
Leagues = new Meteor.Collection('weeks');
Standings = new Meteor.Collection('details');
Meteor.subscribe('leagues');
There's work in progress in iron router to allow (and optimize) this (not immediately stopping the subscriptions when you route to another page). See the sub-manager branch.
But if you create the subscription apart from the waitOn call, I think the subscription is never stopped. For example, in the code below, the routes a and c will wait for the initialData to be received (which will be fetched directly when the user loads the page (even if it uses route b)), and the subscription for it will never stop, even if you leave, for example, route a. However, I don't think you can use this approach if you need to use some parameters in the route (you can probably fix something with setInterval, but it will be ugly).
var handleToDataIMostlyNeed = Meteor.subscribe('initialData')
Router.map(function(){
this.route('a', {
waitOn: function(){
return handleToDataIMostlyNeed
}
})
this.route('b', {
waitOn: function(){
return [] // Wait for nothing.
}
})
this.route('c', {
waitOn: function(){
return handleToDataIMostlyNeed
}
})
})

Resources