Handlebars.js: Handlebars doesn't work in tables

Created on 15 Aug 2013  ·  37Comments  ·  Source: handlebars-lang/handlebars.js

http://jsfiddle.net/cwYhN/3/

Notice that the same handlebars #each succeeds when outside tables. My jsfiddle includes two bordered outputs demonstrating this.

Most helpful comment

I know this issue is quite old, but I ran into this recently due to Sendgrid's email template editor not following these best practices. If anyone else ends up on this issue from Google for that reason, a workaround is to place the handlebars code in a HTML comment.

<table >
  <tbody>
    <!-- {{#each items}} -->
    <tr>
      <td>{{this.key}}</td>
      <td>{{this.value}}</td>
    </tr>
    <!-- {{/each}} -->
  </tbody>
</table>

that produces the following output

<table>
  <tbody>
    <!--  -->
    <tr>
      <td>my key</td>
      <td>my value</td>
    </tr>
    <!--  -->
    <!--  -->
    <tr>
      <td>my key 2</td>
      <td>my value 2</td>
    </tr>
    <!--  -->
  </tbody>
<table>

(NOTE: I have not verified this with Handlebars directly, just on Sendgrid's template editor)

All 37 comments

For what it's worth, if the {{each}} tags are placed outside of the table, the other tags work. This of course has the undesirable effect if duplicating the table repeatedly, but perhaps it helps narrow down the problem:

http://jsfiddle.net/cwYhN/6/

This looks like it might be an issue with jQuery or something else... when you do console.log(frag); you see that the {{#each}} helper is pushed outside of the table even before attempting to compile it with handlebars...

I updated the jsfiddle to put the template inside a <script type="text/x-handlebars-template"></script> tag and it seems to be working fine...

http://jsfiddle.net/doowb/6GdPy/1/

Yeah, I just discovered that myself:
http://jsfiddle.net/cwYhN/10/

For what it's worth, I think it's the browser throwing out what it considers to be invalid html, rather than a jquery flaw.

Yeah, I wasn't sure if it would be a jQuery things or the native browser function that jQuery calls. Either way, it doesn't like the {{ }} between tbody and tr.

It would be nice if handlebars built in some sort of way to operate within this constraint. For example, being able to place the "each" within a tag while having the implicit understanding that it iterates over the contents of the tag. I might imagine it looks like:

<table>
  <tbody handlebars="{{each}}">
    ... these get iterated ...
  </tbody>
</table>

Allowing for something like this would prevent the browser from stripping out the important each statement, and it could piggyback on the closing tag that it belongs to (/tbody)

As you guys have already alluded to, the HTML parser is confused by the handlebars markup and comes up with this as the DOM structure that you try to convert to a handlebars template:

            {{#each activity}}

            {{/each}}
        <table style="border:1px solid #ccc;">
        <thead>
            <tr><th>Month</th>
            <th>Imps</th>
            <th>Clicks</th>
            <th>Spend</th>
        </tr></thead>
        <tbody><tr>
                <th>{{month}}</th>
                <td>{{impressions}}</td><td>{{clicks}}</td><td>{{spend}}</td>
            </tr></tbody>
    </table>

Should use something like the script behavior above, javascript strings, or precompilation to work around this.

Closing this out as this issue is more of a browser implementation detail than a bug in handlebars itself.

Just wasted half a day before finding out that {{#each}} doesn't work in tables. It would be really helpful if this had simply been stated in the documentation.

Each works perfectly fine in tables if loaded properly. How are you loading your templates?

@preichelt You should be loading handlebars from within script tags to prevent the browser from messing up {{#each}} in tables. You can read more on stack overflow:

http://stackoverflow.com/questions/15386276/why-should-we-wrap-our-templates-inside-script-blocks

This bug wasted two hours of my life and now it is 10:30pm at Saturday, I am working in my office just to solve this bug until I found this issue and [Official Block Doc]
(http://handlebarsjs.com/builtin_helpers.html) says nothing about that.

In case it wasn't clear, this is not the fault of handlebars, but yours for not knowing how HTML works: https://html.spec.whatwg.org/multipage/syntax.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser

So, you assume everyone who tries to use Handlebarsjs should have a good understanding of how HTML parser works? And if not, then it is _the user's fault_, not the fault of the clearness of documentation? This assumption does not make sense. Another heavy and complex template engine, Angularjs, use Html compatible syntax as their template syntax, for example, if I need to loop table, I can use

<tr ng-repeat="dto in tableData">

And Angularjs keeps the internal status of loop by using Html comments:

<!-- ngRepeat: column in row -->
<tr ng-repeat="dto in tableData">

Angualrjs tries to respect and follow the syntax of Html as much as possible and avoid the assumption successfully. But Handlebarsjs's template syntax does not so it exposes the Html parser problem to users.
If you developer can't solve the problem at your side and then you have the _duty_ to tell your users to be aware of the problem if you really care about your project and your users from all over the world.
Anyway, I will open a document issue because _I believe I am not the first one who came across this headache issue and I will not be the last one._

Angular runs in the DOM, so it is DOM aware. Handlebars works with strings only and has no concept of the DOM.

@profullstack: As @stevenvachon said, Handlebars does not work with HTML, but only with text. But this may not be obvious from the docs, since all examples are HTML. It personally use it also to generate markdown...

If you could tell me, what kind of text you would have needed in the docs, I would be happy to add it. However, the general usage instructions (including the part with the <script>-tag are already there on the main page of http://handlebarsjs.com/. That's why I'm not sure what your need is here.

@profullstack I get that you're frustrated about having "wasted time", but in my opinion, you've got this entirely backwards.

No open source project nor contributor has any duty to you. Tools like handlebars are created and maintained for free and with almost no thanks, to try to making other developers lives easier. If you were not able to use any open source projects what-so-ever, how long would your work have taken?

I can tell you, first hand, that coming along and writing comments with a tone such as this will have the opposite to the desired effect. Simple carrot vs stick, if you want someone to do something for you, ask nicely or even better raise a PR and do it yourself.

@ErisDS Thank you for your advice. Maybe my first comment was too strong, but I totally agree with your say about:

to try to making other developers lives easier

And why I drop Angularjs to use Handlebarsjs? _Because I believe Handlebarsjs will make my life easier than Angularjs._
So, just kindly add some notice words in the documentation to help your thousands of open source users to avoid the potential problem is against your will of "making other developers lives easier"? I guess it's no.
For:

No open source project nor contributor has any duty to you.

Yes, it's true. You can start an open source project and then drop it, it's totally OK. But when your project grows, you spend more time and heart on your project, you are loving more about your project and you also want to share it with the world. At this time you will start to _care about your users_ and _care about your documentation_. Handlebarsjs is at this stage, this is not a small project, it's a popular project and used by programmers all over the world, even in South Korea, in China and so on.
As far as I know, most open source programmers on Github are proud of their project and their README.md file, and wish more and more people to use their tools and codes.
So if:

  • You don't want to care about your open source users
  • I am the last one who will come across this issue
    You can totally ignore my technical analysis in the first comment and user-kindly discussion in this comment.
    Besides, open source project is not something like _charity_ you give the world, it's the _communication bridge_ between you and other programmers.

@nknapp Thanks for your reply.
Maybe my first comment is too strong and I apologize for that and to people who read my comment.
From the first sight, Handlebars works simply like _a text replacement tool_ and it's really easy to understand. So when I write something like this:

{{#each myListData}}
                        <tr>{{name}}</tr>
{{/each}}

If it is just simply replacement, I would expect something like this:

<tr>nameA</tr>
<tr>nameB</tr>

But when came across HTML, it behaves something different, and it is because of the natual of HTML parser just as @stevenvachon kindly noted.
So I guess just one sentence will be enough below the "The each block helper" section:

WARNING: please use script block when you try to use #each block inside table, due to the know issue of [How HTML parse works(thanks for @stevenvachon's reference](https://html.spec.whatwg.org/multipage/syntax.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser)

I really appreciate it if you will share your opinion. Thanks.

The thing is, that this has nothing to do with the #each-helper. You should just never write a template directly into HTML. An statement like this should be on the main page, but the question is: Would you have read it there?

Maybe we could a a warning like yours below the example on the landing-page...

Just another note:

I would not use the script-blocks method in production. Instead I would use Webpack or another tool to precompile the templates.

@nknapp Yes, I guess your opinion is right: we should never write a template directly into HTML. So I guess on the landing-page, the first "Getting Started" section should use script as example and in the below gray block, we can change

You **can** deliver a template to the browser by including it in a <script> tag.

to

You **should always** deliver a template to the browser by including it in a <script> tag.

I guess your suggestion:

**never** write a template directly into HTML

is totally right, and should be put at the landing-page to let everyone follow this and it can avoid many problems. I guess it's a good idea, do you think so?

That explanation doesn't work, though, because they might comprehend it as:

<script type="text/x-handlebars">
  {{#each list as |item|}}
    <td>{{item}}</td>
  {{/each}}
</script>

@stevenvachon Yes, just putting templates inside the script is not enough in your example, maybe we need to add more notes about the right way to use a template, we would like to hear your suggestions about this.

Perhaps:

For most production applications, you will not want to use the parser and string templates. Look into precompilation.

@stevenvachon Thanks for your comments and I guess it will be better if we put your HTML parser link together with your comments:

For most production applications, you will not want to use the parser and string templates. Look into precompilation. There are some known issues if you use a template directly into HTML, for example, the #each block helper won't work inside tables due to how HTML parser works.

It should probably be split into

  1. Directly below the <script>-tag example

It is important that you put the template inside a <script>-tag. Do not put it into the HTML directly or the HTML-parser might modify it (for example, if it contains a table)

  1. Change the bullet point about precompilation to.

Please note that this approach is not recommended for production applications. It is also possible to precompile your templates. [...]

Actually, I personally would not use Handlebars in the browser anymore: There are many other frameworks out there (not only Angular and React, but also Vue and Ractive). This video is pretty cool and shows the problem...

I use Handlebars on the server and as static-page generator, but for browser-rendering, I think there are better ways.

@nknapp I started writing handlebars-html-parser for use in bringing it to the browser through VDOMs, but it met resistance.

@nknapp Thanks for your reply. Your comments include:

  1. The importance of putting template inside a

@nknapp Thank you, I believe a lot of people will benefit from your changes.

I know this issue is quite old, but I ran into this recently due to Sendgrid's email template editor not following these best practices. If anyone else ends up on this issue from Google for that reason, a workaround is to place the handlebars code in a HTML comment.

<table >
  <tbody>
    <!-- {{#each items}} -->
    <tr>
      <td>{{this.key}}</td>
      <td>{{this.value}}</td>
    </tr>
    <!-- {{/each}} -->
  </tbody>
</table>

that produces the following output

<table>
  <tbody>
    <!--  -->
    <tr>
      <td>my key</td>
      <td>my value</td>
    </tr>
    <!--  -->
    <!--  -->
    <tr>
      <td>my key 2</td>
      <td>my value 2</td>
    </tr>
    <!--  -->
  </tbody>
<table>

(NOTE: I have not verified this with Handlebars directly, just on Sendgrid's template editor)

Thanks for that tip @gurpreetatwal . You saved me hours of debugging this evening with that tidbit for SendGrid!

@gurpreetatwal but why?

@Lazarencjusz why in regards to why the fix works or why is SendGrid email template editor doesn't work correctly?

Hi! When you use div elements instead of a table, will be work.
Also rewrite all table elements to div.

@gurpreetatwal 's Solution works for me on Mailchimp/Mandrills templating for handlebars. Thanks for saving me a ton of trouble!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DylanPiercey picture DylanPiercey  ·  7Comments

rizen picture rizen  ·  6Comments

mattkime picture mattkime  ·  4Comments

novwhisky picture novwhisky  ·  4Comments

ShintaroOkuda picture ShintaroOkuda  ·  7Comments