I have a vuetify data-table which renders the data received from axios call.
On the last column I'm using template v-slot for bespoke column so I can add two buttons. v-btn accepts has two props for loading state as per the documentation https://vuetifyjs.com/en/components/buttons#loaders:
:loading
:disabled
The problem is that when I call a function that changes those values, all of the buttons in the data table are receiving the prop state so instead of 1 button displying loader, all of them are.
<v-row no-gutters>
<v-data-table
:headers="tableHeaders"
:items="requests"
:items-per-page="10"
class="elevation-1"
:loading="loading" loading-text="Loading... Please wait"
>
<template v-slot:item.action="{ item }">
<v-btn color="success" #click="createPacks(item)" :loading="createloading" :disabled="createloading">Create</v-btn>
<v-btn color="error" #click="excludeRequest(item)" :loading="cancelloading" :disabled="cancelloading">Cancel</v-btn>
</template>
</v-data-table>
</v-row>
I'm aware this is because the buttons in the DOM are not unique and the framework is calling all of them but I have no idea how to change that default behaviour.
Data:
export default {
data() {
return {
loading: null,
error: null,
tableHeaders: [
{
text: 'ID',
value: 'req_id',
align: 'center',
},
{ text: 'Template', value: 'templateConcatenated'},
{ text: 'No of batches', value: 'no_batches' },
{ text: 'Batch size', value: 'batch_size' },
{ text: 'Date requested', value: 'requested_at' },
{ text: 'Requested by', value: 'requester' },
{ text: 'Actions', value: 'action', sortable: false, align: 'center'},
],
createloading: false,
cancelloading: false,
successmessage: '',
errormessage: '',
};
},
methods: {
createPacks(item) {
this.loading = true;
this.createloading = true;
let page_url = '/api/CreateProcedure?api_token='+this.$api_token;
axios
.post(page_url, item)
.then((response) => {
this.loading = false;
this.error = null;
this.createloading = false;
this.successmessage = 'Packs created successfully!';
this.errormessage = null;
})
.catch(err => {
this.createloading = false;
this.successmessage = null;
this.errormessage = 'Error creating the packs: '+err;
console.log("error: "+err);
})
},
}
}
Any idea how to call each individual button to change it's state?
Thank you
You've to set the loading properties on the item itself instead of defining them globally:
createPacks(item) {
this.loading = true;
item.createloading = true;
let page_url = '/api/CreateProcedure?api_token='+this.$api_token;
axios
.post(page_url, item)
.then((response) => {
this.loading = false;
this.error = null;
item.createloading = false;
this.successmessage = 'Packs created successfully!';
this.errormessage = null;
})
.catch(err => {
item.createloading = false;
this.successmessage = null;
this.errormessage = 'Error creating the packs: '+err;
console.log("error: "+err);
})
},
== UPDATE ==
I added a code based on the codepen you added in the comments, you have to use item.createloasing also in the HTML else it is not working. https://codepen.io/reijnemans/pen/LYPerLg?editors=1010
Currently only one button is working at the same time but this is probably because of axios is not defined in the codepen.
Related
I hope you could help me out.
Before going through the code, let me quickly explain what I want:
I have two components that I use for uploading and displaying images. I have FileResourceService that is used for uploading, and FileResourceImage which is used for storing and displaying the data. These work together with a v-model called profilePictureFileResourceId which basically just ties the images to specific users on the page, depending on who is logged on.
When displaying the image on a template, it is very straightforward. I just grab the FileResourceImage component and tie it with the v-model.
<file-resource-image v-model="form.user.profilePictureFileResourceId" can-upload style="width: 100px; height: 100px;" />
That is all very easy, but I have some pages where I use tables that contain information about my users, and I would like for the user’s profile images to actually be displayed in the table. Here is an example of a list used for the table.
fields() {
return [
{
key: "email",
label: this.$t('email'),
sortable: true,
template: {type: 'email'}
},
{
key: "name",
label: this.$t('name'),
sortable: true
},
{
key: 'type',
label: this.$t('type'),
formatter: type => this.$t(`model.user.types.${type}`),
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: 'status',
label: this.$t('status'),
formatter: type => this.$t(`model.user.status.${type}`),
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: "actions",
template: {
type: 'actions',
head: [
{
icon: 'fa-plus',
text: 'createUser',
placement: 'left',
to: `/users/add`,
if: () => this.$refs.areaAuthorizer.fullControl
}
],
cell: [
{
icon: 'fa-edit',
to: data => `/users/${data.item.id}/edit`
}
]
}
I know that I cannot just make an array that looks like this:
fields() {
return [
{
<file-resource-image v-model="form.user.profilePictureFileResourceId" can-upload />
}
]
}
So how would you make the component display from within in the list? I believe it can be done with props, but I am totally lost at what to do.
By the way, these are the two components I use for uploading and display. I thought I might as well show them, so you can get an idea of what they do.
For upload:
import axios from '#/config/axios';
import utils from '#/utils/utils';
export const fileResourceService = {
getFileResource(fileResourceId) {
return axios.get(`file/${fileResourceId}`);
},
getFileResourceFileContent(fileResourceId) {
return axios.get(`file/${fileResourceId}/download`, {responseType: 'arraybuffer', timeout: 0});
},
downloadFileResource(fileResourceId) {
return fileResourceService.getPublicDownloadToken(fileResourceId)
.then(result => fileResourceService.downloadPublicTokenFile(result.data));
},
downloadPublicTokenFile(fileResourcePublicDownloadTokenId) {
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href =
`${axios.defaults.baseURL}/file/public/${fileResourcePublicDownloadTokenId}/download`;
tempLink.setAttribute('download', '');
document.body.appendChild(tempLink);
tempLink.click();
setTimeout(() => document.body.removeChild(tempLink), 0);
},
getPublicDownloadToken(fileResourceId) {
return axios.get(`file/${fileResourceId}/public-download-token`);
},
postFileResource(fileResource, file) {
return axios.post(`file`, utils.toFormData([
{name: 'fileResource', type: 'json', data: fileResource},
{name: 'file', data: file}
]), {timeout: 0});
}
};
Then we have the component that is used for DISPLAYING the images:
<template>
<div :style="style" #click="upload" style="cursor: pointer;">
<div v-if="url === null">
<i class="fas fa-camera"></i>
</div>
<div v-if="canUpload" class="overlay">
<i class="fas fa-images"></i>
</div>
</div>
</template>
<script>
import {fileResourceService} from '#/services/file-resource';
import utils from '#/utils/utils';
export default {
model: {
prop: 'fileResourceId',
event: 'update:fileResourceId'
},
props: {
fileResourceId: String,
canUpload: Boolean,
defaultIcon: {
type: String,
default: 'fas fa-camera'
}
},
data() {
return {
url: null
};
},
computed: {
style() {
return {
backgroundImage: this.url && `url(${this.url})`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
};
}
},
methods: {
upload() {
if(this.canUpload) {
utils.openFileDialog()
.then(([file]) => fileResourceService.postFileResource({}, file))
.then(result => this.$emit('update:fileResourceId', result.data.id))
.catch(() => this.$bvModalExt.msgBoxError())
}
}
},
watch: {
fileResourceId: {
immediate: true,
handler() {
this.url = null;
if (this.fileResourceId) {
fileResourceService.getFileResourceFileContent(this.fileResourceId).then(result => {
const reader = new FileReader();
reader.onload = event => this.url = event.target.result;
reader.readAsDataURL(new Blob([result.data]));
});
}
}
}
}
};
</script>
I have a method that sends data to the database and can be triggered from a button using #click but I want to trigger it from another function by calling it in conditional function any one can help? the post is edited so I want to add the addPatient method called right after submitted notification is triggered in else{}
onSubmit () {
namefRef.value.validate()
namelRef.value.validate()
birthdateRef.value.validate()
if (namefRef.value.hasError || namelRef.value.hasError || birthdateRef.value.hasError ) {
// form has error
}
else if (gender.value !== 'male'&& gender.value !== 'female') {
$q.notify({
color: 'negative',
message: 'You need to choose a gender'
})
}
else {
$q.notify({
icon: 'done',
color: 'positive',
message: 'Submitted',
}),
loadinga.value = true
setTimeout(() => {loadinga.value = false }, 300)
}
},
}
},
data() {
return{
patient:{
id:uid(),
caption:'',
namef:'',
namel:'',
age:'',
lastappointment:'',
location:'',
photo:null,
date:Date.now()
},
loading:false,
}
},
methods:{
addPatient() {
let formData = new FormData()
formData.append('id', this.patient.id)
formData.append('index', this.patient.index)
formData.append('namef', this.namef)
formData.append('namel', this.namel)
formData.append('age', this.patient.age)
formData.append('date', this.patient.date)
formData.append('lastappointment', this.patient.lastappointment)
//* formData.append('file', this.patient.photo, this.patient.id + '.png')
this.$axios.post(`${ process.env.API}/createPatient`, formData).then(
response => {
console.log('response:',response)
}).catch(err => {
console.log('err:',err)
})
}
I'm developing an admin panel with "SCUTUM" admin template with nuxt.js.
When I trying to use datatables component in the template it gives following error in the console. Component is made by template provider. And I used it as the example given by them.
[Vue warn]: Failed to resolve async component: function Datatable() {
return Promise.all(/*! import() */[__webpack_require__.e(0), __webpack_require__.e(1)]).then(__webpack_require__.bind(null, /*! ~/components/datatables/Datatables */ "./components/datatables/Datatables.vue"));
}
Reason: TypeError: Cannot read property 'display' of undefined
What is the reason for this?
Here is my code
<template>
<div id="sc-page-wrapper">
<div id="sc-page-content">
<ScCard class="">
<ScCardHeader seperator>
<div class="uk-flex-middle uk-grid-small uk-grid" data-uk-grid>
<div class="uk-flex-1">
<ScCardTitle>
List of Lecturers
</ScCardTitle>
</div>
<div class="uk-width-auto#s">
<div id="sc-dt-buttons"></div>
</div>
</div>
</ScCardHeader>
<ScCardBody>
<client-only>
<Datatable
id="sc-dt-buttons-table"
ref="buttonsTable"
:data="dtDData"
:options="dtDOptions"
:buttons="true"
#initComplete="dtButtonsInitialized"
/>
</client-only>
</ScCardBody>
</ScCard>
</div>
</div>
</template>
<script>
const rows = require('~/data/pages/datatables.json');
import ScCard from "#/components/card/";
import {ScCardTitle, ScCardBody, ScCardHeader} from "#/components/card"
export default {
name: 'Lecturer',
components: {
Datatable: () => import('~/components/datatables/Datatables'),
ScCard,
ScCardTitle,
ScCardHeader,
ScCardBody
},
data: () => ({
dtDData: JSON.parse(JSON.stringify(rows)),
dtDOptions: {
buttons: [
{
extend: "copyHtml5",
className: "sc-button",
text: 'Copy'
},
{
extend: "csvHtml5",
className: "sc-button",
text: 'CSV '
},
{
extend: "excelHtml5",
className: "sc-button",
text: 'Excel '
},
{
extend: "pdfHtml5",
className: "sc-button sc-button-icon",
text: '<i class="mdi mdi-file-pdf md-color-red-800"></i>'
},
{
extend: "print",
className: "sc-button sc-button-icon",
text: '<i class="mdi mdi-printer"></i>',
title: 'Custom Title',
messageTop: 'Custom message on the top',
messageBottom: 'Custom message on the bottom',
autoPrint: true
}
]
}
}),
mounted () {
this.$nextTick(() => {
})
},
methods: {
dtButtonsInitialized () {
// append buttons to custom container
this.$refs.buttonsTable.$dt.buttons().container().appendTo(document.getElementById('sc-dt-buttons'))
},
}
}
</script>
<style lang="scss">
</style>
Here is the Datatables component
<template>
<div>
<table :id="id" class="uk-table uk-table-striped uk-table-middle" :class="[tableClass]" style="width:100%">
<thead>
<tr>
<th v-for="header in headers" :key="header" class="uk-text-nowrap">
{{ header }}
</th>
</tr>
</thead>
<slot name="footer"></slot>
</table>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { scMq } from '~/assets/js/utils'
require('~/plugins/jquery');
require('datatables.net/js/jquery.dataTables');
require('datatables.net-responsive');
require('datatables.net-select');
require('./dataTables.uikit');
require('./dataTables.responsive.uikit');
export default {
name: 'Datatable',
props: {
data: {
type: Array,
default: () => [],
required: true
},
options: {
type: Object,
default: () => {}
},
id: {
type: String,
required: true
},
buttons: {
type: Boolean,
default: false
},
autoWidth: Boolean,
tableClass: {
type: String,
default: ''
},
customEvents: {
type: Array,
default: () => []
}
},
data: () => ({
$dt: null
}),
computed: {
dtData () {
return JSON.parse(JSON.stringify(this.data))
},
headers () {
let names = [];
Object.keys(this.data[0]).map(k => {
let name = k.replace(/_/g, ' ');
names.push(name.charAt(0).toUpperCase() + name.slice(1))
});
return names
},
columns () {
let columns = [];
Object.keys(this.data[0]).map(k => {
columns.push({
data: k
})
});
return columns;
},
...mapState([
'vxSidebarMainExpanded'
])
},
watch: {
dtData (newVal, oldVal) {
const newLength = newVal.length;
const oldLength = oldVal.length;
const newIds = newVal.map(k => {
return k.id
});
const oldIds = oldVal.map(k => {
return k.id
});
if(newLength > oldLength) {
let uniq = newIds.filter(k => {
return !oldIds.includes(k)
});
if (uniq.length) {
const newEl = newVal.filter(obj => {
return obj.id === uniq[0]
});
this.$dt.row.add(newEl[0]).draw('full-hold');
}
} else {
let uniq = oldIds.filter(k => {
return !newIds.includes(k)
});
if (uniq.length) {
this.$dt.row(':eq('+ uniq[0] +')').remove().draw('full-hold')
}
}
},
vxSidebarMainExpanded () {
if(scMq.mediumMin()) {
setTimeout(() => {
$('#' + this.id).resize()
}, 300);
}
}
},
mounted () {
const self = this;
if(self.buttons) {
const pdfMake = require('~/assets/js/vendor/pdfmake/pdfmake');
const pdfFonts =require('~/assets/js/vendor/pdfmake/vfs_fonts');
pdfMake.vfs = pdfFonts.pdfMake.vfs;
window.JSZip = require("~/assets/js/vendor/jszip");
require('datatables.net-buttons');
require('datatables.net-buttons/js/buttons.html5');
require('datatables.net-buttons/js/buttons.print');
}
let _options = {
data: self.data,
columns: self.columns,
responsive: true,
"initComplete" (settings, json) {
self.$dt = this.api();
self.$emit('initComplete');
}
};
const options = $.extend(_options, self.options);
if(options.responsive === 'responsiveModal') {
_options.responsive = {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header (row) {
return 'Details for row ' + (parseInt(row[0]) + 1);
}
}),
renderer: $.fn.dataTable.Responsive.renderer.tableAll({
tableClass: 'table'
})
}
}
}
$('#' + this.id).DataTable(options);
if(this.customEvents.length) {
this.customEvents.forEach(event => {
this.$dt.on(event.name, event.function)
})
}
}
}
</script>
Is there a way to resolve this?
I have these two grid. The bottom one is based on the top one:
Each of the items in the lower grid is related to the Program Name and the Tool# selected from the top grid. In this picture the "Delete Tool Change" button is enable since I have selected an item from the lower grid.
Now, if I choose a different Program name and Tool# (Eg: from #6 to 1 on the top grid), and choose a different item from the bottom grid(Eg:1#), it suddenly disables the "Delete Tool Change" button.
The two grid after choosing a different item from upper grid
This is my code for the upper grid component.
columns: ColumnDef[] = [
{ field: 'programName', name: 'Program Name', editable: false, filterField: true, width: '12em', tooltip: 'Read Only' },
{ field: 'toolNoPrefix', name: 'Tool #(Prefix)', editable: false, filterField: true, width: '12em', tooltip: 'First 8 characters of the Tool Tip - Read Only' },
{ field: 'toolNoSuffix', name: '(Suffix)', filterField: true, width: '8em' },
{ field: 'toolName', name: 'Tool Name', editable: false, filterField: true, width: '24em' },
{ field: 'tlLeadFileName', name: 'Tool File Name' },
{ field: 'typeName', name: 'Fixture Type', editable: false, width: '12em' },
{field: 'engineerId', name: 'MSE', type: 'dropdown',
optionsList: this.engineers, optionsListField: 'id', optionsListName: 'lastFirstName', width: '10em'},
{ field: 'userSource', name: 'User Source', editable: false, width: '12em' },
{ field: 'tprCreateDate', name: 'Date Created', type: 'date', editable: false, width: '8em' },
];
hasLoaded = false;
resourced = false;
selectedEcmTool$: BehaviorSubject<any> = new BehaviorSubject(null);
#ViewChild('tools') dataTable: DataTableComponent;
constructor(
private router: Router,
private cgpAlertDialogService: CgpAlertDialogService,
private ecmService: EcmService,
private dialog: MatDialog,
private readonly toastr: ToastrService
) {}
ngOnInit() {
if (!this.selectionCriteria) {
this._init = this.cgpAlertDialogService.showAlertDialog({
title: 'Invalid Selection Criteria',
message: 'A selection criteria has not been selected. Redirecting back to the main page.',
alert: cgpAlertTypes.warning,
closeLabel: 'OK'
}).afterClosed().subscribe(
() => this.router.navigate([''])
);
}
if (this.router.url === '/tprecm/ecm/re-source') {
this.resourced = true;
this.columns.forEach(val => val.editable = false);
}
this.updateNameSources();
this.hasLoaded = true;
}
ngOnDestroy() {
if (this._init) {
this._init.unsubscribe();
}
}
loadECMs() {
this.loading = true;
const body = {
...this.selectionCriteria,
filterColumn: this._currentFilters,
reSourced: +this.resourced,
};
this.ecmService.getAllTools(body, this.pageOptions)
.pipe(
filter(Boolean),
finalize(() => this.loading = false)
).subscribe((res: { totalCount: number, data: any[] }) => {
this.total = res.totalCount;
this.data = res.data;
if (this.data.length >= 1) {
this.dataTable.selections = [this.data[0]];
this.selectedEcmTool$.next(this.data[0]);
}
});
}
onSelect(selectedEcmTool) {
this.selectedEcmTool$.next(selectedEcmTool);
}
This is my html for the uppergrid:
<cgp-app-card titleText="View/Update ECM" showFullScreenToggle="true" [showBackButton]="true"
[onBackButtonClicked]="onBackButtonClicked">
<div *ngIf="hasLoaded">
<data-table #tools [data]="data" [columns]="columns" (lazyLoad)="onLazyLoad($event)" [lazy]="true" [paging]="true"
[pageSize]="pageOptions.size" [totalRecords]="total" [loading]="loading" [filterable]="true" (edit)="updateTool($event)"
(select)="onSelect($event)">
<ng-container actionStart>
<button mat-button (keypress)="onEcmNecReportsClick()" (click)="onEcmNecReportsClick()">Nec Reports</button>
<button mat-button (keypress)="onEcmReportsClick()" (click)="onEcmReportsClick()">ECM Reports</button>
<button mat-button (keypress)="onToolPartRelationshipClick()" (click)="onToolPartRelationshipClick()">Edit
Tool/Part Relationship</button>
<button mat-button (keypress)="onEcmPartsClick()" (click)="onEcmPartsClick()">Parts</button>
<button mat-button [disabled]="this.resourced" (keypress)="onEcmPartsUploadClick()"
(click)="onEcmPartsUploadClick()">Upload Parts from a File</button>
</ng-container>
<ng-container actionEnd>
<button mat-button>Change Log</button>
</ng-container>
</data-table>
<ecm-tool-change [resourced]="this.resourced" [selectedEcmTool$]="selectedEcmTool$"></ecm-tool-change>
</div>
</cgp-app-card>
This is my code for the lower grid component:
#Input() selectedEcmTool$: BehaviorSubject<any>;
#ViewChild('toolChangeTable') toolChangeTable: DataTableComponent;
constructor(
private readonly ecmToolChangeService: EcmToolChangeService,
private readonly ecmService: EcmService,
private readonly dialog: MatDialog,
private readonly toastr: ToastrService,
private readonly confirmation: CgpConfirmationDialogService,
private readonly cgpAlertDialogService: CgpAlertDialogService,
) {
}
onSelect(selectedEcmTool, toolChangeTable: DataTableComponent) {
if (selectedEcmTool.dtShippedToDatabank) {
const selected = toolChangeTable.selections;
const index = selected.findIndex(s => s.toolId === selectedEcmTool.toolId);
if (index !== -1) {
toolChangeTable.selections.splice(index, 1);
}
this.toastr.error('You cannot check this Tool Change after you have entered the Shipped to Databank Date ');
}
}
onUnSelect(dataItem) {
return dataItem;
}
ngOnInit() {
if (this.resourced) {
this.columns.forEach((val) => val.editable = false);
}
this.selectedEcmTool$.subscribe(
(selectedEcmTool) => {
if (selectedEcmTool) {
const toolId = selectedEcmTool.toolId;
this.updateSelectedEcmTool(toolId);
this.updateDesignSources();
} else {
this.data = [];
}
}
);
}
ngOnDestroy() {
if (this.selectedEcmTool$) { this.selectedEcmTool$.unsubscribe(); }
}
onLazyLoad(event: LazyLoadEvent) {
this.pageOptions.order = event.sortOrder === 1 ? 'asc' : 'desc';
this.pageOptions.size = event.rows;
this.pageOptions.sort = event.sortField;
this.pageOptions.page = event.first / this.pageOptions.size;
this.updateSelectedEcmTool(this.toolId);
}
clearSelections() {
this.toolChangeTable.selections = [];
}
updateSelectedEcmTool(toolId) {
if (!toolId) {
return;
}
this.toolId = toolId;
this.loading = true;
this.ecmToolChangeService.getToolChange(toolId, this.pageOptions)
.pipe(filter(Boolean))
.subscribe({
next: (res: { totalCount: number, data: any[] }) => {
this.total = res ? res.totalCount : 0;
this.data = res ? res.data : [];
},
complete: () => this.loading = false
});
}
updateToolChange(event: any) {
const body = event.data;
body.sourceName = undefined;
this.ecmToolChangeService.updateToolChange(body)
.subscribe();
}
This is my code for the lower grid html:
<data-table #toolChangeTable [columns]="columns" [data]="data" [loading]="loading" (lazyLoad)="onLazyLoad($event)" [lazy]="true"
[lazyLoadOnInit]="false" [pageSize]="pageOptions.size" [multiselect]="true" [paging] = "true" [totalRecords]="total"
defaultSortField="necLec" (edit)="updateToolChange($event, toolChangeTable)" (select)="onSelect($event, toolChangeTable)" (unSelect)="onUnSelect($event)">
<ng-container actionStart>
<button mat-button (click)="onMultiRowUpdateClick()" (keypress.enter)="onMultiRowUpdateClick()"
[disabled]="this.resourced || hasSelectedNone">Multi-Edit</button>
<button mat-button (click)="clearSelections()" (keypress.enter)="clearSelections()">Clear All</button>
<button mat-button (click)="onAddToolChangeClick()" [disabled]="this.resourced">Add Tool Change</button>
<button mat-button (click)="onDeleteToolChangeClick()" (keypress.enter)="onDeleteToolChangeClick()"
[disabled]="!hasSelectedSingle">Delete Tool Change</button>
<button mat-button [disabled]="!hasSelectedSingle" (click)="onEditAuthoritiesClick()"
(keypress.enter)="onEditAuthoritiesClick()">Edit Tool Change
Authorities</button>
</ng-container>
</data-table>
How can I write a function or trigger ngOnDestroy so that it does not remembers the previously selected rows anymore.
why not just call clearSelections() from within this.selectedEcmTool$.subscribe()?
every time the selectedEcmTool$ observable gets a new value, the lower grid clears it's selections.
or am I missing something?
ngOnInit() {
if (this.resourced) {
this.columns.forEach((val) => val.editable = false);
}
this.selectedEcmTool$.subscribe(
//clear selections whenever the tool changes
this.clearSelections();
(selectedEcmTool) => {
if (selectedEcmTool) {
const toolId = selectedEcmTool.toolId;
this.updateSelectedEcmTool(toolId);
this.updateDesignSources();
} else {
this.data = [];
}
}
);
}
I fixed it by adding:
get hasSelectedSingleCheck(): boolean {
return (this.toolChangeTable.selections || [])
.filter((row) => row.toolId === this.selectedToolId).length === 1;
}
and adding this check in the html to disable the button if its true.
I'm creating a custom directive to make a form submit via ajax - however I can't seem to get validation errors to bind to the Vue instance.
I am adding my directive here:
<form action="{{ route('user.settings.update.security', [$user->uuid]) }}" method="POST"
enctype="multipart/form-data" v-ajax errors="formErrors.security" data="formData.security">
My directive looks like:
Vue.directive('ajax', {
twoWay: true,
params: ['errors', 'data'],
bind: function () {
this.el.addEventListener('submit', this.onSubmit.bind(this));
},
update: function (value) {
},
onSubmit: function (e) {
e.preventDefault();
this.vm
.$http[this.getRequestType()](this.el.action, vm[this.params.data])
.then(this.onComplete.bind(this))
.catch(this.onError.bind(this));
},
onComplete: function () {
swal({
title: 'Success!',
text: this.params.success,
type: 'success',
confirmButtonText: 'Back'
});
},
onError: function (response) {
swal({
title: 'Error!',
text: response.data.message,
type: 'error',
confirmButtonText: 'Back'
});
this.set(this.vm, this.params.errors, response.data);
},
getRequestType: function () {
var method = this.el.querySelector('input[name="_method"]');
return (method ? method.value : this.el.method).toLowerCase();
},
});
And my VUE instance looks like:
var vm = new Vue({
el: '#settings',
data: function () {
return {
active: 'profile',
updatedSettings: getSharedData('updated'),
formData: {
security: {
current_password: ''
}
},
formErrors: {
security: []
},
}
},
methods: {
setActive: function (name) {
this.active = name;
},
isActive: function (name) {
if (this.active == name) {
return true;
} else {
return false;
}
},
hasError: function (item, array, sub) {
if (this[array][sub][item]) {
return true;
} else {
return false;
}
},
isInArray: function (value, array) {
return array.indexOf(value) > -1;
},
showNotification: function () {
if (this.updatedSettings) {
$.iGrowl({
title: 'Updated',
message: 'Your settings have been updated successfully.',
type: 'success',
});
}
},
}
});
However, when I output the data, the value for formErrors.security is empty
Any idea why?
The issue is with the this.set(/* ... */) line. this.set doesn't work the same as Vue.set or vm.$set.
this.set attempts to set the path that you passed to the directive: v-my-directive="a.b.c". So running this.set('foo') will attempt to set $vm.a.b.c to 'foo'.
What you want to do is this:
(ES6)
this.params.errors.splice(0, this.params.errors.length, ...response.data)
(Vanilla JS)
this.params.errors.splice.apply(this.params.errors, [0,this.params.errors.length].concat(response.data))
That will update whatever Object is tied to the errors param on the DOM Node. Make sure that you do v-bind:errors="formErrors.security" or :errors="formErrors.security".