I have been looking for a way to drag and drop rows on a Bootstrap Vue table.
I was able to find a working version here: Codepen
I have tried to implement this code to my own table:
Template:
<b-table v-sortable="sortableOptions" #click="(row) => $toast.open(`Clicked ${row.item.name}`)" :per-page="perPage" :current-page="currentPage" striped hover :items="blis" :fields="fields" :filter="filter" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" :sort-direction="sortDirection" #filtered="onFiltered">
<template slot="move" slot-scope="row">
<i class="fa fa-arrows-alt"></i>
</template>
<template slot="actions" slot-scope="row">
<b-btn :href="'/bli/'+row.item.id" variant="light" size="sm" #click.stop="details(cell.item,cell.index,$event.target)"><i class="fa fa-pencil"></i></b-btn>
<b-btn variant="light" size="sm" #click.stop="details(cell.item,cell.index,$event.target)"><i class="fa fa-trash"></i></b-btn>
</template>
<template slot="priority" slot-scope="row">
<input v-model="row.item.priority" #keyup.enter="row.item.focussed = false; updatePriority(row.item), $emit('update')" #blur="row.item.focussed = false" #focus="row.item.focussed = true" class="form-control" type="number" name="priority" >
</template>
</b-table>
Script:
import Buefy from 'buefy';
Vue.use(Buefy);
const createSortable = (el, options, vnode) => {
return Sortable.create(el, {
...options
});
};
const sortable = {
name: 'sortable',
bind(el, binding, vnode) {
const table = el.querySelector('table');
table._sortable = createSortable(table.querySelector('tbody'), binding.value, vnode);
}
};
export default {
name: 'ExampleComponent',
directives: { sortable },
data() {
let self = this;
return {
blis: [],
currentPage: 1,
perPage: 10,
pageOptions: [ 5, 10, 15 ],
totalRows: 0,
sortBy: null,
sortDesc: false,
sortDirection: 'asc',
sortableOptions: {
chosenClass: 'is-selected'
},
filter: null,
modalInfo: { title: 'Title', content: 'priority' },
fields: [
{
key: 'move',
sortable: true
},
///...rest of the fields
]
}
};
Now I have been getting this error: Error in directive sortable bind hook: "TypeError: Cannot read property 'querySelector' of null"
Why is it not able to find the <tbody> ?
Edit: https://jsfiddle.net/d7jqtkon/
In line const table = el.querySelector('table'); you are trying to get the table element. The var el is the table element. That is why it return null when you use querySelector
after assigning the correct table variable the error disappears
const table = el;
table._sortable = createSortable(table.querySelector("tbody"), binding.value, vnode);
Link to working fiddle
new Vue({
el: "#app",
directives: {
sortable: {
bind(el, binding, vnode) {
let self =el
Sortable.create(el.querySelector('tbody'),{
...binding.value,
vnode:vnode,
onEnd: (e) => {
let ids = el.querySelectorAll("span[id^=paper_]")
let order = []
for (let i = 0; i < ids.length; i++) {
let item = JSON.parse(ids[i].getAttribute('values'))
//extract items checkbox onChange v-model
let itemInThisData = vnode.context.items.filter(i => i.id==item.id)
order.push({
id:item.id,
paper: item.paper,
domain:item.domain,
platform: item.platform,
country:item.country,
sort_priority: item.sort_priority,
selectpaper:itemInThisData[0].selectpaper
})
}
binding.value = []
vnode.context.items = []
binding.value = order
vnode.context.items = order
console.table(vnode.context.items)
},
});
},
}
},
mounted() {
this.totalRows = this.items?this.items.length: 0
},
methods:{
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length
this.currentPage = 1
},
log(){
console.table(this.items)
console.log(this)
},
},
data(){
return {
rankOption:'default',
totalRows: 1,
currentPage: 1,
filter: null,
filterOn:[],
sortBy:'paper',
sortDesc: false,
sortableOptions: {
chosenClass: 'is-selected'
},
perPage: this.results_per_page==='Todo' ? this.items.length : this.results_per_page?this.results_per_page:50,
pageOptions: [10, 50, 100, 500,'Todo'],
sortDirection: 'asc',
fields : [
{ key: 'paper', label: 'Soporte', sortable: true},
{ key: 'domain', label: 'Dominio', sortable: true},
{ key: 'platform', label: 'Medio', sortable: true},
{ key: 'country', label: 'País', sortable: true},
{ key: 'sort_priority', label: 'Rank', sortable: true},
{ key: 'selectpaper', label: 'Selección', sortable: true},
],
items : [
{
id:12,
paper: 'Expansion',
domain:'expansion.com',
platform: 'p',
country:'España',
sort_priority: '',
selectpaper:false
},
{
id:13,
paper: 'El economista',
domain:'eleconomista.es',
platform: 'p',
country:'España',
sort_priority: '',
selectpaper:false
},
{
id:14,
paper: 'El país',
domain:'elpais.es',
platform: 'p',
country:'España',
sort_priority: '',
selectpaper:false
}
]
}
}
})
<div id="app">
<template id="">
<b-table
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
v-sortable="items"
show-empty
small
stacked="md"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:filterIncludedFields="filterOn"
:sort-direction="sortDirection"
#filtered="onFiltered"
>
<template v-slot:cell(selectpaper)="row">
<span :id="'paper_'+row.item.id" :values="JSON.stringify(row.item)"></span>
<b-form-group>
<input type="checkbox" #change="log" v-model="row.item.selectpaper" />
</b-form-group>
</template>
<template v-slot:cell(sort_priority)="row" v-if="rankOption==='foreach-row'">
<b-form-group>
<b-form-input type="number" #change="log"
size="sm" placeholder="Rank" v-model="row.item.sort_priority">
</b-form-input>
</b-form-group>
</template>
</b-table>
</template>
</div>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs#latest/Sortable.min.js"></script>
Related
I am having this issue with a project using Vue 2.6.12. The goal is to open "add-metric" modal when the user closes "close-metric" modal with an option or multi-options selected. These 2 modals are separate components that are a child to the parent component. I am able to close the "close-metric" modal, pass the data into the parent component, flip a boolean called "add" to "true" on the parent component to signal "add-metric" modal to open. However,the "add-metric" modal doesn't open with the :if="add" even when the boolean is true. No error pops up at all.
Here is the "Close-metric" modal Code which builds the request to send over to the Parent Component.
addProjectMetric(projectMetricRequest) {
this.errors = [];
let request = {
projectMetricId: this.$helpers.generateUUID(),
projectMetricGroupId: this.projectMetric.projectMetricGroupId,
projectId: this.projectId,
type: projectMetricRequest.resolutionReason,
name: projectMetricRequest.name,
status: "Open",
tags: projectMetricRequest.tags,
description: projectMetricRequest.description,
dateCreated: projectMetricRequest.dateResolved,
dateIdentified: projectMetricRequest.dateResolved,
createdBy: [projectMetricRequest.createdBy],
assignedToUsers: this.projectMetric.assignedToUsers,
identifiedByRole: projectMetricRequest.identifiedByRole,
timeSaved: projectMetricRequest.timeSaved,
costSaved: projectMetricRequest.costSaved,
participants: projectMetricRequest.participants,
};
this.addProjectMetricFromParent(request);
//request = this.applyOverrides(request);
},
addProjectMetricFromParent(projectMetric) {
this.$emit('add-Resolution-Metric', projectMetric)
this.cancel();
},
cancel() {
this.reset();
this.hide();
},
reset() {
this.errors = [];
// this.form = this.newForm();
this.form = null;
},
hide() {
this.showCloseModal = false;
},
Here is the parent page html with the two components I am closing and trying to open
<CloseProjectMetricModal
v-if="item.action === 'Close'"
:ref="`close-${slugify(addNewText)}-modal`"
:projectId="$route.params.id"
:title="`Close "${limitText(currentMetric)}"`"
mode="Close"
:projectMetric="currentMetric"
:possibleAssignees="item.possibleAssignees"
:addTypes="item.addTypes"
:meetingId="$route.params.meetingId"
#add-Resolution-Metric="openAddModal($event)"
#submit="closeProjectMetricSubmit(item)"
/>
<addProjectMetricModal
v-if="add"
:ref="`add-project-metric-reason-modal`"
:projectId="$route.params.id"
:title="'Add New Metric '"
:mode="'Add'"
:projectMetric="viewingNewMetric"
:possibleAssignees="item.possibleAssignees"
:presetTags="projectSummary.project.presetTags"
:meetingId="$route.params.meetingId"
#submit="addProjectMetricReasonSubmit()">
</addProjectMetricModal>
Here is the code on the Parent component which should be allowing the "add-metric" modal to be seen.
props: {
title: {
type: String,
required: true,
},
addNewText: {
type: String,
},
items: {
type: Array,
required: true,
},
length: {
type: Number,
default: 6,
},
projectSummary: {
type: Object,
},
projectTags: {
type: Array,
},
status: {
type: String,
},
},
data() {
return {
currentMetric: null,
newProjectMetric: null,
add : false,
};
},
openAddModal(newMetric) {
this.newProjectMetric = newMetric;
console.log(this.newProjectMetric)
console.log("hit")
//this.closeProjectMetricSubmit();
this.$nextTick(() => {
this.add = true;
// let modal = document.getElementById(this.metricReason)
// modal.show();
// let ref = `add-project-metric-reason-modal`;
// this.$refs[ref].show();
});
},
Is there something I am missing or not understanding fully with modals? For quick example here are the HTML for the "Close-metric" and "Add-metric" modal.
<template>
<b-modal
:title="title"
v-model="showCloseModal"
:ok-disabled="editDisabled"
#ok.prevent="submit"
size="lg"
#cancel.prevent="cancel"
>
<b-form v-if="form" #submit.prevent>
<b-form-group
v-if="isPresent('name')"
:label="getTitle('name', 'Title')"
label-for="project-metric-name"
invalid-feedback="Please fill out this field"
>
<b-form-input
id="project-metric-name"
v-model.trim="form.name"
required
:disabled="editDisabled"
:autofocus="!showCloseFields"
maxlength="100"
:state="
errors.length == 0
? null
: errors.length == 0
? null
: form.name.length > 0
"
></b-form-input>
</b-form-group>
<!-- This is for only the Closed Section -->
<b-row v-if="!this.meetingId == '' && this.mode=='Close'">
<b-col cols="4" v-if="isPresent('resolutionReason')">
<b-form-group
:label="getTitle('resolutionReason', 'Resolution Reason')"
label-for="project-metric-resolution-reason"
>
<multiselect
v-model="projectMetricResolutions"
:options="otherMetricResolutions"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick Resolution Reason"
label="name"
track-by="name"
:preselect-first="false"
class="normalFontSize"
:show-labels="false"
>
<template slot="selection" slot-scope="{ values, isOpen }"
><span
class="multiselect__single"
v-if="values.length && !isOpen"
>{{ values.length }} options selected</span
></template
>
</multiselect>
</b-form-group>
</b-col>
<b-col v-if="isPresent('resolutionLevel')">
<b-form-group
:label="getTitle('resolutionLevel', 'Resolution Level')"
label-for="project-metric-resolution-level"
>
<b-form-select
id="project-metric-resolution-level"
:disabled="editDisabled"
v-model="form.resolutionLevel"
>
<option value=""></option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</b-form-select>
</b-form-group>
</b-col>
</b-row>
<!-- This is where the Closed Section Ends -->
</b-form>
<MessageBlock :messages="errors"></MessageBlock>
<template v-slot:modal-ok>
<div>
<b-spinner v-if="sending" small label="Spinning" />
<div v-else-if="mode === 'Close'">Close</div>
</div>
</template>
</b-modal>
</template>
export default {
name: "CloseProjectMetricModal",
mixins: [pmMixin],
components: { Multiselect },
props: {
title: { type: String, default: "Edit Action Item" },
mode: { type: String, default: "Edit" },
projectMetric: { type: Object },
projectId: String,
possibleAssignees: { type: Array, default: () => [] },
addTypes: {
type: Array,
default: () => [
"Risk",
"Issue",
"Efficiency",
"Innovation",
"Partnership",
"Sustainability",
],
},
dataTemplate: Object,
presetTags: { type: Array, default: () => [] },
meetingId: { type: String, default: "" },
},
data() {
return {
errors: [],
showCloseModal: false,
sending: false,
returnToView: false,
form: null,
description: "",
filterCriteria: "",
projectMetricResolutions: [],
otherMetricResolutions: [
{ name: "Innovation", type: "Innovation" },
{ name: "Efficiency", type: "Efficiency" },
{ name: "Partnership", type: "Partnership" },
{ name: "Sustainability", type: "Sustainability" },
],
allResolutionReason: "",
todaysNewDate: new Date().toISOString().substr(0, 10),
showAddModal: false,
};
},
}
AddMetricModal
<template>
<b-modal
:title="title"
v-model="showModal"
:ok-disabled="editDisabled"
#ok.prevent="submit"
size="lg"
#cancel.prevent="cancel"
>
<b-form v-if="form" #submit.prevent>
<b-form-group
v-if="isPresent('name')"
:label="getTitle('name', 'Title')"
label-for="project-metric-name"
invalid-feedback="Please fill out this field"
>
<b-form-input
id="project-metric-name"
v-model.trim="form.name"
required
:disabled="editDisabled"
:autofocus="!showCloseFields"
maxlength="100"
:state="
errors.length == 0
? null
: errors.length == 0
? null
: form.name.length > 0
"
></b-form-input>
</b-form-group>
<b-form-group
v-if="isPresent('description')"
:label="getTitle('description')"
label-for="project-metric-description"
>
<b-form-textarea
id="project-metric-description"
v-model.trim="form.description"
:disabled="editDisabled"
maxlength="1000"
rows="3"
></b-form-textarea>
</b-form-group>
<b-row>
<b-col v-if="isPresent('type')">
<b-form-group
:label="getTitle('type')"
label-for="project-metric-type"
>
<div class="select-wrapper">
<b-form-select
id="project-metric-type"
:disabled="editDisabled"
v-model="form.type"
:options="getTypeOptions"
></b-form-select>
</div>
</b-form-group>
</b-col>
<b-col>
<b-button
v-for="(t, i) in unselectedTags"
:key="i"
class="mr-1 button-tag"
#click="addTag(t)"
>
{{ t }}
</b-button>
<b-form-group
v-if="isPresent('tags')"
:label="getTitle('tags')"
label-for="project-metric-tags"
>
<b-form-tags
id="project-metric-tags"
v-model.trim="form.tags"
separator=","
add-button-variant="secondary"
:disabled="editDisabled"
></b-form-tags>
</b-form-group>
</b-col>
</b-row>
</b-form>
<MessageBlock :messages="errors"></MessageBlock>
<template v-slot:modal-ok>
<div>
<b-spinner v-if="sending" small label="Spinning" />
<div v-else-if="mode === 'Add'">Add</div>
<div v-else-if="mode === 'Edit'">Save</div>
<div v-else-if="mode === 'Close'">Close</div>
</div>
</template>
</b-modal>
</template>
import axios from "axios";
import pmMixin from "#/mixins/project-metric-mixin.js";
import Multiselect from "vue-multiselect";
export default {
name: "EditProjectMetricModal",
mixins: [pmMixin],
components: { Multiselect },
props: {
title: { type: String, default: "Edit Action Item" },
mode: { type: String, default: "Edit" },
projectMetric: { type: Object },
projectId: String,
possibleAssignees: { type: Array, default: () => [] },
addTypes: {
type: Array,
default: () => [
"Risk",
"Issue",
"Efficiency",
"Innovation",
"Partnership",
"Sustainability",
],
},
dataTemplate: Object,
presetTags: { type: Array, default: () => [] },
meetingId: { type: String, default: "" },
},
data() {
return {
errors: [],
showModal: false,
sending: false,
returnToView: false,
form: null,
description: "",
filterCriteria: "",
uselessArray: [],
projectMetricResolutions: [],
otherMetricResolutions: [
{ name: "Innovation", type: "Innovation" },
{ name: "Efficiency", type: "Efficiency" },
{ name: "Partnership", type: "Partnership" },
{ name: "Sustainability", type: "Sustainability" },
],
allResolutionReason: "",
todaysNewDate: new Date().toISOString().substr(0, 10)
};
},
Any help or direction would be greatly appreciated !
The title says it all. I want to filter my bootstrap table with an input box.
Here is my .html part of the component:
<b-table
:items="Table"
:fields="fields"
striped
small
>
</b-table>
Here is the .vue file
<template src="./jointable.component.html"> </template>
<style scoped src="./jointable.component.css"> </style>
<script>
import axios from 'axios'
export default {
name: 'jointable',
data(){
return {
Table: [],
fields: [
{key: 'client_id', label: "School Code", sortable: true},
{key: 'client_name', sortable: true},
{key: 'uuid', label: "ID", sortable: true},
{key: 'step', label: "Job Running", sortable: true},
{key: 'serverid', sortable: true},
{key: 'create_timestamp', label: "Job Start", sortable: true},
{key: 'time_elapsed', sortable: true},
{key: 'wh_db_host', sortable: true}
]
}
},
methods : {
loadData: function(){
axios.get("http://192.168.56.101:5000/jointable")
.then((res) => {
this.Table = res.data
})
.catch((err) => {
console.log(err)
})
}
},
mounted() {
this.loadData();
setInterval(function(){
this.loadData()
}.bind(this), 10000)
},
computed() {
}
}
</script>
So right now you can see that my script reloads the table every 10 seconds with updated data. This is fine. I want to also now have my table searchable/filterable. I know I have to use the computed thing but how do I use it.
Thank you to whoever helps me!
There's various ways to filter, but the most simple version is simply passing in a string to the filter prop on b-table.
This will search all columns for the string you pass in, so if you bind a data property to a input's v-model and the same data property to the filter property on your table, anything you type in the input will filter the table.
You can read more about how filtering works and some of the other methods in the documentation.
new Vue({
el: '#app',
data() {
return {
filter: '',
items: [
{ id: 1, first_name: "Mikkel", last_name: "Hansen", age: 54 },
{ id: 2, first_name: "Kasper", last_name: "Hvidt", age: 42 },
{ id: 3, first_name: "Lasse", last_name: "Boesen", age: 39 },
{ id: 4, first_name: "Kasper", last_name: "Hansen", age: 62 },
{ id: 5, first_name: "Mads", last_name: "Mikkelsen", age: 31 },
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.6.1/dist/bootstrap-vue.min.js"></script>
<link href="https://unpkg.com/bootstrap-vue#2.6.1/dist/bootstrap-vue.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<div id="app" class="p-5">
<b-input v-model="filter" placeholder="Filter table.."></b-input>
<hr />
<b-table :items="items" :fields="fields" :filter="filter">
</b-table>
</div>
Detailed Code for filtering bootstrap-vue please refer documentation Thanks
new Vue({
el: '#app',
data() {
return {
items: [{
isActive: true,
age: 40,
name: {
first: 'Dickerson',
last: 'Macdonald'
}
},
{
isActive: false,
age: 21,
name: {
first: 'Larsen',
last: 'Shaw'
}
},
{
isActive: false,
age: 9,
name: {
first: 'Mini',
last: 'Navarro'
},
_rowVariant: 'success'
},
{
isActive: false,
age: 89,
name: {
first: 'Geneva',
last: 'Wilson'
}
},
{
isActive: true,
age: 38,
name: {
first: 'Jami',
last: 'Carney'
}
},
{
isActive: false,
age: 27,
name: {
first: 'Essie',
last: 'Dunlap'
}
},
{
isActive: true,
age: 40,
name: {
first: 'Thor',
last: 'Macdonald'
}
},
{
isActive: true,
age: 87,
name: {
first: 'Larsen',
last: 'Shaw'
},
_cellVariants: {
age: 'danger',
isActive: 'warning'
}
},
{
isActive: false,
age: 26,
name: {
first: 'Mitzi',
last: 'Navarro'
}
},
{
isActive: false,
age: 22,
name: {
first: 'Genevieve',
last: 'Wilson'
}
},
{
isActive: true,
age: 38,
name: {
first: 'John',
last: 'Carney'
}
},
{
isActive: false,
age: 29,
name: {
first: 'Dick',
last: 'Dunlap'
}
}
],
fields: [{
key: 'name',
label: 'Person full name',
sortable: true,
sortDirection: 'desc'
},
{
key: 'age',
label: 'Person age',
sortable: true,
class: 'text-center'
},
{
key: 'isActive',
label: 'Is Active',
formatter: (value, key, item) => {
return value ? 'Yes' : 'No'
},
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: 'actions',
label: 'Actions'
}
],
totalRows: 1,
currentPage: 1,
perPage: 5,
pageOptions: [5, 10, 15, {
value: 100,
text: "Show a lot"
}],
sortBy: '',
sortDesc: false,
sortDirection: 'asc',
filter: null,
filterOn: [],
infoModal: {
id: 'info-modal',
title: '',
content: ''
}
}
},
computed: {
sortOptions() {
// Create an options list from our fields
return this.fields
.filter(f => f.sortable)
.map(f => {
return {
text: f.label,
value: f.key
}
})
}
},
mounted() {
// Set the initial number of items
this.totalRows = this.items.length
},
methods: {
info(item, index, button) {
this.infoModal.title = `Row index: ${index}`
this.infoModal.content = JSON.stringify(item, null, 2)
this.$root.$emit('bv::show::modal', this.infoModal.id, button)
},
resetInfoModal() {
this.infoModal.title = ''
this.infoModal.content = ''
},
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length
this.currentPage = 1
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.6.1/dist/bootstrap-vue.min.js"></script>
<link href="https://unpkg.com/bootstrap-vue#2.6.1/dist/bootstrap-vue.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<div id="app" class="p-5">
<template>
<b-container fluid>
<!-- User Interface controls -->
<b-row>
<b-col lg="6" class="my-1">
<b-form-group
label="Sort"
label-for="sort-by-select"
label-cols-sm="3"
label-align-sm="right"
label-size="sm"
class="mb-0"
v-slot="{ ariaDescribedby }"
>
<b-input-group size="sm">
<b-form-select
id="sort-by-select"
v-model="sortBy"
:options="sortOptions"
:aria-describedby="ariaDescribedby"
class="w-75"
>
<template #first>
<option value="">-- none --</option>
</template>
</b-form-select>
<b-form-select v-model="sortDesc" :disabled="!sortBy" :aria-describedby="ariaDescribedby" size="sm" class="w-25">
<option :value="false">Asc</option>
<option :value="true">Desc</option>
</b-form-select>
</b-input-group>
</b-form-group>
</b-col>
<b-col lg="6" class="my-1">
<b-form-group label="Initial sort" label-for="initial-sort-select" label-cols-sm="3" label-align-sm="right" label-size="sm" class="mb-0">
<b-form-select id="initial-sort-select" v-model="sortDirection" :options="['asc', 'desc', 'last']" size="sm"></b-form-select>
</b-form-group>
</b-col>
<b-col lg="6" class="my-1">
<b-form-group label="Filter" label-for="filter-input" label-cols-sm="3" label-align-sm="right" label-size="sm" class="mb-0">
<b-input-group size="sm">
<b-form-input id="filter-input" v-model="filter" type="search" placeholder="Type to Search"></b-form-input>
<b-input-group-append>
<b-button :disabled="!filter" #click="filter = ''">Clear</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
</b-col>
<b-col lg="6" class="my-1">
<b-form-group v-model="sortDirection" label="Filter On" description="Leave all unchecked to filter on all data" label-cols-sm="3" label-align-sm="right" label-size="sm" class="mb-0" v-slot="{ ariaDescribedby }">
<b-form-checkbox-group v-model="filterOn" :aria-describedby="ariaDescribedby" class="mt-1">
<b-form-checkbox value="name">Name</b-form-checkbox>
<b-form-checkbox value="age">Age</b-form-checkbox>
<b-form-checkbox value="isActive">Active</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
</b-col>
<b-col sm="5" md="6" class="my-1">
<b-form-group label="Per page" label-for="per-page-select" label-cols-sm="6" label-cols-md="4" label-cols-lg="3" label-align-sm="right" label-size="sm" class="mb-0">
<b-form-select id="per-page-select" v-model="perPage" :options="pageOptions" size="sm"></b-form-select>
</b-form-group>
</b-col>
<b-col sm="7" md="6" class="my-1">
<b-pagination v-model="currentPage" :total-rows="totalRows" :per-page="perPage" align="fill" size="sm" class="my-0"></b-pagination>
</b-col>
</b-row>
<!-- Main table element -->
<b-table :items="items" :fields="fields" :current-page="currentPage" :per-page="perPage" :filter="filter" :filter-included-fields="filterOn" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" :sort-direction="sortDirection" stacked="md" show-empty small
#filtered="onFiltered">
<template #cell(name)="row">
{{ row.value.first }} {{ row.value.last }}
</template>
<template #cell(actions)="row">
<b-button size="sm" #click="info(row.item, row.index, $event.target)" class="mr-1">
Info modal
</b-button>
<b-button size="sm" #click="row.toggleDetails">
{{ row.detailsShowing ? 'Hide' : 'Show' }} Details
</b-button>
</template>
<template #row-details="row">
<b-card>
<ul>
<li v-for="(value, key) in row.item" :key="key">{{ key }}: {{ value }}</li>
</ul>
</b-card>
</template>
</b-table>
<!-- Info modal -->
<b-modal :id="infoModal.id" :title="infoModal.title" ok-only #hide="resetInfoModal">
<pre>{{ infoModal.content }}</pre>
</b-modal>
</b-container>
</template>
</div>
Is this what you are looking for?
new Vue({
el: "#app",
data: {
Items: [
{Name: 'Lorem ipsum'},
{Name: 'consectetur'},
{Name: 'adipisicing.'}
],
search: ''
},
computed: {
filteredItems() {
return this.Items.filter(item => item.Name.toLowerCase().includes(this.search.toLowerCase()))
}
}
})
.item {
border: solid black 2px;
padding: 10px;
margin: 10px;
}
.search {
border: solid black 2px;
padding: 10px;
margin: 10px;
}
p {
padding: 10px;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<p>Found {{filteredItems.length}} items</p>
<input class="search" type="text" placeholder="Search" v-model="search">
<div class="item" v-for="item in filteredItems">{{item.Name}}</div>
</div>
Error using a slot and slot-scope on VueJS to put into a column of a table.
A template as follows:
<b-table hover striped :items="users" :fields="fields">
<template slot="actions" slot-scope="data">
<b-button variant="warning" #click="loadUser(data.item)" class="mr-2">
<i class="fa fa-pencil"></i>
</b-button>
<b-button variant="danger" #click="loadUser(data.item, 'remove')">
<i class="fa fa-trash"></i>
</b-button>
</template>
</b-table>
After all, I'm able to get users on DB. And have success to show it on table. But, the buttons on slot can't.
The idea is put two buttons:
The update button
The delete button
And each button manage one function.
<script>
import { baseApiUrl, showError } from '#/global'
import axios from 'axios'
export default {
name: 'UserAdmin',
data: function(){
return {
mode: 'save',
user: {},
users: [],
fields: [
{ key: 'id', label: 'Código', sortable: true},
{ key: 'name', label: 'Nome', sortable: true},
{ key: 'email', label: 'E-mail', sortable: true},
{ key: 'adminMaster', label: 'Administrador', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{key: 'adminEnterprise', label: 'Chefe de Empreendimento', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{ key: 'manager', label: 'Gerente', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{ key: 'customer', label: 'Cliente', sortable: true,
formatter: value => value ? 'Sim' : 'Não'},
{ key: 'actions', label: 'Ações'}
],
}
},
methods: {
loadUsers() {
const url = `${baseApiUrl}/users`
axios.get(url).then(res => {
this.users = res.data
})
},
reset() {
this.mode = 'save'
this.user = {}
this.loadUsers()
},
save() {
const method = this.user.id ? 'put' : 'post'
const id = this.user.id ? `/${this.user.id}` : ''
axios[method](`${baseApiUrl}/users${id}`, this.user)
.then(() => {
this.$toasted.global.defaultSuccess()
this.reset()
})
.catch(showError)
},
remove() {
const id = this.user.id
axios.delete(`${baseApiUrl}/users/${id}`)
.then(() => {
this.$toasted.global.defaultSuccess()
this.reset()
})
.catch(showError)
},
loadUser(user, mode = 'save') {
this.mode = mode
this.user = { ...user }
}
},
mounted() {
this.loadUsers()
}
}
</script>
Alright, so, it looks like you're using an official release of BV 2.0., BV is preparing for Vue 3 which is revising the component-slotting system.
As such, BV has renamed the custom slots, you need to use cell({key}) like so:
<template slot="cell(actions)" slot-scope="data">
I'm making a dynamic admin panel with crud generator with the help of laravel and vue.
I have a table where I'm loading data asynchronously from API. There is an is_featured column in my table which I want to be a switch. So that the user can change the value from the table page instead of going to edit page.
To generate my entire table there is a configuration object that contains which fields to show and the type of that field and other metadata. In the configuration object, there is a field named prerender which is responsible to prerender fields that require calling other API or some editable fields like select dropdown or switch.
To make switch and select fields work, I have an empty object named fieldData.
When a field with type: boolean and prerender: true is found, my code will initialize a property with field.name as property name in field data and fill it with the corresponding values under that field name
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
BUT THE SWITCH AND SELECT ARE NOT WORKING HERE
So I need help.
Here's my entire code for reference
<template>
<div class="app-container">
<el-row :gutter="20" style="display: flex; align-items: center;">
<el-col :span="10">
<h1 style="text-transform: capatilize;">{{ resourceName }}</h1>
</el-col>
<el-col :span="14" style="display: flex; justify-content: flex-end; align-items: center">
<el-input
v-model="navigation.search"
placeholder="Search anything here"
prefix-icon="el-icon-search"
style="width: 300px; margin: 0 10px;"
#keydown.enter.native="handleGlobalSearch"
/>
<FilterPannel
style="margin: 0 10px"
:filter-pannel-obj="filterPannelObj"
#set-filter="handleFilterVals"
#reset-filter="getTableData({})"
/>
<Import
:url="`/api/${resourceName}/upload`"
#import-success="handleImportSucces"
#import-error="handleImportError"
/>
<Export :url="`/api/${resourceName}/export`" :selected-ids="selected.map(el => el.id)" />
<el-button
type="info"
icon="el-icon-delete"
#click="$refs['table'].clearSelection()"
>Clear Selection</el-button>
<el-button type="danger" icon="el-icon-delete" #click="handleMultipleDelete">Delete Selected</el-button>
</el-col>
</el-row>
<el-row>
<el-table
ref="table"
v-loading="loading.tableData"
:data="tableData"
border
:row-key="getRowKeys"
#sort-change="handleSortChange"
#selection-change="handleSelectionChange"
>
<el-table-column type="selection" label="Selection" reserve-selection />
<el-table-column label="Actions" width="200">
<template slot-scope="scope">
<div style="display: flex; justify-content: space-around;">
<el-button
icon="el-icon-view"
type="primary"
circle
#click="$router.push(`/${resourceName}/view/${scope.row.id}`)"
/>
<el-button
icon="el-icon-edit"
type="success"
circle
#click="$router.push(`/${resourceName}/edit/${scope.row.id}`)"
/>
<el-button
icon="el-icon-delete"
type="danger"
circle
#click="handleDeleteClick(scope.row.id)"
/>
</div>
</template>
</el-table-column>
<el-table-column
v-for="field in fieldsToShow"
:key="field.name"
:prop="field.name"
:label="field.name.replace('_',' ')"
sortable="custom"
>
<template slot-scope="scope">
<div
v-if="field.type=='multilangtext'"
class="cell"
>{{ JSON.parse(scope.row[field.name])[$store.state.app.language] }}</div>
<div v-if="field.type=='text'" class="cell">{{ scope.row[field.name] }}</div>
<el-tag v-if="field.type=='tag'">{{ scope.row.type }}</el-tag>
<img v-if="field.type=='image'" :src="scope.row.icon" width="100px" height="100px" />
<div v-if="field.type=='oneFrom'" class="cell">
<el-tag
:key="scope.row[field.name]"
>{{field.multilang ? scope.row[field.name][$store.state.app.language] : scope.row[field.name]}}</el-tag>
</div>
<div v-if="field.type=='manyFrom'" class="cell">
<el-tag
style="margin: 5px"
v-for="(item, index) in scope.row[field.name]"
:key="`${field.multilang ? item[$store.state.app.language] : item}+${index}`"
>{{field.multilang ? item[$store.state.app.language] : item}}</el-tag>
</div>
<div v-if="field.type=='boolean'" class="cell">
{{scope.row.id}} =>
{{field.name}} =>>
{{scope.row[field.name]}} =>>>
{{fieldData[field.name][scope.row.id]}}
<el-switch
:key="`switch${scope.row.id}`"
v-model="fieldData[field.name][scope.row.id]"
:active-value="1"
:inactive-value="0"
></el-switch>
</div>
<div v-if="field.type=='select'" class="cell">
<el-select :key="`select${scope.row.id}`" v-model="fieldData[field.name][scope.row.id]" placeholder="Select">
<el-option
v-for="item in field.options"
:key="item"
:label="item"
:value="item"
></el-option>
</el-select>
</div>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row>
<pagination
style="padding: 0;"
:total="paginationData.total"
:page.sync="paginationData.current_page"
:limit.sync="paginationData.per_page"
#pagination="handlePagination"
/>
</el-row>
</div>
</template>
<script>
import Pagination from '#/components/Pagination';
import FilterPannel from '#/components/FilterPannel';
import Export from './components/Export'; // ? not needed
import Import from './components/Import';
import axios from 'axios';
import Resource from '#/api/resource';
const resourceName = 'coupons';
const ResourceApi = new Resource(resourceName);
export default {
name: 'CategoryList',
components: {
Pagination,
FilterPannel,
Export,
Import,
},
data() {
return {
resourceName: resourceName,
language: 'en',
tableData: [],
fieldData: {},
fieldsToShow: [
{ name: 'title', type: 'multilangtext' },
// { name: 'description', type: 'multilangtext' },
{ name: 'code', type: 'text' },
// { name: 'expiry_date', type: 'text' },
{ name: 'type', type: 'tag' },
{
name: 'store_id',
type: 'oneFrom',
url: '/api/stores/',
foreignKey: 'store_id',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'tags',
type: 'manyFrom',
url: '/api/tags?idsarr=',
foreignKey: 'tags',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'brands',
type: 'manyFrom',
url: '/api/brands?idsarr=',
foreignKey: 'brands',
attrName: 'name',
multilang: true,
prerender: true,
},
// { name: 'brands', type: 'text' },
{ name: 'is_featured', type: 'boolean', prerender: true },
{
name: 'status',
type: 'select',
options: ['publish', 'draft', 'trash'],
prerender: true,
},
],
paginationData: {
current_page: 0,
last_page: 0,
per_page: 0,
total: 0,
},
navigation: {
page: 1,
limit: 10,
sort: '',
'sort-order': 'asc',
filters: '',
search: '',
},
filters: '',
selected: [], // ? for selection
loading: {
tableData: false,
},
allData: [],
filterPannelObj: {
title: {
default: '',
type: 'Input',
label: 'Title',
},
description: {
default: '',
type: 'Input',
label: 'Description',
},
promo_text: {
default: '',
type: 'Input',
label: 'Promo Text',
},
type: {
default: [],
type: 'checkbox',
label: 'Type',
src: [
{ value: 'coupon', label: 'Coupon' },
{ value: 'offer', label: 'Offer' },
],
},
code: {
default: '',
type: 'Input',
label: 'Code',
},
store_id: {
default: [],
type: 'select',
label: 'Store',
src: [],
multiple: true,
},
brands: {
default: [],
type: 'select',
label: 'Brands',
src: [],
multiple: true,
},
tags: {
default: [],
type: 'select',
label: 'Tags',
src: [],
multiple: true,
},
cats: {
default: [],
type: 'select',
label: 'Categories',
src: [],
multiple: true,
},
},
};
},
watch: {
async 'navigation.search'(newVal, oldVal) {
await this.handleGlobalSearch();
},
},
async created() {
await this.getTableData({});
this.allData = await ResourceApi.list({ limit: -1 });
// to fill filter dialog selects
// To get brands
this.filterPannelObj.brands.src = (await axios.get(
`/api/brands?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get tags
this.filterPannelObj.tags.src = (await axios.get(
`/api/tags?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get categories
this.filterPannelObj.cats.src = (await axios.get(
`/api/categories?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get stores
this.filterPannelObj.store_id.src = (await axios.get(
`/api/stores?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
},
methods: {
printScope(x) {
console.log('TCL: printScope -> x', x);
},
async getTableData(query) {
this.loading.tableData = true;
const responseData = await ResourceApi.list(query);
this.tableData = responseData.data;
this.paginationData = this.pick(
['current_page', 'last_page', 'per_page', 'total'],
responseData
);
Object.keys(this.paginationData).forEach(
key => (this.paginationData[key] = parseInt(this.paginationData[key]))
);
await this.handlePrerender();
this.loading.tableData = false;
},
async handlePrerender() {
this.fieldsToShow.forEach(async field => {
if (field.prerender) {
switch (field.type) {
case 'oneFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'manyFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'boolean': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
case 'select': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
}
}
});
},
// utils
pick(propsArr, srcObj) {
return Object.keys(srcObj).reduce((obj, k) => {
if (propsArr.includes(k)) {
obj[k] = srcObj[k];
}
return obj;
}, {});
},
// ? remember to refactor the parameter id
async setRelatedFieldName({
name,
type,
url,
foreignKey,
attrName,
multilang,
}) {
this.tableData.forEach(async data => {
if (type === 'oneFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data[
attrName
];
if (multilang) {
data[name] = JSON.parse(data[name]);
}
} else if (type === 'manyFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data.map(
idata => (multilang ? JSON.parse(idata[attrName]) : idata[attrName])
);
}
});
},
// Sort
async handleSortChange(change) {
this.navigation.sort = change.prop;
if (change.order === 'ascending') {
this.navigation['sort-order'] = 'asc';
} else if (change.order === 'descending') {
this.navigation['sort-order'] = 'desc';
}
await this.getTableData(this.navigation);
},
// Pagination
async handlePagination(obj) {
// obj page obj containing {page: ..., limit: ...}
this.navigation.page = obj.page;
this.navigation.limit = obj.limit;
await this.getTableData(this.navigation);
},
// Global Search
async handleGlobalSearch() {
await this.getTableData(this.navigation);
},
// ? Skipped for now
// Filters
async handleFilterVals(filterparams) {
console.log('TCL: handleFilterVals -> filterparams', filterparams);
this.navigation.filters = JSON.stringify(filterparams.filters);
this.navigation.sort = filterparams.sort.field;
this.navigation.sort = 'name';
this.navigation['sort-order'] = filterparams.sort.asc ? 'asc' : 'desc';
await this.getTableData(this.navigation);
},
async handleDeleteClick(id) {
ResourceApi.destroy(id)
.then(res => {
this.$message.success('Delete Successfully');
this.getTableData({ page: this.paginationData.current_page });
})
.error(err => {
this.$message.error(err);
});
},
// Selection methods
handleSelectionChange(selection) {
this.selected = selection;
},
getRowKeys(row) {
return row.id;
},
// Multiple Delete
handleMultipleDelete() {
axios
.delete(
`/api/${this.resourceName}/delete-multiple?ids=${this.selected
.map(item => item.id)
.join(',')}`
)
.then(async () => {
this.$message.success('Records deleted successfully');
await this.getTableData({ page: this.paginationData.current_page });
if (this.tableData.length === 0) {
await this.getTableData({
page: this.paginationData.current_page,
});
}
this.$refs.table.clearSelection();
})
.catch();
},
// Import Events
handleImportSucces() {
this.$message.success('New Data Imported');
this.getTableData({});
},
handleImportError(err) {
this.$message.error('There were some errors. CHK console');
console.log(err);
},
},
};
</script>
<style lang="scss" scoped>
</style>
I guess this is a reactive issue. The value of dict and actually not affecting the feildData dict.
changes -
this.fieldData[field.name] = {};
replace this with
self.$set(self.fieldData,field.name,{});
and
this.fieldData[field.name][data.id] = data[field.name] // to
//Replace
self.$set(self.fieldData[field.name],data.id, data[field.name]);
I think this will fix the issue.Instead of using = use $set for value assignment.
Codepen - https://codepen.io/Pratik__007/pen/gObGOKx
I have a JSON file that feeds data from the backend and into a table (Bootstrap-Vue built in). In that table I want to have VueJS create a button that would be generated under a specific condition and if clicked would then generate a new page with additional details.
Here is a sample of the JSON output:
{"driver_id":2,"driver_name":"{driver_first_name}, {driver_last_name}","driver_truck":"58","driver_trailer":"37","driver_status":"sleeping","has_violations":true},
So if "has_violations" is true, then the button would be generated that onclick would request additional data from the backend via JSON and then generate a new page based on that data.
So sort of the vanilla js equivalent of event.target.closest('tr').dataset.userId
But how can I do this in VueJS?
Edited in Table Code (Per Request):
<template>
<b-container fluid>
<!--Search Controls-->
<b-row>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Filter" class="mb-0">
<b-input-group>
<b-form-input v-model="filter" placeholder="Type to Search" />
<b-input-group-append>
<b-btn :disabled="!filter" #click="filter = ''">Clear</b-btn>
</b-input-group-append>
</b-input-group>
</b-form-group>
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Sort" class="mb-0">
<b-input-group>
<b-form-select v-model="sortBy" :options="sortOptions">
<option slot="first" :value="null">-- none --</option>
</b-form-select>
<b-form-select :disabled="!sortBy" v-model="sortDesc" slot="append">
<option :value="false">Asc</option>
<option :value="true">Desc</option>
</b-form-select>
</b-input-group>
</b-form-group>
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Sort direction" class="mb-0">
<b-input-group>
<b-form-select v-model="sortDirection" slot="append">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
<option value="last">Last</option>
</b-form-select>
</b-input-group>
</b-form-group>
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Per page" class="mb-0">
<b-form-select :options="pageOptions" v-model="perPage" />
</b-form-group>
</b-col>
</b-row>
<!--Search Controls-->
<!-- Main table element -->
<b-table show-empty
stacked="md"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-direction="sortDirection"
#filtered="onFiltered"
>
<template slot="driverName" slot-scope="row">{{row.value.driver_first_name}} {{row.value.driver_last_name}}</template>
<template slot="truckNumber" slot-scope="row">{{row.value.driver_truck}}</template>
<template slot="truckNumber" slot-scope="row">{{row.value.driver_trailer}}</template>
<template slot="status" slot-scope="row">{{row.value.driver_status}}</template>
<template slot="violations" slot-scope="row">{{row.value?'Yes':'No'}}</template>
<template slot="actions" slot-scope="row">
<router-link :to="{name: 'driver_violations_list'}">
<b-button id="driverLogs">View Driver Logs</b-button>
</router-link>
</template>
</b-table>
<b-row>
<b-col md="6" class="my-1">
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage" class="my-0" />
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
data () {
return {
items: items,
fields: [
{ key: 'driver_name', label: 'Driver Name', sortable: true, sortDirection: 'desc' },
{ key: 'truck_number', label: 'Truck Number', sortable: true, 'class': 'text-center' },
{ key: 'trailer_number', label: 'Trailer Number', sortable: true, 'class': 'text-center' },
{ key: 'has_violations', label: 'Violations' },
{ key: 'driver_status', label: 'Status' },
{ key: 'actions', label: 'Actions' }
],
currentPage: 1,
perPage: 5,
totalRows: items.length,
pageOptions: [ 5, 10, 15 ],
sortBy: null,
sortDesc: false,
sortDirection: 'asc',
filter: null,
}
},
computed: {
sortOptions () {
return this.fields
.filter(f => f.sortable)
.map(f => { return { text: f.label, value: f.key } })
}
},
methods: {
onFiltered (filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length
this.currentPage = 1
}
//Get JSON
getDriverStatus: function () {
const url = 'driver_status_data.json'
axios.get(url, {
dataType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
mode: 'no-cors',
credentials: 'include'
})
.then(function (response) {
console.log(JSON.stringify(response.data))
this.courses = JSON.stringify(response.data)
})
.catch(function (error) {
console.log(error)
})
}
}
}
</script>
Use the table slots as documented here to conditionally render the button.
Here's an example:
new Vue({
el: '#app',
computed: {
driverName () {
return this.driver ? `${this.driver.driver_name.driver_first_name} ${this.driver.driver_name.driver_last_name}` : 'N / A'
}
},
methods: {
showViolations (driver) {
this.driver = driver
this.loading = true
// Simulate an async api call with setInterval and setTimeout
let interval = setInterval(() => {
this.progress += 10
}, 250)
setTimeout(() => {
this.loading = false
this.progress = 0
clearInterval(interval)
}, 2500)
}
},
data () {
return {
progress: 0,
driver: null,
loading: false,
violations: [
'Violation 1',
'Violation 2',
'Violation 3'
],
fields: {
driver_id: {
label: 'ID',
sortable: true
},
first_name: {
key: 'driver_name.driver_first_name',
label: 'First Name',
sortable: true
},
last_name: {
key: 'driver_name.driver_last_name',
label: 'Last Name',
sortable: true
},
driver_truck: {
label: 'Truck',
sortable: true
},
driver_trailer: {
label: 'Trailer',
sortable: true
},
driver_status: {
label: 'Status',
sortable: true
},
has_violations: {
label: 'Violations',
sortable: true
}
},
items: [
{
driver_id:2,
driver_name: {
driver_first_name: 'Bob',
driver_last_name: 'Dole'
},
driver_truck: 58,
driver_trailer: 37,
driver_status: 'Sleeping',
has_violations: true
},
{
driver_id:3,
driver_name: {
driver_first_name: 'John',
driver_last_name: 'Lennon'
},
driver_truck: 69,
driver_trailer: 34,
driver_status: 'Deep Sleeping',
has_violations: false
}
]
}
}
})
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill#latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table striped hover :fields="fields" :items="items">
<template slot="has_violations" slot-scope="data">
<template v-if="data.item.has_violations">
<b-button size="md" variant="primary" v-b-modal.violations #click="showViolations(data.item)">
See Violations
</b-button>
</template>
</template>
</b-table>
<b-modal id="violations" :title="`Violations By: ${driverName}`">
<b-progress v-show="loading" :value="progress" :max="100" animated></b-progress>
<p v-if="!loading" class="my-4" v-for="violation in violations" :key="violation">{{ violation }}</p>
</b-modal>
</div>