Handlebars: inherited/implicit parameters - handlebars.js

Is there an easy way how to pass a "configuration" parameter to all embedded partials in handlebars?
In my case, I would like to set language by the page layout partial and would like all the embedded partials to have access to it, something like this:
#layout-en.hbs:
SOMEHOW SETTING LANG PARAMETER TO "EN"
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
</head>
<body>
<p>Top of all English pages</p>
{{{content}}}
<p>Footer of all English pages</p>
</body>
</html>
#layout-fr.hbs - analogous
some-page-en.hbs:
{{#> #layout_en title="Title in English"}}
some content in English
{{#> demo}}
some more content in English
{{/#layout_en}}
some-page-fr.hbs:
{{#> #layout_fr title="Title in French"}}
some content in French
{{#> demo}}
some more content in French
{{/#layout_fr}}
Is it possible for demo to return a button with a different text when included on a page using layout_en and different when on a page using layout_fr?
Or should this be done completely differently?

It's possible to set a variable that is accessible to all views and partials that are rendered in the response. I am doubtful that such a variable could be set from a partial and, even if it could, I would recommend against it because I think it would make the application more difficult to troubleshoot if global variables were being set in views.
Instead, I think a better approach would be to set such a variable in your controller logic.
You have not mentioned what Node web server framework you are using, but I am going to assume you are using express with the express-handlebars package.
The express API provides a locals object on the response (res), to which variables can be attached and made available to all rendered templates.
A simple example of setting a global lang variable within our express handlers looks like:
app.get("/en", (req, res) => {
res.locals.lang = "en";
res.render("some-page-en");
});
app.get("/fr", (req, res) => {
res.locals.lang = "fr";
res.render("some-page-fr");
});
This will allow us to use {{lang}} from within any rendered view/layout/partial and the corresponding value will be rendered.
The problem with this approach is that it does allow for simple conditions within our template of the sort lang === "fr" ? "French content" : "English content". This is because Handlebars does not ship with a way to perform such a conditional check. A custom helper could help, however, if your application will only support the two languages - English and French - then we could replace our lang string variable with a boolean one - ex., isFrench:
app.get("/en", (req, res) => {
res.locals.isFrench = false;
res.render("some-page-en");
});
app.get("/fr", (req, res) => {
res.locals.isFrench = true;
res.render("some-page-fr");
});
As a boolean, this variable can be used with a Handlebars #if helper. The demo partial could then look something like:
<button type="button">
{{#if isFrench}}
Cliquez ici
{{else}}
Click here
{{/if}}
</button>
Additionally, I would recommend using a similar pattern within a single layout file instead of potentially having an English and a French layout with mostly duplicated HTML.
Additional note:
{{#> demo}} on its own is not valid Handlebars syntax. The #> is for rendering Partial Blocks and those must have an accompanying closing tag: {{/demo}}. A regular (non-Block) partial would be rendered with {{> demo}}.

Related

Can Svelte be used for templating like Handlebars?

My objective is to let end-users build some customization in my app. Can I do something like this? I know this is sometimes also referred to as liquid templates, similar to how handlebars.js works.
app.svelte
<script>
let name = 'world';
const template = '<h1> Hello {name} </h1>'
</script>
{#html template}
I'm sorry if this is already answered, but I could not find it.
Link to REPL.
This is a little bit hacky but it will do the trick:
<script>
let name = 'world';
let template;
</script>
<div class="d-none" bind:this={template}>
<h1>Hello {name}</h1>
</div>
{#html template?.innerHTML}
<style>
.d-none {
display: none;
}
</style>
If the template should be a string one solution might be to simply replace the {variable} with the values before displaying via a {#html} element >> REPL
Notice the warning in the SVELTE docs
Svelte does not sanitize expressions before injecting HTML. If the data comes from an untrusted source, you must sanitize it, or you are exposing your users to an XSS vulnerability.
(Inside a component $$props could be used to get the passed values in one object if they were handled seperately)
<script>
const props = {
greeting: 'Hello',
name: 'world'
}
let template = '<h1> {greeting} {name} </h1>'
let filledTemplate = Object.entries(props).reduce((template, [key,value]) => {
return template.replaceAll(`{${key}}`, value)
},template)
</script>
{#html filledTemplate}
Previous solution without string
To achieve this I would build a Component for every template and use a <svelte:component> element and a switch to display the selected one > REPL
[App.svelte]
<script>
import Template1 from './Template1.svelte'
import Template2 from './Template2.svelte'
let selectedTemplate = 'template1'
const stringToComponent = (str) => {
switch(str) {
case 'template1':
return Template1
case 'template2':
return Template2
}
}
</script>
<button on:click={() => selectedTemplate = 'template1'}>Template1</button>
<button on:click={() => selectedTemplate = 'template2'}>Template2</button>
<svelte:component this={stringToComponent(selectedTemplate)} adjective={'nice'}/>
[Template.svelte]
<script>
export let adjective
</script>
<hr>
<h1>This is a {adjective} template</h1>
<hr>
Well, you could do it, but that not what Svelte was designed for.
Svelte was designed to compile the template at build time.
I'd recommend using a template engine (like handlebars) for your use case.
A. Using Handlebars inside Svelte REPL:
<script>
import Handlebars from 'handlebars';
let name = 'world';
const template = "<h1> Hello {{name}} </h1>";
$: renderTemplate = Handlebars.compile(template);
</script>
{#html renderTemplate({ name })}
This of course limits the available syntax to handlebars, and you can't use svelte components inside a handlebar template.
B. Dynamic Svelte syntax templates inside a Svelte app
To be able to use svelte syntax you'll need to run the svelte compiler inside the frontend.
The output the compiler generates is not directly usable so you'll also need to run a bundler or transformer that is able to import the svelte runtime dependencies. Note that this is a separate runtime so using <svelte:component> wouldn't behave as expected, and you need to mount the component as a new svelte app.
In short, you could, but unless you're building a REPL tool you shouldn't.
C. Honourable mentions
Allow user to write markdown, this gives some flexibility (including using html) and use marked in the frontend to convert it to html.
Write the string replacements manually {#html template.replace(/\{name\}/, name)}

Create/modify Meteor templates at runtime

I am wondering how to solve this problem:
I have a template which contains some text with some template helpers inside:
<template>Hello {{who}}, the wheather is {{weather}}</template>
Now I need to change the content of the template dynamically at runtime, while maintaining the helper functionality. For example I would need it like this:
<template>Oh, the {{weather}}. Good evening {{who}}</template>
The text changes and the helpers are needed at different positions. Think of an application where users can create custom forms with placeholders for certain variables like the name of the user who fills out the form. Basically, the content of the template is stored in a mongo document and needs to be turned into a template at runtime, or an existing template needs to be changed.
How to approach this? Can I change the contents of a template at runtime?
To solve this use case you need to use two techniques.
Firstly you need to be able to change the template reactivel. To do this you can use Template.dynamic. eg:
{{> Template.dynamic template=helperToReturnName [data=data] }}
See here: http://docs.meteor.com/#/full/template_dynamic
Now that you can change template, you need to be able to create new templates on the fly from you database content. This is non trivial, but it's possible if you're willing to write code to create them, like this:
Template.__define__("postList", (function() {
var view = this;
return [
HTML.Raw("<h1>Post List</h1>\n "),
HTML.UL("\n ", Blaze.Each(function() {
return Spacebars.call(view.lookup("posts"));
},
function() {
return [ "\n ", HTML.LI(Blaze.View(function() {
return Spacebars.mustache(view.lookup("title"));
})), "\n " ];
}), "\n ")
];
}));
That code snippet was taken from this article on Meteorhacks, and the article itself goes into far more detail. After reading the article you'll be armed with the knowledge you need to complete the task...
Just have a helper dynamically build the entire string (remembering that this refers to the current data context):
Template.foo.helpers({
dynamicString: function(switch){
if ( switch == 1) return "Hello "+this.who+", the wheather is "+this.weather;
else return "Oh, the "+this.weather+". Good evening "+this.who;
}
});
Then in your template:
<template name="foo">
{{dynamicString}}
</template>
Alternatively, just use {{#if variable}} or {{#unless variable}} blocks to change the logic in your template. Much, much simpler.
<template name="foo">
{{#if case1}}
Hello {{who}}, the wheather is {{weather}}
{{else}}
Oh, the {{weather}}. Good evening {{who}}
{{/if}}
</template>
You can always have a template helper that computes the necessary boolean variables (e.g. case1).

Meteor - TRIPLE template tag is not allowed in an HTML attribute error

I got error message when trying to run existing meteor project.
$meteor
=> Started proxy.
=> Started MongoDB.
=> Errors prevented startup:
While building the application:
client/coinmx.html:169: TRIPLE template tag is not allowed in an HTML attribute
...title="Totals: {{{get...
^
In Meteor 0.8, it's possible to return a Javascript object which is directly rendered into HTML attributes versus earlier versions, where you had to render it yourself.
Old version:
<input name={{name}} title={{title}}>
helpers:
Template.foo.name = "fooName";
Template.foo.title = "fooTitle";
New version:
<input {{attributes}}>
helpers:
Template.foo.attributes = {
name: "fooName",
title: "fooTitle"
};
All of these can be functions, and reactive, etc. Because the object is rendered directly into attributes, there is no need for you to SafeString some manually rendered content as before. This is the recommended way to go if need to render HTML attributes.
See also the following for how conditional attributes work under this scheme:
https://github.com/meteor/meteor/wiki/Using-Blaze#conditional-attributes-with-no-value-eg-checked-selected
The error is pretty much explanatory: you cannot use {{{something}}} inside a HTML attribute, you need to use {{something}} instead. Depending on what the something is (it's not known from your question as you didn't provide the code), that's either all you need to do, or you can achieve similar functionality by returning new Handlebars.SafeString("result") from your helper instead of just "result". However, if you do, you need to be super sure that the thing you'll return won't break the HTML structure.
Hugo's answer above gave me the missing piece I needed for the same issue-- triple stashes in 0.8 no longer supported. Here is an example that hopefully helps.
Where you might have had {{{resolve}}} in your template, you would now do:
<template name='thing'>
<ol>
{{#each all}}
{{resolve}}
{{/each}}
</ol>
<template>
The helper code then makes use of Spacebars.SafeString which looks to be preferred with Blaze:
Template.thing.helpers({
all: function () {
return Things.find();
},
resolve: function () {
var result = "<li>";
for (var i = 0; i < this.arrayOfClassNames.length; ++i)
result += <'div class='" + this.arrayOfClassNames[i] + "'></div>";
result += "</li>";
return new Spacebars.SafeString(result);
}
});
The key here is to return the 'new Spacebars.SafeString(result)' to wrap your HTML (which must be well formed).

Template is re-rendered even though there is no data change

I'm struggling with an issue that I will explain giving a simple demo.
There's following very simple document in People Collection.
{
"_id" : "vxmLRndHgNocZouJg",
"fname" : "John" ,
"nicks" : [ "Johnny" , "Jo"]
}
Now let's consider following templates. Basically I display username and a list of nicknames with input field for adding more nicknames.
<head>
<title>test</title>
</head>
<body>
{{> name}}<br/>
{{> nicks}}
</body>
<template name="name">
<input type="text" value="{{fname}}"/>
</template>
<template name="nicks">
{{#each nicks}}
<div>{{this}}</div>
{{else}}
no nicks yet
{{/each}}
<input type="text" name="nicks"/>
<input type="submit"/>
</template>
My client javascript code is as follows:
Template.name.fname = function() {
return People.findOne({"fname" : "John"},{
transform : function(doc) {
return doc.fname;
}
});
}
Template.name.rendered = function() {
console.log('Template "name" rendered!');
}
Template.nicks.nicks = function() {
var john = People.findOne({"fname" : "John"});
if(john) return john.nicks;
}
Template.nicks.events({
'click input[type="submit"]' : function () {
var johnId = People.findOne({"fname" : "John"})._id; // demo code
People.update(johnId,{
$addToSet : {
nicks : $('input[name="nicks"]').val()
}
})
}
});
My problem is that after adding nickname (update of nicks field in a document) template name is re-rendered (I know because I console.log it). When I query People collection to provide data for name template I use transform option so changes in nicks field shouldn't have impact on name template.
Meteor docs supports this:
Cursors are a reactive data source. The first time you retrieve a cursor's documents with fetch, map, or forEach inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation.
Why template name is re-rendered then?
The template is re-rendered because you change the People collection.
When you alter the People collection, Meteor automatically assumes that everything that it provides data to needs to be recalculated. (Which your name template does via Template.name.fname.
Even though you transform the output of the cursor, the People collection has changed in some way. The query is done before the transform is used, in other words, its not the transform that is looked at but the whole collection.
Meteor thinks that perhaps your document with {'fname':'John'} may have some other field that might have changed and it needs to requery it to check (which the nicks field has been altered). The transform is then applied after the requery.
Your HTML might not actually change at this point, only if the cursor returns something different will the html be changed.
If it becomes an issue in any scenario (i.e forms being cleared or DOM being changed when it shouldn't be) you can use the {{#isolate}} {{/isolate}} blocks to ensure that only everything inside them is re-rendered and nothing outside.

How to use a javascript function inside a dust.js template?

I'm using dust.js to do a client side templating. I would like to use a javascript function in my template, the function would get it's argument during templating i.e
Ex:
mytemplate = " <span> Hi getName({id}) </span>"
myjson = { id : 1 }
In this case, both the template and json-data are sent from server and templating happens on client side.
In the above example, I would get the 'id' from json data and want to display the name of user corresponding to that id.
I'm new to templating. I would like to know how this can be done using dust.js.
Thank you :)
This can be done with Dust.js by creating a script block within your template:
{! Dust template !}
<script type="text/javascript">
var userName = getName('{id|s|J');
// Do whatever you want with the username
</script>
Note, the |s|j, which is important for security filtering.
In your particular use case, however, it would probably be better to just send the user's name and id in the JSON:
{! Dust template !}
<span id="user-{id}"> Hi {name} </span>
// JSON
{
id: 1,
name: smfoote
}

Resources