How to make expansion on dynamic column datatable - vue.js

I have a problem, I need to make an expansion row so I did something like:
<q-table
:title="title"
:data="driverTable.data"
:columns="driverTable.columns"
:visible-columns="driverTable.visibleCols"
row-key="objectId"
:loading="driverTable.loading"
:filter="driverTable.filter"
>
<template
v-slot:body-cell-expandingrow="cellProperties"
>
<q-td
auto-width
:props="cellProperties"
>
<q-btn
size="sm"
color="accent"
round
dense
#click="cellProperties.expand = !cellProperties.expand"
:icon="cellProperties.expand ? 'remove' : 'add'"
/>
</q-td>
<q-tr
v-show="cellProperties.expand"
:props="cellProperties"
>
<q-td
colspan="100%"
>
<div
class="text-left"
>
This is expand slot for row above: {{ cellProperties.row.name }}.
</div>
</q-td>
</q-tr>
</template>
...
data() {
return {
driverTable: {
filter: '',
loading: false,
columns: [
{
name: 'objectId',
field: row => row.objectId,
format: val => '',
},
{
name: 'name',
label: 'Nom complete',
align: 'left',
field: row => row.name,
format: val => `${val}`,
sortable: true,
classes: 'ellipsis',
},
{
name: 'phone',
required: true,
label: 'Téléphone',
field: row => row.phone,
format: val => `${val}`,
},
{
name: 'position',
required: true,
label: 'Position Actuelle',
field: row => `${row.latitude}, ${row.longitude}`,
format: val => `${val}`,
},
{
name: 'carName',
required: true,
label: 'Voiture',
field: row => row.carName,
format: val => `${val}`,
sortable: true,
},
{
name: 'carSerie',
required: true,
label: 'Série',
field: row => row.carSerie,
format: val => `${val}`,
},
{
name: 'carRef',
required: true,
label: 'Référence',
field: row => row.carRef,
format: val => `${val}`,
},
{
name: 'carImage',
required: true,
label: 'Image',
field: row => row.carImage,
align: 'center',
format: val => `${val}`,
}
],
data: []
Just like quasar documentation but it display the expanding row just like another td on the table.
I know that the problem is when I make the td of btn with the row but as U see I dont use on my html any element jsut q-table to display the data table.
here is the result:

Related

Cannot read properties of undefined on formatter, but data is showing fine

I have a bootstrap table that shows a list of appliances. I am importing my data with Axios and for this specific table I am outputting data from two database tables, so I have one object which is called applianceReferences which stores another object called activeAppliances.
Not sure if it is relevant for this question, but just so you know.
Before talking about the problem, let me just post the whole code and below I will talk about the section that is giving me issues.
<template>
<b-container class="my-2">
<b-card v-if="showTable" class="ml-4 mr-4">
<b-table
search-placeholder="search"
:filter-included-fields="fields.map(f => f.key)"
include-filter
:items="applianceReferences"
:fields="fields"
/>
</b-card>
</b-container>
</template>
<script>
import {applianceService} from "#/services/appliance";
import CommonCollapsible from "#/components/common/CommonCollapsible";
import moment from 'moment';
export default {
components: { CommonCollapsible, CommonTable },
props: {
ownerId: String,
ownerType: String,
showDocuments: Boolean,
goToAppliances: "",
importAppliances: ""
},
data() {
return {
applianceReferences: [],
showTable: true
}
},
computed: {
fields() {
return [
{
key: 'referenceName',
label: this.$t('referenceName'),
sortable: true
},
{
key: 'activeAppliance.type',
label: this.$t('type'),
sortable: true,
},
{
key: 'activeAppliance.brandName',
label: this.$t('brand'),
sortable: true
},
{
key: 'activeAppliance.purchaseDate',
label: this.$t('purchaseDate'),
sortable: true,
template: {type: 'date', format: 'L'}
},
{
key: 'activeAppliance.warrantyDuration',
label: this.$t('warrantyDuration'),
sortable: true,
formatter: (warrantyDuration, applianceId, appliance) =>
this.$n(warrantyDuration) + ' ' +
this.$t(appliance.activeAppliance.warrantyDurationType ?
`model.appliance.warrantyDurationTypes.${appliance.activeAppliance.warrantyDurationType}` :
''
).toLocaleLowerCase(this.$i18n.locale),
sortByFormatted: (warrantyDuration, applianceId, appliance) =>
appliance.activeAppliance.warrantyDurationType === 'YEARS' ? warrantyDuration * 12 : warrantyDuration
},
{
key: 'activeAppliance.purchaseAmount',
label: this.$t('amount'),
sortable: true,
template: {
type: 'number', format: {minimumFractionDigits: '2', maximumFractionDigits: '2'},
foot: sum
}
},
{
key: 'actions',
template: {
type: 'actions',
head: [
{
text: 'overviewOfAppliances',
icon: 'fas fa-fw fa-arrow-right',
action: this.createAppliance
},
{
icon: 'fas fa-fw fa-file-excel',
action: this.importAppliance,
tooltip: this.$t('importAppliances'),
}
],
cell: [
{
icon: 'fa-trash',
variant: 'outline-danger',
action: this.remove
},
]
}
}
]
},
},
methods: {
load() {
Object.assign(this.$data, this.$options.data.apply(this));
this.applianceReferences = null;
applianceService.listApplianceReferences(this.ownerId).then(({data: applianceReferences}) => {
this.applianceReferences = applianceReferences;
this.applianceReferences.forEach( reference => {
applianceService.listAppliances(reference.id).then(result => {
this.$set(reference, 'appliances', result.data);
this.$set(reference, 'activeAppliance', result.data.find(appliance => appliance.active))
this.loaded = true
})
})
}).catch(error => {
console.error(error);
})
},
createAppliance(){
this.goToAppliances()
},
importAppliance(){
this.importAppliances()
},
},
watch: {
ownerId: {
immediate: true,
handler: 'load'
}
},
}
</script>
Okay, so the error occurs in this specific property:
{
key: 'activeAppliance.warrantyDuration',
label: this.$t('warrantyDuration'),
sortable: true,
formatter: (warrantyDuration, applianceId, appliance) =>
this.$n(warrantyDuration) + ' ' +
this.$t(appliance.activeAppliance.warrantyDurationType ?
`model.appliance.warrantyDurationTypes.${appliance.activeAppliance.warrantyDurationType}` :
''
).toLocaleLowerCase(this.$i18n.locale),
sortByFormatted: (warrantyDuration, applianceId, appliance) =>
appliance.activeAppliance.warrantyDurationType === 'YEARS' ? warrantyDuration * 12 : warrantyDuration
},
What I am basically doing here is combining two values from the object: warrantyDuration and warrantyDurationType and putting them in one single row in my bootstrap table.
The problem is that this is giving me an error: Cannot read properties of undefined (reading 'warrantyDurationType'
Yet the data actually outputs normally.
So what exactly does it want me to do?
I tried wrapping a v-if around the table to make sure that the application checks if the data exist before outputting it, but this does not solve the issue.
<div v-if="applianceReferences && applianceReferences.activeAppliance">
<b-card v-if="showTable" class="ml-4 mr-4">
<common-table
search-placeholder="search"
:filter-included-fields="fields.map(f => f.key)"
include-filter
:items="applianceReferences"
:fields="fields"
/>
</b-card>
</div>
Last, just to give you a full overview, my array looks like this:
Any ideas?

Vue Good Table how to access data in Selected Row Actions

I'm trying to use the checkbox in vue-good-table to select rows, then a button in the selected row action slow to perform a function on the selected rows. How can I access the data?
https://xaksis.github.io/vue-good-table/guide/advanced/checkbox-table.html#selected-row-action-slot
This doesn't work:
<vue-good-table
#on-selected-rows-change="selectAll"
:columns="columns2"
id="shift.id"
:ref="shift.id"
:rows="orderedPlacedUsers(shift)"
:select-options="{
enabled: true,
selectOnCheckboxOnly: true,
}"
>
<div slot="selected-row-actions">
<button class="btn btn__small btn__flat" #click="lockAll(shift)">Lock All <i class="ml-2 fas fa-lock-alt"></i></button>
</div>
</div>
</vue-good-table>
Then
data() {
return {
columns2: [
{
label: '',
field: 'extras',
tdClass: 'text-center',
sortable: false,
},
{
label: 'Name',
field: 'fullName',
},
{
label: 'Signed Up',
field: 'created',
sortable: false,
},
{
label: 'Job',
field: 'requestedJob.title',
tdClass: 'text-center',
},
{
label: '',
field: 'notes',
sortable: false,
tdClass: 'text-center',
},
{
label: '',
field: 'reservations',
tdClass: 'text-center',
tdClass: 'text-center',
sortable: false,
},
{
label: '',
field: 'delete',
tdClass: 'text-center',
sortable: false,
},
]
}
}
methods: {
lockAll(shift) {
console.log(shift.id)
console.log(this.$refs[shift.id].selectedRows)
},
orderedPlacedUsers (shift) {
function compare(a, b) {
if (a.firstName < b.firstName)
return -1;
if (a.firstName > b.firstName)
return 1;
return 0;
}
return this.filteredPlacedUsers.sort(compare).filter(user => {
return user.shift == shift.id && user.day == shift.day
});
},
}
The shift is "shift in eventShifts"... here's what that looks like:
{"day":"2021-08-27","endTime":null,"payrollComplete":true,"startTime":"14:00","event":"Los Bukis","id":"dvaBm5wQXMXvVCGBSK8e","exportedCont":{"seconds":1631208172,"nanoseconds":886000000},"collapse":false,"position":{"title":null},"selectedStaff":null,"eventId":"CGHMVzcKPnNLsmRxoeVj","exportedEmp":{"seconds":1631208185,"nanoseconds":622000000},"name":"Line Cook","staff":"50"}
Thank you!
To access the vue-good-table via this.$refs you need to add :ref property into vue-good-table.
<vue-good-table
:id="shift.id"
:ref="shift.id"
>
</vue-good-table>
But there is another thing to be considered, it says here that
When used on elements/components with v-for, the registered reference
will be an Array containing DOM nodes or component instances.
In your case, probably vue-good-table is used on an element/component with v-for. So, you can access it via this.$refs[shift.id][0]. Finally, you can print the selectedRows using console.log(this.$refs[shift.id][0].selectedRows)

Show remaining API data on b-table through pagination

I'm trying to hit all the data from API using axios in Nuxt, but it seems only shows 10 data each page, so if there's actually 15 data which I expect to hit, it has to show 2 page (page 1 with 10 data, page 2 with the remaining 5 data). I had no idea why does it only want to show every 10 data
per page.
How to show the remaining data in next page? Here's what I've been doing so far
<script>
// eslint-disable-next-line no-unused-vars
import { getAllProvinces } from '~/api/delivery'
export default {
data() {
return {
filter: null,
filterOn: [],
perPage: 10,
currentPage: 1,
rows: 0,
items: [],
fields: [
{
key: 'id',
sortable: true,
label: 'ID',
class: 'truncate',
},
{
key: 'uploadReference',
sortable: true,
label: 'Upload Reference',
class: 'truncate',
},
{
key: 'requestId',
sortable: true,
label: 'Request ID',
class: 'truncate',
},
{
key: 'storeCode',
sortable: true,
label: 'Store Code',
class: 'truncate',
},
{
key: 'branchCode',
sortable: true,
label: 'Branch Code',
class: 'truncate',
},
{
key: 'b2bId',
sortable: true,
label: 'B2B ID',
class: 'truncate',
},
{
key: 'request',
sortable: true,
label: 'Request',
class: 'truncate',
},
{
key: 'response',
sortable: true,
label: 'Response',
class: 'truncate',
},
{
key: 'createDate',
sortable: true,
label: 'Create Date',
class: 'truncate',
},
{
key: 'errorClassification',
sortable: true,
label: 'Error Classification',
class: 'truncate',
},
],
}
},
watch: {
currentPage: {
handler(value) {
this.getAllStock()
},
},
},
created() {
this.getAllStock()
},
methods: {
getAllStock() {
this.$axios
.get(
'axioslink' +
this.currentPage +
'&status=1'
)
.then((res) => {
// eslint-disable-next-line no-console
console.log(res.data)
this.items = res.data.stocks
this.allStock = res.data
this.rows = res.data.totalDocuments
// eslint-disable-next-line no-console
})
this.rows = this.items.length
},
onFiltered(filteredItems) {
this.rows = filteredItems.length
this.currentPage = 1
},
},
}
</script>
<div class="text-center">
<b-table
id="my-table"
:per-page="perPage"
:current-page="currentPage"
striped
small
hover
dark
responsive
show-empty
:items="items"
:fields="fields"
:filter="filter"
:filter-included-fields="filterOn"
#filtered="onFiltered"
>
<template v-slot:cell()="data">
<span v-b-tooltip.hover :title="data.value">{{
data.value
}}</span>
</template>
</b-table>
</div>
</template>
<div class="overflow-auto">
<b-card-footer class="py-4 d-flex justify-content-end">
<b-pagination
:total-rows="rows"
:per-page="perPage"
aria-controls="my-table"
#change="currentPage = $event"
></b-pagination>
</b-card-footer>
</div>
Thanks and have a great day
new Vue({
el: "#menu",
data: () => ({
filter: null,
filterOn: [],
perPage: 10,
currentPage: 1,
rows: 0,
items: [],
fields: [{
key: 'id',
sortable: true,
label: 'ID',
class: 'truncate',
},
{
key: 'uploadReference',
sortable: true,
label: 'Upload Reference',
class: 'truncate',
},
{
key: 'requestId',
sortable: true,
label: 'Request ID',
class: 'truncate',
},
{
key: 'storeCode',
sortable: true,
label: 'Store Code',
class: 'truncate',
},
{
key: 'branchCode',
sortable: true,
label: 'Branch Code',
class: 'truncate',
},
{
key: 'b2bId',
sortable: true,
label: 'B2B ID',
class: 'truncate',
},
{
key: 'request',
sortable: true,
label: 'Request',
class: 'truncate',
},
{
key: 'response',
sortable: true,
label: 'Response',
class: 'truncate',
},
{
key: 'createDate',
sortable: true,
label: 'Create Date',
class: 'truncate',
},
{
key: 'errorClassification',
sortable: true,
label: 'Error Classification',
class: 'truncate',
},
],
}),
methods: {
getAllStock() {
this.items = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }, { id: 8 }, { id: 10 }, { id: 11 }, { id: 12 }, ]
// this.allStock = res.data
this.rows = this.items.length
},
onFiltered(filteredItems) {
this.rows = filteredItems.length
this.currentPage = 1
},
},
created() {
this.getAllStock()
},
});
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.css" />
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.js"></script>
<div id="menu">
<b-table id="my-table" :per-page="perPage" :current-page="currentPage" striped small hover dark responsive show-empty :items="items" :fields="fields" :filter="filter" :filter-included-fields="filterOn" #filtered="onFiltered">
</b-table>
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage" aria-controls="my-table"></b-pagination>
</div>

Conditionally show / hide b-table column based on store getters

I want to show / hide table column 'first_name' based on if user isAdmin or not.
This is my table:
<b-table
striped
hover
small
stacked="sm"
selectable
select-mode="single"
show-empty
:items="allProducts"
:fields="productsFieldsForBTable"
:busy="isLoading"
primary-key="id"
>
<template slot="actions" slot-scope="data">
<select v-model="data.selected">
<option v-for="user in users" :key="user.id" :value="user.id">{{user.first_name}}</option>
</select>
</template>
</b-table>
This is productsFieldsForBTable in store getters.
productsFieldsForBTable: () => {
return [
{
key: 'product_name',
sortable: true,
},
{
key: 'buying_price',
sortable: true,
},
{
key: 'notes',
sortable: true,
},
{
key: 'first_name',
label: 'User',
sortable: true,
},
// A virtual column for additional action buttons
{ key: 'actions', label: 'Actions' }
]
}
store getters have isAdmin flag that will be used to hide / show column
I am not sure what syntax to use and where to check conditions? (in b-table tag or in computed?)
Updated -
How do I access isAdmin value from store itself? its in getters as shown below:
getters: {
isAdmin: (state, getters) => { return getters.isLoggedIn && state.user.role === 'Admin' }, ....
if(getter.isAdmin){ // how to access getters' isAdmin here? // this.$store.getters.isAdmin is not working here.
fields.push({
key: 'first_name',
label: 'User',
sortable: true
})
}
You could use mapGetters from Vuex and map productFieldsForBTable as computed property to your component. Then in your getter instead of returning an array in line do something like this:
productsFieldsForBTable: (state, getters) => {
var fields = [
{
key: 'product_name',
sortable: true,
},
{
key: 'buying_price',
sortable: true,
},
{
key: 'notes',
sortable: true,
},
{
key: 'first_name',
label: 'User',
sortable: true,
},
// A virtual column for additional action buttons
{ key: 'actions', label: 'Actions' }
]
if(getters.isAdmin){
fields.push({
key: 'admin_field',
sortable: true,
})
}
return fields
}
If you didn't want the admin fields at the end of the table you could always the splice function instead of push. Something like below would put the admin item before the actions:
var index = 4 // index you want to insert the new column at
fields.splice(index, 0, { key: 'admin_field', sortable: true })

Vue Good Table Component, insert html in filter select options

I am a web-designer, i am working in laravel 5.8 with vue js 2, this is a work for my company.
I have a vue good table component and i want in the flags(languages) column a filter with a drop-down options.
The options(list of languages(example: pt,fr and gb)) are coming from database by a axios request.
Don't want the languages to appear in the filter options with name of the languages but with "span" for a flags icon
(Example: "<span :class="'flag-icon flag-icon-gb
flag-icon-squared'"></span>" ).
This examples works just fine with a row scope and get the flags but in the filter I can get it to translate to html just gives the line as plain text.
I have already tried with "html: true," and "htmlContent: <span></span>"
The scop for the row flag works just fine
vue component scop
<template slot="table-row" slot-scope="row">
<span class="text-center" v-if="row.column.field == 'language.name'">
<span :class="'flag-icon flag-icon-' + row.formattedRow[row.column.field] + ' flag-icon-squared'"></span>
</span>
<span v-else>
{{ row.formattedRow[row.column.field] }}
</span>
</template>
The table data
data() {
return {
columns: [
{
label: 'Name',
field: 'user.name',
filterOptions: {
enabled: true
}
},
{
label: 'Country',
field: 'language.name',
html: true,
filterOptions: {
enabled: true,
filterDropdownItems: []
}
},
{
label: 'Status',
field: 'status.name',
filterOptions: {
enabled: true,
filterDropdownItems: []
}
},
{
label: 'Property',
field: 'property.title',
filterOptions: {
enabled: true,
filterDropdownItems: []
}
},
{
label: 'Last Reply',
field: 'updated_at',
type: 'date',
dateInputFormat: 'YYYY-MM-DD hh:mm:ss',
dateOutputFormat: 'Do MMM YYYY',
filterOptions: {
enabled: true
}
},
{
label: 'Create Date',
field: 'created_at',
type: 'date',
dateInputFormat: 'YYYY-MM-DD hh:mm:ss',
dateOutputFormat: 'Do MMM YYYY',
filterOptions: {
enabled: true
}
},
],
rows: [],
totalRecords: 0,
serverParams: {
columnFilters: {},
sort: {
field: '',
type: '',
},
page: 1,
perPage: 10
}
}
},
The axios request where i get the language list and try to put the span in the filter options
vue
getLanguages() {
let url = '/manage/data/request-language';
axios.get(url)
.then(({status, data}) => {
if (status === 200) {
data.records.forEach(item => {
let newData = {value: item.id, html: true, text: "<span :class='flag-icon flag-icon-" + item.name + " flag-icon-squared'></span>"};
this.columns[1].filterOptions.filterDropdownItems.push(newData);
});
}
}).catch(error => Alert.handleRequestError(error));
},
I expect to appear a flag list and not a span list of the flags.
Is it possible to override that specific filter or to force it html instead of plain text?