diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 21ad0e41a..4128fa44c 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -131,11 +131,10 @@ async function fetch(data) { const rows = keyData ? data[keyData] : data; resetData(rows); emit('onFetch', rows); - $props.insertOnLoad && await insert(); + $props.insertOnLoad && (await insert()); return rows; } - function resetData(data) { if (!data) return; if (data && Array.isArray(data)) { @@ -146,15 +145,22 @@ function resetData(data) { formData.value = JSON.parse(JSON.stringify(data)); if (watchChanges.value) watchChanges.value(); //destroy watcher - watchChanges.value = watch(formData, (nVal) => { - hasChanges.value = false; - const filteredNewData = nVal.filter(row => !isRowEmpty(row) || row[$props.primaryKey]); - const filteredOriginal = originalData.value.filter(row => row[$props.primaryKey]); + watchChanges.value = watch( + formData, + (nVal) => { + hasChanges.value = false; + const filteredNewData = nVal.filter( + (row) => !isRowEmpty(row) || row[$props.primaryKey], + ); + const filteredOriginal = originalData.value.filter( + (row) => row[$props.primaryKey], + ); - const changes = getDifferences(filteredOriginal, filteredNewData); - hasChanges.value = !isEmpty(changes); - - }, { deep: true }); + const changes = getDifferences(filteredOriginal, filteredNewData); + hasChanges.value = !isEmpty(changes); + }, + { deep: true }, + ); } async function reset() { await fetch(originalData.value); @@ -183,9 +189,8 @@ async function onSubmit() { }); } isLoading.value = true; - + await saveChanges($props.saveFn ? formData.value : null); - } async function onSubmitAndGo() { @@ -194,10 +199,10 @@ async function onSubmitAndGo() { } async function saveChanges(data) { - formData.value = formData.value.filter(row => - row[$props.primaryKey] || !isRowEmpty(row) + formData.value = formData.value.filter( + (row) => row[$props.primaryKey] || !isRowEmpty(row), ); - + if ($props.saveFn) { $props.saveFn(data, getChanges); isLoading.value = false; @@ -228,31 +233,29 @@ async function saveChanges(data) { } async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) { - formData.value = formData.value.filter(row => !isRowEmpty(row)); + formData.value = formData.value.filter((row) => !isRowEmpty(row)); const lastRow = formData.value.at(-1); const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false; - + if (formData.value.length && isLastRowEmpty) return; const $index = formData.value.length ? formData.value.at(-1).$index + 1 : 0; - + const nRow = Object.assign({ $index }, pushData); formData.value.push(nRow); - - const hasChange = Object.keys(nRow).some(key => !isChange(nRow, key)); + + const hasChange = Object.keys(nRow).some((key) => !isChange(nRow, key)); if (hasChange) hasChanges.value = true; } function isRowEmpty(row) { - return Object.keys(row).every(key => isChange(row, key)); + return Object.keys(row).every((key) => isChange(row, key)); } - -function isChange(row,key){ +function isChange(row, key) { return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key); } - async function remove(data) { if (!data.length) return quasar.notify({ @@ -270,7 +273,9 @@ async function remove(data) { (form) => !preRemove.some((index) => index == form.$index), ); formData.value = newData; - hasChanges.value = JSON.stringify(removeIndexField(formData.value)) !== JSON.stringify(removeIndexField(originalData.value)); + hasChanges.value = + JSON.stringify(removeIndexField(formData.value)) !== + JSON.stringify(removeIndexField(originalData.value)); } if (ids.length) { quasar @@ -286,7 +291,7 @@ async function remove(data) { }) .onOk(async () => { newData = newData.filter((form) => !ids.some((id) => id == form[pk])); - fetch(newData); + await reload(); }); } diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index 29f5f1a5a..c9b896331 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -254,17 +254,13 @@ async function save() { old: originalData.value, }); if ($props.reload) await arrayData.fetch({}); + if ($props.goTo) push({ path: $props.goTo }); hasChanges.value = false; } finally { isLoading.value = false; } } -async function saveAndGo() { - await save(); - push({ path: $props.goTo }); -} - function reset() { formData.value = JSON.parse(JSON.stringify(originalData.value)); updateAndEmit('onFetch', { val: originalData.value }); @@ -385,7 +381,7 @@ defineExpose({ diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index 05c764d73..952b54583 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -185,6 +185,7 @@ const col = computed(() => { newColumn.attrs = { ...newColumn.component?.attrs, autofocus: $props.autofocus }; newColumn.event = { ...newColumn.component?.event, ...$props?.eventHandlers }; } + return newColumn; }); diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 93ced44d8..eeb500c6d 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -151,6 +151,10 @@ const $props = defineProps({ type: String, default: 'vnTable', }, + selectionFn: { + type: Function, + default: null, + }, }); const { t } = useI18n(); @@ -338,10 +342,10 @@ function stopEventPropagation(event) { event.stopPropagation(); } -function reload(params) { +async function reload(params) { selected.value = []; selectAll.value = false; - CrudModelRef.value.reload(params); + await CrudModelRef.value.reload(params); } function columnName(col) { @@ -395,12 +399,14 @@ function hasEditableFormat(column) { } const clickHandler = async (event) => { - const clickedElement = event.target.closest('td'); - const isDateElement = event.target.closest('.q-date'); - const isTimeElement = event.target.closest('.q-time'); - const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon'); + const el = event.target; + const clickedElement = el.closest('td'); + const isDateElement = el.closest('.q-date'); + const isTimeElement = el.closest('.q-time'); + const isQSelectDropDown = el.closest('.q-select__dropdown-icon'); + const isDialog = el.closest('.q-dialog'); - if (isDateElement || isTimeElement || isQSelectDropDown) return; + if (isDateElement || isTimeElement || isQSelectDropDown || isDialog) return; if (clickedElement === null) { await destroyInput(editingRow.value, editingField.value); @@ -447,6 +453,7 @@ async function renderInput(rowId, field, clickedElement) { const row = CrudModelRef.value.formData[rowId]; const oldValue = CrudModelRef.value.formData[rowId][column?.name]; + if (column.disable) return; if (!clickedElement) clickedElement = document.querySelector( `[data-row-index="${rowId}"][data-col-field="${field}"]`, @@ -480,6 +487,7 @@ async function renderInput(rowId, field, clickedElement) { await destroyInput(rowId, field, clickedElement); }, keydown: async (event) => { + await column?.cellEvent?.['keydown']?.(event, row); switch (event.key) { case 'Tab': await handleTabKey(event, rowId, field); @@ -655,7 +663,9 @@ const rowCtrlClickFunction = computed(() => { }); const handleHeaderSelection = (evt, data) => { if (evt === 'updateSelected' && selectAll.value) { - selected.value = tableRef.value.rows; + const fn = $props.selectionFn; + const rows = tableRef.value.rows; + selected.value = fn ? fn(rows) : rows; } else if (evt === 'selectAll') { selected.value = data; } else { @@ -701,7 +711,6 @@ const handleHeaderSelection = (evt, data) => { :search-url="searchUrl" :disable-infinite-scroll="isTableMode" :before-save-fn="removeTextValue" - @save-changes="reload" :has-sub-toolbar="$props.hasSubToolbar ?? isEditable" :auto-load="hasParams || $attrs['auto-load']" > @@ -729,7 +738,15 @@ const handleHeaderSelection = (evt, data) => { :virtual-scroll="isTableMode" @virtual-scroll="onVirtualScroll" @row-click="(event, row) => handleRowClick(event, row)" - @update:selected="emit('update:selected', $event)" + @update:selected=" + (evt) => { + if ($props.selectionFn) selected = $props.selectionFn(evt); + emit( + 'update:selected', + selectionFn ? selectionFn(selected) : selected, + ); + } + " @selection="(details) => handleSelection(details, rows)" :hide-selected-banner="true" :data-cy diff --git a/src/components/common/VnChangePassword.vue b/src/components/common/VnChangePassword.vue index d8374498f..9fbc8fda8 100644 --- a/src/components/common/VnChangePassword.vue +++ b/src/components/common/VnChangePassword.vue @@ -36,8 +36,6 @@ const validate = async () => { isLoading.value = true; await props.submitFn(newPassword, oldPassword); emit('onSubmit'); - } catch (e) { - notify('errors.writeRequest', 'negative'); } finally { changePassDialog.value.hide(); isLoading.value = false; diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index bb9e1a4d8..93ff04f75 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -445,7 +445,12 @@ function getOptionLabel(property) {