lforms 29.3.0 → 33.2.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 (116) hide show
  1. package/CHANGELOG.md +261 -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 +100 -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 +2471 -0
  17. package/dist/lforms/webcomponent/magnifying_glass.png +0 -0
  18. package/dist/lforms/webcomponent/main.js +2464 -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 +382 -0
  29. package/lforms-33.2.0.tgz +0 -0
  30. package/package.json +94 -88
  31. package/r4-support.md +147 -0
  32. package/sdc-support.md +127 -0
  33. package/app/.npmignore +0 -3
  34. package/app/images/blank.gif +0 -0
  35. package/app/images/signal-stop.png +0 -0
  36. package/app/index.html +0 -10
  37. package/app/lforms.js +0 -56
  38. package/app/lforms.tpl.js +0 -964
  39. package/app/scripts/angular-packages.js +0 -11
  40. package/app/scripts/bootstrap-decorators.js +0 -31
  41. package/app/scripts/bower-index.js +0 -14
  42. package/app/scripts/fhir/R4/export.js +0 -33
  43. package/app/scripts/fhir/R4/fhirRequire.js +0 -36
  44. package/app/scripts/fhir/R4/lformsFHIR.js +0 -27260
  45. package/app/scripts/fhir/R4/sdc-export.js +0 -455
  46. package/app/scripts/fhir/R4/sdc-import.js +0 -483
  47. package/app/scripts/fhir/STU3/export.js +0 -27
  48. package/app/scripts/fhir/STU3/fhirRequire.js +0 -36
  49. package/app/scripts/fhir/STU3/lformsFHIR.js +0 -27291
  50. package/app/scripts/fhir/STU3/sdc-export.js +0 -493
  51. package/app/scripts/fhir/STU3/sdc-import.js +0 -494
  52. package/app/scripts/fhir/diagnostic-report.js +0 -707
  53. package/app/scripts/fhir/export-common.js +0 -209
  54. package/app/scripts/fhir/expression-processor.js +0 -670
  55. package/app/scripts/fhir/extensions/rendering-style.js +0 -23
  56. package/app/scripts/fhir/fhir-common.js +0 -3
  57. package/app/scripts/fhir/obs-prepop.mjs +0 -97
  58. package/app/scripts/fhir/runtime-common.js +0 -33
  59. package/app/scripts/fhir/sdc-common.js +0 -334
  60. package/app/scripts/fhir/sdc-export-common.js +0 -1046
  61. package/app/scripts/fhir/sdc-import-common.js +0 -1343
  62. package/app/scripts/fhir/versions.js +0 -8
  63. package/app/scripts/index.js +0 -20
  64. package/app/scripts/lforms-config.js +0 -12
  65. package/app/scripts/lforms-constants.js +0 -10
  66. package/app/scripts/lforms-controllers.js +0 -1196
  67. package/app/scripts/lforms-directives.js +0 -424
  68. package/app/scripts/lforms-index.js +0 -20
  69. package/app/scripts/lib/cross_browser.js +0 -21
  70. package/app/scripts/lib/lforms-data.js +0 -3591
  71. package/app/scripts/lib/lforms-hl7.js +0 -525
  72. package/app/scripts/lib/lforms-util.js +0 -1122
  73. package/app/scripts/lib/lforms-validate.js +0 -277
  74. package/app/scripts/lib/polyfill.js +0 -81
  75. package/app/scripts/themeList.js +0 -17
  76. package/app/styles/lforms.css +0 -1219
  77. package/app/styles/redmond-theme/README.txt +0 -2
  78. package/app/styles/redmond-theme/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  79. package/app/styles/redmond-theme/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  80. package/app/styles/redmond-theme/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  81. package/app/styles/redmond-theme/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  82. package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  83. package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  84. package/app/styles/redmond-theme/images/ui-icons_217bc0_256x240.png +0 -0
  85. package/app/styles/redmond-theme/images/ui-icons_2e83ff_256x240.png +0 -0
  86. package/app/styles/redmond-theme/images/ui-icons_469bdd_256x240.png +0 -0
  87. package/app/styles/redmond-theme/images/ui-icons_6da8d5_256x240.png +0 -0
  88. package/app/styles/redmond-theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  89. package/app/styles/redmond-theme/images/ui-icons_d8e7f3_256x240.png +0 -0
  90. package/app/styles/redmond-theme/images/ui-icons_f9bd01_256x240.png +0 -0
  91. package/app/styles/redmond-theme/jquery-ui.css +0 -1311
  92. package/app/styles/redmond-theme/jquery-ui.min.css +0 -7
  93. package/app/styles/redmond-theme/theme.css +0 -443
  94. package/app/styles/themes.css +0 -1541
  95. package/app/styles/themes.css.map +0 -9
  96. package/app/styles/themes.scss +0 -411
  97. package/app/version.json +0 -1
  98. package/app/views/partials/attachment.html +0 -38
  99. package/app/views/partials/field-answers.html +0 -67
  100. package/app/views/partials/field-units.html +0 -24
  101. package/app/views/partials/form-controls.html +0 -19
  102. package/app/views/partials/form-header.html +0 -35
  103. package/app/views/partials/form-options.html +0 -50
  104. package/app/views/partials/form-title.html +0 -20
  105. package/app/views/partials/form-view.html +0 -13
  106. package/app/views/partials/item-options.html +0 -48
  107. package/app/views/partials/item.html +0 -126
  108. package/app/views/partials/itemPrefixAndText.html +0 -4
  109. package/app/views/partials/layout-horizontal.html +0 -156
  110. package/app/views/partials/layout-matrix.html +0 -145
  111. package/app/views/partials/popover-content.html +0 -1
  112. package/app/views/partials/repeating-button.html +0 -16
  113. package/app/views/partials/table-item.html +0 -30
  114. package/app/views/partials/template-table.html +0 -69
  115. package/app/views/partials/uib-popover-templates/uib-popover-template.html +0 -13
  116. package/app/views/partials/uib-popover-templates/uib-popover.html +0 -11
@@ -1,1343 +0,0 @@
1
- var LForms = require('../lforms-index');
2
-
3
- /**
4
- * Defines SDC import functions that are the same across the different FHIR
5
- * versions. The function takes SDC namespace object defined in the sdc export
6
- * code, and adds additional functions to it.
7
- */
8
- function addCommonSDCImportFns(ns) {
9
- "use strict";
10
-
11
- var self = ns;
12
-
13
- // FHIR extension urls
14
- self.fhirExtUrlCardinalityMin = "http://hl7.org/fhir/StructureDefinition/questionnaire-minOccurs";
15
- self.fhirExtUrlCardinalityMax = "http://hl7.org/fhir/StructureDefinition/questionnaire-maxOccurs";
16
- self.fhirExtUrlItemControl = "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl";
17
- self.fhirExtUrlUnit = "http://hl7.org/fhir/StructureDefinition/questionnaire-unit";
18
- self.fhirExtUrlUnitOption = "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption";
19
- self.fhirExtUrlOptionPrefix = "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix";
20
- self.fhirExtVariable = "http://hl7.org/fhir/StructureDefinition/variable";
21
- self.fhirExtUrlMinValue = "http://hl7.org/fhir/StructureDefinition/minValue";
22
- self.fhirExtUrlMaxValue = "http://hl7.org/fhir/StructureDefinition/maxValue";
23
- self.fhirExtUrlMinLength = "http://hl7.org/fhir/StructureDefinition/minLength";
24
- self.fhirExtUrlRegex = "http://hl7.org/fhir/StructureDefinition/regex";
25
- self.fhirExtUrlAnswerRepeats = "http://hl7.org/fhir/StructureDefinition/questionnaire-answerRepeats";
26
- self.fhirExtUrlExternallyDefined = "http://hl7.org/fhir/StructureDefinition/questionnaire-externallydefined";
27
- self.argonautExtUrlExtensionScore = "http://fhir.org/guides/argonaut-questionnaire/StructureDefinition/extension-score";
28
- self.fhirExtUrlHidden = "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden";
29
- self.fhirExtTerminologyServer = "http://hl7.org/fhir/StructureDefinition/terminology-server";
30
- self.fhirExtUrlDataControl = "http://lhcforms.nlm.nih.gov/fhirExt/dataControl";
31
- self.fhirExtCalculatedExp = "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression";
32
- self.fhirExtInitialExp = "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression";
33
- self.fhirExtObsLinkPeriod = "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod";
34
- self.fhirExtObsExtract = 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationExtract';
35
- self.fhirExtAnswerExp = "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression";
36
- self.fhirExtEnableWhenExp = "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression";
37
- self.fhirExtChoiceOrientation = "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation";
38
- self.fhirExtLaunchContext = "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext";
39
- self.fhirExtMaxSize = "http://hl7.org/fhir/StructureDefinition/maxSize";
40
- self.fhirExtMimeType = "http://hl7.org/fhir/StructureDefinition/mimeType";
41
-
42
- self.fhirExtUrlRestrictionArray = [
43
- self.fhirExtUrlMinValue,
44
- self.fhirExtUrlMaxValue,
45
- self.fhirExtUrlMinLength,
46
- self.fhirExtUrlRegex
47
- ];
48
-
49
- // One way or the other, the following extensions are converted to lforms internal fields.
50
- // Any extensions not listed here (there are many) are recognized as lforms extensions as they are.
51
- self.handledExtensionSet = new Set([
52
- self.fhirExtUrlCardinalityMin,
53
- self.fhirExtUrlCardinalityMax,
54
- self.fhirExtUrlItemControl,
55
- self.fhirExtUrlUnit,
56
- self.fhirExtUrlUnitOption,
57
- self.fhirExtUrlOptionPrefix,
58
- self.fhirExtUrlMinValue,
59
- self.fhirExtUrlMaxValue,
60
- self.fhirExtUrlMinLength,
61
- self.fhirExtUrlRegex,
62
- self.fhirExtUrlAnswerRepeats,
63
- self.fhirExtUrlExternallyDefined,
64
- self.argonautExtUrlExtensionScore,
65
- self.fhirExtUrlHidden,
66
- self.fhirExtTerminologyServer,
67
- self.fhirExtUrlDataControl,
68
- self.fhirExtChoiceOrientation
69
- ]);
70
-
71
- // Simple functions for mapping extensions to properties in the internal structure.
72
- // Parameters:
73
- // extension: the FHIR extension object
74
- // item: The LForms item to be updated
75
- // Returns: true if the extension should still be added to the LForms item
76
- // extension array, and false/undefined otherwise.
77
- //
78
- self.extensionHandlers = {};
79
- self.extensionHandlers[self.fhirExtMaxSize] = function(extension, item) {
80
- item.maxAttachmentSize = extension.valueDecimal || extension.valueInteger; // not sure why it is decimal
81
- };
82
- self.extensionHandlers[self.fhirExtMimeType] = function(extension, item) {
83
- item.allowedAttachmentTypes || (item.allowedAttachmentTypes = []);
84
- item.allowedAttachmentTypes.push(extension.valueCode);
85
- };
86
- self.extensionHandlers[
87
- "http://hl7.org/fhir/StructureDefinition/questionnaire-initialExpression"] = function(extension, item) {
88
- // Update the URI to the current one.
89
- extension.url = 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression';
90
- return true; // add extension to LForms item
91
- };
92
-
93
- self.formLevelFields = [
94
- // Resource
95
- 'id',
96
- 'meta',
97
- 'implicitRules',
98
- 'language',
99
-
100
-
101
- // Domain Resource
102
- 'text',
103
- 'contained',
104
- 'extension',
105
- 'modifiedExtension',
106
-
107
- // Questionnaire
108
- 'date',
109
- 'version',
110
- 'identifier',
111
- 'code', // code in FHIR clashes with previous definition in lforms. It needs special handling.
112
- 'subjectType',
113
- 'derivedFrom', // New in R4
114
- 'status',
115
- 'experimental',
116
- 'publisher',
117
- 'contact',
118
- 'description',
119
- 'useContext',
120
- 'jurisdiction',
121
- 'purpose',
122
- 'copyright',
123
- 'approvalDate',
124
- 'reviewDate',
125
- 'effectivePeriod',
126
- 'url'
127
- ];
128
-
129
- self.itemLevelIgnoredFields = [
130
- 'definition'
131
- ];
132
-
133
- /**
134
- * Convert FHIR SQC Questionnaire to LForms definition
135
- *
136
- * @param fhirData - FHIR Questionnaire object
137
- * @returns {{}} - LForms json object
138
- */
139
- self.convertQuestionnaireToLForms = function (fhirData) {
140
- var target = null;
141
-
142
- if(fhirData) {
143
- target = LForms.Util.baseFormDef();
144
- self._processFormLevelFields(target, fhirData);
145
- var containedVS = self._extractContainedVS(fhirData);
146
-
147
- if(fhirData.item && fhirData.item.length > 0) {
148
- var linkIdItemMap = self._createLinkIdItemMap(fhirData);
149
- target.items = [];
150
- for( var i = 0; i < fhirData.item.length; i++) {
151
- var item = self._processQuestionnaireItem(fhirData.item[i], containedVS, linkIdItemMap);
152
- // no instructions on the questionnaire level
153
- target.items.push(item);
154
- }
155
- }
156
- target.fhirVersion = self.fhirVersion;
157
- }
158
- return target;
159
- };
160
-
161
-
162
- /**
163
- * Parse form level fields from FHIR questionnaire and assign to LForms object.
164
- *
165
- * @param lfData - LForms object to assign the extracted fields
166
- * @param questionnaire - FHIR questionnaire resource object to parse for the fields.
167
- * @private
168
- */
169
- self._processFormLevelFields = function(lfData, questionnaire) {
170
- self.copyFields(questionnaire, lfData, self.formLevelFields);
171
-
172
- // Handle title and name. In LForms, "name" is the "title", but FHIR
173
- // defines both.
174
- lfData.shortName = questionnaire.name; // computer friendly
175
- lfData.name = questionnaire.title;
176
-
177
- // Handle extensions on title
178
- if (questionnaire._title)
179
- lfData.obj_title = questionnaire._title;
180
-
181
- // For backward compatibility, we keep lforms.code as it is, and use lforms.codeList
182
- // for storing questionnaire.code. While exporting, merge lforms.code and lforms.codeList
183
- // into questionnaire.code. While importing, convert first of questionnaire.code
184
- // as lforms.code, and copy questionnaire.code to lforms.codeList.
185
- if(questionnaire.code) {
186
- // Rename questionnaire code to codeList
187
- lfData.codeList = questionnaire.code;
188
- }
189
- var codeAndSystemObj = self._getCode(questionnaire);
190
- if(codeAndSystemObj) {
191
- lfData.code = codeAndSystemObj.code;
192
- lfData.codeSystem = codeAndSystemObj.system;
193
- }
194
- };
195
-
196
-
197
- /**
198
- * Returns the number of sinificant digits in the number after, ignoring
199
- * trailing zeros. (I am including this on "self" so we can have tests for it.)
200
- */
201
- self._significantDigits = function(x) {
202
- // Based on https://stackoverflow.com/a/9539746/360782
203
- // Make sure it is a number and use the builtin number -> string.
204
- var s = "" + (+x);
205
- // The following RegExp include the exponent, which we don't need
206
- //var match = /(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(s);
207
- var match = /(\d+)(?:\.(\d+))?/.exec(s);
208
- // NaN or Infinity or integer.
209
- // We arbitrarily decide that Infinity is integral.
210
- if (!match) { return 0; }
211
- var wholeNum = match[1];
212
- var fraction = match[2];
213
- //var exponent = match[3];
214
- return wholeNum === '0' ? 0 : wholeNum.length + (fraction ? fraction.length : 0);
215
- };
216
-
217
-
218
- /**
219
- * Imports an observation's values into the given LForms item.
220
- * @param lfItem the LForms item to which a value will be assigned.
221
- * @param obs the observation whose value will be assigned to lfItem. It
222
- * assumed that obs has an appropriate data type for its value.
223
- */
224
- self.importObsValue = function(lfItem, obs) {
225
- // Get the value from obs, based on lfItem's data type. (The altertnative
226
- // seems to be looping through the keys on obs looking for something that
227
- // starts with "value".
228
- var val = null;
229
- var lfDataType = lfItem.dataType;
230
- var fhirValType = this._lformsTypesToFHIRFields[lfDataType];
231
- // fhirValType is now the FHIR data type for a Questionnaire. However,
232
- // where Questionnaire uses Coding, Observation uses CodeableConcept.
233
- if (fhirValType === 'Coding')
234
- fhirValType = 'CodeableConcept';
235
- if (fhirValType)
236
- val = obs['value'+fhirValType];
237
- if (!val && (lfDataType === 'REAL' || lfDataType === 'INT')) {
238
- // Accept initial value of type Quantity for these types.
239
- val = obs.valueQuantity;
240
- if (val)
241
- val._type = 'Quantity';
242
- }
243
-
244
- if (val) {
245
- if (!val._type && typeof val === 'object')
246
- val._type = fhirValType;
247
-
248
- // Before importing, confirm val contains a valid unit from the
249
- // item's unit list.
250
- var unitOkay = true;
251
- if (val._type === 'Quantity') {
252
- if (lfItem.units) {
253
- var matchingUnit;
254
- var valSystem = val.system;
255
- // On SMART sandbox, val.system might have a trailing slash (which is wrong, at least
256
- // for UCUM). For now, just remove it.
257
- if (valSystem && valSystem[valSystem.length - 1] === '/')
258
- valSystem = valSystem.slice(0, -1);
259
- var isUCUMUnit = valSystem === self.UCUM_URI;
260
- var ucumUnit;
261
- for (var i=0, len=lfItem.units.length; i<len && !matchingUnit; ++i) {
262
- var lfUnit = lfItem.units[i];
263
- if (lfUnit.system && (lfUnit.system===valSystem && lfUnit.code===val.code) ||
264
- !lfUnit.system && (lfUnit.name===val.unit)) {
265
- matchingUnit = lfUnit;
266
- }
267
- if (isUCUMUnit && !matchingUnit && !ucumUnit && lfUnit.system === self.UCUM_URI)
268
- ucumUnit = lfUnit;
269
- }
270
- if (!matchingUnit && ucumUnit) {
271
- // See if we can convert to the ucumUnit we found
272
- var result = LForms.ucumPkg.UcumLhcUtils.getInstance().convertUnitTo(val.code, val.value, ucumUnit.code);
273
- if (result.status === 'succeeded') {
274
- matchingUnit = ucumUnit;
275
- // Round the result to the same number of significant digits as the
276
- // input value.
277
- var originalSD = this._significantDigits(val.value);
278
- if (originalSD > 0)
279
- val.value = parseFloat(result.toVal.toPrecision(originalSD));
280
- else
281
- val.value = result.toVal;
282
- val.code = ucumUnit.code;
283
- }
284
- }
285
- if (!matchingUnit)
286
- unitOkay = false;
287
- else
288
- lfItem.unit = matchingUnit;
289
- }
290
- }
291
- if (unitOkay) {
292
- this._processFHIRValues(lfItem, [val]);
293
- }
294
- }
295
- };
296
-
297
-
298
- /**
299
- * Assigns FHIR values to an LForms item.
300
- * @param lfItem the LForms item to receive the values from fhirVals
301
- * @param fhirVals an array of FHIR values (e.g. Quantity, Coding, string, etc.).
302
- * Complex types like Quantity should have _type set to the type, if
303
- * possible, or an attempt will be made to guess the FHIR type from the
304
- * lfItem's data type.
305
- * @param setDefault if true, the default value in lfItem will be set instead
306
- * of the value.
307
- */
308
- self._processFHIRValues = function(lfItem, fhirVals, setDefault) {
309
- var lfDataType = lfItem.dataType;
310
- var isMultiple = lfItem.answerCardinality && lfItem.answerCardinality.max === '*';
311
- var answers = [];
312
- for (let i=0, len=fhirVals.length; i<len; ++i) {
313
- let fhirVal = fhirVals[i];
314
- var answer = null;
315
- if (lfDataType === 'CWE' || lfDataType === 'CNE' ) {
316
- var codings = null;
317
- if (fhirVal._type === 'CodeableConcept') {
318
- codings = fhirVal.coding;
319
- }
320
- else if (fhirVal._type === 'Coding' || typeof fhirVal === 'object') {
321
- codings = [fhirVal];
322
- }
323
- if (!codings) {
324
- // the value or the default value could be a string for 'open-choice'/CWE
325
- if (lfDataType === 'CWE') {
326
- answer = fhirVal;
327
- }
328
- }
329
- else {
330
- // Pick a Coding that is appropriate for this list item.
331
- if (lfItem.answers) {
332
- var itemAnswers = lfItem._modifiedAnswers || lfItem.answers; // _modified contains _displayText
333
- for (var k=0, kLen=codings.length; k<kLen && !answer; ++k) {
334
- var coding = codings[k];
335
- for (var j=0, jLen=itemAnswers.length; j<jLen && !answer; ++j) {
336
- var listAnswer = itemAnswers[j];
337
- var listAnswerSystem = listAnswer.system ? LForms.Util.getCodeSystem(listAnswer.system) : null;
338
- if ((!coding.system && !listAnswerSystem || coding.system === listAnswerSystem) &&
339
- ((coding.hasOwnProperty('code') && listAnswer.hasOwnProperty('code') &&
340
- coding.code===listAnswer.code) ||
341
- (coding.hasOwnProperty('display') && listAnswer.hasOwnProperty('text') &&
342
- coding.display === listAnswer.text))) {
343
- answer = itemAnswers[j]; // include label in answer text
344
- }
345
- }
346
- }
347
- }
348
- }
349
- }
350
- else if(fhirVal._type === 'Quantity' && (lfDataType === 'QTY' ||
351
- lfDataType === 'REAL' || lfDataType === 'INT')) {
352
- if (fhirVal.value !== undefined) {
353
- answer = fhirVal.value; // Associated unit is parsed in _processUnitLists
354
- }
355
- }
356
- // For date types, convert them to date objects, but only for values.
357
- // If we're setting defaultAnswer, leave them as strings.
358
- else if (!setDefault && lfItem.dataType === 'DTM' && typeof fhirVal === 'string')
359
- answer = new Date(fhirVal);
360
- else if (!setDefault && lfItem.dataType === 'DT' && typeof fhirVal === 'string')
361
- answer = LForms.Util.stringToDTDateISO(fhirVal);
362
- else {
363
- answer = fhirVal;
364
- }
365
- if (answer !== undefined)
366
- answers.push(answer);
367
- }
368
- if (isMultiple) {
369
- if (setDefault)
370
- lfItem.defaultAnswer = answers;
371
- else
372
- lfItem.value = answers;
373
- }
374
- else { // there should just be one answer
375
- if (setDefault)
376
- lfItem.defaultAnswer = answers[0];
377
- else
378
- lfItem.value = answers[0];
379
- }
380
- };
381
-
382
-
383
- /**
384
- * Get a FHIR value from an object given a partial string of hash key.
385
- * Use it where at most only one key matches.
386
- *
387
- * @param obj {object} - Object to search
388
- * @param keyRegex {regex} - Regular expression to match a key. This should
389
- * be the beginning part of the key up to the type (e.g., /^value/, to match
390
- * "valueQuantity").
391
- * @returns {*} - Corresponding value of matching key. For complex types,
392
- * such as Quantity, the type of the returned object will be present under
393
- * a _type attribute.
394
- * @private
395
- */
396
- self._getFHIRValueWithPrefixKey = function(obj, keyRegex) {
397
- var ret = null;
398
- if(typeof obj === 'object') {
399
- for(var key in obj) {
400
- var matchData = key.match(keyRegex);
401
- if (matchData) {
402
- ret = obj[key];
403
- if (ret && typeof ret === 'object') {
404
- ret = LForms.Util.deepCopy(ret); // Work with clone
405
- ret._type = key.substring(matchData[0].length);
406
- }
407
- break;
408
- }
409
- }
410
- }
411
-
412
- return ret;
413
- };
414
-
415
-
416
- /**
417
- * Process the text and prefix data.
418
- * @param lfItem {object} - LForms item object to receive the data
419
- * @param qItem {object} - Questionnaire item object (as the source)
420
- */
421
- self._processTextAndPrefix = function(lfItem, qItem) {
422
- // prefix
423
- if (qItem.prefix)
424
- lfItem.prefix = qItem.prefix;
425
- // text
426
- lfItem.question = qItem.text;
427
-
428
- // Copy item extensions
429
- for (let extField of ['_prefix', '_text']) {
430
- let extFieldData = qItem[extField];
431
- if (extFieldData)
432
- lfItem['obj'+extField] = extFieldData;
433
- }
434
- };
435
-
436
-
437
- /**
438
- * Parse questionnaire item for code and code system
439
- * @param lfItem {object} - LForms item object to assign question code
440
- * @param qItem {object} - Questionnaire item object
441
- * @private
442
- */
443
- self._processCodeAndLinkId = function (lfItem, qItem) {
444
- if(qItem.code) {
445
- lfItem.codeList = qItem.code;
446
- }
447
- var code = self._getCode(qItem);
448
- if (code) {
449
- lfItem.questionCode = code.code;
450
- lfItem.questionCodeSystem = code.system;
451
- }
452
- // use linkId as questionCode, which should not be exported as code
453
- else {
454
- lfItem.questionCode = qItem.linkId;
455
- lfItem.questionCodeSystem = "LinkId";
456
- }
457
-
458
- lfItem.linkId = qItem.linkId;
459
- };
460
-
461
-
462
- /**
463
- * Parse questionnaire item for question cardinality and answer cardinality
464
- *
465
- * @param lfItem {object} - LForms item object to assign question cardinality
466
- * @param qItem {object} - Questionnaire item object
467
- * @private
468
- */
469
- self._processFHIRQuestionAndAnswerCardinality = function(lfItem, qItem) {
470
- var min = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlCardinalityMin);
471
- var max = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlCardinalityMax);
472
- var repeats = qItem.repeats;
473
- var required = qItem.required;
474
- var answerCardinality, questionCardinality;
475
- // CNE/CWE, repeats handled by autocompleter with multiple answers in one question
476
- if (lfItem.dataType === 'CNE' || lfItem.dataType === 'CWE') {
477
- if (repeats) {
478
- answerCardinality = max ? {max: max.valueInteger.toString()} : {max: "*"};
479
- }
480
- else {
481
- answerCardinality = {max: "1"};
482
- }
483
- if (required) {
484
- answerCardinality.min = min ? min.valueInteger.toString() : "1";
485
- }
486
- else {
487
- answerCardinality.min = "0";
488
- }
489
- }
490
- // not CNE/CWE, question repeats
491
- else {
492
- // repeats
493
- if (repeats) {
494
- questionCardinality = max ? {max: max.valueInteger.toString()} : {max: "*"};
495
- }
496
- else {
497
- questionCardinality = {max: "1"};
498
- }
499
- // required
500
- if (required) {
501
- questionCardinality.min = min ? min.valueInteger.toString() : "1";
502
- answerCardinality = {min: "1"};
503
- }
504
- else {
505
- questionCardinality.min = "1";
506
- }
507
- }
508
-
509
- if (questionCardinality)
510
- lfItem.questionCardinality = questionCardinality;
511
- if (answerCardinality)
512
- lfItem.answerCardinality = answerCardinality;
513
- };
514
-
515
-
516
- /**
517
- * Parse questionnaire item for units list
518
- *
519
- * @param lfItem {object} - LForms item object to assign units
520
- * @param qItem {object} - Questionnaire item object
521
- * @private
522
- */
523
- self._processUnitList = function (lfItem, qItem) {
524
-
525
- var lformsUnits = [];
526
- var lformsDefaultUnit = null;
527
- // The questionnaire-unitOption extension is only for item.type = quantity
528
- var unitOption = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlUnitOption, 0, true);
529
- if(unitOption && unitOption.length > 0) {
530
- if (qItem.type !== 'quantity') {
531
- throw new Error('The extension '+self.fhirExtUrlUnitOption+
532
- ' can only be used with type quantity. Question "'+
533
- qItem.text+'" is of type '+qItem.type);
534
- }
535
- for(var i = 0; i < unitOption.length; i++) {
536
- var coding = unitOption[i].valueCoding;
537
- var lUnit = {
538
- name: coding.display,
539
- code: coding.code,
540
- system: coding.system
541
- };
542
- lformsUnits.push(lUnit);
543
- }
544
- }
545
-
546
- // The questionnaire-unit extension is only for item.type = integer or decimal
547
- var unit = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlUnit);
548
- if (unit) {
549
- if (qItem.type !== 'integer' && qItem.type !== 'decimal') {
550
- throw new Error('The extension '+self.fhirExtUrlUnit+
551
- ' can only be used with types integer or decimal. Question "'+
552
- qItem.text+'" is of type '+qItem.type);
553
- }
554
- lformsDefaultUnit = {
555
- name: unit.valueCoding.display,
556
- code: unit.valueCoding.code,
557
- system: unit.valueCoding.system,
558
- default: true
559
- };
560
- lformsUnits.push(lformsDefaultUnit);
561
- }
562
-
563
- if (qItem.type === 'quantity') {
564
- let initialQ = this.getFirstInitialQuantity(qItem);
565
- if (initialQ && initialQ.unit) {
566
- lformsDefaultUnit = LForms.Util.findItem(lformsUnits, 'name', initialQ.unit);
567
- if(lformsDefaultUnit) {
568
- lformsDefaultUnit.default = true;
569
- }
570
- else {
571
- lformsDefaultUnit = {
572
- name: initialQ.unit,
573
- code: initialQ.code,
574
- system: initialQ.system,
575
- default: true
576
- };
577
- lformsUnits.push(lformsDefaultUnit);
578
- }
579
- }
580
- }
581
-
582
- if(lformsUnits.length > 0) {
583
- if (!lformsDefaultUnit) {
584
- lformsUnits[0].default = true;
585
- }
586
- lfItem.units = lformsUnits;
587
- }
588
- };
589
-
590
-
591
- /**
592
- * Parse questionnaire item for display control
593
- *
594
- * @param lfItem {object} - LForms item object to assign display control
595
- * @param qItem {object} - Questionnaire item object
596
- * @private
597
- */
598
- self._processDisplayControl = function (lfItem, qItem) {
599
- var itemControlType = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlItemControl);
600
-
601
- if(itemControlType) {
602
- var displayControl = {};
603
- switch (itemControlType.valueCodeableConcept.coding[0].code) {
604
- case 'Lookup': // backward-compatibility with old export
605
- case 'Combo-box': // backward-compatibility with old export
606
- case 'autocomplete':
607
- lfItem.isSearchAutocomplete = true;
608
- // continue to drop-down case
609
- case 'drop-down':
610
- displayControl.answerLayout = {type: 'COMBO_BOX'};
611
- break;
612
- case 'Checkbox': // backward-compatibility with old export
613
- case 'check-box':
614
- case 'Radio': // backward-compatibility with old export
615
- case 'radio-button':
616
- displayControl.answerLayout = {type: 'RADIO_CHECKBOX'};
617
- var answerChoiceOrientation = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtChoiceOrientation);
618
- if (answerChoiceOrientation) {
619
- if (answerChoiceOrientation.valueCode === "vertical") {
620
- displayControl.answerLayout.columns = "1"
621
- }
622
- else if (answerChoiceOrientation.valueCode === "horizontal") {
623
- displayControl.answerLayout.columns = "0"
624
- }
625
- }
626
- break;
627
- case 'Table': // backward-compatibility with old export
628
- case 'gtable': // Not in STU3, but we'll accept it
629
- if(lfItem.dataType === 'SECTION') {
630
- displayControl.questionLayout = "horizontal";
631
- }
632
- break;
633
- case 'Matrix': // backward-compatibility with old export
634
- case 'table':
635
- if(lfItem.dataType === 'SECTION') {
636
- displayControl.questionLayout = "matrix";
637
- }
638
- break;
639
- default:
640
- displayControl = null;
641
- }
642
-
643
- if(displayControl && !jQuery.isEmptyObject(displayControl)) {
644
- lfItem.displayControl = displayControl;
645
- }
646
- }
647
- };
648
-
649
-
650
- /**
651
- * Parse questionnaire item for data control
652
- *
653
- * @param lfItem {object} - LForms item object to assign data control
654
- * @param qItem {object} - Questionnaire item object
655
- * @private
656
- */
657
- self._processDataControl = function (lfItem, qItem) {
658
- var dataControlType = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlDataControl);
659
-
660
- if(dataControlType && dataControlType.valueString) {
661
- try {
662
- var dataControl = JSON.parse(dataControlType.valueString);
663
- if (dataControl) {
664
- lfItem.dataControl = dataControl;
665
- }
666
- }
667
- catch(e){
668
- console.log("Invalid dataControl data!");
669
- }
670
- }
671
- };
672
-
673
-
674
- // ---------------- QuestionnaireResponse Import ---------------
675
-
676
- var qrImport = self._mergeQR;
677
-
678
- /**
679
- * Merge a QuestionnaireResponse instance into an LForms form object
680
- * @param formData an LForms form definition or LFormsData object.
681
- * @param qr a QuestionnaireResponse instance
682
- * @returns {{}} an updated LForms form definition, with answer data
683
- */
684
- qrImport.mergeQuestionnaireResponseToLForms = function(formData, qr) {
685
- if (!(formData instanceof LForms.LFormsData)) {
686
- // get the default settings in case they are missing in the form data
687
- // not to set item values by default values for saved forms with user data
688
- formData.hasSavedData = true;
689
- formData = (new LForms.LFormsData(formData)).getFormData();
690
- }
691
- // The reference to _mergeQR below is here because this function gets copied to
692
- // the containing object to be a part of the public API.
693
- var qrInfo = qrImport._getQRStructure(qr);
694
- qrImport._processQRItemAndLFormsItem(qrInfo, formData);
695
- return formData;
696
- };
697
-
698
-
699
- /**
700
- * Merge data into items on the same level
701
- * @param parentQRItemInfo structural information of a parent item
702
- * @param parentLFormsItem a parent item, could be a LForms form object or a form item object.
703
- * @private
704
- */
705
- qrImport._processQRItemAndLFormsItem = function(parentQRItemInfo, parentLFormsItem) {
706
-
707
- // note: parentQRItemInfo.qrItemInfo.length will increase when new data is inserted into the array
708
- for(var i=0; i<parentQRItemInfo.qrItemsInfo.length; i++) {
709
-
710
- var qrItemInfo = parentQRItemInfo.qrItemsInfo[i];
711
- var qrItem = qrItemInfo.item;
712
- if (qrItem) {
713
- // first repeating qrItem
714
- if (qrItemInfo.total > 1 && qrItemInfo.index === 0) {
715
- var defItem = this._findTheMatchingItemByLinkId(parentLFormsItem, qrItemInfo.linkId);
716
- // add repeating items in form data
717
- // if it is a case of repeating questions, not repeating answers
718
- if (ns._questionRepeats(defItem)) {
719
- this._addRepeatingItems(parentLFormsItem, qrItemInfo.linkId, qrItemInfo.total);
720
- // add missing qrItemInfo nodes for the newly added repeating LForms items (questions, not sections)
721
- if (defItem.dataType !== 'SECTION' && defItem.dataType !== 'TITLE') {
722
- for (var j=1; j<qrItemInfo.total; j++) {
723
- var newQRItemInfo = LForms.Util.deepCopy(qrItemInfo);
724
- newQRItemInfo.index = j;
725
- newQRItemInfo.item.answer = [newQRItemInfo.item.answer[j]];
726
- if(qrItemInfo.qrAnswersItemsInfo && qrItemInfo.qrAnswersItemsInfo[j]) {
727
- newQRItemInfo.qrAnswersItemsInfo = [qrItemInfo.qrAnswersItemsInfo[j]];
728
- }
729
- parentQRItemInfo.qrItemsInfo.splice(i+j, 0, newQRItemInfo);
730
- }
731
- // change the first qr item's answer too
732
- qrItemInfo.item.answer = [qrItemInfo.item.answer[0]];
733
- if(qrItemInfo.qrAnswersItemsInfo && qrItemInfo.qrAnswersItemsInfo[0]) {
734
- qrItemInfo.qrAnswersItemsInfo = [qrItemInfo.qrAnswersItemsInfo[0]];
735
- }
736
- else {
737
- delete qrItemInfo.qrAnswersItemsInfo;
738
- }
739
- }
740
- }
741
- // reset the total number of questions when it is the answers that repeats
742
- else if (ns._answerRepeats(defItem)) {
743
- qrItemInfo.total = 1;
744
- }
745
- }
746
- // find the matching LForms item
747
- var item = this._findTheMatchingItemByLinkIdAndIndex(parentLFormsItem, qrItemInfo.linkId, qrItemInfo.index);
748
-
749
- // set up value and units if it is a question
750
- if ((item.dataType !== 'SECTION' && item.dataType !== 'TITLE')) {
751
- var qrAnswer = qrItem.answer;
752
- if (qrAnswer && qrAnswer.length > 0) {
753
- this._setupItemValueAndUnit(qrItem.linkId, qrAnswer, item);
754
- // process item.answer.item, if applicable
755
- if(qrItemInfo.qrAnswersItemsInfo) {
756
- // _setupItemValueAndUnit seems to assume single-answer except for multiple choices on CNE/CWE
757
- // moreover, each answer has already got its own item above if question repeats
758
- if(qrItemInfo.qrAnswersItemsInfo.length > 1) {
759
- throw new Error('item.answer.item with item.answer.length > 1 is not yet supported');
760
- }
761
- this._processQRItemAndLFormsItem(qrItemInfo.qrAnswersItemsInfo[0], item);
762
- }
763
- }
764
- }
765
-
766
- // process items on the sub-level
767
- if (qrItemInfo.qrItemsInfo && qrItemInfo.qrItemsInfo.length>0) {
768
- this._processQRItemAndLFormsItem(qrItemInfo, item);
769
- }
770
- }
771
- }
772
- };
773
-
774
-
775
- /**
776
- * Set value and units on a LForms item
777
- * @param linkId an item's linkId
778
- * @param answer value for the item
779
- * @param item a LForms item
780
- * @private
781
- */
782
- qrImport._setupItemValueAndUnit = function(linkId, answer, item) {
783
-
784
- if (item && linkId === item.linkId && (item.dataType !== 'SECTION' && item.dataType !== 'TITLE')) {
785
- var dataType = item.dataType;
786
-
787
- // any one has a unit must be a numerical type, let use REAL for now.
788
- // dataType conversion should be handled when panel data are added to lforms-service.
789
- if ((!dataType || dataType==="ST") && item.units && item.units.length>0 ) {
790
- item.dataType = dataType = "REAL";
791
- }
792
-
793
- var qrValue = answer[0];
794
-
795
- switch (dataType) {
796
- case "BL":
797
- if (qrValue.valueBoolean === true || qrValue.valueBoolean === false) {
798
- item.value = qrValue.valueBoolean;
799
- }
800
- break;
801
- case "INT":
802
- if (qrValue.valueQuantity) {
803
- item.value = qrValue.valueQuantity.value;
804
- if(qrValue.valueQuantity.code) {
805
- item.unit = {name: qrValue.valueQuantity.code};
806
- }
807
- }
808
- else if (qrValue.valueInteger) {
809
- item.value = qrValue.valueInteger;
810
- }
811
- break;
812
- case "REAL":
813
- case "QTY":
814
- if (qrValue.valueQuantity) {
815
- item.value = qrValue.valueQuantity.value;
816
- if(qrValue.valueQuantity.code) {
817
- item.unit = {name: qrValue.valueQuantity.code};
818
- }
819
- }
820
- else if (qrValue.valueDecimal) {
821
- item.value = qrValue.valueDecimal;
822
- }
823
- break;
824
- case "DT":
825
- item.value = qrValue.valueDate;
826
- break;
827
- case "DTM":
828
- item.value = qrValue.valueDateTime;
829
- break;
830
- case "CNE":
831
- case "CWE":
832
- if (ns._answerRepeats(item)) {
833
- var value = [];
834
- for (var j=0,jLen=answer.length; j<jLen; j++) {
835
- var val = ns._processCWECNEValueInQR(answer[j]);
836
- if (val) {
837
- value.push(val);
838
- }
839
- }
840
- item.value = value;
841
- }
842
- else {
843
- var val = ns._processCWECNEValueInQR(qrValue);
844
- if (val) {
845
- item.value = val;
846
- }
847
- }
848
- break;
849
- case "ST":
850
- case "TX":
851
- item.value = qrValue.valueString;
852
- break;
853
- case "attachment":
854
- item.value = qrValue.valueAttachment;
855
- break;
856
- case "SECTION":
857
- case "TITLE":
858
- case "":
859
- // do nothing
860
- break;
861
- default:
862
- item.value = qrValue.valueString;
863
- }
864
- }
865
- }
866
-
867
-
868
- /**
869
- * Get LForms data type from questionnaire item
870
- *
871
- * @param qItem {object} - Questionnaire item object
872
- * @private
873
- */
874
- self._getDataType = function (qItem) {
875
- var type = 'string';
876
-
877
- switch (qItem.type) {
878
- case 'string':
879
- type = 'ST';
880
- break;
881
- case 'group':
882
- type = 'SECTION';
883
- break;
884
- case "choice":
885
- type = 'CNE';
886
- break;
887
- case "open-choice":
888
- type = 'CWE';
889
- break;
890
- case 'integer':
891
- type = 'INT';
892
- break;
893
- case 'decimal':
894
- type = 'REAL';
895
- break;
896
- case 'text':
897
- type = 'TX';
898
- break;
899
- case "boolean":
900
- type = 'BL';
901
- break;
902
- case "date":
903
- //dataType = 'date';
904
- type = 'DT';
905
- break;
906
- case "dateTime":
907
- type = 'DTM';
908
- break;
909
- case "time":
910
- type = 'TM';
911
- break;
912
- case "display":
913
- type = 'TITLE';
914
- break;
915
- case "url":
916
- type = 'URL';
917
- break;
918
- case "quantity":
919
- type = 'QTY';
920
- break;
921
- case "attachment":
922
- type = 'attachment';
923
- break;
924
- }
925
- return type;
926
- };
927
-
928
-
929
- /**
930
- * Build a map of items to linkid from a questionnaire resource.
931
- * @param qResource - FHIR Questionnaire resource
932
- * @returns {*} - Hash object with link id keys pointing to their respective items.
933
- * @private
934
- */
935
- self._createLinkIdItemMap = function (qResource) {
936
- var traverse = function (itemArray, collection) {
937
- itemArray.forEach(function(item) {
938
- collection[item.linkId] = item;
939
- if(item.item) {
940
- traverse(item.item, collection);
941
- }
942
- });
943
-
944
- return collection;
945
- };
946
-
947
- var ret = {};
948
- if(qResource.item) {
949
- ret = traverse(qResource.item, ret);
950
- }
951
- return ret;
952
- };
953
-
954
-
955
- /**
956
- * Get an object with code and code system
957
- *
958
- * @param questionnaireItemOrResource {object} - question
959
- * @private
960
- */
961
- self._getCode = function (questionnaireItemOrResource) {
962
- var code = null;
963
- if(questionnaireItemOrResource &&
964
- Array.isArray(questionnaireItemOrResource.code) &&
965
- questionnaireItemOrResource.code.length) {
966
- code = {
967
- code: questionnaireItemOrResource.code[0].code,
968
- system: self._toLfCodeSystem(questionnaireItemOrResource.code[0].system)
969
- };
970
- }
971
- // If code is missing look for identifier.
972
- else if(questionnaireItemOrResource &&
973
- Array.isArray(questionnaireItemOrResource.identifier) &&
974
- questionnaireItemOrResource.identifier.length) {
975
- code = {
976
- code: questionnaireItemOrResource.identifier[0].value,
977
- system: self._toLfCodeSystem(questionnaireItemOrResource.identifier[0].system)
978
- };
979
- }
980
-
981
- return code;
982
- };
983
-
984
-
985
- /**
986
- * Converts the given ValueSet into an array of answers that can be used with a prefetch autocompleter.
987
- * @return the array of answers, or null if the extraction cannot be done.
988
- */
989
- self.answersFromVS = function (valueSet) {
990
- var vs = valueSet;
991
- var rtn = [];
992
- if (vs.expansion && vs.expansion.contains && vs.expansion.contains.length > 0) {
993
- vs.expansion.contains.forEach(function (vsItem) {
994
- var answer = {code: vsItem.code, text: vsItem.display, system: vsItem.system};
995
- var ordExt = LForms.Util.findObjectInArray(vsItem.extension, 'url',
996
- self.fhirExtUrlValueSetScore);
997
- if(ordExt) {
998
- answer.score = ordExt.valueDecimal;
999
- }
1000
- rtn.push(answer);
1001
- });
1002
- }
1003
- return rtn.length > 0 ? rtn : null;
1004
- };
1005
-
1006
-
1007
- /**
1008
- * Convert the given code system to LForms internal code system. Currently
1009
- * only converts 'http://loinc.org' to 'LOINC' and returns all other input as is.
1010
- * @param codeSystem
1011
- * @private
1012
- */
1013
- self._toLfCodeSystem = function(codeSystem) {
1014
- var ret = codeSystem;
1015
- switch(codeSystem) {
1016
- case 'http://loinc.org':
1017
- ret = 'LOINC';
1018
- break;
1019
- }
1020
-
1021
- return ret;
1022
- };
1023
-
1024
-
1025
- // Copy the main merge function to preserve the same API usage.
1026
- self.mergeQuestionnaireResponseToLForms = qrImport.mergeQuestionnaireResponseToLForms;
1027
-
1028
- /**
1029
- * Processes the terminology server setting, if any.
1030
- *
1031
- * @param lfItem - LForms item object to assign externallyDefined
1032
- * @param qItem - Questionnaire item object
1033
- * @private
1034
- */
1035
- self._processTerminologyServer = function (lfItem, qItem) {
1036
- var tServer = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtTerminologyServer);
1037
- if (tServer && tServer.valueUrl) {
1038
- lfItem.terminologyServer = tServer.valueUrl;
1039
- }
1040
- };
1041
-
1042
-
1043
- /**
1044
- * Finds the terminology server URL (if any) for the given item.
1045
- * @param item a question, title, or group in the form (in the LFormsData
1046
- * structure, not the Questionnaire).
1047
- * @return the base terminology server URL, or undefined if there isn't one
1048
- * for this item.
1049
- */
1050
- self._getTerminologyServer = function(item) {
1051
- var terminologyServer = item.terminologyServer;
1052
- var parent = item._parentItem;
1053
- while (!terminologyServer && parent) {
1054
- terminologyServer = parent.terminologyServer;
1055
- parent = parent._parentItem;
1056
- }
1057
- return terminologyServer;
1058
- };
1059
-
1060
-
1061
- /**
1062
- * Returns the URL for performing a ValueSet expansion for the given item,
1063
- * if the given item has a terminology server and answerValueSet
1064
- * configured; otherwise it returns undefined.
1065
- * @param item a question, title, or group in the form
1066
- */
1067
- self._getExpansionURL = function(item) {
1068
- var rtn;
1069
- if (item.answerValueSet) {
1070
- var terminologyServer = this._getTerminologyServer(item);
1071
- if (terminologyServer)
1072
- rtn = terminologyServer + '/ValueSet/$expand?url='+ item.answerValueSet;
1073
- }
1074
- return rtn;
1075
- };
1076
-
1077
-
1078
- /**
1079
- * Loads answerValueSets for prefetched lists.
1080
- * @param lfData the LFormsData for the form
1081
- * @return an array of promise objects which resolve when the answer valuesets
1082
- * have been loaded and imported.
1083
- */
1084
- self.loadAnswerValueSets = function (lfData) {
1085
- var pendingPromises = [];
1086
- var items = lfData.itemList;
1087
- for (var i=0, len=items.length; i<len; ++i) {
1088
- let item = items[i];
1089
- if (item.answerValueSet && !item.isSearchAutocomplete) {
1090
- let expURL = this._getExpansionURL(item);
1091
- let vsKey = expURL ? expURL : item.answerValueSet;
1092
- item._answerValueSetKey = vsKey;
1093
- if (!LForms._valueSetAnswerCache)
1094
- LForms._valueSetAnswerCache = {};
1095
- let answers = LForms._valueSetAnswerCache[vsKey];
1096
- if (answers) {
1097
- item.answers = answers;
1098
- lfData._updateAutocompOptions(item, true);
1099
- }
1100
- else { // if not already loaded
1101
- if (expURL) {
1102
- pendingPromises.push(fetch(expURL).then(function(response) {
1103
- return response.json();
1104
- }).then(function(parsedJSON) {
1105
- answers = self.answersFromVS(parsedJSON);
1106
- if (answers) {
1107
- LForms._valueSetAnswerCache[expURL] = answers;
1108
- item.answers = answers;
1109
- lfData._updateAutocompOptions(item, true);
1110
- }
1111
- }, function fail() {
1112
- throw new Error("Unable to load ValueSet from "+expURL);
1113
- }));
1114
- }
1115
- else { // use FHIR context
1116
- var fhirClient = LForms.fhirContext;
1117
- pendingPromises.push(fhirClient.request(lfData._buildURL(
1118
- ['ValueSet','$expand'], {url: item.answerValueSet})
1119
- ).then(function(response) {
1120
- var valueSet = response;
1121
- var answers = self.answersFromVS(valueSet);
1122
- if (answers) {
1123
- LForms._valueSetAnswerCache[vsKey] = answers;
1124
- item.answers = answers;
1125
- lfData._updateAutocompOptions(item, true);
1126
- }
1127
- }, function fail() {
1128
- throw new Error("Unable to load ValueSet "+item.answerValueSet+ " from FHIR server");
1129
- }));
1130
- }
1131
- }
1132
- }
1133
- }
1134
- return pendingPromises;
1135
- };
1136
-
1137
-
1138
- /**
1139
- * Handle the item.value in QuestionnaireResponse for CWE/CNE typed items
1140
- * @param qrItemValue a value of item in QuestionnaireResponse
1141
- * @returns {{code: *, text: *}}
1142
- * @private
1143
- */
1144
- self._processCWECNEValueInQR = function(qrItemValue) {
1145
- var retValue;
1146
- // a valueCoding, which is one of the answers
1147
- if (qrItemValue.valueCoding) {
1148
- retValue = {
1149
- "code": qrItemValue.valueCoding.code,
1150
- "text": qrItemValue.valueCoding.display,
1151
- "system": qrItemValue.valueCoding.system
1152
- };
1153
- }
1154
- // a valueString, which is a user supplied value that is not in the answers
1155
- else if (qrItemValue.valueString) {
1156
- retValue = qrItemValue.valueString;
1157
- }
1158
- return retValue;
1159
- };
1160
-
1161
-
1162
- /**
1163
- * Parse questionnaire item for coding instructions
1164
- *
1165
- * @param qItem {object} - Questionnaire item object
1166
- * @return {{}} an object contains the coding instructions info.
1167
- * @private
1168
- */
1169
- self._processCodingInstructions = function(qItem) {
1170
- // if the qItem is a "display" typed item with a item-control extension, then it meant to be a help message,
1171
- // which in LForms is an attribute of the parent item, not a separate item.
1172
- let ret = null;
1173
- let ci = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlItemControl);
1174
- let xhtmlFormat;
1175
- if ( qItem.type === "display" && ci) {
1176
- // only "redering-xhtml" is supported. others are default to text
1177
- if (qItem._text) {
1178
- xhtmlFormat = LForms.Util.findObjectInArray(qItem._text.extension, 'url', "http://hl7.org/fhir/StructureDefinition/rendering-xhtml");
1179
- }
1180
-
1181
- // there is a xhtml extension
1182
- if (xhtmlFormat) {
1183
- ret = {
1184
- codingInstructionsFormat: "html",
1185
- codingInstructions: xhtmlFormat.valueString,
1186
- codingInstructionsPlain: qItem.text // this always contains the coding instructions in plain text
1187
- };
1188
- }
1189
- // no xhtml extension, default to 'text'
1190
- else {
1191
- ret = {
1192
- codingInstructionsFormat: "text",
1193
- codingInstructions: qItem.text,
1194
- codingInstructionsPlain: qItem.text // this always contains the coding instructions in plain text
1195
- };
1196
- }
1197
- }
1198
-
1199
- return ret;
1200
- };
1201
-
1202
-
1203
- /**
1204
- * Processes the child items of the item.
1205
- * @param targetItem the LForms node being populated with data
1206
- * @param qItem the Questionnaire (item) node being imported
1207
- * @param linkIdItemMap - Map of items from link ID to item from the imported resource.
1208
- * @param containedVS - contained ValueSet info, see _extractContainedVS() for data format details
1209
- */
1210
- self._processChildItems = function(targetItem, qItem, containedVS, linkIdItemMap) {
1211
- if (Array.isArray(qItem.item)) {
1212
- targetItem.items = [];
1213
- for (var i=0; i < qItem.item.length; i++) {
1214
- var help = self._processCodingInstructions(qItem.item[i]);
1215
- // pick one coding instruction if there are multiple ones in Questionnaire
1216
- if (help !== null) {
1217
- targetItem.codingInstructions = help.codingInstructions;
1218
- targetItem.codingInstructionsFormat = help.codingInstructionsFormat;
1219
- targetItem.codingInstructionsPlain = help.codingInstructionsPlain;
1220
- }
1221
- else {
1222
- var item = self._processQuestionnaireItem(qItem.item[i], containedVS, linkIdItemMap);
1223
- targetItem.items.push(item);
1224
- }
1225
- }
1226
- }
1227
- };
1228
-
1229
-
1230
- /**
1231
- * Copy extensions that haven't been handled before.
1232
- *
1233
- * @param lfItem the LForms node being populated with data
1234
- * @param qItem the Questionnaire (item) node being imported
1235
- */
1236
- self._processExtensions = function(lfItem, qItem) {
1237
- var extensions = [];
1238
- if (Array.isArray(qItem.extension)) {
1239
- for (var i=0; i < qItem.extension.length; i++) {
1240
- var ext = qItem.extension[i];
1241
- var extHandler = self.extensionHandlers[ext.url];
1242
- if ((extHandler && extHandler(ext, lfItem)) ||
1243
- !self.handledExtensionSet.has(qItem.extension[i].url)) {
1244
- extensions.push(qItem.extension[i]);
1245
- }
1246
- }
1247
- }
1248
- if(extensions.length > 0) {
1249
- lfItem.extension = extensions;
1250
- }
1251
- };
1252
-
1253
-
1254
- /**
1255
- * If the given entity is an array, it will return the array length, return -1 otherwise.
1256
- * @param entity the given entity (can be anything) that needs to be tested to see if it's an array
1257
- * @return {number} the array length or -1 if the given entity is not an array.
1258
- * @private
1259
- */
1260
- self._arrayLen = function(entity) {
1261
- return entity && Array.isArray(entity)? entity.length: -1;
1262
- };
1263
-
1264
-
1265
- /**
1266
- * Get structural info of a QuestionnaireResponse item.answer.item in a way similar to that of item.item.
1267
- * If any answer entry in item.answer has items, the qrItemInfo.qrAnswersItemsInfo will be assigned, which
1268
- * will be an array where each element corresponds to one answer element in item.answer. When an answer entry
1269
- * does not have any items, null will be used to fill the position.
1270
- * @param qrItemInfo the structural info of the given item
1271
- * @param item the item in a QuestionnaireResponse object whose answer.item structure is to be created.
1272
- * @private
1273
- */
1274
- self._checkQRItemAnswerItems = function(qrItemInfo, item) {
1275
- var answerLen = self._arrayLen(item.answer);
1276
- if(answerLen < 1) {
1277
- return;
1278
- }
1279
-
1280
- var numAnswersWithItems = 0;
1281
- var answersItemsInfo = []; // one entry for each answer; each entry is an qrItemsInfo array for the answer.item
1282
- for (var i = 0; i < answerLen; i++) {
1283
- if(this._arrayLen(item.answer[i].item) > 0) {
1284
- answersItemsInfo.push({});
1285
- self._mergeQR._checkQRItems(answersItemsInfo[i], item.answer[i]);
1286
- ++ numAnswersWithItems;
1287
- }
1288
- else {
1289
- answersItemsInfo.push(null);
1290
- }
1291
- }
1292
-
1293
- if(numAnswersWithItems > 0) {
1294
- qrItemInfo.numAnswersWithItems = numAnswersWithItems;
1295
- qrItemInfo.qrAnswersItemsInfo = answersItemsInfo;
1296
- }
1297
- };
1298
-
1299
-
1300
- /**
1301
- * Parse questionnaire item for restrictions
1302
- *
1303
- * @param lfItem {object} - LForms item object to assign restrictions
1304
- * @param qItem {object} - Questionnaire item object
1305
- * @private
1306
- */
1307
- self._processRestrictions = function (lfItem, qItem) {
1308
- var restrictions = {};
1309
- if(typeof qItem.maxLength !== 'undefined') {
1310
- restrictions['maxLength'] = qItem.maxLength.toString();
1311
- }
1312
-
1313
- for(var i = 0; i < self.fhirExtUrlRestrictionArray.length; i++) {
1314
- var restriction = LForms.Util.findObjectInArray(qItem.extension, 'url', self.fhirExtUrlRestrictionArray[i]);
1315
- var val = self._getFHIRValueWithPrefixKey(restriction, /^value/);
1316
- if (val !== undefined && val !== null) {
1317
-
1318
- if(restriction.url.match(/minValue$/)) {
1319
- // TODO -
1320
- // There is no distinction between inclusive and exclusive.
1321
- // Lforms looses this information when converting back and forth.
1322
- restrictions['minInclusive'] = val;
1323
- }
1324
- else if(restriction.url.match(/maxValue$/)) {
1325
- restrictions['maxInclusive'] = val;
1326
- }
1327
- else if(restriction.url.match(/minLength$/)) {
1328
- restrictions['minLength'] = val;
1329
- }
1330
- else if(restriction.url.match(/regex$/)) {
1331
- restrictions['pattern'] = val;
1332
- }
1333
- }
1334
- }
1335
-
1336
- if(!jQuery.isEmptyObject(restrictions)) {
1337
- lfItem.restrictions = restrictions;
1338
- }
1339
- };
1340
-
1341
- }
1342
-
1343
- export default addCommonSDCImportFns;