@memberjunction/ng-explorer-core 5.37.0 → 5.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/lazy-feature-config.d.ts +1 -1
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +4 -2
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +10 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.js +15 -5
- package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.js +22 -6
- package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +12 -0
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +24 -5
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/component-cache-manager.js +58 -14
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +42 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +195 -12
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +5 -4
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +20 -9
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +31 -37
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +64 -306
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/package.json +46 -45
|
@@ -1,49 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ActivatedRoute } from '@angular/router';
|
|
1
|
+
import { EventEmitter } from '@angular/core';
|
|
3
2
|
import { CompositeKey, BaseEntity } from '@memberjunction/core';
|
|
4
|
-
import {
|
|
3
|
+
import { FormNavigationEvent, FormNotificationEvent } from '@memberjunction/ng-base-forms';
|
|
5
4
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
6
5
|
import * as i0 from "@angular/core";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Explorer-side host for a single entity record in the main tab area.
|
|
8
|
+
*
|
|
9
|
+
* This is now a **thin wrapper** around the Generic `<mj-entity-form-host>`
|
|
10
|
+
* (in `@memberjunction/ng-base-forms`), which owns all the mechanics: resolving
|
|
11
|
+
* the form (class / custom / interactive override + variants), loading the
|
|
12
|
+
* record, dynamically creating the form, binding it, and tearing it down.
|
|
13
|
+
*
|
|
14
|
+
* SingleRecordComponent's only remaining job is the **Explorer mapping**:
|
|
15
|
+
* translating the host's framework-agnostic events into Explorer services —
|
|
16
|
+
* `Navigate` → {@link NavigationService}, `Notification` → {@link SharedService},
|
|
17
|
+
* record loads → {@link RecentAccessService} — none of which belong in a Generic
|
|
18
|
+
* component.
|
|
19
|
+
*/
|
|
20
|
+
export declare class SingleRecordComponent extends BaseAngularComponent {
|
|
10
21
|
PrimaryKey: CompositeKey;
|
|
11
22
|
entityName: string | null;
|
|
12
23
|
newRecordValues: string | Record<string, unknown> | null;
|
|
13
|
-
loadComplete: EventEmitter<
|
|
24
|
+
loadComplete: EventEmitter<void>;
|
|
14
25
|
recordSaved: EventEmitter<BaseEntity>;
|
|
15
|
-
|
|
26
|
+
/** Emitted when the hosted form asks to be dismissed (e.g. Discard on a new record). */
|
|
27
|
+
recordDismissed: EventEmitter<void>;
|
|
16
28
|
private navigationService;
|
|
17
29
|
private sharedService;
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
private _formEventSubscriptions;
|
|
29
|
-
ngOnInit(): void;
|
|
30
|
-
ngAfterViewInit(): void;
|
|
31
|
-
LoadForm(primaryKey: CompositeKey, entityName: string): Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
* Render a user-visible error state inside the record pane AND log a structured
|
|
34
|
-
* console.error for developers. Always emits `loadComplete` so the Explorer shell
|
|
35
|
-
* does not hang on its first-resource-load gate.
|
|
36
|
-
*/
|
|
37
|
-
private failWithUserError;
|
|
38
|
-
protected SetNewRecordValues(record: BaseEntity): void;
|
|
39
|
-
/**
|
|
40
|
-
* Subscribe to BaseFormComponent @Output events and map them to Explorer services.
|
|
41
|
-
*/
|
|
42
|
-
private subscribeToFormEvents;
|
|
43
|
-
private handleNavigation;
|
|
44
|
-
private cleanupFormSubscriptions;
|
|
45
|
-
ngOnDestroy(): void;
|
|
30
|
+
private formPresenter;
|
|
31
|
+
private recentAccessService;
|
|
32
|
+
/** Unblock the shell's first-resource-load gate (success or error). */
|
|
33
|
+
onLoadComplete(): void;
|
|
34
|
+
/** Log access for existing records once the form's record is ready. */
|
|
35
|
+
onRecordReady(record: BaseEntity): void;
|
|
36
|
+
onSaved(record: BaseEntity): void;
|
|
37
|
+
onNotification(event: FormNotificationEvent): void;
|
|
38
|
+
/** Map the form's navigation requests onto Explorer's NavigationService. */
|
|
39
|
+
handleNavigation(event: FormNavigationEvent): void;
|
|
46
40
|
static ɵfac: i0.ɵɵFactoryDeclaration<SingleRecordComponent, never>;
|
|
47
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<SingleRecordComponent, "mj-single-record", never, { "PrimaryKey": { "alias": "PrimaryKey"; "required": false; }; "entityName": { "alias": "entityName"; "required": false; }; "newRecordValues": { "alias": "newRecordValues"; "required": false; }; }, { "loadComplete": "loadComplete"; "recordSaved": "recordSaved"; }, never, never, false, never>;
|
|
41
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SingleRecordComponent, "mj-single-record", never, { "PrimaryKey": { "alias": "PrimaryKey"; "required": false; }; "entityName": { "alias": "entityName"; "required": false; }; "newRecordValues": { "alias": "newRecordValues"; "required": false; }; }, { "loadComplete": "loadComplete"; "recordSaved": "recordSaved"; "recordDismissed": "recordDismissed"; }, never, never, false, never>;
|
|
48
42
|
}
|
|
49
43
|
//# sourceMappingURL=single-record.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single-record.component.d.ts","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"single-record.component.d.ts","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAyB,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAA0B,MAAM,+BAA+B,CAAC;AAEnH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAErE;;;;;;;;;;;;;GAaG;AACH,qBAMa,qBAAsB,SAAQ,oBAAoB;IAC7C,UAAU,EAAE,YAAY,CAAsB;IAC9C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAM;IAC/B,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAM;IAE7D,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,CAA4B;IAC5D,WAAW,EAAE,YAAY,CAAC,UAAU,CAAC,CAAkC;IACxF,wFAAwF;IACvE,eAAe,EAAE,YAAY,CAAC,IAAI,CAAC,CAA4B;IAEhF,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,mBAAmB,CAA6B;IAExD,uEAAuE;IACvE,cAAc,IAAI,IAAI;IAItB,uEAAuE;IACvE,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAMvC,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIjC,cAAc,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI;IAIlD,4EAA4E;IAC5E,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;yCApCvC,qBAAqB;2CAArB,qBAAqB;CA4EjC"}
|
|
@@ -1,280 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CompositeKey
|
|
3
|
-
import {
|
|
4
|
-
import { Container } from '@memberjunction/ng-container-directives';
|
|
5
|
-
import { BaseFormComponent } from '@memberjunction/ng-base-forms';
|
|
1
|
+
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
|
|
2
|
+
import { CompositeKey } from '@memberjunction/core';
|
|
3
|
+
import { MJFormPresenterService } from '@memberjunction/ng-base-forms';
|
|
6
4
|
import { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';
|
|
7
5
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
8
6
|
import * as i0 from "@angular/core";
|
|
9
|
-
import * as i1 from "@
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
i0.ɵɵtextInterpolate(ctx_r0.errorDetail);
|
|
25
|
-
} }
|
|
26
|
-
function SingleRecordComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
27
|
-
i0.ɵɵelementStart(0, "div", 1)(1, "div", 3);
|
|
28
|
-
i0.ɵɵelement(2, "i", 4);
|
|
29
|
-
i0.ɵɵelementEnd();
|
|
30
|
-
i0.ɵɵelementStart(3, "h2", 5);
|
|
31
|
-
i0.ɵɵtext(4);
|
|
32
|
-
i0.ɵɵelementEnd();
|
|
33
|
-
i0.ɵɵconditionalCreate(5, SingleRecordComponent_Conditional_1_Conditional_5_Template, 2, 1, "p", 6);
|
|
34
|
-
i0.ɵɵelementStart(6, "p", 7);
|
|
35
|
-
i0.ɵɵtext(7, "See browser console for technical details.");
|
|
36
|
-
i0.ɵɵelementEnd()();
|
|
37
|
-
} if (rf & 2) {
|
|
38
|
-
const ctx_r0 = i0.ɵɵnextContext();
|
|
39
|
-
i0.ɵɵadvance(4);
|
|
40
|
-
i0.ɵɵtextInterpolate(ctx_r0.errorTitle);
|
|
41
|
-
i0.ɵɵadvance();
|
|
42
|
-
i0.ɵɵconditional(ctx_r0.errorDetail ? 5 : -1);
|
|
43
|
-
} }
|
|
44
|
-
function SingleRecordComponent_ng_template_2_Template(rf, ctx) { }
|
|
7
|
+
import * as i1 from "@memberjunction/ng-base-forms";
|
|
8
|
+
/**
|
|
9
|
+
* Explorer-side host for a single entity record in the main tab area.
|
|
10
|
+
*
|
|
11
|
+
* This is now a **thin wrapper** around the Generic `<mj-entity-form-host>`
|
|
12
|
+
* (in `@memberjunction/ng-base-forms`), which owns all the mechanics: resolving
|
|
13
|
+
* the form (class / custom / interactive override + variants), loading the
|
|
14
|
+
* record, dynamically creating the form, binding it, and tearing it down.
|
|
15
|
+
*
|
|
16
|
+
* SingleRecordComponent's only remaining job is the **Explorer mapping**:
|
|
17
|
+
* translating the host's framework-agnostic events into Explorer services —
|
|
18
|
+
* `Navigate` → {@link NavigationService}, `Notification` → {@link SharedService},
|
|
19
|
+
* record loads → {@link RecentAccessService} — none of which belong in a Generic
|
|
20
|
+
* component.
|
|
21
|
+
*/
|
|
45
22
|
export class SingleRecordComponent extends BaseAngularComponent {
|
|
46
|
-
route;
|
|
47
|
-
formContainer;
|
|
48
23
|
PrimaryKey = new CompositeKey();
|
|
49
24
|
entityName = '';
|
|
50
25
|
newRecordValues = '';
|
|
51
26
|
loadComplete = new EventEmitter();
|
|
52
27
|
recordSaved = new EventEmitter();
|
|
53
|
-
|
|
28
|
+
/** Emitted when the hosted form asks to be dismissed (e.g. Discard on a new record). */
|
|
29
|
+
recordDismissed = new EventEmitter();
|
|
54
30
|
navigationService = inject(NavigationService);
|
|
55
31
|
sharedService = inject(SharedService);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.recentAccessService = new RecentAccessService();
|
|
61
|
-
}
|
|
62
|
-
appDescription = '';
|
|
63
|
-
useGenericForm = false;
|
|
64
|
-
loading = true;
|
|
65
|
-
errorTitle = null;
|
|
66
|
-
errorDetail = null;
|
|
67
|
-
// Track dynamically created components and entities for cleanup
|
|
68
|
-
_formComponentRef = null;
|
|
69
|
-
_currentRecord = null;
|
|
70
|
-
_eventHandlerSubscription = null;
|
|
71
|
-
_formEventSubscriptions = [];
|
|
72
|
-
ngOnInit() {
|
|
73
|
-
}
|
|
74
|
-
ngAfterViewInit() {
|
|
75
|
-
this.LoadForm(this.PrimaryKey, this.entityName);
|
|
76
|
-
}
|
|
77
|
-
async LoadForm(primaryKey, entityName) {
|
|
78
|
-
// Perform any necessary actions with the ViewID, such as fetching data
|
|
79
|
-
if (!entityName || entityName.trim().length === 0) {
|
|
80
|
-
// No entity yet — caller will re-invoke once it has one. Don't emit loadComplete;
|
|
81
|
-
// it would race the real load. The shell's recovery timer will surface the
|
|
82
|
-
// "taking longer than expected" reset if this is the terminal state.
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
this.entityName = entityName;
|
|
86
|
-
if (primaryKey.HasValue) {
|
|
87
|
-
// we have an existing record to load up
|
|
88
|
-
this.PrimaryKey = primaryKey;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// new record, no existing primary key
|
|
92
|
-
this.PrimaryKey = new CompositeKey();
|
|
93
|
-
}
|
|
94
|
-
const formReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormComponent, entityName);
|
|
95
|
-
const md = this.ProviderToUse;
|
|
96
|
-
const entity = md.EntityByName(entityName);
|
|
97
|
-
const permissions = entity?.GetUserPermisions(md.CurrentUser);
|
|
98
|
-
try {
|
|
99
|
-
if (!entity) {
|
|
100
|
-
this.failWithUserError(`Entity not found: "${entityName}"`, `This MemberJunction instance has no metadata for entity "${entityName}". Check that the entity name in the URL matches a row in __mj.Entity, and that the metadata cache is current.`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (!formReg) {
|
|
104
|
-
this.failWithUserError(`No form is registered for "${entityName}".`, `MemberJunction could not find an Angular form component registered against BaseFormComponent for entity "${entityName}". This usually means CodeGen has not generated a form for this entity in the running build (forms live under packages/MJExplorer or your app's entity-form package). Run CodeGen, ensure the generated module is imported, or register a custom form via @RegisterClass(BaseFormComponent, '${entityName}').`, { entityId: entity.ID, recordKey: primaryKey?.ToString?.() ?? '(none)' });
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const record = await md.GetEntityObject(entityName);
|
|
108
|
-
if (!record) {
|
|
109
|
-
throw new Error(`Unable to instantiate entity ${entityName} with primary key values: ${primaryKey.ToString()}`);
|
|
110
|
-
}
|
|
111
|
-
if (primaryKey.HasValue) {
|
|
112
|
-
const loadOk = await record.InnerLoad(primaryKey);
|
|
113
|
-
if (!loadOk) {
|
|
114
|
-
this.failWithUserError(`Could not load ${entityName} record.`, record.LatestResult?.Message
|
|
115
|
-
? `Server error: ${record.LatestResult.Message}`
|
|
116
|
-
: `InnerLoad returned false for entity "${entityName}" with key ${primaryKey.ToString()}. The record may not exist, you may lack permission to view it, or the load may have been blocked server-side.`, { recordKey: primaryKey.ToString() });
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
// Log access to existing record (fire-and-forget, don't await)
|
|
120
|
-
this.recentAccessService.logAccess(entityName, primaryKey, 'record');
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
record.NewRecord();
|
|
124
|
-
this.SetNewRecordValues(record);
|
|
125
|
-
}
|
|
126
|
-
// CRITICAL: Track the event handler subscription for cleanup
|
|
127
|
-
this._eventHandlerSubscription = record.RegisterEventHandler((eventType) => {
|
|
128
|
-
if (eventType.type === 'save')
|
|
129
|
-
this.recordSaved.emit(record);
|
|
130
|
-
});
|
|
131
|
-
const viewContainerRef = this.formContainer.viewContainerRef;
|
|
132
|
-
viewContainerRef.clear();
|
|
133
|
-
const componentRef = viewContainerRef.createComponent(formReg.SubClass);
|
|
134
|
-
// Track component and record for cleanup
|
|
135
|
-
this._formComponentRef = componentRef;
|
|
136
|
-
this._currentRecord = record;
|
|
137
|
-
componentRef.instance.record = record;
|
|
138
|
-
componentRef.instance.userPermissions = permissions;
|
|
139
|
-
componentRef.instance.EditMode = !primaryKey.HasValue; // for new records go direct into edit mode
|
|
140
|
-
// Subscribe to form @Output events and map them to Explorer services
|
|
141
|
-
this.subscribeToFormEvents(componentRef.instance);
|
|
142
|
-
this.useGenericForm = false;
|
|
143
|
-
this.errorTitle = null;
|
|
144
|
-
this.errorDetail = null;
|
|
145
|
-
this.loadComplete.emit();
|
|
146
|
-
}
|
|
147
|
-
catch (err) {
|
|
148
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
-
this.failWithUserError(`Failed to load ${entityName} record.`, `An unexpected error occurred while loading this record: ${msg}`, { error: err });
|
|
150
|
-
}
|
|
151
|
-
this.loading = false;
|
|
152
|
-
this.cdr.detectChanges();
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Render a user-visible error state inside the record pane AND log a structured
|
|
156
|
-
* console.error for developers. Always emits `loadComplete` so the Explorer shell
|
|
157
|
-
* does not hang on its first-resource-load gate.
|
|
158
|
-
*/
|
|
159
|
-
failWithUserError(title, detail, context) {
|
|
160
|
-
this.errorTitle = title;
|
|
161
|
-
this.errorDetail = detail;
|
|
162
|
-
this.loading = false;
|
|
163
|
-
// Single structured console.error for devs — easy to grep, easy to read.
|
|
164
|
-
console.error(`[SingleRecord] ${title}\n${detail}` +
|
|
165
|
-
(context ? `\nContext: ${JSON.stringify(context, null, 2)}` : ''));
|
|
166
|
-
// Clear any prior form/component so the error UI is what shows.
|
|
167
|
-
if (this._formComponentRef) {
|
|
168
|
-
try {
|
|
169
|
-
this._formComponentRef.destroy();
|
|
170
|
-
}
|
|
171
|
-
catch { /* noop */ }
|
|
172
|
-
this._formComponentRef = null;
|
|
173
|
-
}
|
|
174
|
-
if (this.formContainer?.viewContainerRef) {
|
|
175
|
-
this.formContainer.viewContainerRef.clear();
|
|
176
|
-
}
|
|
177
|
-
// Always unblock the shell.
|
|
32
|
+
formPresenter = inject(MJFormPresenterService);
|
|
33
|
+
recentAccessService = new RecentAccessService();
|
|
34
|
+
/** Unblock the shell's first-resource-load gate (success or error). */
|
|
35
|
+
onLoadComplete() {
|
|
178
36
|
this.loadComplete.emit();
|
|
179
|
-
this.cdr.detectChanges();
|
|
180
37
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Handle both object and string (URL segment) formats
|
|
186
|
-
if (typeof this.newRecordValues === 'string') {
|
|
187
|
-
if (this.newRecordValues.length === 0) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
// we have a URL segment string format: "field1|value1||field2|value2"
|
|
191
|
-
const fv = new FieldValueCollection();
|
|
192
|
-
fv.SimpleLoadFromURLSegment(this.newRecordValues);
|
|
193
|
-
// now apply the values to the record
|
|
194
|
-
fv.KeyValuePairs.filter(kvp => kvp.Value !== null && kvp.Value !== undefined).forEach(kvp => {
|
|
195
|
-
const f = record.Fields.find(f => f.Name.trim().toLowerCase() === kvp.FieldName.trim().toLowerCase());
|
|
196
|
-
if (f) {
|
|
197
|
-
// make sure we set the value to the right type based on the f.TSType property
|
|
198
|
-
switch (f.EntityFieldInfo.TSType) {
|
|
199
|
-
case EntityFieldTSType.String:
|
|
200
|
-
record.Set(kvp.FieldName, kvp.Value);
|
|
201
|
-
break;
|
|
202
|
-
case EntityFieldTSType.Number:
|
|
203
|
-
record.Set(kvp.FieldName, parseFloat(kvp.Value));
|
|
204
|
-
break;
|
|
205
|
-
case EntityFieldTSType.Boolean:
|
|
206
|
-
if (kvp.Value === 'false' || kvp.Value === '0' || kvp.Value.toString().trim().length === 0)
|
|
207
|
-
record.Set(kvp.FieldName, false);
|
|
208
|
-
else
|
|
209
|
-
record.Set(kvp.FieldName, true);
|
|
210
|
-
break;
|
|
211
|
-
case EntityFieldTSType.Date:
|
|
212
|
-
record.Set(kvp.FieldName, new Date(kvp.Value));
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
// we have a plain object format: { field1: value1, field2: value2 }
|
|
220
|
-
const recordValues = this.newRecordValues;
|
|
221
|
-
Object.keys(recordValues)
|
|
222
|
-
.filter(key => recordValues[key] !== null && recordValues[key] !== undefined)
|
|
223
|
-
.forEach(key => {
|
|
224
|
-
const f = record.Fields.find(f => f.Name.trim().toLowerCase() === key.trim().toLowerCase());
|
|
225
|
-
if (f) {
|
|
226
|
-
const value = recordValues[key];
|
|
227
|
-
// Set the value with proper type conversion
|
|
228
|
-
switch (f.EntityFieldInfo.TSType) {
|
|
229
|
-
case EntityFieldTSType.String:
|
|
230
|
-
record.Set(key, value?.toString() || '');
|
|
231
|
-
break;
|
|
232
|
-
case EntityFieldTSType.Number:
|
|
233
|
-
record.Set(key, typeof value === 'number' ? value : parseFloat(value?.toString() || '0'));
|
|
234
|
-
break;
|
|
235
|
-
case EntityFieldTSType.Boolean:
|
|
236
|
-
if (typeof value === 'boolean') {
|
|
237
|
-
record.Set(key, value);
|
|
238
|
-
}
|
|
239
|
-
else if (typeof value === 'string') {
|
|
240
|
-
record.Set(key, value !== 'false' && value !== '0' && value.trim().length > 0);
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
record.Set(key, !!value);
|
|
244
|
-
}
|
|
245
|
-
break;
|
|
246
|
-
case EntityFieldTSType.Date:
|
|
247
|
-
record.Set(key, value instanceof Date ? value : new Date(value?.toString() || ''));
|
|
248
|
-
break;
|
|
249
|
-
default:
|
|
250
|
-
record.Set(key, value);
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
38
|
+
/** Log access for existing records once the form's record is ready. */
|
|
39
|
+
onRecordReady(record) {
|
|
40
|
+
if (record?.IsSaved) {
|
|
41
|
+
this.recentAccessService.logAccess(record.EntityInfo.Name, record.PrimaryKey, 'record');
|
|
255
42
|
}
|
|
256
43
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
*/
|
|
260
|
-
subscribeToFormEvents(form) {
|
|
261
|
-
this.cleanupFormSubscriptions();
|
|
262
|
-
this._formEventSubscriptions.push(form.Navigate.subscribe((event) => this.handleNavigation(event)), form.Notification.subscribe((event) => {
|
|
263
|
-
this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);
|
|
264
|
-
}));
|
|
44
|
+
onSaved(record) {
|
|
45
|
+
this.recordSaved.emit(record);
|
|
265
46
|
}
|
|
47
|
+
onNotification(event) {
|
|
48
|
+
this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);
|
|
49
|
+
}
|
|
50
|
+
/** Map the form's navigation requests onto Explorer's NavigationService. */
|
|
266
51
|
handleNavigation(event) {
|
|
267
52
|
switch (event.Kind) {
|
|
268
53
|
case 'record':
|
|
269
54
|
this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });
|
|
270
55
|
break;
|
|
271
56
|
case 'new-record':
|
|
272
|
-
// Creating a
|
|
273
|
-
//
|
|
274
|
-
// intact — otherwise the new-record form silently replaces the parent in
|
|
275
|
-
// single-resource mode and the user loses their context. This is the
|
|
276
|
-
// original intent of dea32401ff, now stated explicitly at the call site
|
|
277
|
-
// instead of as a global navigation heuristic.
|
|
57
|
+
// Creating a related record from inside an open form: force a new tab so the
|
|
58
|
+
// parent record stays intact in single-resource mode.
|
|
278
59
|
this.navigationService.OpenNewEntityRecord(event.EntityName, {
|
|
279
60
|
newRecordValues: event.DefaultValues,
|
|
280
61
|
forceNewTab: true,
|
|
@@ -289,62 +70,37 @@ export class SingleRecordComponent extends BaseAngularComponent {
|
|
|
289
70
|
case 'email':
|
|
290
71
|
window.open(`mailto:${event.EmailAddress}`, '_self');
|
|
291
72
|
break;
|
|
73
|
+
case 'dismiss':
|
|
74
|
+
this.recordDismissed.emit();
|
|
75
|
+
break;
|
|
76
|
+
case 'create-related': {
|
|
77
|
+
// A FK field wants a new related record created. Open the related entity's form
|
|
78
|
+
// as a dialog/slide-in (prefilled), then hand the saved record back so the field
|
|
79
|
+
// can select it.
|
|
80
|
+
const ref = this.formPresenter.Open({
|
|
81
|
+
EntityName: event.EntityName,
|
|
82
|
+
Presentation: event.Presentation ?? 'dialog',
|
|
83
|
+
NewRecordValues: event.NewRecordValues,
|
|
84
|
+
Provider: event.Provider,
|
|
85
|
+
});
|
|
86
|
+
ref.AfterSaved().then(created => event.Complete(created));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
292
89
|
}
|
|
293
90
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
ngOnDestroy() {
|
|
301
|
-
// CRITICAL: Clean up form event subscriptions first
|
|
302
|
-
this.cleanupFormSubscriptions();
|
|
303
|
-
// CRITICAL: Clean up dynamically created form component to prevent zombie components
|
|
304
|
-
if (this._formComponentRef) {
|
|
305
|
-
this._formComponentRef.destroy();
|
|
306
|
-
this._formComponentRef = null;
|
|
307
|
-
}
|
|
308
|
-
// CRITICAL: Unsubscribe from event handler to prevent memory leaks
|
|
309
|
-
if (this._eventHandlerSubscription) {
|
|
310
|
-
this._eventHandlerSubscription.unsubscribe();
|
|
311
|
-
this._eventHandlerSubscription = null;
|
|
312
|
-
}
|
|
313
|
-
// Clean up record reference
|
|
314
|
-
if (this._currentRecord) {
|
|
315
|
-
this._currentRecord = null;
|
|
316
|
-
}
|
|
317
|
-
// Clear the view container to ensure no lingering references
|
|
318
|
-
if (this.formContainer?.viewContainerRef) {
|
|
319
|
-
this.formContainer.viewContainerRef.clear();
|
|
320
|
-
}
|
|
321
|
-
// Reset state
|
|
322
|
-
this.loading = true;
|
|
323
|
-
this.useGenericForm = false;
|
|
324
|
-
}
|
|
325
|
-
static ɵfac = function SingleRecordComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SingleRecordComponent)(i0.ɵɵdirectiveInject(i1.ActivatedRoute)); };
|
|
326
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SingleRecordComponent, selectors: [["mj-single-record"]], viewQuery: function SingleRecordComponent_Query(rf, ctx) { if (rf & 1) {
|
|
327
|
-
i0.ɵɵviewQuery(Container, 7);
|
|
91
|
+
static ɵfac = /*@__PURE__*/ (() => { let ɵSingleRecordComponent_BaseFactory; return function SingleRecordComponent_Factory(__ngFactoryType__) { return (ɵSingleRecordComponent_BaseFactory || (ɵSingleRecordComponent_BaseFactory = i0.ɵɵgetInheritedFactory(SingleRecordComponent)))(__ngFactoryType__ || SingleRecordComponent); }; })();
|
|
92
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SingleRecordComponent, selectors: [["mj-single-record"]], inputs: { PrimaryKey: "PrimaryKey", entityName: "entityName", newRecordValues: "newRecordValues" }, outputs: { loadComplete: "loadComplete", recordSaved: "recordSaved", recordDismissed: "recordDismissed" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 1, vars: 4, consts: [[3, "LoadComplete", "LoadError", "RecordReady", "Saved", "Navigate", "Notification", "Dismissed", "EntityName", "PrimaryKey", "NewRecordValues", "Provider"]], template: function SingleRecordComponent_Template(rf, ctx) { if (rf & 1) {
|
|
93
|
+
i0.ɵɵelementStart(0, "mj-entity-form-host", 0);
|
|
94
|
+
i0.ɵɵlistener("LoadComplete", function SingleRecordComponent_Template_mj_entity_form_host_LoadComplete_0_listener() { return ctx.onLoadComplete(); })("LoadError", function SingleRecordComponent_Template_mj_entity_form_host_LoadError_0_listener() { return ctx.onLoadComplete(); })("RecordReady", function SingleRecordComponent_Template_mj_entity_form_host_RecordReady_0_listener($event) { return ctx.onRecordReady($event); })("Saved", function SingleRecordComponent_Template_mj_entity_form_host_Saved_0_listener($event) { return ctx.onSaved($event); })("Navigate", function SingleRecordComponent_Template_mj_entity_form_host_Navigate_0_listener($event) { return ctx.handleNavigation($event); })("Notification", function SingleRecordComponent_Template_mj_entity_form_host_Notification_0_listener($event) { return ctx.onNotification($event); })("Dismissed", function SingleRecordComponent_Template_mj_entity_form_host_Dismissed_0_listener() { return ctx.recordDismissed.emit(); });
|
|
95
|
+
i0.ɵɵelementEnd();
|
|
328
96
|
} if (rf & 2) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
} }, inputs: { PrimaryKey: "PrimaryKey", entityName: "entityName", newRecordValues: "newRecordValues" }, outputs: { loadComplete: "loadComplete", recordSaved: "recordSaved" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 2, consts: [["size", "large", 3, "showText"], ["role", "alert", 1, "single-record-error"], ["mjContainer", ""], [1, "single-record-error__icon"], [1, "fa-solid", "fa-triangle-exclamation"], [1, "single-record-error__title"], [1, "single-record-error__detail"], [1, "single-record-error__hint"]], template: function SingleRecordComponent_Template(rf, ctx) { if (rf & 1) {
|
|
332
|
-
i0.ɵɵconditionalCreate(0, SingleRecordComponent_Conditional_0_Template, 1, 1, "mj-loading", 0);
|
|
333
|
-
i0.ɵɵconditionalCreate(1, SingleRecordComponent_Conditional_1_Template, 8, 2, "div", 1);
|
|
334
|
-
i0.ɵɵtemplate(2, SingleRecordComponent_ng_template_2_Template, 0, 0, "ng-template", 2);
|
|
335
|
-
} if (rf & 2) {
|
|
336
|
-
i0.ɵɵconditional(ctx.loading ? 0 : -1);
|
|
337
|
-
i0.ɵɵadvance();
|
|
338
|
-
i0.ɵɵconditional(ctx.errorTitle ? 1 : -1);
|
|
339
|
-
} }, dependencies: [i2.Container, i3.LoadingComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n.single-record-error[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 48px 32px;\n max-width: 720px;\n margin: 64px auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n color: var(--mj-text-primary);\n}\n\n.single-record-error__icon[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--mj-status-error);\n margin-bottom: 16px;\n}\n\n.single-record-error__title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 600;\n margin: 0 0 12px 0;\n color: var(--mj-status-error-text);\n}\n\n.single-record-error__detail[_ngcontent-%COMP%] {\n font-size: 14px;\n line-height: 1.5;\n color: var(--mj-text-secondary);\n margin: 0 0 16px 0;\n white-space: pre-wrap;\n}\n\n.single-record-error__hint[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n margin: 0;\n font-style: italic;\n}"] });
|
|
97
|
+
i0.ɵɵproperty("EntityName", ctx.entityName)("PrimaryKey", ctx.PrimaryKey)("NewRecordValues", ctx.newRecordValues)("Provider", ctx.Provider);
|
|
98
|
+
} }, dependencies: [i1.MjEntityFormHostComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n.single-record-error[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 48px 32px;\n max-width: 720px;\n margin: 64px auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n color: var(--mj-text-primary);\n}\n\n.single-record-error__icon[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--mj-status-error);\n margin-bottom: 16px;\n}\n\n.single-record-error__title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 600;\n margin: 0 0 12px 0;\n color: var(--mj-status-error-text);\n}\n\n.single-record-error__detail[_ngcontent-%COMP%] {\n font-size: 14px;\n line-height: 1.5;\n color: var(--mj-text-secondary);\n margin: 0 0 16px 0;\n white-space: pre-wrap;\n}\n\n.single-record-error__hint[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n margin: 0;\n font-style: italic;\n}"] });
|
|
340
99
|
}
|
|
341
100
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SingleRecordComponent, [{
|
|
342
101
|
type: Component,
|
|
343
|
-
args: [{ standalone: false, selector: 'mj-single-record', template: "
|
|
344
|
-
}],
|
|
345
|
-
type: ViewChild,
|
|
346
|
-
args: [Container, { static: true }]
|
|
347
|
-
}], PrimaryKey: [{
|
|
102
|
+
args: [{ standalone: false, selector: 'mj-single-record', template: "<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (Dismissed)=\"recordDismissed.emit()\">\n</mj-entity-form-host>\n", styles: [":host {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n.single-record-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 48px 32px;\n max-width: 720px;\n margin: 64px auto;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n color: var(--mj-text-primary);\n}\n\n.single-record-error__icon {\n font-size: 36px;\n color: var(--mj-status-error);\n margin-bottom: 16px;\n}\n\n.single-record-error__title {\n font-size: 18px;\n font-weight: 600;\n margin: 0 0 12px 0;\n color: var(--mj-status-error-text);\n}\n\n.single-record-error__detail {\n font-size: 14px;\n line-height: 1.5;\n color: var(--mj-text-secondary);\n margin: 0 0 16px 0;\n white-space: pre-wrap;\n}\n\n.single-record-error__hint {\n font-size: 12px;\n color: var(--mj-text-muted);\n margin: 0;\n font-style: italic;\n}\n"] }]
|
|
103
|
+
}], null, { PrimaryKey: [{
|
|
348
104
|
type: Input
|
|
349
105
|
}], entityName: [{
|
|
350
106
|
type: Input
|
|
@@ -354,6 +110,8 @@ export class SingleRecordComponent extends BaseAngularComponent {
|
|
|
354
110
|
type: Output
|
|
355
111
|
}], recordSaved: [{
|
|
356
112
|
type: Output
|
|
113
|
+
}], recordDismissed: [{
|
|
114
|
+
type: Output
|
|
357
115
|
}] }); })();
|
|
358
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SingleRecordComponent, { className: "SingleRecordComponent", filePath: "src/lib/single-record/single-record.component.ts", lineNumber:
|
|
116
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SingleRecordComponent, { className: "SingleRecordComponent", filePath: "src/lib/single-record/single-record.component.ts", lineNumber: 27 }); })();
|
|
359
117
|
//# sourceMappingURL=single-record.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single-record.component.js","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts","../../../src/lib/single-record/single-record.component.html"],"names":[],"mappings":"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","sourcesContent":["import { AfterViewInit, ChangeDetectorRef, Component, ComponentRef, EventEmitter, inject, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router'\nimport { Metadata, KeyValuePair, CompositeKey, BaseEntity, BaseEntityEvent, FieldValueCollection, EntityFieldTSType } from '@memberjunction/core';\nimport { Subscription } from 'rxjs';\nimport { MJGlobal } from '@memberjunction/global';\nimport { Container } from '@memberjunction/ng-container-directives';\nimport { BaseFormComponent, FormNavigationEvent, FormNotificationEvent } from '@memberjunction/ng-base-forms';\nimport { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';\n\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n@Component({\n standalone: false,\n selector: 'mj-single-record',\n templateUrl: './single-record.component.html',\n styleUrls: ['./single-record.component.css']\n})\nexport class SingleRecordComponent extends BaseAngularComponent implements OnInit, AfterViewInit, OnDestroy {\n @ViewChild(Container, {static: true}) formContainer!: Container;\n @Input() public PrimaryKey: CompositeKey = new CompositeKey();\n @Input() public entityName: string | null = '';\n @Input() public newRecordValues: string | Record<string, unknown> | null = '';\n\n @Output() public loadComplete: EventEmitter<any> = new EventEmitter<any>();\n @Output() public recordSaved: EventEmitter<BaseEntity> = new EventEmitter<BaseEntity>();\n\n private recentAccessService: RecentAccessService;\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private cdr = inject(ChangeDetectorRef);\n\n constructor (private route: ActivatedRoute) {\n super();\n this.recentAccessService = new RecentAccessService();\n }\n\n public appDescription: string = ''\n public useGenericForm: boolean = false;\n public loading: boolean = true;\n public errorTitle: string | null = null;\n public errorDetail: string | null = null;\n\n // Track dynamically created components and entities for cleanup\n private _formComponentRef: ComponentRef<BaseFormComponent> | null = null;\n private _currentRecord: BaseEntity | null = null;\n private _eventHandlerSubscription: Subscription | null = null;\n private _formEventSubscriptions: Subscription[] = [];\n\n ngOnInit(): void {\n }\n\n ngAfterViewInit() {\n this.LoadForm(this.PrimaryKey, <string>this.entityName)\n }\n\n public async LoadForm(primaryKey: CompositeKey, entityName: string) {\n // Perform any necessary actions with the ViewID, such as fetching data\n if (!entityName || entityName.trim().length === 0) {\n // No entity yet — caller will re-invoke once it has one. Don't emit loadComplete;\n // it would race the real load. The shell's recovery timer will surface the\n // \"taking longer than expected\" reset if this is the terminal state.\n return;\n }\n\n this.entityName = entityName;\n if (primaryKey.HasValue) {\n // we have an existing record to load up\n this.PrimaryKey = primaryKey;\n }\n else {\n // new record, no existing primary key\n this.PrimaryKey = new CompositeKey();\n }\n\n const formReg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormComponent, entityName);\n\n const md = this.ProviderToUse;\n const entity = md.EntityByName(entityName);\n const permissions = entity?.GetUserPermisions(md.CurrentUser);\n\n try {\n if (!entity) {\n this.failWithUserError(\n `Entity not found: \"${entityName}\"`,\n `This MemberJunction instance has no metadata for entity \"${entityName}\". Check that the entity name in the URL matches a row in __mj.Entity, and that the metadata cache is current.`\n );\n return;\n }\n\n if (!formReg) {\n this.failWithUserError(\n `No form is registered for \"${entityName}\".`,\n `MemberJunction could not find an Angular form component registered against BaseFormComponent for entity \"${entityName}\". This usually means CodeGen has not generated a form for this entity in the running build (forms live under packages/MJExplorer or your app's entity-form package). Run CodeGen, ensure the generated module is imported, or register a custom form via @RegisterClass(BaseFormComponent, '${entityName}').`,\n { entityId: entity.ID, recordKey: primaryKey?.ToString?.() ?? '(none)' }\n );\n return;\n }\n\n const record = await md.GetEntityObject<BaseEntity>(entityName);\n if (!record) {\n throw new Error(`Unable to instantiate entity ${entityName} with primary key values: ${primaryKey.ToString()}`);\n }\n\n if (primaryKey.HasValue) {\n const loadOk = await record.InnerLoad(primaryKey);\n if (!loadOk) {\n this.failWithUserError(\n `Could not load ${entityName} record.`,\n record.LatestResult?.Message\n ? `Server error: ${record.LatestResult.Message}`\n : `InnerLoad returned false for entity \"${entityName}\" with key ${primaryKey.ToString()}. The record may not exist, you may lack permission to view it, or the load may have been blocked server-side.`,\n { recordKey: primaryKey.ToString() }\n );\n return;\n }\n // Log access to existing record (fire-and-forget, don't await)\n this.recentAccessService.logAccess(entityName, primaryKey, 'record');\n }\n else {\n record.NewRecord();\n this.SetNewRecordValues(record);\n }\n\n // CRITICAL: Track the event handler subscription for cleanup\n this._eventHandlerSubscription = record.RegisterEventHandler((eventType: BaseEntityEvent) => {\n if (eventType.type === 'save')\n this.recordSaved.emit(record);\n });\n\n const viewContainerRef = this.formContainer.viewContainerRef;\n viewContainerRef.clear();\n\n const componentRef = viewContainerRef.createComponent<typeof formReg.SubClass>(formReg.SubClass);\n\n // Track component and record for cleanup\n this._formComponentRef = componentRef;\n this._currentRecord = record;\n\n componentRef.instance.record = record\n componentRef.instance.userPermissions = permissions\n componentRef.instance.EditMode = !primaryKey.HasValue; // for new records go direct into edit mode\n\n // Subscribe to form @Output events and map them to Explorer services\n this.subscribeToFormEvents(componentRef.instance);\n\n this.useGenericForm = false;\n this.errorTitle = null;\n this.errorDetail = null;\n this.loadComplete.emit();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.failWithUserError(\n `Failed to load ${entityName} record.`,\n `An unexpected error occurred while loading this record: ${msg}`,\n { error: err }\n );\n }\n\n this.loading = false;\n this.cdr.detectChanges();\n }\n\n /**\n * Render a user-visible error state inside the record pane AND log a structured\n * console.error for developers. Always emits `loadComplete` so the Explorer shell\n * does not hang on its first-resource-load gate.\n */\n private failWithUserError(title: string, detail: string, context?: Record<string, unknown>): void {\n this.errorTitle = title;\n this.errorDetail = detail;\n this.loading = false;\n\n // Single structured console.error for devs — easy to grep, easy to read.\n console.error(\n `[SingleRecord] ${title}\\n${detail}` +\n (context ? `\\nContext: ${JSON.stringify(context, null, 2)}` : '')\n );\n\n // Clear any prior form/component so the error UI is what shows.\n if (this._formComponentRef) {\n try { this._formComponentRef.destroy(); } catch { /* noop */ }\n this._formComponentRef = null;\n }\n if (this.formContainer?.viewContainerRef) {\n this.formContainer.viewContainerRef.clear();\n }\n\n // Always unblock the shell.\n this.loadComplete.emit();\n this.cdr.detectChanges();\n }\n\n protected SetNewRecordValues(record: BaseEntity) {\n if (!this.newRecordValues) {\n return;\n }\n\n // Handle both object and string (URL segment) formats\n if (typeof this.newRecordValues === 'string') {\n if (this.newRecordValues.length === 0) {\n return;\n }\n // we have a URL segment string format: \"field1|value1||field2|value2\"\n const fv = new FieldValueCollection();\n fv.SimpleLoadFromURLSegment(this.newRecordValues);\n // now apply the values to the record\n fv.KeyValuePairs.filter(kvp => kvp.Value !== null && kvp.Value !== undefined).forEach(kvp => {\n const f = record.Fields.find(f => f.Name.trim().toLowerCase() === kvp.FieldName.trim().toLowerCase());\n if (f) {\n // make sure we set the value to the right type based on the f.TSType property\n switch (f.EntityFieldInfo.TSType) {\n case EntityFieldTSType.String:\n record.Set(kvp.FieldName, kvp.Value);\n break;\n case EntityFieldTSType.Number:\n record.Set(kvp.FieldName, parseFloat(kvp.Value));\n break;\n case EntityFieldTSType.Boolean:\n if (kvp.Value === 'false' || kvp.Value === '0' || kvp.Value.toString().trim().length === 0 )\n record.Set(kvp.FieldName, false);\n else\n record.Set(kvp.FieldName, true);\n break;\n case EntityFieldTSType.Date:\n record.Set(kvp.FieldName, new Date(kvp.Value));\n break;\n }\n }\n });\n }\n else {\n // we have a plain object format: { field1: value1, field2: value2 }\n const recordValues = this.newRecordValues as Record<string, unknown>;\n Object.keys(recordValues)\n .filter(key => recordValues[key] !== null && recordValues[key] !== undefined)\n .forEach(key => {\n const f = record.Fields.find(f => f.Name.trim().toLowerCase() === key.trim().toLowerCase());\n if (f) {\n const value = recordValues[key];\n // Set the value with proper type conversion\n switch (f.EntityFieldInfo.TSType) {\n case EntityFieldTSType.String:\n record.Set(key, value?.toString() || '');\n break;\n case EntityFieldTSType.Number:\n record.Set(key, typeof value === 'number' ? value : parseFloat(value?.toString() || '0'));\n break;\n case EntityFieldTSType.Boolean:\n if (typeof value === 'boolean') {\n record.Set(key, value);\n }\n else if (typeof value === 'string') {\n record.Set(key, value !== 'false' && value !== '0' && value.trim().length > 0);\n }\n else {\n record.Set(key, !!value);\n }\n break;\n case EntityFieldTSType.Date:\n record.Set(key, value instanceof Date ? value : new Date(value?.toString() || ''));\n break;\n default:\n record.Set(key, value);\n break;\n }\n }\n });\n }\n }\n\n /**\n * Subscribe to BaseFormComponent @Output events and map them to Explorer services.\n */\n private subscribeToFormEvents(form: BaseFormComponent): void {\n this.cleanupFormSubscriptions();\n\n this._formEventSubscriptions.push(\n form.Navigate.subscribe((event: FormNavigationEvent) => this.handleNavigation(event)),\n form.Notification.subscribe((event: FormNotificationEvent) => {\n this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);\n })\n );\n }\n\n private handleNavigation(event: FormNavigationEvent): void {\n switch (event.Kind) {\n case 'record':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n break;\n case 'new-record':\n // Creating a new related record from inside an open record form (e.g. + New\n // on a related-entity grid). Force a new tab so the parent record stays\n // intact — otherwise the new-record form silently replaces the parent in\n // single-resource mode and the user loses their context. This is the\n // original intent of dea32401ff, now stated explicitly at the call site\n // instead of as a global navigation heuristic.\n this.navigationService.OpenNewEntityRecord(event.EntityName, {\n newRecordValues: event.DefaultValues,\n forceNewTab: true,\n });\n break;\n case 'entity-hierarchy':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey);\n break;\n case 'external-link':\n window.open(event.Url, '_blank');\n break;\n case 'email':\n window.open(`mailto:${event.EmailAddress}`, '_self');\n break;\n }\n }\n\n private cleanupFormSubscriptions(): void {\n for (const sub of this._formEventSubscriptions) {\n sub.unsubscribe();\n }\n this._formEventSubscriptions = [];\n }\n\n ngOnDestroy(): void {\n // CRITICAL: Clean up form event subscriptions first\n this.cleanupFormSubscriptions();\n\n // CRITICAL: Clean up dynamically created form component to prevent zombie components\n if (this._formComponentRef) {\n this._formComponentRef.destroy();\n this._formComponentRef = null;\n }\n\n // CRITICAL: Unsubscribe from event handler to prevent memory leaks\n if (this._eventHandlerSubscription) {\n this._eventHandlerSubscription.unsubscribe();\n this._eventHandlerSubscription = null;\n }\n \n // Clean up record reference\n if (this._currentRecord) {\n this._currentRecord = null;\n }\n \n // Clear the view container to ensure no lingering references\n if (this.formContainer?.viewContainerRef) {\n this.formContainer.viewContainerRef.clear();\n }\n \n // Reset state\n this.loading = true;\n this.useGenericForm = false;\n }\n}\n","@if (loading) {\n <mj-loading [showText]=\"false\" size=\"large\"></mj-loading>\n}\n@if (errorTitle) {\n <div class=\"single-record-error\" role=\"alert\">\n <div class=\"single-record-error__icon\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n </div>\n <h2 class=\"single-record-error__title\">{{ errorTitle }}</h2>\n @if (errorDetail) {\n <p class=\"single-record-error__detail\">{{ errorDetail }}</p>\n }\n <p class=\"single-record-error__hint\">See browser console for technical details.</p>\n </div>\n}\n<ng-template mjContainer></ng-template>\n"]}
|
|
1
|
+
{"version":3,"file":"single-record.component.js","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts","../../../src/lib/single-record/single-record.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, inject, Input, Output } from '@angular/core';\nimport { CompositeKey, BaseEntity } from '@memberjunction/core';\nimport { FormNavigationEvent, FormNotificationEvent, MJFormPresenterService } from '@memberjunction/ng-base-forms';\nimport { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n\n/**\n * Explorer-side host for a single entity record in the main tab area.\n *\n * This is now a **thin wrapper** around the Generic `<mj-entity-form-host>`\n * (in `@memberjunction/ng-base-forms`), which owns all the mechanics: resolving\n * the form (class / custom / interactive override + variants), loading the\n * record, dynamically creating the form, binding it, and tearing it down.\n *\n * SingleRecordComponent's only remaining job is the **Explorer mapping**:\n * translating the host's framework-agnostic events into Explorer services —\n * `Navigate` → {@link NavigationService}, `Notification` → {@link SharedService},\n * record loads → {@link RecentAccessService} — none of which belong in a Generic\n * component.\n */\n@Component({\n standalone: false,\n selector: 'mj-single-record',\n templateUrl: './single-record.component.html',\n styleUrls: ['./single-record.component.css']\n})\nexport class SingleRecordComponent extends BaseAngularComponent {\n @Input() public PrimaryKey: CompositeKey = new CompositeKey();\n @Input() public entityName: string | null = '';\n @Input() public newRecordValues: string | Record<string, unknown> | null = '';\n\n @Output() public loadComplete: EventEmitter<void> = new EventEmitter<void>();\n @Output() public recordSaved: EventEmitter<BaseEntity> = new EventEmitter<BaseEntity>();\n /** Emitted when the hosted form asks to be dismissed (e.g. Discard on a new record). */\n @Output() public recordDismissed: EventEmitter<void> = new EventEmitter<void>();\n\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private formPresenter = inject(MJFormPresenterService);\n private recentAccessService = new RecentAccessService();\n\n /** Unblock the shell's first-resource-load gate (success or error). */\n onLoadComplete(): void {\n this.loadComplete.emit();\n }\n\n /** Log access for existing records once the form's record is ready. */\n onRecordReady(record: BaseEntity): void {\n if (record?.IsSaved) {\n this.recentAccessService.logAccess(record.EntityInfo.Name, record.PrimaryKey, 'record');\n }\n }\n\n onSaved(record: BaseEntity): void {\n this.recordSaved.emit(record);\n }\n\n onNotification(event: FormNotificationEvent): void {\n this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);\n }\n\n /** Map the form's navigation requests onto Explorer's NavigationService. */\n handleNavigation(event: FormNavigationEvent): void {\n switch (event.Kind) {\n case 'record':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n break;\n case 'new-record':\n // Creating a related record from inside an open form: force a new tab so the\n // parent record stays intact in single-resource mode.\n this.navigationService.OpenNewEntityRecord(event.EntityName, {\n newRecordValues: event.DefaultValues,\n forceNewTab: true,\n });\n break;\n case 'entity-hierarchy':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey);\n break;\n case 'external-link':\n window.open(event.Url, '_blank');\n break;\n case 'email':\n window.open(`mailto:${event.EmailAddress}`, '_self');\n break;\n case 'dismiss':\n this.recordDismissed.emit();\n break;\n case 'create-related': {\n // A FK field wants a new related record created. Open the related entity's form\n // as a dialog/slide-in (prefilled), then hand the saved record back so the field\n // can select it.\n const ref = this.formPresenter.Open({\n EntityName: event.EntityName,\n Presentation: event.Presentation ?? 'dialog',\n NewRecordValues: event.NewRecordValues,\n Provider: event.Provider,\n });\n ref.AfterSaved().then(created => event.Complete(created));\n break;\n }\n }\n }\n}\n","<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (Dismissed)=\"recordDismissed.emit()\">\n</mj-entity-form-host>\n"]}
|