Merge pull request #8170 from artoscinote/ma_SCI_9221

Prevent multi-click submissions across the app [SCI-9221]
This commit is contained in:
Martin Artnik 2025-01-21 11:32:03 +01:00 committed by GitHub
commit cfb7b8dc6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 221 additions and 72 deletions

View file

@ -19,11 +19,14 @@
});
function restoreMyModules(url, ids) {
$.post(url, { my_modules_ids: ids, view: 'cards' });
$.post(url, { my_modules_ids: ids, view: 'cards' }, () => {
window.location.reload();
});
}
function initRestoreMyModules() {
$('#module-archive').on('click', '#restoreTask', (e) => {
$('#restoreTask').addClass('disable-click');
e.stopPropagation();
restoreMyModules(e.currentTarget.dataset.url, selectedTasks);
});
@ -46,6 +49,8 @@
});
$('#modal-move-modules').on('click', 'button[data-action="confirm"]', () => {
$('#modal-move-modules button[data-action="confirm"]').addClass('disable-click');
const moveParams = {
to_experiment_id: $('#modal-move-modules').find('.selectpicker').val(),
my_module_ids: selectedTasks
@ -54,6 +59,7 @@
HelperModule.flashAlertMsg(data.message, 'success');
window.location.reload();
}).fail((data) => {
$('#modal-move-modules button[data-action="confirm"]').removeClass('disable-click');
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
$('#modal-move-modules').modal('hide');

View file

@ -731,10 +731,13 @@ var MyModuleRepositories = (function() {
});
UPDATE_REPOSITORY_MODAL.on('click', '.downstream-action', function() {
$(this).addClass('disable-click');
submitUpdateRepositoryRecord({ downstream: true });
}).on('click', '.task-action', function() {
$(this).addClass('disable-click');
submitUpdateRepositoryRecord({ downstream: false });
}).on('hidden.bs.modal', function() {
UPDATE_REPOSITORY_MODAL.find('.downstream-action, .task-action').removeClass('disable-click');
FULL_VIEW_MODAL.focus();
}).on('click', '.next-step', function() {
UPDATE_REPOSITORY_MODAL.find('.next-step, .description-1, .rows-list-container').addClass('hidden');

View file

@ -476,10 +476,16 @@ function importProtocolFromFile(
// Bind on "Import" buttons
$('[data-action="import-current"]')
.off('click')
.on('click', function(ev) { importCurrentProtocol(ev); });
.on('click', function(ev) {
$(this).attr('disabled', true);
importCurrentProtocol(ev);
});
$('[data-action="import-all"]')
.off('click')
.on('click', function(ev) { importAllProtocols(ev); });
.on('click', function(ev) {
$(this).attr('disabled', true);
importAllProtocols(ev);
});
// Bind on navigation buttons
$('[data-action="jump-to-first-protocol"]')
@ -520,6 +526,7 @@ function importProtocolFromFile(
animateSpinner();
importSingleProtocol(currentProtocol, false, function(data) {
importModal.find('[data-role="preview-container"]').html('');
importModal.find('[data-action]').removeAttr('disabled');
afterImportCallback([data]);
});
} else {
@ -529,6 +536,7 @@ function importProtocolFromFile(
animateSpinner(importModal, false);
importModal.find('[data-role="preview-container"]').html('');
importModal.modal('hide');
importModal.find('[data-action]').removeAttr('disabled');
afterImportCallback([data]);
});
}

View file

@ -277,6 +277,7 @@ var protocolsIO = function() {
function initFormSubmits() {
var modal = $('#protocol-preview-modal');
modal.find('button[data-action=import_protocol]').off('click').on('click', function() {
$(this).attr('disabled', true);
var form = modal.find('form');
var hiddenField = form.find('#protocol_protocol_type');
hiddenField.attr('value', $(this).data('import_type'));

View file

@ -61,6 +61,8 @@ var CommentsSidebar = (function() {
function initSendButton() {
$(document).on('click', `${SIDEBAR} .send-comment, ${SIDEBAR} .update-comment`, function() {
$(this).addClass('disable-click');
var requestUrl;
var requestType;
var updateMode = $(SIDEBAR).find('.sidebar-footer').hasClass('update');
@ -94,9 +96,11 @@ var CommentsSidebar = (function() {
}
$('.error-container').empty();
updateCounter();
$(this).removeClass('disable-click');
},
error: (result) => {
$('.error-container').text(result.responseJSON.errors.message);
$(this).removeClass('disable-click');
}
});
});

View file

@ -4,7 +4,7 @@
:class="{ 'sn-action-toolbar--button-overflow': buttonOverflow }"
:style="`width: ${width}px; bottom: ${bottomOffset}px; transform: translateX(${leftOffset}px)`"
:data-e2e="`e2e-CO-actionToolbar`">
<div class="sn-action-toolbar__actions flex gap-4">
<div class="sn-action-toolbar__actions flex gap-4" :class="{ 'disable-click': submitting }">
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
<a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a>
</div>
@ -94,7 +94,8 @@ export default {
width: 0,
bottomOffset: 0,
leftOffset: 0,
buttonOverflow: false
buttonOverflow: false,
submitting: false
};
},
created() {
@ -185,6 +186,8 @@ export default {
break;
case 'request':
event.stopPropagation();
this.submitting = true;
$.ajax({
type: action.request_method,
url: action.path,
@ -194,6 +197,7 @@ export default {
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'danger');
}).always(() => {
this.submitting = false;
if (this.reloadCallback) this.reloadCallback();
});
break;

View file

@ -21,7 +21,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" :disabled="!targetProject" type="submit">
<button class="btn btn-primary" :disabled="submitting || !targetProject" type="submit">
{{ i18n.t('experiments.clone.modal_submit') }}
</button>
</div>
@ -53,10 +53,13 @@ export default {
data() {
return {
targetProject: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.experiment.urls.clone, {
experiment: {
project_id: this.targetProject,
@ -65,6 +68,7 @@ export default {
this.$emit('update');
window.location.replace(response.data.url);
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
},

View file

@ -30,7 +30,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button type="submit" :disabled="!validName" class="btn btn-primary">
<button type="submit" :disabled="submitting || !validName" class="btn btn-primary">
{{ i18n.t('experiments.edit.modal_create') }}
</button>
</div>
@ -55,6 +55,7 @@ export default {
return {
name: this.experiment.name,
description: this.experiment.description,
submitting: false
};
},
computed: {
@ -69,6 +70,8 @@ export default {
mixins: [modalMixin],
methods: {
submit() {
this.submitting = true;
axios.patch(this.experiment.urls.update, {
experiment: {
name: this.name,
@ -76,8 +79,10 @@ export default {
},
}).then((response) => {
this.$emit('update');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
},

View file

@ -20,7 +20,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" :disabled="!targetProject || disableSubmit" type="submit">
<button class="btn btn-primary" :disabled="!targetProject || submitting" type="submit">
{{ i18n.t('experiments.move.modal_submit') }}
</button>
</div>
@ -49,12 +49,13 @@ export default {
data() {
return {
targetProject: null,
disableSubmit: false
submitting: false
};
},
methods: {
async submit() {
this.disableSubmit = true;
this.submitting = true;
await axios.post(this.experiment.movePath, {
project_id: this.targetProject
}).then((response) => {
@ -63,7 +64,7 @@ export default {
}).catch((error) => {
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
this.disableSubmit = false;
this.submitting = false;
},
changeProject(project) {
this.targetProject = project;

View file

@ -29,7 +29,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button type="submit" :disabled="!validName" class="btn btn-primary">
<button type="submit" :disabled="submitting || !validName" class="btn btn-primary">
{{ i18n.t('experiments.new.modal_create') }}
</button>
</div>
@ -54,7 +54,8 @@ export default {
return {
name: '',
description: '',
error: null
error: null,
submitting: false
};
},
computed: {
@ -68,6 +69,8 @@ export default {
mixins: [modalMixin],
methods: {
submit() {
this.submitting = true;
axios.post(this.createUrl, {
experiment: {
name: this.name,
@ -77,6 +80,7 @@ export default {
this.$emit('create');
window.location.replace(response.data.path);
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.name[0];
});
},

View file

@ -99,7 +99,8 @@ export default {
headerName: this.i18n.t('label_templates.index.created_at'),
sortable: true
}
]
],
submitting: false
};
},
computed: {
@ -133,41 +134,70 @@ export default {
},
methods: {
setDefault(action) {
if (this.submitting) return;
this.submitting = true;
axios.post(action.path).then((response) => {
this.reloadingTable = true;
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
},
duplicate(action, rows) {
if (this.submitting) return;
this.submitting = true;
axios.post(action.path, { selected_ids: rows.map((row) => row.id) }).then((response) => {
this.reloadingTable = true;
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
},
createTemplate(action) {
if (this.submitting) return;
this.submitting = true;
axios.post(action.path).then((response) => {
window.location.href = response.data.redirect_url;
});
},
syncFluicsLabels(action) {
if (this.submitting) return;
this.submitting = true;
axios.post(action.path).then((response) => {
this.reloadingTable = true;
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
},
async deleteTemplates(action, rows) {
if (this.submitting) return;
const ok = await this.$refs.deleteModal.show();
if (ok) {
this.submitting = true;
axios.delete(action.path, { data: { selected_ids: rows.map((row) => row.id) } }).then((response) => {
this.reloadingTable = true;
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
}

View file

@ -21,7 +21,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button type="submit" :disabled="!nameIsValid" class="btn btn-primary">
<button type="submit" :disabled="submitting || !nameIsValid" class="btn btn-primary">
{{ i18n.t('experiments.canvas.edit.modal_edit_module.confirm') }}
</button>
</div>
@ -44,7 +44,8 @@ export default {
},
data() {
return {
name: this.my_module.name
name: this.my_module.name,
submitting: false
};
},
computed: {
@ -55,13 +56,17 @@ export default {
mixins: [modalMixin],
methods: {
submit() {
this.submitting = true;
axios.patch(this.my_module.urls.update, {
my_module: {
name: this.name
}
}).then(() => {
this.$emit('update');
this.submitting = false;
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
}

View file

@ -19,7 +19,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" :disabled="!targetExperiment" type="submit">
<button class="btn btn-primary" :disabled="submitting || !targetExperiment" type="submit">
{{ i18n.t('experiments.table.modal_move_modules.confirm') }}
</button>
</div>
@ -47,17 +47,22 @@ export default {
},
data() {
return {
targetExperiment: null
targetExperiment: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.my_module.movePath, {
to_experiment_id: this.targetExperiment
}).then((response) => {
this.$emit('move');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
},

View file

@ -63,7 +63,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button type="submit" :disabled="!validName" class="btn btn-primary">
<button type="submit" :disabled="submitting || !validName" class="btn btn-primary">
{{ i18n.t('experiments.canvas.new_my_module_modal.create') }}
</button>
</div>
@ -100,7 +100,8 @@ export default {
tags: [],
users: [],
allTags: [],
allUsers: []
allUsers: [],
submitting: false
};
},
computed: {
@ -135,6 +136,8 @@ export default {
methods: {
submit() {
this.submitting = true;
axios.post(this.createUrl, {
my_module: {
name: this.name,
@ -144,7 +147,9 @@ export default {
}
}).then(() => {
this.$emit('create');
this.submitting = false;
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
},

View file

@ -34,7 +34,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole">
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole || submitting">
{{ i18n.t('projects.index.modal_edit_project.submit') }}
</button>
</div>
@ -78,11 +78,13 @@ export default {
visible: !this.project.hidden,
defaultRole: this.project.default_public_user_role_id,
error: null,
userRoles: []
userRoles: [],
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.put(this.project.urls.update, {
project: {
name: this.name,
@ -92,7 +94,9 @@ export default {
}).then(() => {
this.error = null;
this.$emit('update');
this.submitting = false;
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.errors.name;
});
},

View file

@ -23,7 +23,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit">
<button class="btn btn-primary" type="submit" :disabled="submitting">
{{ i18n.t('projects.index.modal_edit_folder.submit') }}
</button>
</div>
@ -52,10 +52,12 @@ export default {
return {
name: this.folder.name,
error: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.put(this.folder.urls.update, {
project_folder: {
name: this.name,
@ -63,7 +65,9 @@ export default {
}).then(() => {
this.error = null;
this.$emit('update');
this.submitting = false;
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.errors.name;
});
},

View file

@ -36,7 +36,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit">
<button class="btn btn-primary" type="submit" :disabled="submitting">
{{ i18n.t('projects.index.modal_move_folder.submit') }}
</button>
</div>
@ -66,6 +66,7 @@ export default {
selectedFolderId: null,
foldersTree: [],
query: '',
submitting: false
};
},
components: {
@ -115,6 +116,7 @@ export default {
this.selectedFolderId = folderId;
},
submit() {
this.submitting = true;
axios.post(this.moveToUrl, {
destination_folder_id: this.selectedFolderId || 'root_folder',
movables: this.selectedObjects.map((obj) => (
@ -125,8 +127,10 @@ export default {
)),
}).then((response) => {
this.$emit('move');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
});
},

View file

@ -36,7 +36,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" :disabled="disableSubmit || (visible && !defaultRole)">
<button class="btn btn-primary" type="submit" :disabled="submitting || (visible && !defaultRole)">
{{ i18n.t('projects.index.modal_new_project.create') }}
</button>
</div>
@ -81,13 +81,14 @@ export default {
visible: false,
defaultRole: null,
error: null,
disableSubmit: false,
submitting: false,
userRoles: []
};
},
methods: {
async submit() {
this.disableSubmit = true;
this.submitting = true;
await axios.post(this.createUrl, {
project: {
name: this.name,
@ -101,7 +102,7 @@ export default {
}).catch((error) => {
this.error = error.response.data.name;
});
this.disableSubmit = false;
this.submitting = false;
},
changeRole(role) {
this.defaultRole = role;

View file

@ -23,7 +23,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit">
<button class="btn btn-primary" type="submit" :disabled="submitting">
{{ i18n.t('projects.index.modal_new_project.create') }}
</button>
</div>
@ -55,10 +55,12 @@ export default {
return {
name: '',
error: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.createFolderUrl, {
project_folder: {
name: this.name,
@ -68,8 +70,10 @@ export default {
}).then(() => {
this.error = null;
this.$emit('create');
this.submitting = false;
}).catch((error) => {
this.error = error.response.data.name;
this.submitting = false;
});
},
changeRole(role) {

View file

@ -25,7 +25,7 @@
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancel" data-e2e="e2e-BT-publishProtocol-cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" @click="confirm" data-e2e="e2e-BT-publishProtocol-publish">{{ i18n.t('protocols.publish_modal.publish')}}</button>
<button class="btn btn-primary" @click="confirm" :disabled="submitting" data-e2e="e2e-BT-publishProtocol-publish">{{ i18n.t('protocols.publish_modal.publish')}}</button>
</div>
</div>
</div>
@ -40,6 +40,11 @@ export default {
required: true
}
},
data() {
return {
submitting: false
};
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
@ -51,6 +56,7 @@ export default {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('publish', this.protocol.attributes.version_comment);
this.submitting = true;
},
cancel() {
$(this.$refs.modal).modal('hide');

View file

@ -13,7 +13,8 @@
<div class="modal-footer">
<button v-if="state === 'confirm'" type="button"
class="btn btn-primary"
@click.stop.prevent="confirm">{{ i18n.t('protocols.import_modal.import') }}</button>
@click.stop.prevent="confirm"
:disabled="submitting">{{ i18n.t('protocols.import_modal.import') }}</button>
<button v-else-if="state === 'failed'" type="button"
class="btn btn-primary"
data-dismiss="modal">{{ i18n.t('protocols.import_modal.close') }}</button>
@ -41,7 +42,8 @@ export default {
jobPollInterval: null,
pollCount: 0,
jobId: null,
refreshCallback: null
refreshCallback: null,
submitting: false
};
},
mounted() {
@ -69,6 +71,7 @@ export default {
confirm() {
const formData = new FormData();
Array.from(this.files).forEach((file) => formData.append('files[]', file, file.name));
this.submitting = true;
$.post({
url: this.importUrl, data: formData, processData: false, contentType: false
@ -97,10 +100,12 @@ export default {
break;
case 'done':
this.state = 'done';
this.submitting = false;
clearInterval(this.jobPollInterval);
break;
case 'failed':
this.state = 'failed';
this.submitting = false;
clearInterval(this.jobPollInterval);
break;
}

View file

@ -41,7 +41,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-newProtocolModal-cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole" data-e2e="e2e-BT-newProtocolModal-create">
<button class="btn btn-primary" type="submit" :disabled="submitting || (visible && !defaultRole)" data-e2e="e2e-BT-newProtocolModal-create">
{{ i18n.t('protocols.new_protocol_modal.create_new') }}
</button>
</div>
@ -85,11 +85,14 @@ export default {
visible: false,
defaultRole: null,
userRoles: [],
error: null
error: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.createUrl, {
protocol: {
name: this.name,
@ -99,7 +102,9 @@ export default {
}).then(() => {
this.error = null;
this.$emit('create');
this.submitting = false;
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.error;
});
},

View file

@ -24,7 +24,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-duplicateInventoryModal-cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" data-e2e="e2e-BT-duplicateInventoryModal-create">
<button class="btn btn-primary" type="submit" :disabled="submitting" data-e2e="e2e-BT-duplicateInventoryModal-create">
{{ i18n.t('repositories.index.modal_copy.copy') }}
</button>
</div>
@ -49,11 +49,14 @@ export default {
data() {
return {
name: this.repository.name,
error: null
error: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.repository.urls.duplicate, {
repository: {
name: this.name
@ -61,8 +64,10 @@ export default {
}).then((response) => {
this.error = null;
this.$emit('duplicate');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.name;
});
}

View file

@ -25,7 +25,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-renameInventoryModal-cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" data-e2e="e2e-BT-renameInventoryModal-save">
<button class="btn btn-primary" type="submit" :disabled="submitting" data-e2e="e2e-BT-renameInventoryModal-save">
{{ i18n.t('repositories.index.modal_rename.rename') }}
</button>
</div>
@ -49,11 +49,14 @@ export default {
data() {
return {
name: this.repository.name,
error: null
error: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.put(this.repository.urls.update, {
repository: {
name: this.name
@ -61,7 +64,9 @@ export default {
}).then(() => {
this.error = null;
this.$emit('update');
this.submitting = false;
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.name;
});
}

View file

@ -27,7 +27,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button type="submit" class="btn btn-primary"> {{ i18n.t('repositories.index.modal_export.export') }} </button>
<button type="submit" class="btn btn-primary" :disabled="submitting"> {{ i18n.t('repositories.index.modal_export.export') }} </button>
</div>
</form>
</div>
@ -48,7 +48,8 @@ export default {
mixins: [modalMixin],
data() {
return {
selectedOption: this.exportAction.export_file_type
selectedOption: this.exportAction.export_file_type,
submitting: false
};
},
methods: {
@ -58,10 +59,14 @@ export default {
file_type: this.selectedOption
};
this.submitting = true;
axios.post(this.exportAction.path, payload).then((response) => {
this.$emit('export');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
}

View file

@ -25,7 +25,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-newInventoryModal-cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" data-e2e="e2e-BT-newInventoryModal-create">
<button class="btn btn-primary" type="submit" :disabled="submitting" data-e2e="e2e-BT-newInventoryModal-create">
{{ i18n.t('repositories.index.modal_create.submit') }}
</button>
</div>
@ -50,11 +50,14 @@ export default {
data() {
return {
name: '',
error: null
error: null,
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.createUrl, {
repository: {
name: this.name
@ -62,8 +65,10 @@ export default {
}).then((response) => {
this.error = null;
this.$emit('create');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
this.error = error.response.data.name;
});
}

View file

@ -63,7 +63,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"> {{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" @click="submitPrint">
<button class="btn btn-primary" @click="submitPrint" :disabled="submitting">
{{ i18n.t(`repository_row.modal_print_label.${labelTemplateError ? 'print_anyway' : 'print_label'}`) }}
</button>
</div>
@ -114,7 +114,8 @@ export default {
zebraPrinters: null,
labelTemplateError: null,
labelTemplateCode: null,
fetchedPrintersAndTemplates: false
fetchedPrintersAndTemplates: false,
submitting: false
};
},
components: {
@ -223,6 +224,8 @@ export default {
});
},
submitPrint() {
this.submitting = true;
this.$nextTick(() => {
if (this.selectedPrinter.attributes.type_of === 'zebra') {
this.zebraPrinters.print(
@ -247,8 +250,10 @@ export default {
}, (data) => {
$(this.$refs.modal).modal('hide');
this.$emit('close');
this.submitting = false;
PrintProgressModal.init(data);
}).fail(() => {
this.submitting = false;
HelperModule.flashAlertMsg(this.i18n.t('repository_row.modal_print_label.general_error'), 'danger');
});
}

View file

@ -1,5 +1,5 @@
<template>
<div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]" data-e2e="e2e-CO-actionToolbar">
<div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]" :class="{ 'disable-click': submitting }" data-e2e="e2e-CO-actionToolbar">
<div class="flex gap-4 items-center h-full">
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
<a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a>
@ -42,6 +42,7 @@ export default {
reloadCallback: null,
loaded: false,
loading: true,
submitting: false
};
},
watch: {
@ -72,8 +73,8 @@ export default {
switch (action.type) {
case 'emit':
event.preventDefault();
this.submitting = true;
this.$emit('toolbar:action', action);
// do nothing, this is handled by legacy code based on the button class
break;
case 'modal':
// do nothihg, boostrap modal handled by data-toggle="modal" and data-target

View file

@ -180,10 +180,11 @@ export default {
type: Object
}
},
mounted() {
if (this.searchValue.length > 0) {
this.openSearch();
}
data() {
return {
showSearch: false,
showColumnsModal: false
};
},
components: {
MenuDropdown,
@ -239,12 +240,6 @@ export default {
return this.columnDefs.filter((column) => column.sortable);
}
},
data() {
return {
showSearch: false,
showColumnsModal: false
};
},
watch: {
searchValue() {
if (this.searchValue.length > 0) {
@ -252,6 +247,11 @@ export default {
}
}
},
mounted() {
if (this.searchValue.length > 0) {
this.openSearch();
}
},
methods: {
openSearch() {
this.showSearch = true;

View file

@ -40,7 +40,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" type="submit" :disabled="!validObject">
<button class="btn btn-primary" type="submit" :disabled="submitting || !validObject">
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_action`) }}
</button>
</div>
@ -102,7 +102,7 @@ export default {
rowId: this.selectedRow,
containerId: this.selectedContainer,
position: this.selectedPosition,
saving: false
submitting: false
};
},
components: {
@ -112,21 +112,21 @@ export default {
},
methods: {
submit() {
if (this.saving) {
if (this.submitting) {
return;
}
this.saving = true;
this.submitting = true;
axios.post(this.actionUrl, {
repository_row_id: this.rowId,
metadata: { position: this.position?.map((pos) => parseInt(pos, 10)) }
}).then(() => {
this.$emit('close');
this.saving = false;
this.submitting = false;
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.errors.join(', '), 'danger');
this.saving = false;
});
}
}

View file

@ -43,7 +43,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" :disabled="!validContainer" type="submit">
<button class="btn btn-primary" :disabled="submitting || !validContainer" type="submit">
{{ i18n.t('general.move') }}
</button>
</div>
@ -81,17 +81,22 @@ export default {
storageLocationsTree: [],
teamId: null,
query: '',
moveMode: 'locations'
moveMode: 'locations',
submitting: false
};
},
methods: {
submit() {
this.submitting = true;
axios.post(this.moveToUrl, {
destination_storage_location_id: this.selectedStorageLocationId || 'root_storage_location'
}).then((response) => {
this.$emit('move');
this.submitting = false;
HelperModule.flashAlertMsg(response.data.message, 'success');
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
}

View file

@ -88,7 +88,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" :disabled="!validObject" type="submit">
<button class="btn btn-primary" :disabled="submitting || !validObject" type="submit">
{{ mode == 'create' ? i18n.t('general.create') : i18n.t('general.save') }}
</button>
</div>
@ -127,7 +127,7 @@ export default {
},
attachedImage: null,
imageError: false,
savingLocaiton: false,
submitting: false,
errors: {}
};
},
@ -187,11 +187,11 @@ export default {
// Smart annotation fix
this.object.description = $(this.$refs.description).val();
if (this.savingLocaiton) {
if (this.submitting) {
return;
}
this.savingLocaiton = true;
this.submitting = true;
const params = {
name: this.object.name,
@ -214,22 +214,22 @@ export default {
.then(() => {
this.$emit('tableReloaded');
HelperModule.flashAlertMsg(this.i18n.t(`storage_locations.index.edit_modal.success_message.edit_${this.editModalMode}`, { name: this.object.name }), 'success');
this.savingLocaiton = false;
this.submitting = false;
this.close();
}).catch((error) => {
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
this.savingLocaiton = false;
this.submitting = false;
});
} else {
axios.post(this.createUrl, params)
.then(() => {
this.$emit('tableReloaded');
this.submitting = false;
HelperModule.flashAlertMsg(this.i18n.t(`storage_locations.index.edit_modal.success_message.create_${this.editModalMode}`, { name: this.object.name }), 'success');
this.savingLocaiton = false;
this.close();
}).catch((error) => {
this.submitting = false;
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
this.savingLocaiton = false;
});
}
},