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}}
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.
I am trying to use a link-to around a whole table row. Here is how I am setting it up:
{{#each model as |servicerequest|}}
{{#link-to 'servicerequest.detail' servicerequestId tagName="tr"}}
<td>
{{servicerequest.status}}
</td>
<td>
<strong>{{servicerequest.srdescription}}</strong>
</td>
<td>
{{servicerequest.priority}}
</td>
{{/link-to}}
{{/each}}
I see the data but it is not a clickable link, just text. Any ideas on what I am doing wrong?
Consider using divs with flexbox and proper styling. Tables are not the best option and are a quite ancient solution already.
Some examples:
https://hashnode.com/post/really-responsive-tables-using-css3-flexbox-cijzbxd8n00pwvm53sl4l42cx
The link-to will not generate a href attribute if you use any tagName other than a. See changes in 1.4.0.
So maybe try putting the link-to helper around the tags.
I have a page which contains detail sections that are common across multiple pages of my app. I am trying to reduce code redundancy and create these mini views that will be loaded with the page.
In the main detail page I have a section that I am trying to load using ng-include
<div ng-show="checkProducts()">
<hr style="margin-bottom:5px!important"/>
<p><strong><em>Products</em></strong></p>
<div class="bs-associationPanel">
<ng-include src="commontemplates/productView/shoes"></ng-include>
</div>
</div>
I can't use routing here as this is acting like a partial view within the main detail page which is already using routes to load it and bind it to the controller.
The src value is a path in the APP to an html page called productView.html
I wrapped it in a script ng-template tag with an id
<script type="text/ng-template" id="shoes">
<table class="table table-condensed table-responsive">
<thead>
<tr>
<th>brand</th>
<th>color</th>
<th>size</th>
</tr>
</thead>
<tbody ng-repeat="s in detail.shoes">
<tr>
<td>{{s.brand}}</td>
<td>{{s.color}}</td>
<td>{{s.size}}</td>
</tr>
</tbody>
</table>
</script>
I was hoping this would work but when I render the page I get no temple and looking at the element explorer I see the following
<!-- ngInclude: undefined -->
I think I am close I just don't know what I am missing. Any suggestions on how this could be accomplished or Can this be accomplished?
Basically the script's with type text/ng-template are read by angular & angular consider it as template then put those templates inside $templateCache service for faster retrieval.
src attribute should have template name enclosed with ' single quotes, so that it can look up through $templateCache for getting the template.
<ng-include src="'commontemplates/productView/shoes'"></ng-include>
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 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.