Vuejs, Dynamic b-table with editable fields and two-way databinding - vue.js

I'm trying to generate a dynamic b-table with editable fields and with two-way databinding.
I would like to not have any hardcoded values. Now, I have:
<b-table striped hover :items="filtered">
<template v-slot:cell(issueDescription)="row">
<b-form-input v-model="row.item.issueDescription" />
</template>
<template v-slot:cell(endTime)="row">
<b-form-input v-model="row.item.endTime" />
</template>
<template v-slot:cell(startTime)="row">
<b-form-input v-model="row.item.startTime" />
</template>
</b-table>
Where:
filtered = [ { "issueDescription": "this is a description", "endTime": "2020-02-11T14:00:00.000Z",
"startTime": "2020-02-11T01:24:00.000Z" }]
If I generate template with a v-for, then I got editable fields in every column, but no binding at each field.
<b-table striped hover :items="filtered">
<template v-for="x in filtered" v-slot:cell()="row">
<b-form-input v-model="row.item.BIND_TO_SPECIFIC_TABLE_ROW_COL" />
</template>
</b-table>
How do I bind to the specific row,col??
I've made a fiddle: https://jsfiddle.net/gfhu1owt/

If you want to have ALL fields editable, you can use this syntax.
<template v-slot:cell()="{ item, field: { key } }">
<b-form-input v-model="item[key]" />
</template>
It's pretty similar to what you had. You just needed to use the key from the slot context object. (I've shorthanded it a bit, but it's the same as going row.field.key).
Also note that i don't use a v-for in the template, this is because v-slot:cell() is a fallback slot which is valid for all slots unless a specific one is defined. For example v-slot:cell(issueDescription) would overwrite v-slot:cell() for the issueDescription field.
While the above works, the problem might come one day when you have a field you DONT want to be editable, like maybe an id field in your object.
To solve this issue, I've defined my fields and passed them to the table. I've also added a editable property to the fields i want to be editable. (note this is not a standard thing in the field object, but something specific for you use-case).
I then created a computed property editableFields which returns all fields that have editable: true, and then use editableFields in my v-for inside b-table.
This way you can pick and choose which properties you want to be editable in your objects.
new Vue({
el: "#app",
computed: {
editableFields() {
return this.fields.filter(field => field.editable)
}
},
data: {
filtered: [
{
"id": "1",
"issueDescription": "this is a description",
"endTime": "2020-02-11T14:00:00.000Z",
"startTime": "2020-02-11T01:24:00.000Z"
},
{
"id": "2",
"issueDescription": "this is a description",
"endTime": "2020-02-11T14:00:00.000Z",
"startTime": "2020-02-11T01:24:00.000Z"
}
],
fields: [
{ key: 'id', label: 'ID' },
{ key: 'issueDescription', label: 'Issue Description', editable: true },
{ key: 'startTime', label: 'Start Time', editable: true },
{ key: 'endTime', label: 'End Time', editable: true }
]
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.4.1/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.4.1/dist/bootstrap-vue.min.js"></script>
<div id='app'>
<b-table :items="filtered" :fields="fields">
<template v-for="field in editableFields" v-slot:[`cell(${field.key})`]="{ item }">
<b-input v-model="item[field.key]"/>
</template>
</b-table>
</div>

Related

Buefy - Dynamically load fields in table with customization

I'm using the code below to load a table, which works well. However the fields of the dataset can vary, so I would like the template for the table to be dynamic.
So instead of defining each column in code, I would like it to load as many columns as are in the data, using the provided template. To do this, I need to replace {{props.row.uid}} by something similar where the "uid" part is dynamically loaded with the value from "column.field".
I haven't been able to figure out how to do this...
<template>
<b-table :data="data">
<template v-for="column in columns">
<b-table-column :key="column" :field="column.field" :label="column.label" centered v-slot="props">
<template v-if="column.type === 'string'">
<span>{{props.row.uid}}</span>
</template>
<template v-if="column.type === 'list'">
<span v-for="item in props.row.list" :key="item" class="tag mr-2">{{item}}</span>
</template>
</b-table-column>
</template>
</b-table>
</template>
<script>
export default {
data() {
return {
data: [
{ 'uid': 1, 'list': ["Value1","Value2","Value3"] },
{ 'uid': 2, 'list': ["Value1","Value2","Value3"] },
{ 'uid': 3, 'list': ["Value1","Value2","Value3"] }
],
columns: [
{
field: 'uid',
label: 'UID',
type: 'string'
},
{
field: 'list',
label: 'List',
type: 'list'
}
]
}
}
}
</script>
Can't believe I didn't try this sooner, but following did the job:
props.row[column.field]

Vuetify: Data table slot value to toggle another slot value

I am using a Vuetify data table and I am using v-slot:item on two of the columns to insert switches.
<v-data-table
:headers="headers"
:items="records"
:search="search"
:items-per-page="5"
>
<template v-slot:item.monitor="{ item }">
<v-switch v-model="item.monitor" color="success"></v-switch>
</template>
<template v-slot:item.manage="{ item }">
<v-switch v-model="item.manage" color="success"></v-switch>
</template>
</v-data-table>
What I want to do is set the switches like an Item Group so that only one may be selected (on) at a time, but both can be false (off). So only one can have a true value.
I have tried using the Item Group component and got that to work, but I am not sure if it is even possible within the table the way I want since these two switches are in separate slots. I am under the impression that the v-item should be sibling components, so that makes me think that would't work for this situation.
The way I see it, there are two different approches to this problem. You can either:
Watch the records array to detect changes in either monitor or manage attributes of one of its objects.
Do not use v-model directive on the switches but provide a readonly value, then change it manually on click.
I prefer the second option. You can do it like so:
Template section
<div id="app">
<v-app id="inspire">
<v-data-table :headers="headers" :items="records">
<template v-slot:item.monitor="{ item }">
<v-switch
:input-value="item.monitor"
readonly
color="success"
#click.stop="setSwitchesForItem(item.id, 'monitor', !item.monitor)"
/>
</template>
<template v-slot:item.manage="{ item }">
<v-switch
:input-value="item.manage"
readonly
color="success"
#click.stop="setSwitchesForItem(item.id, 'manage', !item.manage)"
/>
</template>
</v-data-table>
</v-app>
</div>
A couple of things to note here:
v-model has been replaced by :input-value
readonly props has been set
the click event calls a custom method. It requires the .stop suffix because of a bug documented here
Script section
export default {
// ...
data() {
return {
headers: [
{ text: "Name", value: "name" },
{ text: "Monitor", value: "monitor" },
{ text: "Manage", value: "manage" }
],
records: [
{
id: 1,
name: "Item 1",
monitor: true,
manage: false
},
{
id: 2,
name: "Item 2",
monitor: false,
manage: true
},
{
id: 3,
name: "Item 3",
monitor: false,
manage: false
}
]
};
},
methods: {
setSwitchesForItem(itemId, attributeName, attributeValue) {
const record = this.records.find(r => r.id === itemId);
// In case the new value is true, we need to make sure the other one is set to false
if (attributeValue) {
record[attributeName === "monitor" ? "manage" : "monitor"] = false;
}
record[attributeName] = attributeValue;
}
}
}
Link to the Codepen.

Switching text in each cell of a b-table to input box and back

New to vue and so far I'm loving it.
Basically trying use BV tables to create a excel like table layout. When I click on the cell text, I want it to be replaced with an input box and revert back to text on blur. Sorting doesn't work on input fields, from what I understand, so hiding seemed like the best option.
This code technically works (duno how to get to actually run in SO's code editor), but I'm trying not to have a isHidden field for each field. Currently I would need isHiddenStartTime and isHiddenEndTime plus 1 for every other field. Considering only 1 should need the flag at a time, it seems messy.
new Vue({
el: "#app",
data() {
return {
fields: [
{
key: 'startTime',
sortable: true
},
{
key: 'endTime',
sortable: true
}
],
port: [
{
"portNumber": 1,
"startTime": "00:00:00.00",
"endTime": "21:59:59.01",
"isHidden": false,
"hiddenType": ""
},
{
"portNumber": 7,
"startTime": "00:00:00.00",
"endTime": "22:59:59.00",
"isHidden": false,
"hiddenType": ""
}
]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css" />
<div id="app">
<b-table :items="port" :fields="fields" striped fixed responsive="sm">
<template v-slot:cell(endTime)="{ item }">
<div v-if="!item.isHidden" #click="item.isHidden = true">
{{ item.endTime }}
</div>
<div v-else>
<b-form-input
v-model="item.endTime"
#blur="item.isHidden = false"
placeholder="Enter end time"
autofocus
></b-form-input>
</div>
</template>
</b-table>
</div>
I tried adding a hiddenType with the name of the field added on click, but the v-if runs before the click. If I use the same flag (port.isHidden), the entire row changes. If I put the flag in the fields, the entire column changes. Considering it's a list, I don't see a correct way to use ref.
Again, new to view, all the issues make sense, I just can't find a clean way to make it work.
Instead of having a isHidden property for each field, you can create a property that contains the current field that you're editing.
In the below example i create a isEditingField property and set that to the key of the column, so either startTime or endTime. Then on blur i simply remove the property from the object again with $delete.
It's important that you use $set to create the new property if it doesn't already exist on the object, otherwise it wont be reactive. You can read more about this [here].(https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats)
You might also notice that I'm using v-slot:cell() without specifying a field. This is because it's a fallback slot, which slot all fields that aren't specifically slotted. So in this case it will count for all the slots without having to create a specific one for each.
new Vue({
el: "#app",
data() {
return {
fields: [
{
key: 'startTime',
sortable: true
},
{
key: 'endTime',
sortable: true
}
],
port: [
{
"portNumber": 1,
"startTime": "00:00:00.00",
"endTime": "21:59:59.01",
"hiddenType": ""
},
{
"portNumber": 7,
"startTime": "00:00:00.00",
"endTime": "22:59:59.00",
"hiddenType": ""
}
]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css" />
<div id="app">
<b-table :items="port" :fields="fields" striped fixed responsive="sm">
<template v-slot:cell()="{ item, field: { key } }">
<div v-if="item.isEditingField == key">
<b-form-input
v-model="item[key]"
#blur="$delete(item, 'isEditingField')"
placeholder="Enter end time"
autofocus
></b-form-input>
</div>
<div v-else #click="$set(item, 'isEditingField', key)">
{{ item[key] }}
</div>
</template>
</b-table>
</div>

How to get item value in v-slot:cell() template of b-table - BootstrapVue

I'm a very new at programming. I'm trying to figure it out how to bind the data to get the link :href work using store, vuex and bootstrap-vue table. I have spent 4 days for this, and now I'm dying. Please help.
books.js(store, vuex)
books: [
{
id: 1,
name: "name 1",
bookTitle: "book1",
thumbnail: '../../assets/img/book01.jpeg',
url: "https://www.google.com",
regDate: '2019-10'
},
{
id: 2,
name: "name2",
bookTitle: "book2",
thumbnail: "book2",
url: "http://www.yahoo.com",
regDate: '2019-10'
},
BookList.vue
<script>
export default {
name: "BookList",
components: {
},
computed: {
fields() {
return this.$store.state.fields
},
books() {
return this.$store.state.books
},
bookUrl() {
return this.$store.state.books.url
}
},
data() {
return {
itemFields: this.$store.state.fields,
items: this.$store.state.books,
//url: this.$store.state.books.url
}
}
};
</script>
<template>
<b-container>
<b-table striped hover :items="items" :fields="itemFields" >
<template v-slot:cell(thumbnail)="items">
<img src="" alt="image">
</template>
<template v-slot:cell(url)="items">
<b-link :href="bookUrl" >link</b-link>
</template>
</b-table>
</b-container>
</template>
The cell slot contains two properties you're generally interested in:
item (the current row, or, to be exact, the current item in items)
value (the cell - or, to be exact, the value of the current column within the item).
Therefore, considering your data, in the case of v-slot:cell(url)="{ value, item }", value is equivalent to item.url
Any of these would work:
<template v-slot:cell(url)="{ value }">
<b-link :href="value">link</b-link>
</template>
<template v-slot:cell(url)="slot">
<b-link :href="slot.value">{{ slot.item.bookTitle }}</b-link>
</template>
<template v-slot:cell(url)="{ item }">
<b-link :href="item.url">{{ item.bookTitle }}</b-link>
</template>
Working example here.
Note your question contains a few minor issues which might prevent your code from working (itemFields is referenced but not defined, not using proper getters, etc...). For details have a look at the working example.
And read the docs!

How to use prismic group fields as items in a vuetify v-select element?

I created a field group (question_topics) in prismic with a single key-text field (topic) inside of it for adding as many topics as I need. I have been able to get the content on to my site successfully, but I don't know how I can get the field data in the :items prop of a v-select element.
Typically with group fields I have used them in instances where I am looping through each field to render the data to do stuff like this...
<v-expansion-panel v-for="(item, index) in fields.question_topics" :key="index">
<v-expansion-panel-content>
<template slot="header">
<v-card-title class="py-4">
<h4>{{item.topic}}</h4>
</v-card-title>
</template>
</v-expansion-panel-content>
</v-expansion-panel>
But now I'm trying to do something like this...
<v-select
:items="fields.question_topics"
>
But doing this populates the v-select field with [object OBJECT] for each field I've entered into my dashboard, not the actual field values.
<v-select
:items="fields.question_topics.topic"
>
This doesn't create any data at all.
I can just render out the field {{fields.question_topics}} and I get this array:
[{ "topic": "First topic" }, { "topic": "Second topic" }, { "topic": "Third topic" }]
Is anyone able to explain how to get these topic values into the v-select element :items prop?
You just need to set item-text or item-value attributes for v-select depending on what you want item-text for the visual part and item-value for the value bidden to the options .... here is an example of the given array :
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
arr: [{
"topic": "First topic"
}, {
"topic": "Second topic"
}, {
"topic": "Third topic"
}]
}
}
})
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-content>
<v-select :items="arr"
item-value="topic"
item-text="topic"
label="Select a topic"
></v-select>
</v-content>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>