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

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) => {...

Related

Child-modal is empty on every second click

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>

I am getting a warning regarding an infinite update loop in a component render. What part of my code is causing this?

In my application I have a list of documents that I display in a table, each document has a specific type, which is specified in json file as enum values.
I can already display all the documents without any problems, but now I have tried to create an input where the user can choose from the list of enum values, and when doing so only the documents with the selected enum type will be shown in the table.
This does sorta work, but the problem is that I have somehow created an infinite update loop, which causes the application to randomly stop working.
This is the template. I am using a custom made template component, but that is not related to the issue.
<template>
<b-container>
<div #dragover.prevent.stop #drop.prevent.stop="onDropEvent">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button-toolbar justify>
<b-button
:class="showCollapse ? 'collapsed' : null"
#click="showCollapse = !showCollapse"
variant="outline-info"
class="flex-grow-1"
>
<span class="float-left">{{ folderName }}</span>
<span class="float-right">
{{
search ?
$t('documentCountFiltered', {filtered: filteredDocuments.length, count: documents.length}) :
$tc('documentCount', documents.length)
}}
</span>
</b-button>
<template v-if="!inherited && mode !== 'READ_ONLY'">
<label class="document-uploader btn btn-sm btn-outline-primary ml-2">
<span>
<i class="fas fa-fw fa-file-upload"></i>
{{ $t('uploadFiles') }}
</span>
<input type="file" multiple #change="selectFiles">
</label>
<b-dropdown
v-if="mode !== 'READ_ONLY' && mode !== 'RESTRICTED'"
size="sm" variant="outline-primary" class="ml-2 template-dropdown" no-caret right>
<template slot="button-content">
<i class="fas fa-fw fa-file-medical"></i> {{ $t('createDocument') }}
</template>
<b-dropdown-header v-if="companyTemplates.length !== 0" id="dropdown-header-templates">
{{ $t('companyTemplates') }}
</b-dropdown-header>
<b-dropdown-item v-if="companyTemplates.length !== 0" v-for="template of companyTemplates"
#click="emitWordDocumentCreateSelectEvent(template)">
{{ template.name }} <i v-if="template.freeTextEnabled" class="fa-fw fas fa-paragraph"></i>
</b-dropdown-item>
<b-dropdown-divider
v-if="companyTemplates.length !== 0 && companyFormBuilderTemplates.length > 0"></b-dropdown-divider>
<b-dropdown-header v-if="companyFormBuilderTemplates.length > 0" id="dropdown-header-inheritedDocuments">
{{ $t('formBuilderTemplates') }}
</b-dropdown-header>
<b-dropdown-item
v-for="template of companyFormBuilderTemplates"
#click="emitDocumentCreateSelectEvent(template)">
{{ template.name }}
</b-dropdown-item>
<b-dropdown-divider
v-if="globalTemplates.length !== 0"></b-dropdown-divider>
<b-dropdown-header v-if="globalTemplates.length > 0" id="dropdown-header-globalDocuments">
{{ $t('globalTemplates') }}
</b-dropdown-header>
<b-dropdown-item
v-for="template of globalTemplates"
#click="emitGlobalDocumentCreateSelectEvent(template)">
{{ template.name }}
</b-dropdown-item>
</b-dropdown>
</template>
</b-button-toolbar>
</b-card-header>
<b-form-group class="mt-4">
<w-b-form-select v-model="filteredDocumentType">
<template>
<b-form-select-option :value="null" disabled>-- {{ $t('selectDocumentByType') }} --</b-form-select-option>
</template>
<b-form-select-option :value="'all'">({{ $t('all') }})</b-form-select-option>
<option v-for="documentType in availableDocumentTypes" :key="documentType" :value="documentType">
{{ $t('model.document.types.' + documentType) }}
</option>
</w-b-form-select>
</b-form-group>
<b-collapse v-model="showCollapse">
<common-table
:fields="fields"
:items="filteredDocuments"
primary-key="id"
sort-by="creationDateTime"
sort-desc
>
<template slot="head(select)">
<check-all-checkbox :list="filteredDocuments" #change="handleTag(documents, $event)" property="selected"/>
</template>
<template slot="cell(select)" slot-scope="data">
<w-b-form-checkbox v-model="data.item.selected" v-if="data.item.selected != null"
#change="handleTag([data.item], $event)" data-test-id="check-box"/>
</template>
<template slot="cell(name)" slot-scope="data">
<div class="d-flex">
<div style="flex: 2 0 0">
<b-dropdown :text="data.item.name" variant="link" toggle-class="name-cell">
<b-dropdown-item #click="viewFile(data.item)" v-if="data.item.type != 'EMAIL'"><i
class="fas fa-eye"></i> {{ $t('showDocument') }}
</b-dropdown-item>
<b-dropdown-item #click="downloadFile(data.item)"><i class="fas fa-file-download"></i> {{
$t('download')
}}
</b-dropdown-item>
</b-dropdown>
</div>
<div style="flex: 1 0 0" v-if="data.item.uploading">
<b-progress :animated="!data.item.error" striped class="h-100">
<b-progress-bar
:value="100"
:variant="data.item.error ? 'danger' : 'primary'"
:label="data.item.error ? 'Error' : 'Uploading...'"
/>
</b-progress>
</div>
</div>
</template>
</common-table>
</b-collapse>
<p-d-f-j-s-viewer ref="pdf-viewer"/>
</div>
</b-container>
</template>
My script
<script>
import CheckAllCheckbox from '#/components/CheckAllCheckbox';
import CommonTable from '#/components/common/CommonTable';
import CommonInput from '#/components/common/CommonInput';
import {applianceService} from '#/services/appliance';
import PDFJSViewer from '#/components/PDFJSViewer';
import axios from '#/config/axios';
import {documentService} from '#/services/document';
import documentTypes from '#/models/document/type';
import {propertyFacilityService} from '#/services/property-facility';
import CommonCollapsible from '#/components/common/CommonCollapsible';
export default {
props: {
documents: Array,
documentOwnerType: String,
companyTemplates: Array,
companyFormBuilderTemplates: Array,
globalTemplates: Array,
inherited: Boolean,
startCollapsed: {
type: Boolean,
default: false
},
mode: String,
search: String,
applianceId: String,
propertyFacilityId: String,
noteId:String
},
components: {
CheckAllCheckbox,
CommonTable,
CommonCollapsible,
CommonInput,
PDFJSViewer
},
data() {
return {
documentTypes,
showCollapse: true,
filteredDocumentType: null
};
},
computed: {
filteredDocuments() {
console.log(this.filteredDocumentType)
console.log(this.documents)
if (this.filteredDocumentType === null || this.filteredDocumentType === 'all') {
return (this.documents ?? []).filter(document =>
(document.name.toUpperCase().includes(this.search.toUpperCase()) ||
this.$t(`model.document.types.${document.type}`).toUpperCase().includes(this.search.toUpperCase())))
}
else {
return (this.documents ?? []).filter(document =>
(document.name.toUpperCase().includes(this.search.toUpperCase()) ||
this.$t(`model.document.types.${document.type}`).toUpperCase().includes(this.search.toUpperCase())) &&
document.type === this.filteredDocumentType)
}
},
folderName() {
if (this.inherited) {
return this.$t('sharedDocuments');
} else if (this.documentOwnerType === 'COMPANY') {
return this.$t('companyDocuments');
} else {
return this.$t('documents');
}
},
availableDocumentTypes() {
return this.documentTypes.sort((a, b) => this.getDocumentTypeText(a).localeCompare(this.getDocumentTypeText(b)));
},
fields() {
return [
{
key: 'select',
thStyle: 'width: 1%'
},
{
key: 'name',
label: this.$t('name'),
sortable: true
},
{
key: 'inherited',
label: this.$t('inherited'),
formatter: inherited => this.$t(inherited ? 'yes' : 'no'),
sortable: true,
hide: this.inherited
},
{
key: 'sharedOnTC',
label: this.$t('sharedOnTC'),
formatter: sharedOnTC => this.$t(sharedOnTC ? 'yes' : 'no'),
sortable: true,
hide: this.sharedOnTC
},
{
key: 'type',
label: this.$t('type'),
formatter: type => this.$t('model.document.types.' + type),
sortable: true,
sortByFormatted: true
},
{
key: 'tags',
label: this.$t('tags'),
formatter: tags => tags.join(', '),
sortable: true,
sortByFormatted: true
},
{
key: 'signed',
label: this.$t('signed'),
formatter: sharedOnTC => this.$t(sharedOnTC ? 'yes' : 'no'),
sortable: true,
sortByFormatted: true
},
{
key: 'creationDateTime',
label: this.$t('created'),
sortable: true,
template: {type: 'date', format: 'L LT'}
},
{
key: 'changedDateTime',
label: this.$t('changed'),
sortable: true,
template: {type: 'date', format: 'L LT'}
},
{
key: 'actions',
hide: this.inherited || this.mode === 'READ_ONLY',
template: {
type: 'actions',
cell: [
{
icon: 'fa-edit',
tooltip: this.$t('edit'),
if: this.mode !== 'RESTRICTED',
disabled: data => data.item.uploading || !data.item.documentTemplateId || data.item.signed,
action: data => this.emitDocumentEditTemplateSelectEvent(data.item)
},
{
icon: 'fa-cog',
tooltip: this.$t('documentSettings'),
disabled: data => data.item.uploading,
action: data => this.emitDocumentEditSelectEvent(data.item)
},
{
icon: 'fa-trash',
variant: 'outline-danger',
disabled: data => data.item.uploading,
action: data => this.emitDocumentDeleteSelectEvent(data.item)
}
]
}
}
].filter(field => !field.hide);
}
},
methods: {
getDocumentTypeText(type) {
return this.$t(`model.document.types.${type}`);
},
selectFiles(event) {
this.emitFileUploadEvent(event.target.files);
event.target.value = '';
},
onDropEvent(event) {
this.emitFileUploadEvent(event.dataTransfer.files);
},
downloadFile(document) {
documentService.downloadDocument(document.id)
.catch(error => {
console.error(error);
});
},
viewFile(document) {
documentService.getPublicDownloadToken(document.id).then(result => {
let fileName = `${axios.defaults.baseURL}/file/public/${result.data}/download`;
this.$refs['pdf-viewer'].show(fileName);
}).catch(error => {
console.error(error);
});
},
emitGlobalDocumentCreateSelectEvent(template) {
this.$emit('document-global-create-select', template);
},
emitDocumentCreateSelectEvent(template) {
this.$emit('document-create-select', template);
},
emitWordDocumentCreateSelectEvent(template) {
this.$emit('document-word-create-select', template);
},
emitDocumentEditSelectEvent(document) {
documentService.getDocument(document.id).then(result => {
document = result.data;
this.$emit('document-edit-select', document);
});
},
emitDocumentEditTemplateSelectEvent(document) {
this.$emit('document-edit-template-select', document);
},
emitDocumentDeleteSelectEvent(document) {
this.$emit('document-delete-select', document);
},
emitFileUploadEvent(files) {
if (files && files.length) {
this.$emit('file-upload', [...files]);
}
},
handleTag(documents, selected) {
if (this.applianceId) {
applianceService.updateDocuments(this.applianceId, {
documentIds: documents.map(doc => doc.id), selected: selected
}).then(({data: documents}) => {
documents.forEach(doc => {
this.documents.splice(this.documents.findIndex(d => d.id === doc.id), 1,
Object.assign(doc, {selected: selected}));
});
});
}
if (this.propertyFacilityId) {
propertyFacilityService.updateDocuments(this.propertyFacilityId, {
documentIds: documents.map(doc => doc.id), selected: selected
}).then(({data: documents}) => {
documents.forEach(doc => {
this.documents.splice(this.documents.findIndex(d => d.id === doc.id), 1,
Object.assign(doc, {selected: selected}));
});
});
}
}
},
created() {
this.showCollapse = !this.startCollapsed;
}
};
</script>

Unit test a V-If label with Jest is alway true

I'm trying to test the following label using Jest:
<label class="no-content-label" v-if="products.length===0 && !isTableBusy">No Content To Show</label>
with the following test:
it('Test No Content To Show When table is not empty', async () => {
const wrapper = mount(productTable, {
propsData: {
products: items1,
fields: fields1
}
})
expect(wrapper.find(".no-content-label").exists()).toBeFalsy()
wrapper.destroy();
})
where items are just: [{ a: 1, b: 2, c: 3 }, { a: 4, b: 5, c: 6 }] nut I always getting True even that the components is not showing and should get false.
The full components code:
<template>
<div id="ProductTable" class="container mt-3 mb-5">
<h1 class="mainTitle">Product Table Home</h1>
<b-alert variant="danger" :show="showError" v-for="(error,index) in errorArray" :key="index" dismissible>
{{ error }}
</b-alert>
<b-row class="mb-3">
<b-col md="3">
<b-form-input v-model="filterInput" type="search" id="filterInput" placeholder="Type to Search"></b-form-input>
</b-col>
</b-row>
<b-table id="product-table" striped hover head-variant="light"
:busy="isTableBusy"
:items="products"
:filter="filterInput"
:fields="fields"
:bordered="bordered"
:borderless="bordered"
:total-rows="rows">
<template #table-busy>
<div class="text-center text-danger my-2">
<b-spinner class="align-middle"></b-spinner>
<strong>Loading...</strong>
</div>
</template>
<template #cell(cogs.unitManufacturingCost)="row">
{{
row.item.cogs.unitManufacturingCost ? Number(row.item.cogs.unitManufacturingCost).toLocaleString() + '$' : '0$'
}}
</template>
<template #cell(cogs.shipmentUnitCost)="row">
{{ row.item.cogs.shipmentUnitCost ? Number(row.item.cogs.shipmentUnitCost).toLocaleString() + '$' : '0$' }}
</template>
<template #cell(cogs.monthlyAdvertismentCost)="row">
{{
row.item.cogs.monthlyAdvertismentCost ? Number(row.item.cogs.monthlyAdvertismentCost).toLocaleString() + '$' : '0$'
}}
</template>
<template #cell(cogs.manufacturingCountry)="row">
{{ row.item.cogs.manufacturingCountry ? row.item.cogs.manufacturingCountry : '-' }}
</template>
<template #cell(actions)="row">
<b-button size="md" variant="primary" #click="openEditProductModal(row.item)">
Edit
</b-button>
<edit-product-modal ref="EditProductModal" :item="row.item" #fetchProducts="fetchProducts"></edit-product-modal>
</template>
</b-table>
<label class="no-content-label" v-if="products.length===0 && !isTableBusy">No Content To Show</label>
</div>
</template>
<script>
import EditProductModal from "#/components/EditProductModal";
import {BAlert} from "bootstrap-vue";
import {BRow} from "bootstrap-vue";
import {BCol} from "bootstrap-vue";
import {BFormInput} from "bootstrap-vue";
import {BTable} from "bootstrap-vue";
export default {
name: "BootstrapProductTable",
components: {
EditProductModal: EditProductModal,
'b-alert': BAlert,
'b-row': BRow,
'b-col': BCol,
'b-form-input': BFormInput,
'b-table': BTable,
},
data() {
return {
filterInput: "",
bordered: true,
isTableBusy: false,
products: [],
fields: [
{key: 'productName', label: 'Product Name', sortable: true, sortDirection: 'desc'},
{key: 'cogs.unitManufacturingCost', label: 'Unit manufacturing cost', sortable: true,},
{key: 'cogs.shipmentUnitCost', label: 'Shipment unit cost', sortable: true},
{key: 'cogs.monthlyAdvertismentCost', label: 'Monthly advertising cost', sortable: true,},
{key: 'cogs.manufacturingCountry', label: 'Manufacturing country', sortable: true,},
{key: 'actions', label: 'Actions'}
],
errorArray: [],
showError: false
}
},
computed: {
rows() {
return this.products.length;
}
},
created() {
this.fetchProducts();
},
methods: {
openEditProductModal(row) {
this.$refs.EditProductModal.show(row)
},
async fetchProducts() {
try {
this.isTableBusy = true;
//delay example
// const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
// await delay(1000)
let response;
response = await this.axios.get(
"http://localhost:3000/products"
);
this.products = response.data;
} catch (error) {
this.showError = true;
this.errorArray = error.response ? error.response.data.errors : [error]
}
this.isTableBusy = false;
}
}
}
</script>
You are trying to pass products, but your component is not expecting any props.
I see that products are fetched in fetchProducts method. All api calls must be mocked. fields are already set in the component. So all you have to do is to mock api call. That's why I suggest you to mock fetchProducts method so it will be always returning the same array of products:
const mockProducts = [{ a: 1, b: 2, c: 3 }, { a: 4, b: 5, c: 6 }];
it('Test No Content To Show When table is not empty', async () => {
const wrapper = mount(productTable, {
mocks: {
fetchProducts: () => mockProducts
}
})
expect(wrapper.find(".no-content-label").exists()).toBeFalsy()
wrapper.destroy();
})
I was finally been able to fix the test by adding an id attribute to the label and finding it by id:
the label:
<label id="no-content-label" v-if="products.length===0 && !isTableBusy">No Content To Show</label>
The working tests:
let wrapper = null
beforeEach(() => {
wrapper = mount(ProductTable)
})
afterEach(() => {
wrapper.destroy()
})
it('Test "No Content To" Show appear When Products are empty', async () => {
await wrapper.setData({products: []})
expect(wrapper.find('[id="no-content-label"]').exists()).toBe(true)
expect(wrapper.find('[id="no-content-label"]').text()).toBe("No Content To Show");
})
it('Test "No Content To" not Show do not appear When Products are not empty', async () => {
await wrapper.setData({products: mockProduct})
expect(wrapper.find('[id="no-content-label"]').exists()).toBe(false);
})

Ant Design Vue3 Editable Table Value Does Not Change

I have used the table component from the ant design, and right now I'm stuck on why this cannot work,
The reference link is:
https://2x.antdv.com/components/table-cn#components-table-demo-edit-cell
Here's the source code from the ant design about editable table:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered>
<template v-for="col in ['name', 'age', 'address']" #[col]="{ text, record }" :key="col">
<div>
<a-input
v-if="editableData[record.key]"
v-model:value="editableData[record.key][col]"
style="margin: -5px 0"
/>
<template v-else>
{{ text }}
</template>
</div>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="editableData[record.key]">
<a #click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" #confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a #click="edit(record.key)">Edit</a>
</span>
</div>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import { defineComponent, reactive, ref, UnwrapRef } from 'vue';
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' },
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' },
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' },
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' },
},
];
interface DataItem {
key: string;
name: string;
age: number;
address: string;
}
const data: DataItem[] = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
export default defineComponent({
setup() {
const dataSource = ref(data);
const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
const edit = (key: string) => {
editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
};
const save = (key: string) => {
Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const cancel = (key: string) => {
delete editableData[key];
};
return {
dataSource,
columns,
editingKey: '',
editableData,
edit,
save,
cancel,
};
},
});
</script>
<style scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>
However, I have the functionality that my name will be a hyperlink, so I need to put the name column and its value outside the v-for. Here's what I got:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered>
<template v-for="col in ['age', 'address']" #[col]="{ text, record }" :key="col">
<div>
<a-input
v-if="editableData[record.key]"
v-model:value="editableData[record.key][col]"
style="margin: -5px 0"
/>
<template v-else>
{{ text }}
</template>
</div>
<template #name="{ text, record }">
<div>
<a-input v-if="editableData[record.key]" v-model:value="editableData[record.key][record.name]" style="margin:
-5px 0"></a-input>
<router-link v-else="v-else" to="/tables/123">{{ text }}</router-link>
</div>
</template>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="editableData[record.key]">
<a #click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" #confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a #click="edit(record.key)">Edit</a>
</span>
</div>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import { defineComponent, reactive, ref, UnwrapRef } from 'vue';
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' },
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' },
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' },
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' },
},
];
interface DataItem {
key: string;
name: string;
age: number;
address: string;
}
const data: DataItem[] = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
export default defineComponent({
setup() {
const dataSource = ref(data);
const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
const edit = (key: string) => {
editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
};
const save = (key: string) => {
Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const cancel = (key: string) => {
delete editableData[key];
};
return {
dataSource,
columns,
editingKey: '',
editableData,
edit,
save,
cancel,
};
},
});
</script>
<style scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>
However, when I changed my name through the editing mode, it seems that the name will not change based on the edited name. Is there anything I'm wrong? Any help will be greatly appreciated!
Ok, I finally got the answer - should be using a editableData[record.key].name when we are using v-model instead of editableData[record.key][record.name]

how to add edit button on vue-good-table in vue

I m new to Vue and stuck in a situation and don't know how to do that if anybody suggests me how I can do this let me show my code first
<div class="table-responsive-sm">
<vue-good-table
title="Shop List Table"
:columns="columns"
:rows="rows"
:paginate="true"
:lineNumbers="true"
:globalSearch="true" >
<template slot="table-row" slot-scope="props" ><a class="btn btn-sm primary" #on-row-click="onRowClick">save</a></template>
</vue-good-table>
and in script
data(){
return{
columns: [
{
label: 'Brand Name',
field: 'brand_name',
},
{
label: 'Brand Desc',
field: 'brand_desc',
},
{
label: 'Action',
field: 'before',
},
],
rows:[],
}
}
getTotals(){
var self = this;
var new1=[];
this.$http.get('/api/brands')
.then(function (response) {
self.rows=response.data
})
},
now my problem is that if I use
<span v-if="props.column.field == 'before'">
before
</span>
as suggested in this https://jsfiddle.net/aks9800/hsf0sqf8/ it throws an error like field not defined I just want to add an extra action button for edit this is vue-good table one more thing none of the action as suggested in this link for eg:- #on-row-click="onRowClick" not working
Try this
<div class="table-responsive-sm">
<vue-good-table
title="Shop List Table"
:columns="columns"
:rows="rows"
:paginate="true"
:lineNumbers="true"
:globalSearch="true" >
<template slot="table-row" slot-scope="props" >
<a class="btn btn-sm primary" #on-row-click="onRowClick">save</a>
</template>
<template slot="table-row" slot-scope="props">
<span v-if="props.column.field == 'actions'">
<a class="btn btn-sm primary" #on-row-click="onRowClick">save</a>
</span>
<span v-else>
{{props.formattedRow[props.column.field]}}
</span>
</template>
</vue-good-table>
</div>
data(){
return{
columns: [
{
label: 'Brand Name',
field: 'brand_name',
},
{
label: 'Brand Desc',
field: 'brand_desc',
},
{
label: 'Actions',
field: 'actions',
sortable: false,
}
],
rows:[],
}
}
getTotals(){
var self = this;
var new1=[];
this.$http.get('/api/brands')
.then(function (response) {
self.rows=response.data
})
},
Here is very good example how to add "edit" button in a row by marekfilip
https://jsfiddle.net/marekfilip/jm4ywzor/
html:
<div id="app">
<vue-good-table
:columns="columns"
:rows="rows" >
<template slot="table-row" slot-scope="props">
<span v-if="props.column.field == 'before'">
<button #click="editRow(props.row.id)">Edit</button>
<button #click="deleteRow(props.row.id)">Delete</button>
</span>
<span v-else>
{{props.formattedRow[props.column.field]}}
</span>
</template>
</vue-good-table>
<span>{{ text }}</span>
</div>
javascript
new Vue({
el: '#app',
data() {
return {
columns: [
{
label: 'Before',
field: 'before'
},
{
label: 'ID',
field: 'id',
sortable: true,
},
{
label: 'Text',
field: 'text',
type: 'number',
sortable: true,
},
],
rows: [
{ text: 'A', id: 1 },
{ text: 'B', id: 2 },
{ text: 'C', id: 3 },
{ text: 'D', id: 5 },
],
text: ''
};
},
methods: {
editRow(id) {
this.showAlert(id, 'EDIT')
},
deleteRow(id) {
this.showAlert(id, 'DELETE')
},
showAlert(id, type) {
this.text = `You clicked ${type} on row ID ${id}`
}
}
});