I see many things that just refers me to partials which sucks because they have to built out of the layout context.
What I'm wanting to do is make nested templates
For example:
<div id="person">
{{name}}
<div id="address">
{{street}}
</div>
</div>
<script>
var outer = Handlebars.compile($('#person').html());
outer({ name: 'someone special' });
var inner = Handlebars.compile($('#address').html());
inner({ street: 'somewhere cool' });
</script>
When running this, the inner template is never rendered as the outer templating gobbles it up.
It would be nice if you could namespace nested templates like this:
{{> name}}
<div id="person">
{{name}}
{{> address}}
<div id="address">
{{street}}
</div>
{{/> address}}
</div>
{{/> name}}
<script>
var outer = Handlebars.compile($('#person').html(), 'name');
outer({ name: 'someone special' });
var inner = Handlebars.compile($('#address').html(), 'address');
inner({ street: 'somewhere cool' });
</script>
or something like this, so that when the outer renders, it will leave the address alone and let inner render address itself without removing it from the DOM.
Is anything like this possible?
The reason for this question is that I'm using backbone and want to separate out all of my small views but it compiles to one file. when the outer is templated with handlebars, everything else breaks. I don't want to use partials as that just take everything out of the flow of the html document for the designers.
EDIT
I think what I really need is a way to do {{noparse}} and from the registerHelper just return the raw html between the noparse tags
Here is a demo of a no-parse helper. To use this functionality, you will need to use a version of at least v2.0.0-alpha.1. You can get it from the handlebars build page. Here is the pull request that details about it.
Here is the relevant code.
Template
<script id="template" type="text/x-handlebars-template">
<div id="person">
{{name}}
{{{{no-parse}}}}
<div id="address">
{{street}}
</div>
{{{{/no-parse}}}}
</div>
</script>
Handlebars.registerHelper('no-parse', function(options) {
return options.fn();
});
You're missing a crucial step. You have to take the template & compile it (which you're doing) but you also have to register the partial.
Really the pattern you want to use is the partial template - where you register the inner template as a partial template then fill it with the person's address data - there is a great example here, but this also will help in your specific HTML setup.
<script id="person-template" type="text/x-handlebars-template">
{{#each person}}
{{name}}
{{> address}}
{{/each}}
</script>
<script id="address-partial" type="text/x-handlebars-template">
<div class="address">
<h2>{{street}}</h2>
</div>
</script>
<script type="text/javascript">
$(document).ready(function() {
var template = Handlebars.compile($("#person-template").html());
Handlebars.registerPartial("address", $("#address-partial").html());
template({ name: 'someone special', street: 'somewhere cool' });
}
</script>
Related
I am in the process of implementing some components in my codebase. However, I have ran into an smaller issue with the template part. I would like to send in the template as an input to a knockout-component but I am not sure how to do it or if it even is possible.
Taking an example from http://knockoutjs.com/documentation/component-overview.html I hope that I can do something like this:
<like-or-dislike params="value: userRating">
<div class="like-or-dislike" data-bind="visible: !chosenValue()">
<button data-bind="click: like">Like it</button>
<button data-bind="click: dislike">Dislike it</button>
</div>
<div class="result" data-bind="visible: chosenValue">
You <strong data-bind="text: chosenValue"></strong> it.
And this was loaded from an external file.
</div>
</like-or-dislike>
But I cannot find any documentation if that works at all. The reason why I want to implement it that way is simply because I am having some server generated html that I want to still be a part of a component. Otherwise I will have to make it a json-object and render the html inside the component which seems like a unnecessary extra step. The good thing about using components is that the logic is seperated in it's own file and it is easier to seperate logic between different components. I understand that if I do it like this I have to copy the html if I want to reuse the component.
Am I thinking of this the wrong way or is this possible?
Thanks for your sage advice and better wisdom.
I can't say I fully understand your situation but I think I may have the answer. You can actually have the server generate <script type="text/html"> and use that (by id of course) with a component. The KO documentation is pretty poor on component templating, but here is an example using an element.
A couple of things I've learned with components. The viewmodel must be declared before declaration, and the <script> must be in the dom prior to binding.
function ComponentViewModel() {
var self = this;
self.Title = ko.observable("This is a Component VM");
}
function ViewModel() {
var self = this;
self.ExampleComponent = ko.observable({
name: 'Example'
});
}
ko.components.register('Example', {
template: {
element: 'ComponentTemplate'
},
viewModel: ComponentViewModel
})
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script id="ComponentTemplate" type="text/html">
<span data-bind="text: Title"></span>
</script>
<div data-bind="component: ExampleComponent"> </div>
I won't devalue components, but I also would point you to using templates with a data binding, it's essentially the same thing (please correct me if I'm wrong). and doesn't require the component be established. This is better for situations where the would-be component occurs less frequently.
function ComponentViewModel() {
var self = this;
self.Title = ko.observable("This is a Template with a VM");
}
function ViewModel() {
var self = this;
self.ComponentVM = ko.observable(new ComponentViewModel());
self.ExampleComponent = ko.observable({
name: 'ExampleTemplate', // This is the ID
data: self.ComponentVM
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script id="ExampleTemplate" type="text/html">
<span data-bind="text: Title"></span>
</script>
<div data-bind="template: ExampleComponent"> </div>
I hope these help!
Hey everyone, thank you very much for your help. Question is edited per suggestions in the comments.
I'm new to Mongo and Meteor.
I have a collection "posts" with a field "slug".
The "post" template is populating correctly with each post's values. Slug value is always something like "my-great-post".
I need to get the text value for the _id's slug, which will be different each time the template is accessed, encode it, write a string, and spit the string back out into the template.
Things tried
can't return a value for "this.slug" or "this.data.slug" in either template helpers or onRendered, even though collection is defined and correctly populating spacebars values in the template
"this" returns "[object Object]" to console.log
app crashes when I try to javascript encode and deliver a string from the helper, probably I don't fully understand helper syntax from the documentation
(I followed advice in the comments to avoid trying to create scripts in the template html, so below is more information requested by everyone helping on this thread)
- Template html -
{{#with post}}
<div class="blog-article">
<div class="blog-header">
<div class="left">
<!-- title -->
<h1 class="post-title">{{title}}</h1>
<div class="holder">
<div class="post-tags">
<!-- tags -->
{{#each tags}}
<span>{{this}}</span>
{{/each}}
</div>
</div>
</div>
</div>
<div class="blog-post">
<div class="blog-copy">
<!-- date -->
<div class="post-date">{{post_date}}</div>
<!-- social -->
<div class="blog-social">
<!--
<a class="so-facebook" target="_blank" href="need to encode slug here"></a>
-->
</div>
<!-- ============== post ============== -->
{{{content}}}
<!-- ============ end post ============ -->
</div>
</div>
</div>
{{/with}}
- Template js -
Template.post.onCreated(function() {
var self = this;
self.autorun(function() {
var postSlug = FlowRouter.getParam('postSlug');
self.subscribe('singlePost', postSlug);
});
});
Template.post.helpers({
post: function() {
var postSlug = FlowRouter.getParam('postSlug');
var post = Posts.findOne({slug: postSlug}) || {};
return post;
}
// can't get these working in a helper, out of helper they crash the app
// console.log(this.slug);
// console.log(this.data.slug);
});
Template.post.onRendered( function () {
// these do not work
// console.log(this.slug);
// console.log(this.data.slug);
});
db.posts.findOne();
{
"_id" : ObjectId("576c95708056bea3bc25a91f"),
"title" : "How Meteor Raised the Bar For New Rapid-Development Technologies",
"post_date" : "May 28, 2016",
"image" : "meteor-raised-the-bar.png",
"slug" : "how-meteor-raised-the-bar",
"bitlink" : "ufw-29Z9h7s",
"tags" : [
"Tools",
"Technologies"
],
"excerpt" : "sizzling excerpt",
"content" : "bunch of post content html"
}
If some one can solve this using any method, I will accept answer with joy and gratitude most intense.
The problem is probably with the parent template, rather than this one. The way that Meteor works is that the JS files are separated from the HTML, so don't try to include a <script> tag in the HTML.
The first thing is that you have to load all of your documents into the client. (NOTE: once you've got the hang of that, then you can worry about only loading the documents that you need).
To do that, you need a collection and a publication. By default all collections are automatically published completely, so unless you removed the autopublished module, then I'll assume that it is still loaded.
So let's start with the parent template. In this case, I'm going to just loop through all of the posts in the collection and display them using the innerTemplate.
<template name=parent>
<ul>
{{#each post}}
{{> innerTemplate}}
{{/each}}
</ul>
</template>
And now our inner template might look like this:
<template name=innerTemplate>
<li>{{slug}}</li>
</template>
The end result will be a simple list with each slug.
Now, to link everything together, we need to create a JS file, which will:
1. define the collection on both client and server
2. pass the collection to the parent template
This file should be accessible to both the client and the server.
posts = new Mongo.Collection('posts');
if(Meteor.isClient) {
Template.parent.helpers({
posts() {
return Posts.find();
}
});
}
Now, if you want to do something with 'slug' in the JS file, you could do something like this:
if(Meteor.isClient) {
Template.innerTemplate.helpers({
upperCaseSlug() {
return this.slug.toUpperCase();
}
});
}
Then, you could refer to upperCaseSlug in your template, like thus:
<template name=innerTemplate>
<li>{{upperCaseSlug}}</li>
</template>
A few things about Meteor:
You should never see a pattern such as:
<script type="text/javascript">
...some code
</script>
Because Meteor combines all your js files into one big file and includes it automatically in your app. You should never have to declare your own script in this way.
Secondly, you should never have to get the value of a data object by reading the DOM. The data context of each template gives you your data in the variable this.
In either a helper or template event you can refer to this and be assured that you're going to get exactly the data being displayed in that instance of the template.
Having now seen your template code it's now apparent that your template has no data context - you set the data context inside your {{#with post}} and its associated helper but that doesn't end up creating the this you need one level below.
So... #Nathan was on the right track except that he assumed you were iterating over a cursor instead of just looking at a single post.
Take all html you have between your {{#with post}} and {{/with}} and put it in a new template, say postDetail then make your outer template:
<template name="post">
{{#with post}}
{{> postDetail}}
{{/with}}
</template>
Now your postDetail template will get a data context equal to the post object automatically and your helpers can refer to this safely.
Template.postDetail.helper({
slugURI{
return "/"+encodeURI(this.slug);
}
});
Then in your postDetail template you can get the encoded slug with:
<a class="so-facebook" target="_blank" href={{slugURI}}>
I am having a strange issue where Handlebars is compiling my template properly, but when passing context data, the resulting fields in the html are blank. I've confirmed that my JSON data is actually a javascript object and not a string. My apologies if this has been answered elsewhere. I saw a lot of answers about the JSON string needing to be an actual object, but as i've stated, is not the case.
Template:
<script type="text/x-handlebars-template" id="items-template">
{{#each downloads}}
<div class="download-item">
<h3>{{pagetitle}}</h3>
<p>{{description}}</p>
</div>
{{/each}}
</script>
JS:
var source = $("#items-template").html();
var template = Handlebars.compile( $.trim(source) );
var html = template({
downloads:[
{pagetitle:'title', description:'the description'},
{pagetitle:'title', description:'the description'},
{pagetitle:'title', description:'the description'}
]
});
Result of html (Only One Single Blank Item):
<div class="download-item">
<h3></h3>
<p></p>
</div>
Any insight would be greatly appreciated. If i figure it out before someone sees this, i'll be sure to post an update.
You should use this
{{#each downloads}}
<div class="download-item">
<h3>{{this.pagetitle}}</h3>
<p>{{this.description}}</p>
</div>
{{/each}}
I am trying to render my application template, which is getting very complicated, so I want to split it up into separate <script type="text/x-handlebars">'s
I think I want to use the {{view}} helper, but I don't know. Let's say I have this:
<script type="text/x-handlebars" data-template-name="application">
<div id="wrapper" class="clearfix">
<hgroup>
<header>
<div class="logo"></div>
<h1 id="facilityName">{{facilityName}}</h1>
<div id="sessionInfo">
<a id="userName">{{user}}</a>
<a id="logOut" href="../logout">log out</a>
</div>
</header>
</hgroup>
{{view App.breadcrumbsView}}
</div>
</script>
And I want to load this next template inside of the one above:
<script type="text/x-handlebars" id="breadcrumbs">
<div id="breadcrumbs">
<p>
Network
{{#each breadcrumbObj}}
<span>></span><a {{bindAttr href="link"}}>{{name}}</a>
{{/each}}
</p>
</div>
</script>
Right now I am trying to do this with this code from my app.js
App.breadcrumbsView = Ember.View.create({
templateName: 'breadcrumbs',
breadcrumbObj: Util.breadcrumbs()
});
This is not working and I am getting Uncaught Error: assertion failed: Unable to find view at path 'App.breadcrumbsView'
Thanks
I think that you declared your App using var keyword. So it's not visible in handlebars template.
Change this
var App = Ember.Application.create();
To this
App = Ember.Application.create();
And you have to use extend instead of create when creating your views. Because ember will handle the object lifecycle like: create, destroy, add and remove binding etc.
#Marcio is right, you should .extend() and let the framework create it for you automatically by request. Furthermore when you extend the best practice is to use capitalized names like App.BreadcrumbsView.
See here a demo how it renders correctly doing it this way.
Hope it helps.
I have an array that contains the information for social buttons (href,alt,img). I created a partial that would cycle through the array and add the objects, here is what I have.
the array:
var social=[
{
"url":"http://twitter.com/share?text="+encodeURIComponent(this.title)+" "+encodeURIComponent('#grnsrve')+"&url="+domain+"&via="+twitterAcct,
"image":"IMG/media/twitter_16.png",
"alt":"twitter link"
},
{
"url":"http://twitter.com/share?text="+encodeURIComponent(this.title)+" "+encodeURIComponent('#grnsrve')+"&url="+domain+"&via="+twitterAcct,
"image":"IMG/media/twitter_16.png",
"alt":"twitter link"
},
{
"url":"http://twitter.com/share?text="+encodeURIComponent(this.title)+" "+encodeURIComponent('#grnsrve')+"&url="+domain+"&via="+twitterAcct,
"image":"IMG/media/twitter_16.png",
"alt":"twitter link"
}
];
The template:
social_partial = '<img src="{{image}}"/>';
The partial function:
Handlebars.registerPartial('addSocial',social_partial);
and the main template:
<div class="tip_social">
{{>addSocial social}}
</div>
I'm getting a 'Depth0 is undefined error'. I tried looking for documentation on parials getting a different context , but I have yet to find it.
edit here is a more complete fiddle of it
Same issue for me. Bit of digging and one face-palm later here's what I've found.
tl;dr answer
Be very careful about the context you pass to your partial. If you somehow pass a null, or empty object to your partial you'll get the depth0 is undefined error
Very slightly longer answer
JSFiddle Examples:
depth0 bug gets triggered http://jsfiddle.net/paulcollinsiii/dAmfc/
working version http://jsfiddle.net/paulcollinsiii/sMAkT/
The only thing that changed in the working version is I'm passing a valid context to the partial.
BTW a very helpful trick is to use the debug helper from this blog
Answerbot dictator "code example"
<script id="parent">
<table>
<thead>
<tr>
<th>One</th>
<th>Two</th>
</tr>
</thead>
<tbody>
{{#each collection}}
{{>myPartial nothingHere}} <!-- Broken context -->
{{/each}}
</tbody>
</table>
</script>
The broken context above will cause handlebars to die. For your problem try digging into that with a {{debug}} tag and see if that helps.
For a better code example please just take a look at the jsFiddles. Reposting all the code in here, formatting it to make it pretty looking and making the StackOverflow answerbot dictator happy was a bit much for doing this at work ^_~
I'm not sure what you're doing wrong as you haven't provided enough code to duplicate your problem. However, it isn't hard to make it work. First of all, your main template should be iterating over social and feeding each element to your partial, something like this:
<script id="t" type="text/x-handlebars-template">
{{#each social}}
<div class="tip_social">
{{>addSocial this}}
</div>
{{/each}}
</script>
And then you can get your HTML with something like this:
var t = Handlebars.compile($('#t').html());
var html = t({social: social}));
Demo: http://jsfiddle.net/ambiguous/SsdbU/1/
Here is the only way I found:
Handlebars doc indicates that you need to use Block Helpers to pass different contexts. So here are two files: the main template and the script in node contening two contexts: the main context and the context for the social data. The script having the source for the partial template. The Block is #twitter_list and its associated registerHelper uses option.fn(other_context_object) which seems the only way to pass another context in handlebars.
Main template:
<html>
<head>
<title>Test content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div id = main>
<!-- This is using the main context-->
<h2> Hello {{name}}!</h2>
<p>{{some_content}}</p>
<!-- This is using the partial template and its own context -->
<ul>
{{#twitter_list}}
{{>yourpartial}}
{{/twitter_list}}
</ul>
</div>
</body>
</html>
The javascript using Node js:
var handlebars = require('handlebars'),
fs = require('fs');
const main_context = { name: "world",
some_content: "Bla bla bla all my bla bla"};
const twitter_data={social:[
{
"url":"some url 1",
"image":"IMG/media/twitter_1.png",
"alt":"twitter link 1"
},
{
"url":"some url 2",
"image":"IMG/media/twitter_2.png",
"alt":"twitter link 2"
},
{
"url":"some url 3",
"image":"IMG/media/twitter_3.png",
"alt":"twitter link 3"
}
]};
var partial_source = '{{#each social}}<li><img src="{{image}}"/></li>{{/each}}';
handlebars.registerPartial('yourpartial', partial_source);
handlebars.registerHelper('twitter_list', function(options) {
//you need to use the options.fn to pass the specific context
return options.fn(twitter_data);
});
fs.readFile('maintemplate1.hbs', 'utf-8', function(error, source){
var template = handlebars.compile(source);
var html = template(main_context);
console.log(html)
});