lforms 29.3.0 → 33.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +261 -0
  2. package/README.md +97 -14
  3. package/changed-features.md +99 -0
  4. package/dist/lforms/LICENSE.md +70 -0
  5. package/dist/lforms/README.md +100 -0
  6. package/dist/lforms/fhir/R4/lformsFHIR.min.js +2 -0
  7. package/dist/lforms/fhir/R4/lformsFHIR.min.js.map +1 -0
  8. package/dist/lforms/fhir/STU3/lformsFHIR.min.js +2 -0
  9. package/dist/lforms/fhir/STU3/lformsFHIR.min.js.map +1 -0
  10. package/dist/lforms/fhir/lformsFHIRAll.min.js +2 -0
  11. package/dist/lforms/fhir/lformsFHIRAll.min.js.map +1 -0
  12. package/dist/lforms/webcomponent/assets/lib/zone.min.js +134 -0
  13. package/dist/lforms/webcomponent/down_arrow_gray_10_10.png +0 -0
  14. package/dist/lforms/webcomponent/favicon.ico +0 -0
  15. package/dist/lforms/webcomponent/index.html +9 -0
  16. package/dist/lforms/webcomponent/lhc-forms.js +2471 -0
  17. package/dist/lforms/webcomponent/magnifying_glass.png +0 -0
  18. package/dist/lforms/webcomponent/main.js +2464 -0
  19. package/dist/lforms/webcomponent/main.js.map +1 -0
  20. package/dist/lforms/webcomponent/polyfills.js +2 -0
  21. package/dist/lforms/webcomponent/polyfills.js.map +1 -0
  22. package/dist/lforms/webcomponent/runtime.js +2 -0
  23. package/dist/lforms/webcomponent/runtime.js.map +1 -0
  24. package/dist/lforms/webcomponent/scripts.js +3 -0
  25. package/dist/lforms/webcomponent/scripts.js.map +1 -0
  26. package/dist/lforms/webcomponent/styles.css +3 -0
  27. package/dist/lforms/webcomponent/styles.css.map +1 -0
  28. package/form_definition.md +382 -0
  29. package/lforms-33.2.0.tgz +0 -0
  30. package/package.json +94 -88
  31. package/r4-support.md +147 -0
  32. package/sdc-support.md +127 -0
  33. package/app/.npmignore +0 -3
  34. package/app/images/blank.gif +0 -0
  35. package/app/images/signal-stop.png +0 -0
  36. package/app/index.html +0 -10
  37. package/app/lforms.js +0 -56
  38. package/app/lforms.tpl.js +0 -964
  39. package/app/scripts/angular-packages.js +0 -11
  40. package/app/scripts/bootstrap-decorators.js +0 -31
  41. package/app/scripts/bower-index.js +0 -14
  42. package/app/scripts/fhir/R4/export.js +0 -33
  43. package/app/scripts/fhir/R4/fhirRequire.js +0 -36
  44. package/app/scripts/fhir/R4/lformsFHIR.js +0 -27260
  45. package/app/scripts/fhir/R4/sdc-export.js +0 -455
  46. package/app/scripts/fhir/R4/sdc-import.js +0 -483
  47. package/app/scripts/fhir/STU3/export.js +0 -27
  48. package/app/scripts/fhir/STU3/fhirRequire.js +0 -36
  49. package/app/scripts/fhir/STU3/lformsFHIR.js +0 -27291
  50. package/app/scripts/fhir/STU3/sdc-export.js +0 -493
  51. package/app/scripts/fhir/STU3/sdc-import.js +0 -494
  52. package/app/scripts/fhir/diagnostic-report.js +0 -707
  53. package/app/scripts/fhir/export-common.js +0 -209
  54. package/app/scripts/fhir/expression-processor.js +0 -670
  55. package/app/scripts/fhir/extensions/rendering-style.js +0 -23
  56. package/app/scripts/fhir/fhir-common.js +0 -3
  57. package/app/scripts/fhir/obs-prepop.mjs +0 -97
  58. package/app/scripts/fhir/runtime-common.js +0 -33
  59. package/app/scripts/fhir/sdc-common.js +0 -334
  60. package/app/scripts/fhir/sdc-export-common.js +0 -1046
  61. package/app/scripts/fhir/sdc-import-common.js +0 -1343
  62. package/app/scripts/fhir/versions.js +0 -8
  63. package/app/scripts/index.js +0 -20
  64. package/app/scripts/lforms-config.js +0 -12
  65. package/app/scripts/lforms-constants.js +0 -10
  66. package/app/scripts/lforms-controllers.js +0 -1196
  67. package/app/scripts/lforms-directives.js +0 -424
  68. package/app/scripts/lforms-index.js +0 -20
  69. package/app/scripts/lib/cross_browser.js +0 -21
  70. package/app/scripts/lib/lforms-data.js +0 -3591
  71. package/app/scripts/lib/lforms-hl7.js +0 -525
  72. package/app/scripts/lib/lforms-util.js +0 -1122
  73. package/app/scripts/lib/lforms-validate.js +0 -277
  74. package/app/scripts/lib/polyfill.js +0 -81
  75. package/app/scripts/themeList.js +0 -17
  76. package/app/styles/lforms.css +0 -1219
  77. package/app/styles/redmond-theme/README.txt +0 -2
  78. package/app/styles/redmond-theme/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  79. package/app/styles/redmond-theme/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  80. package/app/styles/redmond-theme/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  81. package/app/styles/redmond-theme/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  82. package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  83. package/app/styles/redmond-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  84. package/app/styles/redmond-theme/images/ui-icons_217bc0_256x240.png +0 -0
  85. package/app/styles/redmond-theme/images/ui-icons_2e83ff_256x240.png +0 -0
  86. package/app/styles/redmond-theme/images/ui-icons_469bdd_256x240.png +0 -0
  87. package/app/styles/redmond-theme/images/ui-icons_6da8d5_256x240.png +0 -0
  88. package/app/styles/redmond-theme/images/ui-icons_cd0a0a_256x240.png +0 -0
  89. package/app/styles/redmond-theme/images/ui-icons_d8e7f3_256x240.png +0 -0
  90. package/app/styles/redmond-theme/images/ui-icons_f9bd01_256x240.png +0 -0
  91. package/app/styles/redmond-theme/jquery-ui.css +0 -1311
  92. package/app/styles/redmond-theme/jquery-ui.min.css +0 -7
  93. package/app/styles/redmond-theme/theme.css +0 -443
  94. package/app/styles/themes.css +0 -1541
  95. package/app/styles/themes.css.map +0 -9
  96. package/app/styles/themes.scss +0 -411
  97. package/app/version.json +0 -1
  98. package/app/views/partials/attachment.html +0 -38
  99. package/app/views/partials/field-answers.html +0 -67
  100. package/app/views/partials/field-units.html +0 -24
  101. package/app/views/partials/form-controls.html +0 -19
  102. package/app/views/partials/form-header.html +0 -35
  103. package/app/views/partials/form-options.html +0 -50
  104. package/app/views/partials/form-title.html +0 -20
  105. package/app/views/partials/form-view.html +0 -13
  106. package/app/views/partials/item-options.html +0 -48
  107. package/app/views/partials/item.html +0 -126
  108. package/app/views/partials/itemPrefixAndText.html +0 -4
  109. package/app/views/partials/layout-horizontal.html +0 -156
  110. package/app/views/partials/layout-matrix.html +0 -145
  111. package/app/views/partials/popover-content.html +0 -1
  112. package/app/views/partials/repeating-button.html +0 -16
  113. package/app/views/partials/table-item.html +0 -30
  114. package/app/views/partials/template-table.html +0 -69
  115. package/app/views/partials/uib-popover-templates/uib-popover-template.html +0 -13
  116. package/app/views/partials/uib-popover-templates/uib-popover.html +0 -11
@@ -1,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;