@aehrc/smart-forms-renderer 0.7.0 → 0.7.2

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.
Files changed (189) hide show
  1. package/README.md +5 -5
  2. package/lib/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.js +1 -1
  3. package/lib/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.js.map +1 -1
  4. package/lib/components/FormComponents/DecimalItem/DecimalItem.js +3 -2
  5. package/lib/components/FormComponents/DecimalItem/DecimalItem.js.map +1 -1
  6. package/lib/components/FormComponents/GridGroup/GridGroup.js +2 -2
  7. package/lib/components/FormComponents/GridGroup/GridGroup.js.map +1 -1
  8. package/lib/components/FormComponents/GridGroup/GridRow.js +2 -2
  9. package/lib/components/FormComponents/GridGroup/GridRow.js.map +1 -1
  10. package/lib/components/FormComponents/GroupItem/GroupChildItemSwitcher.d.ts +10 -0
  11. package/lib/components/FormComponents/GroupItem/GroupChildItemSwitcher.js +73 -0
  12. package/lib/components/FormComponents/GroupItem/GroupChildItemSwitcher.js.map +1 -0
  13. package/lib/components/FormComponents/GroupItem/GroupItem.js +3 -3
  14. package/lib/components/FormComponents/GroupItem/GroupItem.js.map +1 -1
  15. package/lib/components/FormComponents/GroupItem/ItemSwitcher.d.ts +10 -0
  16. package/lib/components/FormComponents/GroupItem/ItemSwitcher.js +81 -0
  17. package/lib/components/FormComponents/GroupItem/ItemSwitcher.js.map +1 -0
  18. package/lib/components/FormComponents/GroupItem/TopLevelGroupItemSwitcher.d.ts +10 -0
  19. package/lib/components/FormComponents/GroupItem/TopLevelGroupItemSwitcher.js +45 -0
  20. package/lib/components/FormComponents/GroupItem/TopLevelGroupItemSwitcher.js.map +1 -0
  21. package/lib/components/FormComponents/IntegerItem/IntegerItem.js +3 -2
  22. package/lib/components/FormComponents/IntegerItem/IntegerItem.js.map +1 -1
  23. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.js +2 -2
  24. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.js.map +1 -1
  25. package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js +4 -3
  26. package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js.map +1 -1
  27. package/lib/components/FormComponents/RepeatGroup/index.d.ts +1 -0
  28. package/lib/{theme/overrides/Typography.js → components/FormComponents/RepeatGroup/index.js} +2 -15
  29. package/lib/components/FormComponents/RepeatGroup/index.js.map +1 -0
  30. package/lib/components/FormComponents/RepeatItem/RepeatItem.js +1 -1
  31. package/lib/components/FormComponents/RepeatItem/RepeatItem.js.map +1 -1
  32. package/lib/components/FormComponents/SingleItem/index.d.ts +1 -0
  33. package/lib/components/FormComponents/SingleItem/index.js +18 -0
  34. package/lib/components/FormComponents/SingleItem/index.js.map +1 -0
  35. package/lib/components/FormComponents/StringItem/StringItem.js +3 -2
  36. package/lib/components/FormComponents/StringItem/StringItem.js.map +1 -1
  37. package/lib/components/FormComponents/Tables/QItemGroupTable.js +7 -5
  38. package/lib/components/FormComponents/Tables/QItemGroupTable.js.map +1 -1
  39. package/lib/components/FormComponents/Tables/QItemGroupTableRow.js +2 -2
  40. package/lib/components/FormComponents/Tables/QItemGroupTableRow.js.map +1 -1
  41. package/lib/components/FormComponents/Tables/index.d.ts +1 -0
  42. package/lib/components/FormComponents/Tables/index.js +18 -0
  43. package/lib/components/FormComponents/Tables/index.js.map +1 -0
  44. package/lib/components/FormComponents/TextItem/TextItem.js +3 -2
  45. package/lib/components/FormComponents/TextItem/TextItem.js.map +1 -1
  46. package/lib/components/FormComponents/index.d.ts +3 -0
  47. package/lib/components/FormComponents/index.js +20 -0
  48. package/lib/components/FormComponents/index.js.map +1 -0
  49. package/lib/components/Renderer/BaseRenderer.js +26 -14
  50. package/lib/components/Renderer/BaseRenderer.js.map +1 -1
  51. package/lib/components/Renderer/FormBodyCollapsible.js +2 -2
  52. package/lib/components/Renderer/FormBodyCollapsible.js.map +1 -1
  53. package/lib/components/Renderer/FormBodyTabbed.js +2 -2
  54. package/lib/components/Renderer/FormBodyTabbed.js.map +1 -1
  55. package/lib/components/Renderer/FormTopLevelItem.d.ts +4 -4
  56. package/lib/components/Renderer/FormTopLevelItem.js +14 -1
  57. package/lib/components/Renderer/FormTopLevelItem.js.map +1 -1
  58. package/lib/components/index.d.ts +1 -0
  59. package/lib/components/index.js +1 -0
  60. package/lib/components/index.js.map +1 -1
  61. package/lib/hooks/useDecimalUpdateFromProp.d.ts +2 -0
  62. package/lib/{utils/isHidden.js → hooks/useDecimalUpdateFromProp.js} +12 -13
  63. package/lib/hooks/useDecimalUpdateFromProp.js.map +1 -0
  64. package/lib/hooks/useInitialiseRepeatAnswers.d.ts +2 -2
  65. package/lib/hooks/useInitialiseRepeatAnswers.js +21 -15
  66. package/lib/hooks/useInitialiseRepeatAnswers.js.map +1 -1
  67. package/lib/hooks/useInitialiseRepeatGroups.d.ts +2 -1
  68. package/lib/hooks/useInitialiseRepeatGroups.js +21 -15
  69. package/lib/hooks/useInitialiseRepeatGroups.js.map +1 -1
  70. package/lib/hooks/useIntegerUpdateFromProp.d.ts +2 -0
  71. package/lib/hooks/useIntegerUpdateFromProp.js +29 -0
  72. package/lib/hooks/useIntegerUpdateFromProp.js.map +1 -0
  73. package/lib/hooks/useNumberInput.d.ts +3 -0
  74. package/lib/hooks/useNumberInput.js +31 -0
  75. package/lib/hooks/useNumberInput.js.map +1 -0
  76. package/lib/hooks/useNumberUpdateFromProp.d.ts +2 -0
  77. package/lib/hooks/useNumberUpdateFromProp.js +29 -0
  78. package/lib/hooks/useNumberUpdateFromProp.js.map +1 -0
  79. package/lib/hooks/useRepeatItemState.d.ts +5 -0
  80. package/lib/hooks/useRepeatItemState.js +35 -0
  81. package/lib/hooks/useRepeatItemState.js.map +1 -0
  82. package/lib/hooks/useRepeatItemUpdateFromProp.d.ts +2 -0
  83. package/lib/hooks/useRepeatItemUpdateFromProp.js +29 -0
  84. package/lib/hooks/useRepeatItemUpdateFromProp.js.map +1 -0
  85. package/lib/hooks/useStringField.d.ts +2 -0
  86. package/lib/hooks/useStringField.js +29 -0
  87. package/lib/hooks/useStringField.js.map +1 -0
  88. package/lib/hooks/useStringInput.d.ts +3 -0
  89. package/lib/hooks/useStringInput.js +31 -0
  90. package/lib/hooks/useStringInput.js.map +1 -0
  91. package/lib/hooks/useStringUpdateFromProp.d.ts +2 -0
  92. package/lib/hooks/useStringUpdateFromProp.js +29 -0
  93. package/lib/hooks/useStringUpdateFromProp.js.map +1 -0
  94. package/lib/index.d.ts +16 -0
  95. package/lib/index.js +27 -0
  96. package/lib/index.js.map +1 -1
  97. package/lib/interfaces/questionnaireStore.interface.d.ts +1 -0
  98. package/lib/stores/useQuestionnaireResponseStore.d.ts +1 -1
  99. package/lib/stores/useQuestionnaireResponseStore.js +15 -10
  100. package/lib/stores/useQuestionnaireResponseStore.js.map +1 -1
  101. package/lib/stores/useQuestionnaireStore.d.ts +2 -1
  102. package/lib/stores/useQuestionnaireStore.js +5 -2
  103. package/lib/stores/useQuestionnaireStore.js.map +1 -1
  104. package/lib/theme/overrides/Overrides.js +1 -2
  105. package/lib/theme/overrides/Overrides.js.map +1 -1
  106. package/lib/utils/calculatedExpression.js +2 -2
  107. package/lib/utils/calculatedExpression.js.map +1 -1
  108. package/lib/utils/formChanges.d.ts +18 -0
  109. package/lib/utils/formChanges.js +91 -0
  110. package/lib/utils/formChanges.js.map +1 -0
  111. package/lib/utils/formChangesOld.d.ts +18 -0
  112. package/lib/utils/formChangesOld.js +91 -0
  113. package/lib/utils/formChangesOld.js.map +1 -0
  114. package/lib/utils/index.d.ts +1 -0
  115. package/lib/utils/index.js +18 -0
  116. package/lib/utils/index.js.map +1 -0
  117. package/lib/utils/mapItem.d.ts +2 -2
  118. package/lib/utils/mapItem.js +3 -3
  119. package/lib/utils/mapItem.js.map +1 -1
  120. package/lib/utils/qItem.d.ts +2 -0
  121. package/lib/utils/qItem.js +22 -0
  122. package/lib/utils/qItem.js.map +1 -1
  123. package/lib/utils/qrItem.d.ts +1 -1
  124. package/lib/utils/qrItem.js +92 -93
  125. package/lib/utils/qrItem.js.map +1 -1
  126. package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js +4 -0
  127. package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js.map +1 -1
  128. package/lib/utils/repopulate.d.ts +9 -0
  129. package/lib/utils/repopulate.js +133 -0
  130. package/lib/utils/repopulate.js.map +1 -0
  131. package/lib/utils/repopulateGenerateItems.d.ts +9 -0
  132. package/lib/utils/repopulateGenerateItems.js +133 -0
  133. package/lib/utils/repopulateGenerateItems.js.map +1 -0
  134. package/lib/utils/repopulateIntoResponse.d.ts +3 -0
  135. package/lib/utils/repopulateIntoResponse.js +92 -0
  136. package/lib/utils/repopulateIntoResponse.js.map +1 -0
  137. package/lib/utils/repopulateItems.d.ts +9 -0
  138. package/lib/utils/repopulateItems.js +131 -0
  139. package/lib/utils/repopulateItems.js.map +1 -0
  140. package/lib/utils/repopulateRepeatGroup.d.ts +4 -0
  141. package/lib/utils/repopulateRepeatGroup.js +54 -0
  142. package/lib/utils/repopulateRepeatGroup.js.map +1 -0
  143. package/package.json +3 -1
  144. package/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.tsx +1 -1
  145. package/src/components/FormComponents/DecimalItem/DecimalItem.tsx +3 -2
  146. package/src/components/FormComponents/GridGroup/GridGroup.tsx +2 -2
  147. package/src/components/FormComponents/GridGroup/GridRow.tsx +2 -2
  148. package/src/components/FormComponents/GroupItem/GroupItem.tsx +3 -3
  149. package/src/components/FormComponents/IntegerItem/IntegerItem.tsx +3 -2
  150. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.tsx +1 -1
  151. package/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx +4 -3
  152. package/src/{theme/overrides/Backdrop.ts → components/FormComponents/RepeatGroup/index.ts} +1 -17
  153. package/src/components/FormComponents/RepeatItem/RepeatItem.tsx +1 -1
  154. package/src/components/FormComponents/SingleItem/index.ts +18 -0
  155. package/src/components/FormComponents/StringItem/StringItem.tsx +3 -2
  156. package/src/components/FormComponents/Tables/QItemGroupTable.tsx +15 -6
  157. package/src/components/FormComponents/Tables/QItemGroupTableRow.tsx +2 -2
  158. package/src/components/FormComponents/Tables/index.ts +18 -0
  159. package/src/components/FormComponents/TextItem/TextItem.tsx +3 -2
  160. package/src/components/FormComponents/index.ts +20 -0
  161. package/src/components/Renderer/BaseRenderer.tsx +39 -18
  162. package/src/components/Renderer/FormBodyCollapsible.tsx +2 -2
  163. package/src/components/Renderer/FormBodyTabbed.tsx +2 -2
  164. package/src/components/Renderer/FormTopLevelItem.tsx +33 -4
  165. package/src/components/index.ts +1 -0
  166. package/src/hooks/index.ts +1 -0
  167. package/src/hooks/useInitialiseRepeatAnswers.ts +28 -17
  168. package/src/hooks/useInitialiseRepeatGroups.ts +28 -17
  169. package/src/hooks/useNumberInput.ts +37 -0
  170. package/src/hooks/useStringInput.ts +37 -0
  171. package/src/index.ts +43 -0
  172. package/src/interfaces/questionnaireStore.interface.ts +1 -0
  173. package/src/stores/useQuestionnaireResponseStore.ts +14 -10
  174. package/src/stores/useQuestionnaireStore.ts +10 -3
  175. package/src/theme/overrides/Overrides.ts +0 -2
  176. package/src/utils/calculatedExpression.ts +2 -2
  177. package/src/utils/formChanges.ts +141 -0
  178. package/src/utils/index.ts +18 -0
  179. package/src/utils/mapItem.ts +6 -4
  180. package/src/utils/qItem.ts +29 -0
  181. package/src/utils/qrItem.ts +104 -94
  182. package/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts +4 -0
  183. package/src/utils/repopulateIntoResponse.ts +153 -0
  184. package/src/utils/repopulateItems.ts +207 -0
  185. package/src/utils/repopulateRepeatGroup.ts +68 -0
  186. package/lib/theme/overrides/Typography.d.ts +0 -13
  187. package/lib/theme/overrides/Typography.js.map +0 -1
  188. package/lib/utils/isHidden.d.ts +0 -3
  189. package/lib/utils/isHidden.js.map +0 -1
@@ -42,6 +42,7 @@ import useTerminologyServerStore from './useTerminologyServerStore';
42
42
 
43
43
  export interface UseQuestionnaireStoreType {
44
44
  sourceQuestionnaire: Questionnaire;
45
+ itemTypes: Record<string, string>;
45
46
  tabs: Tabs;
46
47
  currentTabIndex: number;
47
48
  variables: Variables;
@@ -68,11 +69,15 @@ export interface UseQuestionnaireStoreType {
68
69
  toggleEnableWhenActivation: (isActivated: boolean) => void;
69
70
  updateExpressions: (updatedResponse: QuestionnaireResponse) => void;
70
71
  addCodingToCache: (valueSetUrl: string, codings: Coding[]) => void;
71
- updatePopulatedProperties: (populatedResponse: QuestionnaireResponse) => QuestionnaireResponse;
72
+ updatePopulatedProperties: (
73
+ populatedResponse: QuestionnaireResponse,
74
+ persistTabIndex?: boolean
75
+ ) => QuestionnaireResponse;
72
76
  }
73
77
 
74
78
  const useQuestionnaireStore = create<UseQuestionnaireStoreType>()((set, get) => ({
75
79
  sourceQuestionnaire: cloneDeep(emptyQuestionnaire),
80
+ itemTypes: {},
76
81
  tabs: {},
77
82
  currentTabIndex: 0,
78
83
  variables: { fhirPathVariables: {}, xFhirQueryVariables: {} },
@@ -115,6 +120,7 @@ const useQuestionnaireStore = create<UseQuestionnaireStoreType>()((set, get) =>
115
120
 
116
121
  set({
117
122
  sourceQuestionnaire: questionnaire,
123
+ itemTypes: questionnaireModel.itemTypes,
118
124
  tabs: questionnaireModel.tabs,
119
125
  currentTabIndex: firstVisibleTab,
120
126
  variables: questionnaireModel.variables,
@@ -131,6 +137,7 @@ const useQuestionnaireStore = create<UseQuestionnaireStoreType>()((set, get) =>
131
137
  destroySourceQuestionnaire: () =>
132
138
  set({
133
139
  sourceQuestionnaire: cloneDeep(emptyQuestionnaire),
140
+ itemTypes: {},
134
141
  tabs: {},
135
142
  currentTabIndex: 0,
136
143
  variables: { fhirPathVariables: {}, xFhirQueryVariables: {} },
@@ -197,7 +204,7 @@ const useQuestionnaireStore = create<UseQuestionnaireStoreType>()((set, get) =>
197
204
  [valueSetUrl]: codings
198
205
  }
199
206
  })),
200
- updatePopulatedProperties: (populatedResponse: QuestionnaireResponse) => {
207
+ updatePopulatedProperties: (populatedResponse: QuestionnaireResponse, persistTabIndex) => {
201
208
  const initialCalculatedExpressions = evaluateInitialCalculatedExpressions({
202
209
  initialResponse: populatedResponse,
203
210
  calculatedExpressions: get().calculatedExpressions,
@@ -229,7 +236,7 @@ const useQuestionnaireStore = create<UseQuestionnaireStoreType>()((set, get) =>
229
236
  enableWhenLinkedQuestions: initialEnableWhenLinkedQuestions,
230
237
  enableWhenExpressions: initialEnableWhenExpressions,
231
238
  calculatedExpressions: initialCalculatedExpressions,
232
- currentTabIndex: firstVisibleTab
239
+ currentTabIndex: persistTabIndex ? get().currentTabIndex : firstVisibleTab
233
240
  }));
234
241
 
235
242
  return updatedResponse;
@@ -20,7 +20,6 @@ import Paper from './Paper';
20
20
  import Input from './Input';
21
21
  import Table from './Table';
22
22
  import Button from './Button';
23
- import Backdrop from './Backdrop';
24
23
  import Autocomplete from './Autocomplete';
25
24
  import Accordion from './Accordion';
26
25
  import SpeedDial from './SpeedDial';
@@ -34,7 +33,6 @@ function ComponentsOverrides(theme: Theme) {
34
33
  Input(theme),
35
34
  Paper(),
36
35
  Button(theme),
37
- Backdrop(theme),
38
36
  Autocomplete(theme),
39
37
  SpeedDial(theme)
40
38
  );
@@ -30,7 +30,7 @@ import _isEqual from 'lodash/isEqual';
30
30
  import { emptyResponse } from './emptyResource';
31
31
  import { createFhirPathContext } from './fhirpath';
32
32
  import { getQrItemsIndex, mapQItemsIndex } from './mapItem';
33
- import { updateQrGroup } from './qrItem';
33
+ import { updateQrItemsInGroup } from './qrItem';
34
34
  import cloneDeep from 'lodash.clonedeep';
35
35
 
36
36
  interface EvaluateInitialCalculatedExpressionsParams {
@@ -213,7 +213,7 @@ function initialiseItemCalculatedExpressionValueRecursive(
213
213
  }
214
214
 
215
215
  if (newQrItem) {
216
- updateQrGroup(
216
+ updateQrItemsInGroup(
217
217
  newQrItem,
218
218
  null,
219
219
  qrItem ?? { linkId: qItem.linkId, text: qItem.text, item: [] },
@@ -0,0 +1,141 @@
1
+ /*
2
+ * Copyright 2023 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
+ import type { QuestionnaireResponseItemAnswer } from 'fhir/r4';
19
+
20
+ type JsonDiffOperator = '+' | '-' | '~' | ' ';
21
+
22
+ type QuestionnaireResponseItemDiffArray = [JsonDiffOperator, QuestionnaireResponseDiffItem];
23
+ type QuestionnaireResponseItemAnswerDiffArray = [JsonDiffOperator, QuestionnaireResponseItemAnswer];
24
+
25
+ interface QuestionnaireResponseDiffItem {
26
+ linkId: string;
27
+ item: QuestionnaireResponseItemDiffArray[] | undefined;
28
+ answer:
29
+ | QuestionnaireResponseItemAnswer[]
30
+ | QuestionnaireResponseItemAnswerDiffArray[]
31
+ | undefined;
32
+ }
33
+
34
+ interface ItemChange {
35
+ linkId: string;
36
+ itemType: string;
37
+ operation: 'add' | 'remove' | 'update';
38
+ value: QuestionnaireResponseItemAnswer;
39
+ }
40
+
41
+ export function readFormChanges(formChanges: object, itemTypes: Record<string, string>) {
42
+ if (!('item' in formChanges)) {
43
+ return {};
44
+ }
45
+
46
+ const changedItems: Record<string, ItemChange | null> = {};
47
+ const diffArrays = formChanges.item as QuestionnaireResponseItemDiffArray[];
48
+ for (const diffArray of diffArrays) {
49
+ readItemChangesRecursive(diffArray, itemTypes, changedItems);
50
+ }
51
+
52
+ return changedItems;
53
+ }
54
+
55
+ export function readItemChangesRecursive(
56
+ diffArray: QuestionnaireResponseItemDiffArray,
57
+ itemTypes: Record<string, string>,
58
+ changedItems: Record<string, ItemChange | null>
59
+ ) {
60
+ const operator = diffArray[0];
61
+ const diffItem = diffArray[1];
62
+
63
+ if (!operator || !diffItem) {
64
+ return changedItems;
65
+ }
66
+
67
+ changedItems[diffItem.linkId] = null;
68
+
69
+ const childDiffArrays = diffItem.item;
70
+ if (childDiffArrays && childDiffArrays.length > 0) {
71
+ for (const childDiffArray of childDiffArrays) {
72
+ readItemChangesRecursive(childDiffArray, itemTypes, changedItems);
73
+ }
74
+ }
75
+
76
+ // if (operator !== ' ') {
77
+ // console.log(operator, diffItem);
78
+ // }
79
+
80
+ // explore answer diff arrays
81
+ // exmaple: [["+", {"valueString": "test"}], ["-", {"valueString": "test"}]]
82
+ // or it could be a regular answer array
83
+ // exmaple: [{"valueString": "test"}, {"valueString": "test"}]
84
+
85
+ const answerDiffArrays = diffItem.answer;
86
+ if (answerDiffArrays && isAnswerDiffArray(answerDiffArrays)) {
87
+ for (const answerDiffArray of answerDiffArrays) {
88
+ getItemChange(diffItem, answerDiffArray, itemTypes, changedItems);
89
+ }
90
+ }
91
+
92
+ return changedItems;
93
+ }
94
+
95
+ function getItemChange(
96
+ diffItem: QuestionnaireResponseDiffItem,
97
+ answerDiffArray: QuestionnaireResponseItemAnswerDiffArray,
98
+ itemTypes: Record<string, string>,
99
+ changedItems: Record<string, ItemChange | null>
100
+ ) {
101
+ const operator = answerDiffArray[0];
102
+ const answer = answerDiffArray[1];
103
+
104
+ if (operator === ' ') {
105
+ return;
106
+ }
107
+
108
+ const itemType = itemTypes[diffItem.linkId];
109
+ const operation = answerDiffOperationSwitcher(operator);
110
+ if (operation) {
111
+ changedItems[diffItem.linkId] = {
112
+ linkId: diffItem.linkId,
113
+ itemType: itemType,
114
+ operation: operation,
115
+ value: answer
116
+ };
117
+ }
118
+ }
119
+
120
+ function answerDiffOperationSwitcher(operator: JsonDiffOperator): ItemChange['operation'] | null {
121
+ switch (operator) {
122
+ case '+':
123
+ return 'add';
124
+ case '-':
125
+ return 'remove';
126
+ case '~':
127
+ return 'update';
128
+ default:
129
+ return null;
130
+ }
131
+ }
132
+
133
+ // type predicates
134
+ function isAnswerDiffArray(answer: any[]): answer is QuestionnaireResponseItemAnswerDiffArray[] {
135
+ return answer.every(
136
+ (answerItem) =>
137
+ Array.isArray(answerItem) &&
138
+ typeof answerItem[0] === 'string' &&
139
+ typeof answerItem[1] === 'object'
140
+ );
141
+ }
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Copyright 2023 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
+ export { isSpecificItemControl } from './itemControl';
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
18
+ import type { Questionnaire, QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
19
19
  import { isRepeatItemAndNotCheckbox } from './qItem';
20
20
 
21
21
  /**
@@ -84,13 +84,15 @@ export function getQrItemsIndex(
84
84
  *
85
85
  * @author Sean Fong
86
86
  */
87
- export function mapQItemsIndex(qGroup: QuestionnaireItem): Record<string, number> {
88
- if (!qGroup.item) {
87
+ export function mapQItemsIndex(
88
+ questionnaireOrQItem: QuestionnaireItem | Questionnaire
89
+ ): Record<string, number> {
90
+ if (!questionnaireOrQItem.item) {
89
91
  return {};
90
92
  }
91
93
 
92
94
  // generate a <linkId, QItemIndex> dictionary
93
- return qGroup.item.reduce((mapping: Record<string, number>, item, i) => {
95
+ return questionnaireOrQItem.item.reduce((mapping: Record<string, number>, item, i) => {
94
96
  mapping[item.linkId] = i;
95
97
  return mapping;
96
98
  }, {});
@@ -101,3 +101,32 @@ export function getXHtmlStringFromQuestionnaire(questionnaire: Questionnaire): s
101
101
  }
102
102
  return null;
103
103
  }
104
+
105
+ export function getLinkIdTypeTuples(questionnaire: Questionnaire): [string, string][] {
106
+ if (!questionnaire.item || questionnaire.item.length === 0) {
107
+ return [];
108
+ }
109
+
110
+ const linkIds: [string, string][] = [];
111
+ for (const topLevelItem of questionnaire.item) {
112
+ linkIds.push(...getLinkIdTypeTuplesFromItemRecursive(topLevelItem));
113
+ }
114
+
115
+ return linkIds;
116
+ }
117
+
118
+ export function getLinkIdTypeTuplesFromItemRecursive(qItem: QuestionnaireItem): [string, string][] {
119
+ const linkIds: [string, string][] = [];
120
+
121
+ if (qItem.linkId) {
122
+ linkIds.push([qItem.linkId, qItem.type]);
123
+ }
124
+
125
+ if (qItem.item) {
126
+ for (const childItem of qItem.item) {
127
+ linkIds.push(...getLinkIdTypeTuplesFromItemRecursive(childItem));
128
+ }
129
+ }
130
+
131
+ return linkIds;
132
+ }
@@ -134,116 +134,126 @@ export function createEmptyQrItemWithUnit(
134
134
  *
135
135
  * @author Sean Fong
136
136
  */
137
- export function updateQrGroup(
137
+ export function updateQrItemsInGroup(
138
138
  newQrItem: QuestionnaireResponseItem | null,
139
139
  newQrRepeatGroup: QrRepeatGroup | null,
140
- qrGroup: QuestionnaireResponseItem,
140
+ questionnaireResponseOrQrItem: QuestionnaireResponseItem | QuestionnaireResponse,
141
141
  qItemsIndexMap: Record<string, number>
142
142
  ): void {
143
- if (qrGroup['item']) {
144
- // Get actual sequence indexes of qrItems present within a qrGroup
145
- // e.g. qrGroup has 4 fields but only the 2nd and 3rd field have values - resulting array is [1, 2]
146
- const qrItemsRealIndexArr = qrGroup.item.map((qrItem) => qItemsIndexMap[qrItem.linkId]);
147
-
148
- if (newQrItem && newQrItem.linkId in qItemsIndexMap) {
149
- if (qrGroup.item.length === 0) {
150
- qrGroup.item.push(newQrItem);
151
- } else {
152
- // Get actual sequence index of qrItem within qrGroup
153
- const newQrItemIndex = qItemsIndexMap[newQrItem.linkId];
154
-
155
- for (let i = 0; i < qrItemsRealIndexArr.length; i++) {
156
- // Add qrItem at the end of qrGroup if it is larger than the other indexes
157
- if (newQrItemIndex > qrItemsRealIndexArr[i]) {
158
- if (i === qrItemsRealIndexArr.length - 1) {
159
- qrGroup.item.push(newQrItem);
160
- }
161
- continue;
162
- }
143
+ const qrItems = questionnaireResponseOrQrItem.item;
144
+ if (!qrItems) {
145
+ return;
146
+ }
163
147
 
164
- // Replace or delete qrItem at its supposed position if its index is already present within qrGroup
165
- if (newQrItemIndex === qrItemsRealIndexArr[i]) {
166
- if (newQrItem.item?.length || newQrItem.answer?.length) {
167
- // newQrItem has answer value
168
- qrGroup.item[i] = newQrItem;
169
- } else {
170
- // newQrItem has no answer value
171
- qrGroup.item.splice(i, 1);
172
- }
173
- break;
174
- }
148
+ // Get actual sequence indexes of qrItems present within a qrGroup
149
+ // e.g. qrGroup has 4 fields but only the 2nd and 3rd field have values - resulting array is [1, 2]
150
+ const qrItemsRealIndexArr = qrItems.map((qrItem) => qItemsIndexMap[qrItem.linkId]);
175
151
 
176
- // Add qrItem at its supposed position if its index is not present within qrGroup
177
- if (newQrItemIndex < qrItemsRealIndexArr[i]) {
178
- qrGroup.item.splice(i, 0, newQrItem);
179
- break;
180
- }
152
+ if (newQrItem && newQrItem.linkId in qItemsIndexMap) {
153
+ if (qrItems.length === 0) {
154
+ qrItems.push(newQrItem);
155
+ return;
156
+ }
157
+
158
+ // Get actual sequence index of qrItem within qrGroup
159
+ const newQrItemIndex = qItemsIndexMap[newQrItem.linkId];
160
+ for (let i = 0; i < qrItemsRealIndexArr.length; i++) {
161
+ // Add qrItem at the end of qrGroup if it is larger than the other indexes
162
+ if (newQrItemIndex > qrItemsRealIndexArr[i]) {
163
+ if (i === qrItemsRealIndexArr.length - 1) {
164
+ qrItems.push(newQrItem);
181
165
  }
166
+ continue;
182
167
  }
183
- } else if (newQrRepeatGroup && newQrRepeatGroup.linkId in qItemsIndexMap) {
184
- const newQrItems = newQrRepeatGroup.qrItems;
185
- if (qrGroup.item.length === 0) {
186
- qrGroup.item.push(...newQrItems);
187
- } else {
188
- // Get actual sequence index of qrItems within qrGroup
189
- const newQrItemIndex = qItemsIndexMap[newQrRepeatGroup.linkId];
190
-
191
- for (let i = 0; i < qrItemsRealIndexArr.length; i++) {
192
- // Add qrItem at the end of qrGroup if it is larger than the other indexes
193
- if (newQrItemIndex > qrItemsRealIndexArr[i]) {
194
- if (i === qrItemsRealIndexArr.length - 1) {
195
- qrGroup.item.push(...newQrItems);
196
- }
197
- continue;
198
- }
199
168
 
200
- // Replace or delete qrItem at its supposed position if its index is already present within qrGroup
201
- if (newQrItemIndex === qrItemsRealIndexArr[i]) {
202
- // Get number of repeatGroupItems that has the same linkId present in qrGroup
203
- let repeatGroupItemCount = 0;
204
- while (newQrItemIndex === qrItemsRealIndexArr[i + repeatGroupItemCount]) {
205
- repeatGroupItemCount++;
206
- }
169
+ // Replace or delete qrItem at its supposed position if its index is already present within qrGroup
170
+ if (newQrItemIndex === qrItemsRealIndexArr[i]) {
171
+ // newQrItem has answer value
172
+ if (newQrItem.item?.length || newQrItem.answer?.length) {
173
+ qrItems[i] = newQrItem;
174
+ break;
175
+ }
207
176
 
208
- // Replace each repeat group qrItem with their new counterparts
209
- if (newQrItems.length === repeatGroupItemCount) {
210
- for (let j = 0; j < newQrItems.length; j++) {
211
- qrGroup.item[i + j] = newQrItems[j];
212
- }
213
- break;
214
- } else if (newQrItems.length > repeatGroupItemCount) {
215
- // Replace each repeat group qrItem with their new counterparts,
216
- // followed by adding an extra newQrItem behind the newly replaced qrItems
217
- for (let j = 0, k = repeatGroupItemCount; j < newQrItems.length; j++, k--) {
218
- if (k > 0) {
219
- qrGroup.item[i + j] = newQrItems[j];
220
- } else {
221
- qrGroup.item.splice(i + j, 0, newQrItems[j]);
222
- }
223
- }
224
- break;
225
- } else if (newQrItems.length < repeatGroupItemCount) {
226
- // Replace each repeat group qrItem with their new counterparts (except the last one),
227
- // followed by deleting the last newQrItem which wasn't replaced
228
- for (let j = 0; j < repeatGroupItemCount; j++) {
229
- if (j <= newQrItems.length - 1) {
230
- qrGroup.item[i + j] = newQrItems[j];
231
- } else {
232
- qrGroup.item.splice(i + j, 1);
233
- }
234
- }
235
- break;
177
+ // newQrItem has no answer value
178
+ qrItems.splice(i, 1);
179
+ break;
180
+ }
181
+
182
+ // Add qrItem at its supposed position if its index is not present within qrGroup
183
+ if (newQrItemIndex < qrItemsRealIndexArr[i]) {
184
+ qrItems.splice(i, 0, newQrItem);
185
+ break;
186
+ }
187
+ }
188
+ }
189
+
190
+ if (newQrRepeatGroup && newQrRepeatGroup.linkId in qItemsIndexMap) {
191
+ const newQrItems = newQrRepeatGroup.qrItems;
192
+ if (qrItems.length === 0) {
193
+ qrItems.push(...newQrItems);
194
+ return;
195
+ }
196
+
197
+ // Get actual sequence index of qrItems within qrGroup
198
+ const newQrItemIndex = qItemsIndexMap[newQrRepeatGroup.linkId];
199
+ for (let i = 0; i < qrItemsRealIndexArr.length; i++) {
200
+ // Add qrItem at the end of qrGroup if it is larger than the other indexes
201
+ if (newQrItemIndex > qrItemsRealIndexArr[i]) {
202
+ if (i === qrItemsRealIndexArr.length - 1) {
203
+ qrItems.push(...newQrItems);
204
+ }
205
+ continue;
206
+ }
207
+
208
+ // Replace or delete qrItem at its supposed position if its index is already present within qrGroup
209
+ if (newQrItemIndex === qrItemsRealIndexArr[i]) {
210
+ // Get number of repeatGroupItems that has the same linkId present in qrGroup
211
+ let repeatGroupItemCount = 0;
212
+ while (newQrItemIndex === qrItemsRealIndexArr[i + repeatGroupItemCount]) {
213
+ repeatGroupItemCount++;
214
+ }
215
+
216
+ // Replace each repeat group qrItem with their new counterparts
217
+ if (newQrItems.length === repeatGroupItemCount) {
218
+ for (let j = 0; j < newQrItems.length; j++) {
219
+ qrItems[i + j] = newQrItems[j];
220
+ }
221
+ break;
222
+ }
223
+
224
+ if (newQrItems.length > repeatGroupItemCount) {
225
+ // Replace each repeat group qrItem with their new counterparts,
226
+ // followed by adding an extra newQrItem behind the newly replaced qrItems
227
+ for (let j = 0, k = repeatGroupItemCount; j < newQrItems.length; j++, k--) {
228
+ if (k > 0) {
229
+ qrItems[i + j] = newQrItems[j];
230
+ } else {
231
+ qrItems.splice(i + j, 0, newQrItems[j]);
236
232
  }
237
233
  }
234
+ break;
235
+ }
238
236
 
239
- // Add qrItem at its supposed position if its index is not present within qrGroup
240
- if (newQrItemIndex < qrItemsRealIndexArr[i]) {
241
- for (let j = 0; j < newQrItems.length; j++) {
242
- qrGroup.item.splice(i + j, 0, newQrItems[j]);
237
+ if (newQrItems.length < repeatGroupItemCount) {
238
+ // Replace each repeat group qrItem with their new counterparts (except the last one),
239
+ // followed by deleting the last newQrItem which wasn't replaced
240
+ for (let j = 0; j < repeatGroupItemCount; j++) {
241
+ if (j <= newQrItems.length - 1) {
242
+ qrItems[i + j] = newQrItems[j];
243
+ } else {
244
+ qrItems.splice(i + j, 1);
243
245
  }
244
- break;
245
246
  }
247
+ break;
248
+ }
249
+ }
250
+
251
+ // Add qrItem at its supposed position if its index is not present within qrGroup
252
+ if (newQrItemIndex < qrItemsRealIndexArr[i]) {
253
+ for (let j = 0; j < newQrItems.length; j++) {
254
+ qrItems.splice(i + j, 0, newQrItems[j]);
246
255
  }
256
+ break;
247
257
  }
248
258
  }
249
259
  }
@@ -27,6 +27,7 @@ import { extractOtherExtensions } from './extractOtherExtensions';
27
27
  import type { Variables } from '../../interfaces/variables.interface';
28
28
  import { resolveValueSets } from './resolveValueSets';
29
29
  import { addAdditionalVariables } from './addAdditionalVariables';
30
+ import { getLinkIdTypeTuples } from '../qItem';
30
31
 
31
32
  export async function createQuestionnaireModel(
32
33
  questionnaire: Questionnaire,
@@ -37,6 +38,7 @@ export async function createQuestionnaireModel(
37
38
  return createEmptyModel();
38
39
  }
39
40
 
41
+ const itemTypes: Record<string, string> = Object.fromEntries(getLinkIdTypeTuples(questionnaire));
40
42
  const tabs: Tabs = extractTabs(questionnaire);
41
43
 
42
44
  const launchContexts: Record<string, LaunchContext> = extractLaunchContexts(questionnaire);
@@ -75,6 +77,7 @@ export async function createQuestionnaireModel(
75
77
  processedValueSetCodings = resolveValueSetsResult.processedValueSetCodings;
76
78
 
77
79
  return {
80
+ itemTypes,
78
81
  tabs,
79
82
  variables,
80
83
  launchContexts,
@@ -89,6 +92,7 @@ export async function createQuestionnaireModel(
89
92
 
90
93
  function createEmptyModel(): QuestionnaireModel {
91
94
  return {
95
+ itemTypes: {},
92
96
  tabs: {},
93
97
  variables: { fhirPathVariables: {}, xFhirQueryVariables: {} },
94
98
  launchContexts: {},