I am completely new to dust (linkedin), just working on my first little template. After writing it the obvious (but long) way I thought of a way to optimize using an inline partial.
The long version looks like this:
{#parcours}<tr class="pcsel_pc" id="{id}">
<td class="pcsel_exp_btn"><a href="#" class="list{?exp}Hide{:else}Exp{/exp}Btn">
<span class="glyphicon glyphicon-{?exp}minus{:else}plus{/exp}"></span></a></td>
<td class="pcsel_col">{name}</td><td class="pcsel_col pcsel_num">{count}</td>
</tr>
{?exp}
{#variants}
<tr class="pcsel_var{?sel} pcsel_sel{/sel}" id="{id}" >
<td class="pcsel_col"> </td><td class="pcsel_var pcsel_col">{name}</td>
<td class="pcsel_col pcsel_num">{count}</td>
</tr>
{/variants}
{:else}
{#variants}
<tr class="pcsel_var pcsel_hide" id="{id}" >
<td class="pcsel_col"> </td><td class="pcsel_var pcsel_col">{name}</td>
<td class="pcsel_col pcsel_num">{count}</td>
</tr>
{/variants}
{/exp}
{/parcours}
Explanation:
I have a context parcours that contains an inner context variants. If the variable exp does not exist in the outer context, I want to use a class pcsel_hide in the inner context.
This solution works but the code for the inner context is contained twice which is kind of stupid. So I thought of a way to use an inline partial which is conditionally set in the outer context and used in the inner context:
{#parcours}<tr class="pcsel_pc" id="{id}">
<td class="pcsel_exp_btn"><a href="#" class="list{?exp}Hide{:else}Exp{/exp}Btn">
<span class="glyphicon glyphicon-{?exp}minus{:else}plus{/exp}"></span></a></td>
<td class="pcsel_col">{name}</td><td class="pcsel_col pcsel_num">{count}</td>
</tr>
{?exp}{<hide/}{:else}{<hide} pcsel_hide{/hide}{/exp}
{#variants}
<tr class="pcsel_var{+hide/}{?sel} pcsel_sel{/sel}" id="{id}" >
<td class="pcsel_col"> </td><td class="pcsel_var pcsel_col">{name}</td>
<td class="pcsel_col pcsel_num">{count}</td>
</tr>
{/variants}
{/parcours}
This version is nice and short, but it doesn't seem to do the job. I see the class pcsel_hide all the time even if the outer context contains exp and thus uses the correct classes.
Any ideas ?
This is because inline partials are statically evaluated before template rendering begins. The last version of an inline partial defined with the same name wins.
Inline partials cannot be conditionally evaluated like this. What you probably want instead is to use a logic helper like {#eq}.
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.
I want to do the exact same thing as this Fiddle (The example is mine) but using angular. In the normal HTML I have a parent tr with its own child tr so when I run the example I can see
that the child records that are related to its parent tr
Then I start doing this in Angular but now Im confused because I'm using ng-repeat and a JSON structure to populate the data so now I only have this piece of code, I put this as the parent but I don't know how to deal with the "child":
<tbody>
<tr ng-repeat="d in category" class="parent">
<td class="expand"></td>
<td ng-bind="d.cat"></td>
<td ng-bind="d.LW$"></td>
<td ng-bind="d.LW"></td>
<td ng-bind="d.L4W"></td>
<td ng-bind="d.L13W"></td>
<td ng-bind="d.L52W"></td>
</tr>
</tbody>
Here is my code with the angular version: https://jsfiddle.net/228wkfej/
I am new to knockout, and I can't seem to find this problem stated or documented elsewhere, though I suspect I might be missing something.
I have a foreach in my knockout template HTML as such:
...
<tbody data-bind="foreach: cfeeds">
<tr class="datarow" data-bind="css:{danger:discovered()==false}">
<td><a title="View this feed" data-bind="text: name,attr: {href: view_url}"></a></td>
<td data-bind="text: description, css:{'text-danger':discovered()==false}"></td>
<td class="text-center" data-bind="text: sched_type"></td>
<td class="text-center" data-bind="text: sched_data"></td>
<td class="text-center" data-bind="text: moment(last_poll()).calendar()"></td>
<td class="text-right">
<div class="feedgraph">
<!-- D3 chart container -->
</div>
</td>
</tr>
</tbody>
...
In the last DIV with class="feedgraph", I add (in this case) a D3 chart, which works fine when the DIV is outside the foreach. However, it appears that knockout will rerender the entire TR every time the JSON poll returns, whether the JSON contains a changed value or not, and my .feedgraph div gets emptied.
Relevant viewmodel blocks:
...
self.cfeeds = ko.observableArray([]);
...
self.loadFeeds = function() {
postJSON(ajax_uri+'get_site_feeds/',{'site_id':selected_site_id},function(d){
self.cfeeds.removeAll();
$.each(d['configured_feeds'],function(i,v) {
self.cfeeds.push(new Feed(v));
});
setTimeout(vm.loadFeeds,5000);
});
};
...
$(function(){
vm.loadFeeds();
});
...
It seems to me that the knockout rendering should occur at the atomic TD level only when a bound value changes, rather than at the TR level as it appears to be doing.
Hence, my question is: Is there a way to keep knockout from rerendering the entire row in a foreach, and instead only rerender the TD cells that have a data-bind attribute and leave my D3 charts alone?
I can do this in Angular.js:
<tr ng-repeat="cust in customers">
<td>
{{ cust.name }}
</td>
</tr>
Which would iterate through all the customers by putting each in a <tr> of its own.
But what if I wanted two customers in one <tr>? How would I accomplish that?
I normally do that by messing around with indexes and modulus values, but I'm not sure how to do that here.
It turns out this can be done without any custom filters or changing the format of your data, though it does require some extra markup. I thought this woudn't be possible at first as you can't use a span or anything similar within the table for your ng-repeat. This answer however, points out that you can use a tbody.
So it's just a case of using the ng-if directive (which renders html if the expression is true), the $index variable (provided by ng-repeat) and the $even variable (which is also provided by ng-repeat and is true when $index is even
I've created a demo in this Plunker
<div ng-controller="MainCtrl">
<table>
<tbody ng-repeat="cust in customers">
<tr ng-if="$even">
<td>{{customers[$index].text}}</td>
<td>{{customers[$index+1].text}}</td>
</tr>
</tbody>
</table>
</div>
This would of course only work if you have two columns, what if you have more? Well you can also put a full expression into ng-if rather than just a variable. So you can use modulus values like this:
<tbody ng-repeat="cust in customers">
<tr ng-if="($index % 3) == 0">
<td>{{customers[$index].text}}</td>
<td>{{customers[$index+1].text}}</td>
<td>{{customers[$index+2].text}}</td>
</tr>
</tbody>