From 082ab6e6e350a6ab93477e07a28742903f57c30e Mon Sep 17 00:00:00 2001 From: Binal Patel Date: Mon, 30 Mar 2026 13:33:32 -0700 Subject: [PATCH 1/2] Disable the 'Submit' buttons until validation is completed. Add an indicator while validation is in progress. --- ehr/resources/web/ehr/data/StoreCollection.js | 66 +++++++++++++++++-- ehr/resources/web/ehr/panel/DataEntryPanel.js | 66 +++++++++++++++++++ 2 files changed, 126 insertions(+), 6 deletions(-) diff --git a/ehr/resources/web/ehr/data/StoreCollection.js b/ehr/resources/web/ehr/data/StoreCollection.js index 4efa982b0..0a6c13035 100644 --- a/ehr/resources/web/ehr/data/StoreCollection.js +++ b/ehr/resources/web/ehr/data/StoreCollection.js @@ -12,6 +12,7 @@ Ext4.define('EHR.data.StoreCollection', { serverStores: null, hasLoaded: false, //will be set true after initial load clientDataChangeBuffer: 150, + validationRequestsInFlight: 0, ignoredClientEvents: {}, constructor: function(){ @@ -20,7 +21,7 @@ Ext4.define('EHR.data.StoreCollection', { this.serverStores = Ext4.create('Ext.util.MixedCollection', false, this.getKey); this.callParent(arguments); - this.addEvents('commitcomplete', 'commitexception', 'validation', 'initialload', 'load', 'clientdatachanged', 'serverdatachanged'); + this.addEvents('commitcomplete', 'commitexception', 'beforevalidation', 'validationstart', 'validation', 'validationcomplete', 'initialload', 'load', 'clientdatachanged', 'serverdatachanged'); this.on('clientdatachanged', this.onClientDataChanged, this, {buffer: this.clientDataChangeBuffer}); }, @@ -218,7 +219,7 @@ Ext4.define('EHR.data.StoreCollection', { } else { - //this really isnt the right event to fire, but it will force a recalulation of buttons on the panel + //this really isn't the right event to fire, but it will force a recalculation of buttons on the panel this.fireEvent('validation', this); } }, @@ -230,12 +231,34 @@ Ext4.define('EHR.data.StoreCollection', { }, validateRecords: function(recordMap){ + if(this.fireEvent('beforevalidation', this)===false) + return; for (var serverStoreId in recordMap){ var serverStore = this.serverStores.get(serverStoreId); serverStore.validateRecords(Ext4.Object.getValues(recordMap[serverStoreId]), true); } }, + onValidationRequestStart: function(){ + this.validationRequestsInFlight++; + + if (this.validationRequestsInFlight === 1){ + this.fireEvent('validationstart', this); + } + }, + + onValidationRequestComplete: function(){ + if (!this.validationRequestsInFlight){ + return; + } + + this.validationRequestsInFlight--; + + if (this.validationRequestsInFlight === 0){ + this.fireEvent('validationcomplete', this); + } + }, + serverToClientDataMap: null, getServerToClientDataMap: function(){ @@ -472,11 +495,31 @@ Ext4.define('EHR.data.StoreCollection', { if (EHR.debug) console.log(commands); + var success = this.getOnCommitSuccess(recordsArr, validateOnly, retainErrors); + var failure = this.getOnCommitFailure(recordsArr, validateOnly); var cfg = { url : LABKEY.ActionURL.buildURL('query', 'saveRows', this.containerPath), method : 'POST', - success: this.getOnCommitSuccess(recordsArr, validateOnly, retainErrors), - failure: this.getOnCommitFailure(recordsArr, validateOnly), + success: function(response, options){ + try { + success.call(this, response, options); + } + finally { + if (validateOnly){ + this.onValidationRequestComplete(); + } + } + }, + failure: function(response, options){ + try { + failure.call(this, response, options); + } + finally { + if (validateOnly){ + this.onValidationRequestComplete(); + } + } + }, scope: this, timeout: 5000000, //a little extreme? transacted: true, @@ -498,9 +541,20 @@ Ext4.define('EHR.data.StoreCollection', { if (validateOnly){ cfg.jsonData.validateOnly = true; cfg.jsonData.extraContext.isValidateOnly = true; + this.onValidationRequestStart(); + } + + var request; + try { + request = LABKEY.Ajax.request(cfg); } + catch (e){ + if (validateOnly){ + this.onValidationRequestComplete(); + } - var request = LABKEY.Ajax.request(cfg); + throw e; + } Ext4.Array.forEach(recordsArr, function(command){ Ext4.Array.forEach(command, function(rec){ @@ -893,4 +947,4 @@ Ext4.define('EHR.data.StoreCollection', { s.checkForServerErrorChanges(); }, this); } -}); \ No newline at end of file +}); diff --git a/ehr/resources/web/ehr/panel/DataEntryPanel.js b/ehr/resources/web/ehr/panel/DataEntryPanel.js index ea403c084..c6cfaf1f0 100644 --- a/ehr/resources/web/ehr/panel/DataEntryPanel.js +++ b/ehr/resources/web/ehr/panel/DataEntryPanel.js @@ -12,6 +12,7 @@ Ext4.define('EHR.panel.DataEntryPanel', { storeCollection: null, hideErrorPanel: false, useSectionBorder: true, + validationInProgress: false, layout: 'anchor', border: false, @@ -33,7 +34,10 @@ Ext4.define('EHR.panel.DataEntryPanel', { this.storeCollection.on('initialload', this.onStoreCollectionInitialLoad, this); this.storeCollection.on('commitcomplete', this.onStoreCollectionCommitComplete, this); this.storeCollection.on('validation', this.onStoreCollectionValidation, this); + this.storeCollection.on('validationstart', this.onValidationStart, this); + this.storeCollection.on('validationcomplete', this.onValidationComplete, this); this.storeCollection.on('beforecommit', this.onStoreCollectionBeforeCommit, this); + this.storeCollection.on('beforevalidation', this.onBeforeValidation, this); this.storeCollection.on('commitexception', this.onStoreCollectionCommitException, this); //this.storeCollection.on('serverdatachanged', this.onStoreCollectionServerDataChanged, this); @@ -83,6 +87,45 @@ Ext4.define('EHR.panel.DataEntryPanel', { } }, + onBeforeValidation: function(sc){ + function processItem(item) { + if(item.disableOn) { + item.setDisabled(true); + if (item.setTooltip) + item.setTooltip('Disabled waiting on validation. Select "More Actions" -> "Re-Validate" if this is not clearing.'); + } + + if (item.menu) { + item.menu.items.each(function (menuItem) { + processItem(menuItem); + }, this); + } + } + + var btns = this.getToolbarItems(); + if (btns){ + Ext4.Array.forEach(btns, function(toolbar){ + toolbar.items.each(function(item){ + processItem(item); + }, this); + }, this); + } + }, + + onValidationStart: function(){ + if (!this.hasStoreCollectionLoaded){ + return; + } + + this.validationInProgress = true; + this.setValidationIndicatorVisible(true); + }, + + onValidationComplete: function(){ + this.validationInProgress = false; + this.setValidationIndicatorVisible(false); + }, + onStoreCollectionValidation: function(sc){ if (!this.hasStoreCollectionLoaded){ return; @@ -509,6 +552,21 @@ Ext4.define('EHR.panel.DataEntryPanel', { return this.dirtyStateArea; }, + getValidationIndicator: function(){ + if (!this.validationIndicator || this.validationIndicator.isDestroyed){ + this.validationIndicator = this.down('#validationIndicator'); + } + + return this.validationIndicator; + }, + + setValidationIndicatorVisible: function(visible){ + var indicator = this.getValidationIndicator(); + if (indicator){ + indicator.setVisible(visible); + } + }, + getButtons: function(){ var buttons = [{ xtype: 'container', @@ -562,6 +620,14 @@ Ext4.define('EHR.panel.DataEntryPanel', { } } + buttons.push({ + xtype: 'container', + itemId: 'validationIndicator', + hidden: !this.validationInProgress, + html: ' Validating...', + style: 'padding-left: 8px; line-height: 24px;' + }); + return buttons; }, From 4895f37cbd2e25d38c1b53eee09abeeecd10e107 Mon Sep 17 00:00:00 2001 From: Binal Patel Date: Wed, 1 Apr 2026 10:32:21 -0700 Subject: [PATCH 2/2] Align the button and error-panel timing. --- .../web/ehr/panel/DataEntryErrorPanel.js | 1 + ehr/resources/web/ehr/panel/DataEntryPanel.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ehr/resources/web/ehr/panel/DataEntryErrorPanel.js b/ehr/resources/web/ehr/panel/DataEntryErrorPanel.js index ba21677d1..c7eff1426 100644 --- a/ehr/resources/web/ehr/panel/DataEntryErrorPanel.js +++ b/ehr/resources/web/ehr/panel/DataEntryErrorPanel.js @@ -19,6 +19,7 @@ Ext4.define('EHR.panel.DataEntryErrorPanel', { this.callParent(arguments); this.mon(this.storeCollection, 'validation', this.updateErrorMessages, this, {buffer: 1000}); + this.mon(this.storeCollection, 'validationcomplete', this.updateErrorMessages, this, {buffer: 50}); this.mon(this.storeCollection, 'commitcomplete', this.updateErrorMessages, this, {buffer: 200}); this.mon(this.storeCollection, 'commitexception', this.updateErrorMessages, this, {buffer: 200}); }, diff --git a/ehr/resources/web/ehr/panel/DataEntryPanel.js b/ehr/resources/web/ehr/panel/DataEntryPanel.js index c6cfaf1f0..fb51eb6d3 100644 --- a/ehr/resources/web/ehr/panel/DataEntryPanel.js +++ b/ehr/resources/web/ehr/panel/DataEntryPanel.js @@ -124,6 +124,13 @@ Ext4.define('EHR.panel.DataEntryPanel', { onValidationComplete: function(){ this.validationInProgress = false; this.setValidationIndicatorVisible(false); + + var errorPanel = this.getErrorPanel(); + if (errorPanel){ + errorPanel.updateErrorMessages(); + } + + this.onStoreCollectionValidation(this.storeCollection); }, onStoreCollectionValidation: function(sc){ @@ -133,6 +140,10 @@ Ext4.define('EHR.panel.DataEntryPanel', { this.updateDirtyStateMessage(); + if (this.storeCollection && this.storeCollection.validationRequestsInFlight > 0){ + return; + } + var maxSeverity = sc.getMaxErrorSeverity(); if(EHR.debug && maxSeverity) @@ -552,6 +563,14 @@ Ext4.define('EHR.panel.DataEntryPanel', { return this.dirtyStateArea; }, + getErrorPanel: function(){ + if (!this.errorPanel || this.errorPanel.isDestroyed){ + this.errorPanel = this.down('#errorPanel'); + } + + return this.errorPanel; + }, + getValidationIndicator: function(){ if (!this.validationIndicator || this.validationIndicator.isDestroyed){ this.validationIndicator = this.down('#validationIndicator');