In Aurelia, can a slot be used in a repeat.for binding? - aurelia

I'd like to create a custom element that loops through an array and applies the to each item in the array. For example, the view template of the custom element would contain something like:
<div repeat.for="i of items">
<div with.bind="i">
<slot></slot>
</div>
</div>
When I remove the repeat.for and with.bind attributes, the slot displays a single time. Is there a way to make it repeat for each item in the list?

No, you cannot use slots with repeat.for or bind today. To do this you have to use replaceable parts. For example:
<div repeat.for="i of items">
<div with.bind="i">
<template replaceable part="content"></template>
</div>
</div>
Usage:
<my-component>
<template replace-part="content">Some Content - ${somePropertyOfI}</template>
</my-component>
Runnable example: https://gist.run/?id=29aa1c1199f080c9ba0e72845044799b

Related

Vue Component that Can Override What Child Renders Dynamically

I am working on a component (InfoCard) that should be able to render any number of fields passed into it with a 'fields' prop, as an array of json objects with a name, value, and some styling options. For certain fields, I want to be able to override what component is used to render, but do it from the parent (Table) rather than inside the InfoCard component, as it should be generic. My first thought was to use a <component :is='field.component'></component>, where it will render as plaintext if field.component is not defined, but to my understand it will be difficult to pass in any potential children necessary for the <component/>. My second thought is to use named slots from within the parent, but I don't think this is possible either in a good way. I'll show my current code.
In my example, I want to be able to detect if the field being rendered is 'status', and if it is, use a different rendering mechanism than displayValue(attribute), without hardcoding it inside InfoCard; I want the parent to be able to override this rendering conditionally. Is there a way to do this in Vue? Thanks
From Table, where data.records is an array of JSON objects:
<info-card
v-for="(record,index) in data.records"
:key="index"
:fields="record"
>
<div v-for="key in Object.keys(record)" :key="key">
<template v-if="field.name=='status'" v-slot:[`${field.name}_value`]>
<p> Field is status !</p>
</template>
</div>
</info-card>
From InfoCard:
<template>
<el-col
:lg="3"
:md="3"
:sm="3"
:xs="3"
v-for="(attribute, index) in fields"
:key="index"
class="attribute"
>
<div
#click="$emit('fieldClicked', attribute)"
>
<el-row
:class="`mid-gray f6 clipped fw5-ns m-b-10 ${attribute.nameClasses}`"
:title="displayName(attribute)"
:style="attribute.nameStyle?attribute.nameStyle:''"
>
<slot
v-if="Object.keys($scopedSlots).includes(`${attribute.name}_name`)"
:name="$scopedSlots[`${attribute.name}_name`]"
>
</slot>
<div v-else>
{{ displayName(attribute) }}
</div>
</el-row>
<el-row
:class="`mid-gray f6 clipped fw5-ns m-b-10 ${attribute.valueClasses}`"
:title="displayValue(attribute)"
:style="attribute.valueStyle?attribute.valueStyle:''"
>
<slot
v-if="Object.keys($scopedSlots).includes(`${attribute.name}_value`)"
:name="$scopedSlots[`${attribute.name}_value`]"
>
</slot>
<div v-else>
{{ displayValue(attribute) }}
</div>
</el-row>
</div>
</el-col>
</template>

Vue to render tr array recursively

I have to create collapsible tree with table data using bootstrap. This look the following way
<tr><td>Parent node1</td><td>...</td><td>...</td></tr>
<tr><td>Subparent node1.1</td><td>...</td><td>...</td></tr>
<tr><td>Subsupparent node1.1.1</td><td>...</td><td>...</td></tr>
<tr><td>Subsupparent node1.1.2</td><td>...</td><td>...</td></tr>
<tr><td>Subparent node1.2</td><td>...</td><td>...</td></tr>
<tr><td>Subsupparent node2.1.1</td><td>...</td><td>...</td></tr>
<tr><td>Subsupparent node2.1.2</td><td>...</td><td>...</td></tr>
I decided to create Vue component rendering on each level html with own data and collection of children via recursion:
<template id="tree-item">
<tr :key="currentNode.id">
<span #click="setExpanded(currentNode)">
<div style="display: inline-block;">
<span class="treegrid-indent" :style="{width: 15 * level + 'px'}"></span>
<span class="treegrid-expander fa" :class="currentNode.isExpanded ?'fa-chevron-down':'fa-chevron-right'">
</span>
</div>
{{level}} {{currentNode.id}}
</span>
</tr>
<tree-item v-for="item in currentNode.items" :currentNode="item" :level="level+1"> </tree-item>
</template>
But i get Vue error:Component template should contain exactly one root element.
Are there any ways to solve the task?
If you can relax your requirements to use a list instead of table then you can try something like this:
<div class="folder_container">
<ul class="no_list no_space">
<FolderTree :folder="folders" :level="0"/>
</ul>
</div>
And the FolderTree component's template looks like this:
<template>
<li>
<div>
<div class="folder_name">{{ folder.name }}</div>
</div>
<ul v-if="folder.child && folder.child.length > 0" class="no_list no_space">
<FolderTree
v-for="(fold,idx) in folder.child"
:key="fold.id"
:folder="fold"
:level="level + 1"
:last="1 + idx === folder.child.length"
/>
</ul>
</li>
</template>
If you need to use a table - then you will have to somehow transform your nested objects into a linear list/array of items. Otherwise you won't be able to overcome the limitation for only 1 root element in templates (unless you use render functions).

VueJS slot in v-for

I want to make a grid component which accepts as slot additional column cell.
<grid>
<td slot="additional-column">...</td>
</grid>
In the actual component:
<template>
<table>
<div v-for="item in items">
...
<slot name="additional-column"></slot>
</div>
</table>
</template>
Unfortunately the slot starts repeating itself and this is something vuejs doesn't like.
Duplicate presence of slot "additional-column" found in the same render tree - this will likely cause render errors.
Does anybody know how can I deal with this issue?
Thanks in advance!
This definitely seems to be your issue. You could do it this way too (as described here). See last paragraph before the subheading Destructuring.
Parent:
<grid>
<td :slot="['additional-column', item].join('-')" v-for="item in items">
...
</td>
</grid>
Child:
<table>
<div v-for="item in items">
...
<slot :name="['additional-column', item].join('-')"></slot>
</div>
</table>
PS: I have NOT tried this out. If you have difficulties I could create a fiddle and share.
Make the item a nested component (which you'll be repeating using v-for) and render the additional-column slot in that particular component.
That's the proper way to overcome this issue. The idea is that you need to have one single slot with a particular name per component.
That is, you could do it that way, which is a very rough version of it but nicely outlines the idea:
<!-- Grid Template -->
<template>
<table>
<GridItem :item="item" v-for="item in items">
<!-- Maybe even pass that column conditionally, based on the item data -->
<div slot="additional-column">
Content of column
</div>
</GridItem>
</table>
</template>
<!-- GridItem Template -->
<template>
<div>
<div>Column 1</div>
<div>Column 2</div>
<slot name="additional-column" />
</div>
</template>

VueJS render once into an element

Is it possible to just render once into an element?
Suppose I have a contenteditable div, and only want to render the first value, then stop rerendering as the model changes. Here only the initial value of variable will be rendered.
<div contenteditable="true"> {{variable}} </div>
Use v-once
<div contenteditable="true" v-once> {{variable}} </div>
You can also wrap it with a <span>:
<div contenteditable="true">
<span v-once> {{variable}} </span>
</div>
refs:
https://v2.vuejs.org/v2/guide/components.html#Cheap-Static-Components-with-v-once
https://v2.vuejs.org/v2/api/#v-once
Or another solution is simply clone the variable and just don't modify it, for example if you call it readOnlyVariable:
<div contenteditable="true"> {{readOnlyVariable}} </div>

Aurelia nested repeat.for context of parent repeat.for

When nesting repeat.for in Aurelia, an internal repeat.for does not have access to the variable used in it's parent repeat.for.
Example
<div repeat.for="x of 8">
<div repeat.for="y of 8">
${x} - ${y}
</div>
</div>
In the above example, ${x} does not emit anything. How do you get the x value when inside the internal repeat.for?
Found my answer. You need to do the following:
<div repeat.for="x of 8">
<div repeat.for="y of 8">
${$parent.x} - ${y}
</div>
</div>