handlebars - cannot read property of undefined - handlebars.js

I have a script that executes once a modal is launched. The js to configure the js object is:
var source = $("#print-script").html();
var template = Handlebars.compile(source);
var data = {
image: image,
title: title,
caption: subtitle,
category: template
};
$("#print-template").html(template(data));
All the variables are set above the object declaration and are working. My html reads:
<script id="print-script" type="text/x-handlebars-template">
<div class="print-only" id="print-template">
<img src="{image}"/>
<h2><span class="icon"></span>{category}</h2>
<h3>{title}</h3>
<p class="headline">{caption}</p>
<div class="description">{long_description}</div>
</div>
</script>
I'm getting a Uncaught TypeError: Cannot read property 'image' of undefined. I have confirmed that the object (data) is being populated with content. Thoughts?

I'd guess that the problem is right here:
var template = Handlebars.compile(source);
var data = {
//...
category: template // <------------------- oops
};
Note that template is the compiled template function there so when the template tries to fill in {category}, it will execute the template function. But the template function is expecting some values to fill in the template with and then it is called through {category}, those values won't be there and you'll get a TypeError. Run these two demos with your console open and you should see what's going on:
Error using category: template:
http://jsfiddle.net/ambiguous/9wEyS/
Works using category: function() { return 'string' }:
http://jsfiddle.net/ambiguous/YgfZx/

turns out there needs to be another object in the data:
var data = { print:{
image: featuredImage,
title: title,
caption: subtitle,
category: template
}
};

Related

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

Recursive component inclusion

I'm looking at the Authoring Ractive.js components document on github from Rich-Harris.
It starts with invoking the foo component and including it this way:
<link rel='ractive' href='foo.html' name='foo'>
<p>This is an imported 'foo' component: <foo/></p>
Which I understand as declaring foo.html as a component and calling it on the foo tag, and this would not require doing a ractive.load (although I did not understand yet where the data loading would occur).
As it does not work at all (no loading of the component), I'm wondering if I mis-understood this part.
Has anyone use this and could give me a complete example?
Components themselves are independent of the loading mechanism.
In the simplest form, components can be declared in javascript:
Ractive.components.foo = Ractive.extend({
template: '#foo' // id of script tag, or can be inline string,
// other options
});
var ractive = new Ractive({
template: '<p>This is an main view with a 'foo' component: <foo/></p>'
});
Creating components is covered here in the docs.
There are many ways to package and load components. Using the link tag, as in your example, requires using ractive-load to actually load the components:
<!-- name is optional if same as file name -->
<link rel='ractive' href='foo.html' name='foo'>
<script src='ractive.js'></script>
<script src='ractive-load.js'></script>
<script>
// calling load() with no parameters loads all link tags and
// registers them as global components
Ractive.load().then( function () {
var ractive = new Ractive({
el: 'body',
template: 'I have access to the <foo/> component!',
data: { ... }
});
// or you could instantiate a component via:
var app = new Ractive.components.app({
... /options
});
}).catch( handleError );
</script>

Best way to prevent a template helper to be rerun when it is unnecessary?

I'm trying to prevent a template helper to be rerun when it is unnecessary. I made a simple application to illustrate this behavior:
Let's say I want to display some items that contain only a title and a description.
<template name="Tests">
{{#each items}}
{{> TestsItems}}
{{/each}}
</template>
<template name="TestsItems">
<div class="title">{{title}}</div>
<div class="description">{{description}}</div>
</template>
I have autopublish enabled.
Template.Tests.helpers({
items: function () {
return Items.find();
}
});
Template.TestsItems.helpers({
description: function () {
// I'm using this helper to do some updates
// on a jQuery plugin when the description field change.
// see example 1: https://github.com/avital/meteor-ui-new-rendered-callback/
console.log("The description is run");
return this.description;
}
});
When a new update is made on the title field only, you can see that the description helper is rerun. What I'm trying to achieve is to only rerun this helper when there is a new value for the description field and not every time a field has changed in the document.
As {{#constant}} and {{#isolate}} are deprecated, how can I get this behavior in the latest Meteor versions?
Note 1: Create a new subtemplate including the description does not fix the problem.
I would avoid side effects in template helpers. Instead I would use an autorun:
Template.TestItems.rendered = function () {
var _id = this.data._id;
this.autorun(function () {
// Select only the description field, so that we only
// trigger a re-run if the description field changes
var description = Items.findOne(_id, {fields: {description: 1}}).description;
// update the JQuery plugin
});
}

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

HandlebarsJS not outputting html template unless receives a param

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/

Resources