Setting {reactive: false} causes {{#each}} to break? - meteor

I'm not sure if I'm misunderstanding how something is supposed to work but whenever I set reactive to false it seems to break {{#each}} loops in my template.
I can recreate the issue with a simple example by creating a new meteor application, leaving autopublish etc intact and then the following code:
.js file:
Numbers = new Mongo.Collection('numbers');
if (Meteor.isClient) {
Template.hello.helpers({
number: function() {
return Numbers.find({},{reactive:false});
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
if (Numbers.find().count() === 0) {
for (var i = 0; i < 100; i++)
Numbers.insert({number: i});
}
});
}
.html file
<head>
</head>
<body>
{{>hello}}
</body>
<template name="hello">
{{#each number}}
{{this.number}}
{{/each}}
</template>
In the instance above, nothing is printed. If I delete {reactive: false} then the numbers are printed as expected.

Your code cannot work ; this is not what the {reactive: false} is made for. It would make sense for the subscribtion.
What happens here are the following steps :
The application starts
The template is rendered and go for the Numbers helpers which returns nothing because it did not received anything from the server yet
And... that's it
What you want to see happen :
The application starts
The template is rendered and go for the Numbers helpers which returns nothing because it did not received anything from the server yet
Because you did not add the {reactive: false} option, each time something is received from the server, the computation is replayed and the template is updated
If you add something that will trigger the computation, it will work :
if (Meteor.isClient) {
Meteor.subscribe('numbers');
Template.hello.helpers({
number: function() {
var n = Numbers.find().fetch();
return Numbers.find({},{reactive:false}).fetch();
}
});
}
It works now because the var n = Numbers.find().fetch(); will trigger when the Numbers.find() cursor will change.
But it does not make much sense. Tell me what you want to achieve.
(I bet, that what you really want to do is more something like that : Meteor.subscribe('numbers', {reactive:false}); and let the helper be reactive.
I made you a quick meteor pad to play with that : http://meteorpad.com/pad/4zJTtdLnWcWXLuEuj/collection_reactivity

Related

Access an original TemplateInstace from the helper in Meteor

Could anybody point me how to access an original TemplateInstance from the meteor helper. I'm aware of the Template.instance() but it appears to return the template instance where the helper was called, not the template instance for which the helper was defined.
Imagine we have two tiny templates:
<template name='demo'>
<h1>{{helper}}</h1>
{{# border}}
<h2>{{helper}}</h2>
{{/border}}
</template>
<template name='border'>
<div style="border:1px solid red">
{{> UI.contentBlock}}
</div>
</template>
With the following behavior:
Template.demo.created = function() {
this.result = "OK";
}
Template.demo.helpers({
helper: function() {
var tmpl = Template.instance();
return tmpl.result || "FAILED";
}
});
I've expected to obtain two "OK" for the demo template: the second one should be in the red border. But since Template.instance() returns original TemplateInstance only when helper is called at the top level of its owner template the result is "FAILED" (of course in the red border).
Question: Is there any public api to get the original TemplateInstance (without need to traverse view/parentView/_templateInstace)?
I think the best way to do this might be to either just set a Session variable, or use a Reactive Variable (using the reactive-var package - here is the documentation).
I've made a meteor pad to show how this more - here.
Basically:
Template.demo.created = function() {
result = new ReactiveVar('OK');
}
Template.demo.helpers({
helper: function() {
return result.get() || "FAILED";
}
});
I think your main problem is that you not setting a template instance variable correctly. Try the below code...
Set an instance variable:
Template.instance().result.set("OK");
Get an instance variable:
Template.instance().get("result");
So your updated code would be:
Template.demo.created = function() {
Template.instance().result.set("OK");
}
Template.demo.helpers({
helper: function() {
return Template.instance().get("result") || "FAILED";
}
});
It seems that it's known and already fixed (?) Meteor bug. More here: https://github.com/meteor/meteor/issues/3745
Comment from rclai on GitHub:
This was already addressed and fixed for the next release.
Run meteor like this, not sure if it still works:
meteor --release TEMPLATE-CURRENT-DATA#0.0.1
Another alternative is to use aldeed:template-extensions, which has super nice features, especially with dealing with template instances and I believe their way of fetching the template instance is a workaround this issue.

Meteor event when Template is freshed

I am working on a Meteor project that has come custom Pagination using Sessions. The template rendering the contents of said items is using ellipsis.js and highlight.js to do some DOM formatting. The code looks something like thus:
if (Meteor.isClient) {
Meteor.startup(function () {
Session.setDefault("homePageSize", 10);
Session.setDefault("homePageStart", 0);
});
}
Template.home.articlesPaginated = function() {
return Articles.find({published: true}, {sort: {post_date: -1}, skip: Session.get("homePageStart"), limit: Session.get("homePageSize")});
}
Template.home.rendered = function() {
// Setup ellipsis
$('.ellipsis').dotdotdot({
ellipsis: '...',
wrap: 'word',
fallbackToLetter: true,
after: $('a.blog_continue')
});
// Setup highlight.js
$('pre code').each(function(i, block) {
hljs.highlightBlock(block);
});
}
Template.home.events({
'click .next': function(event) {
var offset = Session.get("homePageStart") + Session.get("homePageSize");
if (offset < 0) {
offset = 0;
}
Session.set("homePageStart", offset);
},
'click .prev': function(event) {
var offset = Session.get("homePageStart") - Session.get("homePageSize");
if (offset < 0) {
offset = 0;
}
Session.set("homePageStart", offset);
}
});
Pagination is working just fine, but as soon as the Template re-renders I loose all the ellipsis.js and highlight.js formatting. I know the obvious reason is that the DOM has changed, and since the Template.render only runs once up-front and doesn't happen when the Template re-renders the DOM updates are not being applied. So, what is the best way to trigger ellipsis.js and highlight.js after the Template is done such that it gets re-called everytime the Template re-renders?
Basically you need to listen for changes in your Articles collection, which is a client-side subset of the server database clipped to contain only the currently visible paginated articles.
When you detect a change in the articles subset, you'll need to retrigger initialization of ellipsis.js and highlight.js.
You could reorganize your code as follow :
First, we define the cursor declaration as a separate function on his own because we need to use it twice :
function articlesPaginated(){
return Articles.find({
published: true
}, {
sort: {
post_date: -1
},
skip: Session.get("homePageStart"),
limit: Session.get("homePageSize")
});
}
Template.home.helpers({
articlesPaginated:articlesPaginated
});
Then in the rendered callback, we need to setup a reactive computation that will depend on this cursor, so whenever the articles subset is updated to a new page, our computation will rerun.
But we need to be aware that the helper we defined on the home template returns the same cursor so it's going to be invalidated and trigger DOM refresh AT THE SAME TIME... JavaScript is single-threaded and the Tracker.Computation manual states that the order of execution of concurrently invalidated computations is unpredictable.
So we cannot just trigger the ellipsis/highlight initialization code in the computation because this setup code assumes that the DOM is ready, however at this precise moment we don't know if DOM manipulation has just happened before or is going to happen immediately after.
Fortunately there is a Tracker.afterFlush method which allows us to execute code after concurrent computations are done so we are sure that by that time DOM state is OK.
Having understand all these implications, we can write the following rendered callback :
Template.home.rendered=function(){
// declare a template managed Deps.Computation
this.autorun(function(){
// have this reactive computation depend on the SAME cursor
// that triggers DOM rerendering
var articles=articlesPaginated();
// forEach is actually the method that triggers a dependency on the cursor in this computation
articles.forEach(function(article){
// you can manipulate the model here if needed
});
// setup a callback to execute your DOM alteration code after
// it is actually rerendered by Blaze
Tracker.afterFlush(function(){
// your ellipsis/highlight initialization code goes here
});
});
};
If you can put your Articles into another template, then you could apply formatting individually as they are inserted.
Template.article.rendered = function () {
// Setup ellipsis
this.$('.ellipsis').dotdotdot({
ellipsis: '...',
wrap: 'word',
fallbackToLetter: true,
after: $('a.blog_continue')
});
// Setup highlight.js
this.$('pre code').each(function(i, block) {
hljs.highlightBlock(block);
});
};
Assuming your template looks something like this.
<template name="home">
...
{{#each articlesPaginated}}
{{> article}}
{{/each}}
{{> paginationControls}}
...
</template>
This has the added benefit of scoping the formatting to just the articles, rather than the entire DOM.

Meteor template function not rendering

I am trying to make a template render something on the client; I think I tried everything possible (apart from the correct thing apparently).
Html:
<head>
<title>Groups</title>
</head>
<body>
{{loginButtons}}
{{>TplGroups}}
</body>
<template name="TplGroups">
groups found: {{ GroupCount }}
{{#each GetAllGroups}}
<div> hello, {{name}} group! </div>
{{/each}}
</template>
serverStartup.js:
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Meteor.publish("GroupCount"), function()
{
return Groups.find({});
}
});
}
and the Groups.js collection which exposes the two methods GroupCount and GetAllGroups, which I want to access on client side:
var Groups = new Meteor.Collection("groups");
Groups.insert({name: "John"});
if(Meteor.is_client)
{
Meteor.subscribe("GetAllGroups");
Meteor.subscribe("GroupCount");
Template.TplGroup.GetAllGroups = function()
{
return Groups.find({});
}
Template.TplGroup.GroupCount = function()
{
return Groups.find().count();
}
}
I have removed "insecure" and "autopublish" packages.
Where is my mistake? The two functions won't show on client.
Also what is the difference between declaring the functions as "publish" or declaring them as Template functions?
In browser console I get this:
event.returnValue is deprecated. Please use the standard event.preventDefault() instead. (jquery.js)
The publish method should look more or less like this
Meteor.publish("GetAllGroups", function () {
return Groups.find({});
});
#apendua pointed to the right solution. I took your code and refactored it to make the solution a little clearer:
server.js:
if (Meteor.isServer) {
// Publish groups
Meteor.publish('groups', function() {
return Groups.find();
});
}
groups.js
Groups = new Meteor.Collection('groups');
Groups.insert({name: 'John'});
if (Meteor.isClient) {
// Subscribe to groups
Meteor.subscribe('groups');
Template.TplGroup.GetAllGroups = function() {
return Groups.find();
}
Template.TplGroup.GroupCount = function() {
return Groups.find().count();
}
}
It is enough to publish just groups. In your groups.js you try to subscribe to a publication that does not exist (GetAllGroups). Better to just publish and subscribe to simply 'groups' and return the groups count as described above. Also with a newer version of meteor you should use Meteor.isClient and not Meteor.is_client.
The jQuery error you described is not related to your code and appears (at least what I think) because of some issue with Meteor and/or jQuery itself. Don't worry about that.
oups you just forgot "s" in your template name in your js file :
<template name="TplGroups"> <!-- what you wrote -->
and in your js you wrote :
Template.TplGroup.xxx
instead of :
Template.TplGroups.xxx

meteor and textareas

Ok so I'm not sure why I can't render the code. First if I console.log users.content I get the content I want but I'm some how not able to pass it to a textarea so that it show's it...
Users = new Meteor.Collection("users");
if(Meteor.is_client){
Template.inputUser.code = function(){
var el = Users.find({name:"oscar"});
el.forEach(function(users){
console.log(users.content);
})
}
}
And then on my html template I have
<body>{{> inputUser}}</body>
<template name="inputUser">
<textarea>{{content}}</textarea>
</template>
And I would have a record on the db suck as so
if(Meteor.is_server)
Users.insert({name:"oscar",content:"hello world"})
Thanks for your help guys.
Firstly your method Template.inputUser.code should return something, you should also note that it wouldn't be called with that template either as it needs a {{code}} call in it rather than {{content}}
The second point is database contents are not always available if you have disabled the autopublish package, if so check out using publish(in the server code) and subscribe(in the client code): http://docs.meteor.com/#meteor_subscribe you can use this to check when the client has all the data to display. Something like:
Meteor.subscribe('allusers', function() {
Template.inputUser.code = function(){
var user = Users.findOne({name:"oscar"});
return user.content;
}
});
...
Meteor.publish('allusers', function() {
return Users.find();
});

dynamically inserting templates in meteor

Ok so I've got my template in its own file named myApp.html. My template code is as follows
<template name="initialInsertion">
<div class="greeting">Hello there, {{first}} {{last}}!</div>
</template>
Now I want to insert this template into the DOM upon clicking a button. I've got my button rendered in the DOM and I have a click event tied to it as follows
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
Meteor.ui.render(function () {
$("body").append(Template.initialInsertion({first: "Alyssa", last: "Hacker"}));
})
}
}
Now obviously the $("body").append part is wrong but returning Template.initialInsertion... doesn't insert that template into the DOM. I've tried putting a partia {{> initialInsertion}}but that just errors out because I dont have first and last set yet... any clues?
Thanks guys
In meteor 1.x
'click .zaButton':function(){
Blaze.renderWithData(Template.someTemplate, {my: "data"}, $("#parrent-node")[0])
}
In meteor 0.8.3
'click .zaButton':function(){
var t = UI.renderWithData(Template.someTemplate, {my: "data"})
UI.insert(t, $(".some-parrent-to-append"))
}
Is first and last going into a Meteor.Collection eventually?
If not, the simplest way I know is to put the data into the session:
Template.chooseWhatToDo.events = {
'click .zaButton' : function () {
Session.set('first', 'Alyssa');
Session.set('last', 'Hacker');
}
}
Then you would define:
Template.initialInsertion.first = function () {
return Session.get('first');
}
Template.initialInsertion.last = function () {
return Session.get('last');
}
Template.initialInsertion.has_name = function () {
return Template.initialInsertion.first() && Template.initialInsertion.last();
}
Finally, adjust your .html template like this:
<template name="initialInsertion">
{{#if has_name}}
<div class="greeting">Hello there, {{first}} {{last}}!</div>
{{/if}}
</template>
This is the exact opposite solution to your question, but it seems like the "Meteor way". (Basically, don't worry about manipulating the DOM yourself, just embrace the sessions, collections and template system.) BTW, I'm still new with Meteor, so if this is not the "Meteor way", someone please let me know :-)
I think you may want to use Meteor.render within your append statement. Also, note that if you are passing data into your Template, then you must wrap Template.initialInsertion in an anonymous function, since that's what Meteor.render expects. I'm doing something similar that seems to be working:
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
$("body").append(Meteor.render(function() {
return Template.initialInsertion({first: "Alyssa", last: "Hacker"})
}));
}
}
Hope this helps!
Many answer here are going to have problems with the new Blaze engine. Here is a pattern that works in Meteor 0.8.0 with Blaze.
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
More info in this seccion of Meteor docs

Resources