Meteor: Detect when last {{> template}} finishes rendering in {{#each}} - meteor

EDIT: A general solution has been posted in answers!
I am currently using Masonry to position some {{> card}} templates.
{{#each searchResults}}
{{> card}}
{{/each}}
{{searchResults}} returns Session.get("searchResults"), which changes upon a new search. The page then re-renders with new {{> card}} templates.
I am having trouble getting my masonry to fire at the right time:
Template.search.onRendered(function(){
this.autorun(function(){
var tracking = Session.get("searchResults");
// ...masonry code here...
})
});
Currently, the masonry code fires right after Session.get("searchResults") changes, but BEFORE the {{> card}} templates are rendered or exists. In effect, masonry positions zero elements, then the elements get rendered (and remain in the wrong position).
Updated question: How do I call masonry when the last {{> card}} template is updated? (note: I can attach onRendered to the card template, but onRendered only fires when the card is first created, not when updated)

General explanation:
In your object array, mark last item with lastItem attribute set to true.
In your HTML, render a {{> template}} for each array object.
Use onRendered to look for a lastItem attribute.
If it exists, then the last element has been reached!
Example with my code:
Mark the last item in array:
cards[cards.length - 1].lastItem = true;
Add helper that returns array:
Template.search.helpers({
cards: function(){
return cards;
}
}
In search template, render template for each object:
{{#each cards}}
{{> card}}
{{/each}}
Use onRendered to detect last item.
Template.card.onRendered(function(){
if(this.data.lastItem){
//run masonry code here!
}
});

Related

Meteor Blaze : dont wait for child template rendering before rendering parent

playing with Blaze i realized the following :
if i have a parent template where i include a child template with {{> child_template }}
then Blaze will wait for child template to be rendered before rendering parent. This can be good in some cases but not all.
For e.g if my parent template contains a <h1>Welcome to my page</h1> and child a list of 10 000 items. I would like a way to display <h1> asap and wait for the 10 000 items to appear later
what i'm doing currently to manage that is the following :
Template.parent.onRendered(function(){
Blaze.render(Template.child, document.body);
});
it is working but i wonder if anyone have a better solution for this problem that seems pretty common. thanks
You could pass a custom boolean argument to the child component that's false by default, but the parent component's onRendered sets it true. And the child component should check this argument and not render anything unless it's true.
Template.parent.onCreated(function() {
this.state = new ReactiveDict();
this.state.setDefault({
"canRender": false,
});
}
Template.parent.onRendered(function() {
this.state.set("canRender", true);
}
Template.parent.helpers({
canRender() {
return Template.instance().state.get("canRender");
}
});
Pass the state to the child component:
<template name="parent">
{{>child canRender=canRender}}
</template>
<template name="child">
{{#if canRender}}
<p>Millions of items go here.</p>
{{/if}}
</template>
As you say, your Child Template has a list of 10000 items. so, this means you have subscribed to some collection. You can us below code to solve your issue.
<template name="Parent">
<div>
<h1>Welcome to my page</h1>
</div>
{{#if Template.subscriptionsReady}}
{{> Child}}
{{else}}
<p>Loading Child Items...</p>
{{/if}}
</template>

How to force Meteor to rerender immediately, or prioritize reactive update?

I'm experimenting with building my own loading spinner and the way I have it set up is with a helper that check if entry arrays are empty.
{{#if viewThisPage}}
{{#if noEntriesLoaded}}
<div class="loadingSpinner">...</div>
{{/if}}
{{#each entry}}
{{ > myTemplate}}
{{/each}}
{{/if}}
Initially viewThisPage is false. When viewThisPage is set to true I query mongoDB for entries. The issue that I'm seeing from benchmarking is that the query goes so fast that all the DOM update events are squished together so that the spinner never gets rendered, in other words, noEntriesLoaded is true for too short a time.
There is, however, around a 1200ms lag between triggering viewThisPage and the page actually rendering. I want the spinner to display during this period of lag.
Three ways I've thought of to fix this
Somehow force meteor to rerender the section immediately after setting viewThisPage while entries haven't loaded yet.
Somehow prioritize the reactive response to viewThisPage so that it updates before the entires are loaded.
Add fake lag using setTimeout() on the block that adds entries. This works and I get to see my pretty loading spinner but is the dumbest idea ever.
You can show spinner while your entries are being rendering.
This code will hide loadingSpinner only when all entries are rendered:
meteor add reactive-var
<template name="Main">
{{#if viewThisPage}}
{{#if entriesAreNotReady}}
<div class="loadingSpinner">...</div>
{{/if}}
{{> Entries entry=entry onRendered=onEntriesRendered}}
{{/if}}
</template>
<template name="Entries">
{{#each entry}}
{{> myTemplate}}
{{/each}}
</template>
Template.Main.onCreated(function() {
this.areEntriesRendered = new ReactiveVar(false);
});
Template.Main.helpers({
onEntriesRendered: function() {
var areEntriesRendered = Template.instance().areEntriesRendered;
return function() {
areEntriesRendered.set(true);
};
},
entriesAreNotReady: function() {
return ! Template.instance().areEntriesRendered.get();
}
});
Template.Entries.onRendered(function() {
this.data.onRendered();
});
P.S. Possibly you will need to check that entries are loaded before you render Entries template

How to properly find a conditionally rendered element from onRendered callback?

I’d like to find a conditionally rendered element after the template is rendered.
The template:
<template name="one">
<div class="normal">Normal</div>
{{#if active}}
<div class="conditional">Conditional</div>
{{/if}}
</template>
The code:
Template.one.onRendered(function() {
console.log(this.find(".normal"));
console.log(this.find(".conditional");
}
The above will log the ‘.normal’ but null for the ‘.conditional’ (the condition is true, both elements are present in the final DOM). The reason for that is well documented: onRender runs before {{#if}}s. But I want to run after the whole template is rendered, with all its {{#if}}s.
Is there a way to find that .conditional without making it a separate template?
I'm facing the same issue. This is because what's inside the {{#if}} block is rendered as a dynamically generated template.
So, the best workaround I've found so far is to render a specific template inside the {{#if}} block and add a callback to this specific template:
Here's what you should do:
<template name="one">
<div class="normal">Normal</div>
{{#if active}}
{{> activeTemplate}}
{{/if}}
</template>
<template name="activeTemplate">
<div class="conditional">Conditional</div>
</template>
Template.activeTemplate.onRendered(function() {
console.log(this.find(".conditional");
}
You won't find the DOM element because it doesn't exist.
It doesn't exist because active is false.
If you want active to return true:
Template.one.helpers({
"active": function() { return true; }
})

Meteor Block Helper that acts like a template

Here's what I want, a custom block helper that can act like a template, monitoring for it's own events etc. The html would look like this:
{{#expandable}}
{{#if expanded}}
Content!!!
<div id="toggle-area"></div>
{{else}}
<div id="toggle-area"></div>
{{/if}}
{{/expandable}}
And here's some javascript I have put together. This would work if I just declared the above as a template, but I want it to apply to whatever input is given to that expandable block helper.
Template.expandableView.created = function() {
this.data._isExpanded = false;
this.data._isExpandedDep = new Deps.Dependency();
}
Template.expandableView.events({
'click .toggle-area': function(e, t) {
t.data._isExpanded = !t.data._isExpanded;
t.data._isExpandedDep.changed();
}
});
Template.expandableView.expanded = function() {
this._isExpandedDep.depend();
return this._isExpanded;
};
I know I can declare block helpers with syntax like this:
Handlebars.registerHelper('expandable', function() {
var contents = options.fn(this);
// boring block helper that unconditionally returns the content
return contents;
});
But that wouldn't have the template behavior.
Thanks in advance! This might not be really possible with the current Meteor implementation.
Update
The implementation given by HubertOG is super cool, but the expanded helper isn't accessible from within the content below:
<template name="expandableView">
{{expanded}} <!-- this works correctly -->
{{content}}
</template>
<!-- in an appropriate 'home' template -->
{{#expandable}}
{{expanded}} <!-- this doesn't work correctly. Expanded is undefined. -->
<button class="toggle-thing">Toggle</button>
{{#if expanded}}
Content is showing!
{{else}}
Nope....
{{/if}}
{{/expandable}}
In the actual block helper, expanded is undefined, since the real thing is a level up in the context. I tried things like {{../expanded}} and {{this.expanded}}, but to no avail.
Strangely, the event handler is correctly wired up.... it fires when I click that button, but the expanded helper is simply never called from within the content, so even console.log() calls are never fired.
Meteor's new Blaze template engine solves this problem quite nicely.
Here's an excerpt from the Blaze docs showing how they can be used.
Definition:
<template name="ifEven">
{{#if isEven value}}
{{> UI.contentBlock}}
{{else}}
{{> UI.elseBlock}}
{{/if}}
</template>
Template.ifEven.isEven = function (value) {
return (value % 2) === 0;
}
Usage:
{{#ifEven value=2}}
2 is even
{{else}}
2 is odd
{{/ifEven}}
You can achieve this by making a helper that returns a template, and passing the helper options as a data to that template.
First, make your helper template:
<template name="helperTemplate">
<div class="this-is-a-helper-box">
<p>I'm a helper!</p>
{{helperContents}}
</div>
</template>
This will work as a typical template, i.e. it can respond to events:
Template.helperTemplate.events({
'click .click-me': function(e, t) {
alert('CLICK!');
},
});
Finally, make a helper that will return this template.
Handlebars.registerHelper('blockHelper', function(options) {
return new Handlebars.SafeString(Template.helperTemplate({
helperContents: new Handlebars.SafeString(options.fn(this)),
}));
});
The helper options are passed as a helperContents param inside the template data. We used that param in the template to display the contents. Notice also that you need to wrap the returned HTML code in Handlebars.SafeString, both in the case of the template helper and its data.
Then you can use it just as intended:
<template name="example">
{{#blockHelper}}
Blah blah blah
<div class="click-me">CLICK</div>
{{/blockHelper}}
</template>

Meteor template: Pass a parameter into each sub template, and retrieve it in the sub-template helper

I am trying to figure out how to pass a parameter into a sub-template that is in an each block and use the parameter in the sub-template as well as sub-template helper. Here is what I tried so far:
template:
<template name="parent">
{{#each nodes }}
{{> child myParam}}
{{/each}}
</template>
<template name="child">
{{ paramName }}
</template>
js:
Template.parent.nodes = function() {
//return a list
};
Template.parent.myParam = function() {
return {"paramName" : "paramValue"};
};
Template.child.someOtherHelper = function() {
//How do I get access to the "paramName" parameter?
}
So far, it hasn't been working, and it seems somehow mess up my input node list also.
Thanks for help.
When you use {{> child myParam}}, it's calling the child template and associates myParam as current template data context, meaning that in the template you can reference {{paramName}}.
In someOtherHelper you could use this.paramName to retrieve "paramValue".
However, when you're using {{#each nodes}}{{> child}}{{/each}}, it means that you pass the content of the current list item (fetched from a LocalCursor or directly an array item) as the template data of child, and you can reference the list item properties using {{field}} in html or this.field in js.
What's happening here is when you call {{> child myParam}}, the myParam helper content OVERWRITES the current node item as template data, that's why it's messing your node list.
A quick (dirty) trick would be to simply extend the myParam helper so that it also contains the template data from the {{#each}} block.
Template.parent.helpers({
nodes:function(){
// simulate typical collection cursor fetch result
return [{_id:"A"},{_id:"B"},{_id:"C"}];
},
myParam:function(){
// here, this equals the current node item
// so we _.extend our param with it
return _.extend({paramName:"paramValue"},this);
}
});
Template.child.helpers({
someOtherHelper:function(){
return "_id : "+this._id+" ; paramName : "+this.paramName;
}
});
<template name="parent">
{{#each nodes}}
{{> child myParam}}
{{/each}}
</template>
<template name="child">
{{! this is going to output the same stuff}}
<div>_id : {{_id}} ; paramName : {{paramName}}</div>
<div>{{someOtherHelper}}</div>
</template>
Depending on what you're precisely trying to achieve, there might be a better approach but this one gets the job done at least.

Resources