+
+
+
this.selected.includes(id) && r.canDeleteRecord).length
},
+ canUpdateSelectedRecords () {
+ return this.items.filter(({ id, r }) => this.selected.includes(id) && r.canUpdateRecord).length
+ },
+
canUndeleteSelectedRecords () {
return this.items.filter(({ id, r }) => this.selected.includes(id) && r.canUndeleteRecord).length
},
@@ -1580,6 +1595,10 @@ export default {
this.isDeleteActionVisible(record),
].some(v => v)
},
+
+ onBulkUpdateSuccessful () {
+ this.refresh(true)
+ },
},
}
diff --git a/client/web/compose/src/components/PageBlocks/RecordListConfigurator.vue b/client/web/compose/src/components/PageBlocks/RecordListConfigurator.vue
index 268e4e6a8..c2696c99f 100644
--- a/client/web/compose/src/components/PageBlocks/RecordListConfigurator.vue
+++ b/client/web/compose/src/components/PageBlocks/RecordListConfigurator.vue
@@ -263,6 +263,9 @@
{{ $t('recordList.export.allow') }}
+
+ {{ $t('recordList.record.enableBulkRecordEdit') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('recordList.bulkRecord.reset') }}
+
+
+
+
+ {{ $t('general.label.cancel') }}
+
+
+ {{ $t('general.label.save') }}
+
+
+
+
+
+
+
+
diff --git a/client/web/compose/src/mixins/record.js b/client/web/compose/src/mixins/record.js
index 1440afef3..c536418f9 100644
--- a/client/web/compose/src/mixins/record.js
+++ b/client/web/compose/src/mixins/record.js
@@ -262,6 +262,48 @@ export default {
})
}, 500),
+ handleBulkUpdateSelectedRecords: throttle(function () {
+ const { moduleID, namespaceID } = this.module
+ const values = this.record.values.toJSON()
+ const records = this.selectedRecords
+ this.processing = true
+
+ return this
+ .dispatchUiEvent('beforeFormSubmit')
+ .then(() => this.$ComposeAPI.recordPatch({ moduleID, namespaceID, records, values }))
+ .catch(err => {
+ const { details = undefined } = err
+ if (!!details && Array.isArray(details) && details.length > 0) {
+ this.errors = new validator.Validated()
+ this.errors.push(...details)
+
+ throw new Error(this.$t('notification:record.validationErrors'))
+ }
+
+ throw err
+ })
+ .then(({ records }) => {
+ return records[0]
+ })
+ .then((record) => {
+ this.updatePrompts()
+ return record
+ })
+ .then((record) => {
+ if (record.valueErrors && record.valueErrors.set) {
+ this.toastWarning(this.$t('notification:record.validationWarnings'))
+ } else {
+ this.toastSuccess(this.$t('notification:record.bulkRecordUpdateSuccess'))
+ this.onReset()
+ this.onModalHide()
+ }
+ })
+ .catch(this.toastErrorHandler(this.$t('notification:record.deleteBulkRecordUpdateFailed')))
+ .finally(() => {
+ this.processing = false
+ })
+ }, 500),
+
/**
* Validates record and dispatches onFormSubmitError
*
diff --git a/lib/js/src/api-clients/compose.ts b/lib/js/src/api-clients/compose.ts
index d2921331e..fbdb894b3 100644
--- a/lib/js/src/api-clients/compose.ts
+++ b/lib/js/src/api-clients/compose.ts
@@ -1655,6 +1655,42 @@ export default class Compose {
return `/namespace/${namespaceID}/module/${moduleID}/record/${recordID}`
}
+ // Partially update record values
+ async recordPatch (a: KV, extra: AxiosRequestConfig = {}): Promise
{
+ const {
+ namespaceID,
+ moduleID,
+ records,
+ values,
+ } = (a as KV) || {}
+ if (!namespaceID) {
+ throw Error('field namespaceID is empty')
+ }
+ if (!moduleID) {
+ throw Error('field moduleID is empty')
+ }
+ const cfg: AxiosRequestConfig = {
+ ...extra,
+ method: 'patch',
+ url: this.recordPatchEndpoint({
+ namespaceID, moduleID,
+ }),
+ }
+ cfg.data = {
+ records,
+ values,
+ }
+ return this.api().request(cfg).then(result => stdResolve(result))
+ }
+
+ recordPatchEndpoint (a: KV): string {
+ const {
+ namespaceID,
+ moduleID,
+ } = a || {}
+ return `/namespace/${namespaceID}/module/${moduleID}/record/`
+ }
+
// Delete record row from module section
async recordBulkDelete (a: KV, extra: AxiosRequestConfig = {}): Promise {
const {
diff --git a/lib/js/src/compose/types/page-block/record-list.ts b/lib/js/src/compose/types/page-block/record-list.ts
index f00d071d2..c6371c5c6 100644
--- a/lib/js/src/compose/types/page-block/record-list.ts
+++ b/lib/js/src/compose/types/page-block/record-list.ts
@@ -55,6 +55,8 @@ interface Options {
// Ordered list of buttons to display in the block
selectionButtons: Array