diff --git a/ehr/resources/web/ehr/data/StoreCollection.js b/ehr/resources/web/ehr/data/StoreCollection.js
index 4efa982b0..45c893b20 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,24 +219,48 @@ 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);
}
},
validateAll: function(){
+ if(this.fireEvent('beforevalidation', this)===false)
+ return;
this.serverStores.each(function(serverStore){
serverStore.validateRecords(serverStore.getRange(), true);
}, this);
},
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 +497,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 +543,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 +949,4 @@ Ext4.define('EHR.data.StoreCollection', {
s.checkForServerErrorChanges();
}, this);
}
-});
\ No newline at end of file
+});
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 ea403c084..b2ca75f5f 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,55 @@ 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 ehrContext = LABKEY.getModuleContext('ehr');
+ if (ehrContext && !ehrContext.isSubmitEnabledOnValidation) {
+ 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);
+
+ var errorPanel = this.getErrorPanel();
+ if (errorPanel){
+ errorPanel.updateErrorMessages();
+ }
+
+ this.onStoreCollectionValidation(this.storeCollection);
+ },
+
onStoreCollectionValidation: function(sc){
if (!this.hasStoreCollectionLoaded){
return;
@@ -90,6 +143,10 @@ Ext4.define('EHR.panel.DataEntryPanel', {
this.updateDirtyStateMessage();
+ if (this.storeCollection && this.storeCollection.validationRequestsInFlight > 0){
+ return;
+ }
+
var maxSeverity = sc.getMaxErrorSeverity();
if(EHR.debug && maxSeverity)
@@ -509,6 +566,29 @@ 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');
+ }
+
+ return this.validationIndicator;
+ },
+
+ setValidationIndicatorVisible: function(visible){
+ var indicator = this.getValidationIndicator();
+ if (indicator){
+ indicator.setVisible(visible);
+ }
+ },
+
getButtons: function(){
var buttons = [{
xtype: 'container',
@@ -562,6 +642,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;
},
diff --git a/ehr/src/org/labkey/ehr/EHRManager.java b/ehr/src/org/labkey/ehr/EHRManager.java
index 98ca78bc5..b826601ee 100644
--- a/ehr/src/org/labkey/ehr/EHRManager.java
+++ b/ehr/src/org/labkey/ehr/EHRManager.java
@@ -134,6 +134,8 @@ public class EHRManager
public static final String SECURITY_PACKAGE = EHRCompletedInsertPermission.class.getPackage().getName();
+ public static final String EXPERIMENTAL_SUBMIT_ENABLED_ON_VALIDATION = "ehrSubmitEnabledDuringValidation";
+
// Column name constants to reduce hardcoding
private static final class ColumnNames
{
diff --git a/ehr/src/org/labkey/ehr/EHRModule.java b/ehr/src/org/labkey/ehr/EHRModule.java
index e37f59948..0bec890ac 100644
--- a/ehr/src/org/labkey/ehr/EHRModule.java
+++ b/ehr/src/org/labkey/ehr/EHRModule.java
@@ -58,6 +58,8 @@
import org.labkey.api.query.SimpleTableDomainKind;
import org.labkey.api.security.User;
import org.labkey.api.security.roles.RoleManager;
+import org.labkey.api.settings.AppProps;
+import org.labkey.api.settings.OptionalFeatureService;
import org.labkey.api.util.ContextListener;
import org.labkey.api.util.JobRunner;
import org.labkey.api.util.StartupListener;
@@ -289,6 +291,11 @@ public void moduleStartupComplete(ServletContext servletContext)
// Register the Security Escalation Audit Log
AuditLogService.get().registerAuditType(new EHRSecurityEscalatorAuditProvider());
+
+ OptionalFeatureService.get().addExperimentalFeatureFlag(EHRManager.EXPERIMENTAL_SUBMIT_ENABLED_ON_VALIDATION,
+ "Enable 'Submit' buttons during validation",
+ "User can submit form while validation is in progress",
+ false);
}
@Override
@@ -327,6 +334,9 @@ public JSONObject getPageContextJson(ContainerUser context)
Map map = getDefaultPageContextJson(c);
Map ret = new HashMap<>(map);
+ // Expose the experimental 'submit enabled on validation' flag to client-side JavaScript
+ ret.put("isSubmitEnabledOnValidation", AppProps.getInstance().isOptionalFeatureEnabled(EHRManager.EXPERIMENTAL_SUBMIT_ENABLED_ON_VALIDATION));
+
if (map.containsKey(EHRManager.EHRStudyContainerPropName) && map.get(EHRManager.EHRStudyContainerPropName) != null)
{
User u = context.getUser();