Skip to content

Commit 7a9b348

Browse files
Merge 26.6 to develop
2 parents 26ae16d + 6f09282 commit 7a9b348

5 files changed

Lines changed: 182 additions & 22 deletions

File tree

ehr/resources/web/ehr/DataEntryUtils.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,17 @@ EHR.DataEntryUtils = new function(){
801801
schemaName: 'ehr',
802802
queryName: 'observation_types',
803803
columns: 'value,editorconfig',
804-
autoLoad: true
804+
autoLoad: true,
805+
listeners: {
806+
// unlike EHR.data.DataEntryClientStore, LABKEY.ext4.data.Store has no hasLoaded();
807+
// consumers need to distinguish a pending initial load from one that returned no rows
808+
load: {
809+
single: true,
810+
fn: function(store){
811+
store.hasLoadedOnce = true;
812+
}
813+
}
814+
}
805815
});
806816

807817
return EHR._observationTypesStore;

ehr/resources/web/ehr/ehr_ext4_dataEntry.lib.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<script path="ehr/plugin/ResizableTextArea.js"/>
2222
<script path="ehr/plugin/RowEditor.js"/>
2323
<script path="ehr/plugin/CollapsibleDataEntryPanel.js"/>
24+
<script path="ehr/plugin/ClinicalObservationsBulkEdit.js"/>
2425

2526
<!--fields-->
2627
<script path="ehr/form/field/AnimalField.js"/>

ehr/resources/web/ehr/grid/ClinicalObservationGridPanel.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ Ext4.define('EHR.grid.ClinicalObservationGridPanel', {
1313
initComponent: function(){
1414
this.observationTypesStore = EHR.DataEntryUtils.getObservationTypesStore();
1515

16+
// Make the bulk edit panel for this grid offer the same category-dependent Observation/Score
17+
// editor as the grid's cell editor. formConfig is threaded unchanged to EHR.panel.BulkEditPanel,
18+
// which instantiates any plugins listed here.
19+
if (this.formConfig){
20+
this.formConfig.bulkEditPlugins = Ext4.Array.from(this.formConfig.bulkEditPlugins || []);
21+
if (!Ext4.Array.contains(this.formConfig.bulkEditPlugins, 'clinicalobservationsbulkedit')){
22+
this.formConfig.bulkEditPlugins.push('clinicalobservationsbulkedit');
23+
}
24+
}
25+
1626
this.callParent(arguments);
1727
},
1828

ehr/resources/web/ehr/panel/BulkEditPanel.js

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ Ext4.define('EHR.panel.BulkEditPanel', {
4646

4747
this.callParent(arguments);
4848

49+
// Allow a form section to contribute panel plugins to its bulk edit panel via formConfig.bulkEditPlugins.
50+
// For example, the clinical observations grid uses this to make the Observation/Score editor depend on
51+
// the selected Category (see EHR.plugin.ClinicalObservationsBulkEdit), keeping this panel generic. This
52+
// runs after callParent because EHR.form.Panel resets this.plugins for collapsible forms. By this point
53+
// the panel's own plugins have already been constructed, so we construct ours explicitly and append them;
54+
// the component constructor then calls init() on every entry in this.plugins.
55+
var extraPlugins = this.formConfig && this.formConfig.bulkEditPlugins;
56+
if (extraPlugins){
57+
this.plugins = this.plugins || [];
58+
Ext4.Array.forEach(Ext4.Array.from(extraPlugins), function(p){
59+
this.plugins.push(this.constructPlugin(p));
60+
}, this);
61+
}
62+
4963
this.addEvents('bulkeditcomplete');
5064
},
5165

@@ -117,34 +131,38 @@ Ext4.define('EHR.panel.BulkEditPanel', {
117131

118132
item = Ext4.widget(item);
119133

120-
item.on('render', function(field){
121-
if (field.labelEl){
122-
Ext4.QuickTips.register({
123-
target: field.labelEl,
124-
text: 'Click to toggle'
125-
});
126-
127-
field.labelEl.on('click', function(){
128-
if (field.originalDisabled){
129-
Ext4.Msg.alert('Error', 'This field cannot be enabled');
130-
return;
131-
}
132-
133-
field.setDisabled(!field.isDisabled());
134-
Ext4.defer(field.focus, 100, field);
135-
}, this);
136-
}
137-
else {
138-
console.log(field);
139-
}
140-
}, this);
134+
this.addLabelToggle(item);
141135

142136
newItems.push(item);
143137
}, this);
144138

145139
return newItems;
146140
},
147141

142+
addLabelToggle: function(field){
143+
field.on('render', function(field){
144+
if (field.labelEl){
145+
Ext4.QuickTips.register({
146+
target: field.labelEl,
147+
text: 'Click to toggle'
148+
});
149+
150+
field.labelEl.on('click', function(){
151+
if (field.originalDisabled){
152+
Ext4.Msg.alert('Error', 'This field cannot be enabled');
153+
return;
154+
}
155+
156+
field.setDisabled(!field.isDisabled());
157+
Ext4.defer(field.focus, 100, field);
158+
}, this);
159+
}
160+
else {
161+
console.log(field);
162+
}
163+
}, this);
164+
},
165+
148166
onSubmit: function(){
149167
if (!this.suppressConfirmMsg){
150168
var values = this.getForm().getFieldValues();
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright (c) 2026 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
/**
7+
* A plugin for EHR.panel.BulkEditPanel that makes the Observation/Score field's editor depend on the
8+
* selected Category, mirroring EHR.grid.plugin.ClinicalObservationsCellEditing so that the data type
9+
* and options offered in the bulk edit dialog match the clinical observations grid.
10+
*
11+
* It is attached to the bulk edit panel via the clinical observations grid's formConfig.bulkEditPlugins
12+
* (see EHR.grid.ClinicalObservationGridPanel), so the generic bulk edit panel stays free of any
13+
* observation-specific logic.
14+
*/
15+
Ext4.define('EHR.plugin.ClinicalObservationsBulkEdit', {
16+
extend: 'Ext.AbstractPlugin',
17+
alias: 'plugin.clinicalobservationsbulkedit',
18+
19+
init: function(panel){
20+
this.panel = panel;
21+
this.observationTypesStore = EHR.DataEntryUtils.getObservationTypesStore();
22+
panel.on('afterrender', this.setupObservationDependency, this, {single: true});
23+
24+
this.callParent(arguments);
25+
},
26+
27+
setupObservationDependency: function(){
28+
var panel = this.panel;
29+
var categoryField = panel.down('[name=category]');
30+
var observationField = panel.down('[name=observation]');
31+
if (!categoryField || !observationField){
32+
return;
33+
}
34+
35+
// Capture the base config of the original Observation/Score field so it can be rebuilt with a
36+
// category-specific editor (see reconfigureObservationField).
37+
this.observationFieldBaseCfg = {
38+
name: observationField.name,
39+
fieldLabel: observationField.fieldLabel,
40+
labelWidth: observationField.labelWidth,
41+
width: observationField.width,
42+
// honor metadata that marks the field as never-enableable (see BulkEditPanel.getFieldConfigs)
43+
originalDisabled: observationField.originalDisabled
44+
};
45+
46+
categoryField.on('change', function(field, newValue){
47+
this.reconfigureObservationField(newValue);
48+
}, this);
49+
50+
// Initialize based on any pre-populated category value (e.g. when all selected records share a category)
51+
var initialCategory = categoryField.getValue();
52+
if (initialCategory){
53+
this.reconfigureObservationField(initialCategory);
54+
}
55+
},
56+
57+
reconfigureObservationField: function(category){
58+
var store = this.observationTypesStore;
59+
// hasLoadedOnce (set in EHR.DataEntryUtils.getObservationTypesStore) distinguishes a pending
60+
// initial load from one that already returned no rows; for the latter we fall through to the
61+
// textfield default rather than waiting on a load event that will never fire
62+
if (!store.hasLoadedOnce){
63+
store.on('load', function(){
64+
// the dialog may have been closed before the store finished loading
65+
if (this.panel && !this.panel.isDestroyed){
66+
this.reconfigureObservationField(category);
67+
}
68+
}, this, {single: true});
69+
return;
70+
}
71+
72+
var panel = this.panel;
73+
var observationField = panel.down('[name=observation]');
74+
if (!observationField){
75+
return;
76+
}
77+
78+
//note: we proceed even if the category cannot be found, to support records saved under a no-longer-supported category
79+
var rec = category ? store.findRecord('value', category) : null;
80+
var rawEditorConfig = (rec && rec.get('editorconfig')) || null;
81+
82+
// the category combo fires change on every keystroke, so skip the rebuild when the resolved
83+
// editor is unchanged; this avoids churn and preserves any value the user already entered
84+
if (this.appliedEditorConfig !== undefined && this.appliedEditorConfig === rawEditorConfig){
85+
return;
86+
}
87+
88+
var container = observationField.ownerCt;
89+
var index = container.items.indexOf(observationField);
90+
//preserve the user's enable/disable toggle state across category changes
91+
var wasDisabled = observationField.isDisabled();
92+
93+
var editorConfig = rawEditorConfig ? Ext4.decode(rawEditorConfig) : null;
94+
editorConfig = editorConfig || {
95+
xtype: 'textfield'
96+
};
97+
98+
var cfg = Ext4.apply({}, editorConfig);
99+
Ext4.apply(cfg, this.observationFieldBaseCfg);
100+
delete cfg.value;
101+
delete cfg.defaultValue;
102+
cfg.allowBlank = true;
103+
cfg.disabled = wasDisabled;
104+
105+
cfg = EHR.DataEntryUtils.ensureLookupPlugin(cfg, false);
106+
107+
container.remove(observationField, true);
108+
var newField = Ext4.widget(cfg);
109+
panel.addLabelToggle(newField);
110+
container.insert(index, newField);
111+
112+
// the databind plugin only registers listeners on the fields present at init, so register the
113+
// replacement explicitly to keep record syncing and validation consistent with the original field
114+
var databind = panel.getPlugin('ehr-databind');
115+
if (databind){
116+
databind.addFieldListener(newField);
117+
}
118+
119+
this.appliedEditorConfig = rawEditorConfig;
120+
}
121+
});

0 commit comments

Comments
 (0)