Child-modal is empty on every second click - vue.js

I am using row-selected on Bootstrap table, so that when a user select a row in a table, the data of that table is passed to a new object which is then sent as a prop to a child component, which is a modal.
The issue is that when I first click on a row, the modal opens and shows the data correctly, but if I close the modal and click the same row again, the data object is empty and nothing gets shown. I need to click it again for a third time to see the data. Then if I click a fourth time, the data is gone again.
I don't understand it, because in my showModal method, I check first to see if selectedRows are empty or not, so it shouldn't open if there was no data.
Any idea why this happens?
Parent:
<template>
<b-container>
<b-card class="mt-4 mb-4">
<h5>{{ $t('errorLogs.errorLog') }}</h5>
<b-table
:items="completedTasks"
:fields="fields"
:per-page="[10, 25, 50]"
selectable
:select-mode="'single'"
#row-selected="onRowSelected"
#row-clicked="showModal"
include-actions
sort-desc
/>
</b-card>
<error-log-entry-modal ref="errorLogEntryModal" :selected-error-log="selectedRows"/>
</b-container>
</template>
<script>
import ErrorLogEntryModal from '#/components/error-log/ErrorLogEntryModal';
export default {
components: {
ErrorLogEntryModal,
},
data() {
return {
errors: null,
tasksCompleted: null,
selectedRows: []
};
},
computed: {
fields() {
return [
{
key: 'done',
label: '',
thStyle: 'width: 1%',
template: {
type: 'checkbox',
includeCheckAllCheckbox: true,
},
},
{
key: 'priority',
label: this.$t('errorLogs.priority'),
formatter: type => this.$t(`model.errors.types.${type}`),
sortable: true,
},
{
key: 'creationDateTime',
label: this.$t('creationDateTime'),
formatter: date => moment(date).locale(this.$i18n.locale).format('L'),
sortable: true,
},
{
key: 'stackTraceShort',
label: this.$t('errorLogs.stackTrace'),
sortable: true,
},
{
key: 'message',
label: this.$t('message'),
sortable: true
},
]
},
completedTasks(){
if(this.errors){
return this.errors.filter(log => log.done)
}
},
},
methods: {
load(){
errorService.getErrorLogs().then(result => {
result.data.forEach(log => log.stackTraceShort = log.stackTrace.substring(0,30));
this.errors = result.data
})
},
submit(){
this.$bvModalExt
.msgBoxYesNo(
this.$t('archiveErrorLog'),
{
title: this.$t('pleaseConfirm'),
okVariant: 'success'
}
).then(value => {
if (value) {
errorService.updateStatusOnErrorEntryLog(this.errors)
}
})
},
onRowSelected(fields){
this.selectedRows = fields
},
showModal(){
if (this.selectedRows) {
this.$refs.errorLogEntryModal.show()
}
},
},
created() {
this.load()
},
};
</script>
child:
<template>
<b-modal
modal-class="error-log-modal"
v-model="showModal"
ok-only
size="xl">
<b-row class="lg-12" v-for="log in selectedErrorLog">
<b-col class="ml-2 mr-2 mb-4">
<h4>{{ $t('errorLogs.errorMessage') }}</h4>
{{ log.message }}
</b-col>
</b-row>
<b-row class="lg-12">
<b-col class="ml-2 mr-2"><h4>{{ $t('errorLogs.stackTrace') }}</h4></b-col>
</b-row>
<b-row class="lg-12" v-for="log in selectedErrorLog">
<b-col class="ml-2 mr-2" style="word-break: break-word; background-color: #F5C9C1;">
{{ log.stackTrace }}
</b-col>
</b-row>
</b-modal>
</template>
<script>
export default {
props: {
selectedErrorLog: Array
},
data() {
return {
showModal: false,
};
},
methods: {
show(){
this.showModal = true
},
}
};
</script>

Related

How to loop a form to three time in Vuejs

I am facing an issue where I need to display one select field two times in the form and while saving the form it will save the data in an array.
What I have done is created a form and added a select form and I want it to display two times (two select form) and it will be able to select different values for two select displays
I have created a sandbox here
Any ideas are much appreciated.
You could create new variable to second value with same options as first select input and save it as array.
<template>
<div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="optQuality"
v-model="slcQuality"
#input="changeQuality"
>
</b-form-select>
<div>slcQuality: {{ slcQuality }}</div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="optQuality"
v-model="slcQuality2"
#input="changeQuality"
>
</b-form-select>
<div>slcQuality: {{ slcQuality2 }}</div>
<div>
<button #click="submit">Submit</button>
</div>
<div>submitted Data: {{ JSON.stringify(submittedData) }}</div>
</div>
</template>
<script>
export default {
data() {
return {
optQuality: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-1" },
{ value: 3, text: "Kw-2" },
],
slcQuality: null,
slcQuality2: null, // new variable
submittedData: [],
};
},
methods: {
changeQuality() {
console.log("test");
console.log(this.slcQuality);
},
submit() {
const data = [this.slcQuality, this.slcQuality2]; //save data as array
this.submittedData = data;
console.log(data);
},
},
};
</script>
EDIT
To avoid massive code you could use an array of objects as variable or nested array like this, then loop twice in template (nested v-for).
<template>
<div>
<div v-for="(quality, i) in slcQualities" :key="i">
<div v-for="(selection, j) in quality.values" :key="j">
<div>{{ selection.name }}</div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="quality.options"
v-model="selection.value"
#input="changeQuality"
/>
<div>slcQuality: {{ quality.value }}</div>
</div>
</div>
<div>
<button #click="submit">Submit</button>
</div>
<div>submitted Data: {{ JSON.stringify(submittedData) }}</div>
</div>
</template>
<script>
// array of data
const qualities = [
{
options: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-1" },
{ value: 3, text: "Kw-2" },
],
values: [
{ name: "Select 1-1", value: null },
{ name: "Select 1-2", value: null },
],
},
{
options: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-3" },
{ value: 3, text: "Kw-4" },
],
values: [
{ name: "Select 2-1", value: null },
{ name: "Select 2-2", value: null },
],
},
];
export default {
data() {
return {
slcQualities: qualities,
submittedData: [],
};
},
methods: {
changeQuality() {
console.log("test");
console.log(this.slcQuality);
},
submit() {
const data = this.slcQualities.map((i) => i.values.map((j) => j.value)); //map the values
this.submittedData = data;
console.log(data);
},
},
};
</script>
Here's the sandbox

Change background of v-data-table row on event from child component

I have an expanding data table in my parent component and a child component inside the expanded row with a button. I would like to change the background color of the associated row when I click the button inside the child component. I'm not sure how to target the row to add the css class on event.
ScanGrid(parent):
<template>
<v-flex v-if="items.length === 0">
<ScanAdd #selectBatch="showScan" />
</v-flex>
<v-card v-else class="ma-5">
<v-card-text>
<v-layout align-center>
<v-data-table
:headers="headers"
:items="items"
item-key="StorageName"
show-expand
single-expand
:expanded="expanded"
hide-default-footer
#click:row="clickedRow"
>
<template
#isDeleted="deleteRow"
v-if="groupBy === 'barCode'"
v-slot:expanded-item="{ item }"
>
<td :colspan="12">
<ScanGridCode :item="item" />
</td>
</template>
<template v-else v-slot:expanded-item="{ item }">
<td :colspan="12">
<ScanGridDef :item="item" />
</td>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</template>
<script>
import { API } from "#/api";
import ScanAdd from "./ScanAdd";
import ScanGridCode from "./ScanGridCode";
import ScanGridDef from "./ScanGridDef";
export default {
name: "ScanGrid",
props: {
items: {
type: Array,
required: true
}
},
components: {
ScanGridCode,
ScanGridDef,
ScanAdd
},
methods: {
deleteRow(value) {
this.isDeleted = value;
},
showScan(value) {
this.selectedId = value;
this.addScanBatch(value);
this.$emit("processingBatch", true);
this.processingBatch = true;
},
async addScanBatch(Id) {
const selectedItems = await API.getPhysicalInventoryBatch(Id);
if (selectedItems.data.Id === this.selectedId) {
this.items = selectedItems.data.Locations;
}
},
clickedRow(value) {
if (
this.expanded.length &&
this.expanded[0].StorageName == value.StorageName
) {
this.expanded = [];
} else {
this.expanded = [];
this.expanded.push(value);
}
}
},
data: () => ({
isDeleted: false,
groupBy: "barCode",
expanded: [],
items: [],
toDelete: "",
totalResults: 0,
loading: true,
headers: [
{
text: "Localisation",
sortable: true,
value: "StorageName",
class: "large-column font-weight"
},
{
text: "Paquets scannés",
sortable: true,
value: "ScannedProduct",
class: "large-column font-weight"
},
{
text: "Paquets entrants",
sortable: true,
value: "Incoming",
class: "large-column font-weight"
},
{
text: "Paquets sortants",
sortable: true,
value: "Outgoing",
class: "large-column font-weight"
},
{
text: "Paquets inconnus",
sortable: true,
value: "Unknown",
class: "large-column font-weight"
}
]
})
};
</script>
ScanGridCode(child):
<template>
<div class="codeContainer">
<div class="cancelLocation">
<v-flex class="justify-center">
<v-btn class="ma-5" large color="lowerCase" tile #click="deleteLocation"
>Annuler le dépôt de cette localisation</v-btn
>
</v-flex>
</div>
</div>
</template>
<script>
export default {
name: "ScanGridCode",
props: {
item: {
type: Object,
required: true
}
},
methods: {
deleteLocation() {
this.item.IsDeleted = true;
this.$emit("IsDeleted", true);
}
},
data: () => ({
IsDeleted: false,
groupBy: 0,
headersGroupCode: [
{
text: "Code barre",
sortable: true,
value: "SerialNumber",
class: "large-column font-weight-light"
},
{
text: "De",
sortable: true,
value: "FromLocation",
class: "large-column font-weight-light"
},
{
text: "Vers",
sortable: true,
value: "ToLocation",
class: "large-column font-weight-light"
}
]
})
};
</script>
I use Vuetify 2.1.7 and Vue 2.6.10. When I click on the button I call deleteLocation function. I assume I need to $emit a value to my parent but after that I don't know how to target the tr to change its style.
Since you're using Vuex, I would suggest using some variable such as store.state.selectedRow to keep track of whether or not a row has been selected (or in cases where there are more than one row, which row has been selected). Then you can have a computed property myProperty = this.$store.state.selectedRow in your Vue component which will automatically reflect the single source of truth, and your conditional class can be bound to this myProperty. This means you don't need to worry about emitting on events.
The approach to emitting the event is what should be done. So I am assuming you will emit from deleteLocation function.
Since you need a custom styling on rows you need to add the items slot and add your logic there
<template v-slot:item="{ item, select}">
<tr :class="key === coloredRow ? 'custom-highlight-row' : ''">
<td :colspan="12">
<ScanGridCode #changeColor="changeColor(key)" :item="item" />
</td>
//add this method to your script element
changeColor(idx) {
this.coloredRow = idx;
}

VueJs + BootstrapVue: Edit object with data pagination

I'm new to VueJS and I probably missed something in the instance lifecycle.
Actually, I created a page where I list all my users in a BootstrapVue b-table. for each users, I added the possibility to edit / remove the user through a modal.
When I activate pagination with b-pagination element and :per-page attribute, I have an unexpected behavior on my modal to edit the user.
In order to modify my user only after submit action, I create a clone of the original user in the edit modal.
The problem is it works only for the 1st page, but all others pages get the data of the 1st page.
<b-table
id="my-table"
:fields="fields"
:items="users"
:per-page="perPage"
:current-page="currentPage">
<template slot="actions" slot-scope="row">
<edit-user v-bind:user="row.item"</edit-user>
<remove-user v-bind:user="row.item"></remove-user>
</template>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
aria-controls="my-table">
</b-pagination>
import EditUser from "./EditUser.vue";
import RemoveUser from "./RemoveUser.vue";
export default {
name: 'User',
data() {
return {
perPage: 10,
currentPage: 1,
pageOptions: [5, 10, 15],
filter: null,
fields: [
{ key: 'name', sortable: true, sortDirection: 'desc' },
'actions'
],
}
},
computed: {
...mapGetters([
'users',
'error'
]),
rows() {
return this.users.length
}
},
mounted () {
this.$store.dispatch('getUsers')
},
components: {
editUser: EditUser,
removeUser: RemoveUser
}
}
The child editUser component
<b-button variant="info" #click="modalShow = !modalShow">Edit</b-button>
<b-modal v-model="modalShow" hide-footer title="Edit User">
<b-form action="" v-if="modalShow">
<b-form-group id="input-group-name" label="Name:" label-for="input-name">
<b-form-input id="input-name" v-model="editedUser.name" required></b-form-input>
</b-form-group>
<b-button variant="secondary" #click="modalShow = !modalShow">Cancel</b-button>
<b-button variant="primary" #click="onSubmit">Submit</b-button>
</b-form>
</b-modal>
export default {
props: {
user: Object
},
data() {
return {
modalShow: false,
editedUser: JSON.parse(JSON.stringify(this.user)),
}
},
methods: {
onSubmit(evt) {
evt.preventDefault()
this.$store.dispatch('editUser', {
oldItem: this.user,
newItem: this.editedUser
})
this.modalShow = false
}
}
}
I managed to fix my issue by cloning the user object after opening the modal.
<b-button variant="info" #click="editUser(user)">Edit</b-button>
[...]
export default {
props: {
user: Object
},
data() {
return {
modalShow: false,
editedUser: {},
}
},
methods: {
editUser(user) {
this.modalShow = true
this.editedUser = JSON.parse(JSON.stringify(user));
}
onSubmit(evt) {
[...]
}
}
}
However, I still don't understand why the previous piece of code doesn't have the same behavior.

Job posting wont delete, keep getting id is "undefined"

I'm having an issue with my small application. I currently have a job page, I'm able to post and get the jobs.
On the frontend I have a button to press Delete but when I do it keeps giving me this error - DELETE FROM 'jobs' WHERE 'id' = 'undefined'.
Currently, technologies being used ar MySQL, sequelize, node.js, express, and vue.js.
Console
<div>
<h2 class="mb-4 font-weight-light">Job postings</h2>
<div class="d-flex align-items-center justify-content-between">
<b-input-group class="w-30">
<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-button variant="primary" class="d-flex align-items-center" v-b-modal.addJob><i class="material-icons mr-1"></i> Add job</b-button>
</div>
<b-table responsive hover :items="jobs" :fields="fields" :filter="filter" no-sort-reset sort-by="postedOn" :sort-desc="true" class="mt-3 f6">
<template slot="job_postingURL" slot-scope="data">
<a :href="`${data.value}`" target="_blank">{{ data.value }}</a>
</template>
<template slot="Remove" scope="jobs">
<b-btn variant="danger" #click="deleteJob(jobs.ID)"> Delete </b-btn>
</template>
</b-table>
<add-job></add-job>
</div>
</template>
<script>
import AddJob from '#/components/jobs/AddJob'
import JobService from '../../services/JobService'
import axios from 'axios'
export default {
components: {
AddJob
},
data () {
return {
fields: [
{ Key: 'ID', label: 'Job ID', sortable: false},
{ key: 'job_title', label: 'Job title', sortable: true },
{ key: 'job_name', label: 'Company name', sortable: true },
{ key: 'job_location', label: 'Location', sortable: true },
{ key: 'job_postingURL', label: 'Job posting link', sortable: false },
{ key: 'job_postingOn', label: 'Posted on', sortable: true, tdClass: 'text-right' },
{ key: 'job_postingBy', label: 'Posted by', sortable: true },
{ key: 'Remove', sortable: true }
],
filter: null,
jobs: [
{
ID: '',
job_title: '',
job_name: '',
job_location: '',
job_postingURL: '',
job_postingOn: '',
job_postingBy: ''
},
],
}
},
// this method is to get the data from database
async created () {
try {
this.jobs = await JobService.getJobs();
} catch(err) {
this.error = err.message;
}
},
methods: {
deleteJob (ID) {
axios.delete(`http://localhost:5000/api/jobs/${this.ID}`)
.then((res) => {
this.ID = ''
this.job_title = ''
this.job_name = ''
this.job_location = ''
this.job_postingURL =''
this.job_postingOn = ''
this.job_postingBy = ''
console.log(res)
})
.catch((err) => {
console.log(err)
})
}
}
}
</script>
Since you have jobs like a data object property you couldn't use jobs as slot-scope value, try something like row, in this case row object contains some properties like item which contain data about the current item shown in this row, so you should do :
<template slot="Remove" slot-scope="row">
<b-btn variant="danger" #click="deleteJob(row.item.ID)"> Delete </b-btn>
</template>
and in your method :
deleteJob (ID) {
axios.delete('http://localhost:5000/api/jobs/'+ID)
.then((res) => {...

bootstrap-vue datatable show row details issue

trying to achieve the same result as https://bootstrap-vue.js.org/docs/components/table#row-details-support
My code:
<b-table ref="propertydata" striped hover
:items="datatable.items"
:fields="datatable.fields"
:current-page="datatable.currentPage"
:per-page="datatable.perPage"
:filter="datatable.filter"
:sort-by.sync="datatable.sortBy"
:sort-desc.sync="datatable.sortDesc"
:sort-direction="datatable.sortDirection"
#filtered="onFiltered">
<template slot="action" slot-scope="row">
<!-- we use #click.stop here to prevent emitting of a 'row-clicked' event -->
<b-button size="sm" #click.stop="row.toggleDetails" class="mr-2">
{{ row.detailsShowing ? 'Hide' : 'Show'}} Details
</b-button>
</template>
<template slot="row-details" slot-scope="row">
<b-card>
<ul>
<li v-for="(value, key) in row.item" :key="key">{{ key }}: {{ value}}</li>
</ul>
</b-card>
</template>
</b-table>
<b-row>
<b-col md="6" class="my-1">
<b-pagination :total-rows="datatable.totalRows" :per-page="datatable.perPage" v-model="datatable.currentPage" class="my-0" />
</b-col>
</b-row>
/* Datatable related */
datatable: {
fields: [
{
key: 'account_id',
sortable: true
},
{
key: 'account_name',
sortable: true
},
{
key: 'property_count',
sortable: true,
},
{
key: 'commission',
sortable: true,
variant: 'success'
},
{
key: 'action',
}
],
items: [],
currentPage: 1,
perPage: 20,
totalRows: 0,
pageOptions: [ 20, 100, 300, 500, 1000, 2000, 3000 ],
sortBy: null,
sortDesc: false,
sortDirection: 'asc',
filter: null
}
computed: {
sortOptions () {
// Create an options list from our fields
return this.datatable.fields
.filter(f => f.sortable)
.map(f => { return { text: f.label, value: f.key } })
}
}, // END COMPUTED
methods: {
onFiltered (filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.datatable.totalRows = filteredItems.length
this.datatable.currentPage = 1
}
}, // END METHODS
The result is:
As you can see, the show details is isolated to a single column instead of the full row.
What I want to achieve is for this show details section to be the full row width and if I could click the button to go to a method to call additional data to be displayed here as oppose to the native bootstrap-vue functionality.