Passing parameters to helper in Meteor's dynamic templates - javascript

I realise that this has been brought up before, and that Template.dynamic isn't designed to take in a parameter if its template parameter is a helper.
But here is what I would like to do:
// a global helper that composites the template's name using domain-specific and global parameters
Template.registerHelper('templateName', function (name) {
return name + Session.get('someVariable');
});
<!-- use case: a template calling two dynamic ones -->
<template name="someTemplate">
<div class="some-class">
{{> Template.dynamic template=templateName 'title' }}
</div>
<div class="another-class">
{{> Template.dynamic template=templateName 'content' }}
</div>
</template>
This pattern is extremely DRY and it avoids having to set up nested conditionals and rewrite quasi-identical templates each with minimal changes.
Right now, I've got this:
Template.registerHelper('templateName', function () {
var dt = this.dName || Template.parentData().dName;
return dName + Session.get('someVariable'));
});
<template name="someTemplate">
{{> segment dName="title"}}
{{> segment dName="content"}}
</template>
<template name="segment">
<div class="some-class">
{{> Template.dynamic template=templateName }}
</div>
</template>
It works, but it isn't ideal, because;
confusion-prone need to include the parameter for the dynamic template's name in the parent template's call
only ever being allowed one Template.dynamic per template due to one parameter, leading to scalability issues
putting the dName parameter in the template's data context is mixed in with local data, requiring the hacky check whether it's accessible in the current one or the parent's
any further complexity in the DOM requires lots of nested conditionals for parameters or many slightly different static templates, leading to bloat
Are there plans to add this functionality? Am I going about this the wrong way? Did anyone else run into these issues?
Thanks for reading.

There is a trick to doing this with {{with}}. See here:
https://forums.meteor.com/t/pass-argument-to-helper-in-template-dynamic-call/3971

Related

New to Meteor and helper function is not working

I am learning meteor and I cannot get my helper function to return some static text.
<head>
<title>LeaderBoard</title>
</head>
<body>
<h1>Leaderboard</h1>
<p>{{player}}</p>
</body>
in JS
if(Meteor.isClient){
Template.leaderboard.helpers({
player: function(){
return "text";
}
});
}
This only returns the Leaderboard header
UPDATE:
changed to:
LeaderBoard
<body>
<h1>Leaderboard</h1>
<p>{{player}}</p>
</body>
<template name="leaderboard">
{{player}}
</template>
and JS is still the same and it still does not work
So, there are few mistakes you made. Let's deconstruct it.
What is a template?
A template is a piece of code that renders into DOM and can be manipulated using helpers, events and such. For you to use any template, there has to exist one. They can either be put into your app from packages or made by yourself. In this particular case, you're looking for the latter.
To define a template, pick any HTML file or create a new one and define it in HTML way:
<template name="theTemplate">
Hello, I am the template.
</template>
So now you can inject this template wherever in the DOM you want, using this syntax:
<body>
<h1>My super app</h1>
<div>{{> theTemplate}}</div>
</body>
It will render into
<body>
<h1>My super app</h1>
<div>
Hello, I am the template.
</div>
</body>
or, in fact, something a bit uglier since Meteor preserves all the indentation and stuff.
How can I put changeable text into the template?
You're already right that you need helpers for that. A helper is a function that returns an Object (be it String, Number, etc.) which is being injected as is, as if it was document.writed.
Helpers for any template are defined in this way:
Template.theTemplate.helpers({
coolestString: function () {
return 'I am the coolest string put by a helper.';
}
});
Note that Template object contains theTemplate property. It happened exactly after Meteor picked up your template definition and then stored it into an object with helpers method (and a bunch of other useful methods, too).
If you remove theTemplate template definition (aka HTML), the Template object will not have its theTemplate property, and the whole thing will throw a TypeError since you try to access a property of undefined.
How do I put values returned by helpers into the template?
Simply use {{ ... }} syntax. Say, you have a helper coolestString and you need to fetch value from it, whatever it is, and put into h1 tag:
<template name="theTemplate">
<h1>{{ coolestString }}</h1>
</template>
Note the difference between {{> ...}} and {{ ...}}. The former inject a template, the latter inject a value from current context; template's helpers stay within its root context (or just forget it if you don't understand contexts yet).
So, what should I do to use template in my app?
To make a conclusion,
Define a template.
Optionally, define its helpers. Each helper should return a string, a number, an array or an object.
Access helpers' values within the template using {{ ... }} syntax.
Inject the template into your document using {{> ...}} syntax.
That's it.
Okay, show me the whole code!
In myCoolestApp.html,
<body>
{{> theTemplate}}
</body>
<template name="theTemplate">
{{ coolestAppName }}
</template>
And in myCoolestApp.js,
if (Meteor.isClient()) {
Template.theTemplate.helpers({
coolestAppName: function () {
return 'My super cool app!';
}
});
}
Done!
But what if I want to omit template?
In general, a helper by definition belongs to some template, so the hierarchy of injection is the body, then the template, then the helper. But it is possible to inject a helper right into document body and omit intermediary template. You do so with Template.registerHelper method:
Template.registerHelper('theHelper', function () {
return 'I am helper'; // add some logic here and see how it works; hint: reactively.
});
What you do then is just put it into your document:
<body>
{{ theHelper }}
</body>
which gets rendered to
<body>
I am helper
</body>
The principle behind Template.registerHelper is DRY, don't repeat yourself. Sometimes you need to provide exactly same data to more than one template, so at first you would think you have to copy helpers code. But this method helps avoid unnecessary repetition.
You can use more complex objects, covered with more complex logic, this way, or you can even put Mongo collections into the document directly.
Option 1
In case you dont have multiple pages/screens for your app. Edit your template html like this.
<head>
<title>LeaderBoard</title>
</head>
<body>
<h1>Leaderboard</h1>
{{> leaderboard}}
</body>
<template name="leaderboard">
{{player}}
</template>
PS:- {{player}} refers to the template helper "player" and {{> leaderboard}} refers to a template ( This is handlebar syntax ).
Option2 : Your template should look like this.(Assuming you have multiple pages/screens for you app - it would be better if you use some kind of router)
A main layout page - call it master.html
<head>
<title>LeaderBoard</title>
</head>
<body>
</body>
A template named leaderboard. call it leaderboard.html
<template name="leaderboard">
{{player}}
</template>
Then your helper with the same code that you provided in the question.
This should work.

Best way to initialize jquery plugin on DOM element in multiple templates

I'm trying to use chosen on some form elements that are in various templates throughout a meteor app. For example:
HTML
<template name="personSettings">
<!-- some code -->
<template name="chosenSelect">
<select class="chosen">
<!-- options -->
</select>
</template>
<!-- some code -->
</template>
JS
Template.chosenSelect.rendered = function() {
$('.chosen').chosen()
}
Note that the JS at this point is irrelevant, because I get the error:
While building the application:
client/views/personSettings.html:27: Expected "template" end tag
... </select>
I thought you could nest templates, so I'm not sure what the problem is. However, if it turns out I cannot nest templates, the question remains:
What is the best way to initiaize a jquery plugin on a DOM element (by class), that exists in multiple templates?
You can indeed nest templates but your syntax is wrong :
HTML
<template name="chosenSelect">
<select class="chosen">
<!-- options -->
</select>
</template>
<template name="personSettings">
<!-- some code -->
{{> chosenSelect}}
<!-- some code -->
</template>
You must always declare templates within a single pair of matching template tags, if you need to call another template from within your template markup, use the template invokation / inclusion syntax {{> myTemplate}}.
JS
Template.chosenSelect.onRendered(function() {
this.$(".chosen").chosen()
});
When it comes to initializng jQuery plugins within template instances, use the onRendered lifecycle event and prefer using scoped jQuery object (this.$).

Polymer, core-ajax shared response

Using Polymer, I am attempting to instantiate several ajax-service elements using template binding, <template repeat=...>.
Code is as follows:
<template repeat="{{viewName, i in views}}">
<section hash={{viewName}} layout vertical center-center on-tap={{closeOpenDrawer}}>
<core-ajax id="ajaxService" auto response={{list}}" url="../componentsItems/demo-components.json"></core-ajax>
<template repeat="{{element, j in list}}">
<workspace-elem class="dropped" name="{{element.name}}"></workspace-elem>
</template>
</section>
</template>
The problem is, each ajax response is concatenated to a shared list variable, rather then instantiating its own local list variable per repeated template, so when the sub template is triggered, it generates <workspace-elem>s in each section for the sum of data from all ajax calls.
Is there an easy way to solve this? Is there something I am over looking?
EDIT:
Same sort of problem occurs with the inner template. Each instantiated inner template shares the list variable, if anything is pushed to template.model.list, all instantiated template models are updated.
When you use response={{list}} in the template, you are not creating that variable, but you are binding the value of the response attribute to an existing variable called ‘list’.
The question you need to ask yourself is: ‘Where does the “list” variable that I'm binding to come from?’ It is not obvious from the snippet you mention, but it's very likely that it's coming from some enclosing custom element, so it's only natural that it will be shared between the iterations of the template (though I'm surprised that it get concatenated instead of overwritten…). I think a good solution would be to encapsulate the code you have in the outer template in a custom element to hold the variable:
<polymer-element name="my-element" attributes="viewName">
<template>
<!--Your original code starts here-->
<section hash={{viewName}} layout vertical center-center>
<core-ajax id="ajaxService" auto response={{list}}" url="../componentsItems/demo-components.json"></core-ajax>
<template repeat="{{element, j in list}}">
<workspace-elem class="dropped" name="{{element.name}}"></workspace-elem>
</template>
</section>
<!--Your original code ends here-->
</template>
<script>
Polymer({
publish: {
list: null
},
created: function() {
// Create your array on instantiation of an element
this.list = [];
}
}
</script>
</polymer-element>
<template repeat="{{viewName, i in views}}">
<my-element on-tap={{closeOpenDrawer}}></my-element>
</template>
I think that should solve your problem. Alternatively, I think it might help to make the outer template an auto-binding template(‘is="auto-binding"’). Then the model of the template would be the template itself, but since I have not used this facility very often, I'm not quite sure (it might be that you're then loosing the ability to bind to ‘views’).

Meteor: Re-use template with different data

I've got a few templates which list items (log lines, users) and style them accordingly in tables. Now I've added a "search" page which searches for a string in different item types and displays them on one page. In terms of layout, I want to reuse the original templates, but obviously with the data returned by the search.
How can I do that without duplicating the template itself in the HTML file?
Put another way: How can I use the actual data from the top-level template in the sub-templates.
Small example:
<template name="user_list">
{{#each user_list}}
</template>
<template name="users">
{{> user_list}}
</template>
<template name="search">
{{> user_list}}
{{> group_list}}
</template>
In .coffee:
Template.users.user_list = ->
[a,b,c,d]
Template.search.user_list = ->
[b,c]
Maybe this is an easy one, which would show how little I really understood about Meteor.
Just put the template you want to use inside the {{#each}} statement. Then each item in the array will be the context for that inner template.
HTML file
<template name="users">
<ul>
{{#each UserArr}}
{{> userItem}}
{{/each}}
</ul>
</template>
<template name="userItem">
<li>{{username}} {{profile.name}}</li>
</template>
JS file
Template.users.UserArr = function()
{
return Meteor.users.find({});
};
another solution is to put your request in a Session attribute and check for its existence when querying for the users. By default Session.get("userQuery") would return undefined but once you enter something in the search field you change that with Session.set("userQuery", {type: "something"})
Now in the coffeescript you could say:
Template.users.user_list = ->
if(Session.get("userQuery"))
[a,b,c,d]
else
[b,c]
or alternatively use a MiniMongo query because it is much nicer :-)
the nice thing is that your HTML will rerender because it is reactive to the Session's content.

Dynamically render partial templates using mustache

Is there a way to dynamically inject partial templates (and have it work the same way in both Ruby & Javascript)? Basically, I'm trying to render different types of objects in a list.
The best I can come up with is this:
<div class="items">
{{#items}}
<div class="item">
{{#is_message}}
{{> message}}
{{/is_message}}
{{#is_picture}}
{{> picture}}
{{/is_picture}}
</div>
{{/items}}
</div>
I'm not super-psyched about this approach.
Is there a better way?
Also note that the different types of models for the views can have non-similar fields. I suppose I could always go to the lowest common denominator and have the data hash contain the html, however I would rather use the mustache templates.
I did the same thing you did, and for each property type i needed a dynamic partial, I just set a dynamic variable in the js data model that's being rendered in the template...
eval("this.set({is_" + this.get("propertyType") + ": true})")
or
this["is_" + propertyType] = true
At least I don't have to manually set the 'is_whatever' variable...
It would be cool if mustache.js or ICanHaz.js had some clever syntax for dynamic properties inside of mustache tags... maybe something like this:
{{>{{message}} }}

Categories