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,424 +0,0 @@
1
- (function() {
2
- 'use strict';
3
- var LForms = require('../lforms');
4
- // var angular = require('angular');
5
- angular.module('lformsWidget')
6
- .factory('RecursionHelper', ['$compile', function($compile){
7
- return {
8
- /**
9
- * From: http://stackoverflow.com/questions/14430655/recursion-in-angular-directives
10
- * Manually compiles the element, fixing the recursion loop.
11
- * @param element
12
- * @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
13
- * @returns An object containing the linking functions.
14
- */
15
- compile: function(element, link){
16
- // Normalize the link parameter
17
- if(angular.isFunction(link)){
18
- link = { post: link };
19
- }
20
- // Break the recursion loop by removing the contents
21
- var contents = element.contents().remove();
22
- var compiledContents;
23
- return {
24
- pre: (link && link.pre) ? link.pre : null,
25
- /**
26
- * Compiles and re-adds the contents
27
- */
28
- post: function(scope, element){
29
- // Compile the contents
30
- if(!compiledContents){
31
- compiledContents = $compile(contents);
32
- }
33
- // Re-add the compiled contents to the element
34
- compiledContents(scope, function(clone){
35
- element.append(clone);
36
- });
37
- // Call the post-linking function, if any
38
- if(link && link.post){
39
- link.post.apply(null, arguments);
40
- }
41
- }
42
- };
43
- }
44
- };
45
- }])
46
- // each item, use inherited scope
47
- .directive('lfItem', function() {
48
- return {
49
- restrict: 'E',
50
- transclude: true,
51
- templateUrl: 'item.html'
52
- };
53
- })
54
- // each item in table template, use inherited scope
55
- .directive('lfTableItem', ["RecursionHelper", function (RecursionHelper) {
56
- return {
57
- restrict: "E",
58
- templateUrl: "table-item.html",
59
- compile: function (element) {
60
- // Use the compile function from the RecursionHelper,
61
- // And return the linking function(s) which it returns
62
- return RecursionHelper.compile(element);
63
- }
64
- }
65
- }])
66
- // date field
67
- .constant('lfDateConfig', {})
68
- .directive('lfDate', ['lfDateConfig', '$timeout', function (lfDateConfig, $timeout) {
69
- 'use strict';
70
- var options;
71
- options = {};
72
- angular.extend(options, lfDateConfig);
73
- return {
74
- require:'?ngModel',
75
- link:function (scope, element, attrs, controller) {
76
- var getOptions = function () {
77
- return angular.extend({}, lfDateConfig, scope.$eval(attrs.lfDate));
78
- };
79
- var initDateWidget = function () {
80
- var showing = false;
81
- var opts = getOptions();
82
-
83
- // If we have a controller (i.e. ngModelController) then wire it up
84
- if (controller) {
85
-
86
- // Set the view value in a $apply block when users selects
87
- // (calling directive user's function too if provided)
88
- var _onSelect = opts.onSelect || angular.noop;
89
- opts.onSelect = function (value, picker) {
90
- showing = true;
91
- controller.$setViewValue(element.datepicker("getDate"));
92
- _onSelect(value, picker);
93
- element.change();
94
- };
95
- opts.beforeShow = function() {
96
- element.click(); // without this, the UIB datetime picker does not close (if it were open)
97
- showing = true;
98
- };
99
- opts.onClose = function(value, picker) {
100
- showing = false;
101
- };
102
- element.on('change', function() {
103
- var valid_date = LForms.Util.stringToDate(this.value);
104
- if(valid_date) {
105
- controller.$setViewValue(valid_date);
106
- element.datepicker("setDate", valid_date);
107
- if ( !showing ) {
108
- scope.$apply(function() {
109
- element.datepicker("setDate", element.datepicker("getDate"));
110
- controller.$setViewValue(element.datepicker("getDate"));
111
- });
112
- }
113
- }
114
- });
115
-
116
- // Update the date picker when the model changes
117
- controller.$render = function () {
118
- var date = controller.$viewValue;
119
- if ( angular.isDefined(date) && date !== null && !angular.isDate(date) && typeof(date) !== "string" ) {
120
- throw new Error('ng-Model value must be a Date object or a string - currently it is a ' + typeof date);
121
- }
122
- // convert saved user data into date
123
- else if (typeof(date) === "string") {
124
- date = LForms.Util.stringToDate(date, true);
125
- }
126
- element.datepicker("setDate", date);
127
- };
128
- }
129
- // If we don't destroy the old one it doesn't update properly when the config changes
130
- element.datepicker('destroy');
131
- // Create the new datepicker widget
132
- element.datepicker(opts);
133
- if ( controller ) {
134
- // Force a render to override whatever is in the input text box
135
- controller.$render();
136
- }
137
- };
138
- // Run initDateWiget once
139
- scope.$watch({}, initDateWidget, true);
140
-
141
- }
142
- };
143
- }
144
- ])
145
- // answers field, (CNE and CWE) (search field?), inherited scope
146
- .directive('lfAnswers', function() {
147
- return {
148
- restrict: 'E',
149
- transclude: true,
150
- templateUrl: 'field-answers.html'
151
- };
152
- })
153
- // units field, isolated scope?
154
- .directive('lfUnits', function() {
155
- return {
156
- restrict: 'E',
157
- scope: {
158
- item: '='},
159
- transclude: true,
160
- templateUrl: 'field-units.html'
161
- };
162
- })
163
- // horizontal layout, inherited scope
164
- .directive('lfSectionHorizontal', function() {
165
- return {
166
- restrict: 'E',
167
- transclude: true,
168
- templateUrl: 'layout-horizontal.html'
169
- };
170
- })
171
- // matrix layout, inherited scope
172
- .directive('lfSectionMatrix', function() {
173
- return {
174
- restrict: 'E',
175
- transclude: true,
176
- templateUrl: 'layout-matrix.html'
177
- };
178
- })
179
- .directive('lfRepeatingButton', function() {
180
- return {
181
- restrict: 'E',
182
- transclude: true,
183
- templateUrl: 'repeating-button.html'
184
- };
185
- })
186
- .directive('lfFormControls', function() {
187
- return {
188
- restrict: 'E',
189
- transclude: true,
190
- templateUrl: 'form-controls.html'
191
- };
192
- })
193
- .directive('lfFormTitle', function() {
194
- return {
195
- restrict: 'E',
196
- transclude: true,
197
- templateUrl: 'form-title.html'
198
- };
199
- })
200
- .directive('lfFormOptions', function() {
201
- return {
202
- restrict: 'E',
203
- transclude: true,
204
- templateUrl: 'form-options.html'
205
- };
206
- })
207
- .directive('lfItemOptions', function() {
208
- return {
209
- restrict: 'E',
210
- transclude: true,
211
- templateUrl: 'item-options.html'
212
- };
213
- })
214
- .directive('lfFormHeader', function() {
215
- return {
216
- restrict: 'E',
217
- transclude: true,
218
- templateUrl: 'form-header.html'
219
- };
220
- })
221
- .directive('lfAttachment', ['$timeout', function ($timeout) {
222
- // Thanks to https://github.com/mistralworks/ng-file-model/blob/master/ng-file-model.js
223
- // and https://embed.plnkr.co/plunk/7BBYAa
224
- return {
225
- restrict: 'E',
226
- templateUrl: 'attachment.html',
227
- scope: {
228
- item: "=",
229
- labelledBy: "=",
230
- },
231
- controller: ['$scope', function ($scope) {
232
-
233
- /**
234
- * Creates an attachment for an item based on the data entered by the
235
- * user.
236
- */
237
- $scope.removeAttachment = function(item) {
238
- delete item.value;
239
- delete item._attachmentName;
240
- delete item._attachmentURL;
241
- delete item._fileInfo;
242
- delete item._useURL;
243
- },
244
-
245
-
246
- /**
247
- * Creates an attachment for an item based on the data entered by the
248
- * user.
249
- */
250
- $scope.createAttachment = function(item) {
251
- if (!item._fileInfo && !item._attachmentURL) {
252
- alert("An attachment must have either a file or a URL (or both).");
253
- }
254
- else {
255
- item.value = {title: item._attachmentName || item._fileInfo?.name};
256
- var value = item.value;
257
- if (item._attachmentURL)
258
- value.url = item._attachmentURL;
259
- if (item._fileInfo) { // attach the data too
260
- var fileInfo = item._fileInfo
261
- value.contentType = fileInfo.type
262
- if (fileInfo.lastModified)
263
- value.creation = new Date(fileInfo.lastModified).toISOString();
264
- else if (fileInfo.lastModifiedDate) // IE 11
265
- value.creation = fileInfo.lastModifiedDate.toISOString();
266
- item.value._progress = 0.001; // 0.1% of loading; non-zero to trigger display
267
- var reader = new FileReader();
268
- reader.onload = function (loadEvent) {
269
- var data = loadEvent.target.result;
270
- var commaIndex = data.indexOf(',');
271
- if (data.indexOf('data:')!=0 || commaIndex<0) {
272
- throw new Error('data URL did not start with expected prefix, but with '+
273
- data.slice(0, 30));
274
- alert('Unable to attach the file data.');
275
- }
276
- $scope.$apply(function () {
277
- delete value._progress;
278
- value.data = data.slice(commaIndex+1);
279
- });
280
- };
281
- reader.onprogress = function(progressEvent) {
282
- $scope.$apply(function() {
283
- item._progress =
284
- progressEvent.loaded/progressEvent.total;
285
- });
286
- };
287
- reader.readAsDataURL(fileInfo);
288
- }
289
- }
290
- },
291
-
292
-
293
- /**
294
- * Downloads the item's Attachment.
295
- * @param attachment the FHIR Attachment.
296
- * @param event the click event object
297
- * @return a "data:" URL (base 64)
298
- */
299
- $scope.downloadAttachment = function(attachment, event) {
300
- //var a = document.createElement('a');
301
- if (attachment.data) {
302
- var a = event.target; //document.createElement('a');
303
- var originalHref = a.href
304
- a.href= 'data:'+(attachment.contentType ? attachment.contentType : '') +';base64,'+attachment.data;
305
- a.download = attachment.title;
306
- $timeout(function() {
307
- a.href = originalHref;
308
- });
309
- }
310
- };
311
- }],
312
- link: function (scope, element, attributes) {
313
- element.bind("change", function (changeEvent) {
314
- if (changeEvent.target.files) {
315
- var newFile = changeEvent.target.files[0];
316
- var item = scope.item;
317
- if (!newFile.type) {
318
- // Per the FHIR specification, we can't proceed without a mime
319
- // type.
320
- alert('Unknown file type. Please ensure the file has an '+
321
- 'appropriate extension');
322
- newFile = null; // don't proceed
323
- }
324
- else if (item.allowedAttachmentTypes &&
325
- item.allowedAttachmentTypes.indexOf(newFile.type)<0) {
326
- let types = item.allowedAttachmentTypes;
327
- alert('The file '+newFile.name+' is not one of the mime types '+
328
- 'permitted by this questionnaire ('+types.slice(0,-1).join(', ')+
329
- ' and ' +types.slice(-1)+'). Please make sure your file has '+
330
- 'an appropriate file extension for its type in its filename.');
331
- newFile = null; // don't proceed
332
- }
333
- else if (newFile.size > item.maxAttachmentSize) {
334
- var msg = 'The file '+newFile.name+' exceeds the maximum '+
335
- 'attachment size of '+item.maxAttachmentSize+' bytes permitted by '+
336
- 'this questionnaire. If you can specify the file with a URL, '+
337
- 'use the button to open the URL field and enter that instead.';
338
- alert(msg);
339
- newFile = null; // don't proceed
340
- }
341
- else if (newFile.size > 500000000) {
342
- var msg = 'Adding a large file as an attachment might cause your '+
343
- 'computer to run low on memory. There is a button to enter a '+
344
- 'URL instead of attaching the file data. Are you sure you want '+
345
- 'to attach the file data?';
346
- if (!confirm(msg))
347
- newFile = null; // don't proceed
348
- }
349
- if (!newFile)
350
- element[0].getElementsByTagName('input')[0].value = null; // clear the field
351
- item._fileInfo = newFile;
352
- if (newFile && !item._useURL) {
353
- scope.$apply(function () {
354
- scope.createAttachment(item); // see & binding above
355
- });
356
- }
357
- // else Wait for the "create attachment" click
358
- }
359
- });
360
- }
361
- }
362
- }])
363
- .directive('lfValidate', function () {
364
- return {
365
- require: 'ngModel',
366
- restrict: 'A',
367
- scope: {
368
- itemData: '=lfValidate'
369
- },
370
- //transclude: true,
371
- template: '<div ng-repeat="error in itemData._validationErrors">' +
372
- '<div class="validation-error">"{{itemData.question}}" {{error}}</div>' +
373
- '</div>',
374
-
375
- link: function(scope, elem, attr, ctrl) {
376
-
377
- /**
378
- * Validate model data on an item
379
- * @param item an item in the form
380
- * @param value the user input data
381
- * @param ctrl the directive control
382
- */
383
- function validate(item, value, ctrl) {
384
- item._validationErrors = [];
385
- var valid1 = LForms.Validations.checkDataType(item.dataType,
386
- value, item._validationErrors);
387
- ctrl.$setValidity('lf-datatype', valid1);
388
- var valid2 = LForms.Validations.checkRestrictions(item.restrictions,
389
- value, item._validationErrors);
390
- ctrl.$setValidity('lf-restrictions', valid2);
391
- var valid3 = LForms.Validations.checkRequired(item._answerRequired,
392
- value, item._validationErrors);
393
- ctrl.$setValidity('lf-required', valid3);
394
-
395
- for (var i=0, len=item._validationErrors.length; i<len; ++i)
396
- scope.$parent.sendMsgToScreenReader('"'+item._text+'"'+item._validationErrors[i]);
397
-
398
- return valid1 && valid2 && valid3
399
- };
400
-
401
- //For DOM -> model validation
402
- ctrl.$parsers.unshift(function(value) {
403
- if (value !==undefined) {
404
- scope.itemData._validationErrors = [];
405
- var valid = validate(scope.itemData, value, ctrl);
406
- return valid? value : undefined;
407
- }
408
- });
409
-
410
- //For model -> DOM validation
411
- // In the current use, lf-validate is applied to a separate non-input element, the validation always
412
- // happens when the model data changes, i.e. in this function.
413
- ctrl.$formatters.unshift(function(value) {
414
- if (value !==undefined) {
415
- scope.itemData._validationErrors = [];
416
- validate(scope.itemData, value, ctrl);
417
- return value;
418
- }
419
- });
420
- }
421
- };
422
- })
423
- ;
424
- })();
@@ -1,20 +0,0 @@
1
- // A list of files from the lforms package itself to be combined with webpack.
2
- // For the full list for the bower or npm package, see bower-index.js or
3
- // index.js.
4
- var LForms = require('../lforms.js');
5
- require("./lforms-constants.js");
6
- require("./lforms-config.js");
7
- require("./lforms-controllers.js");
8
- require("./lforms-directives.js");
9
- require("./bootstrap-decorators.js");
10
- require("./lib/cross_browser.js");
11
- require("./lib/lforms-util.js");
12
- require("./lib/polyfill.js");
13
- require("./lib/lforms-hl7.js");
14
- require("./lib/lforms-validate.js");
15
- require("./lib/lforms-data.js");
16
- LForms.Util.FHIRSupport = require("./fhir/versions.js");
17
- require("../lforms.tpl.js");
18
- LForms._elementResizeDetectorMaker = require("element-resize-detector");
19
-
20
- module.exports = LForms;
@@ -1,21 +0,0 @@
1
- /**
2
- * On IE 9, the window.console is undefined unless dev tools are open. This can cause the program
3
- * crashing when making function calls like console.log etc. The following code is used to prevent
4
- * the crashing. For details, see the web link listed belw:
5
- * http://stackoverflow.com/questions/3326650/console-is-undefined-error-for-internet-explorer.
6
- **/
7
- (function() {
8
- if (!window.console) {
9
- window.console = {};
10
- // union of Chrome, FF, IE, and Safari console methods
11
- var m = [ "log", "info", "warn", "error", "debug", "trace", "dir", "group",
12
- "groupCollapsed", "groupEnd", "time", "timeEnd", "profile", "profileEnd",
13
- "dirxml", "assert", "count", "markTimeline", "timeStamp", "clear"];
14
- // define undefined methods as noops to prevent errors
15
- for (var i = 0; i < m.length; i++) {
16
- if (!window.console[m[i]])
17
- window.console[m[i]] = function () {
18
- };
19
- }
20
- }
21
- })();