@smallpearl/ngx-helper 0.29.23

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 (117) hide show
  1. package/README.md +230 -0
  2. package/core/index.d.ts +2 -0
  3. package/core/src/ngx-helper.d.ts +7 -0
  4. package/core/src/version.d.ts +1 -0
  5. package/entity-field/index.d.ts +2 -0
  6. package/entity-field/src/entity-field-spec.d.ts +69 -0
  7. package/entity-field/src/provider.d.ts +27 -0
  8. package/fesm2022/smallpearl-ngx-helper-core.mjs +23 -0
  9. package/fesm2022/smallpearl-ngx-helper-core.mjs.map +1 -0
  10. package/fesm2022/smallpearl-ngx-helper-entity-field.mjs +112 -0
  11. package/fesm2022/smallpearl-ngx-helper-entity-field.mjs.map +1 -0
  12. package/fesm2022/smallpearl-ngx-helper-forms.mjs +112 -0
  13. package/fesm2022/smallpearl-ngx-helper-forms.mjs.map +1 -0
  14. package/fesm2022/smallpearl-ngx-helper-hover-dropdown.mjs +108 -0
  15. package/fesm2022/smallpearl-ngx-helper-hover-dropdown.mjs.map +1 -0
  16. package/fesm2022/smallpearl-ngx-helper-locale.mjs +296 -0
  17. package/fesm2022/smallpearl-ngx-helper-locale.mjs.map +1 -0
  18. package/fesm2022/smallpearl-ngx-helper-mat-busy-wheel.mjs +504 -0
  19. package/fesm2022/smallpearl-ngx-helper-mat-busy-wheel.mjs.map +1 -0
  20. package/fesm2022/smallpearl-ngx-helper-mat-context-menu.mjs +184 -0
  21. package/fesm2022/smallpearl-ngx-helper-mat-context-menu.mjs.map +1 -0
  22. package/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs +1486 -0
  23. package/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs.map +1 -0
  24. package/fesm2022/smallpearl-ngx-helper-mat-entity-list.mjs +800 -0
  25. package/fesm2022/smallpearl-ngx-helper-mat-entity-list.mjs.map +1 -0
  26. package/fesm2022/smallpearl-ngx-helper-mat-file-input.mjs +328 -0
  27. package/fesm2022/smallpearl-ngx-helper-mat-file-input.mjs.map +1 -0
  28. package/fesm2022/smallpearl-ngx-helper-mat-form-error.mjs +468 -0
  29. package/fesm2022/smallpearl-ngx-helper-mat-form-error.mjs.map +1 -0
  30. package/fesm2022/smallpearl-ngx-helper-mat-select-entity.mjs +854 -0
  31. package/fesm2022/smallpearl-ngx-helper-mat-select-entity.mjs.map +1 -0
  32. package/fesm2022/smallpearl-ngx-helper-mat-side-menu-layout.mjs +930 -0
  33. package/fesm2022/smallpearl-ngx-helper-mat-side-menu-layout.mjs.map +1 -0
  34. package/fesm2022/smallpearl-ngx-helper-mat-tel-input.mjs +926 -0
  35. package/fesm2022/smallpearl-ngx-helper-mat-tel-input.mjs.map +1 -0
  36. package/fesm2022/smallpearl-ngx-helper-sideload.mjs +111 -0
  37. package/fesm2022/smallpearl-ngx-helper-sideload.mjs.map +1 -0
  38. package/fesm2022/smallpearl-ngx-helper-stationary-with-line-items.mjs +384 -0
  39. package/fesm2022/smallpearl-ngx-helper-stationary-with-line-items.mjs.map +1 -0
  40. package/fesm2022/smallpearl-ngx-helper.mjs +13 -0
  41. package/fesm2022/smallpearl-ngx-helper.mjs.map +1 -0
  42. package/forms/index.d.ts +1 -0
  43. package/forms/src/validation-error-handler.d.ts +52 -0
  44. package/hover-dropdown/index.d.ts +1 -0
  45. package/hover-dropdown/src/hover-dropdown.directive.d.ts +41 -0
  46. package/index.d.ts +5 -0
  47. package/locale/index.d.ts +5 -0
  48. package/locale/src/currency.pipe.d.ts +14 -0
  49. package/locale/src/date.pipe.d.ts +14 -0
  50. package/locale/src/format-currency.d.ts +1 -0
  51. package/locale/src/format-date.d.ts +2 -0
  52. package/locale/src/is-empty.d.ts +1 -0
  53. package/locale/src/providers.d.ts +20 -0
  54. package/mat-busy-wheel/index.d.ts +4 -0
  55. package/mat-busy-wheel/src/busy-wheel-op.d.ts +65 -0
  56. package/mat-busy-wheel/src/busy-wheel.component.d.ts +12 -0
  57. package/mat-busy-wheel/src/busy-wheel.service.d.ts +42 -0
  58. package/mat-busy-wheel/src/host-busy-wheel.directive.d.ts +35 -0
  59. package/mat-context-menu/index.d.ts +1 -0
  60. package/mat-context-menu/src/mat-context-menu.component.d.ts +54 -0
  61. package/mat-entity-crud/index.d.ts +5 -0
  62. package/mat-entity-crud/src/default-config.d.ts +9 -0
  63. package/mat-entity-crud/src/form-view-host.component.d.ts +34 -0
  64. package/mat-entity-crud/src/mat-entity-crud-form-base.d.ts +95 -0
  65. package/mat-entity-crud/src/mat-entity-crud-internal-types.d.ts +66 -0
  66. package/mat-entity-crud/src/mat-entity-crud-types.d.ts +141 -0
  67. package/mat-entity-crud/src/mat-entity-crud.component.d.ts +267 -0
  68. package/mat-entity-crud/src/preview-host.component.d.ts +19 -0
  69. package/mat-entity-crud/src/preview-pane.component.d.ts +27 -0
  70. package/mat-entity-crud/src/providers.d.ts +3 -0
  71. package/mat-entity-list/index.d.ts +3 -0
  72. package/mat-entity-list/src/config.d.ts +6 -0
  73. package/mat-entity-list/src/mat-entity-list-types.d.ts +53 -0
  74. package/mat-entity-list/src/mat-entity-list.component.d.ts +209 -0
  75. package/mat-entity-list/src/providers.d.ts +3 -0
  76. package/mat-file-input/README.md +63 -0
  77. package/mat-file-input/index.d.ts +1 -0
  78. package/mat-file-input/src/mat-file-input.component.d.ts +58 -0
  79. package/mat-form-error/README.md +306 -0
  80. package/mat-form-error/index.d.ts +6 -0
  81. package/mat-form-error/src/locales/en.d.ts +4 -0
  82. package/mat-form-error/src/locales/hu.d.ts +4 -0
  83. package/mat-form-error/src/locales/index.d.ts +3 -0
  84. package/mat-form-error/src/locales/pt-br.d.ts +4 -0
  85. package/mat-form-error/src/ngx-error-list.component.d.ts +9 -0
  86. package/mat-form-error/src/ngx-mat-error-control.d.ts +17 -0
  87. package/mat-form-error/src/ngx-mat-error-def.directive.d.ts +30 -0
  88. package/mat-form-error/src/ngx-mat-errors-for-date-range-picker.directive.d.ts +8 -0
  89. package/mat-form-error/src/ngx-mat-errors.component.d.ts +23 -0
  90. package/mat-form-error/src/types.d.ts +68 -0
  91. package/mat-form-error/src/utils/coerce-to-observable.d.ts +3 -0
  92. package/mat-form-error/src/utils/distinct-until-error-changed.d.ts +2 -0
  93. package/mat-form-error/src/utils/find-error-for-control.d.ts +9 -0
  94. package/mat-form-error/src/utils/get-abstract-controls.d.ts +3 -0
  95. package/mat-form-error/src/utils/get-control-with-error.d.ts +3 -0
  96. package/mat-select-entity/index.d.ts +2 -0
  97. package/mat-select-entity/src/mat-select-entity.component.d.ts +207 -0
  98. package/mat-select-entity/src/providers.d.ts +9 -0
  99. package/mat-side-menu-layout/index.d.ts +6 -0
  100. package/mat-side-menu-layout/src/layout.service.d.ts +23 -0
  101. package/mat-side-menu-layout/src/mat-menu-layout.component.d.ts +39 -0
  102. package/mat-side-menu-layout/src/mat-menu-layout.module.d.ts +18 -0
  103. package/mat-side-menu-layout/src/mat-menu-list-item.component.d.ts +36 -0
  104. package/mat-side-menu-layout/src/mat-menu-pane.component.d.ts +66 -0
  105. package/mat-side-menu-layout/src/nav-item.d.ts +10 -0
  106. package/mat-tel-input/README.md +18 -0
  107. package/mat-tel-input/index.d.ts +2 -0
  108. package/mat-tel-input/src/country-codes.d.ts +5 -0
  109. package/mat-tel-input/src/mat-telephone.component.d.ts +129 -0
  110. package/mat-tel-input/src/providers.d.ts +38 -0
  111. package/ngx-helper.d.ts +2 -0
  112. package/package.json +114 -0
  113. package/public-api.d.ts +1 -0
  114. package/sideload/index.d.ts +1 -0
  115. package/sideload/src/sideload.d.ts +17 -0
  116. package/stationary-with-line-items/index.d.ts +1 -0
  117. package/stationary-with-line-items/src/stationary-with-line-items.component.d.ts +74 -0
@@ -0,0 +1,1486 @@
1
+ import * as i1$1 from '@angular/common/http';
2
+ import { HttpContextToken, HttpContext } from '@angular/common/http';
3
+ import * as i0 from '@angular/core';
4
+ import { InjectionToken, inject, input, signal, viewChild, ViewContainerRef, Component, ChangeDetectionStrategy, computed, viewChildren, EventEmitter, ContentChildren, Output, ChangeDetectorRef } from '@angular/core';
5
+ import * as i4 from '@angular/common';
6
+ import { CommonModule } from '@angular/common';
7
+ import * as i1 from '@angular/material/button';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import * as i2$1 from '@angular/material/snack-bar';
10
+ import { MatSnackBarModule } from '@angular/material/snack-bar';
11
+ import { MatSortModule } from '@angular/material/sort';
12
+ import * as i7 from '@angular/material/table';
13
+ import { MatColumnDef, MatTableModule } from '@angular/material/table';
14
+ import * as i5 from '@angular/router';
15
+ import { RouterModule } from '@angular/router';
16
+ import { SPMatHostBusyWheelDirective, showBusyWheelUntilComplete } from '@smallpearl/ngx-helper/mat-busy-wheel';
17
+ import { SPMatContextMenuComponent } from '@smallpearl/ngx-helper/mat-context-menu';
18
+ import { SPMatEntityListComponent } from '@smallpearl/ngx-helper/mat-entity-list';
19
+ import * as i8 from '@angular/material/menu';
20
+ import { MatMenuModule } from '@angular/material/menu';
21
+ import * as i3 from '@angular/platform-browser';
22
+ import * as i9 from 'angular-split';
23
+ import { AngularSplitModule } from 'angular-split';
24
+ import { startCase } from 'lodash';
25
+ import { plural } from 'pluralize';
26
+ import { Subscription, tap, switchMap, of, map } from 'rxjs';
27
+ import * as i2 from '@angular/material/icon';
28
+ import { MatIconModule } from '@angular/material/icon';
29
+ import * as i1$2 from '@angular/material/toolbar';
30
+ import { MatToolbarModule } from '@angular/material/toolbar';
31
+ import { setServerErrorsAsFormErrors } from '@smallpearl/ngx-helper/forms';
32
+ import { tap as tap$1 } from 'rxjs/operators';
33
+
34
+ const SP_MAT_ENTITY_CRUD_HTTP_CONTEXT = new HttpContextToken(() => ({
35
+ entityName: '',
36
+ entityNamePlural: '',
37
+ endpoint: '',
38
+ op: undefined,
39
+ }));
40
+
41
+ const SP_MAT_ENTITY_CRUD_CONFIG = new InjectionToken('SPMatEntityCrudConfig');
42
+
43
+ function defaultCrudResponseParser(entityName, idKey, method, resp) {
44
+ // If the response is an object with a property '<idKey>', return it as
45
+ // TEntity.
46
+ if (resp.hasOwnProperty(idKey)) {
47
+ return resp;
48
+ }
49
+ // If the response has an object indexed at '<entityName>' and it has
50
+ // the property '<idKey>', return it as TEntity.
51
+ if (resp.hasOwnProperty(entityName)) {
52
+ const obj = resp[entityName];
53
+ if (obj.hasOwnProperty(idKey)) {
54
+ return obj;
55
+ }
56
+ }
57
+ // Return undefined, indicating that we could't parse the response.
58
+ return undefined;
59
+ }
60
+ const DefaultSPMatEntityCrudConfig = {
61
+ i18n: {
62
+ newItemLabel: (itemLabel) => `New ${itemLabel}`,
63
+ editItemLabel: (itemLabel) => `Edit ${itemLabel}`,
64
+ edit: 'Edit',
65
+ delete: 'Delete',
66
+ deleteItemHeader: (itemLabel) => `Confirm Delete ${itemLabel}`,
67
+ deleteItemMessage: (itemLabel) => `Are you sure you want to delete this ${itemLabel}`,
68
+ itemDeletedNotification: (itemLabel) => `${itemLabel} deleted`,
69
+ createdItemNotification: (itemLabel) => `${itemLabel} created.`,
70
+ updatedItemNotification: (itemLabel) => `${itemLabel} saved.`,
71
+ loseChangesPrompt: 'OK to lose changes?'
72
+ },
73
+ crudOpResponseParser: defaultCrudResponseParser
74
+ };
75
+ /**
76
+ * To be called from an object constructor as it internally calls Angular's
77
+ * inject() API.
78
+ * @param userConfig
79
+ * @returns
80
+ */
81
+ function getEntityCrudConfig() {
82
+ const userCrudConfig = inject(SP_MAT_ENTITY_CRUD_CONFIG, {
83
+ optional: true,
84
+ });
85
+ return {
86
+ ...DefaultSPMatEntityCrudConfig,
87
+ ...(userCrudConfig ?? {}),
88
+ };
89
+ }
90
+
91
+ class FormViewHostComponent {
92
+ entityCrudComponentBase = input.required();
93
+ clientViewTemplate = input(null);
94
+ itemLabel = input.required();
95
+ itemLabelPlural = input.required();
96
+ entity = signal(undefined);
97
+ title = signal('');
98
+ params = signal(undefined);
99
+ clientFormView;
100
+ vc = viewChild('clientFormContainer', { read: ViewContainerRef });
101
+ config;
102
+ sub$ = new Subscription();
103
+ constructor() {
104
+ this.config = getEntityCrudConfig();
105
+ }
106
+ ngOnInit() { }
107
+ ngOnDestroy() {
108
+ this.sub$.unsubscribe();
109
+ }
110
+ show(entity, params) {
111
+ this.entity.set(entity);
112
+ if (params && params?.title) {
113
+ this.title.set(params.title);
114
+ }
115
+ else {
116
+ this.title.set(entity ? this.config.i18n.editItemLabel(this.itemLabel()) : this.config.i18n.newItemLabel(this.itemLabel()));
117
+ }
118
+ this.params.set(params);
119
+ this.createClientView();
120
+ }
121
+ close(cancel) {
122
+ this.entityCrudComponentBase().closeCreateEdit(cancel);
123
+ // destroy the client's form component
124
+ this.destroyClientView();
125
+ }
126
+ registerCanCancelEditCallback(callback) {
127
+ this.entityCrudComponentBase().registerCanCancelEditCallback(callback);
128
+ }
129
+ create(entityValue) {
130
+ // console.log(
131
+ // `SPCreateEditEntityHostComponent.create - entity: ${JSON.stringify(
132
+ // entityValue
133
+ // )}`
134
+ // );
135
+ const crudComponent = this.entityCrudComponentBase();
136
+ return crudComponent?.create(entityValue).pipe(tap(() => this.close(false)));
137
+ }
138
+ update(id, entityValue) {
139
+ // console.log(
140
+ // `SPCreateEditEntityHostComponent.update - id: ${String(
141
+ // id
142
+ // )}, entity: ${entityValue}`
143
+ // );
144
+ const crudComponent = this.entityCrudComponentBase();
145
+ return crudComponent?.update(id, entityValue).pipe(tap(() => this.close(false)));
146
+ }
147
+ /**
148
+ * Creates the client view provided via template
149
+ */
150
+ createClientView() {
151
+ /** Render preview component if one was provided */
152
+ const ft = this.clientViewTemplate();
153
+ const vc = this.vc();
154
+ if (ft && vc) {
155
+ this.clientFormView = vc.createEmbeddedView(ft, {
156
+ $implicit: {
157
+ bridge: this,
158
+ entity: this.entity(),
159
+ params: this.params()
160
+ },
161
+ });
162
+ this.clientFormView.detectChanges();
163
+ }
164
+ }
165
+ destroyClientView() {
166
+ if (this.clientFormView) {
167
+ this.clientFormView.destroy();
168
+ this.clientFormView = null;
169
+ }
170
+ }
171
+ onClose() {
172
+ // Can we give the client form component a chance to intercept this
173
+ // and cancel the closure?
174
+ if (this.entityCrudComponentBase().canCancelEdit()) {
175
+ this.entityCrudComponentBase().closeCreateEdit(true);
176
+ this.destroyClientView();
177
+ }
178
+ }
179
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: FormViewHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
180
+ /** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.1.6", type: FormViewHostComponent, isStandalone: true, selector: "sp-create-edit-entity-host", inputs: { entityCrudComponentBase: { classPropertyName: "entityCrudComponentBase", publicName: "entityCrudComponentBase", isSignal: true, isRequired: true, transformFunction: null }, clientViewTemplate: { classPropertyName: "clientViewTemplate", publicName: "clientViewTemplate", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: true, transformFunction: null }, itemLabelPlural: { classPropertyName: "itemLabelPlural", publicName: "itemLabelPlural", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "vc", first: true, predicate: ["clientFormContainer"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
181
+ <div spHostBusyWheel="formBusyWheel">
182
+ <div class="create-edit-topbar">
183
+ <div class="title">
184
+ {{ title() }}
185
+ </div>
186
+ <div class="spacer"></div>
187
+ <div class="close">
188
+ <button mat-icon-button (click)="onClose()">
189
+ <mat-icon>close</mat-icon>
190
+ </button>
191
+ </div>
192
+ </div>
193
+ <ng-container #clientFormContainer></ng-container>
194
+ </div>
195
+ `, isInline: true, styles: [".create-edit-topbar{display:flex;flex-direction:row;align-items:center;min-height:48px}.create-edit-topbar .title{font-size:1.5em;font-weight:500}.create-edit-topbar .spacer{flex-grow:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: SPMatHostBusyWheelDirective, selector: "[spHostBusyWheel]", inputs: ["spHostBusyWheel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
196
+ }
197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: FormViewHostComponent, decorators: [{
198
+ type: Component,
199
+ args: [{ imports: [CommonModule, MatButtonModule, MatIconModule, SPMatHostBusyWheelDirective], selector: 'sp-create-edit-entity-host', template: `
200
+ <div spHostBusyWheel="formBusyWheel">
201
+ <div class="create-edit-topbar">
202
+ <div class="title">
203
+ {{ title() }}
204
+ </div>
205
+ <div class="spacer"></div>
206
+ <div class="close">
207
+ <button mat-icon-button (click)="onClose()">
208
+ <mat-icon>close</mat-icon>
209
+ </button>
210
+ </div>
211
+ </div>
212
+ <ng-container #clientFormContainer></ng-container>
213
+ </div>
214
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".create-edit-topbar{display:flex;flex-direction:row;align-items:center;min-height:48px}.create-edit-topbar .title{font-size:1.5em;font-weight:500}.create-edit-topbar .spacer{flex-grow:1}\n"] }]
215
+ }], ctorParameters: () => [] });
216
+
217
+ class PreviewHostComponent {
218
+ vc = viewChild('previewComponent', { read: ViewContainerRef });
219
+ entityCrudComponentBase = input.required();
220
+ clientViewTemplate = input(null);
221
+ entity = signal(undefined);
222
+ clientView;
223
+ constructor() {
224
+ // effect(() => {
225
+ // const tmpl = this.clientViewTemplate();
226
+ // this.createClientView(tmpl);
227
+ // });
228
+ }
229
+ ngOnInit() { }
230
+ ngOnDestroy() { }
231
+ show(entity, params) {
232
+ this.entity.set(entity);
233
+ // if (params && params?.title) {
234
+ // this.title.set(params.title);
235
+ // } else {
236
+ // this.title.set(entity ? this.config.i18n.editItemLabel(this.itemLabel()) : this.config.i18n.newItemLabel(this.itemLabel()));
237
+ // }
238
+ // this.params.set(params);
239
+ this.createClientView();
240
+ }
241
+ close() {
242
+ // this.entityCrudComponentBase().closeCreateEdit(cancel);
243
+ // destroy the client's form component
244
+ this.destroyClientView();
245
+ }
246
+ createClientView() {
247
+ if (this.clientView) {
248
+ // We have only one view in the ng-container. So we might as well
249
+ // call clear() to remove all views contained in it.
250
+ this.vc().clear();
251
+ this.clientView.destroy();
252
+ }
253
+ /** Render preview component if one was provided */
254
+ const ft = this.clientViewTemplate();
255
+ const vc = this.vc();
256
+ if (ft && vc) {
257
+ this.clientView = this.vc().createEmbeddedView(ft, {
258
+ $implicit: {
259
+ entity: this.entity(),
260
+ entityCrudComponent: this.entityCrudComponentBase(),
261
+ },
262
+ });
263
+ this.clientView.detectChanges();
264
+ }
265
+ }
266
+ destroyClientView() {
267
+ if (this.clientView) {
268
+ this.clientView.destroy();
269
+ this.clientView = null;
270
+ }
271
+ }
272
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: PreviewHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
273
+ /** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.1.6", type: PreviewHostComponent, isStandalone: true, selector: "sp-entity-crud-preview-host", inputs: { entityCrudComponentBase: { classPropertyName: "entityCrudComponentBase", publicName: "entityCrudComponentBase", isSignal: true, isRequired: true, transformFunction: null }, clientViewTemplate: { classPropertyName: "clientViewTemplate", publicName: "clientViewTemplate", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "vc", first: true, predicate: ["previewComponent"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: ` <ng-container #previewComponent></ng-container> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
274
+ }
275
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: PreviewHostComponent, decorators: [{
276
+ type: Component,
277
+ args: [{
278
+ imports: [],
279
+ selector: 'sp-entity-crud-preview-host',
280
+ template: ` <ng-container #previewComponent></ng-container> `,
281
+ changeDetection: ChangeDetectionStrategy.OnPush
282
+ }]
283
+ }], ctorParameters: () => [] });
284
+
285
+ class SPMatEntityCrudComponent extends SPMatEntityListComponent {
286
+ snackBar;
287
+ // entityName = input.required<string>();
288
+ // entityNamePlural = input<string>();
289
+ itemLabel = input();
290
+ itemLabelPlural = input();
291
+ /**
292
+ * Title string displayed above the component. If not specified, will use
293
+ * itemLabelPlural() as the title.
294
+ */
295
+ title = input();
296
+ /**
297
+ *
298
+ */
299
+ itemActions = input([]);
300
+ /**
301
+ * Specify the list of router paths that will be set as the value for
302
+ * [routerLink] for the 'New {{ item }}' button. If not specified,
303
+ * if createEditTemplate is specified, it will be shown. If not, `action`
304
+ * out event will be raised with `{ role: '_new_' }`.
305
+ */
306
+ newItemLink = input();
307
+ /**
308
+ * If not specified, will use label from SPMatEntityCrudConfig.i18n.newItemLabel.
309
+ */
310
+ newItemLabel = input();
311
+ /**
312
+ * Text for the Edit <item> pane title
313
+ */
314
+ editItemTitle = input();
315
+ /**
316
+ * If you want "New {{item}}" button to support multiple entity types,
317
+ * you can set this to `NewItemSubType[]`, where each element stands for for
318
+ * a dropdown menu item. Refer to `NewItemSubType` for details on this
319
+ * interface.
320
+ */
321
+ newItemSubTypes = input();
322
+ /**
323
+ * If you want to take control of the network operations for the CRUD
324
+ * operations (GET/CREATE/UPDATE/DELETE), provide a value for this property.
325
+ */
326
+ crudOpFn = input();
327
+ /**
328
+ * Item preview template.
329
+ */
330
+ previewTemplate = input();
331
+ /**
332
+ * Whether to allow a context menu action or not. Return false to disable
333
+ * the action.
334
+ */
335
+ allowEntityActionFn = input();
336
+ /**
337
+ * A template that allows the header to be replaced. Usage:-
338
+ *
339
+ * ```
340
+ * <sp-map-entity-crud
341
+ * [headerTemplate]="myCrudViewHeader"
342
+ * ></sp-map-entity-crud>
343
+ * <ng-template #myCrudViewHeader>...</ng-template>
344
+ * ```
345
+ */
346
+ headerTemplate = input();
347
+ /**
348
+ * Set this to the custom template identifier that will replace the
349
+ * "New {{Item}}" button portion. This template will expand towards the
350
+ * title which will be placed to its left (right in rtl).
351
+ *
352
+ * ```
353
+ * <sp-map-entity-crud
354
+ * [actionsTemplate]="myCrudActions"
355
+ * ></sp-map-entity-crud>
356
+ * <ng-template #myCrudActions>...</ng-template>
357
+ * ```
358
+ */
359
+ actionsTemplate = input();
360
+ /**
361
+ * CRUD action response parser. This will be called with the response
362
+ * from CREATE & UPDATE operations to parse the response JSON and return
363
+ * the created/updated TEntity.
364
+ */
365
+ crudResponseParser = input();
366
+ /**
367
+ * An ng-template name that contains the component which provides the
368
+ * create/edit CRUD action.
369
+ *
370
+ * ```
371
+ * <ng-template #createEdit let-data>
372
+ * <app-create-edit-entity-demo [bridge]="data.bridge" [entity]="data.entity"></app-create-edit-entity-demo>
373
+ * </ng-template>
374
+ * ```
375
+ * Note how [bridge] & [entity] properties are set deriving them from the
376
+ * implicit template context. [entity] will be undefined for Create
377
+ * opreation and will be the valid entity for an Update.
378
+ * (app-create-edit-entity-demo here is the client code that implements the
379
+ * Create/Edit form)
380
+ */
381
+ createEditFormTemplate = input(null);
382
+ /**
383
+ * Disables the per item actions column, preventing 'Edit' & 'Delete'
384
+ * (and other user defined) item operations.
385
+ */
386
+ disableItemActions = input(false);
387
+ /**
388
+ * Disables the Create function.
389
+ */
390
+ disableCreate = input(false);
391
+ /**
392
+ * View refresh policy after a CREATE/UPDATE operation. Values
393
+ * 'none' - Objects are not refreshed after an edit operation. The return
394
+ * value of the edit operation is used as the object to
395
+ * add/update the component's internal store. This is the default.
396
+ * 'object' - Refresh just the object that was returned from the
397
+ * CREATE/UPDATE operation. Use this if the JSON object returned
398
+ * after a successful CREATE/UPDATE op differs from the JSON
399
+ * object returned for the GET request.
400
+ * 'all' - Refresh the entire list after a CREATE/UPDATE operation. This
401
+ * mimics the behaviour of legacy HTML apps with pure server
402
+ * defined UI.
403
+ */
404
+ refreshAfterEdit = input('none');
405
+ /**
406
+ * HttpContext for crud requests - list, create, retrieve, update & delete.
407
+ * The value can be an object where the property names reflect the CRUD
408
+ * methods with each of these keys taking
409
+ * `[[HttpContextToken<any>, any]] | [HttpContextToken<any>, any]` as its
410
+ * value. This object has a special key 'crud', which if given a value for,
411
+ * would be used for all CRUD requests (CREATE|READ|UPDATE|DELETE).
412
+ *
413
+ * Alternatively the property can be set a
414
+ * `[[HttpContextToken<any>, any]] | [HttpContextToken<any>, any]` as its
415
+ * value, in which case the same context would be used for all HTTP requests.
416
+ */
417
+ crudHttpReqContext = input();
418
+ /**
419
+ * Width of the edit pane as a percentange of the overall <as-split> width.
420
+ */
421
+ editPaneWidth = input(100);
422
+ /**
423
+ * Width of the preview pane as a percentange of the overall <as-split> width.
424
+ */
425
+ previewPaneWidth = input(50);
426
+ // INTERNAL PROPERTIES //
427
+ // Derive a label from a camelCase source string. If the camelCase string
428
+ // can be translated, it returns the translated string. If not, the function
429
+ // converts the camelCase to 'Title Case' and returns it.
430
+ getLabel = (source) => {
431
+ const label = this.ngxHelperConfig.i18nTranslate(source);
432
+ if (label.localeCompare(source) !== 0) {
433
+ // Successful translation, return it
434
+ return label;
435
+ }
436
+ return startCase(source);
437
+ };
438
+ _itemLabel = computed(() => this.itemLabel()
439
+ ? this.itemLabel()
440
+ : this.getLabel(this.entityName()));
441
+ _itemLabelPlural = computed(() => this.itemLabelPlural()
442
+ ? this.itemLabelPlural()
443
+ : this.getLabel(plural(this.entityName())));
444
+ // Computed title
445
+ _title = computed(() => this.title() ? this.title() : this._itemLabelPlural());
446
+ // endpoint with the QP string removed (if one was provided)
447
+ _endpointSansParams = computed(() => this.endpoint().split('?')[0]);
448
+ _endpointParams = computed(() => { });
449
+ componentColumns = viewChildren(MatColumnDef);
450
+ _clientColumnDefs;
451
+ /**
452
+ * Event raised for user selecting an item action. It's also raised
453
+ * for 'New <Item>' action, if 'newItemLink' property is not set.
454
+ */
455
+ action = new EventEmitter();
456
+ /**
457
+ * Event raised when create Create/Edit pane is activated & deactivated.
458
+ * Event contains two flags:-
459
+ * activated - whether the createEdit form view was activated or
460
+ * deactivated.
461
+ * cancelled - whether the form view was cancelled by user. False for this
462
+ * indicates that the form view was closed after a successful
463
+ * edit operation.
464
+ */
465
+ entityViewPaneActivated = new EventEmitter();
466
+ busyWheelId = `entityCrudBusyWheel-${Date.now()}`;
467
+ sub$ = new Subscription();
468
+ spEntitiesList = viewChild((SPMatEntityListComponent));
469
+ crudConfig;
470
+ // This is the internal component that will host the createEditFormTemplate
471
+ createEditHostComponent = viewChild(FormViewHostComponent);
472
+ // A flag to toggle the viewport's contents between the mat-table
473
+ // and the create/edit form template.
474
+ createEditViewActive = signal(false);
475
+ // Whether it's okay to cancel the edit
476
+ canCancelEditCallback;
477
+ // Preview host component
478
+ previewHostComponent = viewChild(PreviewHostComponent);
479
+ previewActive = computed(() => this.previewedEntity() !== undefined);
480
+ previewedEntity = signal(undefined);
481
+ // Whether the pane that hosts the preview/edit-entity template is active.
482
+ // We call it entityPane as it's used to either render a selected entity
483
+ // or to edit one.
484
+ entityPaneActive = computed(() => !!this.previewedEntity() || this.createEditViewActive());
485
+ // Effective width of the entity pane.
486
+ entityPaneWidth = computed(() => !!this.previewedEntity() ? this.previewPaneWidth() : this.editPaneWidth());
487
+ // Width of the pane showing the list of entities. Calculated as
488
+ entitiesPaneWidth = computed(() => 100 - this.entityPaneWidth());
489
+ entitiesPaneHidden = computed(() => this.entityPaneActive() && this.entityPaneWidth() === 100);
490
+ defaultItemCrudActions = signal([]);
491
+ columnsWithAction = computed(() => {
492
+ const cols = JSON.parse(JSON.stringify(this.columns()));
493
+ // JSON.parse(JSON.strigify()) does not clone function objects. So
494
+ // explicitly copy these over. So this is really a shallow clone as
495
+ // the cloned objects still refers to the function objects in the original
496
+ // object.
497
+ this.columns().forEach((col, index, orgColumns) => {
498
+ const orgCol = orgColumns[index];
499
+ if (typeof orgCol !== 'string') {
500
+ const newColumn = cols[index];
501
+ if (orgCol.valueFn) {
502
+ newColumn.valueFn = orgCol.valueFn;
503
+ }
504
+ if (orgCol.valueOptions) {
505
+ newColumn.valueOptions = orgCol.valueOptions;
506
+ }
507
+ }
508
+ });
509
+ const actionDefined = cols.find((c) => typeof c === 'string' ? c === 'action' : c.name === 'action') !== undefined;
510
+ if (!actionDefined && !this.disableItemActions()) {
511
+ cols.push('action');
512
+ }
513
+ return cols;
514
+ });
515
+ // Use a custom computed() signal to derive the action items for each
516
+ // entity so that we can run per item's allow item action function to
517
+ // selectively disable one or more actions based on the item's state.
518
+ _itemActions = computed(() => {
519
+ const actions = this.itemActions() && this.itemActions().length
520
+ ? this.itemActions()
521
+ : this.defaultItemCrudActions();
522
+ let actionsCopy = JSON.parse(JSON.stringify(actions));
523
+ actionsCopy.forEach((action, index) => {
524
+ const orgDisable = actions[index]?.disable;
525
+ action.disable = (entity) => {
526
+ if (orgDisable) {
527
+ return orgDisable(entity);
528
+ }
529
+ const allowItemActionFn = this.allowEntityActionFn();
530
+ if (allowItemActionFn) {
531
+ return !allowItemActionFn(entity, action.role ?? action.label);
532
+ }
533
+ return false;
534
+ };
535
+ });
536
+ return actionsCopy;
537
+ });
538
+ // This uses the previewActive signal to compute the visible columns
539
+ // when preview is activated. For now we just hide the 'action' column when
540
+ // preview is active. We can further customize this logic by allowing the
541
+ // client to specify the columns to display when preview is active thereby
542
+ // reducing column clutter when the table width becomes narrower owing to
543
+ // preview pane taking up screen space.
544
+ visibleColumns = computed(() => this.entityPaneActive()
545
+ ? this.columnsWithAction()
546
+ .map((col) => (typeof col === 'string' ? col : col.name))
547
+ .filter((name) => name !== 'action')
548
+ : []);
549
+ constructor(http, snackBar, sanitizer, injector) {
550
+ super(http, sanitizer, injector);
551
+ this.snackBar = snackBar;
552
+ this.crudConfig = getEntityCrudConfig();
553
+ if (this.crudConfig?.defaultItemActions) {
554
+ this.defaultItemCrudActions.set(this.crudConfig?.defaultItemActions);
555
+ }
556
+ else {
557
+ this.defaultItemCrudActions.set([
558
+ { label: this.crudConfig.i18n.edit, role: '_update_' },
559
+ { label: this.crudConfig.i18n.delete, role: '_delete_' },
560
+ ]);
561
+ }
562
+ }
563
+ ngOnInit() { }
564
+ ngOnDestroy() {
565
+ this.sub$.unsubscribe();
566
+ }
567
+ ngAfterViewInit() {
568
+ const spEntitiesList = this.spEntitiesList();
569
+ if (spEntitiesList) {
570
+ // Build contentColumnDefs using our component's content. Then add our own
571
+ // 'action' column definition to it. Then set this as the value of
572
+ // child SPMatEntityListComponent.contentColumnDef. This way we force
573
+ // SPMatEntityListComponent to use our component's any project MatColumnDef
574
+ // content in the final mat-table.
575
+ const clientColumnDefs = this.clientColumnDefs;
576
+ if (clientColumnDefs && spEntitiesList) {
577
+ // Note that we process any content projected matColumnDef first and
578
+ // our own internal content later. And when we process our own internal
579
+ // columns (for now only 'action'), it's not added if a column with that
580
+ // name has already been defined via content projection. This allows the
581
+ // clients to override even internal columns with their column defintion.
582
+ let contentColumnDefs = new Array();
583
+ clientColumnDefs.toArray().forEach((c) => contentColumnDefs.push(c));
584
+ this.componentColumns().forEach((ic) => {
585
+ if (!contentColumnDefs.find((c) => c.name === ic.name)) {
586
+ contentColumnDefs.push(ic);
587
+ }
588
+ });
589
+ spEntitiesList.contentColumnDefs = contentColumnDefs;
590
+ }
591
+ // This is a replication of SPMatEntityCrudList.ngAfterViewInit. That
592
+ // code is skipped as we declare set the _deferViewInit=true in
593
+ // sp-mat-entity-list declaration.
594
+ spEntitiesList.buildColumns();
595
+ spEntitiesList.setupSort();
596
+ spEntitiesList.loadMoreEntities();
597
+ }
598
+ }
599
+ /**
600
+ * If the create/edit entity form is active, it calls its registered
601
+ * canCancelEdit callback to determine if it's okay to cancel the edit.
602
+ * You can use this method from the host component's router guard to
603
+ * ensure that any changes made to the form are not accidentally lost by
604
+ * navigating away from the CRUD page.
605
+ *
606
+ * If your CRUD page has multiple sp-mat-entity-crud components, you have to
607
+ * implement the logic to call this method on the appropriate component.
608
+ *
609
+ * If the the create/edit form is not active, this method returns true.
610
+ * @returns
611
+ */
612
+ canDeactivate() {
613
+ if (this.createEditViewActive()) {
614
+ return this.canCancelEdit();
615
+ }
616
+ return true;
617
+ }
618
+ refresh() {
619
+ this.spEntitiesList()?.refresh();
620
+ }
621
+ closeCreateEdit(cancelled) {
622
+ this.createEditViewActive.set(false);
623
+ this.entityViewPaneActivated.emit({ activated: false, cancelled: !!cancelled, mode: 'edit' });
624
+ }
625
+ canCancelEdit() {
626
+ if (this.canCancelEditCallback) {
627
+ return this.canCancelEditCallback();
628
+ }
629
+ return true;
630
+ }
631
+ registerCanCancelEditCallback(callback) {
632
+ this.canCancelEditCallback = callback;
633
+ }
634
+ triggerEntityUpdate(entity) {
635
+ this.onUpdate(entity);
636
+ }
637
+ triggerEntityDelete(entity) {
638
+ this.onDelete(entity);
639
+ }
640
+ create(entityValue) {
641
+ let obs;
642
+ const crudOpFn = this.crudOpFn();
643
+ if (crudOpFn) {
644
+ obs = crudOpFn('create', undefined, entityValue, this);
645
+ }
646
+ else {
647
+ obs = this.http.post(this.getUrl(this.endpoint()), entityValue, {
648
+ context: this.getCrudReqHttpContext('create'),
649
+ });
650
+ }
651
+ return obs.pipe(showBusyWheelUntilComplete('formBusyWheel'), switchMap((resp) => resp ? this.doRefreshAfterEdit(resp, 'create') : of(null)), tap((entity) => {
652
+ // If pagination is infinite or if the pagination if none or if the
653
+ // count of items in the current page is less than pageSize()
654
+ // wec an safely add the item to the list, which will cause the view
655
+ // render the new item in the mat-table.
656
+ if (entity) {
657
+ this.spEntitiesList()?.addEntity(entity);
658
+ this.snackBar.open(this.crudConfig.i18n.createdItemNotification(this._itemLabel()));
659
+ }
660
+ }));
661
+ }
662
+ update(id, entityValue) {
663
+ let obs;
664
+ const crudOpFn = this.crudOpFn();
665
+ if (crudOpFn) {
666
+ obs = crudOpFn('update', id, entityValue, this);
667
+ }
668
+ else {
669
+ obs = this.http.patch(this.getEntityUrl(id), entityValue, {
670
+ context: this.getCrudReqHttpContext('update'),
671
+ });
672
+ }
673
+ return obs.pipe(showBusyWheelUntilComplete('formBusyWheel'), switchMap((resp) => resp ? this.doRefreshAfterEdit(resp, 'update') : of(null)), tap((entity) => {
674
+ if (entity) {
675
+ this.spEntitiesList()?.updateEntity(id, entity);
676
+ this.snackBar.open(this.crudConfig.i18n.updatedItemNotification(this._itemLabel()));
677
+ }
678
+ }));
679
+ }
680
+ /**
681
+ * Refresh the entity list, after a CRUD CREATE or UPDATE operation.
682
+ * @param resp This is the response from the CRUD operation (CREATE/UPDATE).
683
+ * @param method The CRUD operation post which REFRESH is requested.
684
+ * @returns Observable<TEntity|null>
685
+ */
686
+ doRefreshAfterEdit(resp, method) {
687
+ const refreshAfterEdit = this.refreshAfterEdit();
688
+ const entity = this.getCrudOpResponseParser()(this.entityName(), this.idKey(), method, resp);
689
+ if (refreshAfterEdit === 'object') {
690
+ let obs;
691
+ const crudOpFn = this.crudOpFn();
692
+ if (crudOpFn) {
693
+ obs = crudOpFn('get', entity[this.idKey()], undefined, this);
694
+ }
695
+ else {
696
+ obs = this.http.get(this.getEntityUrl(entity[this.idKey()]), { context: this.getCrudReqHttpContext('retrieve') });
697
+ }
698
+ return obs.pipe(map((entity) => {
699
+ return this.getCrudOpResponseParser()(this.entityName(), this.idKey(), 'retrieve', entity);
700
+ }));
701
+ }
702
+ else if (refreshAfterEdit === 'all') {
703
+ this.spEntitiesList()?.refresh();
704
+ return of(null);
705
+ }
706
+ return of(entity);
707
+ }
708
+ getCrudOpResponseParser() {
709
+ if (this.crudResponseParser()) {
710
+ // Without the `as SPMatEntityCrudResponseParser`, TSC will complain.
711
+ return this.crudResponseParser();
712
+ }
713
+ // crudConfig will have a parser as our default config provides one.
714
+ return this.crudConfig
715
+ .crudOpResponseParser;
716
+ }
717
+ // SPMatEntityCrudComponentBase interface method. Thunk to the implementation
718
+ // method closePreviewImpl().
719
+ closePreview() {
720
+ this.closePreviewImpl(true);
721
+ }
722
+ closePreviewImpl(toggleEntityListActiveEntity) {
723
+ if (this.previewedEntity()) {
724
+ if (toggleEntityListActiveEntity) {
725
+ this.spEntitiesList()?.toggleActiveEntity(this.previewedEntity());
726
+ }
727
+ this.previewedEntity.set(undefined);
728
+ this.entityViewPaneActivated.emit({ activated: false, cancelled: undefined, mode: 'preview' });
729
+ }
730
+ }
731
+ onItemAction(role, entity) {
732
+ if (role === '_update_') {
733
+ this.onUpdate(entity);
734
+ }
735
+ else if (role === '_delete_') {
736
+ this.onDelete(entity);
737
+ }
738
+ else {
739
+ this.action.emit({ role, entity });
740
+ }
741
+ }
742
+ onCreate(event) {
743
+ // If newItemLink() has not been provided, check if createEditFormTemplate
744
+ // is specified. If it is, load it and make that cover the entire viewport.
745
+ // If that too is not specified, emit an action event with role='_new_'.
746
+ if (!this.newItemLink() || this.newItemLink()?.length == 0) {
747
+ event.preventDefault();
748
+ event.stopImmediatePropagation();
749
+ const params = {
750
+ title: this.newItemLabel() ?? this.crudConfig.i18n.newItemLabel(this._itemLabel()),
751
+ };
752
+ this.showCreateEditView(undefined, params);
753
+ // const tmpl = this.createEditFormTemplate();
754
+ // if (tmpl) {
755
+ // // If preview is active deactivate it
756
+ // if (this.previewActive()) {
757
+ // this.closePreview();
758
+ // }
759
+ // const createEditHost = this.createEditHostComponent();
760
+ // createEditHost!.show(undefined);
761
+ // this.createEditViewActive.set(true);
762
+ // }
763
+ }
764
+ if (!this.createEditViewActive()) {
765
+ this.action.emit({ role: '_new_' });
766
+ }
767
+ }
768
+ onUpdate(entity) {
769
+ const params = {
770
+ title: this.editItemTitle() ?? this.crudConfig.i18n.editItemLabel(this._itemLabel()),
771
+ };
772
+ this.showCreateEditView(entity, params);
773
+ // const tmpl = this.createEditFormTemplate();
774
+ // if (tmpl) {
775
+ // // If preview is active deactivate it
776
+ // if (this.previewActive()) {
777
+ // this.closePreview();
778
+ // }
779
+ // const createEditHost = this.createEditHostComponent();
780
+ // if (tmpl && createEditHost) {
781
+ // createEditHost.show(entity);
782
+ // this.createEditViewActive.set(true);
783
+ // }
784
+ // }
785
+ if (!this.createEditViewActive()) {
786
+ this.action.emit({ role: '_update_' });
787
+ }
788
+ }
789
+ /**
790
+ * Show the create/edit component. This is deliberately made public so as to
791
+ * be callable from the client. This allows the client to dynamically
792
+ * set the form edit template and then show the edit pane by calling this
793
+ * method.
794
+ * @param entity
795
+ * @param params
796
+ */
797
+ showCreateEditView(entity, params) {
798
+ const tmpl = this.createEditFormTemplate();
799
+ if (!this.createEditViewActive() && !this.previewActive() && tmpl) {
800
+ // If preview is active deactivate it
801
+ if (this.previewActive()) {
802
+ this.closePreviewImpl(true);
803
+ }
804
+ const createEditHost = this.createEditHostComponent();
805
+ createEditHost.show(entity, params);
806
+ this.createEditViewActive.set(true);
807
+ this.entityViewPaneActivated.emit({ activated: true, cancelled: undefined, mode: 'edit' });
808
+ }
809
+ }
810
+ showPreviewView(entity, params) {
811
+ const tmpl = this.previewTemplate();
812
+ if (tmpl) {
813
+ if (!this.createEditViewActive()) {
814
+ const previewHost = this.previewHostComponent();
815
+ this.previewedEntity.set(entity);
816
+ previewHost?.show(entity, params);
817
+ this.entityViewPaneActivated.emit({ activated: true, cancelled: undefined, mode: 'preview' });
818
+ // this.previewActivated.emit({ entity, activated: true });
819
+ }
820
+ }
821
+ }
822
+ hidePreviewView() {
823
+ if (this.previewActive()) {
824
+ const previewHost = this.previewHostComponent();
825
+ previewHost?.close();
826
+ this.closePreviewImpl(false);
827
+ }
828
+ }
829
+ async onDelete(entity) {
830
+ // Do the delete prompt asynchronously so that the context menu is
831
+ // dismissed before the prompt is displayed.
832
+ setTimeout(() => {
833
+ const deletedItemPrompt = this.crudConfig.i18n.deleteItemMessage(this._itemLabel());
834
+ const yes = confirm(deletedItemPrompt);
835
+ if (yes) {
836
+ const entityId = entity[this.idKey()];
837
+ // If preview is active deactivate it
838
+ if (this.previewActive()) {
839
+ this.closePreviewImpl(false);
840
+ }
841
+ let obs;
842
+ const crudOpFn = this.crudOpFn();
843
+ if (crudOpFn) {
844
+ obs = crudOpFn('delete', entityId, undefined, this);
845
+ }
846
+ else {
847
+ obs = this.http.delete(this.getEntityUrl(entityId), {
848
+ context: this.getCrudReqHttpContext('delete'),
849
+ });
850
+ }
851
+ this.sub$.add(obs
852
+ .pipe(
853
+ // TODO: how to display a busy wheel?
854
+ // showBusyWheelUntilComplete(this.busyWheelId),
855
+ tap(() => {
856
+ this.spEntitiesList().removeEntity(entityId);
857
+ // TODO: customize by providing an interface via SPMatEntityCrudConfig?
858
+ const deletedMessage = this.crudConfig.i18n.itemDeletedNotification(this._itemLabel());
859
+ this.snackBar.open(deletedMessage);
860
+ }))
861
+ .subscribe());
862
+ }
863
+ });
864
+ }
865
+ getUrl(endpoint) {
866
+ return this.entityListConfig?.urlResolver
867
+ ? this.entityListConfig?.urlResolver(endpoint)
868
+ : endpoint;
869
+ }
870
+ getEntityUrl(entityId) {
871
+ const endpoint = this.endpoint();
872
+ const endpointParts = endpoint.split('?');
873
+ const entityEndpoint = (endpointParts[0].endsWith('/')
874
+ ? endpointParts[0]
875
+ : endpointParts[0] + '/') + `${String(entityId)}/`;
876
+ if (endpointParts.length > 1) {
877
+ return this.getUrl(entityEndpoint + `?${endpointParts[1]}`);
878
+ }
879
+ return this.getUrl(entityEndpoint);
880
+ }
881
+ handleSelectEntity(entity) {
882
+ if (!this.createEditViewActive()) {
883
+ if (this.previewTemplate()) {
884
+ entity ? this.showPreviewView(entity) : this.hidePreviewView();
885
+ // this.previewedEntity.set(entity);
886
+ }
887
+ else {
888
+ // If 'previewTemplate' is not set, propagate the event to client.
889
+ this.selectEntity.emit(entity);
890
+ }
891
+ }
892
+ }
893
+ handleNewItemSubType(subtype) {
894
+ // console.log(`handleNewItemSubType: ${subtype}`);
895
+ if (subtype.role === '_new_') {
896
+ this.showCreateEditView(undefined, subtype?.params);
897
+ }
898
+ else {
899
+ this.action.emit({ role: subtype.role });
900
+ }
901
+ }
902
+ getCrudReqHttpContext(op) {
903
+ const contextParamToHttpContext = (context, reqContext) => {
904
+ if (reqContext.length == 2 && !Array.isArray(reqContext[0])) {
905
+ // one dimensional array of a key, value pair.
906
+ context.set(reqContext[0], reqContext[1]);
907
+ }
908
+ else {
909
+ reqContext.forEach(([k, v]) => context.set(k, v));
910
+ }
911
+ };
912
+ let context = new HttpContext();
913
+ const crudHttpReqContext = this.crudHttpReqContext();
914
+ if (crudHttpReqContext) {
915
+ if (Array.isArray(crudHttpReqContext)) {
916
+ // Same HttpContext for all crud requests
917
+ contextParamToHttpContext(context, crudHttpReqContext);
918
+ }
919
+ else if (typeof crudHttpReqContext === 'object' &&
920
+ op &&
921
+ crudHttpReqContext[op]) {
922
+ contextParamToHttpContext(context, crudHttpReqContext[op]);
923
+ // if (crudHttpReqContext[op]) {
924
+ // context = contextParamToHttpContext(crudHttpReqContext[op] as any);
925
+ // } else if (crudHttpReqContext['crud']) {
926
+ // context = contextParamToHttpContext(crudHttpReqContext['crud'] as any);
927
+ // }
928
+ }
929
+ // } else if (this.httpReqContext()) {
930
+ // context = contextParamToHttpContext(this.httpReqContext()!);
931
+ }
932
+ context.set(SP_MAT_ENTITY_CRUD_HTTP_CONTEXT, {
933
+ entityName: this.entityName(),
934
+ entityNamePlural: this._entityNamePlural(),
935
+ endpoint: this.endpoint(),
936
+ op,
937
+ });
938
+ return context;
939
+ // const httpReqContext = this.httpReqContext();
940
+ // return httpReqContext
941
+ // ? contextParamToHttpContext(httpReqContext)
942
+ // : undefined;
943
+ }
944
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudComponent, deps: [{ token: i1$1.HttpClient }, { token: i2$1.MatSnackBar }, { token: i3.DomSanitizer }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
945
+ /** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.6", type: SPMatEntityCrudComponent, isStandalone: true, selector: "sp-mat-entity-crud", inputs: { itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemLabelPlural: { classPropertyName: "itemLabelPlural", publicName: "itemLabelPlural", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, itemActions: { classPropertyName: "itemActions", publicName: "itemActions", isSignal: true, isRequired: false, transformFunction: null }, newItemLink: { classPropertyName: "newItemLink", publicName: "newItemLink", isSignal: true, isRequired: false, transformFunction: null }, newItemLabel: { classPropertyName: "newItemLabel", publicName: "newItemLabel", isSignal: true, isRequired: false, transformFunction: null }, editItemTitle: { classPropertyName: "editItemTitle", publicName: "editItemTitle", isSignal: true, isRequired: false, transformFunction: null }, newItemSubTypes: { classPropertyName: "newItemSubTypes", publicName: "newItemSubTypes", isSignal: true, isRequired: false, transformFunction: null }, crudOpFn: { classPropertyName: "crudOpFn", publicName: "crudOpFn", isSignal: true, isRequired: false, transformFunction: null }, previewTemplate: { classPropertyName: "previewTemplate", publicName: "previewTemplate", isSignal: true, isRequired: false, transformFunction: null }, allowEntityActionFn: { classPropertyName: "allowEntityActionFn", publicName: "allowEntityActionFn", isSignal: true, isRequired: false, transformFunction: null }, headerTemplate: { classPropertyName: "headerTemplate", publicName: "headerTemplate", isSignal: true, isRequired: false, transformFunction: null }, actionsTemplate: { classPropertyName: "actionsTemplate", publicName: "actionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, crudResponseParser: { classPropertyName: "crudResponseParser", publicName: "crudResponseParser", isSignal: true, isRequired: false, transformFunction: null }, createEditFormTemplate: { classPropertyName: "createEditFormTemplate", publicName: "createEditFormTemplate", isSignal: true, isRequired: false, transformFunction: null }, disableItemActions: { classPropertyName: "disableItemActions", publicName: "disableItemActions", isSignal: true, isRequired: false, transformFunction: null }, disableCreate: { classPropertyName: "disableCreate", publicName: "disableCreate", isSignal: true, isRequired: false, transformFunction: null }, refreshAfterEdit: { classPropertyName: "refreshAfterEdit", publicName: "refreshAfterEdit", isSignal: true, isRequired: false, transformFunction: null }, crudHttpReqContext: { classPropertyName: "crudHttpReqContext", publicName: "crudHttpReqContext", isSignal: true, isRequired: false, transformFunction: null }, editPaneWidth: { classPropertyName: "editPaneWidth", publicName: "editPaneWidth", isSignal: true, isRequired: false, transformFunction: null }, previewPaneWidth: { classPropertyName: "previewPaneWidth", publicName: "previewPaneWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", entityViewPaneActivated: "entityViewPaneActivated" }, queries: [{ propertyName: "_clientColumnDefs", predicate: MatColumnDef }], viewQueries: [{ propertyName: "componentColumns", predicate: MatColumnDef, descendants: true, isSignal: true }, { propertyName: "spEntitiesList", first: true, predicate: (SPMatEntityListComponent), descendants: true, isSignal: true }, { propertyName: "createEditHostComponent", first: true, predicate: FormViewHostComponent, descendants: true, isSignal: true }, { propertyName: "previewHostComponent", first: true, predicate: PreviewHostComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
946
+ <as-split direction="horizontal" [gutterSize]="6">
947
+ <as-split-area [size]="entitiesPaneWidth()" [visible]="!entitiesPaneHidden()">
948
+ <div
949
+ [class]="crudConfig.listPaneWrapperClass"
950
+ >
951
+ <ng-content select="[breadCrumbs]"></ng-content>
952
+
953
+ <ng-template #defaultActionButtons>
954
+ <div class="action-bar-actions">
955
+ @if (!disableCreate()) { @if (newItemSubTypes()) {
956
+ <!-- New {{item}} displays a dropdown menu from which the subtype can be selected -->
957
+ <button
958
+ type="button"
959
+ mat-raised-button
960
+ color="primary"
961
+ [matMenuTriggerFor]="newSubTypesMenu"
962
+ >
963
+ {{
964
+ newItemLabel() ??
965
+ crudConfig.i18n.newItemLabel(this._itemLabel())
966
+ }}&nbsp;&#9660;
967
+ <!-- down arrow-head -->
968
+ </button>
969
+ <mat-menu #newSubTypesMenu="matMenu">
970
+ @for (subtype of newItemSubTypes(); track $index) { @if
971
+ (subtype.role) {
972
+ <button mat-menu-item (click)="handleNewItemSubType(subtype)">
973
+ {{ subtype.label }}
974
+ </button>
975
+ } @else {
976
+ <div style="padding: .2em 0.5em;">
977
+ <strong>{{ subtype.label }}</strong>
978
+ </div>
979
+ } }
980
+ </mat-menu>
981
+ } @else {
982
+ <button
983
+ mat-raised-button
984
+ color="primary"
985
+ (click)="onCreate($event)"
986
+ [routerLink]="newItemLink()"
987
+ >
988
+ {{
989
+ newItemLabel() ??
990
+ crudConfig.i18n.newItemLabel(this._itemLabel())
991
+ }}
992
+ </button>
993
+ } }
994
+ </div>
995
+ </ng-template>
996
+
997
+ <ng-template #defaultHeaderTemplate>
998
+ <div class="action-bar">
999
+ <div class="action-bar-title">
1000
+ {{ _title() }}
1001
+ </div>
1002
+ <span class="spacer"></span>
1003
+ <!-- Hide the action buttons when Preview/Edit pane is active -->
1004
+ @if (!entityPaneActive()) {
1005
+ <ng-container
1006
+ [ngTemplateOutlet]="actionsTemplate() || defaultActionButtons"
1007
+ ></ng-container>
1008
+ }
1009
+ </div>
1010
+ </ng-template>
1011
+ <ng-container
1012
+ [ngTemplateOutlet]="headerTemplate() || defaultHeaderTemplate"
1013
+ ></ng-container>
1014
+ <sp-mat-entity-list
1015
+ [entityName]="entityName()"
1016
+ [entityNamePlural]="entityNamePlural()"
1017
+ [_deferViewInit]="true"
1018
+ [endpoint]="endpoint()"
1019
+ [entityLoaderFn]="entityLoaderFn()"
1020
+ [columns]="columnsWithAction()"
1021
+ [displayedColumns]="visibleColumns()"
1022
+ [idKey]="idKey()"
1023
+ [pagination]="pagination()"
1024
+ [paginator]="paginator()"
1025
+ [pageSize]="pageSize()"
1026
+ [sorter]="sorter()"
1027
+ [disableSort]="disableSort()"
1028
+ (selectEntity)="handleSelectEntity($event)"
1029
+ [httpReqContext]="httpReqContext()"
1030
+ >
1031
+ </sp-mat-entity-list>
1032
+ </div>
1033
+
1034
+ <!--
1035
+ We'll be initializing the contentColumnDefs separately and not
1036
+ relying on <sp-mat-entity-list>'s internal @ContentChildre() querylist
1037
+ for building this. So we define this independenly and not as
1038
+ <sp-mat-entity-list> content.
1039
+ -->
1040
+ <ng-container matColumnDef="action">
1041
+ <th mat-header-cell *matHeaderCellDef></th>
1042
+ <td
1043
+ mat-cell
1044
+ *matCellDef="let element"
1045
+ (click)="$event.stopImmediatePropagation()"
1046
+ >
1047
+ @if (_itemActions().length) {
1048
+ <sp-mat-context-menu
1049
+ [menuItems]="_itemActions()"
1050
+ (selected)="onItemAction($event, element)"
1051
+ [contextData]="element"
1052
+ [hasBackdrop]="true"
1053
+ ></sp-mat-context-menu>
1054
+ }
1055
+ </td>
1056
+ </ng-container>
1057
+
1058
+ <!--
1059
+ <div
1060
+ [class]="crudConfig.listPaneWrapperClass"
1061
+ [ngStyle]="{ display: createEditViewActive() ? 'inherit' : 'none' }"
1062
+ spHostBusyWheel="formBusyWheel"
1063
+ >
1064
+ <ng-content select="[breadCrumbs]"></ng-content>
1065
+ <sp-create-edit-entity-host
1066
+ [itemLabel]="_itemLabel()"
1067
+ [itemLabelPlural]="_itemLabelPlural()"
1068
+ [entityCrudComponentBase]="this"
1069
+ [clientViewTemplate]="createEditFormTemplate()"
1070
+ ></sp-create-edit-entity-host>
1071
+ </div>
1072
+ -->
1073
+ </as-split-area>
1074
+ <as-split-area [size]="entityPaneWidth()" [visible]="entityPaneActive()">
1075
+ <div [class]="crudConfig.previewPaneWrapperClass" spHostBusyWheel="formBusyWheel">
1076
+ <sp-entity-crud-preview-host
1077
+ [ngClass]="createEditViewActive() ? 'd-none' : 'd-inherit'"
1078
+ [entityCrudComponentBase]="this"
1079
+ [clientViewTemplate]="previewTemplate()!"
1080
+ ></sp-entity-crud-preview-host>
1081
+ <!-- Create/Edit Entity -->
1082
+ <sp-create-edit-entity-host
1083
+ [ngClass]="createEditViewActive() ? 'd-inherit' : 'd-none'"
1084
+ [itemLabel]="_itemLabel()"
1085
+ [itemLabelPlural]="_itemLabelPlural()"
1086
+ [entityCrudComponentBase]="this"
1087
+ [clientViewTemplate]="createEditFormTemplate()"
1088
+ ></sp-create-edit-entity-host>
1089
+ </div>
1090
+ </as-split-area>
1091
+ </as-split>
1092
+ `, isInline: true, styles: [".d-none{display:none}.d-inherit{display:inherit}.action-bar{display:flex;flex-direction:row;align-items:center;padding-bottom:.5em}.action-bar-title{font-size:1.5em;font-weight:600}.spacer{flex-grow:1}.action-bar-actions{text-align:end}.active-row{font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i5.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "directive", type: i7.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i7.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i7.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i7.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i7.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "ngmodule", type: MatSortModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i8.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i8.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: AngularSplitModule }, { kind: "component", type: i9.SplitComponent, selector: "as-split", inputs: ["gutterSize", "gutterStep", "disabled", "gutterClickDeltaPx", "direction", "dir", "unit", "gutterAriaLabel", "restrictMove", "useTransition", "gutterDblClickDuration"], outputs: ["gutterClick", "gutterDblClick", "dragStart", "dragEnd", "transitionEnd"], exportAs: ["asSplit"] }, { kind: "component", type: i9.SplitAreaComponent, selector: "as-split-area", inputs: ["size", "minSize", "maxSize", "lockSize", "visible"], exportAs: ["asSplitArea"] }, { kind: "component", type: SPMatEntityListComponent, selector: "sp-mat-entity-list", inputs: ["entityName", "entityNamePlural", "endpoint", "entityLoaderFn", "columns", "displayedColumns", "pageSize", "idKey", "pagination", "paginator", "sorter", "disableSort", "infiniteScrollContainer", "infiniteScrollDistance", "infiniteScrollThrottle", "infiniteScrollWindow", "httpReqContext", "_deferViewInit"], outputs: ["selectEntity"] }, { kind: "component", type: SPMatContextMenuComponent, selector: "sp-mat-context-menu", inputs: ["menuItems", "label", "menuIconName", "enableHover", "contextData", "hasBackdrop"], outputs: ["selected"] }, { kind: "component", type: FormViewHostComponent, selector: "sp-create-edit-entity-host", inputs: ["entityCrudComponentBase", "clientViewTemplate", "itemLabel", "itemLabelPlural"] }, { kind: "directive", type: SPMatHostBusyWheelDirective, selector: "[spHostBusyWheel]", inputs: ["spHostBusyWheel"] }, { kind: "component", type: PreviewHostComponent, selector: "sp-entity-crud-preview-host", inputs: ["entityCrudComponentBase", "clientViewTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1093
+ }
1094
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudComponent, decorators: [{
1095
+ type: Component,
1096
+ args: [{ imports: [
1097
+ CommonModule,
1098
+ RouterModule,
1099
+ MatButtonModule,
1100
+ MatTableModule,
1101
+ MatSortModule,
1102
+ MatMenuModule,
1103
+ MatSnackBarModule,
1104
+ AngularSplitModule,
1105
+ SPMatEntityListComponent,
1106
+ SPMatContextMenuComponent,
1107
+ FormViewHostComponent,
1108
+ SPMatHostBusyWheelDirective,
1109
+ PreviewHostComponent,
1110
+ ], selector: 'sp-mat-entity-crud', template: `
1111
+ <as-split direction="horizontal" [gutterSize]="6">
1112
+ <as-split-area [size]="entitiesPaneWidth()" [visible]="!entitiesPaneHidden()">
1113
+ <div
1114
+ [class]="crudConfig.listPaneWrapperClass"
1115
+ >
1116
+ <ng-content select="[breadCrumbs]"></ng-content>
1117
+
1118
+ <ng-template #defaultActionButtons>
1119
+ <div class="action-bar-actions">
1120
+ @if (!disableCreate()) { @if (newItemSubTypes()) {
1121
+ <!-- New {{item}} displays a dropdown menu from which the subtype can be selected -->
1122
+ <button
1123
+ type="button"
1124
+ mat-raised-button
1125
+ color="primary"
1126
+ [matMenuTriggerFor]="newSubTypesMenu"
1127
+ >
1128
+ {{
1129
+ newItemLabel() ??
1130
+ crudConfig.i18n.newItemLabel(this._itemLabel())
1131
+ }}&nbsp;&#9660;
1132
+ <!-- down arrow-head -->
1133
+ </button>
1134
+ <mat-menu #newSubTypesMenu="matMenu">
1135
+ @for (subtype of newItemSubTypes(); track $index) { @if
1136
+ (subtype.role) {
1137
+ <button mat-menu-item (click)="handleNewItemSubType(subtype)">
1138
+ {{ subtype.label }}
1139
+ </button>
1140
+ } @else {
1141
+ <div style="padding: .2em 0.5em;">
1142
+ <strong>{{ subtype.label }}</strong>
1143
+ </div>
1144
+ } }
1145
+ </mat-menu>
1146
+ } @else {
1147
+ <button
1148
+ mat-raised-button
1149
+ color="primary"
1150
+ (click)="onCreate($event)"
1151
+ [routerLink]="newItemLink()"
1152
+ >
1153
+ {{
1154
+ newItemLabel() ??
1155
+ crudConfig.i18n.newItemLabel(this._itemLabel())
1156
+ }}
1157
+ </button>
1158
+ } }
1159
+ </div>
1160
+ </ng-template>
1161
+
1162
+ <ng-template #defaultHeaderTemplate>
1163
+ <div class="action-bar">
1164
+ <div class="action-bar-title">
1165
+ {{ _title() }}
1166
+ </div>
1167
+ <span class="spacer"></span>
1168
+ <!-- Hide the action buttons when Preview/Edit pane is active -->
1169
+ @if (!entityPaneActive()) {
1170
+ <ng-container
1171
+ [ngTemplateOutlet]="actionsTemplate() || defaultActionButtons"
1172
+ ></ng-container>
1173
+ }
1174
+ </div>
1175
+ </ng-template>
1176
+ <ng-container
1177
+ [ngTemplateOutlet]="headerTemplate() || defaultHeaderTemplate"
1178
+ ></ng-container>
1179
+ <sp-mat-entity-list
1180
+ [entityName]="entityName()"
1181
+ [entityNamePlural]="entityNamePlural()"
1182
+ [_deferViewInit]="true"
1183
+ [endpoint]="endpoint()"
1184
+ [entityLoaderFn]="entityLoaderFn()"
1185
+ [columns]="columnsWithAction()"
1186
+ [displayedColumns]="visibleColumns()"
1187
+ [idKey]="idKey()"
1188
+ [pagination]="pagination()"
1189
+ [paginator]="paginator()"
1190
+ [pageSize]="pageSize()"
1191
+ [sorter]="sorter()"
1192
+ [disableSort]="disableSort()"
1193
+ (selectEntity)="handleSelectEntity($event)"
1194
+ [httpReqContext]="httpReqContext()"
1195
+ >
1196
+ </sp-mat-entity-list>
1197
+ </div>
1198
+
1199
+ <!--
1200
+ We'll be initializing the contentColumnDefs separately and not
1201
+ relying on <sp-mat-entity-list>'s internal @ContentChildre() querylist
1202
+ for building this. So we define this independenly and not as
1203
+ <sp-mat-entity-list> content.
1204
+ -->
1205
+ <ng-container matColumnDef="action">
1206
+ <th mat-header-cell *matHeaderCellDef></th>
1207
+ <td
1208
+ mat-cell
1209
+ *matCellDef="let element"
1210
+ (click)="$event.stopImmediatePropagation()"
1211
+ >
1212
+ @if (_itemActions().length) {
1213
+ <sp-mat-context-menu
1214
+ [menuItems]="_itemActions()"
1215
+ (selected)="onItemAction($event, element)"
1216
+ [contextData]="element"
1217
+ [hasBackdrop]="true"
1218
+ ></sp-mat-context-menu>
1219
+ }
1220
+ </td>
1221
+ </ng-container>
1222
+
1223
+ <!--
1224
+ <div
1225
+ [class]="crudConfig.listPaneWrapperClass"
1226
+ [ngStyle]="{ display: createEditViewActive() ? 'inherit' : 'none' }"
1227
+ spHostBusyWheel="formBusyWheel"
1228
+ >
1229
+ <ng-content select="[breadCrumbs]"></ng-content>
1230
+ <sp-create-edit-entity-host
1231
+ [itemLabel]="_itemLabel()"
1232
+ [itemLabelPlural]="_itemLabelPlural()"
1233
+ [entityCrudComponentBase]="this"
1234
+ [clientViewTemplate]="createEditFormTemplate()"
1235
+ ></sp-create-edit-entity-host>
1236
+ </div>
1237
+ -->
1238
+ </as-split-area>
1239
+ <as-split-area [size]="entityPaneWidth()" [visible]="entityPaneActive()">
1240
+ <div [class]="crudConfig.previewPaneWrapperClass" spHostBusyWheel="formBusyWheel">
1241
+ <sp-entity-crud-preview-host
1242
+ [ngClass]="createEditViewActive() ? 'd-none' : 'd-inherit'"
1243
+ [entityCrudComponentBase]="this"
1244
+ [clientViewTemplate]="previewTemplate()!"
1245
+ ></sp-entity-crud-preview-host>
1246
+ <!-- Create/Edit Entity -->
1247
+ <sp-create-edit-entity-host
1248
+ [ngClass]="createEditViewActive() ? 'd-inherit' : 'd-none'"
1249
+ [itemLabel]="_itemLabel()"
1250
+ [itemLabelPlural]="_itemLabelPlural()"
1251
+ [entityCrudComponentBase]="this"
1252
+ [clientViewTemplate]="createEditFormTemplate()"
1253
+ ></sp-create-edit-entity-host>
1254
+ </div>
1255
+ </as-split-area>
1256
+ </as-split>
1257
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".d-none{display:none}.d-inherit{display:inherit}.action-bar{display:flex;flex-direction:row;align-items:center;padding-bottom:.5em}.action-bar-title{font-size:1.5em;font-weight:600}.spacer{flex-grow:1}.action-bar-actions{text-align:end}.active-row{font-weight:700}\n"] }]
1258
+ }], ctorParameters: () => [{ type: i1$1.HttpClient }, { type: i2$1.MatSnackBar }, { type: i3.DomSanitizer }, { type: i0.Injector }], propDecorators: { _clientColumnDefs: [{
1259
+ type: ContentChildren,
1260
+ args: [MatColumnDef]
1261
+ }], action: [{
1262
+ type: Output
1263
+ }], entityViewPaneActivated: [{
1264
+ type: Output
1265
+ }] } });
1266
+
1267
+ /**
1268
+ * A preview pane container to provide a consistent UX for all preview panes.
1269
+ * It consits of a toolbar on the top and a container div below that takes up
1270
+ * the rest of the preview pane area.
1271
+ */
1272
+ class SPMatEntityCrudPreviewPaneComponent {
1273
+ entity = input.required();
1274
+ entityCrudComponent = input.required();
1275
+ title = input();
1276
+ disableUpdate = input(false);
1277
+ hideUpdate = input(false);
1278
+ disableDelete = input(false);
1279
+ hideDelete = input(false);
1280
+ config;
1281
+ constructor() {
1282
+ this.config = getEntityCrudConfig();
1283
+ }
1284
+ ngOnInit() {
1285
+ }
1286
+ ngOnDestroy() {
1287
+ }
1288
+ onEdit() {
1289
+ this.entityCrudComponent().triggerEntityUpdate(this.entity());
1290
+ }
1291
+ onDelete() {
1292
+ this.entityCrudComponent().triggerEntityDelete(this.entity());
1293
+ }
1294
+ onClose() {
1295
+ this.entityCrudComponent().closePreview();
1296
+ }
1297
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudPreviewPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1298
+ /** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.6", type: SPMatEntityCrudPreviewPaneComponent, isStandalone: true, selector: "sp-mat-entity-crud-preview-pane", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityCrudComponent: { classPropertyName: "entityCrudComponent", publicName: "entityCrudComponent", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, disableUpdate: { classPropertyName: "disableUpdate", publicName: "disableUpdate", isSignal: true, isRequired: false, transformFunction: null }, hideUpdate: { classPropertyName: "hideUpdate", publicName: "hideUpdate", isSignal: true, isRequired: false, transformFunction: null }, disableDelete: { classPropertyName: "disableDelete", publicName: "disableDelete", isSignal: true, isRequired: false, transformFunction: null }, hideDelete: { classPropertyName: "hideDelete", publicName: "hideDelete", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1299
+ <div class="preview-wrapper">
1300
+ <mat-toolbar>
1301
+ <mat-toolbar-row>
1302
+ @if (title()) {
1303
+ <h2>{{ title() }}</h2>&nbsp;
1304
+ }
1305
+ @if (!hideUpdate()) {
1306
+ <button mat-icon-button aria-label="Edit" (click)="onEdit()" [disabled]="disableUpdate()">
1307
+ <mat-icon>edit</mat-icon>
1308
+ </button>
1309
+ }
1310
+ @if (!hideDelete()) {
1311
+ <button mat-icon-button aria-label="Delete" (click)="onDelete()" [disabled]="disableDelete()">
1312
+ <mat-icon>delete</mat-icon>
1313
+ </button>
1314
+ }
1315
+ <ng-content select="[previewToolbarContent]"></ng-content>
1316
+ <span class="spacer"></span>
1317
+ <button mat-icon-button aria-label="Close" (click)="onClose()">
1318
+ <mat-icon>close</mat-icon>
1319
+ </button>
1320
+ </mat-toolbar-row>
1321
+ </mat-toolbar>
1322
+ <div [class]="'preview-content ' + (config.previewPaneContentClass ?? '')">
1323
+ <ng-content select="[previewContent]"></ng-content>
1324
+ </div>
1325
+ </div>
1326
+ `, isInline: true, styles: [".preview-wrapper{display:flex;flex-direction:column;height:100%}.spacer{flex:1 1 auto}.preview-content{flex-grow:1;overflow:scroll}\n"], dependencies: [{ kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i1$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "directive", type: i1$2.MatToolbarRow, selector: "mat-toolbar-row", exportAs: ["matToolbarRow"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1327
+ }
1328
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudPreviewPaneComponent, decorators: [{
1329
+ type: Component,
1330
+ args: [{ imports: [MatToolbarModule, MatButtonModule, MatIconModule], selector: 'sp-mat-entity-crud-preview-pane', template: `
1331
+ <div class="preview-wrapper">
1332
+ <mat-toolbar>
1333
+ <mat-toolbar-row>
1334
+ @if (title()) {
1335
+ <h2>{{ title() }}</h2>&nbsp;
1336
+ }
1337
+ @if (!hideUpdate()) {
1338
+ <button mat-icon-button aria-label="Edit" (click)="onEdit()" [disabled]="disableUpdate()">
1339
+ <mat-icon>edit</mat-icon>
1340
+ </button>
1341
+ }
1342
+ @if (!hideDelete()) {
1343
+ <button mat-icon-button aria-label="Delete" (click)="onDelete()" [disabled]="disableDelete()">
1344
+ <mat-icon>delete</mat-icon>
1345
+ </button>
1346
+ }
1347
+ <ng-content select="[previewToolbarContent]"></ng-content>
1348
+ <span class="spacer"></span>
1349
+ <button mat-icon-button aria-label="Close" (click)="onClose()">
1350
+ <mat-icon>close</mat-icon>
1351
+ </button>
1352
+ </mat-toolbar-row>
1353
+ </mat-toolbar>
1354
+ <div [class]="'preview-content ' + (config.previewPaneContentClass ?? '')">
1355
+ <ng-content select="[previewContent]"></ng-content>
1356
+ </div>
1357
+ </div>
1358
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".preview-wrapper{display:flex;flex-direction:column;height:100%}.spacer{flex:1 1 auto}.preview-content{flex-grow:1;overflow:scroll}\n"] }]
1359
+ }], ctorParameters: () => [] });
1360
+
1361
+ /**
1362
+ * This is a convenience base class that clients can derive from to implement
1363
+ * their CRUD form component. Particularly this class registers the change
1364
+ * detection hook which will be called when the user attempts to close the
1365
+ * form's parent container pane via the Close button on the top right.
1366
+ *
1367
+ * This button behaves like a Cancel button in a desktop app and therefore if
1368
+ * the user has entered any data in the form's controls, (determined by
1369
+ * checking form.touched), then a 'Lose Changes' prompt is displayed allowing
1370
+ * the user to cancel the closure.
1371
+ *
1372
+ * The @Component is fake just to keep the VSCode angular linter quiet.
1373
+ *
1374
+ * To use this class:-
1375
+ *
1376
+ * 1. Declare a FormGroup<> type as
1377
+ *
1378
+ * ```
1379
+ * type MyForm = FormGroup<{
1380
+ * name: FormControl<string>;
1381
+ * type: FormControl<string>;
1382
+ * notes: FormControl<string>;
1383
+ * }>;
1384
+ * ```
1385
+ *
1386
+ * 2. Derive your form's component class from this and implement the
1387
+ * createForm() method returing the FormGroup<> instance that matches
1388
+ * the FormGroup concrete type above.
1389
+ *
1390
+ * ```
1391
+ * class MyFormComponent extends SPMatEntityCrudFormBase<MyForm, MyEntity> {
1392
+ * constructor() {
1393
+ * super()
1394
+ * }
1395
+ *
1396
+ * createForm() {
1397
+ * return new FormGroup([...])
1398
+ * }
1399
+ * }
1400
+ * ```
1401
+ *
1402
+ * 3. If you form's value requires manipulation before being sent to the
1403
+ * server, override getFormValue() method and do it there before returning
1404
+ * the modified values.
1405
+ *
1406
+ * 4. Wire up the form in the template as:
1407
+ *
1408
+ * ```
1409
+ * <form [formGroup]='form'.. (ngSubmit)="onSubmit()">
1410
+ * <button type="submit">Submit</button>
1411
+ * </form>
1412
+ * ```
1413
+ */
1414
+ class SPMatEntityCrudFormBase {
1415
+ _form = signal(undefined);
1416
+ entity = input.required();
1417
+ bridge = input.required();
1418
+ params = input();
1419
+ sub$ = new Subscription();
1420
+ form = computed(() => this._form());
1421
+ crudConfig = getEntityCrudConfig();
1422
+ cdr = inject(ChangeDetectorRef);
1423
+ canCancelEdit = () => {
1424
+ return this._canCancelEdit();
1425
+ };
1426
+ _canCancelEdit() {
1427
+ const form = this._form();
1428
+ if (form && form.touched) {
1429
+ return window.confirm(this.crudConfig.i18n.loseChangesPrompt);
1430
+ }
1431
+ return true;
1432
+ }
1433
+ ngOnInit() {
1434
+ this._form.set(this.createForm(this.entity()));
1435
+ this.bridge()?.registerCanCancelEditCallback(this.canCancelEdit);
1436
+ }
1437
+ ngOnDestroy() {
1438
+ this.sub$.unsubscribe();
1439
+ }
1440
+ /**
1441
+ * Override to customize the id key name if it's not 'id'
1442
+ * @returns The name of the unique identifier key that will be used to
1443
+ * extract the entity's id for UPDATE operation.
1444
+ */
1445
+ getIdKey() {
1446
+ return 'id';
1447
+ }
1448
+ /**
1449
+ * Return the form's value to be sent to server as Create/Update CRUD
1450
+ * operation data.
1451
+ * @returns
1452
+ */
1453
+ getFormValue() {
1454
+ const form = this.form();
1455
+ return form ? form.value : undefined;
1456
+ }
1457
+ onSubmit() {
1458
+ const value = this.getFormValue();
1459
+ const obs = !this.entity()
1460
+ ? this.bridge()?.create(value)
1461
+ : this.bridge()?.update(this.entity()[this.getIdKey()], value);
1462
+ this.sub$.add(obs
1463
+ ?.pipe(setServerErrorsAsFormErrors(this._form, this.cdr), tap$1((res) => {
1464
+ if (res) {
1465
+ // this.bridge()?.close();
1466
+ }
1467
+ }))
1468
+ .subscribe());
1469
+ }
1470
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
1471
+ /** @nocollapse */ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.1.6", type: SPMatEntityCrudFormBase, isStandalone: false, selector: "_#_sp-mat-entity-crud-form-base_#_", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, bridge: { classPropertyName: "bridge", publicName: "bridge", isSignal: true, isRequired: true, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ``, isInline: true });
1472
+ }
1473
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: SPMatEntityCrudFormBase, decorators: [{
1474
+ type: Component,
1475
+ args: [{
1476
+ selector: '_#_sp-mat-entity-crud-form-base_#_', template: ``,
1477
+ standalone: false
1478
+ }]
1479
+ }] });
1480
+
1481
+ /**
1482
+ * Generated bundle index. Do not edit.
1483
+ */
1484
+
1485
+ export { SPMatEntityCrudComponent, SPMatEntityCrudFormBase, SPMatEntityCrudPreviewPaneComponent, SP_MAT_ENTITY_CRUD_CONFIG, SP_MAT_ENTITY_CRUD_HTTP_CONTEXT };
1486
+ //# sourceMappingURL=smallpearl-ngx-helper-mat-entity-crud.mjs.map