handlebars partial context - javascript

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

Related

javascript get value of Mongo field already rendered - Meteor

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

Working with ux-datagrid, angularJS and nested interations

I got it working excellent with one template but ran into an issue trying to replace my inner ng-repeat with another template. Kind of like grouped data or more like nested data.
so let me simplify my html here:
<div data-ng-controller="index.dataGridController" data-ux-datagrid="index.items" class="listA datagrid" data-addons="whichTemplate" grouped="'focus'">
<script type="template/html" data-template-name="default" data-template-item="item">
<div>
<h3>{{ item.obj.name }}</h3>
<!--- instead of
<ul>
<li ng-repeat="listItem in item.focus>{{listitem}}</li>
</uL
-->
</div>
</script>
<script type="template/html" data-template-name="innerFocus" data-template-item="item">
<div>
<ul>
<li>{{item.focus.id}}</li>
</ul>
</div>
</script>
</div>
and the index.items look like this:
[{
"obj": {
"name": "someName"
}
"focus": [
{"id": "something here"},
{"id": "Another etc"}
]
}]
// which template is basically copy and pasted from examples
angular.module('ux').factory('whichTemplate', function () {
return function (inst) {
// now we override it with our method so we decide what template gets displayed for each row.
inst.templateModel.getTemplate = function (item) {
var name = item.focus ? 'innerFocus' : 'default';
// now we get the template from the name.
return inst.templateModel.getTemplateByName(name);
};
};
});
Trying to iterate a list with the focus array. Any ideas? I think it would make for a great example in the docs. Adding a fiddle. http://jsfiddle.net/uftsG/88/
Instead of putting all focus array in one li i'm trying to get them spread out while also having the item.obj.name above the list.
I am not familiar with ux-data grid. If you are set on using it, you can ignore this answer, but I did a small search engine (purely for academic reasons) and i ended up using ng-grid to display the data. I followed the github documentation and ended up boot-strapping #scope.myDefs with the data I needed on the backend using a $scope.getMyData function. In the view all you really need to do is add input fields to get the data, and then a div class with your ng-grid. It's super cool because it ensures the code is modular, and there are no funky functions going on in the view with nested ng-repeats so on and so forth. The only part that gave me trouble was the CSS because I'm not bootstrap savvy yet :) Here's some docs if you end up interested in ng-grid at all.
http://angular-ui.github.io/ng-grid/

converting Jquery to angular or making Jquery work in angular

I am new to angular and we are converting a set of screens based on jsp to angular. Initially we have written lot of code in Jquery. Converting them to angular is tedious task and thought of trying to see if we can make jquery work with angular. Here is teh code snippet that i am trying to make it work while it in Jquery.
$(document).ready(function() {
$("#ClickTask2").click(function() {
$(".ClickTask1").hide();
$(".ClickTask2").show();
});
});
Above is the piece of code I have in JQuery and i tried to make it work.
angular.element(document).ready(function() {
$("#ClickTask2").click(function() {
$(".ClickTask1").hide();
$(".ClickTask2").show();
});
});
Can anyone tell me how i could make it work with minimal changes to the above one and rest of the jqueries?
You can convert many jquery features over to Angular by simply changing the $() method to angular.element() e.g.
$('#output').html('<h1>Title</h1>');
You could convert this to:
angular.element('#output').html('<h1>Title</h1>');
However not all function work, and some are renamed e.g.
$("#output").click(function() { console.log('Hi'); });
Would need to be changed to:
angular.element('#output').on('click', function() { console.log('Hi'); });
You can find a full list of the supported functions here:
https://docs.angularjs.org/api/ng/function/angular.element
like said Luis Masuelli on the comments read the basis of Angular. a quick lesson
app.js
function TaskCtrl($scope) {
$scope.selectedTask = null;
$scope.tasks = [/* ... */];
$scope.onClickTask = function(task) {
$scope.selectedTask = task;
}
$scope.isSelected = function (task) {
return task === $scope.seletectedTask;
}
}
$scope it is a special variable, it is injected by Angular to controllers and serves to communicate the controller with the view among other things. A controller can be any function and the name does not matter.
main HTML
<ul data-ng-controller="TaskCtrl">
<li data-ng-repeat="task in tasks" data-ng-click="onClickTask(task)">
{{task.title}}
<div data-ng-show="isSelected(task)">{{task.description}}</div>
</li>
</ul>
data-ng-controller tells to Angular "this is the controller" for this tag and her children. The other directives are pretty explanatory, but the documentation you left it more clearly.
Of course I am assuming that your tasks has the following structure:
{
title: "...",
description: "..."
}
in your html you need include the angular.js, the previous js and a directive to tell angular that this is a application
<!DOCTYPE html>
<html>
<head></head>
<body data-ng-app>
<!-- main HTML -->
<script src="angular.js"><script/>
<script src="app.js"><script/>
</body>
</html>
the data- prefix on each directive is not necessary but as angular "extend" HTML and these are not native attributes, I use them to place custom attributes as "ng-repeat", "ng-controller", "ng-app" etc. They are called directives
Remember, with Angular you need not manipulate the DOM directly as is done with jQuery, except for some special exceptions

How do you populate a hidden div using handlebars data?

The handlebars data shows up fine on the static page, but the problem is I also have a modal, and the data is not being populated correctly on the modal.
I am using the avgrund modal (you can view the Codepen here)
main.js - handlebars code
var projectData = [
{
id: "0",
name: "Jack",
},
{
id: "1",
name: "Sally",
},
];
var theTemplateScript = $("#project-template").html();
var theTemplate = Handlebars.compile(theTemplateScript);
$("#content").append(theTemplate(projectData));
index.html
<div id="content">
<script id="project-template" type="x-handlebars-template">
<div class="pile">
{{#each this}}
<a onclick="avgrund.activate( 'stack' );">
<div class="box box{{id}}">
<span class="project_name project_name{{id}}">{{name}}</span>
</div>
</a>
<aside class="avgrund-popup">
<h2>{{name}}</h2>
<p>
You can hit ESC or click outside to close the modal. Give it a go to see the reverse transition.
</p>
</aside>
{{/each}}
</div>
</script>
</div>
Problem
The div boxes will show up fine, but whenever I click the boxes so that the modal appears, the modal only displays the last element in projectData.
I've been able to have the data appear fine when I use bootstrap modals, but for some reason not the avgrund modal.
Hint at a possible solution?
I think the answer to this StackOverflow q: how to populate hidden form field hints as something that might work, e.g. pulling out the data and re-inserting it in the avgrund modal code, as such
var data = {
"Value": [
{"Id": "6b7", "Notes": "testing", "CreatedBy": "User1"},
{"Id": "6b7", "Notes": "Testing 1", "CreatedBy": "User2"}
]};
data.Id = data.Value[0].Id;
var tmpl = Handlebars.compile($('#template').html());
var html = tmpl(data);
But I don't fully understand the answer and wasn't able to get it to work. Could someone point to some approaches or guiding principles that might be helpful?
Here's a fiddle that shows a working example: http://jsfiddle.net/9f9w1trs/
As pointed out by Roamer-1888 you had an extra closing </div> in there. You didn't include the code that actually compiles the template and populates it.
The basic idea is that you define a template - like you did
<script id="project-template" type="x-handlebars-template"> ....</script>
Then you "compile" the template (you can pre-compile them for performance reasons or if you'd like to only use the lightweight run-time library opposed to the full library).
var source = $("#project-template").html();
var template = Handlebars.compile(source);
and then you can populate your template with data ...
var populatedHtml = template(projectData);
and finally you add your populated HTML to the DOM
$('#myContent').append(populatedHtml);
Handlebars doesn't really care if you add it to a hidden div or not - it just replaces contents between {{}} ;)

Handlebarsjs: render multiple passes with nested templates

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>

Categories

Resources