Image not showing in Vuetify data table slot - vue.js

I am trying to display images in a Vuetify data table column, but the image is not displaying in the desired slot, and only shows the URL of the image, e.g. "9384053.jpg". How do I display an image using slots in Vuetify?
Segment is an array containing image URLs such as 93034.jpg, 9348903.jpg etc. I am trying to display the first image only e.g. Segment[0] with a sample output of 846454.jpg.
<v-card>
<v-card-title>
Animals
<v-spacer></v-spacer>
</v-card-title>
<v-data-table
:headers="headers"
:items="entries"
:items-per-page="12"
class="elevation-3"
:multi-sort="true"
mobile-breakpoint
:search="search"
>
<template v-slot:item.Image="{ item }">
<img :src="require(`${item.Image}`)" style="width: 50px; height: 50px" />
</template>
</v-data-table>
</v-card>
Here is the script file
<script>
import firebase from '../firebaseConfig';
const db = firebase.database().ref('/');
export default {
name: 'Animals',
data: () => ({
search: '',
entries: [],
headers: [
{ text: 'ID', value: 'ID' },
{ text: 'RFID', value: 'RFID' },
{ text: 'Image', value: 'Segment[0]' },
{ text: 'Age', value: 'Age Years' },
{ text: 'Weight', value: 'Weight' },
],
}),
methods: {
readAnimals() {
db.once('value', (snapshot) => {
snapshot.forEach((doc) => {
const dataRetrieve = doc.val();
this.$data.entries.push({
ID: dataRetrieve.ID,
RFID: dataRetrieve.RFID,
'Age Years': dataRetrieve['Age Years']
Weight: dataRetrieve.Weight,
Length_History: dataRetrieve['Length_History'],
Length: dataRetrieve.Length,
Tag: dataRetrieve.Tag,
Head: dataRetrieve.Head,
Segment: dataRetrieve.Segment,
});
});
return this.$data.entries;
}).catch((error) => {
console.log('error getting documents', error);
});
},
},
mounted() {
this.readAnimals();
},
};
</script>

<template v-slot:item.Image="{ item }">
<img :src="item.Image" style="width: 50px; height: 50px" />
</template>
You should bind it this way I think.

This seems to do the trick
<template v-slot:item.Segment[0]="{item}">
<img :src="item.Segment[0]" style="width: 50px; height: 50px" />
</template>
https://imgur.com/a/LX7JKoy

Related

Dynamically insert images into 'v-data-table'

<v-data-table
:headers="headers"
:items="items"
:search="search"
hide-default-footer
class="elevation-1"
>
<template #[`item.employee_avatar`]="{ item }">
<v-img
:src="require('#/assets/img/img2.jpg')"
:alt="item.name"
style="width: 100px; height: 100px"
/>
</template>
</v-data-table>
The image with a fixed path is received, but I want to express the image with the image path of each object. I want to automatically follow the imgsrc path value of items.
export default {
data() {
return {
search: '',
loading: true,
headers: [
{
text: 'Avatar',
value: 'employee_avatar',
divider: true,
align: 'center',
sortable: false,
width: '100px',
},
{
text: 'myname',
align: 'center',
sortable: false,
value: 'name',
},
],
items: [
{
name: 'myname',
imgsrc: "#/assets/avatar.png",
},
The code below is the code I tried.
<template #[`item.employee_avatar`]="{ item }">
<v-img
:src="require(item.imgsrc)"
:alt="item.name"
style="width: 100px; height: 100px"
/>
</template>
There will be an error even if I fill out the above.
What's wrong with it? Help me...
You can use v-slot instead of #[item.employee_avatar]:
<template v-slot:item.employee_avatar="{ item }">
<v-img
:src="require(item.imgsrc)"
:alt="item.name"
style="width: 100px; height: 100px"
/>
</template>

Ant Design Vue3 Editable Table Value Does Not Change

I have used the table component from the ant design, and right now I'm stuck on why this cannot work,
The reference link is:
https://2x.antdv.com/components/table-cn#components-table-demo-edit-cell
Here's the source code from the ant design about editable table:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered>
<template v-for="col in ['name', 'age', 'address']" #[col]="{ text, record }" :key="col">
<div>
<a-input
v-if="editableData[record.key]"
v-model:value="editableData[record.key][col]"
style="margin: -5px 0"
/>
<template v-else>
{{ text }}
</template>
</div>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="editableData[record.key]">
<a #click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" #confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a #click="edit(record.key)">Edit</a>
</span>
</div>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import { defineComponent, reactive, ref, UnwrapRef } from 'vue';
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' },
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' },
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' },
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' },
},
];
interface DataItem {
key: string;
name: string;
age: number;
address: string;
}
const data: DataItem[] = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
export default defineComponent({
setup() {
const dataSource = ref(data);
const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
const edit = (key: string) => {
editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
};
const save = (key: string) => {
Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const cancel = (key: string) => {
delete editableData[key];
};
return {
dataSource,
columns,
editingKey: '',
editableData,
edit,
save,
cancel,
};
},
});
</script>
<style scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>
However, I have the functionality that my name will be a hyperlink, so I need to put the name column and its value outside the v-for. Here's what I got:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered>
<template v-for="col in ['age', 'address']" #[col]="{ text, record }" :key="col">
<div>
<a-input
v-if="editableData[record.key]"
v-model:value="editableData[record.key][col]"
style="margin: -5px 0"
/>
<template v-else>
{{ text }}
</template>
</div>
<template #name="{ text, record }">
<div>
<a-input v-if="editableData[record.key]" v-model:value="editableData[record.key][record.name]" style="margin:
-5px 0"></a-input>
<router-link v-else="v-else" to="/tables/123">{{ text }}</router-link>
</div>
</template>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="editableData[record.key]">
<a #click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" #confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a #click="edit(record.key)">Edit</a>
</span>
</div>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import { defineComponent, reactive, ref, UnwrapRef } from 'vue';
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' },
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' },
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' },
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' },
},
];
interface DataItem {
key: string;
name: string;
age: number;
address: string;
}
const data: DataItem[] = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
export default defineComponent({
setup() {
const dataSource = ref(data);
const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
const edit = (key: string) => {
editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
};
const save = (key: string) => {
Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const cancel = (key: string) => {
delete editableData[key];
};
return {
dataSource,
columns,
editingKey: '',
editableData,
edit,
save,
cancel,
};
},
});
</script>
<style scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>
However, when I changed my name through the editing mode, it seems that the name will not change based on the edited name. Is there anything I'm wrong? Any help will be greatly appreciated!
Ok, I finally got the answer - should be using a editableData[record.key].name when we are using v-model instead of editableData[record.key][record.name]

Vuetify data table with nested data and v-slot:item

I would like to create a Vuetify table with nested data. The problem is that v-slot:item doesn't seem to work with nested data.
This is my code: https://codepen.io/blakex/pen/XWKWjaE
<v-data-table :headers="headers" :items="desserts">
<template v-slot:item.calories="{ item }">
<td>Slot works: {{ item.calories }}</td>
</template>
<template v-slot:item.nested.nestedCalories="{ item }">
<td>Nested slot works: {{ item.nested.nestedCalories }}</td>
</template>
</v-data-table>
data () {
return {
headers: [
{ text: 'Dessert', value: 'name' },
{ text: 'Calories', value: 'calories' },
{ text: 'Nested Calories', value: 'nested.nestedCalories' },
],
desserts: [
{
name: 'Yogurt',
calories: 100,
nested: { nestedCalories: 100 },
},
...
],
}
}
As you can see, the v-slot:item.nested.nestedCalories doesn't work.
Does anyone know what is missing?
This doesn't seem to be mentioned in the DOM Template Parsing Caveats, but HTML tags and attributes are not case-sensitive. In Codepen you're using the DOM as a template, so the v-slot:item.nested.nestedCalories attribute becomes lowercase (v-slot:item.nested.nestedcalories). If you change the value in headers to lowercase you'll see that it works.
To avoid this you should always use string templates with Vue. String templates can be:
.vue files
The template option
<script type="text/x-template"> like the vuetify codepen template uses
Your code written with x-template looks like this:
<div id="app"></div>
<script type="text/x-template" id="app-template">
<v-app>
<v-data-table
:headers="headers"
:items="desserts"
:items-per-page="5"
class="elevation-1"
>
<template v-slot:item.calories="{ item }">
<td>Slot works: {{ item.calories }}</td>
</template>
<template v-slot:item.nested.nestedCalories="{ item }">
<td>Nested slot works: {{ item.nested.nestedCalories }}</td>
</template>
</v-data-table>
</v-app>
</script>
<script>
const App = {
template: '#app-template',
data: () => ({
headers: [
{ text: 'Dessert', value: 'name' },
{ text: 'Calories', value: 'calories' },
{ text: 'Nested Calories', value: 'nested.nestedCalories' },
],
desserts: [
{
name: 'Yogurt',
calories: 100,
nested: { nestedCalories: 100 },
},
{
name: 'Ice cream',
calories: 200,
nested: { nestedCalories: 200 },
},
{
name: 'Eclair',
calories: 300,
nested: { nestedCalories: 300 },
},
],
})
}
new Vue({
vuetify: new Vuetify(),
render: h => h(App)
}).$mount('#app')
</script>

Filter out duplicate data in Vue

How can I filter out duplicate tags in a list, so that only one of each tag is listed?
Here's my component code:
<template>
<v-layout row wrap>
<ul>
<li v-for="post in sortByName(posts)" :key="post.key">
<v-chip
v-for="i in sortByName(post.tags)"
:key="i.key"
:color="`${i.color} lighten-3`"
label
small
>
<span class="pr-2">
{{ i.text }}
</span>
</v-chip>
</li>
</ul>
</v-layout>
</template>
<script>
import { mapState } from 'vuex'
const fb = require('../firebaseConfig.js')
export default {
data: () => ({
}),
computed: {
...mapState(['posts'])
},
methods: {
// function to put the tags in the right order to a -> z
sortByName (list) {
return _.orderBy(list, 'text', 'asc');
}
}
}
</script>
For example, in the screenshot below, I want to filter out Beach, so that I only see Beach once in the list:
The data looks like this:
One solution is to use a computed property that returns a new list (e.g., filteredPosts) of posts with its tags array filtered. In the example below, a cache inside the computed property is used to track tags. The computed handler maps this.posts[] into a new Array and filters each entry's tags[], tracking new tags in the cache as "seen" and removing tags already "seen".
template:
<li v-for="post in filteredPosts" :key="post.key">
script:
computed: {
filteredPosts() {
const tagCache = {};
const newPosts = this.posts.map(post => {
return {
...post,
tags: post.tags.filter(tag => {
const seen = tagCache[tag.text];
tagCache[tag.text] = true;
return !seen;
})
};
});
return newPosts;
}
}
new Vue({
el: '#app',
data() {
return {
posts: [
{
key: 1,
tags: [
{ color: 'blue', text: 'Sky' },
{ color: 'green', text: 'Tree' },
{ color: 'yellow', text: 'Beach' },
],
},
{
key: 2,
tags: [
{ color: 'purple', text: 'Grape' },
{ color: 'red', text: 'Apple' },
{ color: 'orange', text: 'Orange' },
],
},
{
key: 3,
tags: [
{ color: 'blue', text: 'Blueberry' },
{ color: 'yellow', text: 'Beach' },
],
},
{
key: 4,
tags: [
{ color: 'pink', text: 'Flower' },
{ color: 'yellow', text: 'Beach' },
],
},
]
};
},
methods: {
// function to put the tags in the right order to a -> z
sortByName (list) {
return _.orderBy(list, 'text', 'asc');
},
},
computed: {
filteredPosts () {
const tagCache = {};
// map `posts` to a new array that filters
// out duplicate tags
const newPosts = this.posts.map(post => {
return {
...post,
tags: post.tags.filter(tag => {
const seen = tagCache[tag.text];
tagCache[tag.text] = true;
return !seen;
})
};
});
return newPosts;
}
}
})
#import 'https://unpkg.com/vuetify#1.1.9/dist/vuetify.min.css'
<script src="https://unpkg.com/vue#2.5.17"></script>
<script src="https://unpkg.com/lodash#4.17.10/lodash.min.js"></script>
<script src="https://unpkg.com/vuetify#1.1.9/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container fluid class="pa-0">
<v-layout row wrap>
<ul>
<li v-for="post in filteredPosts" :key="post.key">
<v-chip v-for="i in sortByName(post.tags)"
:key="i.key"
:color="`${i.color} lighten-3`"
label
small>
<span class="pr-2">
{{ i.text }}
</span>
</v-chip>
</li>
</ul>
</v-layout>
</v-container>
</v-app>
</div>

vue.js doesn't update on mounted() hook

Edit : Solved, see answer.
So i'm new to vue.js and quasar so it is probably a rookie mistake about vue lifecycle hooks and vue reactivity.
I want to resize an element based on the browser's size. The height of this element is calculated using the height of other elements. I use $refs to get the other element's height and I capture the onResize() event launched by a quasar component.
This event is launched once when the page is loading, and my element's sizes are all 0 because I guess they are not rendered in the DOM yet. I have a method to refresh my calculated height, which I call when "onResize" event is captured, and also at the "mounted()" vue.js hook.
My problem is :
the first onResize() calls the method but all elements have 0px in height.
mounted() calls the method again and here all elements have their height calculated. The results are good, but it does not show on the display, see screenshot #1 : resize events and sizes logged in the console, note that the size is calculated twice, once on onResize() and once on mounted() . the one on mounted() has the good value but it does not show in the DOM.
after I resize the window once, then everything is ok and i don't have any problem anymore. (screenshots #2 (window mode) and #3 (fullscreen again))
My question is : why the height is not update in the DOM when mounted() hook is called even if it is calculated correctly ? (everything is in the same .vue file)
My code :
My problem is with the height of the div that has the "tableRow" ref
<template>
<q-page>
<div class="row" :style="'height: '+pageSize.height*0.95+'px;'">
<div class="col-6 q-pa-lg">
<div class="row" ref="actionsRow">
<div class="col-6 q-mb-sm">
<q-search hide-underline v-model="filter" />
</div>
<div class="col-6">
</div>
</div>
<div class="row" ref="tableHeaderRow">
<q-table class="col-12" :selection="selectionMode" :selected.sync="selectedRows" :data="templateTableData" :columns="templateColumns"
row-key="slug" :pagination.sync="pagination" dense hide-bottom>
<q-tr slot="body" slot-scope="props" :props="props">
</q-tr>
</q-table>
</div>
<div class="row" ref="tableRow" :style="'height: '+tableHeight+'px;'">
<q-scroll-area style="height: 100%" class="col-12 q-mt-sm shadow-3">
<q-table :selection="selectionMode" :selected.sync="selectedRows" :data="templateTableData" :columns="templateColumns" row-key="slug"
:filter="filter" :pagination.sync="pagination" dense hide-bottom hide-header>
<q-tr slot="body" slot-scope="props" :props="props" #click.native="onRowClick(props.row)" class="cursor-pointer">
<q-td auto-width>
<q-checkbox color="primary" v-model="props.selected" />
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</q-table>
</q-scroll-area>
</div>
</div>
<router-view class="col-6 q-pa-lg">
</router-view>
</div>
<q-window-resize-observable #resize="onResize" />
</q-page>
</template>
Script:
var data = []
for (var i = 1; i <= 100; i++) {
data.push({
id: i,
name: 'Template ' + i,
slug: 'template' + i,
active: true
})
}
import { mapActions, mapGetters } from 'vuex'
export default {
data: () => ({
pageSize: {
height: 0,
width: 0
},
tableHeight: 0,
templateColumns: [
{
name: 'templateName',
required: true,
label: 'Name',
align: 'left',
field: 'name',
sortable: true
},
{
name: 'templateSlug',
label: 'Slug',
align: 'left',
field: 'slug',
sortable: true
},
{
name: 'templateActive',
label: 'Active',
align: 'left',
field: 'active',
sortable: true,
sort: (a, b) => {
if ((a && b) || (!a && !b)) {
return 0
} else if (a) {
return 1
} else {
return -1
}
}
}
],
selectionMode: 'multiple',
selectedRows: [],
pagination: {
sortBy: null, // String, column "name" property value
descending: false,
page: 1,
rowsPerPage: 0 // current rows per page being displayed
},
templateTableData: data,
filter: ''
}),
computed: {
...mapGetters('appUtils', [
'getPageTitle',
'allConst'
])
},
methods: {
...mapActions('appUtils', [
'setPageTitle',
'deletePageTitle'
]),
onResize (size) {
this.pageSize.height = size.height - this.getPageTitle.height
this.resizeTable()
console.log('ON RESIZE EVENT:')
console.log('tableHeaderRow:'+
this.$refs.tableHeaderRow.clientHeight)
console.log('actionsRow:' + this.$refs.actionsRow.clientHeight)
console.log('table:' + this.tableHeight)
},
onRowClick (row) {
this.$router.push('/templates/' + row.slug)
},
resizeTable () {
this.tableHeight = this.pageSize.height - this.$refs.actionsRow.clientHeight -
this.$refs.tableHeaderRow.clientHeight - this.getPageTitle.height -
this.allConst.templatePageHeaderMargins
}
},
mounted () {
console.log('MOUNT TEMPLATES')
this.setPageTitle({ text: 'Manage templates', height: this.allConst.titleHeight })
this.resizeTable()
console.log('tableHeaderRow:' + this.$refs.tableHeaderRow.clientHeight)
console.log('actionsRow:' + this.$refs.actionsRow.clientHeight)
console.log('table:' + this.tableHeight)
},
destroyed () {
console.log('DESTROY TEMPLATES')
this.deletePageTitle()
}
}
Another one of my variables (titleSize) was 0, like the other sizes, during the first onResize() event, and I did not take that into account to correct it during the mounted() Hook.