I'm building a free TV-tracking app with Meteor, and along the way I seem to have hit a wall. A part of my template contains this:
<template name="results">
<div class="row">
<div class="span6 offset6">
<h4>Search Results</h4>
<ul>
{{#each series}}
<li><a href="http://thetvdb.com/?tab=episode&seriesid{{tvdbseriesid}}&lid={{tvdblid}}">{{seriesname}}</li>
{{/each}}
</ul>
</div>
</div>
</template>
Then, within my client js code, I do this:
Template.search.events({
'click #search' : function(evt) {
evt.preventDefault();
var query = $('#query').val();
if (query) {
Meteor.call('queryshow', query, function(error, result) {
Template.results.series = result;
console.log(Template.results());
});
}
}
});
The "queryshow" server method is just a Collection query method that returns an object containing the data that my template needs.
The problem is this however: the change isn't reflected in the browser window. I can't seem to figure out why, because the console.log(Template.results()) call shown below returns the correct html that I am expecting.
How do I fix this? I tried looking around Meteor's docs and I can't seem to find a function that forcibly re-renders a Template. Any help will be much appreciated!
Template.results.series should be a function that returns the series, rather than set directly to the series you want to use. That way, when Meteor runs the function to get the series, it can register what the function depends on, and then re-run the function whenever any of the dependencies change.
The easiest way to register a dependency for your information is to use the Session object. So your code would look something like:
Template.results.series = function () { return Session.get('resultsSeries'); }
Meteor.call('queryshow', query, function (err, res) {
// handle the error if it's there
// ...
// Then set the session variable, which will re-run anything that depends on it.
Session.set('resultsSeries', res)
}
Related
I'm trying to implement a basic route using Flow Router. But no matter what _id of a collection document I request; I always only get the info about the first document in my collection - 'Requests'.
So here's my route definition in the file /lib/routes.js:
FlowRouter.route('/requests/:reqId', {
subscriptions: function (params, queryParams) {
this.register('theRequest', Meteor.subscribe('singleRequest', params.reqId));
},
action: function (params, queryParams) {
FlowLayout.render('layout', { aside: 'aside', main: 'singleRequest' });
console.log("Yeah! We are on the post:", params.reqId);
},
name: 'aRequest'
});
Here's my helper:
Template.singleRequest.helpers({
getRequest: function () {
return Requests.findOne();
}
});
Here's my server publish:
Meteor.publish('singleRequest', function (reqId) {
return Requests.find({ _id: reqId});
});
And here's the template:
<template name="singleRequest">
{{#if isSubReady 'theRequest'}}
{{#with getRequest}}
<h2>{{_id}}</h2>
<p>{{reqFrom}}</p>
<p>{{reqBy}}</p>
{{/with}}
{{else}}
loading...
{{/if}}
</template>
What am I doing wrong?
Note: In the console, I do see right 'reqId' slug due to the console.log command in the route definition. But I do not see the relevant info for the document which it belongs to.
Thanks!
OK my problem was that I previously had another subscription where I published all the Requests - not just the one with that certain _id. And because I did not create the helper to get only the one with that certain _id; of course server just sent me the very first request.
My solution was only to subscribe to previous subscription and define in the helper to fetch to the request _id:
FlowRouter.route('/requests/:reqId', {
subscriptions: function (params, queryParams) {
this.register('allRequests', Meteor.subscribe('Requests', params.reqId));
},
action: function (params, queryParams) {
FlowLayout.render('layout', { aside: 'aside', main: 'singleRequest' });
console.log("Yeah! We are on the post:", params.reqId);
},
name: 'aRequest'
});
and the helper:
Template.singleRequest.helpers({
getRequest: function () {
var reqId = FlowRouter.getParam("reqId")
return Requests.findOne({_id:reqId});
}
});
For anyone who browses to this question looking for how to get Flow Router to capture and dynamically link to slugs from the db, then call a template page for each item, I made a very simple example app and posted it on here on GitHub.
Hope it will help someone.
I believe your code is correct for what you are trying to do. However, I infer from your direct replication of the code from flow-router that you're pretty new to Meteor. Therefore, I'm willing to take a punt that you still have the autopublish package in your app.
Autopublish pushes all data in your collection to the client. Even without a publish/subscribe call.
You can do two things. To keep autopublish (which will make your development process easier at the start but maybe harder later on), just change your template helper to:
Template.singleRequest.helpers({
getRequest: function () {
return Requests.findOne({ _id: FlowRouter.getParam("reqId")});
}
});
Alternatively (and you will want to do this eventually), go the the command line and type:
meteor remove autopublish
You can read up on the pros and cons of autopublish in lots of places to make your own decision on which option is best for you. However, you should also consider whether or not you want to cache your data in future (e.g. using subsManager) so you may want to change your template helper regardless.
I have a Books and Chapters collection. Self-explanatory: A book can have many chapters.
subscriptions.js:
Meteor.publish("singleChapter", function(id) {
return Chapters.find(id);
});
book_page.js:
Template.bookPage.helpers({
chapters: function() {
Chapters.find({
bookId: this._id
}, {
sort: {
position: 1
}
});
}
});
book_page.html:
<template name="bookPage">
<div class="chapter-list hidden">
<div class="chapter-items">
{{#each chapters}}
{{> chapterItem}}
{{/each}}
</div>
</div>
</template>
chapter_item.html:
<template name="chapterItem">
<div class="chapter clearfix">
<div class="chapter-arrows">
<a class="delete-current-chapter" href="javascript:;">X</a>
</div>
</div>
</template>
Right now, I'm trying to get the current chapter item in chapter_item.js:
Meteor.subscribe("singleChapter", this._id); // even tried this but didn't work
Template.chapterItem.events({
"click .delete-current-chapter": function(e) {
e.preventDefault();
var currentChapter = Chapters.find(this._id);
}
});
But when I do
console.log(currentChapter);
I get undefined. What am I doing wrong?
TL/DR - skip to 3 as it's probably most relevant, but I've included the rest for completeness.
I assume you're putting the console.log... line in the "click .delete-current-chapter" callback? The currentChapter variable is going to be local to that function, so you won't get anything by entering that in the console. Apologies if that's obvious, but it's not clear that you're not doing this from the question.
Even in the callback, currentChapter is going to be a cursor, not a document or array of documents. Use findOne to return a single doc (or null), or find(query).fetch() to return an array (which in this case should probably be just one doc).
Where and when are you trying to subscribe to singleChapter? If it's in the callback, you have to bear in mind that this isn't a reactive function. This means that you'll subscribe (once you know the _id to which to subscribe), but immediately return currentChapter before the collection is actually ready (and thus doesn't have anything in it). In this case, the callback won't rerun once the collection is ready as event handlers aren't reactive.
The easiest way to resolve this would be to use the onReady callback when you subscribe, and set currentChapter in there. The alternative would be a self-stopping Tracker.autorun in the event handler, but this seems like overkill.
As a final point, you need to be a bit careful about subscriptions with this sort of setup, as you can easily accumulate dozens and dozens of them per client, with none of the automatic subscription stopping that Iron Router provides. Given this use case, it's probably preferable to stop the subscription as soon as your callback has run and the item in question has been deleted.
Is your publish function working? Perhaps Mongo has a feature that I'm not aware of, but I'd expect you need to include {_id:id}, not just (id).
Meteor.publish('singleChapter', function(id){
return Chapters.find({_id: id});
});
I want to insert a Meteor template (a simple login form) but I want to control what happens after the form is submitted. Ideally, I'd like to be able to pass a function afterLogin() to the template. But I'm quite unsure how to do this and if this is even possible.
I've recently seen an interesting package viewmodel and I'm not sure how related it is. But the goal in this context is basically to render a view with a different view model.
Any ideas? I'm currently using a session variable and then after login, I check that session variable to run the correct function but this is ugly and not easy to work with. Any ideas?
This is how I do it :
I assume that your login form is called from within a parent template, use the attributes syntax to pass the value of a custom helper to the data context of the login form.
<template name="parent">
{{> loginForm options=loginFormOptions}}
</template>
The helper returns an object encapsulating a function, the caller is responsible for setting this function to whatever they want.
Template.parent.helpers({
loginFormOptions:function(){
return {
afterLogin:function(){
// we assert that the context is correct :
// this will print Template.loginForm
console.log(this.view.name);
}
};
}
});
Your login form code, acting as a library, can read from its data context the function that was passed by the caller template, and then call the function with the proper this context.
Template.loginForm.events({
"submit":function(event,template){
// ...
Meteor.loginWithPassword(...,function(error){
if(error){
console.log(error);
return;
}
// guard against the existence of the custom afterLogin function
if(template.data.options && template.data.options.afterLogin){
// execute the custom function with proper context
template.data.options.afterLogin.call(template);
}
});
}
});
First of all, I would use Iron Router for navigating through different views of my application, you can get it here:
https://github.com/EventedMind/iron-router
meteor add iron:route
Then, check http://docs.meteor.com/#template_events. I would use something like:
Template.loginFormTemplate.events({
'click .loginButton': function() {
//... if success login ...
Router.go('nextScreen');
}
});
[Update 1]
I am afraid that trying to pass function to Route is an ugly approach in a sense of Meteor architecture.
You can try though, defining some Global variable, which is responsible for listening and forward-triggering events across the Routes
var eventHelper = (function () {
var self = _.extend({
afterLogin: function () {
self.trigger('forwardedEvent');
}}, Backbone.Events);
return self;
})();
Route1.events({
'click': function () {
//... Let's call after login
eventHelper.afterLogin();
}
});
eventHelper.on('forwardedEvent',function() {
// ...
});
I have a Meteor template that should be displaying some data.
Template.svg_template.rendered = function () {
dataset_collection = Pushups.find({},{fields: { date:1, data:1 }}, {sort: {date: -1}}).fetch();
a = moment(dataset_collection[0].date, "YYYY/M/D");
//more code follows that is also dependent on the collection being completely loaded
};
Sometimes it works, sometimes I get this error:
Exception from Deps afterFlush function: TypeError: Cannot read property 'date' of undefined
I'm not using Deps in any context. As I understand it, the collection is being referenced before it is completely finished loading.
I therefore would like to figure out how to simply say "wait until the collection is found before moving on." Should be straightforward, but can't find an updated solution.
You are right, you should ensure that code depending on fetching the content of a client-side subscribed collection is executed AFTER the data is properly loaded.
You can achieve this using a new pattern introduced in Meteor 1.0.4 : https://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
client/views/svg/svg.js
Template.outer.onCreated(function(){
// subscribe to the publication responsible for sending the Pushups
// documents down to the client
this.subscribe("pushupsPub");
});
client/views/svg/svg.html
<template name="outer">
{{#if Template.subscriptionsReady}}
{{> svgTemplate}}
{{else}}
Loading...
{{/if}}
</template>
In the Spacebars template declaration, we use an encapsulating outer template to handle the template level subscription pattern.
We subscribe to the publication in the onCreated lifecycle event, and we use the special reactive helper Template.subscriptionsReady to only render the svgTemplate once the subscription is ready (data is available in the browser).
At this point, we can safely query the Pushups collection in the svgTemplate onRendered lifecycle event because we made sure data made its way to the client :
Template.svgTemplate.onRendered(function(){
console.log(Pushups.find().fetch());
});
Alternatively, you could use the iron:router (https://github.com/iron-meteor/iron-router), which provides another design pattern to achieve this common Meteor related issue, moving subscription handling at the route level instead of template level.
Add the package to your project :
meteor add iron:router
lib/router.js
Router.route("/svg", {
name: "svg",
template: "svgTemplate",
waitOn: function(){
// waitOn makes sure that this publication is ready before rendering your template
return Meteor.subscribe("publication");
},
data: function(){
// this will be used as the current data context in your template
return Pushups.find(/*...*/);
}
});
Using this simple piece of code you'll get what you want plus a lot of added functionalities.
You can have a look at the Iron Router guide which explains in great details these features.
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
EDIT 18/3/2015 : reworked the answer because it contained outdated material and still received upvotes nonetheless.
This is one of those problems that I really wish the basic meteor documentation addressed directly. It's confusing because:
You did the correct thing according to the API.
You get errors for Deps which doesn't point you to the root issue.
So as you have already figured out, your data isn't ready when the template gets rendered. What's the easiest solution? Assume that the data may not be ready. The examples do a lot of this. From leaderboard.js:
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Only if player is actually found, will player.name be accessed. In coffeescript you can use soaks to accomplish the same thing.
saimeunt's suggestion of iron-router's waitOn is good for this particular use case, but be aware you are very likely to run into situations in your app where the data just doesn't exist in the database, or the property you want doesn't exist on the fetched object.
The unfortunate reality is that a bit of defensive programming is necessary in many of these cases.
Using iron-router to wait on the subscription works, but I like to keep subscriptions centrally managed in something like a collections.js file. Instead, I take advantage of Meteor's file load order to have subscriptions loaded before everything else.
Here's what my collections.js file might look like:
// ****************************** Collections **********************************
Groups = new Mongo.Collection("groups");
// ****************************** Methods **************************************
myGroups = function (userId) {
return Groups.find({"members":{$elemMatch:{"user_id":userId}}});
};
// ****************************** Subscriptions ********************************
if(Meteor.isClient){
Meteor.subscribe("groups");
}
// ****************************** Publications *********************************
if(Meteor.isServer){
Meteor.publish("groups", function () {
return myGroups(this.userId);
});
}
I then put collections.js into a lib/ folder so that it will get loaded prior to my typical client code. That way the subscription is centralized to a single collections.js file, and not as part of my routes. This example also centralizes my queries, so client code can use the same method to pull data:
var groups = myGroups(Meteor.userId());
I am playing with SignalR and KnockoutJS and can't seem to find a simple way to get an array from the database presented using the MVC4 framework.
I have no problem sending a single object from the server - but when I try to send an array I get stuck. Hopefully someone with more experience can spot the probably obvious mistakes I am making, and show how this should be done (JavaScript is not my strong side). The problem as far as I can understand is the mapping of the data passed from the server. Any help is much appreciated!
The SignalR Hub (orders is a simple table with Id and Name)
public class feedHub : Hub
{
private dataContext db = new dataContext();
public void GetAll()
{
var orders = db.orders.ToArray();
Clients.getData(orders);
}
}
Simple HTML code to present the orders;
<div id="Demo">
<div data-bind="foreach: orders">
<div data-bind="html: Id"></div>
<div data-bind="html: Name"></div>
</div>
</div>
JavaScript
<script type="text/javascript">
var viewModel = {
orders: ko.observableArray(orders)
};
ko.applyBindings(viewModel, $("#Demo")[0]);
$(function () {
// Client side version of the feebHub class
var hubcon = $.connection.feedHub;
// getData called from server
hubcon.getData = function (data) { viewModel.orders(data) };
// Start connection and call getAll
$.connection.hub.start(function () { hubcon.getAll(); });
});
</script>
A couple of points:
Just use ko.observableArray(), i.e. without the parameter
Put the call to ko.applyBindings inside your ready function, e.g. just before you get your hub reference
That should be enough to get it working. At least, it works me in this fiddle which I based on your code.
One further point though ... you are passing plain JSON objects to KO (i.e. inside your observable array). This is like data-binding in C# against some classes that do not implement INotifyPropertyChanged. IOW the binding will work properly once and changes in the objects will never get reflected on the UI. If you want SignalR to feed changes into your objects, then they will need to have observable properties and you might want to look into the KO mapping plugin.