HandlebarsJS not outputting html template unless receives a param - handlebars.js

I have this code:
if (this.template) {
var template = Handlebars.compile( $(this.template).html() );
$(this.el).html(template());
}
with this template:
<script id="tmpl-nav-account" type="text/x-handlebars-template">
{{#this}}
<div class="nav-account">
topbar
</div>
{{/this}}
However, if run the 'template()' function with no params, nothing outputs. Yet, if I pass something in like: "template('ben')", it outputs the static HTML fine. Anyone got any ideas?
Does template() always have to have something passed into it to render the template?
EDIT:
If I remove the {{#this}} from the template, then it works with no parameters...

The this at the top level of a template is the argument you supply to the compiled template function. So, given this:
var o = { ... };
var t = Handlebars.compile(some_template_text);
t(o);
this will be o at the top level of the template. So, if you say template(), this is undefined inside the template and {{#this}} won't do anything because undefined is false in a boolean context.
You can see this clearly if you use this template:
<script id="tmpl-nav-account" type="text/x-handlebars-template">
{{#this}}
<div class="nav-account">
{{this.where_is}}
</div>
{{/this}}
</script>​
and this JavaScript:
var t = Handlebars.compile($('#tmpl-nav-account').html());
console.log(t());
console.log(t({ where_is: 'pancakes house?' }));​
Demo: http://jsfiddle.net/ambiguous/fS8c9/

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)}

How to override Handlebars' `if` helper

I'm looking for a way to change how Handlebars' if helper works.
Mandrill has made a few modifications to the standard Handlebars. One of these handy changes is in the way they handle Handlebars' if block helper. They've added the ability to evaluate expressions inside the if helper like this:
{{#if `purchases > 3`}}
I need to be able to compile Mandrill templates locally, for testing and preview, and this feature is making it difficult. I know I can write my own custom helpers, but in this case I'm looking for a way to alter the way the built-in if helper works.
I guess I could build my own version of Handlebars.
Any other suggestions?
I don't think that there is a problem to redefine the standard helpers as long as yours are working fine. You'll find in the below snippet one example that override the if helper to put instead a text. So just register your own helper this will override the default one. If you want the {{else}} also working you'll have to handle it in your if helper code.
$(document).ready(function () {
var context = {
"textexample1" : "hello",
};
Handlebars.registerHelper('if', function(text) {
return new Handlebars.SafeString(text);
});
var source = $("#sourceTemplate").html();
var template = Handlebars.compile(source);
var html = template(context);
$("#resultPlaceholder").html(html);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script id="sourceTemplate" type="text/x-handlebars-template">
{{#if 'test override'}}
{{textexample1}}
{{/if}}
</script>
<br/>
<div id="resultPlaceholder">
</div>

Setting the document title with Meteor

I'm just starting with Meteor. In an app which is to be localized, I want to set the document title.
I am following the advice given by Bernát
In my barebones version, I have just 2 documents:
head.html
<head>
<meta charset="utf-8">
<title>{{localizedTitle}}</title>
</head>
ui.js
UI.registerHelper("localizedTitle", function() {
var title = "Localized Title"
document.title = title;
});
When the app loads, the document title is "{{localizedTitle}}". If I call UI._globalHelpers.localizedTitle() from the console, the correct title is shown.
What do I have to do to get the localized title to show when the page is loaded?
EDIT: This works for me, but it seems to be a bit of a hack. The title template does nothing but get itself rendered, which actually adds nothing to the interface.
body.html
<body>
{{> title}}
</body>
<template name="title">
</template>
title.js
Template.title.onRendered(function () {
document.title = getLocalizedString()
function getLocalizedString() {
return "Title : in English"
}
})
Following Bernát's answer, your global helper should not be called in the head's <title> tag, but within the <template> tag of the template where you wish to have a given title. In Meteor, <head> does not count as a template, therefore you cannot use Spacebars notation in it: it will just be considered as simple text.
Also, keep in mind that your helper will not return (i.e. print) anything to the page. document.title = "something" directly assigns "something" to your ` tag. So no need to call your helper inside it!
So, say you want to have the "Localized Title" title for a page using the localized template :
<template name="localized">
<h1>This is the localized page</h1>
{{localizedTitle}}
</template>
Here, your trick should work.
I've found it convenient to set the title in onAfterAction in my iron-router routes:
onAfterAction: function(){
document.title = 'foo'; // ex: your site's name
var routeName = Router.current().route.getName();
if ( routeName ) document.title = document.title + ': ' + routeName;
}
I think a more elegant solution is to make the title reactive and set it via a Session variable (other reactive data sources are of course also OK). Like that:
Template.body.onRendered(function() {
this.autorun(function() {
document.title = Session.get('documentTitle');
});
});
Now every time you set the 'documentTitle' variable with
Session.set('documentTitle', 'Awesome title');
the page title will change. No need for hacks and you can do this anywhere in your client code.
Try this instead:
UI.registerHelper('title', function() {
return 'LocalizedTitle'
});
we can use title where ever you want
you can use like this {{title}}

Is it possible to reference a Template name dynamically and call onRedered on it

Currently I have the following in a package:
Template.layout.onRendered(function() {
//Do stuff when the template called "layout" is rendered
});
But I would like to make the template name user configurable. Something like:
var templateName = 'customLayout';
Template.{templateName}.onRendered(function() {
//Do stuff when the template called "customLayout" is rendered
});
Any help in the right direction would be great!
In JavaScript you can use the square brackets syntax to do that:
Template['customLayout'].onRendered( [...] )
'customLayout' above can be an arbitrary expression.
From Meteor 0.8.2, you can write this:
var myTemplate='foo';
{{> UI.dynamic template=myTemplate}}
https://www.discovermeteor.com/blog/blaze-dynamic-template-includes/

Meteor: Passing more than one value to a template

I want to create a template/js combo similar to the ones below. What I would like is to have two variables available to the 'collection' template
<template name="collection">
Title: {{title}}
<UL>
{{#each items}}
{{> item}}
{{/each}}
</UL>
</template>
<template name="collection_items">
<LI>{{item_title}}</LI>
</template>
Where the javascript function would be something like:
Template.collection.data = function() {
var record = Record.findOne({whatever:value});
return { title: record.title, items: record.items }
}
I've tried using Handlebars' {{#with data}} helper and return an object as above, but that just crashed the template. I've tried creating a 'top level' function like:
Template.collection = function () {... }
but that also crashed the template.
What I'm trying to avoid is having two separate functions (one Template.collection.title, and one Template collection.items) where each of them calls a findOne on the Record collection where really its the same template and one call should suffice.
Any ideas?
Template.collection = function () {... }
Template.collection is not a function, it's an instance and thus an object.
You can type Template.collection in the console to see something essential as well as Template.collection. and autocomplete that to see its methods and fields.
For a #with example, the Todos indeed doen't seem to contain one as you have outlined in your comments. So, an example use of it can be found here:
https://github.com/meteor/meteor/blob/master/packages/templating/templating_tests.js#L75
https://github.com/meteor/meteor/blob/master/packages/templating/templating_tests.html#L92
Here is another example that I tried that works on both the current master and devel branch:
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{#with author}}
<h2>By {{firstName}} {{lastName}}</h2>
{{/with}}
</template>
And the JS part of it:
if (Meteor.is_client) {
Template.hello.author = function () {
return {
firstName: "Charles",
lastName: "Jolley"
};
};
}
Any specific reason why you're hoping to avoid two functions?
From your code sample I see one issue: the first template is calling a second template with this line:
{{> item}}
But your second template is not called 'items'. I believe that your second template should be called this way:
<template name="item">
Seems that it would be simple enough to have helper functions for the first and the second. Although I haven't gotten it to work with my own code, I believe the second helper function would want to use the 'this' convention to refer to the collection you're referring to.
Cheers - holling
Tom's answer is correct. I want to just chime in and add that in my scenario the reason why #with was failing was because due to the 'reactive' nature of meteor my first call to load the model resulted in 'undefined' and I didn't check for it. A fraction later it was loaded ok.
The moral is to do something like
var record = Record.findOne({whatever:value})
if (record) {
return record;
} else {
// whatever
return "loading"
}

Resources