I am using Handlebars (v 1.0.0) to fill a table in HTML.
However somehow it does not fill the table like I am used to.
Here is my template:
{{#if users}}
<table>
{{#each users}}
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
{{/each}}
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
So I does find users because I do not see the "No users found!" and when I call an empty object it does show the "No users found!".
When I do not use the table and print out these users like the next example. The usersnames and mail address' will show up in my HTML.
{{#if users}}
{{#each users}}
{{username}}<br/>
{{email}}<br/>
{{/each}}
{{else}}
<h3>No users found!</h3>
{{/if}}
Here is how my template is build in the javascript:
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
var compiled = template(usersArray);
that.$el.html(compiled);
Now when I console.log the compiled object, it doesn't show the table already.
Do you know why it doesn't work and can you help me out?
EDIT:
I just tested some more and found that the data will show up in the HTML when I leave out the <table> tags. However the <tr> and <td> won't show up in html. The data in it will be shown.
EDIT 2:
I found out that it seems to be a jquery issue or javascript issue.
When I console.log the htmlSource the HTML template is changed to this:
{{#if users}}
{{#each users}}
{{/each}}
{{else}}
<h3>No users found!</h3>
{{/if}}
<table><tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr></table>
As you can see the table is moved outside the if statement.
I tried other jquery versions (2.0.2, 2.0.3, 2.0.1, 1.10.2) but this didn't work.
I tried using the innerXHTML script however this works the same as jQuery.
So my guess is that it might be a FireFox issue (though I tried 25 and 26), Chrome does the same... maybe something in EcmaScript??
I will let you know soon...
EDIT 3:
I created an html file with the html I need. With a script I get the specific section of html I need and place this in the data variable.
Now when console logging the data (console.log(data)) there is nothing wrong.
When console logging the data with jQuery, the html is altered: console.log($(data));
It seems something is going wrong there.. but only when using table tags.
Is this something jQuery can't handle? I don't know. I know how to overcome this issue by using the script tag... Though I would like to load that using require ;)
P.S. nemesv thanks for you're edits ;)
Make sure you're outputting your template in a tag so the browser don't try to parse it.
<script type="x-template" id="the-tpl">
{{#if users}}
<table>
{{#each users}}
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
{{/each}}
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
</script>
If there's no type attribute on the script tag, the HTML parser will find a bug and try to resolve it. From your bug details, it really looks like it is the case here.
Using require
As you noted you load using Require, than make sure you load your template using the text! plugin: require("text!path/to/template.html")
To be even fancier, you could delegate all the handlebars compilation to a handlebars template loading plugin so templates are precompiled inside your build - this is probably the best.
Either way, your issue is that your template get parsed by the HTML parser and that is breaking your template content. Make sure you load it as "text" via XMLHttpRequest or with require or inside a script tag correctly typed.
Simon's solution is the right one and problem is discussed in various comments.
I am just putting the pieces here.
as #rescuecreative pointed out some browsers remove empty <table> tags when the html is inserted in DOM.
The case here is a bit similar. The browsers do misbehave when they see invalid markup. but in this case its not because of missing tr, td inside table. its because of extra lines before tr in the table.
Your problem starts here.
var htmlSource = $(data).html();
whatever loading mechanism you are using, you inserting the template in the DOM before compiling it with handlebars.
This is what happens when you add uncompiled template to DOM.
{{#if users}} <!-- browser does not understand this and tries to print it as it is-->
<table> <!-- browser sees the table tag and immediately looks for child TR, tbody, thead -->
{{#each users}} <!-- but instead finds plain text and hence considers it as invalid markup -->
<tr> <!-- the same story is repeated again>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
{{/each}} <!-- such plain strings are taken out of table markup and placed before or after depending upon the browser, in case of chrome placed before the table -->
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
This is how chrome renders it -
{{#if users}}
{{#each users}}
{{/each}}
<table>
<tbody>
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
</tbody>
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
I dont have firefox with me as of now. but i am sure firefox has its own markup correction method.
after this when you take it out of DOM using jquery and compile
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
it will just output this
<table>
<tbody>
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
</tbody>
</table>
if you want you can dump it after compilation to see.
var compiled = template(usersArray);
console.log(compiled);
that.$el.html(compiled);
This also explains why you are getting username and email when you strip out table markup in the original template.
Now to solve this issue either use text plugin as Simon pointed out. or place the template inline.
I too use requriejs with something along the following
define([
'underscore', // you can replace this with handlebars
'text!path/to/template.html'
], function(_, Tmpl){
return _.template(Tmpl); //and handlebar pre-compilation here
});
this way you can also replace this module with a pre-compiled template in your build process.
Related
I have a basic Rails app with a nested association and I am doing a standard 'line items' type view where I am dynamically adding and removing rows from a template tag. I have a select that triggers a AJAX call to replace all the nested row selects (the context changed). The expenditure controller handles the overall form and the nested-form controller is used on other pages to handle the adding and removal of rows:
<div data-controller="expenditure nested-form">
<select data-target="expenditure.budget" data-action="change->expenditure#update_related"></select>
<table>
<thead></thead>
<tbody>
inserted rows...
<tr>
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
<template data-target="nested-form.template">
<tr data-new_record="true">
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
</template>
</tbody>
</table>
</div>
It works fine. I can add and remove rows and if I change the expenditure.budget select all the expenditure.budgetItemSelect targets get updated EXCEPT for the one inside the template. It's as if it's missing from the entire scope of the controller. I had them nested before but now have them in the same div data-controller="expenditure nested-form" to double check and it still doesn't work. Checked spelling and even tried removing the data-target="nested-form.template". No errors in the browser console. Am I missing something obvious here?
UPDATE
Hmmm... it seems that the template tag is read only and NOT part of the DOM which is why this fails.
I managed a hack where I replaced the contents of the entire template but that seems to break the controller that adds / deletes the rows 🤦♂️.
UPDATE 2
I found a workaround - If someone can improve this code I will accept this as a better answer.
It seems to be an issue with the <template> tag in HTML5.
I have a workaround but it's ugly.
<div data-controller="expenditure nested-form">
<select data-target="expenditure.budget" data-action="change->expenditure#update_related"></select>
<table>
<thead></thead>
<tbody>
inserted rows...
<tr>
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
<template id="expenditure_items_template">
<tr data-new_record="true">
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
</template>
<script type="text/template" data-target="nested-form.template" id="expenditure_items_template_script">
</script>
</tbody>
</table>
</div>
Here is what I did in my controller:
// find the template
var template = document.getElementById("expenditure_items_template");
// load the template contents
var new_template = document.importNode(template.content, true);
// replace the select with my new content (off screen)
new_template.getElementById('expenditure_expenditure_items_attributes_NEW_RECORD_budget_item_id').innerHTML = select.innerHTML;
// clear the new script place holder
document.getElementById("expenditure_items_template_script").innerHTML = "";
// set the new updated template into the script tag
document.getElementById("expenditure_items_template_script").appendChild(new_template);
I basically have two templates - one <template> which holds the raw HTML and the second <script> that works with Stimulus.
(First allow me to say that I'm just beginning to learn Node-RED concepts; I went through some beginners' guides at nodered.org, and now am trying to extend what I learned so far).
I'm trying to build a flow that starts with a simple JSON tree like
[{"position":"1", "title":"element #1"},
{"position":"2", "title":"element #2"},
{"position":"3", "title":"element #3"}]
To build that treee I use a template node, property is set to msg.payload.
The number of array elements (in theory) is dynamic. To make sure that this tree is true JSON I added a JSON node converting from String to JSON object.
Next I wish to parse that object into a dynamic html table. For this I used a JS function node that's looping through the object and embedding its elements into the according html elements like this:
var return="";
for(var i=0;i<=msg.payload.length-1;i++){
var row=msg.payload[i];
if(row){
return+="<tr>";
return+="<td>"+row.position+"</td>";
return+="<td>"+row.title+"</td>";
return+="</tr>";
}else{
return+="no object at index "+i.toString();
}
}
msg.payload=return;
return msg;
The output of the function then should be passed into a 2nd template like this:
<html>
<head></head>
<body>
<table border="1">
<tr>
<td>#</td>
<td>Title</td>
</tr>
{{ payload }}
</table>
</body>
</html>
I would have expected that the function's result is inserted into the static table of my template, and that happens indeed but not the way I hoped: somehow the html elements that got created by my function are not recognized as what they shoud be; instead I see that they are rendered as
<tr><td>1</td><
instead of
<tr><td>1</td>
etc.
Result is that the browser does not recognize those elements and prints them together with their contents outside my static table
Questions:
what do I need to do so that my 2nd template recognizes my computed string as a set of html elements?
or is this probably a concept not suitable for my purpose?
I'm assuming you are using handelbars for your templating engine. In that case use:
{{{ payload }}}
Instead of
{{ payload }}
However a more elegant approach would be this:
<html>
<head></head>
<body>
<table border="1">
<tr>
<td>#</td>
<td>Title</td>
</tr>
{{#each payload}}
<tr><td>{{this.position}}</td><td>{{this.title}}</td></tr>
{{/each}}
</table>
</body>
</html>
then just
return msg.payload
again thanks to #als9xd for pointing me into the right direction; his 2nd idea indeed sounds much more elegant but first I couldn't get it to work. After some trial-and-error and looking up documentation for the template node I finally came up with this: removed the function node from my original question and then altered the 2nd template to this code:
<html>
<head></head>
<body>
<table border="1">
<tr>
<td>#</td>
<td>Title</td>
</tr>
{{#payload}}
<tr>
<td>{{position}}</td>
<td>{{title}}</td>
</tr>
{{/payload}}
</table>
</body>
</html>
Difference to #als9xd's example is that I replaced {{#each payload}} with a simple {{#payload}}, plus omitted this when referencing the object keys.
Could this be due to different Node-RED versions?
Anyways this is starting to be much fun!
This is a branch off from this question: Handlebars.JS (w/ Dashbars) parse error "expecting open_endblock got inverse" (There's also a codepen therein.) I posted this there because I thought the problems were related, maybe (since {{else}} can be used with if's or each's,) but that turned out not to be the case.
Specifically:
I'm also having the problem of my outermost {{#each}} looping in such a way that only the last record returned is being output through the template. Everything logs in {{log this}} after that opening {{#each}}, but it's not even hiding in the HTML output somewhere.
So my table SHOULD have 4 rows, and objects 0 through 3 log to the console, but only the fourth item, item 3, is added to the table. Inner each's work as expected, as do any and all other iterator functions; I'm not sure why it's JUST the outer one that's failing. The JSON from which my array of objects is created validates in every single linter I've used, as does my JavaScript. Atom is supposed to have a handlebars linter, but it doesn't seem to actually...umm...work.
I AM using Dashbars with this, (with both its lodash.js and moment.js dependencies,) but this bug has existed since before I started using that library. FWIW, jQuery 2.1.3 IS installed, and loaded before any other library, and all of that is in the <head> tag. (I don't see where it would matter, but just in case.) And since JavaScript is involved, yes, I'm in Chrome. The version of Handlebars is the latest, 3.0.1, using the full version because my use-case doesn't allow pre-compiling. Another NB is that all of this is embedded in a .cfm file (not my choice,) so everything does get run through the CFML interpreter first.
So...I'd managed to outsmart myself again. I've created a fork of my original code, but HTTPS policy in my browser is keeping it from working =-\
The "money" differences are as follows (can't use SO's <ul> because I can't embed code in a bullet-block)
*I'd been trying to jam my template into a <tr> element, to avoid having Handlebars process more lines of code than I thought was necessary:
<tr class="searchResults" id="searchResultsHTML"></tr>
<script id="result-template" type="text/x-handlebars-template">
<!--- To accommodate for that this would all begin and end with a <tr>, I tried this: --->
{{#unless #first}}
<tr>
{{/unless}}
<!--- The rest of the template --->
{{#unless #last}}
</tr>
{{/unless}}
</script>
*In good-programmer fashion, I'd forked my actual file several times, trying different things. (Mostly did this to avoid endless git resetting and branching; I know git's meant for this sort of thing, but I just wanted to be able to refresh old and new tabs at once. Anyway!) Took out those {{unless}} blocks and...apparently, I'd misunderstood what that does (since the official documentation is a bit...scant...on that point. There's no TRUE inverse of {{#if}}) For the sake of those who don't want to bother clicking to the codepen:
<div class="searchResults" id="searchResultsHTML">
<script id="result-template" type="text/x-handlebars-template">
<table id="resultTable">
<tr>
<th>Personal Information</th>
<th>Education</th>
</tr>
<!--- The log tag DOES output all records, even the ones that aren't showing in the HTML. --->
{{#each this}} {{log #index}} {{log this}}
<tr>
<td>{{#with basicInformation}}
<p>{{MASTER_CUSTOMER_ID}} ({{CUSTOMER_STATUS_CODE}})<br />
{{SEARCH_NAME}}<br />
(<span class="lightBlue">ADD:{{ADDOPER}}–{{{d-format 'MM/DD/YYYY' (d-date 'YYYY-MM-DD' ADDDATE)}}}</span>)</p>
<p>DOB:{{{d-format 'MM/DD/YYYY' (d-date 'YYYY-MM-DD' BIRTH_DATE)}}}<br />
{{/with}}</td>
<!--- More table columns; six total --->
<td>{{#each education}}
<p>{{INSTITUTION_NAME}} ({{{d-format 'YYYY' (d-date 'YYYY-MM-DD' BEGIN_DATE)}}}–{{{d-format 'YYYY' (d-date 'YYYY-MM-DD' END_DATE)}}})</p>
{{else}}
<p>No education records found</p>
{{/each}}
</td>
</tr>
{{else}}
<tr class="alert largerError" id="errorMessageRow">
<td class="empty" id="errorMessage" colspan="6">Either there has been an error, or your search did not return any records from any datasource.</td>
</tr>
{{/each}}
</table>
</script>
Yes, I stuck with ColdFusion comments; since those are ONLY for my reference, or the poor next developer, they don't need to go to the client and that way Handlebars isn't taking valuable time rendering them. (I'm already annoyed Handlebars is going to have to churn through my header row and a bunch of other text.) This tool searches our databases for customer numbers that MIGHT represent accidental duplicate accounts, so if you search for John Smith...oi!
So for anyone who Googled their way over here...I'll be glad if this helps even one person avoid spinning their wheels the way I did.
I've recently started to use Handlebars.js and so far I'm loving it!
On my current project, I'm storing a bunch of literal values in data attributes. I'm wondering if there is a way store the full JSON object in a tag for later use.
Current
{{#each this}}
<tr data-id="{{Id}}"
data-prod-code="{{ProductCode1}}"
data-gague="{{Gague}}"
data-rvalue="{{RValue}}"
data-density="{{Density}}"
data-fhaclass="{{FHAClass}}"
data-yards="{{Yards}}"
data-netting="{{Netting}}"
data-film="{{Film}}"
data-compressed="{{Compressed}}"
data-color="{{Color}}"
data-cfd="{{CFD}}"
data-elongation="{{Elongation}}"
data-tensilestrength="{{TensileStrength}}"
data-compressionset="{{CompressionSet}}"
data-title="{{Title}}">
<td>{{ProductCode1}}</td>
<td>{{Title}}</td>
<td>{{Gague}}</td>
</tr>
{{/each}}
Desired
{{#each this}}
<tr data-id="{{Id}}"
data-object="{{FULL JSON for current object in iteration}}">
<td>{{ProductCode1}}</td>
<td>{{Title}}</td>
<td>{{Gague}}</td>
</tr>
{{/each}}
Just in case it is relevant...
The data is returned from an MVC4 action and is called from a $.get() request.
The reason I am wanting to do this is so that I can use a separate Handlebars template without the need to rebuild the data from the data attributes.
In another post on here they recommended using jQuery to store the data in it's internal K,V pair. Unless it has some wicked cleanup ability that I'm not aware of I'm going to avoid this so that it's memory usage doesn't skyrocket. The data that is stored in the data attributes is swapped out quite frequently based on a search term so I don't want it stored in the jQuery object (unless this is a 100% unfounded concern).
SOLUTION:
I ended up needing to make a Handlebars helper.
Handlebars.registerHelper('getObjectJSON', function() {
return JSON.stringify(this);
});
Then my Handlebars template looked like this...
<script id="product-row-template" type="text/x-handlebars-template">
{{#each this}}
<tr data-id="{{Id}}"
data-title="{{Title}}"
data-productcode1="{{ProductCode1}}"
data-object='{{getObjectJSON}}'>
<td class="addProductToCart">{{ProductCode1}}</td>
<td class="addProductToCart">{{Title}}</td>
<td class="addProductToCart">{{Gague}}</td>
<td class="viewProductDetail" style="width: 5%"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></td>
</tr>
{{/each}}
</script>
This allowed me to pass that data object to other Handlebars templates for use in modals/etc...
$("#modalBodyAndFooter").html(marketingModalTemplate($(this).closest("tr").data("object")));
I've got a handlebars conditional statement in a table, and inside that I have a view.
If i remove the conditional then the view template is displayed. If I remove the view template, the straight html is displayed. But if both are there, the view template is never shown, and there is no error shown in the console:
<script type="text/x-handlebars" data-template-name="application">
<table>
<tr>
<td>
<button {{action 'click_me'}}>Click me</button>
</td>
</tr>
{{#if controller.new_visible}}
{{#view App.MyView}}
<tr>
<td>
Hello!
</td>
</tr>
{{/view}}
{{/if}}
</table>
</script>
What am I doing wrong?
Further to this, it would seem (from http://emberjs.com/guides/understanding-ember/the-view-layer/, section 4) that Ember creates virtual views for Handlebars logic block helpers (like {{#if}}, {{#unless}}, and so on. They're not part of the regular view hierarchy, so they don't play nicely with regular nested child views.
I suspect the answer to this is to be found somewhere in this line:
"When the path passed to an {{#if}} or {{#with}} changes, Ember automatically re-renders the virtual view, which will replace its contents, and importantly, destroy all child views to free up their memory." (from the section linked above).
My best guess is that the {{#if}} block is re-rendering on insert, which destroys the 'child' {{#view}} block within it.
move your condition inside view. see this jsFiddle
{{#view App.MyView}}
{{#if controller.new_visible}}
<tr>
<td>
Hello!
</td>
</tr>
{{/if}}
{{/view}}