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,1196 +0,0 @@
1
- var LForms = require('../lforms');
2
-
3
- angular.module('lformsWidget')
4
- .controller('LFormsCtrl',
5
- ['$window','$scope', '$element', '$timeout', '$interval', '$sce', 'smoothScroll', 'LF_CONSTANTS', 'lformsConfig',
6
- function ($window, $scope, $element, $timeout, $interval, $sce, smoothScroll, LF_CONSTANTS, lformsConfig) {
7
- 'use strict';
8
-
9
- $scope.debug = false;
10
-
11
- $scope.hasUnused = false;
12
- $scope.repeatingSectionStatus = {};
13
-
14
- $scope.validationInitialShowTime = LF_CONSTANTS.VALIDATION_MESSAGE_INITIAL_SHOW_TIME;
15
-
16
- // Provide blank image to satisfy img tag. Bower packaging forces us to
17
- // avoid using image files from html templates, therefore using base64
18
- // encoding for a 1x1 blank gif file.
19
- $scope.blankGifDataUrl = LF_CONSTANTS.BLANK_GIF_DATAURL;
20
-
21
- // Default option for calendar - for the JQuery datepicker
22
- $scope.dateOptions = {
23
- changeYear: true,
24
- changeMonth: true,
25
- yearRange: '1800:-0',
26
- showOn: 'button',
27
- constrainInput: false,
28
- showOtherMonths: true,
29
- selectOtherMonths: true,
30
- showMonthAfterYear: true,
31
- buttonText: "Show date picker"
32
- };
33
-
34
- // uib booststrap datetime picker, https://github.com/Gillardo/bootstrap-ui-datetime-picker
35
- $scope.uibDateTimePickerFormat = "MM/dd/yyyy HH:mm";
36
- $scope.uibDatePickerAltFormats = [
37
- 'yyyy',
38
- 'M/yyyy', 'MM/yyyy', 'yyyy/M', 'yyyy/MM',
39
- 'M/d/yyyy', 'MM/d/yyyy', 'M/dd/yyyy', 'MM/dd/yyyy',
40
- "M/d/yyyy HH:mm", "MM/d/yyyy HH:mm", "M/dd/yyyy HH:mm",
41
- "yyyy-MM", "yyyy-MM-dd", "yyyy-MM-dd HH:mm"
42
- ];
43
-
44
- $scope.uibDatePickerOptions = {
45
- showWeeks: false
46
- };
47
-
48
- $scope.uibTimePickerOptions = {
49
- arrowkeys: true,
50
- //showSeconds: false
51
- };
52
-
53
- // open the uib bootstrap datetime picker
54
- $scope.openUibDtmPicker = function(e) {
55
- this.isOpen = true;
56
- }
57
-
58
- $scope.uibDtmPickerButtonBar = {
59
- // specify the class for the close button so that we can locate the element.
60
- // must specify each button but each button's conf fall back to system default if not specified.
61
- show: true,
62
- now: {},
63
- today: {},
64
- clear: {},
65
- date: {},
66
- time: {},
67
- close: {
68
- cls: 'btn-sm btn-default lf-dtmpk-close'
69
- },
70
- cancel: {}
71
- };
72
-
73
- // Close the uib bootstrap datetime picker if the input field loses focus -
74
- // if the input loses focus due to clicking on the calendar icon, it will open fine.
75
- // not intended to use on other fields but having the eleName param make it useful for troubleshooting
76
- $scope.uibDtmPickerOnBlur = function(eleName) {
77
- if(eleName === 'input' && this.isOpen) {
78
- setTimeout(function(){
79
- document.getElementsByClassName('lf-dtmpk-close')[0].click()
80
- });
81
- }
82
- };
83
-
84
- /**
85
- * Check the current view's width
86
- */
87
- $scope.checkViewWidth = function() {
88
- // the $element is where the controller is set on
89
- var width = $element.width(); //$window.innerWidth;
90
- $scope._viewMode = "";
91
- $scope._inputFieldWidth = "";
92
-
93
- // small screen
94
- if (width <= 400) //480
95
- $scope._viewMode = "sm";
96
- // medium screen
97
- else if (width <= 600) //800
98
- $scope._viewMode = "md";
99
- // large screen
100
- else {
101
- $scope._viewMode = "lg";
102
- }
103
- $scope._inputFieldWidth = {'width': width / 2};
104
- };
105
-
106
-
107
- // initialize an element resize detector
108
- var erd = LForms._elementResizeDetectorMaker({
109
- strategy: "scroll" //<- For ultra performance.
110
- });
111
-
112
-
113
- // check the width when the containing div changes its size
114
- erd.listenTo($element, function(element) {
115
- $scope.$apply($scope.checkViewWidth());
116
- });
117
-
118
-
119
- // initial values
120
- $scope.checkViewWidth();
121
-
122
-
123
- /**
124
- * get the CSS class the form's view mode
125
- * @returns {string}
126
- */
127
- $scope.getViewModeClass = function() {
128
- var viewModeClass = "";
129
- if ($scope.lfData) {
130
- var viewMode = $scope.lfData.templateOptions.viewMode;
131
- // responsive to the screen/container's size
132
- if (! viewMode || viewMode === "auto") {
133
- viewMode = $scope._viewMode;
134
- }
135
- switch (viewMode) {
136
- // fixed to be the large layout
137
- case "lg":
138
- viewModeClass = "lf-view-lg";
139
- break;
140
- // fixed to be the large layout
141
- case "md":
142
- viewModeClass = "lf-view-md";
143
- break;
144
- // fixed to be the large layout
145
- case "sm":
146
- viewModeClass = "lf-view-sm";
147
- break;
148
- }
149
- }
150
-
151
- return viewModeClass;
152
- };
153
-
154
-
155
- /**
156
- * get the CSS class for an item's view mode
157
- * @param item a form item
158
- * @returns {string}
159
- */
160
- $scope.getItemViewModeClass = function(item) {
161
- var viewMode;
162
- var viewModeClass = "";
163
- if ($scope.lfData) {
164
- // if viewMode is specified on the item
165
- if (item.displayControl && item.displayControl.viewMode) {
166
- viewMode = item.displayControl.viewMode;
167
- }
168
- // otherwise use the default viewMode of the form
169
- else {
170
- viewMode = $scope.lfData.templateOptions.viewMode;
171
- }
172
-
173
- // responsive to the screen/container's size
174
- if (!viewMode || viewMode === "auto") {
175
- viewMode = $scope._viewMode;
176
- }
177
-
178
- switch (viewMode) {
179
- // fixed to be the large layout
180
- case "lg":
181
- viewModeClass = "lf-item-view-lg";
182
- break;
183
- // fixed to be the large layout
184
- case "md":
185
- viewModeClass = "lf-item-view-md";
186
- break;
187
- // fixed to be the large layout
188
- case "sm":
189
- viewModeClass = "lf-item-view-sm";
190
- break;
191
- default:
192
- viewModeClass = "lf-item-view-lg";
193
- }
194
- }
195
-
196
- return viewModeClass;
197
- };
198
-
199
-
200
- /**
201
- * Set the active row in table
202
- * @param index index of an item in the lforms form items array
203
- */
204
- $scope.setActiveRow = function(item) {
205
- $scope.lfData.setActiveRow(item);
206
- };
207
-
208
-
209
- /**
210
- * Get the inline width for the input and unit part of a form item
211
- * @item a form item
212
- * @returns {*}
213
- */
214
- $scope.getFieldWidth = function(item) {
215
- return $scope.getItemViewModeClass(item) === 'lf-item-view-lg' ? $scope._inputFieldWidth : null;
216
- };
217
-
218
-
219
- /**
220
- * Set the time that a validation message shows for the first time.
221
- * @param ms time in ms
222
- */
223
- $scope.setValidationInitialShowTime = function(ms) {
224
- $scope.validationInitialShowTime = ms;
225
- };
226
-
227
-
228
- /**
229
- * Set up a timer to make validation messages disappear in 2 seconds when the input field loses focus
230
- * @param item the item which onBlur event happens on its input field
231
- */
232
- $scope.activeRowOnBlur = function(item) {
233
-
234
- // the first visit to the field (and leaving the field), show validation messages for a certain time
235
- if (!item._visitedBefore) {
236
- item._showValidation = true;
237
-
238
- // use $interval instead of $timeout so that protractor will not wait on $timeout
239
- var intervalCanceller = $interval(function() {
240
- // not to show validation messages after 2 seconds
241
- item._showValidation = false;
242
- item._visitedBefore = true;
243
- $interval.cancel(intervalCanceller);
244
- }, $scope.validationInitialShowTime);
245
- }
246
- // the following visits (and leaving the field), not to show validation messages
247
- else {
248
- item._showValidation = false;
249
- }
250
- };
251
-
252
-
253
- /**
254
- * Get the css class for the active row
255
- * @param item an item
256
- * @returns {string}
257
- */
258
- $scope.getActiveRowClass = function(item) {
259
- return $scope.lfData.getActiveRowClass(item);
260
- };
261
-
262
- /**
263
- * Reset the lfData
264
- */
265
- $scope.setFormData = function(formData) {
266
- $scope.lfData = formData;
267
- };
268
-
269
- /**
270
- * Check if the form is finished
271
- * @returns {boolean|*|*}
272
- */
273
- $scope.isFormDone = function() {
274
- return $scope.lfData.isFormDone();
275
- };
276
-
277
- /**
278
- * Check if there's a unit list
279
- * @param item an item in the lforms form items array
280
- * @returns {boolean}
281
- */
282
- $scope.checkUnits = function(item) {
283
- return !!(item._unitAutocompOptions ||
284
- (item._unitReadonly && item.unit && item.unit._displayUnit));
285
- };
286
-
287
- /**
288
- * Get an item's skip logic status
289
- * @param item an item
290
- * @returns {*|string}
291
- */
292
- $scope.getSkipLogicClass = function(item) {
293
- return $scope.lfData.getSkipLogicClass(item);
294
- };
295
-
296
-
297
- /**
298
- * Get the CSS styles on an item in formHeaderItems
299
- * @param col an item in formHeaderItems
300
- * @returns {{}} CSS style object
301
- */
302
- $scope.getHeaderItemStyle = function(col) {
303
- var ret = {};
304
- if (col.displayControl &&
305
- angular.isArray(col.displayControl.colCSS)) {
306
- // only when the view mode is lg, i.e. the items are listed like table columns
307
- var viewModeClass = $scope.getViewModeClass();
308
- if (viewModeClass === 'lf-view-lg') {
309
- var colCSS = col.displayControl.colCSS;
310
- for (var i = 0, iLen = colCSS.length; i < iLen; i++) {
311
- var css = colCSS[i];
312
- ret[css.name] = css.value;
313
- }
314
- }
315
- }
316
- return ret;
317
- };
318
-
319
-
320
- /**
321
- * Get the CSS styles on a table column
322
- * @param col a column in a HTML table
323
- * @returns {{}} CSS style object
324
- */
325
- $scope.getTableColumnStyle = function(col) {
326
- var ret = {};
327
- if (col.displayControl && angular.isArray(col.displayControl.colCSS)) {
328
- var colCSS = col.displayControl.colCSS;
329
- for (var i= 0, iLen= colCSS.length; i<iLen; i++) {
330
- var css = colCSS[i];
331
- ret[ css.name ] = css.value;
332
- }
333
- }
334
- return ret;
335
- };
336
-
337
-
338
- /**
339
- * Get the CSS styles on an item itself
340
- * @param item an item in a form
341
- * @returns {{}} CSS style object
342
- */
343
- $scope.getItemStyle = function(item) {
344
- var ret = {};
345
- if (item.displayControl && angular.isArray(item.displayControl.css)) {
346
- for (var i= 0, iLen= item.displayControl.css.length; i<iLen; i++) {
347
- var css = item.displayControl.css[i];
348
- ret[ css.name ] = css.value;
349
- }
350
- }
351
- return ret;
352
- };
353
-
354
-
355
- /**
356
- * Check an item's skip logic status to decide if the item should be shown
357
- * @param item an item
358
- * @returns {boolean}
359
- */
360
- $scope.targetShown = function(item) {
361
- return item._enableWhenExpVal !== false &&
362
- $scope.lfData.getSkipLogicClass(item) !== 'target-disabled';
363
- };
364
-
365
-
366
- /**
367
- * Hide/show the form option panel
368
- */
369
- $scope.hideShowFormOptionPanel = function() {
370
- $scope.lfData.templateOptions.showFormOptionPanel = !$scope.lfData.templateOptions.showFormOptionPanel;
371
- };
372
-
373
-
374
- /**
375
- * Check if the button for item option panel should be shown
376
- * @param item a form item
377
- */
378
- $scope.isItemOptionPanelButtonShown = function(item) {
379
- var buttonShown = $scope.lfData.templateOptions.showItemOptionPanelButton &&
380
- (item.dataType === "SECTION" || item.answers && (item.dataType === "CWE" || item.dataType === "CNE" ))
381
- if (!buttonShown)
382
- item._showItemOptionPanel = false;
383
-
384
- return buttonShown;
385
- };
386
-
387
-
388
- /**
389
- * Hide/show the item's option panel
390
- * @param item a form item
391
- */
392
- $scope.hideShowItemOptionPanel = function(item) {
393
- if ($scope.isItemOptionPanelButtonShown(item)) {
394
- item._showItemOptionPanel = !item._showItemOptionPanel;
395
- }
396
- };
397
-
398
- /**
399
- * Check if a particular layout is allowed for a section item
400
- * @param item a form item
401
- * @param layout a type of layout. permissible values are 'horizontal' and 'matrix'
402
- */
403
- $scope.isQuestionLayoutAllowed = function(item, layout) {
404
-
405
- var allowed = false;
406
- if (layout === 'matrix' || layout === 'horizontal') {
407
- allowed = true;
408
- //for both horizontal and matrix
409
- //the item has children but no grand children
410
- if (item.dataType === "SECTION" && item.items && item.items.length>0) {
411
- var firstItemAnswers = item.items[0].answers;
412
- var firstItemDataType = item.items[0].dataType;
413
- var firstItemAnswerCardinality = item.items[0].answerCardinality;
414
- for (var i = 0, iLen = item.items.length; i<iLen; i++) {
415
- var subItem = item.items[i];
416
- if (subItem.dataType === "SECTION" || subItem.dataType === "TITLE" || subItem.items && subItem.items.length > 0) {
417
- allowed = false;
418
- break;
419
- }
420
- // addition requirement for matrix layout: all answers are same
421
- if (layout === "matrix") {
422
- if (subItem.dataType !== "CWE" && subItem.dataType !== "CNE") {
423
- allowed = false;
424
- break;
425
- }
426
- else if (i>0 && firstItemDataType !== item.items[i].dataType ||
427
- !angular.equals(firstItemAnswerCardinality, subItem.answerCardinality) ||
428
- !angular.equals(firstItemAnswers, subItem.answers)) {
429
- allowed = false;
430
- break;
431
- }
432
- }
433
- }
434
- }
435
- else {
436
- allowed = false;
437
- }
438
- }
439
-
440
- return allowed;
441
- };
442
-
443
-
444
- /**
445
- * Check the display type of the coding instructions
446
- * @param item an item in the lforms form items array
447
- * @returns {string}
448
- */
449
- $scope.getCodingInstructionsDisplayType = function(item) {
450
- var ret ='';
451
- if (item.codingInstructions && item.codingInstructions.length > 0) {
452
- var position = $scope.lfData.templateOptions.showCodingInstruction ? "inline" : "popover";
453
- if ($scope.lfData.templateOptions.allowHTMLInInstructions && item.codingInstructionsFormat === "html") {
454
- var format = "html";
455
- }
456
- else {
457
- format = "escaped";
458
- }
459
- ret = position + "-" + format;
460
- }
461
- return ret;
462
- };
463
-
464
-
465
- /**
466
- * Get coding instructions with assumed safe HTML content
467
- * @param item an item in the lforms form items array
468
- * @returns {string}
469
- */
470
- $scope.getTrustedCodingInstructions = function(item) {
471
- return item.codingInstructions ? $sce.trustAsHtml(item.codingInstructions) : ''
472
- };
473
-
474
- /**
475
- * Get the sequence number for the current repeating item
476
- * @param item an item in the lforms form items array
477
- * @returns {string}
478
- */
479
- $scope.getRepeatingSN = function(item) {
480
- var ret = '';
481
- if (item._questionRepeatable) {
482
- var sn = item._idPath.slice(1);
483
- ret = sn.replace(/\//g,'.');
484
- }
485
- return ret;
486
- };
487
-
488
-
489
- /**
490
- * Watch on value and unit changes of controlling/source items for skip logic
491
- * formulas and data controls.
492
- */
493
- $scope.$watch(
494
- function () {
495
- var watchedSourceItems = null;
496
- if ($scope.lfData && $scope.lfData.itemList) {
497
- watchedSourceItems = [];
498
- for (var i= 0, iLen=$scope.lfData.itemList.length; i<iLen; i++) {
499
- var item = $scope.lfData.itemList[i];
500
- if (item._formulaTargets || item._dataControlTargets || item._skipLogicTargets) {
501
- watchedSourceItems.push({value: item.value, unit: item.unit,
502
- id: item._elementId, enableWhenExp: item._enableWhenExpVal});
503
- }
504
- }
505
- }
506
- return watchedSourceItems;
507
- },
508
- function(newValues, oldValues) {
509
- var lastUpdated = [];
510
- if (newValues) {
511
- // no oldValues, initial loading
512
- if (!oldValues) {
513
- for (var i = 0, iLen = newValues.length; i < iLen; i++) {
514
- lastUpdated.push(newValues[i].id);
515
- }
516
- }
517
- // adding a new repeating item/section
518
- else if (oldValues.length < newValues.length) {
519
- //// find out which one is added, solution 1
520
- //for (var m= 0, mLen=newValues.length; m<mLen; m++) {
521
- // var newVal = newValues[m];
522
- // var isNew = true;
523
- // for (var n= 0, nLen=oldValues.length; n<nLen; n++) {
524
- // var oldVal = oldValues[n];
525
- // if (newVal.id === oldVal.id) {
526
- // isNew = false;
527
- // break;
528
- // }
529
- // }
530
- // if (isNew) {
531
- // lastUpdated.push(newVal.id)
532
- // }
533
- //}
534
- // find out which one is added, solution 2
535
- // it is always the last one in current design
536
- lastUpdated.push(newValues[newValues.length-1].id);
537
- }
538
- // removing a repeating item/section
539
- else if (oldValues.length > newValues.length) {
540
- // rules run at the remove event, TBD
541
- }
542
- // data changes only
543
- else {
544
- for (var i = 0, iLen = newValues.length; i < iLen; i++) {
545
- if (!angular.equals(newValues[i], oldValues[i])) {
546
- lastUpdated.push(newValues[i].id);
547
- }
548
- }
549
- }
550
- // do something
551
- for (var j = 0, jLen = lastUpdated.length; j < jLen; j++) {
552
- var item = $scope.lfData.itemHash[lastUpdated[j]];
553
- $scope.updateOnSourceItemChange(item);
554
- }
555
- }
556
- },
557
- true
558
- );
559
-
560
-
561
- /**
562
- * Watch on form changes (shallow watch on the form object)
563
- * Disable animation and validations before a form is loaded,
564
- * then re-enable animation and validations when the form is loaded
565
- */
566
- $scope.$watch("lfData", function() { // or watch on function() {return $scope.lfData;}
567
- var lfData = $scope.lfData;
568
-
569
- // disable animation
570
- lformsConfig.disableAnimate();
571
- // re-enable animation after the form is loaded
572
- if (lfData && lfData.templateOptions && lfData.templateOptions.useAnimation) {
573
- $timeout(function () {
574
- // the templateOptions might be changed again after the $timeout was set
575
- if (lfData && lfData.templateOptions && lfData.templateOptions.useAnimation) {
576
- lformsConfig.enableAnimate();
577
- }
578
- }, 1);
579
- }
580
-
581
- // Only when lfData changes as a whole do we check whether the new
582
- // form has FHIRPath or not. This is to avoid the expensive (for
583
- // large forms) check on the whole of the form data for the need to
584
- // run FHIRPath.
585
- if (LForms.FHIR) {
586
- if (lfData) { // sometimes set to null to clear the page
587
- if (lfData._hasResponsiveExpr || lfData._hasInitialExpr) {
588
- // Watch for changes that require FHIRPath to run
589
- if (lfData._hasResponsiveExpr) {
590
- if ($scope.unwatchFHIRPath)
591
- $scope.unwatchFHIRPath();
592
- $scope.unwatchFHIRPath = $scope.$watch(function() {
593
- return JSON.stringify(lfData, function(key, val) {
594
- // In Safari, "key" is a number (not a string) for arrays
595
- key = ""+key; // a little faster than checking the type
596
- // Ignore changes to internal variables and $$hashKey
597
- return (key.indexOf('_') === 0 || key.indexOf('$$')===0) ? undefined : val;
598
- });
599
- }, function() {
600
- if (lfData) {
601
- lfData._expressionProcessor.runCalculations(false).then(()=>{
602
- // We might or might not be in a digest cycle.
603
- $scope.$evalAsync(); // will trigger a digest cycle
604
- }); // pick up asynchronous model changes
605
- }
606
- });
607
- }
608
- }
609
- if (!lfData._controllerInit && (lfData._hasResponsiveExpr || lfData._hasInitialExpr)) {
610
- lfData._expressionProcessor.runCalculations(true).then(() => {
611
- // We might or might not be in a digest cycle.
612
- $scope.$evalAsync(); // will trigger a digest cycle
613
- });
614
- }
615
- // Angular calls this twice for the same lfData. Set a flag.
616
- // Note: For some reason the watches still need to be set up both times.
617
- lfData._controllerInit = true;
618
- }
619
- else {
620
- if ($scope.unwatchFHIRPath) {
621
- $scope.unwatchFHIRPath(); // stop watching because it is no longer needed
622
- $scope.unwatchFHIRPath = null;
623
- }
624
- if ($scope.unwatchAsync) {
625
- $scope.unwatchAsync();
626
- $scope.unwatchAsync = null;
627
- }
628
- }
629
- }
630
- });
631
-
632
-
633
- /**
634
- * Check skip logic, formulas and data controls when the source item changes.
635
- * @param item the controlling/source item
636
- */
637
- $scope.updateOnSourceItemChange = function(item) {
638
- var widgetData = $scope.lfData;
639
- if (widgetData) {
640
- widgetData.updateOnSourceItemChange(item);
641
- $scope.sendActionsToScreenReader();
642
- }
643
- };
644
-
645
-
646
- /**
647
- * Adjust the height of a textarea
648
- * @param e a textarea DOM element or a ID of a textarea element
649
- */
650
- $scope.autoExpand = function(e) {
651
- var element = typeof e === 'object' ? e.target : document.getElementById(e);
652
- var scrollHeight = element.scrollHeight +2;
653
- element.style.height = scrollHeight + "px";
654
- };
655
-
656
-
657
- /**
658
- * Get total number of questions on the form, not including section headers or titles
659
- * @returns {number}
660
- */
661
- $scope.getNumberOfQuestions = function() {
662
- var ret = 0;
663
- var widgetData = $scope.lfData;
664
- if (widgetData && widgetData.itemList) {
665
- for (var i = 0, iLen = widgetData.itemList.length; i < iLen; i++) {
666
- if (!widgetData.itemList[i].header)
667
- ret++;
668
- }
669
- }
670
- return ret;
671
- };
672
-
673
-
674
- /**
675
- * Get CSS classes for the sibling status (whether it is the first or the last sibling)
676
- * @param item a form item
677
- * @returns {string}
678
- */
679
- $scope.getSiblingStatus = function(item) {
680
- var status = "";
681
- if (item._lastSibling)
682
- status += 'lf-last-item';
683
- if (item._firstSibling)
684
- status += ' lf-first-item'
685
- return status;
686
- };
687
-
688
-
689
- /**
690
- * Get the indentation style of the form
691
- * @returns {string}
692
- */
693
- $scope.getIndentationStyle = function () {
694
- return $scope.lfData.templateOptions.useTreeLineStyle ? "lf-indentation-tree" : "lf-indentation-bar";
695
- };
696
-
697
-
698
- /**
699
- * Get the CSS class on each item row
700
- * @param item an item in the lforms form items array
701
- * @returns {string}
702
- */
703
- $scope.getRowClass = function(item) {
704
- var eleClass = 'level' + item._displayLevel;
705
- eleClass += ' lf-datatype-' + item.dataType;
706
- if (item._answerRequired) {
707
- eleClass += ' lf-answer-required';
708
- }
709
- if (item.header) {
710
- eleClass += ' lf-section-header';
711
- }
712
- else {
713
- eleClass += ' lf-question';
714
- }
715
- if (!item.question || item.question.length === 0) {
716
- eleClass += ' lf-empty-question';
717
- }
718
- if (item._visitedBefore) {
719
- eleClass += ' lf-visited-before';
720
- }
721
- if (item._showValidation) {
722
- eleClass += ' lf-show-validation';
723
- }
724
- if (item._isHiddenFromView) {
725
- eleClass += ' lf-hidden-from-view';
726
- }
727
- if (item.dataType === 'TITLE') {
728
- eleClass += ' lf-title-row';
729
- }
730
-
731
- return eleClass;
732
- };
733
-
734
-
735
- /**
736
- * Add a repeating item or a repeating group
737
- * @param item an item in the lforms form items array
738
- * @param append an optional flag indicate if the new item is added to the end of the repeating group
739
- */
740
- $scope.addOneRepeatingItem = function(item, append) {
741
- var widgetData = $scope.lfData;
742
- var anyEmpty = false;
743
- if ($scope.lfData && !$scope.lfData.templateOptions.allowMultipleEmptyRepeatingItems) {
744
- anyEmpty = widgetData.areAnyRepeatingItemsEmpty(item);
745
- if (anyEmpty && item._showUnusedItemWarning) {
746
- if (!item._unusedItemWarning)
747
- item._unusedItemWarning = 'Please enter info in the blank "' +
748
- item._text+'"';
749
- $scope.sendMsgToScreenReader(item._unusedItemWarning);
750
- }
751
- }
752
- if (!anyEmpty) {
753
- var newItem = append ? widgetData.appendRepeatingItems(item) : widgetData.addRepeatingItems(item);
754
- $scope.sendActionsToScreenReader();
755
-
756
- // broadcast the event
757
- $scope.$emit(LF_CONSTANTS.EVENT_REPEATING_ITEM_ADDED,
758
- {
759
- "event": LF_CONSTANTS.EVENT_REPEATING_ITEM_ADDED,
760
- "formId": $scope.lfData.code,
761
- "itemId": newItem._elementId,
762
- "time": new Date()
763
- });
764
-
765
- setTimeout(function() {
766
- var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
767
-
768
- var headerItem = jQuery("label[for='" + newItem._elementId + "']")[0];
769
- var btnDel = document.getElementById("del-" + newItem._elementId);
770
- // vertical table, find the header item
771
- if (headerItem) {
772
- var anchorItem = headerItem;
773
- }
774
- // horizontal table, find the '-' button
775
- else if (btnDel) {
776
- var anchorItem = btnDel;
777
- }
778
-
779
- if (anchorItem) {
780
- var anchorPosition = anchorItem.getBoundingClientRect();
781
- // scroll down to show about 2 rows of the newly added section
782
- // if the new header item is close enough to the bottom so that the first 2 questions are not visible
783
- if (anchorPosition && anchorPosition.bottom > viewportHeight - 70) {
784
- smoothScroll(anchorItem, {
785
- duration: 500,
786
- easing: 'easeInQuad',
787
- offset: viewportHeight - 105
788
- });
789
- }
790
- // move the focus to the '-' button of the newly added item/section
791
- // a table from the '-' button moves the focus to the next input field
792
- if (btnDel)
793
- btnDel.focus();
794
- }
795
- }, 1);
796
-
797
- }
798
- };
799
-
800
-
801
- /**
802
- * Unset the flag to hide the warning about unused repeating items
803
- * @param item a repeating item
804
- */
805
- $scope.hideUnusedItemWarning = function(item) {
806
- if ($scope.lfData && !$scope.lfData.templateOptions.allowMultipleEmptyRepeatingItems) {
807
- item._showUnusedItemWarning = false;
808
- }
809
- };
810
-
811
-
812
- /**
813
- * Writes a single message to the reader_log element on the page
814
- * so that screen readers can read it.
815
- * @param msg the message to be read
816
- */
817
- $scope.sendMsgToScreenReader = function(msg) {
818
- LForms.Def.Autocompleter.screenReaderLog(msg);
819
- };
820
-
821
-
822
- /**
823
- * Write action logs from the lforms to reader_log element on the page
824
- * so that screen readers can read.
825
- */
826
- $scope.sendActionsToScreenReader = function() {
827
- var widgetData = $scope.lfData;
828
- if (widgetData._actionLogs.length > 0) {
829
- widgetData._actionLogs.forEach(function(log) {
830
- LForms.Def.Autocompleter.screenReaderLog(log);
831
- });
832
- // clean up logs
833
- widgetData._actionLogs = [];
834
- }
835
- };
836
-
837
-
838
- /**
839
- * Remove one repeating item in a group
840
- * @param item an item in the lforms form items array
841
- */
842
- $scope.removeOneRepeatingItem = function(item) {
843
-
844
- var widgetData = $scope.lfData;
845
- var nextItem = widgetData.getNextRepeatingItem(item);
846
-
847
- var btnId = '';
848
- // move the focus to the next '-' button if there's one displayed
849
- // ('-' buttons are shown only when there are two repeating items shown).
850
- if (nextItem) {
851
- if (widgetData.getRepeatingItemCount(item) === 2) {
852
- btnId = 'add-' + nextItem._elementId;
853
- }
854
- else {
855
- btnId = 'del-' + nextItem._elementId;
856
- }
857
- }
858
- // otherwise move the focus to the add button of the previous item
859
- else {
860
- var prevItem = widgetData.getPrevRepeatingItem(item);
861
- if (prevItem) {
862
- btnId = 'add-' + prevItem._elementId;
863
- }
864
- }
865
-
866
- // remove the items
867
- $scope.lfData.removeRepeatingItems(item);
868
-
869
- $scope.sendActionsToScreenReader();
870
-
871
- // broadcast the event
872
- $scope.$emit(LF_CONSTANTS.EVENT_REPEATING_ITEM_DELETED,
873
- {
874
- "event": LF_CONSTANTS.EVENT_REPEATING_ITEM_DELETED,
875
- "formId": $scope.lfData.code,
876
- "itemId": item._elementId,
877
- "time": new Date()
878
- });
879
-
880
- // set the focus
881
- setTimeout(function() {
882
- var btn = document.getElementById(btnId);
883
- if (btn) btn.focus();
884
- }, 1);
885
- };
886
-
887
-
888
- /**
889
- * Check if there's only one repeating item in a group
890
- * (so that the 'remove' button won't show on this item)
891
- * @param item an item in the lforms form items array
892
- * @returns {boolean}
893
- */
894
- $scope.hasOneRepeatingItem =function(item) {
895
- var recCount = $scope.lfData.getRepeatingItemCount(item);
896
- return recCount > 1 ? false : true;
897
- };
898
-
899
-
900
- /**
901
- * Check if the current horizontal table has one row only
902
- * @param item an item in the lforms form items array
903
- * @returns {boolean}
904
- */
905
- $scope.hasOneRepeatingRow = function(item) {
906
- var ret = false;
907
- var tableInfo = $scope.lfData._horizontalTableInfo[item._codePath + item._parentIdPath_];
908
- if (tableInfo && tableInfo.tableRows && tableInfo.tableRows.length === 1) {
909
- ret = true;
910
- }
911
- return ret;
912
- };
913
-
914
-
915
- /**
916
- * Check if the question needs an extra input
917
- * @param item an item in the lforms form items array
918
- * @returns {*}
919
- */
920
- $scope.needExtra = function(item) {
921
- var widgetData = $scope.lfData;
922
- var extra = widgetData.needExtra(item);
923
- return extra;
924
- };
925
-
926
-
927
- /**
928
- * Get user input data from the form, with or without form definition data.
929
- * @param noFormDefData optional, to not include form definition data, the default is false.
930
- * @param noEmptyValue optional, to remove items that have an empty value, the default is false.
931
- * @param noDisabledItem optional, to remove items that are disabled by skip logic, the default is false.
932
- * @returns {{itemsData: (*|Array), templateData: (*|Array)}} form data and template data
933
- */
934
- $scope.getUserData = function(noFormDefData, noEmptyValue, noDisabledItem) {
935
- var formData = $scope.lfData.getUserData(noFormDefData, noEmptyValue, noDisabledItem);
936
- return formData;
937
- };
938
-
939
-
940
- /**
941
- * Get the complete form definition data, including the user input data from the form.
942
- * The returned data could be fed into a LForms widget directly to render the form.
943
- * @return {{}} form definition JSON object
944
- */
945
- $scope.getFormData = function() {
946
- var formData = $scope.lfData.getFormData();
947
- return formData;
948
- };
949
-
950
-
951
- // for debug only. to be removed.
952
- $scope.onclick = function() {
953
- debugger
954
- var ele = $element;
955
- var i = 1;
956
- };
957
-
958
-
959
- /**
960
- * Get the display layout for each answer in a RADIO_CHECKBOX layout of an item's answers
961
- * @param item a form item
962
- * @returns {string}
963
- */
964
- $scope.getAnswerLayoutColumnClass = function(item) {
965
- var ret = "";
966
- if (item && item.displayControl && item.displayControl.answerLayout &&
967
- item.displayControl.answerLayout.type === "RADIO_CHECKBOX") {
968
- var colNum = parseInt(item.displayControl.answerLayout.columns);
969
- if (isNaN(colNum) || colNum >6 || colNum <0 )
970
- colNum = 0;
971
- ret = "lf-" + colNum + "-col";
972
- }
973
-
974
- return ret;
975
- };
976
-
977
-
978
- /**
979
- * Updates the value for an item whose answers are displayed as a list of checkboxes,
980
- * one of which has just been selected or deselected
981
- * @param item a form item that has an answer list and supports multiple selections
982
- * @param answer an answer object in the answer list
983
- */
984
- $scope.updateCheckboxList = function(item, answer) {
985
- if (item.value && angular.isArray(item.value)) {
986
- var index, selected = false;
987
- for(var i= 0,iLen=item.value.length; i<iLen; i++) {
988
- if (angular.equals(item.value[i],answer)) {
989
- selected = true;
990
- index = i;
991
- break;
992
- }
993
- }
994
- // if answer is currently selected
995
- if (selected) {
996
- // remove the answer from the selected list
997
- item.value.splice(index, 1);
998
- }
999
- // if answer is not currently selected
1000
- else {
1001
- // add the answer to the selected list
1002
- item.value.push(answer);
1003
- }
1004
- }
1005
- // the value is not accessed before
1006
- else {
1007
- // add the answer to the selected list
1008
- item.value = [answer];
1009
- }
1010
- };
1011
-
1012
-
1013
- /**
1014
- * Updates the value for an item with the user typed data.
1015
- * The item's answers are displayed as a list of checkboxes, and users have an option to type their own answer.
1016
- * Update the item.value based on selection of extra data input by users
1017
- * Note: In the checkbox display layout only one "OTHER" value is allowed, while in autocomplete multiple
1018
- * "OTHER" values are allowed.
1019
- * @param item a form item that has an answer list and supports multiple selections and user typed data.
1020
- * @param otherValue the user typed string value for the "OTHER" checkbox
1021
- */
1022
- $scope.updateCheckboxListForOther = function(item, otherValue) {
1023
- var other = {"text": otherValue, "_notOnList": true};
1024
-
1025
- // add/update the other value
1026
- if (item._otherValueChecked) {
1027
- // the list is not empty
1028
- if (item.value && angular.isArray(item.value)) {
1029
- var found = false;
1030
- for(var i= 0,iLen=item.value.length; i<iLen; i++) {
1031
- if (item.value[i]._notOnList) {
1032
- item.value[i] = other;
1033
- found = true;
1034
- break;
1035
- }
1036
- }
1037
- // if the other value is not already in the list
1038
- if (!found) {
1039
- // add the other value to the list
1040
- item.value.push(other);
1041
- }
1042
- }
1043
- // the list is empty
1044
- else {
1045
- // add the other value to the list
1046
- item.value = [other];
1047
- }
1048
- }
1049
- // remove other value
1050
- else {
1051
- if (item.value && angular.isArray(item.value)) {
1052
- var index, found = false;
1053
- for(var i= 0,iLen=item.value.length; i<iLen; i++) {
1054
- if (item.value[i]._notOnList) {
1055
- found = true;
1056
- index = i;
1057
- break;
1058
- }
1059
- }
1060
- if (found) {
1061
- item.value.splice(index, 1);
1062
- }
1063
- }
1064
- }
1065
- };
1066
-
1067
-
1068
- /**
1069
- *
1070
- * Update the item.value based on selection of an answer by users
1071
- * @param item a form item that has an answer list and support single selections
1072
- */
1073
- $scope.updateRadioList = function(item) {
1074
- item._otherValueChecked = false;
1075
- };
1076
-
1077
-
1078
- /**
1079
- * Update the item.value based on selection of extra data input by users
1080
- * @param item a form item that has an answer list and support single selections
1081
- * @param otherValue the user typed string value for the "OTHER" radio button
1082
- */
1083
- $scope.updateRadioListForOther = function(item, otherValue) {
1084
-
1085
- // add/update the other value
1086
- if (item._otherValueChecked) {
1087
- item.value = { "text": otherValue, "_notOnList": true};
1088
- }
1089
- };
1090
-
1091
-
1092
- /**
1093
- * Check if a checkbox should be checked based on the value of a form item
1094
- * @param item a form item
1095
- * @param answer an answer in the items' answer list
1096
- * @returns {boolean}
1097
- */
1098
- $scope.checkAnswer = function(item,answer) {
1099
- var checked = false;
1100
- if (item && item.value) {
1101
- if (angular.isArray(item.value)) {
1102
- for(var i=0, iLen=item.value.length; i<iLen; i++) {
1103
- var selectedAnswer = item.value[i];
1104
- if (selectedAnswer.text === answer.text) {
1105
- checked = true;
1106
- break;
1107
- }
1108
- }
1109
- }
1110
- else {
1111
- if (item.value.text === answer.text) {
1112
- checked = true;
1113
- }
1114
- }
1115
- }
1116
- return checked;
1117
- };
1118
-
1119
-
1120
- /**
1121
- * Handle navigation keys using TAB/ SHIFT+TAB keys
1122
- * @param event keypress event
1123
- */
1124
- $scope.handleNavigationKeyEventByTab = function(event) {
1125
-
1126
- var widgetData = $scope.lfData;
1127
- if (widgetData.templateOptions.tabOnInputFieldsOnly && event.keyCode === widgetData.Navigation.TAB) {
1128
- if (event.shiftKey) {
1129
- var simArrowCode = widgetData.Navigation.ARROW.LEFT;
1130
- }
1131
- else {
1132
- var simArrowCode = widgetData.Navigation.ARROW.RIGHT;
1133
- }
1134
-
1135
- var nextId = event.target['id'], nextElement;
1136
- // find the next element, bypass the invisible elements
1137
- do {
1138
- // get the DOM element id of next field
1139
- nextId = widgetData.Navigation.getNextFieldId(simArrowCode, nextId);
1140
- // get the next DOM element by ID
1141
- nextElement = document.getElementById(nextId);
1142
- } while (nextId && (!nextElement || !jQuery(nextElement).is(":visible")));
1143
-
1144
- // set the focus
1145
- var currentElement = event.target;
1146
- if (nextElement && nextElement.id !== currentElement.id) {
1147
- event.preventDefault();
1148
- event.stopPropagation();
1149
- setTimeout(function() {
1150
- nextElement.focus();
1151
- nextElement.select();
1152
- }, 1);
1153
- currentElement.blur();
1154
- }
1155
- }
1156
- };
1157
-
1158
-
1159
- /**
1160
- * Handle navigation keys using CTRL+arrow keys
1161
- * @param event keypress event
1162
- */
1163
- $scope.handleNavigationKeyEventByArrowKeys = function(event) {
1164
-
1165
- var widgetData = $scope.lfData;
1166
- // supported arrow keys
1167
- var arrow = widgetData.Navigation.ARROW;
1168
-
1169
- // only when control key is also pressed
1170
- if (event.ctrlKey &&
1171
- jQuery.inArray(event.keyCode, [arrow.LEFT, arrow.UP, arrow.RIGHT, arrow.DOWN]) >= 0 ) {
1172
-
1173
- var nextId = event.target['id'], nextElement;
1174
- // find the next element, bypass the invisible elements
1175
- do {
1176
- // get the DOM element id of next field
1177
- nextId = widgetData.Navigation.getNextFieldId(event.keyCode, nextId);
1178
- // get the next DOM element by ID
1179
- nextElement = document.getElementById(nextId);
1180
- } while (nextId && (!nextElement || !jQuery(nextElement).is(":visible")));
1181
-
1182
- // set the focus
1183
- var currentElement = event.target;
1184
- if (nextElement && nextElement.id !== currentElement.id) {
1185
- event.preventDefault();
1186
- event.stopPropagation();
1187
- setTimeout(function() {
1188
- nextElement.focus();
1189
- }, 1);
1190
- currentElement.blur();
1191
- }
1192
- }
1193
- };
1194
-
1195
-
1196
- }]);