Add v-html attribute only if slot not given - vue.js

Consider the following in a component snipet:
<tr v-for="row in rows" :key="row[rowKey]">
<td v-for="col in cols">
<slot v-if="col.bSlot" :name="col.bSlot" :row="row"></slot>
<template v-else v-html="formatField(row, col)"></template>
</td>
</tr>
Above I want to render a slot if given and if not to render a string returned by a formatting function unescaped. This did not work because I found out that v-html does not work with templates. The following works but you need extra unnecessary div tags that I don't want:
<td v-for="col in cols">
<slot v-if="col.bSlot" :name="col.bSlot" :row="row"></slot>
<div v-else v-html="formatField(row, col)"></div>
</td>
It would be nice if this still worked but has been deprecated:
<td v-for="col in cols">
<slot v-if="col.bSlot" :name="col.bSlot" :row="row"></slot>
<template v-else>
{{{formatField(row, col)}}}
</template>
</td>
The only other option I am left with is this but because there is no where to put the v-else I made the formatField() return nothing if a slot is given:
<td v-for="col in cols" v-html="formatField(row, col)">
<slot v-if="col.bSlot" :name="col.bSlot" :row="row"></slot>
</td>
formatField(row, col) {
if (col.bSlot) return; // also tried returning null, undefined, and ''
...
}
However now the slot doesn't render when one is provided. It seems like Vue ignores the slot if v-html is provided. So how do I not provide v-html when the col.bSlot is not provided?

You can place default value if slot is not given -
Fallback-Content
How about this:
1 <td v-for="col in cols">
2 <slot :name="col.bSlot" :row="row">
3 {{ formatField(row, col) }}
4 // <-- this is a comment
5 // <span v-html="formatField(row, col)"></span>
6 </slot>
7 </td>

Related

Access and declare vueJs component inside other component slot

I'm trying to declare and access an vue component inside a component slot.
The modal component will be creates several time and there it has to have an unique name but I don't no how I do that inside the slot.
using Laravel blade and vueJs
<template>
<table>
<thead>
<slot name="head"/>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id">
<slot name="line" v-bind:item="item" />
</tr>
</tbody>
</table>
</template>
<list-component ref="teamMembers">
<template v-slot:head>
<tr>
<th>Name</th>
<th>Email-address</th>
<th>Role</th>
</tr>
</template>
<template v-slot:line="slotProps">
<td>
#{{ slotProps.item.name }}
</td>
<td>
#{{ slotProps.item.email }}
</td>
<td>
#{{ slotProps.item.role }}
</td>
<td>
<button #click="$refs.deleteItemModal#{{ slotProps.item.id }}">
Delete
</button>
</td>
<modal-component ref="deleteItemModal#{{ slotProps.item.id }}">
</modal-component>
</template>
</list-component>
How can declare and call modal component?
If I use it like this
<button #click="$refs.deleteItemModal">
and
ref="deleteItemModal"
it works but gives me always the same component.
Please advise me how I can create an unique ref to create something like this 'deleteItemModal' + slotProps.item.id
You can try like this:
<modal-component :ref="'deleteItemModal' + slotProps.item.id">
</modal-component>
Static value should be covered with quotation mark and can use + to concat any dynamic value with the string like in javascript.
To call openModal method of that ref component, create method in modal-component component
deleteItem(itemId) { this.$refs['deleteItemModal' + itemId].openModal() }
and then change for button click event
<button #click="deleteItem(slotProps.item.id)">

How to iterate through an array in a Vue component?

I am trying to iterate through the array props_nfl_days which is equal to ["Sunday", "Thursday Night", "Monday Night"] so it appears as a header for each group of NFL scores by day of the week. The component looks like:
Updated Code:
const nfl = {
nflComponent: Vue.component("tab-nfl", {
props: [
"props_league_data_nfl",
"props_nfl_days",
"props_league_standings"
],
template: `
<div class="vue-root-element">
<div class="container nfl-scores">
<div v-for="(dayDataArray, index) in props_league_data_nfl">
<p> {{ index }} </p>
<h2> {{ props_nfl_days[index] }} </h2>
<div class="row">
<div class="col-xs-12 col-md-4 col-lg-3" v-for="arrayItem in dayDataArray">
<table class="table table-striped table-sm">
<thead>
<th scope="col" class="box-score-status is-completed" v-if="arrayItem.isCompleted">Final</th>
</thead>
<tbody>
<tr>
<td class="box-score-team"> {{ arrayItem.game.awayTeam.Abbreviation }} </td>
<td class="box-score-inning" v-for="quarter_score in arrayItem.quarterSummary.quarter">
{{quarter_score.awayScore }}</span>
<td class="box-score-final" v-bind:class="{ won: arrayItem.awayScore > arrayItem.homeScore }">{{ arrayItem.awayScore }}
</td>
</tr>
<tr>
<td class="box-score-team"> {{ arrayItem.game.homeTeam.Abbreviation }} </td>
<td class="box-score-inning" v-for="quarter_score in arrayItem.quarterSummary.quarter">
{{quarter_score.homeScore }}
</td>
<td class="box-score-final" v-bind:class="{ won: arrayItem.homeScore > arrayItem.awayScore }">{{ arrayItem.homeScore
}}
</td>
</tr>
<tr><td class="box-score-team w-150">Location: {{ arrayItem.game.location }} </td></tr>
</tbody>
</table>
</div> <!-- End v-for dayDataArray -->
</div> <!-- End row -->
</div> <!-- End v-for props_league_data_nfl -->
</div> <!-- End container -->
All I get is index goes to 0 and thats it. props_league_data_nfl is an objectwith three properties. Here is a snapshot of the output of Vue panel :
What I want is the correct nfl_days array element in h2 header for each props_league_data_nfl group. See picture:
I would like these Sunday headers to be Thurday Night and Monday Night respectively. Any guidance on how to achieve this much appreciated.
Computed properties should not have side effects. Computed properties are cached based on their dependencies; since increment doesn't see any changes in its dependencies, it does not re-compute its value. So increment it will run once and always return the same value: 0.
v-for takes additional parameters for the key (in the case of iterating over objet properties) and the current index, so you can remove the increment computed property and re-write your template like so:
<div v-for="(dayDataArray, key, index) in props_league_data_nfl">
**<h2> {{ props_nfl_days[index] }} </h2>**

v-for render html table with multiple tr

I have a table where each "logical" row contains of two "markup rows".
<table class="table table-bordered">
<tbody>
<template v-for="(note, index) in notes">
<tr>
<td>
{{ moment(note.noteTime).format('YYYY-MM-DD HH:mm') }}
</td>
<td>
{{ note.locationName }}
</td>
</tr>
<tr>
<td colspan="2">
{{ note.noteText }}
</td>
</tr>
</template>
</tbody>
</table>
Is there a way generate this kind of table in vue without hurting html syntax (template element is not valid inside tbody)?
<template> does not generate a html element and thus will not interfere with html syntax.
There is actually a similar example inside the VueJS docs:
https://v2.vuejs.org/v2/guide/list.html#v-for-on-a-lt-template-gt
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
Here is a jsFiddle to see the example from the docs in action. You can inspect the HTML syntax:
https://jsfiddle.net/50wL7mdz/545901/

Dynamic collapse component not working in bootsrap-vue

I am trying to add a collapse component from bootstrap-vue into a table I am creating (Not bootstrap-vue table as find it easier to use plain table)
Can anyone tell me why this works as expected (But obviously opens every collapse component with the same ID)
<td>
<fa icon="bars" v-b-toggle.collapse2/>
</td>
</tr>
<td colspan="4">
<b-collapse id="collapse2" >
<b-card>
<p class="card-text">Collapse contents Here</p>
</b-card>
</b-collapse>
</td>
But this doesn't work, I know i have a unique id in case.id
<td>
<fa icon="bars" v-b-toggle="'collapse-' + case.id" />
</td>
</tr>
<td colspan="4">
<b-collapse id="'collapse-' + case.id" >
<b-card>
<p class="card-text">Collapse contents Here</p>
</b-card>
</b-collapse>
</td>
Many Thanks
You are not setting up a proper attribute binding in id="'collapse-' + case.id". It works in v-b-toggle="'collapse-' + case.id" case because as stated in the docs
Directive attribute values are expected to be binding expressions
In case of attributes you should use one of the following:
mustache
<div id="item-{{ id }}"></div>
v-bind
<div v-bind:id="'item-' + id"></div>
shorthand :
<div :id="'item-' + id"></div>

Vue v-if and remove span

Im using folowing code to render generic table:
<tr v-for="row in filteredData" >
<td v-for="column in visibleColumns">
<span v-if="column.type=='Date'" :class="column.cssClass">{{row[column.field] | formatDate}}</span>
<span v-else-if="column.type=='Bool'" :class="column.cssClass"><input #change="genericPost(column.url,row)" class="w3-check" type="checkbox" v-model="row[column.field]" ></span>
<span v-else-if="column.type=='Decimal'" :class="column.cssClass">{{row[column.field] | formatPrice}}</span>
<span v-else-if="column.type=='Button'" :class="column.cssClass"><a #click="clickItem(column.url,row)" class="w3-button w3-blue w3-padding-small">Edit</a></span>
<span v-else-if="column.type=='Link'" :class="column.cssClass"><router-link :to="{ path: row.route }">{{row[column.field]}}</router-link></span>
<span v-else :class="column.cssClass">{{row[column.field]}}</span>
</td>
</tr>
Is there any way to remove span element from conditional rendering ?
To have simple
<td class="xxx">value</td>
not <td><span class="xxx">value</span></td>
You can use v-for with child or sub <template> tags.
<tr v-for="row in filteredData" >
<td v-for="column in visibleColumns" :class="column.cssClass">
<template v-if="column.type=='Date'">{{row[column.field] | formatDate}}</template>
<template v-else-if="column.type=='Bool'" ><input #change="genericPost(column.url,row)" class="w3-check" type="checkbox" v-model="row[column.field]" ></template>
<template v-else-if="column.type=='Decimal'" {{row[column.field] | formatPrice}}</template>
<template v-else-if="column.type=='Button'" ><a #click="clickItem(column.url,row)" class="w3-button w3-blue w3-padding-small">Edit</a></template>
<template v-else-if="column.type=='Link'" ><router-link :to="{ path: row.route }">{{row[column.field]}}</router-link></template>
<template v-else >{{row[column.field]}}</template>
</td>
</tr>
In documentation: https://v2.vuejs.org/v2/guide/conditional.html#Conditional-Groups-with-v-if-on-lt-template-gt and https://v2.vuejs.org/v2/guide/list.html#v-for-on-a-lt-template-gt
FYI, it appears to be undocumented but it seems you can also use the <slot> tag in place of v-for with the <template> tags as well. Since I know there are no hidden "gotchas" with the template tag I just use it.