diff --git a/src/components/inspector/index.js b/src/components/inspector/index.js index c3c417107..ee9503130 100644 --- a/src/components/inspector/index.js +++ b/src/components/inspector/index.js @@ -12,3 +12,5 @@ export { default as ImageVariable } from './image-variable.vue'; export { default as InputVariable } from './input-variable.vue'; export { default as Tooltip } from './tooltip'; export { default as DeviceVisibility } from './device-visibility'; +export { default as LoadingSubmitButton } from './loading-submit-button'; +export { default as LabelSubmitButton } from './label-submit-button'; diff --git a/src/components/inspector/label-submit-button.vue b/src/components/inspector/label-submit-button.vue new file mode 100644 index 000000000..686d07eca --- /dev/null +++ b/src/components/inspector/label-submit-button.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/components/inspector/loading-submit-button.vue b/src/components/inspector/loading-submit-button.vue new file mode 100644 index 000000000..cc2f3bde0 --- /dev/null +++ b/src/components/inspector/loading-submit-button.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/components/renderer/form-button.vue b/src/components/renderer/form-button.vue index 4e74e3e7d..c909d512b 100644 --- a/src/components/renderer/form-button.vue +++ b/src/components/renderer/form-button.vue @@ -1,7 +1,16 @@ @@ -11,10 +20,14 @@ import Mustache from 'mustache'; import { mapActions, mapState } from "vuex"; import { getValidPath } from '@/mixins'; - export default { mixins: [getValidPath], - props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData'], + props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData', 'loading', 'loadingLabel'], + data() { + return { + showSpinner: false + }; + }, computed: { ...mapState('globalErrorsModule', ['valid']), classList() { @@ -65,9 +78,12 @@ export default { this.setValue(this.$parent, this.name, this.fieldValue); } if (this.event === 'submit') { + if (this.loading) { + this.showSpinner = true; + } this.$emit('input', this.fieldValue); - this.$nextTick(()=>{ - this.$emit('submit', this.eventData); + this.$nextTick(() => { + this.$emit('submit', this.eventData, this.loading); }); return; } diff --git a/src/components/task.vue b/src/components/task.vue index e8f34bc43..80536e494 100644 --- a/src/components/task.vue +++ b/src/components/task.vue @@ -79,7 +79,8 @@ export default { csrfToken: { type: String, default: null }, value: { type: Object, default: () => {} }, beforeLoadTask: { type: Function, default: defaultBeforeLoadTask }, - initialLoopContext: { type: String, default: "" } + initialLoopContext: { type: String, default: "" }, + loading: { type: Number, default: null } }, data() { return { @@ -98,6 +99,7 @@ export default { hasErrors: false, refreshScreen: 0, redirecting: null, + loadingButton: false }; }, watch: { @@ -263,10 +265,15 @@ export default { }); }, prepareTask() { - this.resetScreenState(); - this.requestData = _.get(this.task, 'request_data', {}); - this.loopContext = _.get(this.task, "loop_context", ""); - this.refreshScreen++; + // If the immediate task status is completed and we are waiting with a loading button, + // do not reset the screen because that would stop displaying the loading spinner + // before the next task is ready. + if (!this.loadingButton || this.task.status === 'ACTIVE') { + this.resetScreenState(); + this.requestData = _.get(this.task, 'request_data', {}); + this.loopContext = _.get(this.task, "loop_context", ""); + this.refreshScreen++; + } this.$emit('task-updated', this.task); @@ -278,6 +285,8 @@ export default { } }, resetScreenState() { + this.loadingButton = false; + this.disabled = false; if (this.$refs.renderer && this.$refs.renderer.$children[0]) { this.$refs.renderer.$children[0].currentPage = 0; } @@ -303,6 +312,9 @@ export default { if (this.task.process_request.status === 'COMPLETED') { this.loadNextAssignedTask(parentRequestId); + } else if (this.loadingButton) { + this.loadNextAssignedTask(parentRequestId); + } else if (this.task.allow_interstitial) { this.task.interstitial_screen['_interstitial'] = true; this.screen = this.task.interstitial_screen; @@ -362,7 +374,7 @@ export default { } return 'card-header text-capitalize text-white ' + header; }, - submit(formData = null) { + submit(formData = null, loading = false) { //single click if (this.disabled) { return; @@ -372,12 +384,15 @@ export default { if (formData) { this.onUpdate(Object.assign({}, this.requestData, formData)); } - this.$emit('submit', this.task); - this.$nextTick(() => { - this.disabled = false; - }); - if (this.task && this.task.allow_interstitial) { + if (loading) { + this.loadingButton = true; + } else { + this.loadingButton = false; + } + this.$emit('submit', this.task, loading); + + if (this.task && this.task.allow_interstitial && !this.loadingButton) { this.task.interstitial_screen['_interstitial'] = true; this.screen = this.task.interstitial_screen; } @@ -500,6 +515,7 @@ export default { requestIdNode.setAttribute('content', this.requestId); } }, + }, mounted() { this.screenId = this.initialScreenId; diff --git a/src/components/vue-form-renderer.vue b/src/components/vue-form-renderer.vue index 632b44f3b..212cd1221 100644 --- a/src/components/vue-form-renderer.vue +++ b/src/components/vue-form-renderer.vue @@ -231,8 +231,8 @@ export default { node.$children.forEach(child => this.registerCustomFunctions(child)); } }, - submit() { - this.$emit('submit', this.data); + submit(eventData, loading = false) { + this.$emit('submit', this.data, loading); }, parseCss() { const containerSelector = `.${this.containerClass}`; diff --git a/src/form-builder-controls.js b/src/form-builder-controls.js index caadde5d8..281e24ae2 100644 --- a/src/form-builder-controls.js +++ b/src/form-builder-controls.js @@ -38,6 +38,8 @@ import { defaultValueProperty, buttonTypeEvent, tooltipProperty, + LoadingSubmitButtonProperty, + LabelSubmitButtonProperty } from './form-control-common-properties'; export default [ @@ -559,6 +561,8 @@ export default [ label: 'New Submit', variant: 'primary', event: 'submit', + loading: false, + loadingLabel: 'Loading...', defaultSubmit: true, name: null, fieldValue: null, @@ -582,9 +586,11 @@ export default [ helper: 'A variable name is a symbolic name to reference information.', validation: 'regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? `[${match.substr(1)}]`); }, - submit() { - this.$emit('submit', this.value); + submit(eventData, loading = false) { + this.$emit('submit', this.value, loading); }, buildComponent(definition) { if (window.ProcessMaker && window.ProcessMaker.EventBus) { diff --git a/src/mixins/ScreenBase.js b/src/mixins/ScreenBase.js index cb6935cae..07dd17906 100644 --- a/src/mixins/ScreenBase.js +++ b/src/mixins/ScreenBase.js @@ -145,7 +145,7 @@ export default { return 'MUSTACHE: ' + e.message; } }, - async submitForm() { + async submitForm(eventData, loading = false) { await this.validateNow(findRootScreen(this)); this.hasSubmitted(true); if (!this.valid__ || this.disableSubmit__) { @@ -155,7 +155,7 @@ export default { // if the form is not valid the data is not emitted return; } - this.$emit('submit', this.vdata); + this.$emit('submit', this.vdata, loading); }, resetValue(safeDotName, variableName) { this.setValue(safeDotName, null);