Polymorphism in Meteor Templates - meteor

Say I have a Meteor application which displays widgets. A widget can have one of several forms:
<template name="textWidget">
<h1>{{myTitle}}</h1>
<p>{{myTextContent}}</p>
</template>
<template name="imgWidget">
<h1>{{myTitle}}</h1>
<img src="{{myImagePath}}" />
</template>
<template name="listWidget">
<h1>{{myTitle}}</h1>
<ul>
{{#each items}}
{{> listWidgetItem}}
{{/each}}
</ul>
</template>
Is there a good pattern for rendering a specific template given a record type?
Naively, I can do something like:
<template name="masterTemplate">
{{#each widgets}}
{{#if widgetType "text"}}
{{> textWidget}}
{{else}}
{{#if widgetType "img"}}
{{> imgWidget}}
{{else}}
{{if ... }} ... {{/if}}
{{/if}}
{{#/if}}
{{/each}}
</template>
With a helper like:
Template.masterTemplate.widgetType = function(cmp) {
return cmp === this.data.type;
};
This seems like a very clunky and unflexible way to operate though. Am I missing an obvious library or design pattern for this use case?

You can try something like this:
UI.registerHelper('widget', function () {
var templateName = this.data.type + 'Widget';
return Template[templateName].extend({ data: this });
});
and you can use it as follows:
{{> widget context}}
or
{{#with context}}
{{> widget}}
{{/with}}

Related

meteor blaze layout how to pass nested template with parameter when render

I have a template:
<template name="outerTemplateName">
...
{{> Template.dynamic template=inner}}
...
</template>
However, the template that i want to render as inner template also have a dynamic template inside it:
<template name="innerTemplateName">
...
{{> Template.dynamic template=anotherInner}}
...
</template>
Is it possible to use BlazeLayout.render() to achieve something like the template below?
<template name="outerTemplateName">
...
{{> innerTemplateName anotherInner="anotherInnerTemplateName"}}
...
</template>
I don't know about passing Templates to other Templates but you can dynamically switch Templates by passing a String like this.
<template name="outerTemplate">
{{> innerTemplate childTemplateName="templateA"}}
</template>
<template name="innerTemplate">
{{#if showTemplate 'templateA' }}
{{> templateA }}
{{/if}}
{{#if showTemplate 'templateB' }}
{{> templateB }}
{{/if}}
</template>
<template name="templateA">
<h1>I am templateA</h1>
</template>
<template name="templateB">
<h1>I am templateB</h1>
</template>
Template Helper
Template.innerTemplate.helpers({
showTemplate : function (templateName) {
return templateName === this.childTemplateName;
}
});

Meteor show and hide Template on click

I want to show one template instead of another template on a button click. Similar to the "add comment" link in a StackOverFlow question. In the below example I want to replace {{> newCommentLink}} with {{> postEditor}}
<template name="main">
{{#each posts}}
{{> posts}}
{{/each}}
</template>
<template name="posts">
{{#each comments}}
{{> comment}}
{{/each}}
{{> newCommentLink}}
</template>
If this is not possible, is there any other way?
You can use a reactive variable to determine which template to show:
Template.posts.onCreated(function() {
this.newCommentLink = new ReactiveVar(true);
});
Template.posts.helpers({
shouldShowNewCommentLink() {
return Template.instance().newCommentLink.get();
}
});
Template.posts.events({
'click button': function(event, template) {
template.newCommentLink.set(!template.newCommentLink.get());
}
});
In template:
<template name="posts">
{{#each comments}}
{{> comment}}
{{/each}}
<button class="btn btn-default">Click me to switch...</button>
{{#if shouldShowNewCommentLink}}
{{> newCommentLink}}
{{else}}
{{>postEditor}}
{{/if}}
</template>

I lose data context when i generate template with parameters

when I generate subtemplate in #each helper and i add parameter, then i lose data context, what is normally visible.
I found workaround by passing data fields to template by
{{> productItem parameter="test" name=name details=details}}
, but for more complicated collections that would be very tiresome... isn't there better option to solve that problem ?
<template name="main">
{{#each products}}
{{> productItem parameter="test"}}
{{/each}}
</template>
<template name="productItem">
<div class="product">
<p>{{name}}</p>
<p>{{details}}</p>
<p>{{parameter}}</p>
</div>
</template>
And javascript :
Template.main.helpers({
products: function(){
return Products.find({});
}
});
you are creating a new context ( it doesn't magically merge everything ), but its easy enough to include the original context.
you go :-
{{> productItem product=this parameter="test"}}
then
<template name="productItem">
<div class="product">
<p>{{product.name}}</p>
<p>{{product.details}}</p>
<p>{{parameter}}</p>
</div>
</template>
or
<template name="productItem">
<div class="product">
{{#with product}}
<p>{{name}}</p>
<p>{{details}}</p>
{{/with}}
<p>{{parameter}}</p>
</div>
</template>

Fastest way to check whether the cursor returned by a template helper is empty?

I often do something like this, using the items helper twice:
{{#if items}}
<h1>Items</h1>
{{#each items}}
{{> item}}
{{/each}}
{{/if}}
Template.foo.helpers
items: ->
Items.find
bar: true
,
sort: created: -1
transform: (item) ->
i.good = true
i
Is Meteor doing extra work in this scenario? Would it be more efficient to switch the if to use something like areItems?
areItems: ->
Items.find
bar: true
.count() > 0
You can use {{else}}
{{#each this}}
{{> item}}
{{else}}
<h1>No Items</h1>
{{/each}}
In the template, you can use {{#with items}} and then either 'this.count' or 'this.length' to check whether your helper returned any items.
Use this.count if 'items' is a cursor, e.g. the result of a find() operation:
{{#with items}}
{{#if this.count}}
<h1>Items</h1>
{{#each this}}
{{> item}}
{{/each}}
{{/if}}
{{/with}}
Use this.length if 'items' is an array:
{{#with items}}
{{#if this.length}}
<h1>Items</h1>
{{#each this}}
{{> item}}
{{/each}}
{{/if}}
{{/with}}
Use #with, #if this.length, and .fetch:
{{#with items}}
{{#if this.length}}
<h1>Items</h1>
{{#each this}}
{{> item}}
{{/each}}
{{/if}}
{{/with}}
Template.foo.helpers
items: ->
Items.find
bar: true
,
sort: created: -1
transform: (item) ->
i.good = true
i
.fetch()
You can do what you want using spacebars' #with block tag.
Like this:
{{#with items}}
{{#if this.count}}<h1>Items</h1>{{/if}}
{{#each this}}
{{> item}}
{{/each}}
{{/with}}
The block tag is documented here. The relevant quote is:
If the argument to #with is falsy (by the same rules as for #if), the content is not rendered.
update: fixed code for desired behaviour. Also while my example demonstrates making one helper call it is better practice to make an 'itemList' template and include it by using {{> itemList items}}.
I found that simply {{#if items.count}} was sufficient.
{{#if items.count}}
<h2>Below there are items</h2>
{{/if}}
{{#each items}}
<div class="item-name">{{this.name}}</div>
{{else}}
<h2>There are no items</h2>
{{else}}

dynamically choose the right template to use

I am going through a list of posts and choose the right html handlebar template depending on the type of content( image, text, twitter post). This becomes rather ugly with more and more template types though:
<template name="postItem">
{{#if isType "image"}}
{{#if isSinglePost}}
{{>postImageSingle}}
{{else}}
{{>postImage}}
{{/if}}
{{/if}}
{{#if isType "rich"}}
{{#if isSinglePost}}
{{>postRichSingle}}
{{else}}
{{>postRich}}
{{/if}}
{{/if}}
{{#if isType "video"}}
{{#if isSinglePost}}
{{>postRichSingle}}
{{else}}
{{>postRich}}
{{/if}}
{{/if}}
{{#if isType "file"}}
{{#if isMimeType "audio/wav"}}
{{>postAudio}}
{{else}}
{{>postFile}}
{{/if}}
{{/if}}
{{#if isType "link"}}
{{#if isProviderName this "Twitter"}}
{{>postTwitter}}
{{else}}
{{#if isSinglePost }}
{{>postLinkSingle}}
{{else}}
{{>postLink}}
{{/if}}
{{/if}}
{{/if}}
{{#if isType "preview"}}
{{>postPreview}}
{{/if}}
{{#if isType "photo"}}
{{>postImage}}
{{/if}}
</template>
It would be better to move the logic into a helper function, but what I struggle with is how I could return the name of the template to use from the helper function.
{{>getTemplateName}}
Template.postItem.getTemplateName = function () {
return postImage;
};
but this of course gives me:
Exception from Deps recompute: Error: No such template 'getTemplateName'
The {{> template}} syntax is for inserting templates only, while for helpers you use {{helper}}, without the angle bracket >. Remove the bracket from your helper invocation, and render the needed subtemplate inside the helper:
Template.postItem.getTemplateName = function() {
return new Handlebars.safeString(Template.postImage());
};

Resources