Vuetify v-data table get Index - vue.js

Hey there I am new to vue js and vuetify.In my editProductSave I want to pass another variable which is the index of the row in the table. This is my current code and how would i achieve that? The table is plotted using the vuetify v-data-table
Whole code
<template>
<v-card>
<v-data-table
:headers="tableFields"
:items="programs"
:items-per-page="5">
<template v-slot:[`item._id.$oid`]="{ item }">
{{item._id.$oid}}
</template>
<template v-slot:[`item.tags`]="props">
<v-edit-dialog
:return-value.sync="props.item.tags"
large
persistent
#save="editProductSave(props.item)">
<div>{{props.item.tags.length === 0 ? '' : props.item.tags}}</div>
<template v-slot:input>
<div class="mt-1 text-h2">
Update Tag
</div>
<v-text-field
v-model="props.item.tags"
label="Edit"
single-line
counter
autofocus
></v-text-field>
</template>
</v-edit-dialog>
</template>
<script>
import tdmApi from "../services/api/Database";
export default {
props: ["DatabaseList"],
computed: {
totalRows() {
return this.programs.length;
},
},
created () {
this.viewTdmDatabase();
},
data () {
return {
tableFields: [
{text:'ID',value:'_id.$oid'},
{text:'Tag',value:'tags'},
],
programs: [],
}
},
</script>

<template v-slot:item.tags="{item,index}">
{{index}} //Output index
</template>
The code above should work, make sure to cover it with object.

Try the below code:
<template v-slot:[`item.tags`]="{props, index}">
<v-edit-dialog
:return-value.sync="props.item.tags"
large persistent
#save="editProductSave(props.item, index)">
// ...
</v-edit-dialog>
</template>
And in script the method would be
methods: {
editProductSave(item, index) {
// ...
}
}

It seems that vuetify does not have in the v-data-table api the index field, so in order to get it you can change the structure of the v-data-table.
This is an example of how to get the index of each row.
https://www.codegrepper.com/code-examples/whatever/vuetify+v-data-table+get+row+index

You can simply add the index to the programs in a computed property and import it in the data table like so:
template
...
<v-data-table
:headers="tableFields"
:items="programsComputed"
...
script
export default {
...
computed: {
totalRows() {
return this.programs.length;
},
programsComputed () {
return this.programs.map((program, index) => {
program.index = index;
return program;
})
}
},
...
data () {
return {
tableFields: [
{text:'ID',value:'_id.$oid'},
{text:'Tag',value:'tags'},
],
programs: [],
}
},
In your editProductSave(item) you would just have to call item.index

Related

How can I use <v-data-table> item slots? (inheritance scenario)

I created a BaseDataTable component:
<template>
<v-data-table
:class="{ clickable }"
:headers="reshapedHeaders"
:items="items"
:loading="loading"
:sort-by="sortBy"
sort-desc
:item-key="itemKey"
:expanded.sync="expanded"
:show-expand="showExpand"
:hide-default-footer="disablePagination"
:disable-pagination="disablePagination"
#click:row="handleClickRow"
#dblclick:row.stop="handleDblclickRow"
>
<!-- Translating headers
(translationPath is one of my header custom props) -->
<template
v-for="{ text, value, translationPath } in reshapedHeaders"
#[getHeaderSlotName(value)]
>
<!-- This component uses vue-i18n features under the cover -->
<ValueTranslator
:key="value"
:translation-path="translationPath
? translationPath
: commonTranslationPath"
:value="text"
/>
</template>
<!-- Overriding item slots -->
<template
v-for="{ value } in reshapedHeaders"
#[getItemSlotName(value)]="slotData"
>
<slot
:name="getItemSlotName(value)"
v-bind="slotData"
>
{{ slotData.value }}
</slot>
</template>
</v-data-table>
</template>
<script>
import ValueTranslator from '../ValueTranslator.vue
export default {
props: {
headers: Array,
items: Array,
loading: Boolean,
clickable: Boolean,
itemKey: {
type: String,
default: '_id'
}
showExpand: Boolean,
/* Defines pagination and footer visibility;
true = disable pagination and hide footer
false = paginate and show footer */
disablePagination: Boolean,
/* Value added before each translation */
commonTranslationPath: String,
/* Defines actions column visibility;
true = show actions column
false = hide actions column */
showActions: Boolean,
/* Defines table initial sorting;
true = sort (default sorting)
false = don't sort
string = sort by passed value */
sort: [Boolean, String]
},
components: {
ValueTranslator
},
data() {
return {
expanded: []
}
},
computed: {
reshapedHeaders() {
const reshapedHeaders = [...this.headers]
if (this.showActions) {
/* Pushing actions header */
this.reshapedHeaders.push({
text: 'actions',
value: 'actions',
translationPath: 'component.table'
sortable: false
})
}
return reshapedHeaders
},
sortBy() {
if (this.sort) {
return typeof this.sort === 'string'
? this.sort
: 'lastModifiedDate'
} else
return null
}
},
methods: {
handleClickRow(item, data) {
this.$emit('click:row', item, data)
},
handleDblclickRow(_, { item }) {
this.$emit('dblclick:row', item)
},
getHeaderSlotName(value) {
return 'header.' + value
},
getItemSlotName(value) {
return 'item.' + value
}
}
}
</script>
As you can see I did that because I needed to group together a bunch of features. For reasons I can't explain here, I created a DataTable component based on the previously created BaseDataTable:
<template>
<v-container fluid>
<BaseDataTable
class="elevation-1"
:headers="headers"
:items="items"
:loading="loading"
:clickable="clickable"
:item-key="itemKey"
:show-expand="showExpand"
:disable-pagination="disablePagination"
:common-translation-path="commonTranslationPath"
show-actions
:sort="sort"
#click:row="handleClickRow"
#dblclick:row="handleDblclickRow"
>
<!-- code snippet I'll show you later -->
</BaseDataTable>
</v-container>
</template>
<script>
import BaseDataTable from '../../base/BaseDataTable.vue
props: {
headers: Array,
items: Array,
loading: Boolean,
clickable: Boolean,
itemKey: String,
showExpand: Boolean,
disablePagination: Boolean,
commonTranslationPath: String,
sort: [Boolean, String]
},
components: {
BaseDataTable
},
methods: {
handleClickRow(item) {
this.$emit('click:row', item)
},
handleDblclickRow(item) {
this.$emit('dblclick:row', item)
}
}
</script>
If I stop here I won't be able to use any item slot, but if I proceed I'll end up repeating myself...
Code snippet mentioned above:
<!-- Overriding item slots -->
<template
v-for="{ value } in headers"
#[getItemSlotName(value)]="slotData"
>
<slot
:name="getItemSlotName(value)"
v-bind="slotData"
></slot>
</template>
Plus I'm not taking in consideration there is the actions column. Above v-for is cycling through headers, not reshapedHeaders (located inside BaseDataTable). Is there a clean way to implement what I'm trying to implement?
Adding a slot inside v-data-table might probably solve your problem.
<v-data-table
:class="{ clickable }"
:headers="reshapedHeaders"
:items="items"
:loading="loading"
:sort-by="sortBy"
sort-desc
:item-key="itemKey"
:expanded.sync="expanded"
:show-expand="showExpand"
:hide-default-footer="disablePagination"
:disable-pagination="disablePagination"
#click:row="handleClickRow"
#dblclick:row.stop="handleDblclickRow"
>
<!-- Pass on all named slots -->
<slot
v-for="slot in Object.keys($slots)"
:name="slot"
:slot="slot"
/>
<!-- Pass on all scoped slots -->
<template
v-for="slot in Object.keys($scopedSlots)"
:slot="slot"
slot-scope="scope"
>
<slot :name="slot" v-bind="scope" />
</template>
</v-data-table>

How to pass custom props to component in Vue from function?

I want to pass isReadonly boolean value from first component to second.
And it does not work.
Edited after cafertayyar answer.
Method isReadonly moved from methods to computed.
First component:
<template>
<PreliminaryInformationUsageCode :is-readonly="isReadonly" />
</template>
<script>
import PreliminaryInformationUsageCode from './form/PreliminaryInformationUsageCode.vue'
export default {
name: 'FormPage',
computed: {
form() {
return this.$store.getters['form/form']
},
isReadonly: function() {
//return true
return false
}
},
components: {
PreliminaryInformationUsageCode,
},
}
</script>
Second component:
<template>
<v-select
v-model="usageCodesSelected"
:items="usageCodes"
item-text="name"
item-value="code"
label="Label"
multiple
hint="Hint"
persistent-hint
v-bind:readonly="isReadonly"
>
<template v-slot:selection="{ item, index }">
<v-chip v-if="index === 0">
<span>{{ item.name }}</span>
</v-chip>
<span
v-if="index === 1"
class="grey--text text-caption"
>
(+{{ usageCodesSelected.length - 1 }} дополнительно)
</span>
</template>
</v-select>
</template>
<script>
export default {
name: 'PreliminaryInformationUsageCode',
props: {
isReadonly: {
Boolean
},
},
data: function() {
return {
usageCodesSelected: [
],
usageCodes: [
],
}
},
}
</script>
Use this:
<PreliminaryInformationUsageCode :is-readonly="isReadonly"/>
and instead of using isReadonly function, define a computed like:
computed: {
isReadonly() {
return this.form.status.seq != 10;
}
}

Can Vue.Draggable be used with Vuetify v-data-table and allow utilisation of table v-slot:item.<name>?

Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.
We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.
I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.
However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.
Is there a way these two components can be used together and provide v-slot:item.<name> support?
I have created a DataTableRowHandler component which allows v-slot:item.<name> support.
This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.
Here is the example component usage:
<v-data-table
ref="myTable"
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
>
<template v-slot:body="props">
<draggable
:list="props.items"
tag="tbody"
:disabled="!allowDrag"
:move="onMoveCallback"
:clone="onCloneCallback"
#end="onDropCallback"
>
<data-table-row-handler
v-for="(item, index) in props.items"
:key="index"
:item="item"
:headers="headers"
:item-class="getClass(item)"
>
<template v-slot:item.lock="{ item }">
<v-icon #click="item.locked = item.locked ? false : true">{{
item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
}}</v-icon>
</template>
<template v-slot:item.carbs="{ item }">
{{ item.carbs }}
<v-icon>{{
item.carbs > 80
? "mdi-speedometer"
: item.carbs > 45
? "mdi-speedometer-medium"
: "mdi-speedometer-slow"
}}</v-icon>
</template>
</data-table-row-handler>
</draggable>
</template>
</v-data-table>
Here is the DataTableRowHandler component code
<template>
<tr :class="getClass">
<td v-for="(header, index) in headers" :key="index">
<slot :item="item" :name="columnName(header)">
<div :style="getAlignment(header)">
{{ getNonSlotValue(item, header) }}
</div>
</slot>
</td>
</tr>
</template>
<script>
export default {
name: "DataTableRowHandler",
components: {},
props: {
itemClass: {
type: String,
default: "",
},
item: {
type: Object,
default: () => {
return {};
},
},
headers: {
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
computed: {
getClass() {
return this.itemClass;
}
},
methods: {
columnName(header) {
return `item.${header.value}`;
},
getAlignment(header) {
const align = header.align ? header.align : "right";
return `text-align: ${align}`;
},
getNonSlotValue(item, header) {
const val = item[header.value];
if (val) {
return val;
}
return "";
},
},
};
</script>
An example of it's use is in this codesandbox link
I checked the source code of VDataTable and found that the content in tbody is generated by genItems().
The following example is implemented with functional components and is fully compatible with v-slot:item*:
<v-data-table ref="table" ...>
<template #body="props">
<draggable
v-if="$refs.table"
tag="tbody"
:list="props.items"
>
<v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
</draggable>
</template>
<template #item.name="{ item }">
...
</template>
</v-data-table>
Here is the definition of the VNodes component:
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
}
}

How do you use buefy's b-taginput in a custom component so that it works like v-model, It is only working one way binding?

I'm new to vue and decided to try out buefy for some useful components.
To try and keep my code organized I'm trying to make a custom component using the b-taginput.
I have it so that the component loads with the tags in someArrayofTags, but when I'm typing into the b-taginput, it does not add new tags into someArrayofTags. Hence I lose the two-way binding / updating. I would like to know where I am going wrong and how I could adjust this.
I'm not too well versed to understand how they have implemented it, but i do see that it is composed of autocomplete and b-tags https://github.com/buefy/buefy/blob/dev/src/components/taginput/Taginput.vue
I'm trying to use the custom component as such
<mytaglist v-model="someArrayofTags"></mytaglist>
I know v-model is just v-bind on value and #input events. My code is below.
<template>
<b-field label="tag inputs">
<b-taginput
:value="value"
#input=someMethod($event.target.value)
size="is-small"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear"]
};
},
methods: {
someMethod(tags) {
alert(tags)
this.$emit("input", tags)
},
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
};
</script>
Thanks
After going through the source for buefy, I found that I could watch and update the values based on a v-model within the new component.
The code below works for me, but if anyone could provide a better solution I will leave it open.
<template>
<b-field label="tag inputs">
<b-taginput
v-model="newValue"
size="is-large"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear","deep & quiet"],
newValue: this.value
};
},
methods: {
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
watch: {
newValue(value) {
this.$emit('input', value)
},
value(value) {
this.newValue = value
}
}
};
</script>
<style>
</style>

Render block in v-for over object only once vue.js?

I have an object with this structure
object: {
"prop1": [],
"prop2": [],
"prop3": [],
}
In my template I want to loop over it and display data in prop's but if there is no data in any of them I want to show something like this
<div>No data</div>
but only once and not for each prop
So far I have this
<div v-for="(props, index) in object" :key="index">
<div v-if="!props.length">
No data
</div>
</div>
But it shows message 3 times, for each prop.
I'm not sure how to solve it. Any help will be much appreciated.
To solve this in a nice way, you should use a computed property.
A computed property is basically a piece of code that give meaningless caclulations meaningful names.
export default {
data() {
return {
object: {
"prop1": [],
"prop2": [],
"prop3": [],
},
};
},
computed: {
areAllEmpty() {
return Object.values(this.object).map(e => e.length).reduce((a, b) => a + b, 0) === 0;
},
}
};
This can then be used in your template as the following:
<template>
<template v-if="areAllEmpty">
<div>No data</div>
</template>
<template v-else>
<div v-for="(props, index) in object" :key="index">
I'm index {{ index }} with length {{ props.length }}
</div>
</template>
</template>