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,1122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LForms Utility tools
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
var moment = require('moment');
|
|
6
|
-
// Acceptable date formats
|
|
7
|
-
|
|
8
|
-
// Strict parsing -
|
|
9
|
-
var parseDateFormats = [
|
|
10
|
-
'M/D/YYYY',
|
|
11
|
-
'M/D/YY',
|
|
12
|
-
'M/D',
|
|
13
|
-
'M-D-YYYY',
|
|
14
|
-
'M-D-YY',
|
|
15
|
-
'M-D',
|
|
16
|
-
'YYYY',
|
|
17
|
-
'YYYY-M-D',
|
|
18
|
-
'YYYY/M/D',
|
|
19
|
-
moment.ISO_8601,
|
|
20
|
-
'M/D/YYYY HH:mm',
|
|
21
|
-
'M/D/YY HH:mm',
|
|
22
|
-
'M/D HH:mm',
|
|
23
|
-
'M-D-YYYY HH:mm',
|
|
24
|
-
'M-D-YY HH:mm',
|
|
25
|
-
'M-D HH:mm',
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
var LForms = require('../../lforms');
|
|
30
|
-
|
|
31
|
-
LForms.Util = {
|
|
32
|
-
/**
|
|
33
|
-
* Adds an LForms form to the page.
|
|
34
|
-
* @param formDataDef A form definiton (either JSON or a parsed object). Also,
|
|
35
|
-
* for backward compatibility, this can be the name of a global-scope variable
|
|
36
|
-
* (on "window") containing that form definition object. A FHIR Questionnaire can be also be
|
|
37
|
-
* used as a form definition.
|
|
38
|
-
* @param formContainer The ID of a DOM element to contain the form, or the
|
|
39
|
-
* element itself. The contents of this element will be replaced by the form.
|
|
40
|
-
* This element should be outside the scope of any existing AngularJS app on
|
|
41
|
-
* the page.
|
|
42
|
-
* @param {Object} [options] A hash of options
|
|
43
|
-
* @param {boolean} [options.prepopulate] Set to true if you want FHIR prepopulation to happen (if
|
|
44
|
-
* the form was an imported FHIR Questionnaire).
|
|
45
|
-
* @param {string} [options.fhirVersion] Optional, version of FHIR used if formDataDef is a FHIR
|
|
46
|
-
* Questionnaire. options are: `R4` or `STU3`. If not provided an attempt will be made to determine
|
|
47
|
-
* the version from the Questionnaire content.
|
|
48
|
-
* @return a Promise that will resolve after any needed external FHIR
|
|
49
|
-
* resources have been loaded (if the form was imported from a FHIR
|
|
50
|
-
* Questionnaire).
|
|
51
|
-
*/
|
|
52
|
-
addFormToPage: function(formDataDef, formContainer, options) {
|
|
53
|
-
var formContainer = typeof formContainer === 'string' ?
|
|
54
|
-
$('#'+formContainer) : $(formContainer);
|
|
55
|
-
if (typeof formDataDef === 'string') {
|
|
56
|
-
if (formDataDef.indexOf('{') >= 0) // test for JSON
|
|
57
|
-
formDataDef = JSON.parse(formDataDef);
|
|
58
|
-
else // backward compatibility
|
|
59
|
-
formDataDef = window[formDataDef];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// If resourceType is specified assume formDataDef is FHIR
|
|
63
|
-
var resourceType = formDataDef.resourceType;
|
|
64
|
-
var fhirVersion = options && options.fhirVersion;
|
|
65
|
-
|
|
66
|
-
if (resourceType === 'Questionnaire') {
|
|
67
|
-
formDataDef = this.convertFHIRQuestionnaireToLForms(formDataDef, fhirVersion);
|
|
68
|
-
} else if (resourceType) {
|
|
69
|
-
throw new Error('Only Questionnaire FHIR content is supported in addFormToPage.')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!this.pageFormID_)
|
|
73
|
-
this.pageFormID_ = 0;
|
|
74
|
-
var appName = 'LFormsApp' + ++this.pageFormID_;
|
|
75
|
-
var controller = 'LFormsAppController'+ this.pageFormID_;
|
|
76
|
-
if (!LForms.addedFormDefs)
|
|
77
|
-
LForms.addedFormDefs = [];
|
|
78
|
-
var formIndex = LForms.addedFormDefs.length;
|
|
79
|
-
LForms.addedFormDefs.push(formDataDef);
|
|
80
|
-
var prepop = options && options.prepopulate===true;
|
|
81
|
-
formContainer.html(
|
|
82
|
-
'<div ng-controller="'+controller+'">'+
|
|
83
|
-
'<lforms lf-data="myFormData"></lforms>'+
|
|
84
|
-
'</div>'
|
|
85
|
-
);
|
|
86
|
-
var rtnPromise = new Promise(function(resolve, reject) {
|
|
87
|
-
angular.module(appName, ["lformsWidget"])
|
|
88
|
-
.controller(controller, ["$scope", function ($scope) {
|
|
89
|
-
try {
|
|
90
|
-
var myFormData = new LForms.LFormsData(LForms.addedFormDefs[formIndex]);
|
|
91
|
-
if (LForms.fhirContext) {
|
|
92
|
-
myFormData.loadFHIRResources(prepop).then(function() {
|
|
93
|
-
$scope.$apply(function() {
|
|
94
|
-
$scope.myFormData = myFormData;
|
|
95
|
-
resolve();
|
|
96
|
-
})
|
|
97
|
-
}, function failed(e) {
|
|
98
|
-
reject(e);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
$scope.myFormData = myFormData;
|
|
103
|
-
resolve();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch(e) {
|
|
107
|
-
reject(e);
|
|
108
|
-
}
|
|
109
|
-
}]);
|
|
110
|
-
}).catch(function(e) {
|
|
111
|
-
var errMsg = (e instanceof Error) ? e.message : e.toString();
|
|
112
|
-
formContainer.html('<div id=lformsErrors style="color: red"><b>Unable to '+
|
|
113
|
-
'display the form, due to the following error:</b><p id=lformsErrorContent></p>');
|
|
114
|
-
$('#lformsErrorContent').text(errMsg);
|
|
115
|
-
throw e;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Bootstrap the element if needed
|
|
119
|
-
// Following http://stackoverflow.com/a/34501500/360782
|
|
120
|
-
var isInitialized = formContainer.injector && formContainer.injector();
|
|
121
|
-
if (!isInitialized)
|
|
122
|
-
angular.bootstrap(formContainer.children(':first'), [appName]);
|
|
123
|
-
|
|
124
|
-
return rtnPromise;
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Get user input data from the form, with or without form definition data.
|
|
130
|
-
* @param element the containing HTML element that includes the LForm's rendered form.
|
|
131
|
-
* @param noFormDefData optional, to include form definition data, the default is false.
|
|
132
|
-
* @param noEmptyValue optional, to remove items that have an empty value, the default is false.
|
|
133
|
-
* @param noDisabledItem optional, to remove items that are disabled by skip logic, the default is false.
|
|
134
|
-
* @returns {{itemsData: (*|Array), templateData: (*|Array)}} form data and template data
|
|
135
|
-
*/
|
|
136
|
-
getUserData: function(element, noFormDefData, noEmptyValue, noDisabledItem) {
|
|
137
|
-
var formObj = this._getFormObjectInScope(element);
|
|
138
|
-
return formObj ? formObj.getUserData(noFormDefData, noEmptyValue, noDisabledItem) : null;
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Get the complete form definition data, including the user input data from the form.
|
|
144
|
-
* The returned data could be fed into a LForms widget directly to render the form.
|
|
145
|
-
* @param element optional, the containing HTML element that includes the LForm's rendered form.
|
|
146
|
-
* It could either be the DOM element or its id.
|
|
147
|
-
* @param noEmptyValue optional, to remove items that have an empty value, the default is false.
|
|
148
|
-
* @param noDisabledItem optional, to remove items that are disabled by skip logic, the default is false.
|
|
149
|
-
* @returns {{}} Form definition data
|
|
150
|
-
*/
|
|
151
|
-
getFormData: function(element, noEmptyValue, noDisabledItem) {
|
|
152
|
-
var formObj = this._getFormObjectInScope(element);
|
|
153
|
-
return formObj ? formObj.getFormData(noEmptyValue, noDisabledItem) : null;
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Get HL7 v2 OBR and OBX segment data from the form.
|
|
159
|
-
* Empty or hidden questions are not included.
|
|
160
|
-
* @param element optional, the containing HTML element that includes the LForm's rendered form.
|
|
161
|
-
* It could either be the DOM element or its id
|
|
162
|
-
* @returns {*} a string that contains HL7 v2 OBR and OBX segments
|
|
163
|
-
*/
|
|
164
|
-
getFormHL7Data: function(element) {
|
|
165
|
-
var formObj = this._getFormObjectInScope(element);
|
|
166
|
-
return formObj ? LForms.HL7.toHL7Segments(formObj) : null;
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get FHIR data from the form.
|
|
172
|
-
* @param resourceType a FHIR resource type. it currently supports "DiagnosticReport",
|
|
173
|
-
* "Questionnaire" (both standard Questionnaire and SDC Questionnaire profile)
|
|
174
|
-
* and "QuestionnaireResponse" (SDC profile).
|
|
175
|
-
* @param fhirVersion the version of FHIR being used (e.g., 'STU3')
|
|
176
|
-
* @param formDataSource Optional. Either the containing HTML element that
|
|
177
|
-
* includes the LForm's rendered form, a CSS selector for that element, an
|
|
178
|
-
* LFormsData object, or an LForms form definition (parsed). If not
|
|
179
|
-
* provided, the first form found in the page will be used.
|
|
180
|
-
* @param options A hash of other options. See convertLFormsToFHIRData for
|
|
181
|
-
* the allowed values.
|
|
182
|
-
* @returns {*} the requested FHIR resource. For Questionnaire, the full form definition
|
|
183
|
-
* will be returned, but or DiagnosticReport and QuestionnaireResponse, empty
|
|
184
|
-
* or hidden questions will not be included.
|
|
185
|
-
*/
|
|
186
|
-
getFormFHIRData: function(resourceType, fhirVersion, formDataSource, options) {
|
|
187
|
-
if (!formDataSource || formDataSource instanceof HTMLElement || typeof formDataSource === 'string')
|
|
188
|
-
formDataSource = this._getFormObjectInScope(formDataSource);
|
|
189
|
-
return this._convertLFormsToFHIRData(resourceType, fhirVersion, formDataSource, options);
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Get a list of errors preventing the form from being valid.
|
|
195
|
-
* @param [element] optional, the containing HTML element that includes the LForm's rendered form.
|
|
196
|
-
* @return {Array<string>} list of errors or null if form is valid
|
|
197
|
-
*/
|
|
198
|
-
checkValidity: function (element) {
|
|
199
|
-
var formObj = this._getFormObjectInScope(element);
|
|
200
|
-
return formObj ? formObj.checkValidity() : null;
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Convert LForms data into a FHIR resource
|
|
206
|
-
* @param resourceType a FHIR resource type. it currently supports "DiagnosticReport",
|
|
207
|
-
* "Questionnaire" (both standard Questionnaire and SDC Questionnaire profile)
|
|
208
|
-
* and "QuestionnaireResponse" (SDC profile).
|
|
209
|
-
* @param fhirVersion the version of FHIR to be used (e.g., 'STU3')
|
|
210
|
-
* @param formData an LFormsData object or an LForms form definition (parsed).
|
|
211
|
-
* @param options A hash of other options, with the following optional keys:
|
|
212
|
-
* * bundleType: optional, maybe be either "transaction" or "collection".
|
|
213
|
-
* This is used when resourceType is set to "DiagnosticReport", and requests
|
|
214
|
-
* that the DiagnosticReport and associated Observation resources be placed
|
|
215
|
-
* together in a bundle. When this is not present, a bundle will not be
|
|
216
|
-
* used.
|
|
217
|
-
* * noExtensions: a flag that a standard FHIR Questionnaire or QuestionnaireResponse is to be created
|
|
218
|
-
* without any extensions, when resourceType is Questionnaire or QuestionnaireResponse.
|
|
219
|
-
* The default is false.
|
|
220
|
-
* * extract: a flag for QuestionnaireReponse that data should be extracted
|
|
221
|
-
* (using the observationExtract extension). In this case the returned
|
|
222
|
-
* resource will be an array consisting of the QuestionnaireResponse and any
|
|
223
|
-
* extracted Observations.
|
|
224
|
-
* * subject: A local FHIR resource that is the subject of the output resource.
|
|
225
|
-
* If provided, a reference to this resource will be added to the output FHIR
|
|
226
|
-
* resource when applicable.
|
|
227
|
-
* @returns {*} a FHIR resource, or (if extract is true) an array of
|
|
228
|
-
* resources.
|
|
229
|
-
*/
|
|
230
|
-
_convertLFormsToFHIRData: function(resourceType, fhirVersion, formData, options) {
|
|
231
|
-
if (!options)
|
|
232
|
-
options = {};
|
|
233
|
-
if (!(formData instanceof LForms.LFormsData))
|
|
234
|
-
formData = new LForms.LFormsData(formData);
|
|
235
|
-
var version = this.validateFHIRVersion(fhirVersion);
|
|
236
|
-
var fhir = LForms.FHIR[version];
|
|
237
|
-
var fhirData = null;
|
|
238
|
-
if (formData) {
|
|
239
|
-
switch (resourceType) {
|
|
240
|
-
case "DiagnosticReport":
|
|
241
|
-
var bundleType = options ? options.bundleType : undefined;
|
|
242
|
-
var inBundle = bundleType != undefined;
|
|
243
|
-
fhirData = fhir.DiagnosticReport.createDiagnosticReport(formData,
|
|
244
|
-
options.subject, inBundle, bundleType);
|
|
245
|
-
break;
|
|
246
|
-
case "Questionnaire":
|
|
247
|
-
fhirData = fhir.SDC.convertLFormsToQuestionnaire(formData,
|
|
248
|
-
options.noExtensions);
|
|
249
|
-
break;
|
|
250
|
-
case "QuestionnaireResponse":
|
|
251
|
-
if (options.extract)
|
|
252
|
-
fhirData = fhir.SDC.convertLFormsToFHIRData(formData,
|
|
253
|
-
options.noExtensions, options.subject);
|
|
254
|
-
else
|
|
255
|
-
fhirData = fhir.SDC.convertLFormsToQuestionnaireResponse(formData,
|
|
256
|
-
options.noExtensions, options.subject);
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return fhirData;
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Convert FHIR SQC Questionnaire to the LForms internal definition
|
|
266
|
-
*
|
|
267
|
-
* @param fhirData - a FHIR Questionnaire resource, which should be generated through
|
|
268
|
-
* the function "getFormFHIRData('Questionnaire', ...)"
|
|
269
|
-
* @param fhirVersion - the version of FHIR in which the Questionnaire is
|
|
270
|
-
* written. This maybe be omitted if the Questionnaire resource (in
|
|
271
|
-
* fhirData) contains a meta.profile attibute specifying the FHIR version.
|
|
272
|
-
* (See http://build.fhir.org/versioning.html#mp-version)
|
|
273
|
-
* If both are provided, this takes precedence.
|
|
274
|
-
* @returns {*} - an LForms json object
|
|
275
|
-
*/
|
|
276
|
-
convertFHIRQuestionnaireToLForms: function(fhirData, fhirVersion) {
|
|
277
|
-
var rtn = null;
|
|
278
|
-
if (fhirData) {
|
|
279
|
-
fhirVersion = this._requireValidFHIRVersion(fhirVersion, fhirData);
|
|
280
|
-
var fhir = LForms.FHIR[fhirVersion];
|
|
281
|
-
rtn = fhir.SDC.convertQuestionnaireToLForms(fhirData);
|
|
282
|
-
}
|
|
283
|
-
return rtn;
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Merge a FHIR resource into an LForms form object
|
|
289
|
-
* @param resourceType a FHIR resource type. it currently supports "DiagnosticReport" and
|
|
290
|
-
* "QuestionnaireResponse" (SDC profile)
|
|
291
|
-
* @param fhirData a QuestionnaireResponse resource, a DiagnosticReport resource with "contained" Observation
|
|
292
|
-
* resources,or a Bundle with DiagnosticReport and Observation resources
|
|
293
|
-
* @param formData an LForms form definition or LFormsData object.
|
|
294
|
-
* @param fhirVersion - the version of FHIR in which the fhirData is
|
|
295
|
-
* written. This maybe be omitted if the Questionnaire resource (in
|
|
296
|
-
* fhirData) contains a meta.profile attibute specifying the FHIR version.
|
|
297
|
-
* (See http://build.fhir.org/versioning.html#mp-version)
|
|
298
|
-
* If both are provided, this takes precedence.
|
|
299
|
-
* @returns {{}} an updated LForms form definition, with answer data
|
|
300
|
-
*/
|
|
301
|
-
mergeFHIRDataIntoLForms: function(resourceType, fhirData, formData, fhirVersion) {
|
|
302
|
-
if (fhirData) {
|
|
303
|
-
fhirVersion = this._requireValidFHIRVersion(fhirVersion, fhirData);
|
|
304
|
-
var fhir = LForms.FHIR[fhirVersion];
|
|
305
|
-
switch (resourceType) {
|
|
306
|
-
case "DiagnosticReport":
|
|
307
|
-
formData = fhir.DiagnosticReport.mergeDiagnosticReportToLForms(formData, fhirData);
|
|
308
|
-
formData.hasSavedData = true;
|
|
309
|
-
break;
|
|
310
|
-
case "QuestionnaireResponse":
|
|
311
|
-
formData = fhir.SDC.mergeQuestionnaireResponseToLForms(formData, fhirData);
|
|
312
|
-
formData.hasSavedData = true; // will be used to determine whether to update or save
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return formData;
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Ensures that either the given FHIR version is valid and supported, or
|
|
322
|
-
* that a valid version can be determined from the given FHIR resource.
|
|
323
|
-
*/
|
|
324
|
-
_requireValidFHIRVersion: function(fhirVersion, fhirResource) {
|
|
325
|
-
if (!fhirVersion)
|
|
326
|
-
fhirVersion = this.detectFHIRVersion(fhirResource) || this.guessFHIRVersion(fhirResource);
|
|
327
|
-
if (!fhirVersion) {
|
|
328
|
-
throw new Error('Could not determine the FHIR version for this resource. '+
|
|
329
|
-
'Please make sure it is specified via meta.profile (see '+
|
|
330
|
-
'http://build.fhir.org/versioning.html#mp-version and '+
|
|
331
|
-
'https://www.hl7.org/fhir/references.html#canonical). '+
|
|
332
|
-
'Example 1: http://hl7.org/fhir/4.0/StructureDefinition/Questionnaire'+
|
|
333
|
-
' (for Questionnaire version 4.0, a.k.a. R4).'+
|
|
334
|
-
'Example 2: http://hl7.org/fhir/3.0/StructureDefinition/Questionnaire'+
|
|
335
|
-
' (for Questionnaire version 3.0, a.k.a. STU3).'+
|
|
336
|
-
'Example 3: http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire|2.7 '+
|
|
337
|
-
' (for SDC Questionnaire version 2.7).');
|
|
338
|
-
}
|
|
339
|
-
else
|
|
340
|
-
fhirVersion = this.validateFHIRVersion(fhirVersion);
|
|
341
|
-
return fhirVersion;
|
|
342
|
-
},
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* For FHIR applications, provides FHIR context information that might be
|
|
347
|
-
* needed in rendering a Quesitonnaire.
|
|
348
|
-
* Priort to calling this, the LHC-Forms FHIR support files should be loaded.
|
|
349
|
-
* @param fhirContext an optional object for accessing a FHIR context and
|
|
350
|
-
* a FHIR API. It should be an instance of 'client-js', a.k.a. npm package fhirclient,
|
|
351
|
-
* version 2. (See http://docs.smarthealthit.org/client-js).
|
|
352
|
-
*/
|
|
353
|
-
setFHIRContext: function(fhirContext) {
|
|
354
|
-
if (!LForms.FHIR) {
|
|
355
|
-
throw new Error('LHC-Forms FHIR support files have not been loaded.' +
|
|
356
|
-
'See http://lhncbc.github.io/lforms/#fhirScripts');
|
|
357
|
-
}
|
|
358
|
-
LForms.fhirContext = fhirContext;
|
|
359
|
-
LForms.fhirCapabilities = {}; // our own flags about what the server can do
|
|
360
|
-
delete LForms._serverFHIRReleaseID; // in case the version changed
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Converts a FHIR version string (e.g. 3.0.1) to a release ID (e.g. 'STU3').
|
|
366
|
-
* @param versionStr the version string to be converted to a release ID.
|
|
367
|
-
* @return the release ID for the given version string, or versionStr if the
|
|
368
|
-
* version string cannot be mapped to a release ID.
|
|
369
|
-
*/
|
|
370
|
-
_fhirVersionToRelease: function(versionStr) {
|
|
371
|
-
var releaseID = versionStr; // default
|
|
372
|
-
var matchData = versionStr.match(/^\d+(\.\d+)/);
|
|
373
|
-
if (matchData) {
|
|
374
|
-
var versionNum = parseFloat(matchData[0]);
|
|
375
|
-
// Following http://www.hl7.org/fhir/directory.cfml
|
|
376
|
-
var releaseID = versionNum > 3.0 && versionNum <= 4.0 ?
|
|
377
|
-
'R4' : versionNum >= 1.1 && versionNum <= 3.0 ? 'STU3' : versionStr;
|
|
378
|
-
}
|
|
379
|
-
return releaseID;
|
|
380
|
-
},
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Gets the release identifier for the version of FHIR being used by the
|
|
385
|
-
* server providing the FHIR context set via setFHIRContext (which must have
|
|
386
|
-
* been called first).
|
|
387
|
-
* @param callback Because asking the FHIR server for its version is an
|
|
388
|
-
* asynchronous call, this callback function will be used to return the
|
|
389
|
-
* version when found. The callback will be called asynchronously with a
|
|
390
|
-
* release string, like 'STU3' or 'R4'. This string can then be passed to
|
|
391
|
-
* validateFHIRVersion to check that the needed support files have been loaded.
|
|
392
|
-
* If the release ID cannot be determined because the server's fhir
|
|
393
|
-
* version is not known, the version number will passed to the callback. If
|
|
394
|
-
* communication with the FHIR server is not succesful, the callback will be
|
|
395
|
-
* called without an argument.
|
|
396
|
-
*/
|
|
397
|
-
getServerFHIRReleaseID: function(callback) {
|
|
398
|
-
if (!LForms.fhirContext)
|
|
399
|
-
throw new Error('setFHIRContext needs to be called before getFHIRReleaseID');
|
|
400
|
-
if (!LForms._serverFHIRReleaseID) {
|
|
401
|
-
// Retrieve the fhir version
|
|
402
|
-
try {
|
|
403
|
-
var fhirAPI = LForms.fhirContext;
|
|
404
|
-
//fhirAPI.request('metadata?_elements=fhirVersion').then(function(res) // causes an error on lforms-smart-fhir (TBD)
|
|
405
|
-
fhirAPI.getFhirVersion().then(function(fhirVersion) {
|
|
406
|
-
LForms._serverFHIRReleaseID = LForms.Util._fhirVersionToRelease(fhirVersion);
|
|
407
|
-
console.log('Server FHIR version is '+LForms._serverFHIRReleaseID+' ('+
|
|
408
|
-
fhirVersion+')');
|
|
409
|
-
callback(LForms._serverFHIRReleaseID);
|
|
410
|
-
}, function(err) {
|
|
411
|
-
console.log("Error retrieving server's CompatibilityStatement:");
|
|
412
|
-
console.log(err);
|
|
413
|
-
callback();
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
catch(e) {
|
|
417
|
-
setTimeout(function(){callback()});
|
|
418
|
-
throw e;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
else { // preserve the asynchronous nature of the return
|
|
422
|
-
setTimeout(function(){callback(LForms._serverFHIRReleaseID)});
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Checks to see if the given value is a valid FHIR version. If the
|
|
429
|
-
* version is unsupported, an exception is thrown. Also, if the version is
|
|
430
|
-
* supported but the support file is not loaded, an exception will be thrown.
|
|
431
|
-
* @version the version of FHIR that was requested
|
|
432
|
-
* @return the version passed in
|
|
433
|
-
*/
|
|
434
|
-
validateFHIRVersion: function(version) {
|
|
435
|
-
if (LForms.Util.FHIRSupport[version]) {
|
|
436
|
-
if (!LForms.FHIR) {
|
|
437
|
-
throw new Error('The FHIR support files for LHC-Forms do not appear to '+
|
|
438
|
-
'have been loaded. Please consult the documentation at '+
|
|
439
|
-
'http://lhncbc.github.io/lforms/#fhirSupport.');
|
|
440
|
-
}
|
|
441
|
-
else if (!LForms.FHIR[version])
|
|
442
|
-
throw new Error('Version '+version+
|
|
443
|
-
' of FHIR is supported, but the supporting code was not loaded.');
|
|
444
|
-
}
|
|
445
|
-
else
|
|
446
|
-
throw new Error('Version '+version+' of FHIR is not supported.');
|
|
447
|
-
return version;
|
|
448
|
-
},
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Attempts to detect the version of FHIR specified in the given resource.
|
|
453
|
-
* @param fhirData a FHIR resource. Supported resource types are currently
|
|
454
|
-
* just Questionnaire and QuestionnaireResponse.
|
|
455
|
-
* @return the FHIR version, or null if the FHIR version was not explicity
|
|
456
|
-
* specified in the resource.
|
|
457
|
-
*/
|
|
458
|
-
detectFHIRVersion: function(fhirData) {
|
|
459
|
-
var fhirVersion;
|
|
460
|
-
if (fhirData.meta && fhirData.meta.profile) {
|
|
461
|
-
var profiles = fhirData.meta.profile;
|
|
462
|
-
// See http://build.fhir.org/versioning.html#mp-version
|
|
463
|
-
var questionnairePattern =
|
|
464
|
-
new RegExp('http://hl7.org/fhir/(\\d+\.\\d+)([\\.\\d]+)?/StructureDefinition/Questionnaire');
|
|
465
|
-
var sdcPattern =
|
|
466
|
-
new RegExp('http://hl7.org/fhir/u./sdc/StructureDefinition/sdc-questionnaire\\|(\\d+\.\\d+)(\.\\d+)?');
|
|
467
|
-
for (var i=0, len=profiles.length && !fhirVersion; i<len; ++i) {
|
|
468
|
-
var match = profiles[i].match(questionnairePattern);
|
|
469
|
-
if (match)
|
|
470
|
-
fhirVersion = match[1];
|
|
471
|
-
else {
|
|
472
|
-
match = profiles[i].match(sdcPattern);
|
|
473
|
-
if (match) {
|
|
474
|
-
fhirVersion = match[1];
|
|
475
|
-
// See http://www.hl7.org/fhir/uv/sdc/history.cfml
|
|
476
|
-
// Use FHIR 3.0 for SDC 2.0; There was no SDC 3.0
|
|
477
|
-
if (fhirVersion == '2.0') {
|
|
478
|
-
fhirVersion = '3.0';
|
|
479
|
-
}
|
|
480
|
-
// use FHIR 4.0 for SDC version >= 2.1
|
|
481
|
-
else if (parseFloat(fhirVersion) >= 2.1) {
|
|
482
|
-
fhirVersion = '4.0';
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
if (fhirVersion)
|
|
489
|
-
fhirVersion = this._fhirVersionToRelease(fhirVersion);
|
|
490
|
-
return fhirVersion;
|
|
491
|
-
},
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Looks at the structure of the given FHIR resource to determine the version
|
|
496
|
-
* of FHIR, if possible.
|
|
497
|
-
* @param fhirData a FHIR resource. Supported resource types are currently
|
|
498
|
-
* just Questionnaire and QuestionnaireResponse.
|
|
499
|
-
* @return the FHIR version number (e.g. STU3), or null if the type cannot be
|
|
500
|
-
* determined.
|
|
501
|
-
*/
|
|
502
|
-
guessFHIRVersion: function(fhirData) {
|
|
503
|
-
var version = null;
|
|
504
|
-
if (fhirData.resourceType == 'Questionnaire') {
|
|
505
|
-
// See if any items have a property deleted from R4.
|
|
506
|
-
var items = [];
|
|
507
|
-
var foundSTU3 = this._testValues(fhirData, 'item', function(item) {
|
|
508
|
-
return !!(item.option || item.options ||
|
|
509
|
-
(item.enableWhen && 'hasAnswer' in item.enableWhen));
|
|
510
|
-
});
|
|
511
|
-
version = foundSTU3 ? 'STU3' : 'R4';
|
|
512
|
-
}
|
|
513
|
-
else if (fhirData.resourceType == 'QuestionnaireResponse') {
|
|
514
|
-
if (fhirData.parent)
|
|
515
|
-
version = 'STU3';
|
|
516
|
-
else {
|
|
517
|
-
// See if any items have a property deleted from R4.
|
|
518
|
-
var foundSTU3 = this._testValues(fhirData, 'item', function(item) {
|
|
519
|
-
return !!item.subject;
|
|
520
|
-
});
|
|
521
|
-
version = foundSTU3 ? 'STU3' : 'R4';
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
return version;
|
|
525
|
-
},
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Searches the properties and sub-properties of "obj" for the given property
|
|
530
|
-
* name, testing their values to see if valTest returns true.
|
|
531
|
-
* @param obj the object to be searched. This can be an array.
|
|
532
|
-
* @param property the property name to look for
|
|
533
|
-
* @param testVal the function to use to test the property values. This
|
|
534
|
-
* should return true if the value passes the test.
|
|
535
|
-
* @return true if at least one value was found found whose key was "property"
|
|
536
|
-
* and for which testVal returned true.
|
|
537
|
-
*/
|
|
538
|
-
_testValues: function(obj, property, valTest) {
|
|
539
|
-
var rtn = false;
|
|
540
|
-
if (obj instanceof Array) {
|
|
541
|
-
for (var j=0, jLen=obj.length; !rtn && j<jLen; ++j)
|
|
542
|
-
rtn = this._testValues(obj[j], property, valTest);
|
|
543
|
-
}
|
|
544
|
-
else if (typeof obj === "object") {
|
|
545
|
-
var keys = Object.keys(obj);
|
|
546
|
-
for (var i=0, len=keys.length; !rtn && i<len; ++i) {
|
|
547
|
-
var key = keys[i];
|
|
548
|
-
var val = obj[key];
|
|
549
|
-
if (key === property) {
|
|
550
|
-
if (val instanceof Array) {
|
|
551
|
-
for (var k=0, kLen=val.length; !rtn && k<kLen; ++k)
|
|
552
|
-
rtn = valTest(val[k]);
|
|
553
|
-
}
|
|
554
|
-
else
|
|
555
|
-
rtn = valTest(val);
|
|
556
|
-
}
|
|
557
|
-
if (!rtn)
|
|
558
|
-
rtn = this._testValues(val, property, valTest); // search sub-objects
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return rtn;
|
|
562
|
-
},
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Find the form object in the scope based the form dom element
|
|
567
|
-
* @param element element the containing HTML element that includes the LForm's rendered form.
|
|
568
|
-
* @returns {*}
|
|
569
|
-
* @private
|
|
570
|
-
*/
|
|
571
|
-
_getFormObjectInScope: function(element) {
|
|
572
|
-
// find the scope that has the LForms data
|
|
573
|
-
var formObj;
|
|
574
|
-
if (!element) element = jQuery("body");
|
|
575
|
-
|
|
576
|
-
// class="lf-form"> is the element that contains rendered form.
|
|
577
|
-
var lfForms = jQuery(element).find(".lf-form");
|
|
578
|
-
angular.forEach(lfForms, function(ele, index) {
|
|
579
|
-
var lfForm = angular.element(ele);
|
|
580
|
-
if (lfForm.scope() && lfForm.scope().lfData) {
|
|
581
|
-
formObj = lfForm.scope().lfData;
|
|
582
|
-
return false; // break the loop
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
return formObj;
|
|
587
|
-
},
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* This function and stringToDTDateISO are meant to work as a pair on DT (or FHIR date) data type.
|
|
591
|
-
* The idea is that DT/date type does not have timezone info, as a result, the string value could be
|
|
592
|
-
* off by a day during either way of conversion depending on the time zone the code is executed.
|
|
593
|
-
* The solution here is to keep the literal values of the year, month, and date components remain
|
|
594
|
-
* unchanged regardless of the time zones.
|
|
595
|
-
* Convert the given date object into a DT type date string, in "yyyy-mm-dd" format, where the
|
|
596
|
-
* year, month, and date are based on the "local time zone" as the users can see on the display.
|
|
597
|
-
* @param dateObj the date object to be converted.
|
|
598
|
-
* @return date string in yyyy-mm-dd format with year, month, and date values corresponding to that
|
|
599
|
-
* at the local timezone.
|
|
600
|
-
*/
|
|
601
|
-
dateToDTStringISO: function (dateObj) {
|
|
602
|
-
return (! dateObj || !(dateObj instanceof Date) || isNaN(dateObj.getTime()))? undefined: [
|
|
603
|
-
(10000 + dateObj.getFullYear()).toString().substr(1),
|
|
604
|
-
(101 + dateObj.getMonth()).toString().substr(1),
|
|
605
|
-
(100 + dateObj.getDate()).toString().substr(1) ].join('-');
|
|
606
|
-
},
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Parse the given iso date string, that is, a string of format "yyyy[-mm[-dd]]", into a Date object,
|
|
610
|
-
* and then, adjust the year, month, and day so that when displayed (as local date) the literal values of
|
|
611
|
-
* the year, month, and date components remain unchanged.
|
|
612
|
-
* See the comments/docs for function dateToDTStringISO().
|
|
613
|
-
* @param isoDateString
|
|
614
|
-
*/
|
|
615
|
-
stringToDTDateISO: function(isoDateString) {
|
|
616
|
-
var d = new Date(isoDateString);
|
|
617
|
-
return isNaN(d.getTime())? undefined: new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
|
618
|
-
},
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Get a formatted date string from a date object
|
|
622
|
-
* for example: "2016-10-31T14:42:12Z"
|
|
623
|
-
* @param objDate a date object, or a valid string representation of date.
|
|
624
|
-
* @returns a formatted date string
|
|
625
|
-
*/
|
|
626
|
-
dateToDTMString: function(objDate) {
|
|
627
|
-
if(typeof objDate === 'string') {
|
|
628
|
-
objDate = LForms.Util.stringToDate(objDate, true);
|
|
629
|
-
}
|
|
630
|
-
return objDate.toISOString();
|
|
631
|
-
},
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Parse a formatted date string and create a date object
|
|
635
|
-
* @param strDate a formatted date string
|
|
636
|
-
* @param looseParsing {boolean} - Do default parsing. Intended to parse output
|
|
637
|
-
* from native date object, typically from programmatic output from widgets. Default is false.
|
|
638
|
-
* @returns a date object
|
|
639
|
-
*/
|
|
640
|
-
stringToDate: function(strDate, looseParsing) {
|
|
641
|
-
if(! strDate || (typeof strDate) != 'string') { // maybe already a date object.
|
|
642
|
-
return strDate;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
if(strDate.trim() === 't') {
|
|
646
|
-
return new Date();
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
let m = moment(strDate, parseDateFormats, true);
|
|
650
|
-
if(looseParsing && !m.isValid()) { // Make another attempt for loose parsing.
|
|
651
|
-
m = moment(strDate);
|
|
652
|
-
}
|
|
653
|
-
return m.isValid() ? m.toDate() : null;
|
|
654
|
-
},
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Validate date object or date string. If string, check to see if it is in acceptable formats.
|
|
659
|
-
* See stringToDate() for acceptable formats.
|
|
660
|
-
* @param date {Date | string} - Potential date object or date string
|
|
661
|
-
* @returns boolean
|
|
662
|
-
*/
|
|
663
|
-
isValidDate: function(date) {
|
|
664
|
-
return !!(LForms.Util.stringToDate(date));
|
|
665
|
-
},
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Format a date object with given format. Refer to momentjs documentation for
|
|
670
|
-
* format specification.
|
|
671
|
-
*
|
|
672
|
-
* @param date - Date object
|
|
673
|
-
* @param format - Format string
|
|
674
|
-
* @returns {string}
|
|
675
|
-
*/
|
|
676
|
-
formatDate: function(date, format) {
|
|
677
|
-
return moment(date).format(format);
|
|
678
|
-
},
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Check if an item's value is empty, where the data has no meaningful use.
|
|
683
|
-
* @param value the value to be tested
|
|
684
|
-
* @returns {boolean}
|
|
685
|
-
* @private
|
|
686
|
-
*/
|
|
687
|
-
isItemValueEmpty: function(value) {
|
|
688
|
-
var empty = true;
|
|
689
|
-
if(value !== null && value !== undefined && value !== '' && typeof value !== 'function') {
|
|
690
|
-
if(typeof value === 'string' || value instanceof String) {
|
|
691
|
-
empty = value.trim() === '';
|
|
692
|
-
}
|
|
693
|
-
else if(Array.isArray(value)) {
|
|
694
|
-
for(var i=0; i < value.length; ++i) {
|
|
695
|
-
if(! this.isItemValueEmpty(value[i])) {
|
|
696
|
-
empty = false;
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
else if(typeof value === 'object') {
|
|
702
|
-
var keys = Object.keys(value);
|
|
703
|
-
if(keys.length > 0) {
|
|
704
|
-
for(var i=0, iLen=keys.length; i<iLen; i++) {
|
|
705
|
-
if(! this.isItemValueEmpty(value[keys[i]])) {
|
|
706
|
-
empty = false;
|
|
707
|
-
break;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
else if(value.constructor !== Object) { // e.g., a Date object has zero length keys
|
|
712
|
-
empty = false;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
else {
|
|
716
|
-
empty = false;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return empty;
|
|
721
|
-
},
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Get the letter (or letters) indicator for the next repeating instance
|
|
726
|
-
* The letters returned are in the pattern of:
|
|
727
|
-
* 'a','b',...,'z','aa','ab',...,'az','ba','bb',...
|
|
728
|
-
* for index that is:
|
|
729
|
-
* 1, 2, ..., 26, 27, 28, ..., 52, 53, 54, ...
|
|
730
|
-
* @param index the index for the current repeating instance, starting with 1
|
|
731
|
-
*/
|
|
732
|
-
getNextLetter: function(index) {
|
|
733
|
-
var letters = "abcdefghijklmnopqrstuvqxyz";
|
|
734
|
-
var positions = [];
|
|
735
|
-
var wkIndex = index;
|
|
736
|
-
while (wkIndex > 0) {
|
|
737
|
-
var letterIndex = wkIndex % 26;
|
|
738
|
-
letterIndex = letterIndex === 0 ? 25 : letterIndex - 1;
|
|
739
|
-
positions.push(letterIndex);
|
|
740
|
-
wkIndex = Math.floor((wkIndex-1) / 26);
|
|
741
|
-
}
|
|
742
|
-
var nextLetter = "";
|
|
743
|
-
for(var i=positions.length-1; i>=0; i--) {
|
|
744
|
-
nextLetter += letters.charAt(positions[i]);
|
|
745
|
-
}
|
|
746
|
-
return nextLetter;
|
|
747
|
-
},
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* Finds an object from an array using key/value pair with an optional start index.
|
|
752
|
-
* The matching value should be a primitive type. If start index is not specified,
|
|
753
|
-
* it is assumed to be 0.
|
|
754
|
-
*
|
|
755
|
-
* Only returns the first matched object in the array.
|
|
756
|
-
*
|
|
757
|
-
* @param targetObjects - Array of objects to search using key and value
|
|
758
|
-
* @param key - Key of the target object to match the value.
|
|
759
|
-
* @param matchingValue - Matching value of the specified key.
|
|
760
|
-
* @param starting_index - Optional start index to lookup. Negative number indicates index from end.
|
|
761
|
-
* The absolute value should be less than the length of items in the array. If not
|
|
762
|
-
* the starting index is assumed to be 0.
|
|
763
|
-
* @param all - If true, then an array will be returned containing all
|
|
764
|
-
* matches.
|
|
765
|
-
*
|
|
766
|
-
* @returns {*} - If "all" is false (default), then this returns the first matched
|
|
767
|
-
* object, or null if none matched. If "all" is true, then this will return
|
|
768
|
-
* an array containing any matched objects.
|
|
769
|
-
*/
|
|
770
|
-
findObjectInArray: function(targetObjects, key, matchingValue, starting_index, all) {
|
|
771
|
-
var ret = all ? [] : null;
|
|
772
|
-
if(Array.isArray(targetObjects)) {
|
|
773
|
-
var start = 0;
|
|
774
|
-
// Figure out start index.
|
|
775
|
-
if(starting_index && Math.abs(starting_index) < targetObjects.length) {
|
|
776
|
-
if(starting_index < 0) {
|
|
777
|
-
start = targetObjects.length + starting_index;
|
|
778
|
-
}
|
|
779
|
-
else {
|
|
780
|
-
start = starting_index;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
var len = targetObjects.length;
|
|
784
|
-
for(var i = start; i < len; i++) {
|
|
785
|
-
if(targetObjects[i][key] === matchingValue) {
|
|
786
|
-
var match = targetObjects[i];
|
|
787
|
-
if (all)
|
|
788
|
-
ret.push(match);
|
|
789
|
-
else {
|
|
790
|
-
ret = match;
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return ret;
|
|
798
|
-
},
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Recursively find the first occurrence of an item, depth first, that matches the
|
|
802
|
-
* given field value for the given field
|
|
803
|
-
* @param items an array of LForms items, where an item may have its own sub-items.
|
|
804
|
-
* @param key
|
|
805
|
-
* @param matchingValue
|
|
806
|
-
* @return {*}
|
|
807
|
-
*/
|
|
808
|
-
findItem: function(items, key, matchingValue) {
|
|
809
|
-
var ret = null;
|
|
810
|
-
if(items) {
|
|
811
|
-
for(var i=0; !ret && i < items.length; ++i) {
|
|
812
|
-
var item = items[i];
|
|
813
|
-
if(item[key] === matchingValue) {
|
|
814
|
-
ret = item;
|
|
815
|
-
}
|
|
816
|
-
else if(Array.isArray(item.items)) {
|
|
817
|
-
ret = this.findItem(item.items, key, matchingValue);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
return ret;
|
|
823
|
-
},
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Remove key/values from an object based on a regular expression of key.
|
|
827
|
-
*
|
|
828
|
-
* @param obj {object} - Object to prune
|
|
829
|
-
* @param keyRegex {regex} - A regular expression to match the keys for deletion
|
|
830
|
-
* @param recursiveKey {optional|string} - Key of the recursive field. The value
|
|
831
|
-
* of this should be an object or an Array of objects.
|
|
832
|
-
* @private
|
|
833
|
-
*/
|
|
834
|
-
_pruneObject: function (keyRegex, obj, recursiveKey) {
|
|
835
|
-
if(typeof obj === 'object') {
|
|
836
|
-
for(var k in obj) {
|
|
837
|
-
if(k.match(keyRegex)) {
|
|
838
|
-
delete obj[k];
|
|
839
|
-
}
|
|
840
|
-
else if(recursiveKey && k === recursiveKey) {
|
|
841
|
-
var val = obj[k];
|
|
842
|
-
if(Array.isArray(val)) {
|
|
843
|
-
var len = val.length;
|
|
844
|
-
for(var i = 0; i < len; i++) {
|
|
845
|
-
this._pruneObject(keyRegex, val[i], recursiveKey);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
else {
|
|
849
|
-
this._pruneObject(keyRegex, val, recursiveKey);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
},
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Utility to walkthrough recurively through each element in a collection
|
|
859
|
-
*
|
|
860
|
-
* @param collectionObj
|
|
861
|
-
*/
|
|
862
|
-
pruneNulls: function (collectionObj) {
|
|
863
|
-
if (Array.isArray(collectionObj)) {
|
|
864
|
-
for(var i = collectionObj.length - 1; i >= 0; i--) {
|
|
865
|
-
if(collectionObj[i] === null || collectionObj[i] === undefined ) {
|
|
866
|
-
collectionObj.splice(i, 1);
|
|
867
|
-
}
|
|
868
|
-
else if(typeof collectionObj[i] === 'object') {
|
|
869
|
-
LForms.Util.pruneNulls(collectionObj[i]);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
else if (collectionObj && typeof collectionObj === 'object') {
|
|
874
|
-
var keys = Object.keys(collectionObj);
|
|
875
|
-
keys.forEach(function (key) {
|
|
876
|
-
if(collectionObj[key] === null || collectionObj[key] === undefined) {
|
|
877
|
-
delete collectionObj[key];
|
|
878
|
-
}
|
|
879
|
-
else if (typeof collectionObj[key] === 'object') {
|
|
880
|
-
LForms.Util.pruneNulls(collectionObj[key]);
|
|
881
|
-
}
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
},
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* We are transitioning lforms fields representing code (form.code, items[x].questionCode
|
|
889
|
-
* and items[x].questionCodeSystem) to FHIR definition of Coding type.
|
|
890
|
-
* In lforms, these fields are string type and FHIR Coding is an array of
|
|
891
|
-
* objects encapsulating multiple codes
|
|
892
|
-
* .
|
|
893
|
-
* To preserve compatibility with existing lforms code, we preserve
|
|
894
|
-
* both lforms code and FHIR Coding. FHIR Coding is preserved in codeList.
|
|
895
|
-
*
|
|
896
|
-
* This function adopts the following rules.
|
|
897
|
-
*
|
|
898
|
-
* . If codeList is not present create it making the first item representing lforms code.
|
|
899
|
-
* . If lforms code is not present, create it as appropriate (form.code or item[x].questionCode) from
|
|
900
|
-
* first item in codeList.
|
|
901
|
-
* . Always make sure the first item in codeList represents lforms code.
|
|
902
|
-
*
|
|
903
|
-
* @param formOrItem - lforms form or items[x]
|
|
904
|
-
*/
|
|
905
|
-
initializeCodes: function (formOrItem) {
|
|
906
|
-
|
|
907
|
-
var isItem = (formOrItem.question || formOrItem.questionCode);
|
|
908
|
-
var code = isItem ? formOrItem.questionCode : formOrItem.code;
|
|
909
|
-
var codeSystem = isItem ? formOrItem.questionCodeSystem : formOrItem.codeSystem;
|
|
910
|
-
var display = isItem ? formOrItem.question : formOrItem.name;
|
|
911
|
-
var codeSystemUrl = LForms.Util.getCodeSystem(codeSystem);
|
|
912
|
-
|
|
913
|
-
// if there is code
|
|
914
|
-
if(code) {
|
|
915
|
-
if(!formOrItem.codeList) {
|
|
916
|
-
formOrItem.codeList = [];
|
|
917
|
-
}
|
|
918
|
-
var codeList = formOrItem.codeList;
|
|
919
|
-
var found = false;
|
|
920
|
-
for(var i = 0; i < codeList.length; i++) {
|
|
921
|
-
if(code === codeList[i].code && (!codeSystemUrl && !codeList[i].system || codeSystemUrl === codeList[i].system)) {
|
|
922
|
-
found = true;
|
|
923
|
-
break;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
// if form data is converted from a FHIR Questionnaire that has no 'code' on items,
|
|
928
|
-
// don't create a 'code' when converting it back to Questionnaire.
|
|
929
|
-
if(!found && codeSystemUrl !== 'LinkId') {
|
|
930
|
-
var code = {
|
|
931
|
-
code: code,
|
|
932
|
-
display: display
|
|
933
|
-
};
|
|
934
|
-
if (codeSystemUrl) {
|
|
935
|
-
code.system = codeSystemUrl;
|
|
936
|
-
}
|
|
937
|
-
codeList.unshift(code);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
// if there is a codeList
|
|
941
|
-
else {
|
|
942
|
-
if(formOrItem.codeList && formOrItem.codeList.length > 0) {
|
|
943
|
-
if(isItem) {
|
|
944
|
-
// questionCode is required, so this shouldn't happen??
|
|
945
|
-
formOrItem.questionCode = formOrItem.codeList[0].code;
|
|
946
|
-
formOrItem.questionCodeSystem = formOrItem.codeList[0].system;
|
|
947
|
-
}
|
|
948
|
-
else {
|
|
949
|
-
formOrItem.code = formOrItem.codeList[0].code;
|
|
950
|
-
formOrItem.codeSystem = formOrItem.codeList[0].system;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
return formOrItem;
|
|
956
|
-
},
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
/**
|
|
960
|
-
* Creates a Reference to the given FHIR resource, to be used an a subject in
|
|
961
|
-
* another resource.
|
|
962
|
-
* @param fhirRes the FHIR resource for which to create a Reference.
|
|
963
|
-
* @return a FHIR Reference, with a local reference to fhirRes.
|
|
964
|
-
*/
|
|
965
|
-
createLocalFHIRReference: function(fhirRes) {
|
|
966
|
-
var ref = {"reference": fhirRes.resourceType+"/" + fhirRes.id};
|
|
967
|
-
if (fhirRes.resourceType === "Patient") {
|
|
968
|
-
if (fhirRes.name && fhirRes.name.length > 0) {
|
|
969
|
-
var name = fhirRes.name[0];
|
|
970
|
-
if (name.text)
|
|
971
|
-
ref.display = name.text;
|
|
972
|
-
else {
|
|
973
|
-
if (name.given && name.given.length > 0)
|
|
974
|
-
ref.display = name.given[0];
|
|
975
|
-
if (name.family) {
|
|
976
|
-
if (ref.display)
|
|
977
|
-
ref.display = ref.display+' '+name.family;
|
|
978
|
-
else
|
|
979
|
-
ref.display = name.family;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// Not sure what to put for display for something other than patient, but it
|
|
986
|
-
// is optional, so for now I will just leave it blank.
|
|
987
|
-
return ref;
|
|
988
|
-
},
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* Get a code system based on the code system value used in LForms
|
|
993
|
-
* @param codeSystemInLForms code system value used in LForms
|
|
994
|
-
* @private
|
|
995
|
-
*/
|
|
996
|
-
getCodeSystem: function(codeSystemInLForms) {
|
|
997
|
-
|
|
998
|
-
var codeSystem;
|
|
999
|
-
switch (codeSystemInLForms) {
|
|
1000
|
-
case "LOINC":
|
|
1001
|
-
codeSystem = "http://loinc.org";
|
|
1002
|
-
break;
|
|
1003
|
-
default:
|
|
1004
|
-
codeSystem = codeSystemInLForms;
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
return codeSystem;
|
|
1009
|
-
},
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* Removes an object(s) from an array searching it using key/value pair with an optional start index.
|
|
1014
|
-
* The matching value should be a primitive type. If start index is not specified,
|
|
1015
|
-
* it is assumed to be 0.
|
|
1016
|
-
*
|
|
1017
|
-
* @param targetObjects - Array of objects to search using key and value
|
|
1018
|
-
* @param key - key of the object to match the value
|
|
1019
|
-
* @param matchingValue - Matching value of the specified key.
|
|
1020
|
-
* @param starting_index - Optional start index to lookup. Negative number indicates index from end.
|
|
1021
|
-
* The absolute value should be less than the length of items in the array. If not
|
|
1022
|
-
* the starting index is assumed to be 0.
|
|
1023
|
-
* @param all - if false, removes the first matched object otherwise removes all matched objects.
|
|
1024
|
-
* Default is false.
|
|
1025
|
-
*
|
|
1026
|
-
* @returns {Object|Array} - Returns removed object or array of objects.
|
|
1027
|
-
*/
|
|
1028
|
-
removeObjectsFromArray: function(targetObjects, key, matchingValue, starting_index, all) {
|
|
1029
|
-
var ind = all ? [] : null;
|
|
1030
|
-
var ret = all ? [] : null;
|
|
1031
|
-
if(Array.isArray(targetObjects)) {
|
|
1032
|
-
var start = 0;
|
|
1033
|
-
// Figure out start index.
|
|
1034
|
-
if(starting_index && Math.abs(starting_index) < targetObjects.length) {
|
|
1035
|
-
if(starting_index < 0) {
|
|
1036
|
-
start = targetObjects.length + starting_index;
|
|
1037
|
-
}
|
|
1038
|
-
else {
|
|
1039
|
-
start = starting_index;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
var len = targetObjects.length;
|
|
1043
|
-
|
|
1044
|
-
for(var i = start; i < len; i++) {
|
|
1045
|
-
if(targetObjects[i][key] === matchingValue) {
|
|
1046
|
-
var match = targetObjects[i];
|
|
1047
|
-
if (all) {
|
|
1048
|
-
ind.push(i);
|
|
1049
|
-
ret.push(match);
|
|
1050
|
-
}
|
|
1051
|
-
else {
|
|
1052
|
-
ind = i;
|
|
1053
|
-
ret = match;
|
|
1054
|
-
break;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
if(Array.isArray(ind)) {
|
|
1059
|
-
for(var i = ind.length - 1; i >= 0; i--) {
|
|
1060
|
-
targetObjects.splice(ind[i], 1);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
else {
|
|
1064
|
-
if(ind !== null) {
|
|
1065
|
-
targetObjects.splice(ind, 1);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
return ret;
|
|
1071
|
-
},
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
/**
|
|
1075
|
-
* Returns the part of an LForms form definition that all form definitions
|
|
1076
|
-
* should have.
|
|
1077
|
-
*/
|
|
1078
|
-
baseFormDef: function() {
|
|
1079
|
-
return {lformsVersion: LForms.lformsVersion};
|
|
1080
|
-
},
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
/**
|
|
1084
|
-
* Get a list of warning messages about answer lists, which should have been
|
|
1085
|
-
* loaded from the URL in answerValueSet but were not.
|
|
1086
|
-
*
|
|
1087
|
-
* @param {*} formDataSource Optional. Either the containing HTML element that
|
|
1088
|
-
* includes the LForm's rendered form, a CSS selector for that element, an
|
|
1089
|
-
* LFormsData object, or an LForms form definition (parsed). If not
|
|
1090
|
-
* provided, the first form found in the page will be used. */
|
|
1091
|
-
getAnswersResourceStatus : function(formDataSource) {
|
|
1092
|
-
if (!formDataSource || formDataSource instanceof HTMLElement || typeof formDataSource === 'string')
|
|
1093
|
-
formDataSource = this._getFormObjectInScope(formDataSource);
|
|
1094
|
-
|
|
1095
|
-
return formDataSource.checkAnswersResourceStatus();
|
|
1096
|
-
},
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* A wrapper function to deep copy an object so that FHIR lib does not use "angular" directly
|
|
1101
|
-
* @param {*} object
|
|
1102
|
-
*/
|
|
1103
|
-
deepCopy: function(object) {
|
|
1104
|
-
return angular.copy(object);
|
|
1105
|
-
},
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
/**
|
|
1109
|
-
* Shows a warning message, typically about some problem with the form
|
|
1110
|
-
* definition.
|
|
1111
|
-
* @param msg the message to show
|
|
1112
|
-
* @param item (optional) the item in the form to which the message applies.
|
|
1113
|
-
*/
|
|
1114
|
-
showWarning: function(msg, item) {
|
|
1115
|
-
if (item)
|
|
1116
|
-
msg = 'The question "'+item.text+'" produced the following warning: '+msg;
|
|
1117
|
-
console.log(msg);
|
|
1118
|
-
// TBD: add a warning visible on the page.
|
|
1119
|
-
}
|
|
1120
|
-
};
|
|
1121
|
-
|
|
1122
|
-
|