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.
- package/CHANGELOG.md +261 -0
- package/README.md +97 -14
- package/changed-features.md +99 -0
- package/dist/lforms/LICENSE.md +70 -0
- package/dist/lforms/README.md +100 -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 +2471 -0
- package/dist/lforms/webcomponent/magnifying_glass.png +0 -0
- package/dist/lforms/webcomponent/main.js +2464 -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 +382 -0
- package/lforms-33.2.0.tgz +0 -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,1046 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Defines SDC export functions that are the same across the different FHIR
|
|
3
|
-
* versions. The function takes the SDC namespace object defined in the sdc export
|
|
4
|
-
* code, and adds additional functions to it.
|
|
5
|
-
*/
|
|
6
|
-
function addCommonSDCExportFns(ns) {
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
var self = ns;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Convert LForms captured data to FHIR SDC QuestionnaireResponse
|
|
13
|
-
* @param lfData a LForms form object
|
|
14
|
-
* @param noExtensions a flag that a standard FHIR Questionnaire is to be created without any extensions.
|
|
15
|
-
* The default is false.
|
|
16
|
-
* @param subject A local FHIR resource that is the subject of the output resource.
|
|
17
|
-
* If provided, a reference to this resource will be added to the output FHIR
|
|
18
|
-
* resource when applicable.
|
|
19
|
-
* @returns {{}}
|
|
20
|
-
*/
|
|
21
|
-
self.convertLFormsToQuestionnaireResponse = function(lfData, noExtensions, subject) {
|
|
22
|
-
var target = {};
|
|
23
|
-
if (lfData) {
|
|
24
|
-
var source = lfData.getFormData(true,true,true);
|
|
25
|
-
this._processRepeatingItemValues(source);
|
|
26
|
-
this._setResponseFormLevelFields(target, source, noExtensions);
|
|
27
|
-
|
|
28
|
-
if (source.items && Array.isArray(source.items)) {
|
|
29
|
-
var tmp = this._processResponseItem(source, true);
|
|
30
|
-
if(tmp && tmp.item && tmp.item.length) {
|
|
31
|
-
target.item = tmp.item;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// FHIR doesn't allow null values, strip them out.
|
|
36
|
-
LForms.Util.pruneNulls(target);
|
|
37
|
-
|
|
38
|
-
if (subject)
|
|
39
|
-
target["subject"] = LForms.Util.createLocalFHIRReference(subject);
|
|
40
|
-
|
|
41
|
-
this._commonExport._setVersionTag(target);
|
|
42
|
-
return target;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Convert LForms form definition to standard FHIR Questionnaire or FHIR SDC Questionnaire
|
|
48
|
-
* @param lfData a LForms form object
|
|
49
|
-
* @param noExtensions a flag that a standard FHIR Questionnaire is to be created without any extensions.
|
|
50
|
-
* The default is false.
|
|
51
|
-
* @returns {{}}
|
|
52
|
-
*/
|
|
53
|
-
self.convertLFormsToQuestionnaire = function(lfData, noExtensions) {
|
|
54
|
-
var target = {};
|
|
55
|
-
|
|
56
|
-
if (lfData) {
|
|
57
|
-
var source = LForms.Util.deepCopy(lfData);
|
|
58
|
-
if(! (source instanceof LForms.LFormsData)) {
|
|
59
|
-
source = new LForms.LFormsData(source);
|
|
60
|
-
}
|
|
61
|
-
this._removeRepeatingItems(source);
|
|
62
|
-
this._setFormLevelFields(target, lfData, noExtensions);
|
|
63
|
-
|
|
64
|
-
if (source.items && Array.isArray(source.items)) {
|
|
65
|
-
target.item = [];
|
|
66
|
-
for (var i=0, iLen=source.items.length; i<iLen; i++) {
|
|
67
|
-
var newItem = this._processItem(source.items[i], source, noExtensions);
|
|
68
|
-
target.item.push(newItem);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// FHIR doesn't allow null values, strip them out.
|
|
74
|
-
LForms.Util.pruneNulls(target);
|
|
75
|
-
this._commonExport._setVersionTag(target);
|
|
76
|
-
return target;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Process an item of the form
|
|
82
|
-
* @param item an item in LForms form object
|
|
83
|
-
* @param source a LForms form object
|
|
84
|
-
* @param noExtensions a flag that a standard FHIR Questionnaire is to be created without any extensions.
|
|
85
|
-
* The default is false.
|
|
86
|
-
* @returns {{}}
|
|
87
|
-
* @private
|
|
88
|
-
*/
|
|
89
|
-
self._processItem = function(item, source, noExtensions) {
|
|
90
|
-
var targetItem = {};
|
|
91
|
-
|
|
92
|
-
// type
|
|
93
|
-
targetItem.type = this._getFhirDataType(item);
|
|
94
|
-
|
|
95
|
-
// id (empty for new record)
|
|
96
|
-
|
|
97
|
-
// code
|
|
98
|
-
targetItem.code = item.codeList;
|
|
99
|
-
|
|
100
|
-
// extension
|
|
101
|
-
targetItem.extension = item.extension || []; // later we delete if empty
|
|
102
|
-
|
|
103
|
-
// required
|
|
104
|
-
if (item._answerRequired === true || item._answerRequired === false) {
|
|
105
|
-
targetItem.required = item._answerRequired;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// http://hl7.org/fhir/StructureDefinition/questionnaire-minOccurs
|
|
109
|
-
if (targetItem.required) {
|
|
110
|
-
var minOccurInt = parseInt(item.questionCardinality.min);
|
|
111
|
-
if(minOccurInt > 1) {
|
|
112
|
-
targetItem.extension.push({
|
|
113
|
-
"url" : "http://hl7.org/fhir/StructureDefinition/questionnaire-minOccurs",
|
|
114
|
-
"valueInteger" : minOccurInt
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// question/answer repeats
|
|
120
|
-
// http://hl7.org/fhir/StructureDefinition/questionnaire-maxOccurs
|
|
121
|
-
this._processQuestionAndAnswerCardinality(targetItem, item);
|
|
122
|
-
|
|
123
|
-
// http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl
|
|
124
|
-
this._handleItemControl(targetItem, item);
|
|
125
|
-
|
|
126
|
-
// check restrictions
|
|
127
|
-
this._handleRestrictions(targetItem, item);
|
|
128
|
-
|
|
129
|
-
// http://hl7.org/fhir/StructureDefinition/entryFormat
|
|
130
|
-
// looks like tooltip, TBD
|
|
131
|
-
|
|
132
|
-
if(item._isHiddenInDef) {
|
|
133
|
-
targetItem.extension.push({
|
|
134
|
-
url: "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
|
|
135
|
-
valueBoolean: true
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// linkId
|
|
141
|
-
targetItem.linkId = item.linkId;
|
|
142
|
-
|
|
143
|
-
// Text & prefix
|
|
144
|
-
targetItem.text = item.question;
|
|
145
|
-
if (item.prefix) {
|
|
146
|
-
targetItem.prefix = item.prefix;
|
|
147
|
-
}
|
|
148
|
-
// Copy item extensions
|
|
149
|
-
for (let extField of ['_prefix', '_text']) {
|
|
150
|
-
let extFieldData = item['obj'+extField];
|
|
151
|
-
if (extFieldData)
|
|
152
|
-
targetItem[extField] = extFieldData;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// enableWhen
|
|
156
|
-
if (item.skipLogic) {
|
|
157
|
-
this._handleSkipLogic(targetItem, item, source)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// repeats, handled above
|
|
161
|
-
// readonly, (editable)
|
|
162
|
-
if (item.dataType !== "SECTION" && item.dataType !== "TITLE" && item.editable === "0") {
|
|
163
|
-
targetItem.readOnly = true;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
this._handleChoiceField(targetItem, item, noExtensions);
|
|
167
|
-
this._handleTerminologyServer(targetItem, item);
|
|
168
|
-
|
|
169
|
-
// initialValue, for default values
|
|
170
|
-
this._handleInitialValues(targetItem, item);
|
|
171
|
-
// add LForms Extension to units list. Process units after handling initial values.
|
|
172
|
-
if (item.units) {
|
|
173
|
-
this._handleLFormsUnits(targetItem, item);
|
|
174
|
-
}
|
|
175
|
-
// data control
|
|
176
|
-
this._handleDataControl(targetItem, item);
|
|
177
|
-
|
|
178
|
-
if (item.items && Array.isArray(item.items)) {
|
|
179
|
-
targetItem.item = [];
|
|
180
|
-
for (var i=0, iLen=item.items.length; i<iLen; i++) {
|
|
181
|
-
var newItem = this._processItem(item.items[i], source, noExtensions);
|
|
182
|
-
targetItem.item.push(newItem);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// the coding instruction is a sub item with a "display" type, and an item-control value as "help"
|
|
187
|
-
// it is added as a sub item of this item.
|
|
188
|
-
// http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl, for instructions
|
|
189
|
-
if (item.codingInstructions) {
|
|
190
|
-
let helpItem = {
|
|
191
|
-
"text": item.codingInstructionsPlain ? item.codingInstructionsPlain : item.codingInstructions,
|
|
192
|
-
"type": "display",
|
|
193
|
-
"linkId": targetItem.linkId + "-help",
|
|
194
|
-
"extension": [{
|
|
195
|
-
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
|
|
196
|
-
"valueCodeableConcept": {
|
|
197
|
-
"text": "Help-Button",
|
|
198
|
-
"coding": [{
|
|
199
|
-
"code": "help",
|
|
200
|
-
"display": "Help-Button",
|
|
201
|
-
"system": "http://hl7.org/fhir/questionnaire-item-control"
|
|
202
|
-
}]
|
|
203
|
-
}
|
|
204
|
-
}]
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// format could be 'html' or 'text'
|
|
208
|
-
if (item.codingInstructionsFormat === 'html') {
|
|
209
|
-
// add a "_text" field to contain the extension for the string value in the 'text' field
|
|
210
|
-
// see http://hl7.org/fhir/R4/json.html#primitive
|
|
211
|
-
helpItem._text = {
|
|
212
|
-
"extension": [{
|
|
213
|
-
"url": "http://hl7.org/fhir/StructureDefinition/rendering-xhtml",
|
|
214
|
-
"valueString": item.codingInstructions
|
|
215
|
-
}]
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (Array.isArray(targetItem.item)) {
|
|
220
|
-
targetItem.item.push(helpItem)
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
targetItem.item = [
|
|
224
|
-
helpItem
|
|
225
|
-
]
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (item.maxAttachmentSize) {
|
|
230
|
-
var exts = (targetItem.extension || (targetItem.extension = []));
|
|
231
|
-
exts.push({url: self.fhirExtMaxSize, valueDecimal: item.maxAttachmentSize});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (item.allowedAttachmentTypes) {
|
|
235
|
-
exts = (targetItem.extension || (targetItem.extension = []));
|
|
236
|
-
for (let type of item.allowedAttachmentTypes) {
|
|
237
|
-
exts.push({url: self.fhirExtMimeType, valueCode: type});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// handle special constraints for "display" item
|
|
242
|
-
this._handleSpecialConstraints(targetItem, item);
|
|
243
|
-
|
|
244
|
-
// if no extensions are allowed or there is no extension, remove it
|
|
245
|
-
if (noExtensions || targetItem.extension.length === 0)
|
|
246
|
-
delete targetItem.extension;
|
|
247
|
-
|
|
248
|
-
this.copyFields(item, targetItem, this.itemLevelIgnoredFields);
|
|
249
|
-
return targetItem
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Process the LForms questionCardinality and answerCardinality into FHIR.
|
|
255
|
-
* @param targetItem an item in Questionnaire
|
|
256
|
-
* @param item a LForms item
|
|
257
|
-
*/
|
|
258
|
-
self._processQuestionAndAnswerCardinality = function(targetItem, item) {
|
|
259
|
-
var maxOccurs = 0;
|
|
260
|
-
|
|
261
|
-
var qCard = item.questionCardinality, aCard = item.answerCardinality;
|
|
262
|
-
var qCardMax = (qCard && qCard.max !== undefined) ? qCard.max : null;
|
|
263
|
-
var aCardMax = (aCard && aCard.max !== undefined) ? aCard.max : null;
|
|
264
|
-
|
|
265
|
-
// unlimited repeats, no need to set maxOccurs
|
|
266
|
-
if (qCardMax === "*" || aCardMax === "*") {
|
|
267
|
-
if (item.dataType !== "TITLE") {
|
|
268
|
-
targetItem.repeats = true;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// not unlimited repeats
|
|
272
|
-
else {
|
|
273
|
-
var intQCardMax = parseInt(qCardMax), intACardMax = parseInt(aCardMax);
|
|
274
|
-
// has a maxOcurrs value
|
|
275
|
-
if(intQCardMax > 1 || intACardMax > 1) {
|
|
276
|
-
if (item.dataType !== "TITLE") {
|
|
277
|
-
targetItem.repeats = true;
|
|
278
|
-
|
|
279
|
-
// get the maxOccurs value
|
|
280
|
-
if (!isNaN(intQCardMax) && !isNaN(intACardMax)) {
|
|
281
|
-
maxOccurs = Math.max(intQCardMax, intACardMax);
|
|
282
|
-
}
|
|
283
|
-
else if (!isNaN(intQCardMax)) {
|
|
284
|
-
maxOccurs = intQCardMax;
|
|
285
|
-
}
|
|
286
|
-
else if (!isNaN(intACardMax)) {
|
|
287
|
-
maxOccurs = intACardMax
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (maxOccurs > 1) {
|
|
291
|
-
targetItem.extension.push({
|
|
292
|
-
"url": self.fhirExtUrlCardinalityMax,
|
|
293
|
-
"valueInteger": maxOccurs
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Process an item's externally defined answer list
|
|
305
|
-
* @param targetItem an item in FHIR SDC Questionnaire object
|
|
306
|
-
* @param item an item in the LForms form object
|
|
307
|
-
* @returns {*}
|
|
308
|
-
* @private
|
|
309
|
-
*/
|
|
310
|
-
self._handleExternallyDefined = function(targetItem, item) {
|
|
311
|
-
if (item.externallyDefined) {
|
|
312
|
-
targetItem.extension.push({
|
|
313
|
-
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-externallydefined",
|
|
314
|
-
"valueUri": item.externallyDefined
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Process an item's data control
|
|
322
|
-
* @param targetItem an item in FHIR SDC Questionnaire object
|
|
323
|
-
* @param item an item in the LForms form object
|
|
324
|
-
* @returns {*}
|
|
325
|
-
* @private
|
|
326
|
-
*/
|
|
327
|
-
self._handleDataControl = function(targetItem, item) {
|
|
328
|
-
if (item.dataControl) {
|
|
329
|
-
targetItem.extension.push({
|
|
330
|
-
"url": "http://lhcforms.nlm.nih.gov/fhirExt/dataControl",
|
|
331
|
-
"valueString": JSON.stringify(item.dataControl)
|
|
332
|
-
})
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Remove repeating items in a form data object
|
|
339
|
-
* @param source a LForms form data object
|
|
340
|
-
* @private
|
|
341
|
-
*/
|
|
342
|
-
self._removeRepeatingItems = function(source) {
|
|
343
|
-
|
|
344
|
-
if (source.items && Array.isArray(source.items)) {
|
|
345
|
-
for (var i= source.items.length-1; i>=0; i--) {
|
|
346
|
-
// if it is a repeating item, whose _id is not 1
|
|
347
|
-
if (source.items[i]._id > 1) {
|
|
348
|
-
source.items.splice(i,1);
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
this._removeRepeatingItems(source.items[i]);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Set form level attributes
|
|
360
|
-
* @param target a Questionnaire object
|
|
361
|
-
* @param source a LForms form object
|
|
362
|
-
* @param noExtensions a flag that a standard FHIR Questionnaire is to be created without any extensions.
|
|
363
|
-
* The default is false.
|
|
364
|
-
* @private
|
|
365
|
-
*/
|
|
366
|
-
self._setFormLevelFields = function(target, source, noExtensions) {
|
|
367
|
-
this.copyFields(source, target, this.formLevelFields);
|
|
368
|
-
// Handle title and name. In LForms, "name" is the "title", but FHIR
|
|
369
|
-
// defines both.
|
|
370
|
-
target.name = source.shortName; // computer friendly
|
|
371
|
-
target.title = source.name;
|
|
372
|
-
|
|
373
|
-
// Handle extensions on title
|
|
374
|
-
if (source.obj_title)
|
|
375
|
-
target._title = source.obj_title;
|
|
376
|
-
|
|
377
|
-
target.code = source.codeList;
|
|
378
|
-
|
|
379
|
-
// resourceType
|
|
380
|
-
target.resourceType = "Questionnaire";
|
|
381
|
-
target.status = target.status ? target.status : "draft";
|
|
382
|
-
|
|
383
|
-
// meta
|
|
384
|
-
var profile = noExtensions ? this.stdQProfile : this.QProfile;
|
|
385
|
-
|
|
386
|
-
target.meta = target.meta ? target.meta : {};
|
|
387
|
-
target.meta.profile = target.meta.profile ? target.meta.profile : [profile];
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Process itemControl based on LForms item's answerLayout and questionLayout
|
|
393
|
-
* @param targetItem an item in FHIR SDC Questionnaire object
|
|
394
|
-
* @param item an item in LForms form object
|
|
395
|
-
* @private
|
|
396
|
-
*/
|
|
397
|
-
self._handleItemControl = function(targetItem, item) {
|
|
398
|
-
// http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl
|
|
399
|
-
var itemControlType = "";
|
|
400
|
-
var itemControlDisplay, answerChoiceOrientation;
|
|
401
|
-
// Fly-over, Table, Checkbox, Combo-box, Lookup
|
|
402
|
-
if (!jQuery.isEmptyObject(item.displayControl)) {
|
|
403
|
-
var dataType = this._getAssumedDataTypeForExport(item);
|
|
404
|
-
// for answers
|
|
405
|
-
if (item.displayControl.answerLayout &&
|
|
406
|
-
(dataType === "CNE" || dataType === "CWE")) {
|
|
407
|
-
// search field
|
|
408
|
-
if (item.externallyDefined || (item.answerValueSet && item.isSearchAutocomplete)) {
|
|
409
|
-
itemControlType = "autocomplete";
|
|
410
|
-
itemControlDisplay = "Auto-complete";
|
|
411
|
-
}
|
|
412
|
-
// prefetch list
|
|
413
|
-
// combo-box
|
|
414
|
-
else if (item.displayControl.answerLayout.type === "COMBO_BOX") {
|
|
415
|
-
itemControlType = "drop-down";
|
|
416
|
-
itemControlDisplay = "Drop down";
|
|
417
|
-
}
|
|
418
|
-
// radio or checkbox
|
|
419
|
-
else if (item.displayControl.answerLayout.type === "RADIO_CHECKBOX") {
|
|
420
|
-
if (item.answerCardinality &&
|
|
421
|
-
(item.answerCardinality.max === "*" || parseInt(item.answerCardinality.max) > 1)) {
|
|
422
|
-
itemControlType = "check-box";
|
|
423
|
-
itemControlDisplay = "Check-box";
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
itemControlType = "radio-button";
|
|
427
|
-
itemControlDisplay = "Radio Button";
|
|
428
|
-
}
|
|
429
|
-
// answer choice orientation
|
|
430
|
-
if (item.displayControl.answerLayout.columns === "0") {
|
|
431
|
-
answerChoiceOrientation = "horizontal";
|
|
432
|
-
}
|
|
433
|
-
else if (item.displayControl.answerLayout.columns === "1") {
|
|
434
|
-
answerChoiceOrientation = "vertical";
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// for section item
|
|
440
|
-
else if (item.displayControl.questionLayout && dataType === "SECTION") {
|
|
441
|
-
if (item.displayControl.questionLayout === "horizontal") {
|
|
442
|
-
itemControlType = "gtable"; // Not in STU3, but the binding is extensible, so we can use it
|
|
443
|
-
itemControlDisplay = "Group Table";
|
|
444
|
-
}
|
|
445
|
-
else if (item.displayControl.questionLayout === "matrix") {
|
|
446
|
-
itemControlType = "table";
|
|
447
|
-
itemControlDisplay = "Vertical Answer Table";
|
|
448
|
-
}
|
|
449
|
-
// else {
|
|
450
|
-
// itemControlType = "List";
|
|
451
|
-
// }
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (itemControlType) {
|
|
455
|
-
targetItem.extension.push(
|
|
456
|
-
{
|
|
457
|
-
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
|
|
458
|
-
"valueCodeableConcept": {
|
|
459
|
-
"coding": [{
|
|
460
|
-
//"system" : "<uri>", // Identity of the terminology system
|
|
461
|
-
//"version" : "<string>", // Version of the system - if relevant
|
|
462
|
-
//"code" : "<code>", // Symbol in syntax defined by the system
|
|
463
|
-
//"display" : "<string>", // Representation defined by the system
|
|
464
|
-
//"userSelected" : <boolean> // If this coding was chosen directly by the user
|
|
465
|
-
"system": "http://hl7.org/fhir/questionnaire-item-control",
|
|
466
|
-
"code": itemControlType,
|
|
467
|
-
"display": itemControlDisplay
|
|
468
|
-
}],
|
|
469
|
-
"text": itemControlDisplay || itemControlType
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
// answer choice orientation
|
|
473
|
-
if (answerChoiceOrientation) {
|
|
474
|
-
targetItem.extension.push(
|
|
475
|
-
{
|
|
476
|
-
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation",
|
|
477
|
-
"valueCode": answerChoiceOrientation
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Process an item's terminology server setting.
|
|
487
|
-
* @param targetItem a QuestionnaireResponse object
|
|
488
|
-
* @param item an item in the LForms form object
|
|
489
|
-
* @returns {*}
|
|
490
|
-
* @private
|
|
491
|
-
*/
|
|
492
|
-
self._handleTerminologyServer = function(targetItem, item) {
|
|
493
|
-
if (item.terminologyServer) {
|
|
494
|
-
targetItem.extension.push({
|
|
495
|
-
"url": self.fhirExtTerminologyServer,
|
|
496
|
-
"valueUrl": item.terminologyServer
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Convert LForms data type to FHIR SDC data type
|
|
505
|
-
* @param item an item in the LForms form object
|
|
506
|
-
* @returns {string}
|
|
507
|
-
* @private
|
|
508
|
-
*/
|
|
509
|
-
self._getFhirDataType = function(item) {
|
|
510
|
-
|
|
511
|
-
var dataType = this._getAssumedDataTypeForExport(item);
|
|
512
|
-
var type = this._lformsTypesToFHIRTypes[dataType];
|
|
513
|
-
// default is string
|
|
514
|
-
if (!type) {
|
|
515
|
-
type = 'string';
|
|
516
|
-
}
|
|
517
|
-
return type;
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Determine how an item's data type should be for export.
|
|
523
|
-
|
|
524
|
-
If number type has multiple units, change it to quantity type. In such a case,
|
|
525
|
-
multiple units are converted to quesionnaire-unitOption extension and the default unit
|
|
526
|
-
would go into initial.valueQuantity.unit.
|
|
527
|
-
For single unit numbers, use the same type, whose unit will be in questionnaire-unit extension.
|
|
528
|
-
|
|
529
|
-
* @param item an item in the LForms form object
|
|
530
|
-
* @returns {string} dataType - Data type in lforms
|
|
531
|
-
* @private
|
|
532
|
-
*/
|
|
533
|
-
self._getAssumedDataTypeForExport = function (item) {
|
|
534
|
-
var dataType = item.dataType;
|
|
535
|
-
if((item.dataType === 'REAL' || item.dataType === 'INT') && item.units && item.units.length > 1) {
|
|
536
|
-
dataType = 'QTY';
|
|
537
|
-
}
|
|
538
|
-
return dataType;
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Make a FHIR Quantity for the given value and unit info.
|
|
544
|
-
* @param value optional, must be an integer or decimal
|
|
545
|
-
* @param itemUnit optional, lform data item.unit (that has a name property)
|
|
546
|
-
* @param unitSystem optional, overrides any system in itemUnit.
|
|
547
|
-
* @return a FHIR quantity or null IFF the given value is not a number (parseFloat() returns NaN).
|
|
548
|
-
* @private
|
|
549
|
-
*/
|
|
550
|
-
self._makeValueQuantity = function(value, itemUnit, unitSystem) {
|
|
551
|
-
let fhirQuantity = {};
|
|
552
|
-
let floatValue = parseFloat(value);
|
|
553
|
-
|
|
554
|
-
if(! isNaN(floatValue)) {
|
|
555
|
-
fhirQuantity.value = floatValue;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
if(itemUnit) {
|
|
559
|
-
self._setUnitAttributesToFhirQuantity(fhirQuantity, itemUnit);
|
|
560
|
-
if(unitSystem) {
|
|
561
|
-
fhirQuantity.system = unitSystem;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return (Object.keys(fhirQuantity).length > 0) ? fhirQuantity : null;
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Make a FHIR Quantity for the given value and unit info.
|
|
571
|
-
* @param value required, must be an integer or decimal
|
|
572
|
-
* @param itemUnits optional, lform data item.units (An array of units)
|
|
573
|
-
* @param unitSystem optional.
|
|
574
|
-
* @return a FHIR quantity or null IFF the given value is not a number (parseFloat() returns NaN).
|
|
575
|
-
* @private
|
|
576
|
-
*/
|
|
577
|
-
self._makeQuantity = function(value, itemUnits, unitSystem) {
|
|
578
|
-
var defaultUnit = this._getDefaultUnit(itemUnits);
|
|
579
|
-
return this._makeValueQuantity(value, defaultUnit, unitSystem);
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Pick a default unit if found, otherwise return first one as default. Will return
|
|
585
|
-
* null, if passed with empty list.
|
|
586
|
-
* @param lformsUnits - Array of lforms units i.e with {name, default}
|
|
587
|
-
* @returns {*} Return lforms unit if found otherwise null.
|
|
588
|
-
* @private
|
|
589
|
-
*/
|
|
590
|
-
self._getDefaultUnit = function (lformsUnits) {
|
|
591
|
-
if(!lformsUnits || lformsUnits.length === 0) {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
var ret = null;
|
|
596
|
-
for(var i = 0; i < lformsUnits.length; i++) {
|
|
597
|
-
if (lformsUnits[i].default) {
|
|
598
|
-
ret = lformsUnits[i];
|
|
599
|
-
break;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if(!ret) {
|
|
604
|
-
ret = lformsUnits[0];
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
return ret;
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* Create a key from data type to be used in a hash
|
|
613
|
-
* @param prefix a prefix to be added to the key
|
|
614
|
-
* @param item a LForms item
|
|
615
|
-
* @returns {*}
|
|
616
|
-
* @private
|
|
617
|
-
*/
|
|
618
|
-
self._getValueKeyByDataType = function(prefix, item) {
|
|
619
|
-
|
|
620
|
-
// prefix could be 'value', 'initial', 'answer'
|
|
621
|
-
if (!prefix) {
|
|
622
|
-
prefix = "value"
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
var fhirType = this._getFhirDataType(item);
|
|
626
|
-
var dataType = fhirType === 'quantity' ? 'QTY' : item.dataType;
|
|
627
|
-
var valueKey = this._lformsTypesToFHIRFields[dataType];
|
|
628
|
-
|
|
629
|
-
return prefix + valueKey;
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Convert the minInclusive/minExclusive, maxInclusive/maxExclusive to FHIR. See the
|
|
635
|
-
* the function _handleRestrictions() in sdc-export.js for more details on the context.
|
|
636
|
-
* @param dataType Lforms data type, currently supporting DT, DTM, TM, REAL, and INT.
|
|
637
|
-
* @param value the value (in the lforms system, either a number or a string).
|
|
638
|
-
* @param valueKey the valueKey in FHIR minValue/maxValue extension (e.g., valueInteger)
|
|
639
|
-
* @param minMaxKey must be one of minInclusive, minExclusive, maxInclusive, maxExclusive
|
|
640
|
-
* @return The FHIR extension element. Specifically, undefined is returned if:
|
|
641
|
-
* - the given value is null or undefined, or
|
|
642
|
-
* - the dataType is not one of those listed above, or
|
|
643
|
-
* - the minMaxKey is not one of those listed above
|
|
644
|
-
* @private
|
|
645
|
-
*/
|
|
646
|
-
self._MIN_MAX_TYPES = ['DT', 'DTM', 'TM', 'REAL', 'INT']
|
|
647
|
-
.reduce((map, t) => {map[t] = t; return map;}, {});
|
|
648
|
-
self._MIN_MAX_KEYS = ['minExclusive', 'minInclusive', 'maxExclusive', 'maxInclusive']
|
|
649
|
-
.reduce((map, t) => {map[t] = t; return map;}, {});
|
|
650
|
-
|
|
651
|
-
self._exportMinMax = function(dataType, value, valueKey, minMaxKey) {
|
|
652
|
-
if(value === null || value === undefined
|
|
653
|
-
|| ! self._MIN_MAX_TYPES[dataType] || ! self._MIN_MAX_KEYS[minMaxKey]) {
|
|
654
|
-
return undefined;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
var isoDateStr = (dataType === "DT" || dataType === "DTM")? new Date(value).toISOString():
|
|
658
|
-
dataType == "TM"? new Date('1970-01-01T' + value + 'Z').toISOString(): null;
|
|
659
|
-
|
|
660
|
-
var fhirValue =
|
|
661
|
-
dataType === "DT"? isoDateStr.substring(0, 10):
|
|
662
|
-
dataType === "DTM"? isoDateStr:
|
|
663
|
-
dataType === "TM"? isoDateStr.substring(11, isoDateStr.length-1):
|
|
664
|
-
dataType === "REAL"? parseFloat(value): parseInt(value);
|
|
665
|
-
|
|
666
|
-
var fhirExtUrl = minMaxKey.indexOf('min') === 0?
|
|
667
|
-
'http://hl7.org/fhir/StructureDefinition/minValue':
|
|
668
|
-
'http://hl7.org/fhir/StructureDefinition/maxValue';
|
|
669
|
-
|
|
670
|
-
return {
|
|
671
|
-
url: fhirExtUrl,
|
|
672
|
-
[valueKey]: fhirValue
|
|
673
|
-
};
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
// known source data types (besides CNE/CWE) in skip logic export handling,
|
|
678
|
-
// see _createEnableWhenRulesForSkipLogicCondition below
|
|
679
|
-
self._skipLogicValueDataTypes = ["BL", "REAL", "INT", 'QTY', "DT", "DTM", "TM", "ST", "TX", "URL"]
|
|
680
|
-
.reduce((map, type) => {map[type] = type; return map;}, {});
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* @param skipLogicCondition - Lforms skip logic condition object
|
|
685
|
-
* @param sourceItem - Skip logic source item in lforms.
|
|
686
|
-
* @return {Array} FHIR enableWhen array
|
|
687
|
-
* @private
|
|
688
|
-
*/
|
|
689
|
-
self._createEnableWhenRulesForSkipLogicCondition = function (skipLogicCondition, sourceItem) {
|
|
690
|
-
// dataTypes:
|
|
691
|
-
// boolean, decimal, integer, date, dateTime, instant, time, string, uri,
|
|
692
|
-
// Attachment, Coding, Quantity, Reference(Resource)
|
|
693
|
-
let sourceDataType = this._getAssumedDataTypeForExport(sourceItem);
|
|
694
|
-
let sourceValueKey = this._getValueKeyByDataType("answer", sourceItem);
|
|
695
|
-
let enableWhenRules = [];
|
|
696
|
-
|
|
697
|
-
// Per lforms spec, the trigger keys can be:
|
|
698
|
-
// exists, value, minExclusive, minInclusive, maxExclusive, maxInclusive
|
|
699
|
-
Object.keys(skipLogicCondition.trigger).forEach(function(key) {
|
|
700
|
-
let operator = self._operatorMapping[key];
|
|
701
|
-
let triggerValue = skipLogicCondition.trigger[key];
|
|
702
|
-
if(! operator || triggerValue !== 0 && triggerValue !== false && ! triggerValue) {
|
|
703
|
-
throw new Error('Invalid lforms skip logic trigger: ' + JSON.stringify(skipLogicCondition.trigger, null, 4));
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
let rule = null;
|
|
707
|
-
if (operator === 'exists') {
|
|
708
|
-
rule = { answerBoolean: triggerValue };
|
|
709
|
-
}
|
|
710
|
-
// for Coding
|
|
711
|
-
// multiple selections, item.value is an array
|
|
712
|
-
// NO support of multiple selections in FHIR SDC, just pick one
|
|
713
|
-
else if ( sourceDataType === 'CWE' || sourceDataType === 'CNE' ) {
|
|
714
|
-
let answerCoding = self._copyTriggerCoding(triggerValue, null, true);
|
|
715
|
-
if (! answerCoding) {
|
|
716
|
-
throw new Error('Invalid CNE/CWE trigger, key=' + key + '; value=' + triggerValue);
|
|
717
|
-
}
|
|
718
|
-
rule = { answerCoding: answerCoding };
|
|
719
|
-
}
|
|
720
|
-
else if (sourceDataType && self._skipLogicValueDataTypes[sourceDataType]) {
|
|
721
|
-
let answer = triggerValue;
|
|
722
|
-
if(sourceValueKey === 'answerQuantity') {
|
|
723
|
-
answer = self._makeQuantity(answer, sourceItem.units);
|
|
724
|
-
}
|
|
725
|
-
if(answer === 0 || answer === false || answer) {
|
|
726
|
-
rule = { [sourceValueKey]: answer };
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
throw new Error('Invalid value for trigger ' + key + ': ' + triggerValue);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
throw new Error('Unsupported data type for skip logic export: ' + sourceDataType);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
rule.question = sourceItem.linkId;
|
|
737
|
-
rule.operator = operator;
|
|
738
|
-
enableWhenRules.push(rule);
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
return enableWhenRules;
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Set form level attribute
|
|
747
|
-
* @param target a QuestionnaireResponse object
|
|
748
|
-
* @param noExtensions a flag that a standard FHIR Questionnaire is to be created without any extensions.
|
|
749
|
-
* The default is false.
|
|
750
|
-
* @param source a LForms form object
|
|
751
|
-
|
|
752
|
-
* @private
|
|
753
|
-
*/
|
|
754
|
-
self._setResponseFormLevelFields = function(target, source, noExtensions) {
|
|
755
|
-
|
|
756
|
-
// resourceType
|
|
757
|
-
target.resourceType = "QuestionnaireResponse";
|
|
758
|
-
|
|
759
|
-
// meta
|
|
760
|
-
var profile = noExtensions ? this.stdQRProfile : this.QRProfile;
|
|
761
|
-
target.meta = target.meta ? target.meta : {};
|
|
762
|
-
target.meta.profile = target.meta.profile ? target.meta.profile : [profile];
|
|
763
|
-
|
|
764
|
-
// "identifier": - not including identifier in QuestionnaireResponse per LF-1183
|
|
765
|
-
//target.identifier = {
|
|
766
|
-
// "system": LForms.Util.getCodeSystem(source.codeSystem),
|
|
767
|
-
// "value": source.code
|
|
768
|
-
//};
|
|
769
|
-
|
|
770
|
-
// status, required
|
|
771
|
-
// "in-progress", "completed", "amended"
|
|
772
|
-
target.status = "completed";
|
|
773
|
-
|
|
774
|
-
// authored, required
|
|
775
|
-
target.authored = LForms.Util.dateToDTMString(new Date());
|
|
776
|
-
|
|
777
|
-
// questionnaire , required
|
|
778
|
-
// We do not have the ID at this point, so leave it unset for now. Note
|
|
779
|
-
// that the fomat has also changed from Reference to canonical in R4.
|
|
780
|
-
/*
|
|
781
|
-
target.questionnaire = {
|
|
782
|
-
// questionnaireId should be an id of a related existing questionnaire resource stored in the server
|
|
783
|
-
"reference": "Questionnaire/{{questionnaireId}}"
|
|
784
|
-
};
|
|
785
|
-
*/
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* Set unit attributes to a given FHIR quantity.
|
|
791
|
-
*
|
|
792
|
-
* @param fhirQuantity - FHIR Quantity object
|
|
793
|
-
* @param lfUnit - Lforms unit, which includes name, code and system.
|
|
794
|
-
* @private
|
|
795
|
-
*/
|
|
796
|
-
self._setUnitAttributesToFhirQuantity = function(fhirQuantity, lfUnit) {
|
|
797
|
-
if(fhirQuantity && lfUnit) {
|
|
798
|
-
if(lfUnit.name) {
|
|
799
|
-
fhirQuantity.unit = lfUnit.name;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
if(lfUnit.code) {
|
|
803
|
-
fhirQuantity.code = lfUnit.code;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// Unit system is optional. It was using a default system before,
|
|
807
|
-
// Now we have an defined system field, read it from data and
|
|
808
|
-
// not assume a default.
|
|
809
|
-
if(lfUnit.system) {
|
|
810
|
-
fhirQuantity.system = lfUnit.system;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Create a FHIR coding object for a unit.
|
|
818
|
-
*
|
|
819
|
-
* @param lfUnit - Lforms unit, which includes name, code and system.
|
|
820
|
-
* @returns FHIR coding object
|
|
821
|
-
* @private
|
|
822
|
-
*/
|
|
823
|
-
self._createFhirUnitCoding = function(lfUnit) {
|
|
824
|
-
var ret = null;
|
|
825
|
-
if(lfUnit) {
|
|
826
|
-
ret = {};
|
|
827
|
-
if(lfUnit.code) {
|
|
828
|
-
ret.code = lfUnit.code;
|
|
829
|
-
}
|
|
830
|
-
if(lfUnit.name) {
|
|
831
|
-
ret.display = lfUnit.name;
|
|
832
|
-
}
|
|
833
|
-
if(lfUnit.system) {
|
|
834
|
-
ret.system = lfUnit.system;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
return ret;
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Converting the given item's value to FHIR QuestionaireResponse.answer (an array).
|
|
843
|
-
* This is almost straightly refactored out of the original function self._handleAnswerValues.
|
|
844
|
-
* This function only looks at the item value itself and not its sub-items, if any.
|
|
845
|
-
* Here are the details for a single value's conversion (to an element in the returned answer array)
|
|
846
|
-
* - For item data type quantity (QTY), a valueQuantity answer element will be created IF
|
|
847
|
-
* either (or both) item value or item unit is available.
|
|
848
|
-
* - For item data types boolean, decimal, integer, date, dateTime, instant, time, string, attachment, and url,
|
|
849
|
-
* it will be converted to a FHIR value{TYPE} entry if the value is not null, not undefined, and not
|
|
850
|
-
* an empty string.
|
|
851
|
-
* - For CNE and CWE, a valueCoding entry is created IF at least one of the item value's code, text, or system
|
|
852
|
-
* is available
|
|
853
|
-
* - No answer entry will be created in all other cases, e.g., for types reference, title, section, etc.
|
|
854
|
-
* @param item the item whose value is to be converted
|
|
855
|
-
* @return the converted FHIR QuestionnaireResponse answer (an array), or null if the value is not converted -
|
|
856
|
-
* see the function description above for more details.
|
|
857
|
-
* @private
|
|
858
|
-
*/
|
|
859
|
-
self._lfItemValueToFhirAnswer = function(item) {
|
|
860
|
-
|
|
861
|
-
// item could have an empty value if its sub-item has a value
|
|
862
|
-
if (item.value === undefined || item.value === null || item.value === '')
|
|
863
|
-
return null;
|
|
864
|
-
|
|
865
|
-
var dataType = this._getAssumedDataTypeForExport(item);
|
|
866
|
-
var values = this._answerRepeats(item)? item.value: [item.value];
|
|
867
|
-
var answers = [];
|
|
868
|
-
for(var i=0; i < values.length; ++i) {
|
|
869
|
-
var itemValue = values[i];
|
|
870
|
-
if(itemValue !== undefined && itemValue !== null && itemValue !== '') {
|
|
871
|
-
var answer = null;
|
|
872
|
-
// for Coding
|
|
873
|
-
if (dataType === 'CWE' || dataType === 'CNE') {
|
|
874
|
-
// for CWE, the value could be string if it is a user typed, not-on-list value
|
|
875
|
-
if (dataType === 'CWE' && typeof itemValue === 'string') {
|
|
876
|
-
answer = { "valueString" : itemValue };
|
|
877
|
-
}
|
|
878
|
-
else if (!jQuery.isEmptyObject(itemValue)) {
|
|
879
|
-
var answerCoding = this._setIfHasValue(null, 'system', LForms.Util.getCodeSystem(itemValue.system));
|
|
880
|
-
answerCoding = this._setIfHasValue(answerCoding, 'code', itemValue.code);
|
|
881
|
-
answerCoding = this._setIfHasValue(answerCoding, 'display', itemValue.text);
|
|
882
|
-
answer = this._setIfHasValue(null, 'valueCoding', answerCoding);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
// for Quantity
|
|
886
|
-
else if (dataType === "QTY") {
|
|
887
|
-
// For now, handling only simple quantities without the comparators.
|
|
888
|
-
// [{
|
|
889
|
-
// // from Element: extension
|
|
890
|
-
// "value" : <decimal>, // Numerical value (with implicit precision)
|
|
891
|
-
// "comparator" : "<code>", // < | <= | >= | > - how to understand the value
|
|
892
|
-
// "unit" : "<string>", // Unit representation
|
|
893
|
-
// "system" : "<uri>", // Code System that defines coded unit form
|
|
894
|
-
// "code" : "<code>" // Coded form of the unit
|
|
895
|
-
// }]
|
|
896
|
-
answer = this._setIfHasValue(null, 'valueQuantity', this._makeValueQuantity(itemValue, item.unit));
|
|
897
|
-
}
|
|
898
|
-
// for boolean, decimal, integer, date, dateTime, instant, time, string, uri, attachment
|
|
899
|
-
else if (this._lformsTypesToFHIRFields[dataType]) {
|
|
900
|
-
var valueKey = this._getValueKeyByDataType("value", item);
|
|
901
|
-
answer = {[valueKey]: itemValue};
|
|
902
|
-
}
|
|
903
|
-
if(answer !== null) {
|
|
904
|
-
answers.push(answer);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
return answers.length === 0? null: answers;
|
|
910
|
-
};
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
/**
|
|
914
|
-
* Check if an lform item has sub-items, that is, having an "items" field whose value is an array with non-zero length.
|
|
915
|
-
* @param item the item to be checked for the presense of sub-items.
|
|
916
|
-
* @return {*|boolean} true if the item has sub-items, false otherwise.
|
|
917
|
-
* @private
|
|
918
|
-
*/
|
|
919
|
-
self._lfHasSubItems = function(item) {
|
|
920
|
-
return item && item.items && Array.isArray(item.items) && item.items.length > 0;
|
|
921
|
-
};
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* Process an item of the form or the form itself - if it's the form itself, the form-level
|
|
926
|
-
* properties will not be set here and will need to be managed outside of this function.
|
|
927
|
-
* If the lforms item is repeatable, this function handles one particular occurrence of the item.
|
|
928
|
-
* @param lfItem an item in LForms form object, or the form object itself
|
|
929
|
-
* @param isForm optional, default false. If true, the given item is the form object itself.
|
|
930
|
-
* @returns {{}} the converted FHIR item
|
|
931
|
-
* @private
|
|
932
|
-
*/
|
|
933
|
-
self._processResponseItem = function(lfItem, isForm) {
|
|
934
|
-
if(isForm && (typeof isForm) !== 'boolean') { // just in case some are invoking it the old way.
|
|
935
|
-
throw new Error('_processResponseItem function signature has been changed, please check/fix.');
|
|
936
|
-
}
|
|
937
|
-
var targetItem = (isForm || lfItem.dataType === 'TITLE')? {}: {
|
|
938
|
-
linkId: lfItem.linkId,
|
|
939
|
-
text: lfItem.question
|
|
940
|
-
};
|
|
941
|
-
|
|
942
|
-
// just handle/convert the current item's value, no-recursion to sub-items at this step.
|
|
943
|
-
if (!isForm && lfItem.dataType !== 'TITLE' && lfItem.dataType !== 'SECTION') {
|
|
944
|
-
this._setIfHasValue(targetItem, 'answer', this._lfItemValueToFhirAnswer(lfItem));
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
if(this._lfHasSubItems(lfItem)) {
|
|
948
|
-
var fhirItems = [];
|
|
949
|
-
for (var i=0; i < lfItem.items.length; ++i) {
|
|
950
|
-
var lfSubItem = lfItem.items[i];
|
|
951
|
-
if(! lfSubItem._isProcessed) {
|
|
952
|
-
var linkId = lfSubItem.linkId;
|
|
953
|
-
var repeats = lfItem._repeatingItems && lfItem._repeatingItems[linkId];
|
|
954
|
-
if(repeats) { // Can only be questions here per _processRepeatingItemValues
|
|
955
|
-
let fhirItem = { // one FHIR item for all repeats with the same linkId
|
|
956
|
-
linkId: linkId,
|
|
957
|
-
text: lfSubItem.question,
|
|
958
|
-
answer: []
|
|
959
|
-
};
|
|
960
|
-
for(var rpt=0; rpt < repeats.length; ++rpt) {
|
|
961
|
-
var rptItem = repeats[rpt];
|
|
962
|
-
var tmpFhirItem = this._processResponseItem(rptItem);
|
|
963
|
-
if(tmpFhirItem.answer) {
|
|
964
|
-
// TODO: not sure how to handle cases when both (lforms) question and answer repeat.
|
|
965
|
-
// For now, just put all the answers from question and answer repeats into the answer (array).
|
|
966
|
-
Array.prototype.push.apply(fhirItem.answer, tmpFhirItem.answer);
|
|
967
|
-
}
|
|
968
|
-
rptItem._isProcessed = true;
|
|
969
|
-
}
|
|
970
|
-
fhirItems.push(fhirItem);
|
|
971
|
-
delete lfItem._repeatingItems[linkId]; // cleanup, no longer needed
|
|
972
|
-
}
|
|
973
|
-
else {
|
|
974
|
-
let fhirItem = this._processResponseItem(lfSubItem);
|
|
975
|
-
fhirItems.push(fhirItem);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if(lfSubItem._isProcessed) {
|
|
980
|
-
delete lfSubItem._isProcessed; // cleanup, no longer needed
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
if(fhirItems.length > 0) {
|
|
985
|
-
if(! isForm && lfItem.dataType !== 'SECTION') {
|
|
986
|
-
// Question repeat is handled at the "parent level"; TODO: not sure how to handle answer repeat here,
|
|
987
|
-
// assuming it isn't possible for an item to have answer repeat and sub-items at the same time.
|
|
988
|
-
targetItem.answer = targetItem.answer || [];
|
|
989
|
-
targetItem.answer[0] = targetItem.answer[0] || {};
|
|
990
|
-
targetItem.answer[0].item = fhirItems;
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
targetItem.item = fhirItems;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return targetItem;
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
/**
|
|
1003
|
-
* Group values of the questions that have the same linkId
|
|
1004
|
-
* @param item an item in the LForms form object or a form item object
|
|
1005
|
-
* @private
|
|
1006
|
-
*
|
|
1007
|
-
*/
|
|
1008
|
-
self._processRepeatingItemValues = function(item) {
|
|
1009
|
-
if (item.items) {
|
|
1010
|
-
for (var i=0, iLen=item.items.length; i<iLen; i++) {
|
|
1011
|
-
var subItem = item.items[i];
|
|
1012
|
-
// if it is a question and it repeats
|
|
1013
|
-
if (subItem.dataType !== 'TITLE' && subItem.dataType !== 'SECTION' && this._questionRepeats(subItem)) {
|
|
1014
|
-
var linkId = subItem.linkId;
|
|
1015
|
-
item._repeatingItems = item._repeatingItems || {};
|
|
1016
|
-
item._repeatingItems[linkId] = item._repeatingItems[linkId] || [];
|
|
1017
|
-
item._repeatingItems[linkId].push(subItem);
|
|
1018
|
-
}
|
|
1019
|
-
// if it's a section or a question that has children items
|
|
1020
|
-
if(this._lfHasSubItems(subItem)) {
|
|
1021
|
-
this._processRepeatingItemValues(subItem);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
};
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
/**
|
|
1029
|
-
* Get the extract value for the item or the closest parent
|
|
1030
|
-
* @param item an item in Questionnaire
|
|
1031
|
-
*/
|
|
1032
|
-
self._getExtractValue = function (item) {
|
|
1033
|
-
let currentItem = item;
|
|
1034
|
-
|
|
1035
|
-
while (true) {
|
|
1036
|
-
if (currentItem._fhirExt && currentItem._fhirExt[this.fhirExtObsExtract]) {
|
|
1037
|
-
return currentItem._fhirExt[this.fhirExtObsExtract][0].valueBoolean;
|
|
1038
|
-
} else if (!currentItem._parentItem) {
|
|
1039
|
-
return false;
|
|
1040
|
-
}
|
|
1041
|
-
currentItem = currentItem._parentItem;
|
|
1042
|
-
}
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
export default addCommonSDCExportFns;
|