@aehrc/smart-forms-renderer 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/FormComponents/BooleanItem/BooleanField.d.ts +7 -3
- package/lib/components/FormComponents/BooleanItem/BooleanField.js +26 -4
- package/lib/components/FormComponents/BooleanItem/BooleanField.js.map +1 -1
- package/lib/components/FormComponents/BooleanItem/BooleanItem.js +19 -10
- package/lib/components/FormComponents/BooleanItem/BooleanItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.js +5 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js +3 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.d.ts +2 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.js +4 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js +3 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.js +5 -6
- package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js +4 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +3 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +4 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.d.ts +0 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +3 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/GroupItem/GroupItem.d.ts +2 -1
- package/lib/components/FormComponents/GroupItem/GroupItem.js +3 -3
- package/lib/components/FormComponents/GroupItem/GroupItem.js.map +1 -1
- package/lib/components/FormComponents/GroupItem/GroupItemSwitcher.d.ts +2 -1
- package/lib/components/FormComponents/GroupItem/GroupItemSwitcher.js +4 -4
- package/lib/components/FormComponents/GroupItem/GroupItemSwitcher.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.d.ts +0 -2
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.js +6 -4
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.d.ts +0 -2
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.js +3 -3
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceItemSwitcher.js +2 -4
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceItemSwitcher.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.d.ts +0 -2
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.js +4 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.d.ts +0 -2
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.js +2 -2
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/RepeatGroup/RepeatGroup.d.ts +2 -2
- package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js +8 -2
- package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js.map +1 -1
- package/lib/components/FormComponents/RepeatGroup/RepeatGroupItem.d.ts +1 -0
- package/lib/components/FormComponents/RepeatGroup/RepeatGroupItem.js +2 -2
- package/lib/components/FormComponents/RepeatGroup/RepeatGroupItem.js.map +1 -1
- package/lib/components/FormComponents/SingleItem/SingleItem.d.ts +2 -2
- package/lib/components/FormComponents/SingleItem/SingleItem.js +11 -6
- package/lib/components/FormComponents/SingleItem/SingleItem.js.map +1 -1
- package/lib/hooks/useHidden.d.ts +1 -1
- package/lib/hooks/useHidden.js +3 -2
- package/lib/hooks/useHidden.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/interfaces/enableWhen.interface.d.ts +20 -5
- package/lib/interfaces/index.d.ts +1 -0
- package/lib/interfaces/index.js +2 -0
- package/lib/interfaces/index.js.map +1 -0
- package/lib/interfaces/questionnaireStore.interface.d.ts +2 -2
- package/lib/interfaces/renderProps.interface.d.ts +4 -0
- package/lib/stores/questionnaireStore.d.ts +5 -3
- package/lib/stores/questionnaireStore.js +12 -5
- package/lib/stores/questionnaireStore.js.map +1 -1
- package/lib/utils/choice.d.ts +1 -1
- package/lib/utils/choice.js +1 -1
- package/lib/utils/choice.js.map +1 -1
- package/lib/utils/enableWhen.d.ts +11 -4
- package/lib/utils/enableWhen.js +130 -53
- package/lib/utils/enableWhen.js.map +1 -1
- package/lib/utils/enableWhenExpression.d.ts +1 -1
- package/lib/utils/enableWhenExpression.js +26 -7
- package/lib/utils/enableWhenExpression.js.map +1 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/misc.d.ts +5 -0
- package/lib/utils/misc.js +116 -0
- package/lib/utils/misc.js.map +1 -0
- package/lib/utils/qItem.d.ts +2 -1
- package/lib/utils/qItem.js +19 -7
- package/lib/utils/qItem.js.map +1 -1
- package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js +1 -1
- package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js.map +1 -1
- package/lib/utils/questionnaireStoreUtils/extractOtherExtensions.d.ts +13 -3
- package/lib/utils/questionnaireStoreUtils/extractOtherExtensions.js +81 -18
- package/lib/utils/questionnaireStoreUtils/extractOtherExtensions.js.map +1 -1
- package/lib/utils/tabs.d.ts +1 -1
- package/lib/utils/tabs.js +5 -3
- package/lib/utils/tabs.js.map +1 -1
- package/package.json +1 -1
- package/src/components/FormComponents/BooleanItem/BooleanField.tsx +53 -17
- package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +47 -13
- package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.tsx +4 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx +0 -5
- package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.tsx +6 -3
- package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx +2 -5
- package/src/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.tsx +1 -6
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +4 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +1 -5
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +4 -3
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +1 -5
- package/src/components/FormComponents/GroupItem/GroupItem.tsx +8 -2
- package/src/components/FormComponents/GroupItem/GroupItemSwitcher.tsx +10 -2
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.tsx +3 -2
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.tsx +0 -5
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceItemSwitcher.tsx +0 -5
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.tsx +3 -2
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.tsx +1 -4
- package/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx +13 -2
- package/src/components/FormComponents/RepeatGroup/RepeatGroupItem.tsx +4 -0
- package/src/components/FormComponents/SingleItem/SingleItem.tsx +19 -6
- package/src/hooks/useHidden.ts +3 -2
- package/src/index.ts +1 -0
- package/src/interfaces/enableWhen.interface.ts +24 -5
- package/src/interfaces/index.ts +8 -0
- package/src/interfaces/questionnaireStore.interface.ts +2 -2
- package/src/interfaces/renderProps.interface.ts +4 -0
- package/src/stores/questionnaireStore.ts +45 -8
- package/src/utils/choice.ts +4 -2
- package/src/utils/enableWhen.ts +194 -55
- package/src/utils/enableWhenExpression.ts +35 -8
- package/src/utils/index.ts +5 -0
- package/src/utils/misc.ts +160 -0
- package/src/utils/qItem.ts +31 -8
- package/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts +1 -1
- package/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts +122 -25
- package/src/utils/tabs.ts +7 -4
package/src/utils/enableWhen.ts
CHANGED
|
@@ -23,7 +23,11 @@ import type {
|
|
|
23
23
|
QuestionnaireResponseItemAnswer
|
|
24
24
|
} from 'fhir/r4';
|
|
25
25
|
import cloneDeep from 'lodash.clonedeep';
|
|
26
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
EnableWhenItems,
|
|
28
|
+
EnableWhenRepeatItemProperties,
|
|
29
|
+
EnableWhenSingleItemProperties
|
|
30
|
+
} from '../interfaces';
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Create a linkedQuestionsMap that contains linked items of enableWhen items
|
|
@@ -35,18 +39,23 @@ import type { EnableWhenItemProperties, EnableWhenItems } from '../interfaces/en
|
|
|
35
39
|
export function createEnableWhenLinkedQuestions(enableWhenItems: EnableWhenItems) {
|
|
36
40
|
const linkedQuestionsMap: Record<string, string[]> = {};
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
enableWhenItems[linkId].linked.forEach((linkedItem) => {
|
|
40
|
-
const linkQId = linkedItem.enableWhen.question;
|
|
41
|
-
if (!linkedQuestionsMap[linkQId]) {
|
|
42
|
-
linkedQuestionsMap[linkQId] = [];
|
|
43
|
-
}
|
|
42
|
+
const { singleItems, repeatItems } = enableWhenItems;
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
for (const items of [singleItems, repeatItems]) {
|
|
45
|
+
for (const linkId in items) {
|
|
46
|
+
items[linkId].linked.forEach((linkedItem) => {
|
|
47
|
+
const linkQId = linkedItem.enableWhen.question;
|
|
48
|
+
if (!linkedQuestionsMap[linkQId]) {
|
|
49
|
+
linkedQuestionsMap[linkQId] = [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!linkedQuestionsMap[linkQId].includes(linkId)) {
|
|
53
|
+
linkedQuestionsMap[linkQId].push(linkId);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
49
57
|
}
|
|
58
|
+
|
|
50
59
|
return linkedQuestionsMap;
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -117,11 +126,11 @@ function answerOperatorSwitcher(
|
|
|
117
126
|
value: boolean | string | number | Quantity | QuestionnaireResponseItemAnswer,
|
|
118
127
|
operator: QuestionnaireItemEnableWhen['operator']
|
|
119
128
|
): boolean {
|
|
120
|
-
// FIXME runs even when the linked textbox is not changed
|
|
121
129
|
switch (operator) {
|
|
122
|
-
case 'exists':
|
|
123
|
-
|
|
124
|
-
return
|
|
130
|
+
case 'exists': {
|
|
131
|
+
const answerExists = typeof value === 'object' && Object.keys(value).length !== 0;
|
|
132
|
+
return answerExists === expected;
|
|
133
|
+
}
|
|
125
134
|
case '=':
|
|
126
135
|
return value === expected;
|
|
127
136
|
case '!=':
|
|
@@ -139,6 +148,36 @@ function answerOperatorSwitcher(
|
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
export function mutateRepeatEnableWhenItemInstances(
|
|
152
|
+
items: EnableWhenItems,
|
|
153
|
+
parentRepeatGroupLinkId: string,
|
|
154
|
+
parentRepeatGroupIndex: number,
|
|
155
|
+
actionType: 'add' | 'remove'
|
|
156
|
+
): EnableWhenItems {
|
|
157
|
+
const { repeatItems } = items;
|
|
158
|
+
|
|
159
|
+
for (const linkId in repeatItems) {
|
|
160
|
+
for (const linkedItem of repeatItems[linkId].linked) {
|
|
161
|
+
if (linkedItem.parentLinkId !== parentRepeatGroupLinkId) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (actionType === 'add') {
|
|
166
|
+
linkedItem.answers.splice(parentRepeatGroupIndex, 0);
|
|
167
|
+
repeatItems[linkId].enabledIndexes[parentRepeatGroupIndex] = checkItemIsEnabledRepeat(
|
|
168
|
+
repeatItems[linkId],
|
|
169
|
+
parentRepeatGroupIndex
|
|
170
|
+
);
|
|
171
|
+
} else if (actionType === 'remove') {
|
|
172
|
+
linkedItem.answers.splice(parentRepeatGroupIndex, 1);
|
|
173
|
+
repeatItems[linkId].enabledIndexes.splice(parentRepeatGroupIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return items;
|
|
179
|
+
}
|
|
180
|
+
|
|
142
181
|
/**
|
|
143
182
|
* Read initial answer values in questionnaireResponse
|
|
144
183
|
* return a map of initial values with key-value pair <linkedItemId, initial value>
|
|
@@ -203,7 +242,13 @@ export function setInitialAnswers(
|
|
|
203
242
|
const linkedQuestions = linkedQuestionsMap[linkId];
|
|
204
243
|
const newAnswer = initialAnswers[linkId];
|
|
205
244
|
|
|
206
|
-
updatedItems =
|
|
245
|
+
updatedItems = updateEnableWhenItemAnswer(
|
|
246
|
+
updatedItems,
|
|
247
|
+
linkedQuestions,
|
|
248
|
+
linkId,
|
|
249
|
+
newAnswer,
|
|
250
|
+
null
|
|
251
|
+
);
|
|
207
252
|
}
|
|
208
253
|
}
|
|
209
254
|
return updatedItems;
|
|
@@ -215,35 +260,65 @@ export function setInitialAnswers(
|
|
|
215
260
|
*
|
|
216
261
|
* @author Sean Fong
|
|
217
262
|
*/
|
|
218
|
-
export function
|
|
263
|
+
export function updateEnableWhenItemAnswer(
|
|
219
264
|
items: EnableWhenItems,
|
|
220
265
|
linkedQuestions: string[],
|
|
221
266
|
linkId: string,
|
|
222
|
-
newAnswer: QuestionnaireResponseItemAnswer[]
|
|
267
|
+
newAnswer: QuestionnaireResponseItemAnswer[] | undefined,
|
|
268
|
+
parentRepeatGroupIndex: number | null
|
|
223
269
|
): EnableWhenItems {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
270
|
+
const { singleItems, repeatItems } = items;
|
|
271
|
+
|
|
272
|
+
for (const linkedQuestion of linkedQuestions) {
|
|
273
|
+
// Linked question is in single items
|
|
274
|
+
if (singleItems[linkedQuestion]) {
|
|
275
|
+
// Update modified linked answer
|
|
276
|
+
singleItems[linkedQuestion].linked.forEach((linkedItem) => {
|
|
277
|
+
if (linkedItem.enableWhen.question === linkId) {
|
|
278
|
+
linkedItem.answer = newAnswer ?? undefined;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Update enabled status of modified enableWhenItem
|
|
283
|
+
singleItems[linkedQuestion].isEnabled = checkItemIsEnabledSingle(singleItems[linkedQuestion]);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Linked question is in repeat items
|
|
288
|
+
if (repeatItems[linkedQuestion] && parentRepeatGroupIndex !== null) {
|
|
289
|
+
// Update modified linked answer
|
|
290
|
+
repeatItems[linkedQuestion].linked.forEach((linkedItem) => {
|
|
291
|
+
if (linkedItem.enableWhen.question === linkId) {
|
|
292
|
+
if (newAnswer) {
|
|
293
|
+
linkedItem.answers[parentRepeatGroupIndex] = newAnswer[0] ?? undefined;
|
|
294
|
+
} else {
|
|
295
|
+
delete linkedItem.answers[parentRepeatGroupIndex];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Update enabled status of modified enableWhenItem
|
|
301
|
+
repeatItems[linkedQuestion].enabledIndexes[parentRepeatGroupIndex] = checkItemIsEnabledRepeat(
|
|
302
|
+
repeatItems[linkedQuestion],
|
|
303
|
+
parentRepeatGroupIndex
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
231
307
|
|
|
232
|
-
// Update enabled status of modified enableWhenItem
|
|
233
|
-
items[question].isEnabled = checkItemIsEnabled(items[question]);
|
|
234
|
-
});
|
|
235
308
|
return items;
|
|
236
309
|
}
|
|
237
310
|
|
|
238
311
|
/**
|
|
239
|
-
* Check if
|
|
312
|
+
* Check if a single enableWhenItem is enabled based on the answer of its linked items
|
|
240
313
|
*
|
|
241
314
|
* @author Sean Fong
|
|
242
315
|
*/
|
|
243
|
-
export function
|
|
316
|
+
export function checkItemIsEnabledSingle(
|
|
317
|
+
enableWhenItemProperties: EnableWhenSingleItemProperties
|
|
318
|
+
): boolean {
|
|
244
319
|
const checkedIsEnabledItems: boolean[] = [];
|
|
245
320
|
|
|
246
|
-
enableWhenItemProperties.linked
|
|
321
|
+
for (const linkedItem of enableWhenItemProperties.linked) {
|
|
247
322
|
if (linkedItem.answer && linkedItem.answer.length > 0) {
|
|
248
323
|
for (const answer of linkedItem.answer) {
|
|
249
324
|
const isEnabledForThisLinkedItem = isEnabledAnswerTypeSwitcher(
|
|
@@ -257,16 +332,60 @@ export function checkItemIsEnabled(enableWhenItemProperties: EnableWhenItemPrope
|
|
|
257
332
|
break;
|
|
258
333
|
}
|
|
259
334
|
}
|
|
335
|
+
continue;
|
|
260
336
|
}
|
|
261
|
-
|
|
337
|
+
|
|
338
|
+
// Linked item doesn't have any answers, but we still have to check for unanswered booleans
|
|
339
|
+
if (evaluateNonExistentAnswers(linkedItem.enableWhen)) {
|
|
340
|
+
checkedIsEnabledItems.push(true);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (checkedIsEnabledItems.length === 0) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return evaluateEnableBehaviour(checkedIsEnabledItems, enableWhenItemProperties.enableBehavior);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Check if a repeat enableWhenItem is enabled based on the answer of its linked items
|
|
353
|
+
*
|
|
354
|
+
* @author Sean Fong
|
|
355
|
+
*/
|
|
356
|
+
export function checkItemIsEnabledRepeat(
|
|
357
|
+
enableWhenItemProperties: EnableWhenRepeatItemProperties,
|
|
358
|
+
parentRepeatGroupIndex: number
|
|
359
|
+
): boolean {
|
|
360
|
+
const checkedIsEnabledItems: boolean[] = [];
|
|
361
|
+
|
|
362
|
+
for (const linkedItem of enableWhenItemProperties.linked) {
|
|
363
|
+
const linkedAnswer = linkedItem.answers[parentRepeatGroupIndex];
|
|
364
|
+
if (linkedAnswer) {
|
|
365
|
+
const isEnabledForThisLinkedItem = isEnabledAnswerTypeSwitcher(
|
|
366
|
+
linkedItem.enableWhen,
|
|
367
|
+
linkedAnswer
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
if (isEnabledForThisLinkedItem) {
|
|
371
|
+
checkedIsEnabledItems.push(
|
|
372
|
+
isEnabledAnswerTypeSwitcher(linkedItem.enableWhen, linkedAnswer)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Linked item doesn't have any answers, but we still have to check for unanswered booleans
|
|
379
|
+
if (evaluateNonExistentAnswers(linkedItem.enableWhen)) {
|
|
380
|
+
checkedIsEnabledItems.push(true);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
262
383
|
|
|
263
384
|
if (checkedIsEnabledItems.length === 0) {
|
|
264
385
|
return false;
|
|
265
386
|
}
|
|
266
387
|
|
|
267
|
-
return enableWhenItemProperties.enableBehavior
|
|
268
|
-
? checkedIsEnabledItems.some((isEnabled) => isEnabled)
|
|
269
|
-
: checkedIsEnabledItems.every((isEnabled) => isEnabled);
|
|
388
|
+
return evaluateEnableBehaviour(checkedIsEnabledItems, enableWhenItemProperties.enableBehavior);
|
|
270
389
|
}
|
|
271
390
|
|
|
272
391
|
export function assignPopulatedAnswersToEnableWhen(
|
|
@@ -275,7 +394,7 @@ export function assignPopulatedAnswersToEnableWhen(
|
|
|
275
394
|
): { initialisedItems: EnableWhenItems; linkedQuestions: Record<string, string[]> } {
|
|
276
395
|
const linkedQuestions = createEnableWhenLinkedQuestions(items);
|
|
277
396
|
const initialAnswers = readInitialAnswers(questionnaireResponse, linkedQuestions);
|
|
278
|
-
items =
|
|
397
|
+
items = initialiseUnansweredBooleans(items);
|
|
279
398
|
|
|
280
399
|
const initialisedItems =
|
|
281
400
|
Object.keys(initialAnswers).length > 0
|
|
@@ -285,30 +404,50 @@ export function assignPopulatedAnswersToEnableWhen(
|
|
|
285
404
|
return { initialisedItems, linkedQuestions };
|
|
286
405
|
}
|
|
287
406
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
const checkedIsEnabledItems: boolean[] = [];
|
|
291
|
-
const enableWhenItemProperties = items[linkId];
|
|
407
|
+
function initialiseUnansweredBooleans(items: EnableWhenItems): EnableWhenItems {
|
|
408
|
+
const { singleItems, repeatItems } = items;
|
|
292
409
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
410
|
+
// Initialise unanswered booleans for enableWhen single items
|
|
411
|
+
for (const linkId in singleItems) {
|
|
412
|
+
const checkedIsEnabledItems = singleItems[linkId].linked.map((linkedItem) =>
|
|
413
|
+
evaluateNonExistentAnswers(linkedItem.enableWhen)
|
|
414
|
+
);
|
|
298
415
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
416
|
+
singleItems[linkId].isEnabled = evaluateEnableBehaviour(
|
|
417
|
+
checkedIsEnabledItems,
|
|
418
|
+
singleItems[linkId].enableBehavior
|
|
419
|
+
);
|
|
420
|
+
}
|
|
303
421
|
|
|
304
|
-
|
|
305
|
-
|
|
422
|
+
// Initialise unanswered booleans for enableWhen repeat items
|
|
423
|
+
for (const linkId in repeatItems) {
|
|
424
|
+
const checkedIsEnabledItems = repeatItems[linkId].linked.map((linkedItem) =>
|
|
425
|
+
evaluateNonExistentAnswers(linkedItem.enableWhen)
|
|
426
|
+
);
|
|
306
427
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
428
|
+
const isEnabled = evaluateEnableBehaviour(
|
|
429
|
+
checkedIsEnabledItems,
|
|
430
|
+
repeatItems[linkId].enableBehavior
|
|
431
|
+
);
|
|
432
|
+
repeatItems[linkId].enabledIndexes = repeatItems[linkId].enabledIndexes.map(() => isEnabled);
|
|
311
433
|
}
|
|
312
434
|
|
|
313
435
|
return items;
|
|
314
436
|
}
|
|
437
|
+
|
|
438
|
+
// Internal functions
|
|
439
|
+
function evaluateNonExistentAnswers(enableWhen: QuestionnaireItemEnableWhen) {
|
|
440
|
+
const unansweredBoolean =
|
|
441
|
+
typeof enableWhen.answerBoolean === 'boolean' && enableWhen.operator === '!=';
|
|
442
|
+
const unExistingAnswer = enableWhen.answerBoolean === false && enableWhen.operator === 'exists';
|
|
443
|
+
return unansweredBoolean || unExistingAnswer;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function evaluateEnableBehaviour(
|
|
447
|
+
isEnabledArr: boolean[],
|
|
448
|
+
enableBehavior: 'all' | 'any' | undefined
|
|
449
|
+
) {
|
|
450
|
+
return enableBehavior === 'any'
|
|
451
|
+
? isEnabledArr.some((isEnabled) => isEnabled)
|
|
452
|
+
: isEnabledArr.every((isEnabled) => isEnabled);
|
|
453
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { EnableWhenExpression } from '../interfaces
|
|
18
|
+
import type { EnableWhenExpression } from '../interfaces';
|
|
19
19
|
import type { Expression, QuestionnaireResponse } from 'fhir/r4';
|
|
20
20
|
import { createFhirPathContext } from './fhirpath';
|
|
21
21
|
import fhirpath from 'fhirpath';
|
|
@@ -69,13 +69,14 @@ export function evaluateInitialEnableWhenExpressions(
|
|
|
69
69
|
fhirpath_r4_model
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
+
// Update enableWhenExpressions if length of result array > 0
|
|
72
73
|
if (result.length > 0) {
|
|
73
|
-
initialEnableWhenExpressions
|
|
74
|
+
updateEnableWhenExpressionStatus(initialEnableWhenExpressions, linkId, result);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// handle intersect edge case - evaluate() returns empty array if result is false
|
|
77
78
|
if (enableWhenExpressions[linkId].expression.includes('intersect') && result.length === 0) {
|
|
78
|
-
initialEnableWhenExpressions[linkId].
|
|
79
|
+
initialEnableWhenExpressions[linkId].isEnabledSingle = false;
|
|
79
80
|
}
|
|
80
81
|
} catch (e) {
|
|
81
82
|
console.warn(
|
|
@@ -112,16 +113,14 @@ export function evaluateEnableWhenExpressions(
|
|
|
112
113
|
fhirpath_r4_model
|
|
113
114
|
);
|
|
114
115
|
|
|
116
|
+
// Update enableWhenExpressions if length of result array > 0
|
|
115
117
|
if (result.length > 0) {
|
|
116
|
-
|
|
117
|
-
isUpdated = true;
|
|
118
|
-
updatedEnableWhenExpressions[linkId].isEnabled = result[0];
|
|
119
|
-
}
|
|
118
|
+
isUpdated = updateEnableWhenExpressionStatus(updatedEnableWhenExpressions, linkId, result);
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
// handle intersect edge case - evaluate() returns empty array if result is false
|
|
123
122
|
if (enableWhenExpressions[linkId].expression.includes('intersect') && result.length === 0) {
|
|
124
|
-
updatedEnableWhenExpressions[linkId].
|
|
123
|
+
updatedEnableWhenExpressions[linkId].isEnabledSingle = false;
|
|
125
124
|
}
|
|
126
125
|
} catch (e) {
|
|
127
126
|
console.warn(
|
|
@@ -136,3 +135,31 @@ export function evaluateEnableWhenExpressions(
|
|
|
136
135
|
updatedEnableWhenExpressions: updatedEnableWhenExpressions
|
|
137
136
|
};
|
|
138
137
|
}
|
|
138
|
+
|
|
139
|
+
function updateEnableWhenExpressionStatus(
|
|
140
|
+
enableWhenExpressions: Record<string, EnableWhenExpression>,
|
|
141
|
+
linkId: string,
|
|
142
|
+
result: any[]
|
|
143
|
+
): boolean {
|
|
144
|
+
// Values are not fully boolean, expression is invalid
|
|
145
|
+
const everyResultIsBoolean = result.every((r) => typeof r === 'boolean');
|
|
146
|
+
if (!everyResultIsBoolean) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// If result has multiple values
|
|
151
|
+
if (result.length > 1) {
|
|
152
|
+
if (enableWhenExpressions[linkId].isEnabledMultiple !== result) {
|
|
153
|
+
enableWhenExpressions[linkId].isEnabledMultiple = result;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// If result has only one value
|
|
159
|
+
if (enableWhenExpressions[linkId].isEnabledSingle !== result[0]) {
|
|
160
|
+
enableWhenExpressions[linkId].isEnabledSingle = result[0];
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return false;
|
|
165
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Commonwealth Scientific and Industrial Research
|
|
3
|
+
* Organisation (CSIRO) ABN 41 687 119 230.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// TODO to be imported into sdc-fhir-helpers
|
|
19
|
+
|
|
20
|
+
import type { Questionnaire, QuestionnaireItem } from 'fhir/r4';
|
|
21
|
+
|
|
22
|
+
export function getQuestionnaireItem(
|
|
23
|
+
questionnaire: Questionnaire,
|
|
24
|
+
targetLinkId: string
|
|
25
|
+
): QuestionnaireItem | null {
|
|
26
|
+
// Search through the top level items recursively
|
|
27
|
+
const topLevelQItems = questionnaire.item;
|
|
28
|
+
if (topLevelQItems) {
|
|
29
|
+
for (const topLevelQItem of topLevelQItems) {
|
|
30
|
+
const foundQItem = getQuestionnaireItemRecursive(topLevelQItem, targetLinkId);
|
|
31
|
+
if (foundQItem) {
|
|
32
|
+
return foundQItem;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// No matching item found in the questionnaire, return null
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getQuestionnaireItemRecursive(
|
|
42
|
+
qItem: QuestionnaireItem,
|
|
43
|
+
targetLinkId: string
|
|
44
|
+
): QuestionnaireItem | null {
|
|
45
|
+
// Target linkId found in current item
|
|
46
|
+
if (qItem.linkId === targetLinkId) {
|
|
47
|
+
return qItem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Search through its child items recursively
|
|
51
|
+
const childQItems = qItem.item;
|
|
52
|
+
if (childQItems) {
|
|
53
|
+
for (const childQItem of childQItems) {
|
|
54
|
+
const foundQItem = getQuestionnaireItemRecursive(childQItem, targetLinkId);
|
|
55
|
+
if (foundQItem) {
|
|
56
|
+
return foundQItem;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// No matching item found in the current item or its child items, return null
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
///
|
|
66
|
+
|
|
67
|
+
export function getParentItem(
|
|
68
|
+
questionnaire: Questionnaire,
|
|
69
|
+
targetLinkId: string
|
|
70
|
+
): QuestionnaireItem | null {
|
|
71
|
+
// Search through the top level items recursively
|
|
72
|
+
const topLevelQItems = questionnaire.item;
|
|
73
|
+
if (topLevelQItems) {
|
|
74
|
+
for (const topLevelQItem of topLevelQItems) {
|
|
75
|
+
const foundParentQItem = getParentItemRecursive(topLevelQItem, targetLinkId);
|
|
76
|
+
if (foundParentQItem) {
|
|
77
|
+
return foundParentQItem;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// No matching parent item found in the questionnaire, return null
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getParentItemRecursive(
|
|
87
|
+
qItem: QuestionnaireItem,
|
|
88
|
+
targetLinkId: string,
|
|
89
|
+
parentQItem?: QuestionnaireItem
|
|
90
|
+
): QuestionnaireItem | null {
|
|
91
|
+
// Current item has the target linkId, return the parent item if it exists
|
|
92
|
+
if (qItem.linkId === targetLinkId) {
|
|
93
|
+
return parentQItem ?? null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Search through its child items recursively
|
|
97
|
+
const childQItems = qItem.item;
|
|
98
|
+
if (childQItems) {
|
|
99
|
+
for (const childQItem of childQItems) {
|
|
100
|
+
const foundParentQItem = getParentItemRecursive(childQItem, targetLinkId, qItem);
|
|
101
|
+
if (foundParentQItem) {
|
|
102
|
+
return foundParentQItem;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// No matching parent item found in the current item or its child items, return null
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getRepeatGroupParentItem(
|
|
112
|
+
questionnaire: Questionnaire,
|
|
113
|
+
targetLinkId: string
|
|
114
|
+
): QuestionnaireItem | null {
|
|
115
|
+
// Search through the top level items recursively
|
|
116
|
+
const topLevelQItems = questionnaire.item;
|
|
117
|
+
if (topLevelQItems) {
|
|
118
|
+
for (const topLevelQItem of topLevelQItems) {
|
|
119
|
+
const foundParentQItem = getRepeatGroupParentItemRecursive(topLevelQItem, targetLinkId);
|
|
120
|
+
if (foundParentQItem) {
|
|
121
|
+
return foundParentQItem;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// No matching repeat group parent item found in the questionnaire, return null
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getRepeatGroupParentItemRecursive(
|
|
131
|
+
qItem: QuestionnaireItem,
|
|
132
|
+
targetLinkId: string,
|
|
133
|
+
repeatGroupParentQItem?: QuestionnaireItem
|
|
134
|
+
): QuestionnaireItem | null {
|
|
135
|
+
// Current item has the target linkId, return the parent item if it exists
|
|
136
|
+
if (qItem.linkId === targetLinkId && repeatGroupParentQItem) {
|
|
137
|
+
return repeatGroupParentQItem ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check if the current item is a repeat group
|
|
141
|
+
const isRepeatGroup = qItem.repeats && qItem.type === 'group';
|
|
142
|
+
|
|
143
|
+
// Search through its child items recursively
|
|
144
|
+
const childQItems = qItem.item;
|
|
145
|
+
if (childQItems) {
|
|
146
|
+
for (const childQItem of childQItems) {
|
|
147
|
+
const foundParentQItem = getRepeatGroupParentItemRecursive(
|
|
148
|
+
childQItem,
|
|
149
|
+
targetLinkId,
|
|
150
|
+
isRepeatGroup ? qItem : repeatGroupParentQItem
|
|
151
|
+
);
|
|
152
|
+
if (foundParentQItem) {
|
|
153
|
+
return foundParentQItem;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// No matching repeat group parent item found in the current item or its child items, return null
|
|
159
|
+
return null;
|
|
160
|
+
}
|
package/src/utils/qItem.ts
CHANGED
|
@@ -19,28 +19,51 @@ import type { Extension, Questionnaire, QuestionnaireItem } from 'fhir/r4';
|
|
|
19
19
|
import { getChoiceControlType } from './choice';
|
|
20
20
|
import { ChoiceItemControl, OpenChoiceItemControl } from '../interfaces/choice.enum';
|
|
21
21
|
import { getOpenChoiceControlType } from './openChoice';
|
|
22
|
-
import type { EnableWhenExpression, EnableWhenItems } from '../interfaces
|
|
22
|
+
import type { EnableWhenExpression, EnableWhenItems } from '../interfaces';
|
|
23
23
|
|
|
24
24
|
interface isHiddenByEnableWhensParams {
|
|
25
25
|
linkId: string;
|
|
26
26
|
enableWhenIsActivated: boolean;
|
|
27
27
|
enableWhenItems: EnableWhenItems;
|
|
28
28
|
enableWhenExpressions: Record<string, EnableWhenExpression>;
|
|
29
|
+
parentRepeatGroupIndex?: number;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export function isHiddenByEnableWhen(params: isHiddenByEnableWhensParams): boolean {
|
|
32
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
linkId,
|
|
35
|
+
enableWhenIsActivated,
|
|
36
|
+
enableWhenItems,
|
|
37
|
+
enableWhenExpressions,
|
|
38
|
+
parentRepeatGroupIndex
|
|
39
|
+
} = params;
|
|
40
|
+
|
|
41
|
+
const { singleItems, repeatItems } = enableWhenItems;
|
|
42
|
+
|
|
43
|
+
// If enableWhen is not activated, items are not hidden by enableWhen
|
|
44
|
+
if (!enableWhenIsActivated) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
33
47
|
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
48
|
+
if (singleItems[linkId]) {
|
|
49
|
+
return !singleItems[linkId].isEnabled;
|
|
50
|
+
}
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
if (repeatItems[linkId] && parentRepeatGroupIndex !== undefined) {
|
|
53
|
+
return !repeatItems[linkId].enabledIndexes[parentRepeatGroupIndex];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (enableWhenExpressions[linkId] && parentRepeatGroupIndex !== undefined) {
|
|
57
|
+
const isEnabledMultiple = enableWhenExpressions[linkId].isEnabledMultiple;
|
|
58
|
+
if (isEnabledMultiple) {
|
|
59
|
+
return !isEnabledMultiple[parentRepeatGroupIndex];
|
|
41
60
|
}
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
if (enableWhenExpressions[linkId]) {
|
|
64
|
+
return !enableWhenExpressions[linkId].isEnabledSingle;
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
return false;
|
|
45
68
|
}
|
|
46
69
|
|
|
@@ -100,7 +100,7 @@ function createEmptyModel(): QuestionnaireModel {
|
|
|
100
100
|
calculatedExpressions: {},
|
|
101
101
|
enableWhenExpressions: {},
|
|
102
102
|
answerExpressions: {},
|
|
103
|
-
enableWhenItems: {},
|
|
103
|
+
enableWhenItems: { singleItems: {}, repeatItems: {} },
|
|
104
104
|
processedValueSetCodings: {},
|
|
105
105
|
processedValueSetUrls: {},
|
|
106
106
|
fhirPathContext: {}
|