@vgip/meta-ui 2.1.2 → 2.1.3

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 (111) hide show
  1. package/.eslintrc.json +57 -0
  2. package/karma.conf.js +35 -0
  3. package/ng-package.json +10 -0
  4. package/package.json +3 -15
  5. package/src/lib/common/fieldNormalizer/boolean.ts +11 -0
  6. package/src/lib/common/fieldNormalizer/datetime.ts +8 -0
  7. package/src/lib/common/fieldNormalizer/index.ts +171 -0
  8. package/src/lib/common/fieldNormalizer/number.ts +13 -0
  9. package/src/lib/common/fieldNormalizer/options.ts +48 -0
  10. package/src/lib/common/fieldNormalizer/radio.ts +29 -0
  11. package/src/lib/common/fieldNormalizer/reference.ts +32 -0
  12. package/src/lib/common/fieldNormalizer/richtext.ts +15 -0
  13. package/src/lib/common/fieldNormalizer/string.ts +23 -0
  14. package/src/lib/common/fieldNormalizer/text.ts +17 -0
  15. package/src/lib/common/fieldNormalizer/uniqueNameFilter.ts +21 -0
  16. package/src/lib/common/metaAutofocus.directive.ts +31 -0
  17. package/src/lib/common/metaContext.resolver.ts +25 -0
  18. package/src/lib/common/metaIcons.pipe.spec.ts +15 -0
  19. package/src/lib/common/metaIcons.pipe.ts +29 -0
  20. package/src/lib/common/metaModel.pipe.ts +19 -0
  21. package/src/lib/common/metaNormalizer.ts +366 -0
  22. package/src/lib/common/metaStripHtml.pipe.ts +18 -0
  23. package/src/lib/common/utils/colorThemes.ts +86 -0
  24. package/src/lib/common/utils/indexedDbStore/index.ts +244 -0
  25. package/src/lib/common/utils/indexedDbStore/indexedDbStore.spec.ts +149 -0
  26. package/src/lib/common/utils/relativeTimeBuilder.ts +49 -0
  27. package/src/lib/common/utils/resourceCardLabel.ts +25 -0
  28. package/src/lib/common/utils/smartProp.spec.ts +24 -0
  29. package/src/lib/common/utils/smartProp.ts +28 -0
  30. package/src/lib/common/utils/templateBuilder.ts +99 -0
  31. package/src/lib/field.scss +207 -0
  32. package/src/lib/fieldAbstract.ts +327 -0
  33. package/src/lib/fieldBoolean/index.ts +55 -0
  34. package/src/lib/fieldBoolean/style.scss +22 -0
  35. package/src/lib/fieldBoolean/test.spec.ts +43 -0
  36. package/src/lib/fieldBoolean/view.html +30 -0
  37. package/src/lib/fieldComposite/index.ts +86 -0
  38. package/src/lib/fieldComposite/style.scss +6 -0
  39. package/src/lib/fieldComposite/test.spec.ts +43 -0
  40. package/src/lib/fieldComposite/view.html +9 -0
  41. package/src/lib/fieldDatetime/index.ts +359 -0
  42. package/src/lib/fieldDatetime/style.scss +81 -0
  43. package/src/lib/fieldDatetime/test.spec.ts +43 -0
  44. package/src/lib/fieldDatetime/view.html +26 -0
  45. package/src/lib/fieldHidden/index.ts +15 -0
  46. package/src/lib/fieldHidden/view.html +0 -0
  47. package/src/lib/fieldInput/index.ts +477 -0
  48. package/src/lib/fieldInput/style.scss +128 -0
  49. package/src/lib/fieldInput/test.spec.ts +43 -0
  50. package/src/lib/fieldInput/view.html +81 -0
  51. package/src/lib/fieldList/index.ts +73 -0
  52. package/src/lib/fieldList/style.scss +26 -0
  53. package/src/lib/fieldList/test.spec.ts +43 -0
  54. package/src/lib/fieldList/view.html +25 -0
  55. package/src/lib/fieldRadio/index.ts +93 -0
  56. package/src/lib/fieldRadio/style.scss +32 -0
  57. package/src/lib/fieldRadio/test.spec.ts +43 -0
  58. package/src/lib/fieldRadio/view.html +24 -0
  59. package/src/lib/fieldReference/index.ts +871 -0
  60. package/src/lib/fieldReference/style.scss +273 -0
  61. package/src/lib/fieldReference/test.spec.ts +44 -0
  62. package/src/lib/fieldReference/view.html +163 -0
  63. package/src/lib/fieldRichtext/index.ts +98 -0
  64. package/src/lib/fieldRichtext/quill.scss +6 -0
  65. package/src/lib/fieldRichtext/style.scss +87 -0
  66. package/src/lib/fieldRichtext/test.spec.ts +43 -0
  67. package/src/lib/fieldRichtext/view.html +17 -0
  68. package/src/lib/fieldSelect/index.ts +597 -0
  69. package/src/lib/fieldSelect/style.scss +165 -0
  70. package/src/lib/fieldSelect/test.spec.ts +44 -0
  71. package/src/lib/fieldSelect/view.html +128 -0
  72. package/src/lib/fieldText/index.ts +86 -0
  73. package/src/lib/fieldText/style.scss +24 -0
  74. package/src/lib/fieldText/test.spec.ts +43 -0
  75. package/src/lib/fieldText/view.html +23 -0
  76. package/src/lib/fieldUnknown/index.ts +15 -0
  77. package/src/lib/fieldUnknown/test.spec.ts +34 -0
  78. package/src/lib/fieldUnknown/view.html +9 -0
  79. package/src/lib/index.ts +127 -0
  80. package/src/lib/layout/index.ts +255 -0
  81. package/src/lib/layout/style.scss +67 -0
  82. package/src/lib/layout/view.html +45 -0
  83. package/src/lib/metaField/index.ts +133 -0
  84. package/src/lib/metaField/test.spec.ts +32 -0
  85. package/src/lib/refDialog/index.ts +157 -0
  86. package/src/lib/refDialog/style.scss +154 -0
  87. package/src/lib/refDialog/view.html +24 -0
  88. package/src/lib/resource/index.ts +559 -0
  89. package/src/lib/resource/style.scss +132 -0
  90. package/src/lib/resource/view.html +70 -0
  91. package/src/lib/resourceCard/index.ts +44 -0
  92. package/src/lib/resourceCard/style.scss +7 -0
  93. package/src/lib/resourceCard/view.html +14 -0
  94. package/src/lib/services/metaContext/index.ts +61 -0
  95. package/src/lib/services/metaMsg/index.ts +84 -0
  96. package/src/lib/services/metaReference/index.ts +98 -0
  97. package/src/lib/services/metaResource/index.ts +163 -0
  98. package/src/lib/services/metaResource/metaHttpClient.ts +76 -0
  99. package/src/lib/services/metaResource/metaResource.spec.ts +24 -0
  100. package/src/lib/services/metaTracker/index.ts +38 -0
  101. package/src/lib/services/resourceDrafts/index.ts +81 -0
  102. package/src/lib/services/resourceDrafts/resourceDrafts.spec.ts +24 -0
  103. package/src/lib/styles.scss +13 -0
  104. package/src/public-api.ts +5 -0
  105. package/src/test.ts +17 -0
  106. package/tsconfig.lib.json +25 -0
  107. package/tsconfig.lib.prod.json +9 -0
  108. package/tsconfig.spec.json +17 -0
  109. package/fesm2022/vgip-meta-ui.mjs +0 -6079
  110. package/fesm2022/vgip-meta-ui.mjs.map +0 -1
  111. package/index.d.ts +0 -709
@@ -0,0 +1,871 @@
1
+ import { Component, OnInit, ViewContainerRef } from '@angular/core';
2
+ import { FieldAbstract } from '../fieldAbstract';
3
+ import { ControlContainer, NgForm } from '@angular/forms';
4
+ import { MetaReferenceService } from '../services/metaReference';
5
+ import { Subject } from 'rxjs';
6
+ import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
7
+ import { MetaResourceService, IMetaResourceConfig } from '../services/metaResource';
8
+ import { MetaRefDialog } from '../refDialog';
9
+ import { MetaMsgService } from '../services/metaMsg';
10
+ import { MetaContextService } from '../services/metaContext';
11
+ import { IMetaTrackerEvent, MetaTrackerService } from '../services/metaTracker';
12
+
13
+ @Component({
14
+ templateUrl: './view.html',
15
+ styleUrls: ['./style.scss'],
16
+ viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
17
+ standalone: false
18
+ })
19
+ export class FieldReference extends FieldAbstract implements OnInit {
20
+ input: any;
21
+ dropdown: HTMLElement;
22
+ dropdownScrollContainer: HTMLElement;
23
+ searching: boolean;
24
+ searchResourceType: any = {};
25
+ searchText: string;
26
+ searchResults: Array<any>;
27
+ searchError: any;
28
+ activeSuggestionIndex: number;
29
+ searchResourceTypesScope: string;
30
+ searchTextChanged: Subject<string> = new Subject<string>();
31
+ isCreatable = false;
32
+ value: any;
33
+ isPolymorphic: boolean;
34
+ isSearchable: boolean;
35
+ multiple: boolean;
36
+ vr: MetaResourceService;
37
+ externalLink: string;
38
+ dropdownVisible = false;
39
+ searchResourceTypeMeta: any = {
40
+ type: 'select',
41
+ name: 'resourceType',
42
+ label: 'Type',
43
+ options: [], // { label: '-- All types --' }],
44
+ standalone: true
45
+ };
46
+ filteredSuggestions: Array<any> = [];
47
+ placeHolderLabel: string;
48
+ asyncSuggestions: Array<any>;
49
+ smartSuggestions: Array<any> = [];
50
+
51
+ constructor(
52
+ private referenceService: MetaReferenceService,
53
+ private metaResource: MetaResourceService,
54
+ private metaMsgService: MetaMsgService,
55
+ private metaContext: MetaContextService,
56
+ private metaTracker: MetaTrackerService,
57
+ private viewContainerRef: ViewContainerRef
58
+ ) {
59
+ super();
60
+ }
61
+
62
+ get disabled() {
63
+ const superDisabled = this.logicalDisabled || this.validations.disabled || this.meta.disabled === true
64
+ || (this.meta.updatable === false && this.isPersistedParent);
65
+ this.searchResourceTypeMeta.disabled = superDisabled;
66
+ return superDisabled;
67
+ }
68
+
69
+ get showTypes() {
70
+ return (typeof (this.meta.showTypes) !== 'undefined') ? this.meta.showTypes : true;
71
+ }
72
+
73
+ get suggestions() {
74
+ return this.smartSuggestions.concat(
75
+ !this.searchResourceType.resourceType ?
76
+ // eslint-disable-next-line max-len
77
+ (this.asyncSuggestions || this.meta.suggestions || []) : (this.asyncSuggestions || this.meta.suggestions || []).filter((s) => s.type === this.searchResourceType.resourceType)
78
+ );
79
+ }
80
+
81
+ get hasAppendButton() {
82
+ if (!this.multiple && this.model) {
83
+ if (this.isPolymorphic) {
84
+ for (const ref of this.metaReference) {
85
+ if ((ref.name || ref.resourceType) === (this.model.type || this.model.resourceType)) {
86
+ return ref.updatable || (ref.creatable && ref.updatable !== false);
87
+ }
88
+ }
89
+ } else {
90
+ return this.metaReference.updatable || (this.metaReference.creatable && this.metaReference.updatable !== false); // VGIS-6911
91
+ }
92
+ } else if (this.metaReference instanceof Array) {
93
+ // setTimeout(() => {
94
+ // this.searchResourceTypesScope = this.metaReference.map(rt => rt.name).join(',');
95
+ // }, 0);
96
+ for (const rt of this.metaReference) {
97
+ if (rt.hidden !== false && rt.creatable) {
98
+ return true;
99
+ }
100
+ }
101
+ } else {
102
+ return this.metaReference.creatable;
103
+ }
104
+ }
105
+
106
+ get resourceService() {
107
+ if (!this.metaResourceService) {
108
+ const metaResourceConfig: IMetaResourceConfig = {
109
+ integrationCode: this.integrationCode,
110
+ resourceType: this.resourceType,
111
+ delegate: this.delegate
112
+ };
113
+ this.metaResourceService = this.metaResource.new(metaResourceConfig);
114
+ }
115
+ return this.metaResourceService;
116
+ }
117
+
118
+ get metaReference() {
119
+ return this.meta.reference || {};
120
+ }
121
+
122
+ ngOnInit() {
123
+ this.multiple = (this.meta.type === 'multireference') || this.meta.multiple;
124
+ this.searchResourceTypeMeta.label = this.meta.label;
125
+ if (!this.meta.valueType) {
126
+ // this.meta.valueType = 'object';
127
+ Object.defineProperty(this.meta, 'valueType', {
128
+ value: 'object'
129
+ });
130
+ }
131
+ if (this.metaReference instanceof Array) {
132
+ this.isPolymorphic = true;
133
+ this.isSearchable = true;
134
+ this.searchResourceTypesScope = this.metaReference.filter(rt => rt.hidden !== true).map(rt => rt.name).join(',');
135
+ for (const rt of this.metaReference) {
136
+ if (rt.creatable) {
137
+ this.isCreatable = true;
138
+ if (typeof (rt.searchable) !== 'undefined' && !rt.searchable) {
139
+ this.isSearchable = false;
140
+ }
141
+ }
142
+ }
143
+ this.metaReference.forEach((r) => {
144
+ if (!r.hidden) {
145
+ this.searchResourceTypeMeta.options.push({ id: r.name, label: r.label || r.name });
146
+ }
147
+ });
148
+ } else {
149
+ this.isSearchable = typeof (this.metaReference.searchable) === 'undefined' || this.metaReference.searchable;
150
+ this.searchResourceType = this.metaReference;
151
+ this.searchResourceTypesScope = this.metaReference.name || this.metaReference.resourceType;
152
+ this.isCreatable = this.metaReference.creatable;
153
+ }
154
+ this.buildPlaceholderLabel();
155
+ this.searchTextChanged.pipe(debounceTime(300), distinctUntilChanged()).subscribe((value: string) => {
156
+ this.searchText = value;
157
+ this.search();
158
+ });
159
+ const origValue = this.parent[this.meta.name];
160
+
161
+ // this.suggestions = this.meta.suggestions; // TODO filter by default type
162
+ // console.log('this.suggestions', this.suggestions);
163
+
164
+ Object.defineProperty(this.parent, this.meta.name, {
165
+ set: (value) => {
166
+ if (value === '') {
167
+ value = null;
168
+ }
169
+ if (this.multiple) {
170
+ if (value && value instanceof Array) {
171
+ for (const v of value) {
172
+ if (!this.model) {
173
+ this.model = [];
174
+ }
175
+ if (typeof (v) === 'string') {
176
+ this.model.push({
177
+ id: v,
178
+ label: v,
179
+ type: this.metaReference.name
180
+ });
181
+ }
182
+ }
183
+ } else {
184
+ value = null; // protect when value from server is not array
185
+ }
186
+ } else {
187
+ if (typeof (value) === 'string') {
188
+ value = {
189
+ id: value,
190
+ label: value, // could be found with extra call
191
+ type: this.metaReference.name
192
+ };
193
+ }
194
+ this.searchText = value ?
195
+ ((!value.label || value.label === ' ') ? `N/A (${value.type}#${value.id})` : value.label || value) :
196
+ '';
197
+ // if (this.isPolymorphic && this.model) { //TODO show or not?
198
+ // this.searchResourceType = this.metaReference.find((rt)=> {
199
+ // return rt.name === this.model.type;
200
+ // });
201
+ // }
202
+ }
203
+ this.model = value;
204
+ if (!this.multiple) {
205
+ this.value = this.modelToValue(value, this.meta.valueType || 'object');
206
+ this.buildExternalLink();
207
+ } else {
208
+ this.value = !this.model ? this.model : this.model.map((m) => this.modelToValue(m, this.meta.valueType || 'object'));
209
+ }
210
+ this.meta.$optional = this.isOptional;
211
+ },
212
+ get: () => this.value,
213
+ enumerable: this.sendToServer,
214
+ configurable: true
215
+ });
216
+ if (origValue) {
217
+ this.parent[this.meta.name] = origValue;
218
+ }
219
+ if (!this.model && this.default) {
220
+ if (this.multiple) {
221
+ this.parent[this.meta.name] = [];
222
+ if (this.default.length) {
223
+ for (const dv of this.default) {
224
+ if ((dv.id || dv.value) && dv.type) {
225
+ const item: any = {
226
+ id: dv.id || dv.value,
227
+ label: dv.label || dv.id || dv.value,
228
+ type: dv.type
229
+ };
230
+ if (dv.externalLink) {
231
+ item.externalLink = dv.externalLink;
232
+ }
233
+ this.parent[this.meta.name].push(item);
234
+ }
235
+ }
236
+ }
237
+ if (!this.parent[this.meta.name].length) {
238
+ this.clear();
239
+ }
240
+ } else {
241
+ if (this.default.id && this.default.label && this.default.type) {
242
+ setTimeout(() => {
243
+ if (!this.model) {
244
+ const item: any = {
245
+ id: this.default.id || this.default.value,
246
+ label: this.default.label || this.default.id || this.default.value,
247
+ type: this.default.type,
248
+ smart: true
249
+ };
250
+ if (this.default.externalLink) {
251
+ item.externalLink = this.default.externalLink;
252
+ }
253
+ this.parent[this.meta.name] = item;
254
+ }
255
+ }, 0);
256
+ } else {
257
+ console.warn('!!!default value is not valid format', this.meta);
258
+ }
259
+ }
260
+ }
261
+
262
+ if (typeof (this.meta.disabled) !== 'undefined') {
263
+ if (typeof (this.meta.disabled) === 'object') {
264
+ for (const p of Object.keys(this.meta.disabled)) {
265
+ const props = p.split('.');
266
+ if (typeof (this.parent[props[0]]) !== 'undefined') {
267
+ let val = this.parent;
268
+ for (const prop of props) {
269
+ val = val[prop];
270
+ if (!val) {
271
+ break;
272
+ }
273
+ }
274
+ if (val === this.meta.disabled[p]) {
275
+ this.logicalDisabled = true;
276
+ this.clear();
277
+ }
278
+ }
279
+ this.parentChangeSubject.subscribe((value) => {
280
+ if (value && value.hasOwnProperty(props[0])) {
281
+ let val = this.parent;
282
+ for (const prop of props) {
283
+ val = val[prop];
284
+ if (!val) {
285
+ break;
286
+ }
287
+ }
288
+ if (val === this.meta.disabled[p]) {
289
+ this.logicalDisabled = true;
290
+ this.clear();
291
+ } else {
292
+ delete this.logicalDisabled;
293
+ }
294
+ }
295
+ });
296
+ }
297
+ }
298
+ }
299
+
300
+ if (this.meta.auto && this.meta.auto.search) {
301
+ const searchParams = this.meta.auto.search.params;
302
+ if (searchParams) {
303
+ for (const par of Object.keys(searchParams)) {
304
+ const props = searchParams[par].split('.');
305
+ const field = props[0];
306
+ const prop = props[1];
307
+ let parValue = this.parent[field];
308
+ if (parValue) {
309
+ if (prop) {
310
+ parValue = parValue[prop];
311
+ }
312
+ if (parValue) {
313
+ const params = {};
314
+ params[par] = parValue;
315
+ this.searchAutoSuggestions(params);
316
+ }
317
+ }
318
+ this.parentChangeSubject.subscribe((value) => {
319
+ if (value && value.hasOwnProperty(field)) {
320
+ if (value[field]) {
321
+ if (prop) {
322
+ value[field] = value[field][prop];
323
+ }
324
+ const params = {};
325
+ params[par] = value[field];
326
+ this.searchAutoSuggestions(params);
327
+ }
328
+ }
329
+ });
330
+ }
331
+ }
332
+ }
333
+ }
334
+
335
+ clearSearchResourceType() {
336
+ this.searchResourceType = {};
337
+ this.onSearchResourceTypeChanged();
338
+ }
339
+
340
+ onSearchResourceTypeChanged(resourceType?) {
341
+ if (!resourceType || !resourceType.id) {
342
+ this.searchResourceType.resourceType = null;
343
+ this.searchResourceTypesScope = (this.metaReference || []).filter(rt => rt.hidden !== true).map(rt => rt.name).join(',');
344
+ } else {
345
+ this.searchResourceType.resourceType = resourceType.id;
346
+ this.searchResourceTypesScope = resourceType.id;
347
+ }
348
+ this.buildPlaceholderLabel();
349
+ this.focus();
350
+ if (!this.model && !this.searchText) { // filter suggestions by type
351
+ this.filteredSuggestions = !resourceType ? (this.meta.suggestions || []) : (this.meta.suggestions || []).filter(
352
+ (s) => s.type === resourceType.id
353
+ );
354
+ } else { // regular search (return scoped results)
355
+ if (!this.model) {
356
+ this.search();
357
+ } else if (this.searchResults && resourceType) {
358
+ this.searchResults = this.searchResults.filter((s) => s.type === resourceType.id);
359
+ }
360
+ }
361
+ delete this.searchError;
362
+ }
363
+
364
+ onSearchTextChanged(value: string) {
365
+ this.searchTextChanged.next(value);
366
+ }
367
+
368
+ search() {
369
+ this.searching = true;
370
+ // this.activeSuggestionIndex;
371
+ if (!this.searchText || this.searchText.length < 2) {
372
+ this.searching = false;
373
+ delete this.searchResults;
374
+ return;
375
+ }
376
+ const showSearchResults = (results) => {
377
+ this.searching = false;
378
+ delete this.searchError;
379
+ delete this.activeSuggestionIndex;
380
+ if (!results || !(results instanceof Array)) {
381
+ results = [];
382
+ }
383
+ if (this.meta.acceptNew && this.meta.acceptNew.type && this.meta.acceptNew.pattern) {
384
+ const regex = new RegExp(this.meta.acceptNew.pattern);
385
+ if (regex.test(this.searchText)) {
386
+ results.unshift({ id: null, label: this.searchText, type: this.meta.acceptNew.type });
387
+ }
388
+ }
389
+ this.searchResults = results.map((r) => { // eslint-disable-line arrow-body-style
390
+ return { id: r.id, label: r.label, type: r.type || r.resourceType || this.searchResourceType.name, externalLink: r.externalLink };
391
+ });
392
+ if (!/Vlt-dropdown--expanded/.test(this.dropdown.className)) {
393
+ this.showDropdown();
394
+ }
395
+ };
396
+
397
+ if (this.meta.search) {
398
+ const searchParams = this.meta.search.params || {};
399
+ let searchUrl = `/fields/${this.meta.name}/search`;
400
+ if (this.meta.search.url) {
401
+ searchUrl = this.meta.search.url.replace(/\${\s*([\w.]+)\s*}/g, (match, key) => {
402
+ if (searchParams.hasOwnProperty(key)) {
403
+ const props = searchParams[key].split('.');
404
+ const field = props[0];
405
+ const prop = props[1];
406
+ delete searchParams[key]; // if it is URL param, remove it as query param
407
+ let parValue = this.parent[field];
408
+ if (parValue) {
409
+ if (prop) {
410
+ parValue = parValue[prop];
411
+ }
412
+ if (parValue) {
413
+ return parValue;
414
+ }
415
+ }
416
+ }
417
+ return 'undefined';
418
+ });
419
+ }
420
+ for (const p of Object.keys(searchParams)) {
421
+ searchUrl += `${searchUrl.indexOf('?') === -1 ? '?' : '&'}${encodeURIComponent(p)}=${encodeURIComponent(searchParams[p])}`;
422
+ }
423
+ searchUrl += `${searchUrl.indexOf('?') === -1 ? '?' : '&'}q=${encodeURIComponent(this.searchText)}`;
424
+ this.searching = true;
425
+ this.resourceService.getByPath(searchUrl).subscribe((results) => {
426
+ showSearchResults(results);
427
+ }, (error) => {
428
+ delete this.searching;
429
+ this.searchResults = [];
430
+ this.searchError = error.error ? error.error.message || error.error : error.message || error;
431
+ if (!/Vlt-dropdown--expanded/.test(this.dropdown.className)) {
432
+ this.showDropdown();
433
+ }
434
+ });
435
+ } else {
436
+ if (!this.vr) {
437
+ this.vr = this.metaResource.new({
438
+ integrationCode: this.integrationCode,
439
+ resourceType: null,
440
+ delegate: this.delegate
441
+ });
442
+ }
443
+ this.vr.searchIntegration(
444
+ this.searchText,
445
+ this.searchResourceType.name || this.searchResourceTypesScope
446
+ ).subscribe((results: Array<any>) => {
447
+ showSearchResults(results);
448
+ }, (error) => {
449
+ this.searching = false;
450
+ this.searchResults = [];
451
+ this.searchError = error.error ? error.error.message || error.error : error.message || error;
452
+ if (!/Vlt-dropdown--expanded/.test(this.dropdown.className)) {
453
+ this.showDropdown();
454
+ }
455
+ });
456
+ }
457
+ }
458
+
459
+ onActivated(ev) {
460
+ // ev.preventDefault();
461
+ // ev.stopPropagation();
462
+ this.input = ev.srcElement;
463
+ if (!this.dropdown) {
464
+ // this.dropdown = ev.srcElement.parentNode.parentNode;
465
+ this.dropdown = this.elementRef.nativeElement.querySelector('.dropdown-wrapper');
466
+ }
467
+ this.showDropdown();
468
+ }
469
+
470
+ onBlur(ev) {
471
+ let internalControl = false;
472
+ if (ev.relatedTarget) {
473
+ if (!this.elementRef.nativeElement.contains(ev.relatedTarget)) {
474
+ this.dismissDropdown();
475
+ } else {
476
+ internalControl = true;
477
+ }
478
+ }
479
+ if (this.keyListenerActive && !internalControl) {
480
+ this.input.removeEventListener('keydown', this.keydown);
481
+ setTimeout(() => {
482
+ if (!this.keyListenerActive) {
483
+ document.removeEventListener('click', this.clickout);
484
+ }
485
+ }, 400);
486
+ delete this.keyListenerActive;
487
+ }
488
+ }
489
+
490
+ removeSelection(ev?) {
491
+ if (ev) {
492
+ ev.preventDefault();
493
+ ev.stopPropagation();
494
+ }
495
+ if (this.model && !this.suggestions.find((s) => (s.id === this.model.id && s.type === this.model.type))) {
496
+ this.smartSuggestions.push({
497
+ id: this.model.id,
498
+ label: this.model.label,
499
+ type: this.model.type,
500
+ smart: true
501
+ });
502
+ }
503
+ this.metaTracker.emit(this.buildTrackEvent('REMOVE'));
504
+ this.clear();
505
+ }
506
+
507
+ onSuggestionSelect(ev, suggestion) {
508
+ if (!suggestion) {
509
+ return;
510
+ }
511
+ ev.preventDefault();
512
+ ev.stopPropagation();
513
+ this.focus();
514
+ this.metaTracker.emit(this.buildTrackEvent('SELECT'));
515
+ if (this.multiple) {
516
+ if (!this.model) {
517
+ this.model = [];
518
+ }
519
+ let existingEntry;
520
+ for (const entry of this.model) {
521
+ if (suggestion.id && (entry.id === suggestion.id)) {
522
+ existingEntry = entry;
523
+ break;
524
+ } else if (suggestion.label === entry.label) { // create new (google emails)
525
+ existingEntry = entry;
526
+ break;
527
+ }
528
+ }
529
+ if (!existingEntry) {
530
+ this.model.push(suggestion);
531
+ }
532
+ this.onModelChange(this.model);
533
+ delete this.searchText;
534
+ this.searchTextChanged.next(this.searchText);
535
+ } else {
536
+ this.model = suggestion;
537
+ this.externalLink = suggestion.externalLink;
538
+ this.onModelChange(suggestion);
539
+ this.searchText = suggestion.label;
540
+ this.activeSuggestionIndex = (this.searchResults || this.suggestions).indexOf(suggestion);
541
+ }
542
+ this.dismissDropdown();
543
+ // if (this.isPolymorphic) {
544
+ // this.searchResourceType = this.metaReference.find((rt)=> {
545
+ // return rt.name === suggestion.type;
546
+ // });
547
+ // }
548
+ }
549
+
550
+ showDropdown() {
551
+ if (this.disabled) {
552
+ return;
553
+ }
554
+ this.dropdownVisible = true;
555
+ this.dropdown.classList.add('Vlt-dropdown--expanded');
556
+ if (!this.keyListenerActive) {
557
+ this.input.addEventListener('keydown', this.keydown);
558
+ setTimeout(() => {
559
+ document.addEventListener('click', this.clickout);
560
+ }, 200);
561
+ this.keyListenerActive = true;
562
+ }
563
+ if (typeof (this.activeSuggestionIndex) === 'undefined') {
564
+ if (this.model && (this.searchResults || this.suggestions)) {
565
+ for (let suggestionIndex = 0; suggestionIndex < (this.searchResults || this.suggestions).length; suggestionIndex++) {
566
+ const item = (this.searchResults || this.suggestions)[suggestionIndex];
567
+ if (this.model.id === item.id && this.model.type === item.type) {
568
+ this.activeSuggestionIndex = suggestionIndex;
569
+ break;
570
+ }
571
+ }
572
+ }
573
+ }
574
+ this.ensureDropdownIsVisible();
575
+ }
576
+
577
+ dismissDropdown(event?) {
578
+ this.dropdownVisible = false;
579
+ if (event) {
580
+ this.focus();
581
+ }
582
+ if (this.dropdown) {
583
+ this.dropdown.classList.remove('Vlt-dropdown--expanded');
584
+ }
585
+ }
586
+
587
+ clear() {
588
+ this.prevModel = this.model;
589
+ delete this.searchText;
590
+ delete this.searchResults;
591
+ delete this.model;
592
+ delete this.activeSuggestionIndex;
593
+ this.onModelChange(this.model);
594
+ this.onSearchTextChanged(this.searchText);
595
+ this.focus();
596
+ }
597
+
598
+ revert() {
599
+ this.model = this.prevModel;
600
+ delete this.prevModel;
601
+ this.onModelChange(this.model);
602
+ this.focus();
603
+ }
604
+
605
+ remove(ev, item) {
606
+ ev.preventDefault();
607
+ ev.stopPropagation();
608
+ const optionIndex = this.model.indexOf(item);
609
+ if (optionIndex !== -1) {
610
+ this.model.splice(optionIndex, 1);
611
+ if (!this.model.length) {
612
+ delete this.model;
613
+ delete this.value;
614
+ }
615
+ this.onModelChange(this.model);
616
+ }
617
+ }
618
+
619
+ openResource(model?) {
620
+ this.keyListenerActive = true; // suppress dropdown in this case
621
+ setTimeout(() => {
622
+ this.dismissDropdown();
623
+ });
624
+ const refDialogComponent = this.viewContainerRef.createComponent(MetaRefDialog);
625
+ this.metaTracker.emit(this.buildTrackEvent(model ? 'EDIT' : 'NEW'));
626
+ let theme = this.elementRef.nativeElement.dataset.theme;
627
+ if (!theme || theme === 'inherit') {
628
+ let parentComponent = this.elementRef.nativeElement.closest('vgip-meta-layout');
629
+ if (parentComponent) {
630
+ theme = parentComponent.dataset.theme;
631
+ }
632
+ if (!theme || theme === 'inherit') {
633
+ parentComponent = this.elementRef.nativeElement.closest('vgip-meta-resource');
634
+ if (parentComponent) {
635
+ theme = parentComponent.dataset.theme;
636
+ }
637
+ }
638
+ }
639
+ this.referenceService.openDialog(
640
+ refDialogComponent, this.metaResource, this.integrationCode,
641
+ (this.metaReference instanceof Array) ? this.metaReference.filter(rt => rt.hidden !== true) : this.metaReference,
642
+ model, null, this.searchResourceType.resourceType, theme, this.overlayContainer
643
+ ).subscribe((result: any) => {
644
+ if (result) {
645
+ this.parent[this.meta.name] = result;
646
+ this.onChange.emit(result);
647
+ const existingSuggestion = (this.meta.suggestions || []).find((s) => (s.id === result.id && s.type === result.type));
648
+ if (existingSuggestion) {
649
+ existingSuggestion.label = result.label;
650
+ } else {
651
+ if (!this.meta.suggestions) {
652
+ this.meta.suggestions = [];
653
+ }
654
+ this.meta.suggestions.unshift(result);
655
+ }
656
+ this.metaTracker.emit(this.buildTrackEvent(model ? 'UPDATE' : 'CREATE', result.type || result.resourceType));
657
+ } else {
658
+ this.metaTracker.emit(this.buildTrackEvent(model ? 'CANCEL_EDIT' : 'CANCEL_UPDATE'));
659
+ }
660
+ });
661
+ }
662
+
663
+ private ensureDropdownIsVisible() {
664
+ setTimeout(() => {
665
+ const holder = this.dropdown.closest('.Vlt-card__content, .Vlt-modal__content');
666
+ if (holder) {
667
+ const el = this.elementRef.nativeElement.querySelector('.Vlt-dropdown__panel');
668
+ const elRect = el.getBoundingClientRect();
669
+ const holderRect = holder.getBoundingClientRect();
670
+ if (holderRect.top + holderRect.height < elRect.top + elRect.height) {
671
+ el.scrollIntoView({ block: 'end' });
672
+ }
673
+ }
674
+ }, 400);
675
+ }
676
+
677
+ private ensureDropdownOptionIsVisible() {
678
+ if (!this.dropdownScrollContainer) {
679
+ this.dropdownScrollContainer = this.dropdown.querySelector('.Vlt-dropdown__scroll');
680
+ }
681
+ if (this.dropdownScrollContainer && typeof (this.activeSuggestionIndex) === 'number') {
682
+ const scrollTop = 44 * this.activeSuggestionIndex;
683
+ const scrollBottom = 44 * (this.activeSuggestionIndex - 7);
684
+ if (scrollTop < this.dropdownScrollContainer.scrollTop) {
685
+ this.dropdownScrollContainer.scrollTop = scrollTop;
686
+ } else if (scrollBottom > this.dropdownScrollContainer.scrollTop) {
687
+ this.dropdownScrollContainer.scrollTop = scrollBottom;
688
+ }
689
+ }
690
+ }
691
+
692
+ private clickout = (event) => {
693
+ const internalControl = this.elementRef.nativeElement.contains(event.target);
694
+ if (internalControl && ['Vlt-dropdown__link', 'Vlt-dropdown__block'].indexOf(event.target.className) !== -1) {
695
+ this.focus();
696
+ } else if (!internalControl && event.target !== this.input && event.target.className !== 'Vlt-dropdown__title') {
697
+ this.dismissDropdown();
698
+ } else {
699
+ if (!internalControl && internalControl.tagName !== 'INPUT') {
700
+ this.focus();
701
+ }
702
+ }
703
+ };
704
+
705
+ private buildTrackEvent(action: string, value?: string) {
706
+ const e: IMetaTrackerEvent = {
707
+ integration: this.integrationCode,
708
+ resource: this.model ? (this.model.type || this.model.resourceType) : this.searchResourceType.resourceType,
709
+ field: this.meta.name,
710
+ action
711
+ };
712
+ if (this.meta.$isContactable) {
713
+ e.isContactable = true;
714
+ }
715
+ if (typeof (value) !== 'undefined') {
716
+ e.value = value;
717
+ }
718
+ return e;
719
+ }
720
+
721
+ private buildPlaceholderLabel() {
722
+ this.placeHolderLabel = this.meta.label;
723
+ if (this.multiple && !this.isPolymorphic) {
724
+ this.placeHolderLabel = '+Add ';
725
+ } else {
726
+ this.placeHolderLabel = (this.isSearchable && (this.multiple || !this.isPolymorphic)) ? 'Search ' : '';
727
+ }
728
+ this.placeHolderLabel += this.searchResourceType.resourceType ? this.searchResourceType.resourceType : this.searchResourceTypesScope;
729
+ }
730
+
731
+ private keydown = (event) => {
732
+ switch (event.key) {
733
+ case 'ArrowDown': {
734
+ this.showDropdown();
735
+ if (typeof (this.activeSuggestionIndex) === 'undefined') {
736
+ this.activeSuggestionIndex = 0;
737
+ } else {
738
+ this.activeSuggestionIndex++;
739
+ if (this.activeSuggestionIndex >= (this.searchResults || this.suggestions).length) {
740
+ this.activeSuggestionIndex = 0;
741
+ }
742
+ }
743
+ this.ensureDropdownOptionIsVisible();
744
+ event.preventDefault();
745
+ event.stopPropagation();
746
+ break;
747
+ }
748
+ case 'ArrowUp': {
749
+ this.showDropdown();
750
+ if (typeof (this.activeSuggestionIndex) === 'undefined') {
751
+ this.activeSuggestionIndex = (this.searchResults || this.suggestions).length - 1;
752
+ } else {
753
+ this.activeSuggestionIndex--;
754
+ if (this.activeSuggestionIndex < 0) {
755
+ this.activeSuggestionIndex = (this.searchResults || this.suggestions).length - 1;
756
+ }
757
+ }
758
+ this.ensureDropdownOptionIsVisible();
759
+ event.preventDefault();
760
+ event.stopPropagation();
761
+ break;
762
+ }
763
+ case 'Space':
764
+ case 'Enter': {
765
+ if (typeof (this.activeSuggestionIndex) !== 'undefined') {
766
+ event.preventDefault();
767
+ event.stopPropagation();
768
+ this.onSuggestionSelect(event, (this.searchResults || this.suggestions)[this.activeSuggestionIndex]);
769
+ delete this.activeSuggestionIndex;
770
+ }
771
+ break;
772
+ }
773
+ case 'Escape': {
774
+ event.preventDefault();
775
+ event.stopPropagation();
776
+ this.dismissDropdown();
777
+ break;
778
+ }
779
+ case 'Backspace': {
780
+ if (!this.multiple && this.model) { // HPBR-8229
781
+ event.preventDefault();
782
+ event.stopPropagation();
783
+ this.removeSelection();
784
+ }
785
+ break;
786
+ }
787
+ }
788
+ };
789
+
790
+ private buildExternalLink() {
791
+ if (this.model) {
792
+ if (this.model.externalLink) {
793
+ this.externalLink = this.model.externalLink;
794
+ } else if (this.metaContext.profiles[this.integrationCode.toUpperCase()]) {
795
+ delete this.externalLink;
796
+ this.metaMsgService.sendMessage(
797
+ this.integrationCode.toUpperCase(),
798
+ {
799
+ action: 'buildExternalLink',
800
+ integrationCode: this.integrationCode,
801
+ ref: this.model,
802
+ profile: this.metaContext.profiles[this.integrationCode.toUpperCase()]
803
+ },
804
+ (response) => {
805
+ if (!response.error) {
806
+ this.externalLink = response.externalLink;
807
+ } else {
808
+ console.warn('No external link support for', this.integrationCode, this.model.resourceType || this.model.type);
809
+ }
810
+ }
811
+ );
812
+ }
813
+ } else {
814
+ delete this.externalLink;
815
+ }
816
+ }
817
+
818
+ private searchAutoSuggestions(params?) {
819
+ let searchUrl = `/suggestions/${this.meta.name}/search`;
820
+ if (this.meta.auto && this.meta.auto.search) {
821
+ if (this.meta.auto.search.url) {
822
+ searchUrl = this.meta.auto.search.url.replace(/\${\s*([\w.]+)\s*}/g, (match, key) => {
823
+ if (params.hasOwnProperty(key)) {
824
+ const val = params[key];
825
+ delete params[key];
826
+ return val;
827
+ }
828
+ return '';
829
+ });
830
+ }
831
+ for (const p of Object.keys(params)) {
832
+ searchUrl += `${searchUrl.indexOf('?') === -1 ? '?' : '&'}${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`;
833
+ }
834
+ this.searching = true;
835
+ this.resourceService.getByPath(searchUrl).subscribe((result) => {
836
+ delete this.searching;
837
+ if (result) {
838
+ let suggestions;
839
+ suggestions = result;
840
+ if (this.meta.auto.search.extract) {
841
+ suggestions = result[this.meta.auto.search.extract];
842
+ }
843
+ if (suggestions instanceof Array !== true) {
844
+ suggestions = [suggestions];
845
+ }
846
+ const asyncSuggestions = [];
847
+ suggestions.forEach((s) => {
848
+ if (s && s.id && s.type) { // TOOD check the type is acceptable
849
+ asyncSuggestions.push({
850
+ id: s.id,
851
+ label: s.label || `${s.type}#${s.id}`,
852
+ type: s.type
853
+ });
854
+ }
855
+ });
856
+ if (asyncSuggestions.length) {
857
+ if (asyncSuggestions.length === 1) {
858
+ this.parent[this.meta.name] = asyncSuggestions[0];
859
+ } else {
860
+ delete this.asyncSuggestions;
861
+ }
862
+ }
863
+ this.asyncSuggestions = asyncSuggestions;
864
+ }
865
+ }, () => {
866
+ delete this.searching;
867
+ });
868
+ }
869
+ }
870
+
871
+ }