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.
- package/CHANGELOG.md +264 -0
- package/README.md +97 -14
- package/changed-features.md +99 -0
- package/dist/lforms/LICENSE.md +70 -0
- package/dist/lforms/README.md +121 -0
- package/dist/lforms/fhir/R4/lformsFHIR.min.js +2 -0
- package/dist/lforms/fhir/R4/lformsFHIR.min.js.map +1 -0
- package/dist/lforms/fhir/STU3/lformsFHIR.min.js +2 -0
- package/dist/lforms/fhir/STU3/lformsFHIR.min.js.map +1 -0
- package/dist/lforms/fhir/lformsFHIRAll.min.js +2 -0
- package/dist/lforms/fhir/lformsFHIRAll.min.js.map +1 -0
- package/dist/lforms/webcomponent/assets/lib/zone.min.js +134 -0
- package/dist/lforms/webcomponent/down_arrow_gray_10_10.png +0 -0
- package/dist/lforms/webcomponent/favicon.ico +0 -0
- package/dist/lforms/webcomponent/index.html +9 -0
- package/dist/lforms/webcomponent/lhc-forms.js +2472 -0
- package/dist/lforms/webcomponent/magnifying_glass.png +0 -0
- package/dist/lforms/webcomponent/main.js +2465 -0
- package/dist/lforms/webcomponent/main.js.map +1 -0
- package/dist/lforms/webcomponent/polyfills.js +2 -0
- package/dist/lforms/webcomponent/polyfills.js.map +1 -0
- package/dist/lforms/webcomponent/runtime.js +2 -0
- package/dist/lforms/webcomponent/runtime.js.map +1 -0
- package/dist/lforms/webcomponent/scripts.js +3 -0
- package/dist/lforms/webcomponent/scripts.js.map +1 -0
- package/dist/lforms/webcomponent/styles.css +3 -0
- package/dist/lforms/webcomponent/styles.css.map +1 -0
- package/form_definition.md +381 -0
- package/package.json +94 -88
- package/r4-support.md +147 -0
- package/sdc-support.md +127 -0
- package/app/.npmignore +0 -3
- package/app/images/blank.gif +0 -0
- package/app/images/signal-stop.png +0 -0
- package/app/index.html +0 -10
- package/app/lforms.js +0 -56
- package/app/lforms.tpl.js +0 -964
- package/app/scripts/angular-packages.js +0 -11
- package/app/scripts/bootstrap-decorators.js +0 -31
- package/app/scripts/bower-index.js +0 -14
- package/app/scripts/fhir/R4/export.js +0 -33
- package/app/scripts/fhir/R4/fhirRequire.js +0 -36
- package/app/scripts/fhir/R4/lformsFHIR.js +0 -27260
- package/app/scripts/fhir/R4/sdc-export.js +0 -455
- package/app/scripts/fhir/R4/sdc-import.js +0 -483
- package/app/scripts/fhir/STU3/export.js +0 -27
- package/app/scripts/fhir/STU3/fhirRequire.js +0 -36
- package/app/scripts/fhir/STU3/lformsFHIR.js +0 -27291
- package/app/scripts/fhir/STU3/sdc-export.js +0 -493
- package/app/scripts/fhir/STU3/sdc-import.js +0 -494
- package/app/scripts/fhir/diagnostic-report.js +0 -707
- package/app/scripts/fhir/export-common.js +0 -209
- package/app/scripts/fhir/expression-processor.js +0 -670
- package/app/scripts/fhir/extensions/rendering-style.js +0 -23
- package/app/scripts/fhir/fhir-common.js +0 -3
- package/app/scripts/fhir/obs-prepop.mjs +0 -97
- package/app/scripts/fhir/runtime-common.js +0 -33
- package/app/scripts/fhir/sdc-common.js +0 -334
- package/app/scripts/fhir/sdc-export-common.js +0 -1046
- package/app/scripts/fhir/sdc-import-common.js +0 -1343
- package/app/scripts/fhir/versions.js +0 -8
- package/app/scripts/index.js +0 -20
- package/app/scripts/lforms-config.js +0 -12
- package/app/scripts/lforms-constants.js +0 -10
- package/app/scripts/lforms-controllers.js +0 -1196
- package/app/scripts/lforms-directives.js +0 -424
- package/app/scripts/lforms-index.js +0 -20
- package/app/scripts/lib/cross_browser.js +0 -21
- package/app/scripts/lib/lforms-data.js +0 -3591
- package/app/scripts/lib/lforms-hl7.js +0 -525
- package/app/scripts/lib/lforms-util.js +0 -1122
- package/app/scripts/lib/lforms-validate.js +0 -277
- package/app/scripts/lib/polyfill.js +0 -81
- package/app/scripts/themeList.js +0 -17
- package/app/styles/lforms.css +0 -1219
- package/app/styles/redmond-theme/README.txt +0 -2
- package/app/styles/redmond-theme/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
- package/app/styles/redmond-theme/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
- package/app/styles/redmond-theme/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- package/app/styles/redmond-theme/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
- package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
- package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_217bc0_256x240.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_2e83ff_256x240.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_469bdd_256x240.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_6da8d5_256x240.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_cd0a0a_256x240.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_d8e7f3_256x240.png +0 -0
- package/app/styles/redmond-theme/images/ui-icons_f9bd01_256x240.png +0 -0
- package/app/styles/redmond-theme/jquery-ui.css +0 -1311
- package/app/styles/redmond-theme/jquery-ui.min.css +0 -7
- package/app/styles/redmond-theme/theme.css +0 -443
- package/app/styles/themes.css +0 -1541
- package/app/styles/themes.css.map +0 -9
- package/app/styles/themes.scss +0 -411
- package/app/version.json +0 -1
- package/app/views/partials/attachment.html +0 -38
- package/app/views/partials/field-answers.html +0 -67
- package/app/views/partials/field-units.html +0 -24
- package/app/views/partials/form-controls.html +0 -19
- package/app/views/partials/form-header.html +0 -35
- package/app/views/partials/form-options.html +0 -50
- package/app/views/partials/form-title.html +0 -20
- package/app/views/partials/form-view.html +0 -13
- package/app/views/partials/item-options.html +0 -48
- package/app/views/partials/item.html +0 -126
- package/app/views/partials/itemPrefixAndText.html +0 -4
- package/app/views/partials/layout-horizontal.html +0 -156
- package/app/views/partials/layout-matrix.html +0 -145
- package/app/views/partials/popover-content.html +0 -1
- package/app/views/partials/repeating-button.html +0 -16
- package/app/views/partials/table-item.html +0 -30
- package/app/views/partials/template-table.html +0 -69
- package/app/views/partials/uib-popover-templates/uib-popover-template.html +0 -13
- 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;
|