@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.
- package/README.md +230 -0
- package/core/index.d.ts +2 -0
- package/core/src/ngx-helper.d.ts +7 -0
- package/core/src/version.d.ts +1 -0
- package/entity-field/index.d.ts +2 -0
- package/entity-field/src/entity-field-spec.d.ts +69 -0
- package/entity-field/src/provider.d.ts +27 -0
- package/fesm2022/smallpearl-ngx-helper-core.mjs +23 -0
- package/fesm2022/smallpearl-ngx-helper-core.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-entity-field.mjs +112 -0
- package/fesm2022/smallpearl-ngx-helper-entity-field.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-forms.mjs +112 -0
- package/fesm2022/smallpearl-ngx-helper-forms.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-hover-dropdown.mjs +108 -0
- package/fesm2022/smallpearl-ngx-helper-hover-dropdown.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-locale.mjs +296 -0
- package/fesm2022/smallpearl-ngx-helper-locale.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-busy-wheel.mjs +504 -0
- package/fesm2022/smallpearl-ngx-helper-mat-busy-wheel.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-context-menu.mjs +184 -0
- package/fesm2022/smallpearl-ngx-helper-mat-context-menu.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs +1486 -0
- package/fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-entity-list.mjs +800 -0
- package/fesm2022/smallpearl-ngx-helper-mat-entity-list.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-file-input.mjs +328 -0
- package/fesm2022/smallpearl-ngx-helper-mat-file-input.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-form-error.mjs +468 -0
- package/fesm2022/smallpearl-ngx-helper-mat-form-error.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-select-entity.mjs +854 -0
- package/fesm2022/smallpearl-ngx-helper-mat-select-entity.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-side-menu-layout.mjs +930 -0
- package/fesm2022/smallpearl-ngx-helper-mat-side-menu-layout.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-mat-tel-input.mjs +926 -0
- package/fesm2022/smallpearl-ngx-helper-mat-tel-input.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-sideload.mjs +111 -0
- package/fesm2022/smallpearl-ngx-helper-sideload.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper-stationary-with-line-items.mjs +384 -0
- package/fesm2022/smallpearl-ngx-helper-stationary-with-line-items.mjs.map +1 -0
- package/fesm2022/smallpearl-ngx-helper.mjs +13 -0
- package/fesm2022/smallpearl-ngx-helper.mjs.map +1 -0
- package/forms/index.d.ts +1 -0
- package/forms/src/validation-error-handler.d.ts +52 -0
- package/hover-dropdown/index.d.ts +1 -0
- package/hover-dropdown/src/hover-dropdown.directive.d.ts +41 -0
- package/index.d.ts +5 -0
- package/locale/index.d.ts +5 -0
- package/locale/src/currency.pipe.d.ts +14 -0
- package/locale/src/date.pipe.d.ts +14 -0
- package/locale/src/format-currency.d.ts +1 -0
- package/locale/src/format-date.d.ts +2 -0
- package/locale/src/is-empty.d.ts +1 -0
- package/locale/src/providers.d.ts +20 -0
- package/mat-busy-wheel/index.d.ts +4 -0
- package/mat-busy-wheel/src/busy-wheel-op.d.ts +65 -0
- package/mat-busy-wheel/src/busy-wheel.component.d.ts +12 -0
- package/mat-busy-wheel/src/busy-wheel.service.d.ts +42 -0
- package/mat-busy-wheel/src/host-busy-wheel.directive.d.ts +35 -0
- package/mat-context-menu/index.d.ts +1 -0
- package/mat-context-menu/src/mat-context-menu.component.d.ts +54 -0
- package/mat-entity-crud/index.d.ts +5 -0
- package/mat-entity-crud/src/default-config.d.ts +9 -0
- package/mat-entity-crud/src/form-view-host.component.d.ts +34 -0
- package/mat-entity-crud/src/mat-entity-crud-form-base.d.ts +95 -0
- package/mat-entity-crud/src/mat-entity-crud-internal-types.d.ts +66 -0
- package/mat-entity-crud/src/mat-entity-crud-types.d.ts +141 -0
- package/mat-entity-crud/src/mat-entity-crud.component.d.ts +267 -0
- package/mat-entity-crud/src/preview-host.component.d.ts +19 -0
- package/mat-entity-crud/src/preview-pane.component.d.ts +27 -0
- package/mat-entity-crud/src/providers.d.ts +3 -0
- package/mat-entity-list/index.d.ts +3 -0
- package/mat-entity-list/src/config.d.ts +6 -0
- package/mat-entity-list/src/mat-entity-list-types.d.ts +53 -0
- package/mat-entity-list/src/mat-entity-list.component.d.ts +209 -0
- package/mat-entity-list/src/providers.d.ts +3 -0
- package/mat-file-input/README.md +63 -0
- package/mat-file-input/index.d.ts +1 -0
- package/mat-file-input/src/mat-file-input.component.d.ts +58 -0
- package/mat-form-error/README.md +306 -0
- package/mat-form-error/index.d.ts +6 -0
- package/mat-form-error/src/locales/en.d.ts +4 -0
- package/mat-form-error/src/locales/hu.d.ts +4 -0
- package/mat-form-error/src/locales/index.d.ts +3 -0
- package/mat-form-error/src/locales/pt-br.d.ts +4 -0
- package/mat-form-error/src/ngx-error-list.component.d.ts +9 -0
- package/mat-form-error/src/ngx-mat-error-control.d.ts +17 -0
- package/mat-form-error/src/ngx-mat-error-def.directive.d.ts +30 -0
- package/mat-form-error/src/ngx-mat-errors-for-date-range-picker.directive.d.ts +8 -0
- package/mat-form-error/src/ngx-mat-errors.component.d.ts +23 -0
- package/mat-form-error/src/types.d.ts +68 -0
- package/mat-form-error/src/utils/coerce-to-observable.d.ts +3 -0
- package/mat-form-error/src/utils/distinct-until-error-changed.d.ts +2 -0
- package/mat-form-error/src/utils/find-error-for-control.d.ts +9 -0
- package/mat-form-error/src/utils/get-abstract-controls.d.ts +3 -0
- package/mat-form-error/src/utils/get-control-with-error.d.ts +3 -0
- package/mat-select-entity/index.d.ts +2 -0
- package/mat-select-entity/src/mat-select-entity.component.d.ts +207 -0
- package/mat-select-entity/src/providers.d.ts +9 -0
- package/mat-side-menu-layout/index.d.ts +6 -0
- package/mat-side-menu-layout/src/layout.service.d.ts +23 -0
- package/mat-side-menu-layout/src/mat-menu-layout.component.d.ts +39 -0
- package/mat-side-menu-layout/src/mat-menu-layout.module.d.ts +18 -0
- package/mat-side-menu-layout/src/mat-menu-list-item.component.d.ts +36 -0
- package/mat-side-menu-layout/src/mat-menu-pane.component.d.ts +66 -0
- package/mat-side-menu-layout/src/nav-item.d.ts +10 -0
- package/mat-tel-input/README.md +18 -0
- package/mat-tel-input/index.d.ts +2 -0
- package/mat-tel-input/src/country-codes.d.ts +5 -0
- package/mat-tel-input/src/mat-telephone.component.d.ts +129 -0
- package/mat-tel-input/src/providers.d.ts +38 -0
- package/ngx-helper.d.ts +2 -0
- package/package.json +114 -0
- package/public-api.d.ts +1 -0
- package/sideload/index.d.ts +1 -0
- package/sideload/src/sideload.d.ts +17 -0
- package/stationary-with-line-items/index.d.ts +1 -0
- 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
|
+
}} ▼
|
|
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
|
+
}} ▼
|
|
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>
|
|
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>
|
|
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
|