Vue element-ui <el-table-column> formatter – how to display html? - vue.js

How to return html formatted cell value?
I want to get custom formatted value (with html or other components) with <el-table-column> formatter.
<el-table :data="data">
<el-table-column v-for="(column, index) in columns"
:key="index" :label="column.label"
:formatter="column.formatter">
</el-table-column>
</el-table>
data() {
return {
columns: [
{
label: 'Created at',
formatter: (row, col, value, index) => {
return `<span class="date">${value}</span>`
}
},
// edit:
{
label: 'Address',
formatter: (row, col, value, index) => {
return `<mini-map :data="${row}"></mini-map>`
}
}
// other dynamic columns...
]
}
}
But cell content is displayed as escaped html string. Is there any possibility to force html?
EPIC EDIT: I added an answer with a solution.

Ok, after a few hours of brainstorming I found pretty nice solution. I'm putting it here for others - I hope this helps somebody.
Value displayed in custom column can be:
simple string (prop)
formatted string (safe, without any html or components, via original formatter)
customized value (html, component, also safe!)
In order to achieve this, I had to create custom formatter components, which I put in folder i.e. /TableFormatters/
For this purpose, there is simple functional component DatetimeFormatter that display date-time with icon.
TableFormatters/DatetimeFormatter.vue
<template functional>
<span>
<i class="icon-date"></i> {{ props.row[props.column] }}
</span>
</template>
<script>
export default {
name: 'DatetimeFormatter',
}
</script>
Here come's columns configuration:
import DatetimeFormatter from './TableFormatters/DatetimeFormatter'
// ...
data() {
return {
data: [/*...*/],
columns: [
name: {
label: 'Name',
},
state: {
label: 'State',
formatter: row => {
return 'State: '+row.state__label
}
},
created_at: {
label: 'Created at',
formatter: DatetimeFormatter
}
]
}
}
Now it's time to define <el-table>:
<el-table :data="data">
<el-table-column
v-for="(column, index) in columns"
:key="index"
:label="columns[column] ? columns[column].label : column"
:prop="column"
:formatter="typeof columns[column].formatter === 'function' ? columns[column].formatter : null">
<template #default="{row}" v-if="typeof columns[column].formatter !== 'function'">
<div v-if="columns[column].formatter"
:is="columns[column].formatter"
:row="row"
:column="column">
</div>
<template v-else>
{{ row[column] }}
</template>
</template>
</el-table-column>
</el-table>
This works like a charm. What's going on here with formatter?
First we check if the formatter is set as a function. If so, the native <el-table-column> formatter takes the control, because <template #default={row}> will not be created. Otherwise formatter component will be created (via :is attribute). However, it there is no formatter, the plain value for a row will be shown.

If you want to render custom HTML for a <el-table-column>, you will need to make use of the custom column template functionality, rather than the :formatter prop. It's going to look something like this:
<el-table :data="data">
<el-table-column
v-for="(column, index) in columns"
:key="index"
:label="column.label"
>
<template slot-scope="scope">
<span class="date">{{ scope.row.value }}</span>
</template>
</el-table-column>
</el-table>
In the general case, you can make use of the v-html directive if you need to render unescaped HTML. There are some security implications involved, so make sure you understand those before reaching for v-html.
Essentially, it boils down to this: never use v-html to render content that was provided by the user.

Use template slot scope to add html formatted columns
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<template>
<el-table :data="tblData">
<el-table-column prop="title"></el-table-column>
<el-table-column prop="text">
<template scope="scope">
<span style="color:red;">{{scope.row.text}}</span>
</template>
</el-table-column>
</el-table>
</template>
</div>
var Main = {
data() {
return {
tblData : [
{title: 'title1', text:'text1'},
{title: 'title2', text:'text2'},
{title: 'title3', text:'text3'},
],
}
},
methods : {
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

This also works for me:
<el-table
:data="tenancy.renewals"
stripe
height="300"
style="width: 100%">
<el-table-column
prop="term"
label="Term"
:formatter="formatTerm"
width="180">
</el-table-column>
<el-table-column
prop="started"
label="Started"
:formatter="formatColDate"
width="180">
</el-table-column>
<el-table-column
prop="expiry"
:formatter="formatColDate"
label="Expiry">
</el-table-column>
<el-table-column
prop="amount"
:formatter="formatAmount"
label="Amount">
</el-table-column>
</el-table>
Then in your methods have methods corresponding to the formatter ones.
In my case, I already have mixins for numbers and dates:
...
formatTerm (row, col, value, index) {
return this.addSuffix(value, false)
},
formatColDate (row, col, value, index) {
return this.formatDate(value)
},
formatAmount (row, col, value, index) {
return this.formatMoney(value)
},
...

I feel headache, but it worked for me
<el-table :data="tableData" style="width: 100%">
<el-table-column label="shortName" width="180">
<template v-slot="scope">
<span v-html="scope ? scope.row.shortName : ''"></span>
</template>
</el-table-column>
...

Related

V-select issue in Vuetify 3

I'm using Vuetify 3.0.0-beta.0 ~ for my project (because it is the only version that supports vue3), and having a bit weird issue
I want to implement the same thing as described there https://codepen.io/reijnemans/pen/vYNadMo?editors=1010 with v-select involved, so I was needed to use Vuetify
copied snippet
<v-select
:items="items"
label="Standard"
>
<template v-slot:selection="{ item, index }">
<img :src="item.image">{{ item.name }}</template>
</template>
<template v-slot:item="{ item }">
<img :src="item.image">{{ item.name }}</template>
</v-select>
My Component:
<template>
<div class="resourceSelectors">
<v-col cols="10" lg="4" class="mx-auto">
<div class="text-center">
<h2 class="indigo--text" style="margin-bottom: 30px">Some Test H2</h2>
</div>
<v-col class="d-flex" cols="12" sm="6">
<v-select
:items="items"
label="Standard">
<template v-slot:selection="{ item }">
<img :src="item.image">{{ item.name }}
</template>
<template v-slot:item="{ item }">
<img :src="item.image">{{ item.name }}
</template>
</v-select>
</v-col>
</v-col>
</div>
</template>
<script>
import { mapState } from "vuex";
/* eslint-disable */
export default {
name: "testComponent",
data() {
return {
// hardware Configuration Validation Rules
items: [
{ name: 'Foo', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'},
{ name: 'Bar', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'},
{ name: 'Hoo', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'},
{ name: 'Coo', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'}],
}
}}
When I'm trying to run the above component I always get this weird error Failed setting prop "type" on <select>: value text is invalid. TypeError: Cannot set property type of #<HTMLSelectElement> which has only a getter,
Did anyone faced similar issue before?
In Vuetify 3, you need some workarounds to style the items in v-select, because the item slot resets the entire styling.
You should use the menu-props, with it you can pass props through to the v-menu component. It accepts an object with anything from /api/v-menu. This allows you to close the field on click.
In the item slot, you should use a v-list-item with an #click property to set the model.
I made an example here with a selection of symbols:
<script setup>
const symbols = [
'ab-testing',
'abacus',
'account',
'account-alert',
]
const form = { symbol: '', }
</script>
<template>
<v-select
v-model="form.symbol"
:items="symbols"
label="Symbol"
:prepend-inner-icon="'mdi-'+form.symbol"
:menu-props="{
closeOnClick: true,
closeOnContentClick: true,
}"
>
<template v-slot:selection="{ item, index }">
{{ item.value }}
</template>
<template v-slot:item="{ item, index }">
<v-list-item
:title="item.title"
:prepend-icon="'mdi-'+item.title"
#click="form.symbol = item.title"
>
</v-list-item>
</template>
</v-select>
</template>
I hope it helps you.
I couldn't find correct solution but I just wanted to share what I did about scoped slot. I think we should use item.raw to access name and image. And the next problem is how to make it clickable to trigger select event that I didn't know yet :(
const { createApp } = Vue
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = createApp({
data() {
return {
value: null,
items: [
{
name: 'Foo',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
},
{
name: 'Bar',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
},
{
name: 'Hoo',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
},
{
name: 'Coo',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
}
]
}
}
});
app.use(vuetify).mount('#app');
<link href="https://cdn.jsdelivr.net/npm/vuetify#3.0.0-beta.9/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#3.0.0-beta.9/dist/vuetify.min.js"></script>
<div id="app">
<div class="resourceSelectors">
<v-col cols="10" lg="4" class="mx-auto">
<div class="text-center">
<h2 class="indigo--text" style="margin-bottom: 30px">Some Test H2</h2>
</div>
<v-col class="d-flex" cols="12" sm="6">
<v-select
v-model="value"
:items="items"
item-title="name"
item-value="name"
label="Standard">
<template v-slot:item="{item}">
<v-list-item
:prepend-avatar="item.raw.image"
:title="item.raw.name"
/>
</template>
</v-select>
</v-col>
</v-col>
</div>
</div>

Change v-data-table's header's color

I'd like to change the color of the header in my v-data-table but didn't find any way in the Vuetify documentation. Does anyone know how to do it ?
I can change the rest of the colors on the table but no the header...
<v-card-text>
<v-data-table
dark
:footer-props="{ 'items-per-page-options': [10, 25, -1] }"
dense
calculate-widths
fixed-header
height="498"
:headers="headers"
:items="res"
sort-by="publicationDate"
:sortDesc="sortVal"
>
<template #item.video="{ item }">
<a
target="_blank"
v-if="item.video != ''"
class="links1 video-icon"
:href="item.video"
>
<v-btn dark icon>
<v-icon class="ic1">mdi-movie</v-icon>
</v-btn>
</a>
</template>
<template #item.title2="{ item }">
<!-- NEWS column -->
<a
target="_blank"
v-if="item.file != ''"
class="links1"
:href="item.file"
>
<span style="color:white"> {{ item.title }} </span>
</a>
</template>
</v-data-table>
</v-card-text>
You can achieve this by hide default header by adding hide-default-header attribute in <v-data-table> element and then create custom header by using v-slot.
<template v-slot:header="{ props: { headers } }">
<thead>
<tr>
<th v-for="h in headers" :class="h.class">
<span>{{h.text}}</span>
</th>
</tr>
</thead>
</template>
In headers array, add class property in each object which will contain the class name.
Sample structure of headers array :
headers: [
{ text: 'Title', value: 'title', class: 'my-header-style' }
...
...
]
Finally, In CSS you can just add the style to my-header-style class.
.my-header-style {
background: #666fff;
}
Live Demo :
new Vue({
el: '#app',
data: () => ({
headers: [
{ text: 'ID', value: 'id', class: 'my-header-style' },
{ text: 'Name', value: 'name', class: 'my-header-style' },
{ text: 'Age', value: 'age', class: 'my-header-style' }
],
movies: []
})
})
.my-header-style {
background: #666fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.2.5/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify#1.2.5/dist/vuetify.min.css"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"/>
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="items"
hide-default-header>
<template v-slot:header="{ props: { headers } }">
<thead>
<tr>
<th v-for="h in headers" :class="h.class">
<span>{{h.text}}</span>
</th>
</tr>
</thead>
</template>
</v-data-table>
</v-app>
</div>
One option to modify the default Vuetify CSS is to target the inner elements/sub-components using deep selectors. Target the root node of the component, in this case you can select the v-data-table classname, then deep select the classname on the header sub-component:
<style scoped>
.v-data-table >>> .v-data-table-header {
background-color: red !important;
}
</style>
or if not using scoped styling, you can just target the desired class
<style>
.v-data-table-header {
background-color: red !important;
}
</style>
codesandbox example
Also note that if you're using Sass you may need to use ::v-deep or /deep/ instead of >>>

Vuejs/Buetify : clear search filter using searchable prop of b-table

I'm using buefy to create a table with input filters in columns.
This is what it looks like:
<b-table
:data="cars"
:sticky-header="true"
:selected.sync="selected"
>
<template slot-scope="props">
<template v-for="column in columns">
<b-table-column :key="column.id" v-bind="column">
<template
v-if="column.searchable"
slot="searchable"
slot-scope="props"
>
<b-input
v-model="props.filters[props.column.field]"
placeholder="Search..."
icon="magnify"
size="is-small"
/>
</template>
{{ props.row[column.field] }}
</b-table-column>
</template>
</template>
</b-table>
...
...
data () {
return {
selected: null,
columns: [
{
field: 'constructor',
label: 'Constructor',
searchable: true
},
....
]
}
I would like to be able to clear the searched fields.
Any suggestions to achieve this please?
Your b-input is bound to props.filters[props.column.field], so it means that you should be able to add an icon to reset this value:
<b-input
v-model="props.filters[props.column.field]"
...
icon-right="close-circle"
icon-right-clickable
#icon-right-click="props.filters[props.column.field] = ''"
>
Please let me know if that works.

Element UI, passing id to table column with checkbox

Is there a way to pass id to el-table-column with selection type? I've tried passing a slot, but then checkbox is not rendering. Here is the code:
<el-table-column>
<template slot-scope="scope" v-if="scope.row">
<div :id="`column-${scope.row.name}`">{{ `reports-${scope.row.name}` }}</div>
</template>
</el-table-column>
<el-table-column prop="selected" align="center" type="selection" class-name="checkbox-column">
</el-table-column>
First column getting id via scoped slot.
Instead of ID if you are okay with class then use cell-class-name
In your template
<el-table your-attrs ... :cell-class-name="cellClassName">
In your script
methods: {
cellClassName({row, column, rowIndex, columnIndex}) {
if (columnIndex === 1) return `checkbox-${rowIndex}`;
}
}

Condition on template with v-if using v-slot prop

I'm trying to make a condition to enable a named slot like this:
<template v-slot:item="{ item }" v-if="item.loading">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</template>
My use case is a Vuetify datatable: each item has a "loading" property, and I'd like to activate "item" slot only if the row is loading ("item" slot is Slot to replace the default rendering of a row)
The error is that item is undefined in the v-if, which seems logic : item is only defined for template children tag.
Is there a way to solve this problem?
You can filter the items that you pass to the datatable with a computed property.
Can you just not swap element based on loading ?
Vue.config.devtools = false;
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data: {
items: [{data : "", loading: true}, {data : "Some data", loading: false}]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item in items">
<div>
<div v-if="item.loading">
Loading...
</div>
<div v-else>
{{item.data}}
</div>
</div>
</div>
</div>
I had a similar problem, and I solved it in Vuetify 2 by importing VDataTable/Row as 'v-data-table-row', and using it to render 'regular' table rows, and for custom rows I used my own template.
JavaScript
import Row from 'vuetify/lib/components/VDataTable/Row.js';
export default {
components: { 'v-data-table-row': Row },
data() {
return {
currentItemName: 'Ice cream sandwich'
}
}
// headers, items, etc...
}
HTML
<template v-slot:item="{ item }">
<tr v-if="item.name == currentItemName" class="blue-grey lighten-4">
<td>Custom prefix - {{ item.name }}</td>
<td colspan="2">{{ item.calories }} - Custom suffix</td>
</tr>
<v-data-table-row v-else :headers="headers" :item="item">
<template
v-for="(index, name) in $scopedSlots"
v-slot:[name.substr(5)]="data"
>
<slot
v-if="name.substr(0, 5) === 'item.'"
:name="name"
v-bind="data"
></slot>
</template>
</v-data-table-row> </template
You can check out working example here.
You can just put the v-if on the child element
<template #item="{ item }">
<v-progress-circular
v-if="item.loading"
color="primary"
indeterminate
></v-progress-circular>
</template>