I'm having trouble rendering a simple table using dust.js
This template:
<table>
{#hours}
<tr>
<td>{dayText}</td>
<td>{hoursText}</td>
</tr>
{/hours}
</table>
Outputs:
<table>
<tr>
<td></td>
<td></td>
</tr>
</table>
Whereas changing the template to an unordered list works just fine:
<ul>
{#hours}
<li>{dayText} {hoursText}</li>
{/hours}
</ul>
Here is the model:
{
hours: [
{dayText: "Mo-Fri ", hoursText: "11:00-22:00"},
{dayText:"Sat", hoursText: "12:00-22:00"},
{dayText:"Sun", hoursText: "12:00-21:00"}
]
}
The templates are compiled in browser using dust-full-1.1.0.js
I'm using the LinkedIn fork.
Have I found a bug or have I missed something?
I have found the cause of my problem. My template sources are loaded from the within the page:
$("#dust-templates").children().each(function() {
var compiled = dust.compile($(this).html(), $(this).attr('id'));
console.log(compiled);
dust.loadSource(compiled);
})
The problem is that the browser does not deliver the template html verbatim:
{#hours}
{/hours}
<table>
<tbody><tr>
<td>{dayText}</td><td>{hoursText}</td>
</tr></tbody>
</table>
So it is not strange that this does not work as expected. If I store the template as a javascript variable it works.
Related
I don't have much experience with Vue and facing an issue with redering a component with root as TR element.
I read this in the docs https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats and tried adding the component using is property, but that didn't help either.
Check the code below.
Vue.component('car',{
props: ['number']
})
new Vue({
el: "#app"
})
<div id="app">
<table>
<tr is='car' inline-template number='123'>
<tr>
<td>car no</td>
<td>{{number}}</td>
</tr>
</tr>
<tr is='car' inline-template number='456'>
<tr>
<td>car no</td>
<td>{{number}}</td>
</tr>
</tr>
</table>
</div>
This errors out:
Error compiling template:
Inline-template components must have exactly one child element.
How can I fix this? Appreciate the help.
Fiddle https://jsfiddle.net/8d65gvua/
Update ----
More findings
If i wrap my tr in a template it works
<tr is='car' inline-template number='456'>
<template>
<tr>
<td>car no</td>
<td>{{number}}</td>
</tr>
<template>
</tr>
I have no clue why. Shouldn't the issue with tr be fixed with using the is property? Why do we need to do this?
I've never used inline-template before, my guess is that you are using it to pass the number to your "inner template"? You can achieve the same thing by using slots. (Reference)
I did a test but using slots instead, which I think it's a better way to cater for passing "inner templates". Also given the below warning in the documentation with regards to inline-template I would opt for slots.
However, inline-template makes the scope of your templates harder to
reason about. As a best practice, prefer defining templates inside the
component using the template option or in a <template> element in a
.vue file.
Example
Vue.component('my-row', {
template: `
<tr>
<slot></slot>
</tr>
`
})
The above is a component to define a single table row. The <slot> element is basically a placeholder for any content you want to pass into this template.
Usage
<div id="app">
<table>
<tr
is="my-row"
v-for="todo in todos"
>
<td>{{ todo.text }}</td>
</tr>
</table>
</div>
What will happen here is that <td>{{ todo.text}}</td> will be placed instead of the <slot> element in the my-row component. You can have multiple <td> elements, whatever content you put in the my-row will appear instead of <slot>.
JS Fiddle: https://jsfiddle.net/x793nub6
PS: Slots!
I don't want to make the answer too long, but keep in mind that with slots you can have a super customisable components, of course this all depends on your needs. But I would suggest having a look at the documentation and trying out a few expirements.
Update - If you still want to use inline-template
I went through the JS Fiddle you shared once more and realised what's the issue you are having. Basically vue is saying that one child needs to be passed. I guess there's some mixup since there's a <tr> nested in another <tr>. To fix it wrap the inner <tr> with a <template> element.
From:
<tr is='car' inline-template number='123'>
<tr>
<td>car no</td>
<td>{{number}}</td>
</tr>
</tr>
To:
<tr is='car' inline-template number='456'
<template>
<tr>
<td>car no</td>
<td>{{number}}</td>
</tr>
</template>
</tr>
Consider the following table template.hbs:
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{{#* inline "testTableRowTemplate"}}
<tr>
<td>{{Id}}</td>
<td>{{Name}}</td>
<td>{{Value}}</td>
</tr>
{{/inline}}
{{#* inline "testTableEmptyTemplate"}}
<tr>
<td colspan="99">There are no rows</td>
</tr>
{{/inline}}
{{#* inline "testTableLoaderTemplate"}}
<tr>
<td colspan="99">
<i class="fa fa-spinner fa-spin fa-3x fa-fw"></i>
<span class="sr-only">Loading...</span>
</td>
</tr>
{{/inline}}
</tbody>
</table>
From code, I am loading this template with an ajax call and compiling it to a HandlebarsTemplateDelegate which I use to dynamically refresh the table when new data is available. However, I would like to be able to target just a row, so I need a HandlebarsTemplateDelegate of just the "testTableRowTemplate" partial to be able to do this. Is there any way to do this from code? I've tried each of the following after compiling the main template but can't get access to the partial.
var rowPartial = Handlebars.partials["testTableRowTemplate"]; //doesn't work
And
var rowPartial = Handlebars.compile("{{>testTableRowTemplate}}"); //doesn't work
I'm hoping to avoid having multiple .hbs files for the table so ideally I'd be able to define reusable partials in the same file. Any ideas?
It does not appear that you can reference these from code. My workaround is as follows:
Table.hbs
You'll notice a make use of a custom helper registerPartial here. This allows me to dynamically register a handlebars helper.
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{{#registerPartial "testTableRowTemplate"}}
<tr>
<td>{{Id}}</td>
<td>{{Name}}</td>
<td>{{Value}}</td>
</tr>
{{/registerPartial}}
</tbody>
</table>
Page.ts
At the page level, I define the helper registerPartial. It's important to note that this will be registered to the global Handlebars environment. Therefore all partial names need to be unique. This could be solved by declaring a Handlebars environment for each component, i.e. Table, List, etc. but that is beyond the scope of what I need.
Handlebars.registerHelper('registerPartial', (name, options) => Handlebars.registerPartial(name, options.fn));
Table.ts
Following loading the Table.hbs template by way of Handlebars.compile(templateContentFromTablehbs), I can now get compile references to the row partials by doing this:
this._rowTemplate = Handlebars.compile(`{{>${this._rowPartialName}}}`);
Now I can re-use this._rowTemplate to do partial refreshes of table rows without having to refresh the whole thing.
So I am trying to render an array of objects with handlebars in my node app, and it seems to be picking up the objects since table rows are being added to the DOM, but none of the inner values are displaying for some reason. Also, I only have 4 results but there are 6 rows. I tried many different tweaks after going through documentation and similar questions, to no avail.
Here is the html
Course Catalog
<table class="jsonTable">
<thead>
<tr>
<th id="year">Year</th>
<th id="term">Term</th>
<th id="code">Code</th>
<th id="title">Title</th>
</tr>
</thead>
<tbody>
{{#each this}}
<tr>
<td>{{year}}</td>
<td>{{term}}</td>
<td>{{code}}</td>
<td>{{title}}</td>
</tr>
{{/each}}
</tbody>
</table>
main.js
$(".jsonTable").hide();
$("#catalog").on("click", function(){
console.log('catalog button clicked');
$.get('/catalog', function(data){
console.log(data);
$("#catalog").hide();
$(".jsonTable").show();
});
});
});
Console
DevTools
I'm trying to use angular-smart-table for grid in my new AngularJS app. According to the document, to sort a column, I should use the st-sort directive like bellow:
<th st-sort="firstName">first name</th>
<th st-sort="lastName">last name</th>
However, I'm trying to re-use the piece of code for not only one table, so I don't know the table field names in advance until the run-time. I'm doing something like bellow:
<script type="text/ng-template" id="content1">
<div ng-repeat="table in $ctrl.tables">
<h2>{{table._tableName}}</h2>
<table st-table="table._data" class="table table-striped">
<tr>
<th ng-repeat="fieldName in table._fieldNames" st-sort="{{fieldName}}">{{fieldName}}</th>
</tr>
<tr ng-repeat="data in table._data">
<td ng-repeat="fieldName in table._fieldNames">{{$ctrl.formatCell(table, data, fieldName)}}</td>
</tr>
</table>
</div>
</script>
And this cannot work (cannot sort, other functions OK). I tried bellow it does not work, seems the st-sort has to be in the <th> tag.
<th ng-repeat="fieldName in table._fieldNames"><span st-sort="{{fieldName}}">{{fieldName}}</span></th>
And bellow does not work as well:
<tr>
<span ng-repeat="fieldName in table._fieldNames">
<th st-sort="{{fieldName}}">{{fieldName}}</th>
</span>
</tr>
Today I tried to develop a directive and use it in the comment by setting restrict to "M" to solve the above. Then I got a new problem: I'm using UI-Router in this app and I cannot get the table contents in my directive, because UI-Router states have isolated scopes and it only supports controllers but does not support directives. The author may think supporting directives is not necessary (yes in most cases, but this kind of assumptions are always dangerous).
I'm Trying two possible ways: 1., put the field names to the session/local storage for sharing as a work-around; 2., abandon UI-Router. Appreciate anyone providing a better solution.
I have situation when i need to repeat multiple tbody in one table, what im trying to do is to make every tbody directive and i want its template to append to table, but when im put the directive inside the table tag its put his content outside the table.
the cart draw directive:
return {
restrict : 'AE',
templateUrl: 'client/cart/views/cart-draw.html',
scope : {},
replace: true,
controller : controller
}
the tpl:
<tbody ng-repeat="draw in CartService.items.draws track by $index">
<tr>
<td>
//some content
</td>
</tr>
</tbody>
the html:
<table class="table">
<cart-draw></cart-draw>
</table>
here is the plunker, if you inspect element you will see the tbody is out of the table:
http://plnkr.co/edit/9wEGFE5K0w0ayp6qo8Lx?p=preview
That is happening because the <table> tag doesn't recognize your custom <cart-draw> element as a valid child.
I would modify like so: http://plnkr.co/edit/u88N76h5dvLAvR3C1kRs?p=preview
index.html
<table><tbody cart-draw></tbody></table>
cart-draw.html
<tbody ng-repeat="body in bodies">
<tr>
<td>
{{body}}
</td>
</tr>
</tbody>
app.js
$scope.bodies = ["hello1", "hello2", "hello3"];
This is a long pending issue in Angular's Github repo.
https://github.com/angular/angular.js/issues/1459
I also stumbled upon to this problem once (with SVG). It happens because before rendering the directive, the template is cross verified with HTML DTD and alone doesn't make sense (without tag) and so it doesn't work. Same applies to <tr> and <li>
There are many solutions which uses ng-transclude and link functions to wrap it in respective parent tag and then use it.
This is actually a known & strange issue when it comes to directives & <table>'s.
I believe it actually comes in as invalid HTML at first, causing it somehow appear outside of your <table> tag.
Try making cart-draw an attribute of a <tbody>:
<table>
<tbody cart-draw></tbody>
</table>
plunker Example
This will make it work as intended.