lforms 29.3.1 → 33.3.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +264 -0
  2. package/README.md +97 -14
  3. package/changed-features.md +99 -0
  4. package/dist/lforms/LICENSE.md +70 -0
  5. package/dist/lforms/README.md +121 -0
  6. package/dist/lforms/fhir/R4/lformsFHIR.min.js +2 -0
  7. package/dist/lforms/fhir/R4/lformsFHIR.min.js.map +1 -0
  8. package/dist/lforms/fhir/STU3/lformsFHIR.min.js +2 -0
  9. package/dist/lforms/fhir/STU3/lformsFHIR.min.js.map +1 -0
  10. package/dist/lforms/fhir/lformsFHIRAll.min.js +2 -0
  11. package/dist/lforms/fhir/lformsFHIRAll.min.js.map +1 -0
  12. package/dist/lforms/webcomponent/assets/lib/zone.min.js +134 -0
  13. package/dist/lforms/webcomponent/down_arrow_gray_10_10.png +0 -0
  14. package/dist/lforms/webcomponent/favicon.ico +0 -0
  15. package/dist/lforms/webcomponent/index.html +9 -0
  16. package/dist/lforms/webcomponent/lhc-forms.js +2472 -0
  17. package/dist/lforms/webcomponent/magnifying_glass.png +0 -0
  18. package/dist/lforms/webcomponent/main.js +2465 -0
  19. package/dist/lforms/webcomponent/main.js.map +1 -0
  20. package/dist/lforms/webcomponent/polyfills.js +2 -0
  21. package/dist/lforms/webcomponent/polyfills.js.map +1 -0
  22. package/dist/lforms/webcomponent/runtime.js +2 -0
  23. package/dist/lforms/webcomponent/runtime.js.map +1 -0
  24. package/dist/lforms/webcomponent/scripts.js +3 -0
  25. package/dist/lforms/webcomponent/scripts.js.map +1 -0
  26. package/dist/lforms/webcomponent/styles.css +3 -0
  27. package/dist/lforms/webcomponent/styles.css.map +1 -0
  28. package/form_definition.md +381 -0
  29. package/package.json +94 -88
  30. package/r4-support.md +147 -0
  31. package/sdc-support.md +127 -0
  32. package/app/.npmignore +0 -3
  33. package/app/images/blank.gif +0 -0
  34. package/app/images/signal-stop.png +0 -0
  35. package/app/index.html +0 -10
  36. package/app/lforms.js +0 -56
  37. package/app/lforms.tpl.js +0 -964
  38. package/app/scripts/angular-packages.js +0 -11
  39. package/app/scripts/bootstrap-decorators.js +0 -31
  40. package/app/scripts/bower-index.js +0 -14
  41. package/app/scripts/fhir/R4/export.js +0 -33
  42. package/app/scripts/fhir/R4/fhirRequire.js +0 -36
  43. package/app/scripts/fhir/R4/lformsFHIR.js +0 -27260
  44. package/app/scripts/fhir/R4/sdc-export.js +0 -455
  45. package/app/scripts/fhir/R4/sdc-import.js +0 -483
  46. package/app/scripts/fhir/STU3/export.js +0 -27
  47. package/app/scripts/fhir/STU3/fhirRequire.js +0 -36
  48. package/app/scripts/fhir/STU3/lformsFHIR.js +0 -27291
  49. package/app/scripts/fhir/STU3/sdc-export.js +0 -493
  50. package/app/scripts/fhir/STU3/sdc-import.js +0 -494
  51. package/app/scripts/fhir/diagnostic-report.js +0 -707
  52. package/app/scripts/fhir/export-common.js +0 -209
  53. package/app/scripts/fhir/expression-processor.js +0 -670
  54. package/app/scripts/fhir/extensions/rendering-style.js +0 -23
  55. package/app/scripts/fhir/fhir-common.js +0 -3
  56. package/app/scripts/fhir/obs-prepop.mjs +0 -97
  57. package/app/scripts/fhir/runtime-common.js +0 -33
  58. package/app/scripts/fhir/sdc-common.js +0 -334
  59. package/app/scripts/fhir/sdc-export-common.js +0 -1046
  60. package/app/scripts/fhir/sdc-import-common.js +0 -1343
  61. package/app/scripts/fhir/versions.js +0 -8
  62. package/app/scripts/index.js +0 -20
  63. package/app/scripts/lforms-config.js +0 -12
  64. package/app/scripts/lforms-constants.js +0 -10
  65. package/app/scripts/lforms-controllers.js +0 -1196
  66. package/app/scripts/lforms-directives.js +0 -424
  67. package/app/scripts/lforms-index.js +0 -20
  68. package/app/scripts/lib/cross_browser.js +0 -21
  69. package/app/scripts/lib/lforms-data.js +0 -3591
  70. package/app/scripts/lib/lforms-hl7.js +0 -525
  71. package/app/scripts/lib/lforms-util.js +0 -1122
  72. package/app/scripts/lib/lforms-validate.js +0 -277
  73. package/app/scripts/lib/polyfill.js +0 -81
  74. package/app/scripts/themeList.js +0 -17
  75. package/app/styles/lforms.css +0 -1219
  76. package/app/styles/redmond-theme/README.txt +0 -2
  77. package/app/styles/redmond-theme/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  78. package/app/styles/redmond-theme/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  79. package/app/styles/redmond-theme/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  80. package/app/styles/redmond-theme/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  81. package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  82. package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  83. package/app/styles/redmond-theme/images/ui-icons_217bc0_256x240.png +0 -0
  84. package/app/styles/redmond-theme/images/ui-icons_2e83ff_256x240.png +0 -0
  85. package/app/styles/redmond-theme/images/ui-icons_469bdd_256x240.png +0 -0
  86. package/app/styles/redmond-theme/images/ui-icons_6da8d5_256x240.png +0 -0
  87. package/app/styles/redmond-theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  88. package/app/styles/redmond-theme/images/ui-icons_d8e7f3_256x240.png +0 -0
  89. package/app/styles/redmond-theme/images/ui-icons_f9bd01_256x240.png +0 -0
  90. package/app/styles/redmond-theme/jquery-ui.css +0 -1311
  91. package/app/styles/redmond-theme/jquery-ui.min.css +0 -7
  92. package/app/styles/redmond-theme/theme.css +0 -443
  93. package/app/styles/themes.css +0 -1541
  94. package/app/styles/themes.css.map +0 -9
  95. package/app/styles/themes.scss +0 -411
  96. package/app/version.json +0 -1
  97. package/app/views/partials/attachment.html +0 -38
  98. package/app/views/partials/field-answers.html +0 -67
  99. package/app/views/partials/field-units.html +0 -24
  100. package/app/views/partials/form-controls.html +0 -19
  101. package/app/views/partials/form-header.html +0 -35
  102. package/app/views/partials/form-options.html +0 -50
  103. package/app/views/partials/form-title.html +0 -20
  104. package/app/views/partials/form-view.html +0 -13
  105. package/app/views/partials/item-options.html +0 -48
  106. package/app/views/partials/item.html +0 -126
  107. package/app/views/partials/itemPrefixAndText.html +0 -4
  108. package/app/views/partials/layout-horizontal.html +0 -156
  109. package/app/views/partials/layout-matrix.html +0 -145
  110. package/app/views/partials/popover-content.html +0 -1
  111. package/app/views/partials/repeating-button.html +0 -16
  112. package/app/views/partials/table-item.html +0 -30
  113. package/app/views/partials/template-table.html +0 -69
  114. package/app/views/partials/uib-popover-templates/uib-popover-template.html +0 -13
  115. package/app/views/partials/uib-popover-templates/uib-popover.html +0 -11
@@ -1,670 +0,0 @@
1
- // Processes FHIR Expression Extensions
2
- // There are three types of expressions: FHIRPath, x-fhir-query (to a FHIR
3
- // server), and CQL (but we do not yet support CQL).
4
- // Various extensions have an Expression as a value, such as variable,
5
- // initialExpression, calculatedExpression, and answerExpression. When the
6
- // Expression contains a name, that creates a variable which can be used by
7
- // other Expressions defined either on the same item or a child item.
8
- //
9
- // The general processing pattern is depth-first traversal of the "tree" of the
10
- // Questionnaire's items, and while we go through the expressions we keep track
11
- // of whether a field has changed and whether a variable has changed. If there
12
- // are any changes, we traverse the tree again, but if the only things that
13
- // changed were variables, then we only have to traverse the parts of the tree
14
- // for which those variables are in scope.
15
- //
16
- // A further complication is that x-fhir-query Expressions require an
17
- // asynchronous call. So, after each traversal, we have to wait for those to
18
- // complete before starting the next traversal (if one is needed). This is also
19
- // why the main function, runCalculations, returns a promise that resolves
20
- // when the expression run has been completed.
21
- //
22
- // Also, because there is possibility of asynchronous queries, we have to handle
23
- // the fact that runCalculations might get called again while before the first
24
- // call has finished.
25
-
26
- export let ExpressionProcessor;
27
- const deepEqual = require('fast-deep-equal'); // faster than JSON.stringify
28
-
29
- (function() {
30
- "use strict";
31
- // A class whose instances handle the running of FHIR expressions.
32
-
33
- /**
34
- * Constructor.
35
- * @param lfData an instance of LForms.LFormsData. The _fhir attribute
36
- * should be set before this is called.
37
- */
38
- ExpressionProcessor = function(lfData) {
39
- // A cache of x-fhir-query URIs to results
40
- this._queryCache = {};
41
-
42
- // An array of pending x-fhir-query results
43
- this._pendingQueries = [];
44
-
45
- // Keeps track of whether a request to run the calculations has come in
46
- // while we were already busy.
47
- this._pendingRun = false;
48
-
49
- // The promise returned by runCalculations, when a run is active.
50
- this._currentRunPromise = undefined;
51
-
52
- this._lfData = lfData;
53
- if (!lfData._fhir)
54
- throw new Error('lfData._fhir should be set');
55
- this._fhir = lfData._fhir;
56
- this._compiledExpressions = {};
57
- };
58
-
59
-
60
- ExpressionProcessor.prototype = {
61
- /**
62
- * Runs the FHIR expressions in the form. This the main function in this
63
- * module.
64
- * @param includeInitialExpr whether to include the "initialExpression"
65
- * expressions (which should only be run once, after asynchronous loads
66
- * from questionnaire-launchContext have been completed).
67
- * @return a Promise that resolves when the expressions have been run, and
68
- * there are no pending runs left to do.
69
- */
70
- runCalculations: function(includeInitialExpr) {
71
- // Defer running calculations while we are waiting for earlier runs to
72
- // finish.
73
- if (this._currentRunPromise) // then we will just return that promise
74
- this._pendingRun = true; // so we know to run them when we can
75
- else {
76
- this._pendingRun = false; // clear this because we are running them now
77
- this._runStart = new Date();
78
- // Create an export of Questionnaire for the %questionnaire variable in
79
- // FHIRPath. We only need to do this once per form.
80
- var lfData = this._lfData;
81
- if (!lfData._fhirVariables.questionnaire) {
82
- lfData._fhirVariables.questionnaire =
83
- this._fhir.SDC.convertLFormsToQuestionnaire(lfData);
84
- }
85
- this._regenerateQuestionnaireResp();
86
- self = this;
87
- this._currentRunPromise =
88
- this._asyncRunCalculations(includeInitialExpr, false).then(()=>{
89
- // At this point, every promise for the pending queries has been
90
- // resolved, and we are done.
91
- console.log("Ran expressions in "+(new Date()-self._runStart)+" ms");
92
- if (!self._firstExpressionRunComplete) // if this is the first run
93
- self._firstExpressionRunComplete = true;
94
- self._currentRunPromise = undefined;
95
- if (self._pendingRun)
96
- return self.runCalculations(false); // will set self._currentRunPromise again
97
- },
98
- (failureReason) => {
99
- console.log("Run of expressions failed; reason follows");
100
- console.log(failureReason);
101
- self._currentRunPromise = undefined;
102
- self._pendingRun = false;
103
- self._pendingQueries = []; // reset
104
- throw failureReason;
105
- });
106
- }
107
- return this._currentRunPromise;
108
- },
109
-
110
-
111
- /**
112
- * Waits for any pending queries.
113
- * @return a Promise the resolves when everything is finished, including any
114
- * pending re-run request. The returned promise will be rejected if something
115
- * goes wrong.
116
- * @return the same map about changes as in _evaluateExpressions.
117
- */
118
- _handlePendingQueries: function() {
119
- const self = this;
120
- return Promise.allSettled(this._pendingQueries).then(function(results) {
121
- self._pendingQueries = []; // reset
122
- var varsChanged=false, fieldsChanged=false;
123
- for (var i=0, len=results.length;
124
- (!varsChanged || !fieldsChanged) && i<len; ++i) {
125
- var changes = results[i].value;
126
- if (changes) {
127
- varsChanged = varsChanged || changes.variables;
128
- fieldsChanged = fieldsChanged || changes.fields;
129
- }
130
- else if (results[i].status == 'rejected')
131
- return Promise.reject(results[i].reason);
132
- }
133
- return {fields: fieldsChanged, variables: varsChanged};
134
- });
135
- },
136
-
137
-
138
- /**
139
- * This is conceptually a part of runCalculations, but it is this part of
140
- * it that might need to call itself if fields or variables update.
141
- * The basic algorithm is a depth-first traversal of the items to run their
142
- * expressions. Some of those might be asynchronous (e.g. x-fhir-query
143
- * variables), so we wait for those to complete before looking at what has
144
- * changed and deciding whether to run the expressions again.
145
- * @param includeInitialExpr whether to include the "initialExpression"
146
- * expressions (which should only be run once, after asynchronous loads
147
- * from questionnaire-launchContext have been completed).
148
- * @param changesByVarsOnly whether to run all field expressions, or just the ones
149
- * that are likely to have been affected by changes from variable expressions.
150
- * @return a promise that resolves when all Expressions which needed to be
151
- * processed have been processed and the values have stablized.
152
- */
153
- _asyncRunCalculations: function(includeInitialExpr, changesByVarsOnly) {
154
- const self = this;
155
- const lfData = this._lfData;
156
- var changes = null; // data about what the calculations changed
157
- changes = this._evaluateExpressions(lfData, includeInitialExpr, changesByVarsOnly);
158
- // Wait for any asynchronous queries to complete
159
- return this._handlePendingQueries().then(function(queryChanges) {
160
- // Two types of reported changes are possible -- variables and field values
161
- let varsChanged = changes.variables || queryChanges.variables;
162
- let fieldsChanged = changes.fields || queryChanges.fields;
163
- if (varsChanged || fieldsChanged) {
164
- // Run again
165
- if (fieldsChanged)
166
- self._regenerateQuestionnaireResp();
167
- let onlyVarsChanged = !fieldsChanged;
168
- return self._asyncRunCalculations(includeInitialExpr, onlyVarsChanged);
169
- }
170
- });
171
- },
172
-
173
-
174
- /**
175
- * Updates the value of an item's FHIR variable. If the variable value has changed,
176
- * item._varChanged will be set to true.
177
- * @param item the item on which the variable is defined
178
- * @param varName the name of the variable
179
- * @param newVal the new value of the variable.
180
- * @return whether the value changed.
181
- */
182
- _updateItemVariable: function (item, varName, newVal) {
183
- var oldVal = item._fhirVariables[varName];
184
- item._fhirVariables[varName] = newVal;
185
- if (!deepEqual(oldVal, newVal)) {
186
- item._varChanged = true; // flag for re-running expressions.
187
- }
188
- return item._varChanged;
189
- },
190
-
191
-
192
-
193
- /**
194
- * Evaluates the expressions for a given item.
195
- * @param item an LFormsData or item from LFormsData.
196
- * @param includeInitialExpr whether or not to run expressions from
197
- * initialExpression extensions (which should only be run when the form is
198
- * loaded).
199
- * @param changesByVarsOnly whether to run all field expressions, or just the ones
200
- * that are likely to have been affected by changes from variable expressions.
201
- * @return a map with two fields, "variables" and "fields", which will be
202
- * present and set to true if the evaluation changed variables (including
203
- * implicit variables created by named expressions of some other
204
- * non-variable type) or field values, respectively.
205
- */
206
- _evaluateExpressions: function(item, includeInitialExpr, changesByVarsOnly) {
207
- var rtn = {};
208
- // If changesByVarsOnly, for any item that has _varChanged set, we run any field
209
- // expressions that are within that group (or item).
210
- if (changesByVarsOnly && item.items && item._varChanged) {
211
- item._varChanged = false; // clear flag
212
- changesByVarsOnly = false; // clear it, so we process this and all child items
213
- }
214
- if (!changesByVarsOnly) { // process this and all child items
215
- item._varChanged = false; // clear flag in case it was set
216
- var fhirExt = item._fhirExt;
217
- if (fhirExt) {
218
- var sdc = this._fhir.SDC;
219
- var exts = item._exprExtensions;
220
- if (exts) {
221
- var fieldChanged = false;
222
- var self = this;
223
- for (let i=0, len=exts.length; i<len; ++i) {
224
- let ext = exts[i];
225
- // Skip initialExpressions if we are not including those.
226
- if (includeInitialExpr || ext.url != sdc.fhirExtInitialExp) {
227
- // Skip calculated expressions of editable fields for which the user has
228
- // edited the value.
229
- let isCalcExp = ext.url == sdc.fhirExtCalculatedExp;
230
- // Compare the item.value to the last calculated value (if any). If
231
- // they differ, then the user has edited the field, and in that case we
232
- // skip setting the value and halt further calculations for the field.
233
- if (isCalcExp && !item._userModifiedCalculatedValue &&
234
- item._calculatedValue &&
235
- !deepEqual(item._calculatedValue, item.value)) {
236
- item._userModifiedCalculatedValue = true;
237
- }
238
-
239
- if (!isCalcExp || !item._userModifiedCalculatedValue) {
240
- let varName = ext.valueExpression.name; // i.e., a variable name
241
- var itemVars;
242
- if (varName)
243
- itemVars = this._getItemVariables(item);
244
- var oldVal;
245
- let newVal;
246
- var updateValue = false
247
- if (ext.valueExpression.language=="text/fhirpath") {
248
- if (varName) {
249
- // Temporarily delete the old value, so we don't have
250
- // circular references.
251
- oldVal = item._fhirVariables[varName];
252
- delete item._fhirVariables[varName];
253
- }
254
- newVal = this._evaluateFHIRPath(item,
255
- ext.valueExpression.expression);
256
- updateValue = true;
257
- if (varName)
258
- item._fhirVariables[varName] = oldVal; // update handled below
259
- }
260
- else if (ext.valueExpression.language=="application/x-fhir-query") {
261
- let queryURL = ext.valueExpression.expression;
262
- // The expression might have embedded FHIRPath in the URI, inside {{...}}
263
- // Use "undefinedExprVal" to keep track of whether one of
264
- // the embedded FHIRPath expressions returns undefined (or
265
- // null).
266
- let undefinedExprVal = false;
267
- queryURL = queryURL.replace(/\{\{([^}]+)\}\}/g, function(match, fpExp) {
268
- // Replace the FHIRPath with the evaluated expressions
269
- let result = self._evaluateFHIRPath(item, fpExp)[0];
270
- if (result === null || result === undefined)
271
- undefinedExprVal = true; // i.e., URL likely not usable
272
- return undefinedExprVal ? '' : '' + result;
273
- });
274
- if (!item._currentFhirQueryURLs)
275
- item._currentFhirQueryURLs = {};
276
- let oldQueryURL = item._currentFhirQueryURLs[varName];
277
- // If queryURL is not a new value, we don't need to do anything
278
- if (queryURL != oldQueryURL) {
279
- item._currentFhirQueryURLs[varName] = queryURL;
280
- if (!undefinedExprVal) {
281
- // Look for a cached result
282
- if (this._queryCache.hasOwnProperty(queryURL)) {
283
- newVal = this._queryCache[queryURL];
284
- updateValue = true;
285
- }
286
- else { // query not cached
287
- let fetchPromise = this._fetch(queryURL);
288
- // Store the promise that handles the response. We
289
- // will have to wait for it later.
290
- this._pendingQueries.push(fetchPromise.then(function(parsedJSON) {
291
- newVal = (self._queryCache[queryURL] = parsedJSON);
292
- }, function fail(e) {
293
- console.error("Unable to load FHIR data from "+queryURL);
294
- }).then(function() {
295
- // Update the item with the fetched value, and
296
- // update the variable if there was a name defined.
297
- var fChanged = self._updateItemFromExp(
298
- item, ext.url, varName, newVal, isCalcExp);
299
- if (varName) {
300
- var vChanged = self._updateItemVariable(item, varName,
301
- newVal);
302
- }
303
- return {fields: fChanged, variables: vChanged};
304
- }));
305
- }
306
- }
307
- }
308
- }
309
- // else CQL (TBD)
310
-
311
- if (updateValue) {
312
- // Update the item with the fetched value, and
313
- // update the variable if there was a name defined.
314
- var fChanged = this._updateItemFromExp(
315
- item, ext.url, varName, newVal, isCalcExp);
316
- fieldChanged = fieldChanged || fChanged;
317
- if (varName)
318
- this._updateItemVariable(item, varName, newVal);
319
- }
320
- }
321
- }
322
- }
323
- rtn = {fields: fieldChanged, variables: item._varChanged};
324
- }
325
- }
326
- }
327
-
328
- // Process child items
329
- if (item.items) {
330
- var childChanges;
331
- for (var j=0, len=item.items.length; j<len; ++j) {
332
- // Note: We need to process all the child items; we cannot do an
333
- // early loop exit based on rtn.
334
- childChanges = this._evaluateExpressions(item.items[j], includeInitialExpr, changesByVarsOnly);
335
- if (childChanges.fields)
336
- rtn.fields = true;
337
- if (childChanges.variables)
338
- rtn.variables = true;
339
- }
340
- }
341
-
342
- return rtn;
343
- },
344
-
345
-
346
- /**
347
- * Regenerates the QuestionnaireResponse resource and the map from
348
- * LFormsData _elementIDs to items in the QuestionnaireResponse.
349
- */
350
- _regenerateQuestionnaireResp: function() {
351
- var questResp = this._fhir.SDC.convertLFormsToQuestionnaireResponse(this._lfData);
352
- this._lfData._fhirVariables.resource = questResp;
353
- this._elemIDToQRItem = this._createIDtoQRItemMap(questResp);
354
- },
355
-
356
-
357
- /**
358
- * Returns the nearest ancestor of item (or item itelf) that has
359
- * _fhirVariables defined.
360
- * @param item either an LFormsData or an item from an LFormsData.
361
- */
362
- _itemWithVars: function(item) {
363
- var itemWithVars = item;
364
- while (!itemWithVars._fhirVariables)
365
- itemWithVars = itemWithVars._parentItem; // should terminate at lfData
366
- return itemWithVars;
367
- },
368
-
369
-
370
- /**
371
- * Gets or creates if not yet initialized, the item's _fhirVariables
372
- * map (storing its variable values). This should not be called until it is
373
- * known that the item should have a _fhirVariables map.
374
- * @param item either an LFormsData or an item from an LFormsData.
375
- * @return the item's _fhirVariables map
376
- */
377
- _getItemVariables: function(item) {
378
- var rtn = item._fhirVariables;
379
- if (!rtn) {
380
- // Create a hash for variables that will have access to
381
- // variables defined higher up in the tree.
382
- item._fhirVariables = Object.create(
383
- this._itemWithVars(item)._fhirVariables);
384
- }
385
- return rtn;
386
- },
387
-
388
-
389
- /**
390
- * Fetches an x-fhir-query URL.
391
- * @param queryURL the URL (possibly relative) to fetch.
392
- * @return a Promise that resolves to the (parsed) JSON response.
393
- */
394
- _fetch: function(queryURL) {
395
- var fetchPromise;
396
- // If the queryURL is a relative URL, then if there is a FHIR
397
- // context (set via LForms.Util.setFHIRContext), use that to send
398
- // the query; otherwise just use fetch.
399
- // Also, set the format to JSON.
400
- queryURL += (queryURL.indexOf('?')>0 ? '&' : '?')+'_format=json';
401
- if (!/^https?:/.test(queryURL) && LForms.fhirContext) {
402
- fetchPromise = LForms.fhirContext.request(queryURL);
403
- }
404
- else {
405
- fetchPromise = fetch(queryURL).then(function(response) {
406
- return response.json();
407
- });
408
- }
409
- return fetchPromise;
410
- },
411
-
412
-
413
- /**
414
- * Updates an item's data following the run of an expression.
415
- * @param item either an LFormsData or an item from an LFormsData.
416
- * @param expURL the URL of the expression
417
- * @param varName variable name from the expression (if any)
418
- * @param newVal the new value of the variable (if any)
419
- * @param isCalcExp whether the expression was a calculated expression.
420
- * This could be detected from expURL, but the caller already knows it.
421
- * @return true if the field value changed
422
- */
423
- _updateItemFromExp(item, expURL, varName, newVal, isCalcExp) {
424
- var fieldChanged = false;
425
- var sdc = this._fhir.SDC;
426
- if (isCalcExp || expURL != sdc.fhirExtVariable) {
427
- if (expURL == sdc.fhirExtAnswerExp)
428
- fieldChanged = this._setItemListFromFHIRPath(item, newVal);
429
- else if (expURL == sdc.fhirExtEnableWhenExp) {
430
- // The new value should be a boolean. Coerce it to a boolean, and
431
- // report a warning if it was not a boolean.
432
- var actualNewVal = newVal[0];
433
- newVal = !!actualNewVal;
434
- if (newVal !== actualNewVal) {
435
- LForms.Util.showWarning('An expression from enableWhenExpression '+
436
- 'did not resolve to a Boolean as required', item);
437
- }
438
- if (varName) { // if there is a variable name defined, a change in the value matters
439
- var oldVal = !!item._enableWhenExpVal; // _enableWhenExpVal could be undefined
440
- fieldChanged = oldVal != newVal;
441
- }
442
- item._enableWhenExpVal = newVal;
443
- }
444
- else // else initial or calculated expression
445
- fieldChanged = this._setItemValueFromFHIRPath(item, newVal, isCalcExp);
446
- }
447
- return fieldChanged;
448
- },
449
-
450
-
451
- /**
452
- * Evaluates the given FHIRPath expression defined in an extension on the
453
- * given item.
454
- * @param item either an LFormsData or an item from an LFormsData.
455
- * @param expression the FHIRPath to evaluate with the context of item's
456
- * equivalent node in the QuestionnaireResponse.
457
- * @returns the result of the expression.
458
- */
459
- _evaluateFHIRPath: function(item, expression) {
460
- var fhirPathVal;
461
- // Find the item-level fhirpathVars
462
- var itemVars = this._itemWithVars(item)._fhirVariables;
463
- try {
464
- // We need to flatten the fhirVariables chain into a simple hash of key/
465
- // value pairs.
466
- var fVars = {};
467
- for (var k in itemVars)
468
- fVars[k] = itemVars[k];
469
- const fhirContext = item._elementId ? this._elemIDToQRItem[item._elementId] :
470
- this._lfData._fhirVariables.resource;
471
- var compiledExpr = this._compiledExpressions[expression];
472
- if (!compiledExpr) {
473
- compiledExpr = this._compiledExpressions[expression] =
474
- this._fhir.fhirpath.compile(expression, this._fhir.fhirpathModel);
475
- }
476
- fhirPathVal = compiledExpr(fhirContext, fVars);
477
- }
478
- catch (e) {
479
- // Sometimes an expression will rely on data that hasn't been filled in
480
- // yet.
481
- console.log(e);
482
- }
483
- return fhirPathVal;
484
- },
485
-
486
-
487
- /**
488
- * Returns a hash from the LForms _elementId of each item to the
489
- * corresponding QuestionnaireResponse item.
490
- * @param qr the QuestionnaireResponse corresponding to the current
491
- * LFormsData.
492
- */
493
- _createIDtoQRItemMap: function(qr) {
494
- var map = {};
495
- this._addToIDtoQRItemMap(this._lfData, qr, map);
496
- return map;
497
- },
498
-
499
-
500
- /**
501
- * Adds to the map from LFormsData items to QuestionnaireResponse items and
502
- * returns the number of items added.
503
- * @param lfItem an LFormsData, or an item within it.
504
- * @param qrItem the corresponding QuestionnaireResponse or an item within
505
- * it.
506
- * @param map the map to which entries will be added.
507
- * @return the number of items added to the map.
508
- */
509
- _addToIDtoQRItemMap: function(lfItem, qrItem, map) {
510
- var added = 0;
511
- if (lfItem.linkId === qrItem.linkId) {
512
- if (lfItem.items) {
513
- // lfItem.items might contain items that don't have values, but
514
- // qrItem.item will not, so we need to skip the blank items.
515
- //
516
- // Also, for a repeating question, there will be multiple answers on an
517
- // qrItem.item, but repeats of the item in lfItem.items with one answer
518
- // each, unless answerCardinality is '*' (list items), in which case
519
- // there can be multiple answers per lforms item.
520
-
521
- // LForms does not currently support items that contain both answers
522
- // and child items, but I am trying to accomodate that here for the
523
- // future.
524
- if (qrItem && qrItem.item && qrItem.item.length > 0) {
525
- var lfItems = lfItem.items, qrItems = qrItem.item;
526
- var numLFItems = lfItems.length;
527
- for (var i=0, qrI=0, len=qrItems.length; qrI<len && i<numLFItems; ++qrI) {
528
- // Answers are repeated in QR, but items are repeated in LForms
529
- var qrIthItem = qrItems[qrI];
530
- var lfIthItem = lfItems[i];
531
- if (!qrIthItem.answer) {
532
- // process item anyway to handle child items with data
533
- let newlyAdded = this._addToIDtoQRItemMap(lfIthItem, qrIthItem, map);
534
- if (newlyAdded === 0) {
535
- // lfIthItem was blank, so qrIthItem must be for a following
536
- // item.
537
- --qrI; // so we try qrIthItem with the next lfIthItem
538
- }
539
- else
540
- added += newlyAdded;
541
- ++i;
542
- }
543
- else { // there are answers on the qrIthItem item
544
- var numAnswers = qrIthItem.answer ? qrIthItem.answer.length : 0;
545
- for (var a=0; a<numAnswers; ++i) {
546
- if (i >= numLFItems)
547
- throw new Error('Logic error in _addToIDtoQRITemMap; ran out of lfItems');
548
- let lfIthItem = lfItems[i];
549
- let newlyAdded = this._addToIDtoQRItemMap(lfIthItem, qrIthItem, map);
550
- if (newlyAdded != 0) { // lfItems[i] was not blank
551
- if (Array.isArray(lfIthItem.value))
552
- a += lfIthItem.value.length;
553
- else
554
- a += 1;
555
- }
556
- added += newlyAdded;
557
- }
558
- }
559
- }
560
- }
561
- }
562
-
563
- // this item has _elementId and has a value
564
- if (lfItem._elementId && (added || lfItem.value !== undefined && lfItem.value !== null && lfItem.value !== "")) {
565
- if (!qrItem) { // if there is data in lfItem, there should be a qrItem
566
- throw new Error('Logic error in _addToIDtoQRItemMap; missing qrItem');
567
- }
568
- else {
569
- map[lfItem._elementId] = qrItem;
570
- added += 1;
571
- }
572
- }
573
- }
574
- return added;
575
- },
576
-
577
-
578
- /**
579
- * Assigns the given list result to the item. If the list has changed, the
580
- * field is cleared.
581
- * @param list an array of list items computed from a FHIRPath expression.
582
- * @return true if the list changed
583
- */
584
- _setItemListFromFHIRPath: function(item, list) {
585
- let currentList = item.answers;
586
- let hasCurrentList = !!currentList && Array.isArray(currentList);
587
- let listHasData = !!list && Array.isArray(list);
588
- let changed = (hasCurrentList != listHasData) ||
589
- listHasData && (list.length != currentList.length);
590
- let newList = []; // a reformatted version of "list"
591
- const scoreURI = this._fhir.SDC.fhirExtUrlOptionScore;
592
- if (listHasData) {
593
- // list should be an array of any item type, including Coding.
594
- // (In R5, FHIR will start suppoing lists of types other than Coding.)
595
- for (let i=0, len=list.length; i<len; ++i) {
596
- // Assume type "object" means a coding, and that otherwise what we have
597
- // is something useable as display text. It is probably necessary to
598
- // convert them to strings in that case, which means that in the future
599
- // (R5), we might have to save/re-create the original data type and value.
600
- // Work will need to be done to autocomplete-lhc to support data objects
601
- // associated with list values.
602
- let entry = list[i], newEntry = (newList[i] = {});
603
- if (typeof entry === 'object') {
604
- let code = entry.code;
605
- if (code !== undefined)
606
- newEntry.code = code;
607
- let display = entry.display;
608
- if (display !== undefined)
609
- newEntry.text = display;
610
- let system = entry.system;
611
- if (system !== undefined)
612
- newEntry.system = system;
613
- // A Coding can have the extension for scores
614
- let scoreExt = item._fhirExt && item._fhirExt[scoreURI];
615
- if (scoreExt)
616
- newEntry.score = scoreExt[0].valueDecimal;
617
- }
618
- else
619
- newEntry.text = '' + entry;
620
- if (!changed) {
621
- changed = (!hasCurrentList ||
622
- !this._lfData._objectEqual(newEntry, currentList[i]));
623
- }
624
- }
625
- }
626
-
627
- if (changed) {
628
- item.answers = newList;
629
- this._lfData._updateAutocompOptions(item, true);
630
- this._lfData._resetItemValueWithModifiedAnswers(item);
631
- }
632
- return changed;
633
- },
634
-
635
-
636
- /**
637
- * Assigns the given FHIRPath result to the given item.
638
- * @param item the item from the LFormsData object that is receiving the new
639
- * value.
640
- * @param fhirPathRes the result of a FHIRPath evaluation.
641
- * @param isCalcExp whether this is from a calculated expression, in which
642
- * case a decision will be made whether to skip setting the value.
643
- * @return true if the value changed
644
- */
645
- _setItemValueFromFHIRPath: function(item, fhirPathRes, isCalcExp) {
646
- var oldVal = item.value;
647
- var fhirPathVal;
648
- if (fhirPathRes !== undefined)
649
- fhirPathVal = fhirPathRes[0];
650
- if (fhirPathVal === null || fhirPathVal === undefined)
651
- item.value = undefined;
652
- else
653
- this._fhir.SDC._processFHIRValues(item, fhirPathRes); // sets item.value
654
- let changed = !deepEqual(oldVal, item.value);
655
- item._calculatedValue = item.value;
656
- // If this is the first run of the expressions, and there is
657
- // saved user data, then we check whether the calculated value matches
658
- // what the user entered (or erased) and if it doesn't, we halt further
659
- // calculations for this field and restore the saved value.
660
- if (changed && isCalcExp && !this._firstExpressionRunComplete
661
- && this._lfData.hasSavedData) {
662
- item._userModifiedCalculatedValue = true;
663
- item.value = oldVal;
664
- changed = false;
665
- }
666
- return changed
667
- },
668
- };
669
-
670
- })();
@@ -1,23 +0,0 @@
1
- var extURL = 'http://hl7.org/fhir/StructureDefinition/rendering-style';
2
-
3
- /**
4
- * A generic API for processing an extension found on some node in the
5
- * Questionnaire structure being imported.
6
- * (The parameter list will likely get more complicated in the future.)
7
- * @param lfNode the node in the LFormsData structure on which the information
8
- * from the extension will be stored.
9
- * @param fieldName the field name on which the extension was found (e.g.
10
- * 'item' or 'title'). This forms part of the field name on lfNode where the
11
- * data from the extension will be stored.
12
- * @param extNode the extension's structure with its data.
13
- */
14
- function processExtension(lfNode, fieldName, extNode) {
15
- var css = extNode.valueString;
16
- if (css)
17
- lfNode['_'+fieldName+'CSS'] = css;
18
- }
19
-
20
- export default {
21
- extURL: extURL,
22
- processExtension: processExtension
23
- };