@memberjunction/ng-explorer-core 5.37.0 → 5.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/generated/lazy-feature-config.d.ts +1 -1
  2. package/dist/generated/lazy-feature-config.d.ts.map +1 -1
  3. package/dist/generated/lazy-feature-config.js +4 -2
  4. package/dist/generated/lazy-feature-config.js.map +1 -1
  5. package/dist/lib/__tests__/form-resolver.service.test.d.ts +2 -0
  6. package/dist/lib/__tests__/form-resolver.service.test.d.ts.map +1 -0
  7. package/dist/lib/__tests__/form-resolver.service.test.js +258 -0
  8. package/dist/lib/__tests__/form-resolver.service.test.js.map +1 -0
  9. package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +10 -0
  10. package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -1
  11. package/dist/lib/resource-wrappers/artifact-resource.component.js +15 -5
  12. package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -1
  13. package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
  14. package/dist/lib/resource-wrappers/record-resource.component.js +22 -6
  15. package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
  16. package/dist/lib/services/form-resolver.service.d.ts +139 -0
  17. package/dist/lib/services/form-resolver.service.d.ts.map +1 -0
  18. package/dist/lib/services/form-resolver.service.js +235 -0
  19. package/dist/lib/services/form-resolver.service.js.map +1 -0
  20. package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
  21. package/dist/lib/shell/components/header/app-nav.component.js +12 -0
  22. package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
  23. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +24 -5
  24. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
  25. package/dist/lib/shell/components/tabs/component-cache-manager.js +58 -14
  26. package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
  27. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +42 -0
  28. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  29. package/dist/lib/shell/components/tabs/tab-container.component.js +186 -12
  30. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  31. package/dist/lib/single-record/single-record.component.d.ts +41 -3
  32. package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
  33. package/dist/lib/single-record/single-record.component.js +192 -23
  34. package/dist/lib/single-record/single-record.component.js.map +1 -1
  35. package/package.json +46 -45
@@ -7,15 +7,25 @@ import * as i0 from "@angular/core";
7
7
  export declare class SingleRecordComponent extends BaseAngularComponent implements OnInit, AfterViewInit, OnDestroy {
8
8
  private route;
9
9
  formContainer: Container;
10
- PrimaryKey: CompositeKey;
11
- entityName: string | null;
10
+ private _primaryKey;
11
+ set PrimaryKey(value: CompositeKey);
12
+ get PrimaryKey(): CompositeKey;
13
+ private _entityName;
14
+ set entityName(value: string | null);
15
+ get entityName(): string | null;
12
16
  newRecordValues: string | Record<string, unknown> | null;
13
17
  loadComplete: EventEmitter<any>;
14
18
  recordSaved: EventEmitter<BaseEntity>;
19
+ /**
20
+ * Emitted when the hosted form asks to be dismissed (e.g., user clicked Discard on
21
+ * a brand-new record). Parent components should close the tab / route the user back.
22
+ */
23
+ recordDismissed: EventEmitter<void>;
15
24
  private recentAccessService;
16
25
  private navigationService;
17
26
  private sharedService;
18
27
  private cdr;
28
+ private formResolver;
19
29
  constructor(route: ActivatedRoute);
20
30
  appDescription: string;
21
31
  useGenericForm: boolean;
@@ -26,8 +36,36 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
26
36
  private _currentRecord;
27
37
  private _eventHandlerSubscription;
28
38
  private _formEventSubscriptions;
39
+ private _viewInitialized;
29
40
  ngOnInit(): void;
30
41
  ngAfterViewInit(): void;
42
+ /**
43
+ * Re-run LoadForm when an @Input changes after initial view init.
44
+ *
45
+ * Defense-in-depth: the tab/cache rekey on save (in TabContainerComponent) is the
46
+ * primary fix that prevents stale-form bugs after creating a new record. This setter
47
+ * path ensures we self-heal if a host ever swaps the bound inputs without recreating
48
+ * the component — without it, LoadForm only ever runs once per component instance.
49
+ *
50
+ * Tears down the previous form (component, record, event handlers) before loading the
51
+ * new one so we don't leak references or stack multiple forms in the container.
52
+ */
53
+ private reloadCurrentForm;
54
+ /**
55
+ * Compare two CompositeKey instances by their key/value contents.
56
+ * Two different instances representing the same key should NOT count as a change.
57
+ *
58
+ * Filters out KVPs with empty Values before comparing. Different callers represent
59
+ * "no key" differently — some use an empty KeyValuePairs list, others use a single
60
+ * KVP with an empty Value (e.g. `LoadFromURLSegment(entity, '')`). Both mean the
61
+ * same thing semantically and must not trigger a change.
62
+ */
63
+ private compositeKeysEqual;
64
+ /**
65
+ * Tear down the currently rendered form so a new one can be loaded in its place.
66
+ * Mirrors the cleanup done in ngOnDestroy, minus the final state reset.
67
+ */
68
+ private teardownActiveForm;
31
69
  LoadForm(primaryKey: CompositeKey, entityName: string): Promise<void>;
32
70
  /**
33
71
  * Render a user-visible error state inside the record pane AND log a structured
@@ -44,6 +82,6 @@ export declare class SingleRecordComponent extends BaseAngularComponent implemen
44
82
  private cleanupFormSubscriptions;
45
83
  ngOnDestroy(): void;
46
84
  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>;
85
+ 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
86
  }
49
87
  //# 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,EAAE,aAAa,EAA8C,YAAY,EAAiB,SAAS,EAAE,MAAM,EAAqB,MAAM,eAAe,CAAC;AAC7J,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAA0B,YAAY,EAAE,UAAU,EAA4D,MAAM,sBAAsB,CAAC;AAGlJ,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AACrE,qBAMa,qBAAsB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,aAAa,EAAE,SAAS;IAc5F,OAAO,CAAC,KAAK;IAbY,aAAa,EAAG,SAAS,CAAC;IAChD,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,GAAG,CAAC,CAA2B;IAC1D,WAAW,EAAE,YAAY,CAAC,UAAU,CAAC,CAAkC;IAExF,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,GAAG,CAA6B;gBAEnB,KAAK,EAAE,cAAc;IAKnC,cAAc,EAAE,MAAM,CAAK;IAC3B,cAAc,EAAE,OAAO,CAAS;IAChC,OAAO,EAAE,OAAO,CAAQ;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IACjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAGzC,OAAO,CAAC,iBAAiB,CAAgD;IACzE,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,yBAAyB,CAA6B;IAC9D,OAAO,CAAC,uBAAuB,CAAsB;IAErD,QAAQ,IAAI,IAAI;IAGhB,eAAe;IAIF,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM;IA2GlE;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAyBzB,SAAS,CAAC,kBAAkB,CAAC,MAAM,EAAE,UAAU;IA8E/C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,gBAAgB;IA6BxB,OAAO,CAAC,wBAAwB;IAOhC,WAAW,IAAI,IAAI;yCA/SR,qBAAqB;2CAArB,qBAAqB;CA6UjC"}
1
+ {"version":3,"file":"single-record.component.d.ts","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA8C,YAAY,EAAiB,SAAS,EAAE,MAAM,EAAqB,MAAM,eAAe,CAAC;AAC7J,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAA0B,YAAY,EAAE,UAAU,EAA4D,MAAM,sBAAsB,CAAC;AAElJ,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AACrE,qBAMa,qBAAsB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,aAAa,EAAE,SAAS;IAwD5F,OAAO,CAAC,KAAK;IAvDY,aAAa,EAAG,SAAS,CAAC;IAEhE,OAAO,CAAC,WAAW,CAAoC;IACvD,IACW,UAAU,CAAC,KAAK,EAAE,YAAY,EAiBxC;IACD,IAAW,UAAU,IAAI,YAAY,CAEpC;IAED,OAAO,CAAC,WAAW,CAAqB;IACxC,IACW,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAMzC;IACD,IAAW,UAAU,IAAI,MAAM,GAAG,IAAI,CAErC;IAEe,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAM;IAE7D,YAAY,EAAE,YAAY,CAAC,GAAG,CAAC,CAA2B;IAC1D,WAAW,EAAE,YAAY,CAAC,UAAU,CAAC,CAAkC;IACxF;;;OAGG;IACc,eAAe,EAAE,YAAY,CAAC,IAAI,CAAC,CAA4B;IAEhF,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,YAAY,CAA+B;gBAE9B,KAAK,EAAE,cAAc;IAKnC,cAAc,EAAE,MAAM,CAAK;IAC3B,cAAc,EAAE,OAAO,CAAS;IAChC,OAAO,EAAE,OAAO,CAAQ;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IACjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAGzC,OAAO,CAAC,iBAAiB,CAAgD;IACzE,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,yBAAyB,CAA6B;IAC9D,OAAO,CAAC,uBAAuB,CAAsB;IACrD,OAAO,CAAC,gBAAgB,CAAS;IAEjC,QAAQ,IAAI,IAAI;IAGhB,eAAe;IAKf;;;;;;;;;;OAUG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAoBb,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM;IAoKlE;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAyBzB,SAAS,CAAC,kBAAkB,CAAC,MAAM,EAAE,UAAU;IA8E/C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,gBAAgB;IAkCxB,OAAO,CAAC,wBAAwB;IAOhC,WAAW,IAAI,IAAI;yCA3dR,qBAAqB;2CAArB,qBAAqB;CAyfjC"}
@@ -1,9 +1,9 @@
1
1
  import { ChangeDetectorRef, Component, EventEmitter, inject, Input, Output, ViewChild } from '@angular/core';
2
2
  import { CompositeKey, FieldValueCollection, EntityFieldTSType } from '@memberjunction/core';
3
- import { MJGlobal } from '@memberjunction/global';
4
3
  import { Container } from '@memberjunction/ng-container-directives';
5
- import { BaseFormComponent } from '@memberjunction/ng-base-forms';
4
+ import { InteractiveFormComponent } from '@memberjunction/ng-base-forms';
6
5
  import { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';
6
+ import { FormResolverService } from '../services/form-resolver.service';
7
7
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
8
8
  import * as i0 from "@angular/core";
9
9
  import * as i1 from "@angular/router";
@@ -45,15 +45,52 @@ function SingleRecordComponent_ng_template_2_Template(rf, ctx) { }
45
45
  export class SingleRecordComponent extends BaseAngularComponent {
46
46
  route;
47
47
  formContainer;
48
- PrimaryKey = new CompositeKey();
49
- entityName = '';
48
+ _primaryKey = new CompositeKey();
49
+ set PrimaryKey(value) {
50
+ const changed = !this.compositeKeysEqual(this._primaryKey, value);
51
+ this._primaryKey = value ?? new CompositeKey();
52
+ if (!changed || !this._viewInitialized) {
53
+ return;
54
+ }
55
+ // Skip reload when the incoming key just reflects the current record's now-saved PK.
56
+ // After a new-record save, BaseResourceComponent updates parent.Data.ResourceRecordID,
57
+ // the parent's PrimaryKey getter then returns a fresh CK with the saved ID, and Angular
58
+ // pushes it down to us. The form already represents that record in-place — destroying
59
+ // and rebuilding it here would lose edit state, refetch from DB, and spawn a redundant
60
+ // GetRecordFavoriteStatus call. Genuine external swaps (parent rebinds to a different
61
+ // record) still trigger the reload because `value` won't match `_currentRecord.PrimaryKey`.
62
+ if (this._currentRecord && this.compositeKeysEqual(value, this._currentRecord.PrimaryKey)) {
63
+ return;
64
+ }
65
+ this.reloadCurrentForm();
66
+ }
67
+ get PrimaryKey() {
68
+ return this._primaryKey;
69
+ }
70
+ _entityName = '';
71
+ set entityName(value) {
72
+ const changed = this._entityName !== value;
73
+ this._entityName = value;
74
+ if (changed && this._viewInitialized) {
75
+ this.reloadCurrentForm();
76
+ }
77
+ }
78
+ get entityName() {
79
+ return this._entityName;
80
+ }
50
81
  newRecordValues = '';
51
82
  loadComplete = new EventEmitter();
52
83
  recordSaved = new EventEmitter();
84
+ /**
85
+ * Emitted when the hosted form asks to be dismissed (e.g., user clicked Discard on
86
+ * a brand-new record). Parent components should close the tab / route the user back.
87
+ */
88
+ recordDismissed = new EventEmitter();
53
89
  recentAccessService;
54
90
  navigationService = inject(NavigationService);
55
91
  sharedService = inject(SharedService);
56
92
  cdr = inject(ChangeDetectorRef);
93
+ formResolver = inject(FormResolverService);
57
94
  constructor(route) {
58
95
  super();
59
96
  this.route = route;
@@ -69,10 +106,79 @@ export class SingleRecordComponent extends BaseAngularComponent {
69
106
  _currentRecord = null;
70
107
  _eventHandlerSubscription = null;
71
108
  _formEventSubscriptions = [];
109
+ _viewInitialized = false;
72
110
  ngOnInit() {
73
111
  }
74
112
  ngAfterViewInit() {
75
- this.LoadForm(this.PrimaryKey, this.entityName);
113
+ this._viewInitialized = true;
114
+ this.LoadForm(this._primaryKey, this._entityName);
115
+ }
116
+ /**
117
+ * Re-run LoadForm when an @Input changes after initial view init.
118
+ *
119
+ * Defense-in-depth: the tab/cache rekey on save (in TabContainerComponent) is the
120
+ * primary fix that prevents stale-form bugs after creating a new record. This setter
121
+ * path ensures we self-heal if a host ever swaps the bound inputs without recreating
122
+ * the component — without it, LoadForm only ever runs once per component instance.
123
+ *
124
+ * Tears down the previous form (component, record, event handlers) before loading the
125
+ * new one so we don't leak references or stack multiple forms in the container.
126
+ */
127
+ reloadCurrentForm() {
128
+ this.teardownActiveForm();
129
+ if (!this._entityName) {
130
+ return;
131
+ }
132
+ this.loading = true;
133
+ this.LoadForm(this._primaryKey, this._entityName);
134
+ }
135
+ /**
136
+ * Compare two CompositeKey instances by their key/value contents.
137
+ * Two different instances representing the same key should NOT count as a change.
138
+ *
139
+ * Filters out KVPs with empty Values before comparing. Different callers represent
140
+ * "no key" differently — some use an empty KeyValuePairs list, others use a single
141
+ * KVP with an empty Value (e.g. `LoadFromURLSegment(entity, '')`). Both mean the
142
+ * same thing semantically and must not trigger a change.
143
+ */
144
+ compositeKeysEqual(a, b) {
145
+ if (a === b)
146
+ return true;
147
+ if (!a || !b)
148
+ return false;
149
+ const aKvps = (a.KeyValuePairs || []).filter(kvp => String(kvp.Value ?? '').length > 0);
150
+ const bKvps = (b.KeyValuePairs || []).filter(kvp => String(kvp.Value ?? '').length > 0);
151
+ if (aKvps.length !== bKvps.length)
152
+ return false;
153
+ for (let i = 0; i < aKvps.length; i++) {
154
+ if (aKvps[i].FieldName !== bKvps[i].FieldName)
155
+ return false;
156
+ if (String(aKvps[i].Value ?? '') !== String(bKvps[i].Value ?? ''))
157
+ return false;
158
+ }
159
+ return true;
160
+ }
161
+ /**
162
+ * Tear down the currently rendered form so a new one can be loaded in its place.
163
+ * Mirrors the cleanup done in ngOnDestroy, minus the final state reset.
164
+ */
165
+ teardownActiveForm() {
166
+ this.cleanupFormSubscriptions();
167
+ if (this._eventHandlerSubscription) {
168
+ this._eventHandlerSubscription.unsubscribe();
169
+ this._eventHandlerSubscription = null;
170
+ }
171
+ if (this._formComponentRef) {
172
+ try {
173
+ this._formComponentRef.destroy();
174
+ }
175
+ catch { /* noop */ }
176
+ this._formComponentRef = null;
177
+ }
178
+ this._currentRecord = null;
179
+ if (this.formContainer?.viewContainerRef) {
180
+ this.formContainer.viewContainerRef.clear();
181
+ }
76
182
  }
77
183
  async LoadForm(primaryKey, entityName) {
78
184
  // Perform any necessary actions with the ViewID, such as fetching data
@@ -82,16 +188,18 @@ export class SingleRecordComponent extends BaseAngularComponent {
82
188
  // "taking longer than expected" reset if this is the terminal state.
83
189
  return;
84
190
  }
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);
191
+ // Write through to backing fields directly. Going through the setters here would
192
+ // re-trigger reloadCurrentForm() and recurse — we're already inside LoadForm.
193
+ //
194
+ // Use the parameter as-is rather than substituting `new CompositeKey()` for the
195
+ // !HasValue branch. The parent's getter (e.g. EntityRecordResource.GetPrimaryKey)
196
+ // builds its CK via `LoadFromURLSegment(entity, '')`, which yields `[{FieldName: 'ID',
197
+ // Value: ''}]` for a new record — a single KVP with an empty value, NOT an empty
198
+ // KVP list. Substituting an empty CK here creates a structural mismatch that makes
199
+ // the setter's compositeKeysEqual see a phantom change on the very next CD cycle,
200
+ // which triggers reload → LoadForm → CD → setter → reload (infinite loop).
201
+ this._entityName = entityName;
202
+ this._primaryKey = primaryKey ?? new CompositeKey();
95
203
  const md = this.ProviderToUse;
96
204
  const entity = md.EntityByName(entityName);
97
205
  const permissions = entity?.GetUserPermisions(md.CurrentUser);
@@ -100,8 +208,11 @@ export class SingleRecordComponent extends BaseAngularComponent {
100
208
  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
209
  return;
102
210
  }
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)' });
211
+ // Resolve which form to render: User/Role/Global EntityFormOverride first,
212
+ // then ClassFactory-registered Angular form, then nothing.
213
+ const resolution = await this.formResolver.ResolveFormForEntity(entity, md.CurrentUser, md);
214
+ if (resolution.kind === 'none') {
215
+ this.failWithUserError(`No form is registered for "${entityName}".`, `MemberJunction could not find an EntityFormOverride or a class-based form 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, register a custom form via @RegisterClass(BaseFormComponent, '${entityName}'), or create an EntityFormOverride row pointing at a runtime Component.`, { entityId: entity.ID, recordKey: primaryKey?.ToString?.() ?? '(none)' });
105
216
  return;
106
217
  }
107
218
  const record = await md.GetEntityObject(entityName);
@@ -130,15 +241,66 @@ export class SingleRecordComponent extends BaseAngularComponent {
130
241
  });
131
242
  const viewContainerRef = this.formContainer.viewContainerRef;
132
243
  viewContainerRef.clear();
133
- const componentRef = viewContainerRef.createComponent(formReg.SubClass);
244
+ // Generated forms expose properties (e.g. `userPermissions`) that aren't
245
+ // on the abstract `BaseFormComponent`. Widen the instance type for the
246
+ // setter surface we share across class-based and interactive forms.
247
+ const componentRef = resolution.kind === 'interactive'
248
+ ? viewContainerRef.createComponent(InteractiveFormComponent)
249
+ : viewContainerRef.createComponent(resolution.subClass);
250
+ if (resolution.kind === 'interactive') {
251
+ componentRef.instance.ComponentID = resolution.override.ComponentID;
252
+ }
134
253
  // Track component and record for cleanup
135
254
  this._formComponentRef = componentRef;
136
255
  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
256
+ const instance = componentRef.instance;
257
+ instance.record = record;
258
+ instance.userPermissions = permissions;
259
+ instance.EditMode = !primaryKey.HasValue; // for new records go direct into edit mode
260
+ // Push variant list + active selection into the form so the
261
+ // record-form-container's picker renders. The resolver returns
262
+ // every applicable override regardless of status, but the runtime
263
+ // picker should only surface **Active** ones — Inactive rows are
264
+ // historical (e.g. the previous Component version that an agent
265
+ // refinement superseded) and Pending rows are AI-authored work
266
+ // awaiting activation in Form Builder. Picking either does
267
+ // nothing at runtime (pickActive requires Status='Active'), so
268
+ // including them in the picker was misleading the user into
269
+ // thinking "I can switch to this" when they actually can't.
270
+ //
271
+ // Authorship of Pending/Inactive overrides happens in the Form
272
+ // Builder cockpit, which intentionally shows the full lifecycle.
273
+ instance.Variants = (resolution.variants ?? [])
274
+ .filter(v => v.Status === 'Active')
275
+ .map(v => ({
276
+ ID: v.ID,
277
+ Label: v.Name ?? `Override ${v.ID.substring(0, 8)}`,
278
+ Scope: v.Scope,
279
+ Status: v.Status,
280
+ }));
281
+ instance.CurrentVariantID = resolution.kind === 'interactive' ? resolution.override.ID : null;
282
+ // Wire the handler: persist the selection in localStorage and reload
283
+ // the form. Reload uses the existing entry path so all the resolver's
284
+ // tier/priority semantics apply (and the saved choice now overrides).
285
+ instance.OnVariantChanged = (variantID) => {
286
+ // null from the picker = user picked the "Default form" row →
287
+ // store the explicit-default sentinel so the resolver skips ALL
288
+ // overrides and falls back to the CodeGen / @RegisterClass form.
289
+ // Without this, clearing the preference let the resolver auto-pick
290
+ // the first Active override again, making Default unreachable from
291
+ // the UI for entities that have any user-scope overrides.
292
+ if (variantID === null) {
293
+ this.formResolver.SetExplicitDefault(entityName);
294
+ }
295
+ else {
296
+ this.formResolver.SetSelectedVariant(entityName, variantID);
297
+ }
298
+ // Re-run the load with the same key — the resolver will honour the
299
+ // updated session-local selection.
300
+ this.LoadForm(this.PrimaryKey, entityName);
301
+ };
140
302
  // Subscribe to form @Output events and map them to Explorer services
141
- this.subscribeToFormEvents(componentRef.instance);
303
+ this.subscribeToFormEvents(instance);
142
304
  this.useGenericForm = false;
143
305
  this.errorTitle = null;
144
306
  this.errorDetail = null;
@@ -289,6 +451,11 @@ export class SingleRecordComponent extends BaseAngularComponent {
289
451
  case 'email':
290
452
  window.open(`mailto:${event.EmailAddress}`, '_self');
291
453
  break;
454
+ case 'dismiss':
455
+ // Form asked to be dismissed (typically Discard on a new record).
456
+ // Re-emit so the parent (EntityRecordResource) can close the tab.
457
+ this.recordDismissed.emit();
458
+ break;
292
459
  }
293
460
  }
294
461
  cleanupFormSubscriptions() {
@@ -328,7 +495,7 @@ export class SingleRecordComponent extends BaseAngularComponent {
328
495
  } if (rf & 2) {
329
496
  let _t;
330
497
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.formContainer = _t.first);
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) {
498
+ } }, inputs: { PrimaryKey: "PrimaryKey", entityName: "entityName", newRecordValues: "newRecordValues" }, outputs: { loadComplete: "loadComplete", recordSaved: "recordSaved", recordDismissed: "recordDismissed" }, 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
499
  i0.ɵɵconditionalCreate(0, SingleRecordComponent_Conditional_0_Template, 1, 1, "mj-loading", 0);
333
500
  i0.ɵɵconditionalCreate(1, SingleRecordComponent_Conditional_1_Template, 8, 2, "div", 1);
334
501
  i0.ɵɵtemplate(2, SingleRecordComponent_ng_template_2_Template, 0, 0, "ng-template", 2);
@@ -354,6 +521,8 @@ export class SingleRecordComponent extends BaseAngularComponent {
354
521
  type: Output
355
522
  }], recordSaved: [{
356
523
  type: Output
524
+ }], recordDismissed: [{
525
+ type: Output
357
526
  }] }); })();
358
527
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SingleRecordComponent, { className: "SingleRecordComponent", filePath: "src/lib/single-record/single-record.component.ts", lineNumber: 18 }); })();
359
528
  //# 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":"AAAA,OAAO,EAAiB,iBAAiB,EAAE,SAAS,EAAgB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAqB,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7J,OAAO,EAA0B,YAAY,EAA+B,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAElJ,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAA8C,MAAM,+BAA+B,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAGlG,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;;;;ICTnE,gCAAyD;;IAA7C,gCAAkB;;;IAS1B,4BAAuC;IAAA,YAAiB;IAAA,iBAAI;;;IAArB,cAAiB;IAAjB,wCAAiB;;;IAL1D,AADF,8BAA8C,aACL;IACrC,uBAAgD;IAClD,iBAAM;IACN,6BAAuC;IAAA,YAAgB;IAAA,iBAAK;IAC5D,mGAAmB;IAGnB,4BAAqC;IAAA,0DAA0C;IACjF,AADiF,iBAAI,EAC/E;;;IALmC,eAAgB;IAAhB,uCAAgB;IACvD,cAEC;IAFD,6CAEC;;;ADML,MAAM,OAAO,qBAAsB,SAAQ,oBAAoB;IAcxC;IAbiB,aAAa,CAAa;IAChD,UAAU,GAAiB,IAAI,YAAY,EAAE,CAAC;IAC9C,UAAU,GAAkB,EAAE,CAAC;IAC/B,eAAe,GAA4C,EAAE,CAAC;IAE7D,YAAY,GAAsB,IAAI,YAAY,EAAO,CAAC;IAC1D,WAAW,GAA6B,IAAI,YAAY,EAAc,CAAC;IAEhF,mBAAmB,CAAsB;IACzC,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9C,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACtC,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAExC,YAAqB,KAAqB;QACxC,KAAK,EAAE,CAAC;QADW,UAAK,GAAL,KAAK,CAAgB;QAExC,IAAI,CAAC,mBAAmB,GAAG,IAAI,mBAAmB,EAAE,CAAC;IACvD,CAAC;IAEM,cAAc,GAAW,EAAE,CAAA;IAC3B,cAAc,GAAY,KAAK,CAAC;IAChC,OAAO,GAAY,IAAI,CAAC;IACxB,UAAU,GAAkB,IAAI,CAAC;IACjC,WAAW,GAAkB,IAAI,CAAC;IAEzC,gEAAgE;IACxD,iBAAiB,GAA2C,IAAI,CAAC;IACjE,cAAc,GAAsB,IAAI,CAAC;IACzC,yBAAyB,GAAwB,IAAI,CAAC;IACtD,uBAAuB,GAAmB,EAAE,CAAC;IAErD,QAAQ;IACR,CAAC;IAED,eAAe;QACb,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAU,IAAI,CAAC,UAAU,CAAC,CAAA;IACzD,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,UAAwB,EAAE,UAAkB;QAChE,uEAAuE;QACvE,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClD,kFAAkF;YAClF,2EAA2E;YAC3E,qEAAqE;YACrE,OAAO;QACT,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACxB,wCAAwC;YACxC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC/B,CAAC;aACI,CAAC;YACJ,sCAAsC;YACtC,IAAI,CAAC,UAAU,GAAG,IAAI,YAAY,EAAE,CAAC;QACvC,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC;QAE9F,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;QAC9B,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,MAAM,EAAE,iBAAiB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;QAE9D,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,iBAAiB,CACpB,sBAAsB,UAAU,GAAG,EACnC,4DAA4D,UAAU,gHAAgH,CACvL,CAAC;gBACF,OAAO;YACT,CAAC;YAED,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,iBAAiB,CACpB,8BAA8B,UAAU,IAAI,EAC5C,4GAA4G,UAAU,gSAAgS,UAAU,KAAK,EACra,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,QAAQ,EAAE,CACzE,CAAC;gBACF,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CAAa,UAAU,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,6BAA6B,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAClH,CAAC;YAED,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;gBAClD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,IAAI,CAAC,iBAAiB,CACpB,kBAAkB,UAAU,UAAU,EACtC,MAAM,CAAC,YAAY,EAAE,OAAO;wBAC1B,CAAC,CAAC,iBAAiB,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;wBAChD,CAAC,CAAC,wCAAwC,UAAU,cAAc,UAAU,CAAC,QAAQ,EAAE,gHAAgH,EACzM,EAAE,SAAS,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,CACrC,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,+DAA+D;gBAC/D,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;YACvE,CAAC;iBACI,CAAC;gBACJ,MAAM,CAAC,SAAS,EAAE,CAAC;gBACnB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC;YAED,6DAA6D;YAC7D,IAAI,CAAC,yBAAyB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC,SAA0B,EAAE,EAAE;gBAC1F,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;oBAC3B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;YAEH,MAAM,gBAAgB,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC;YAC7D,gBAAgB,CAAC,KAAK,EAAE,CAAC;YAEzB,MAAM,YAAY,GAAG,gBAAgB,CAAC,eAAe,CAA0B,OAAO,CAAC,QAAQ,CAAC,CAAC;YAEjG,yCAAyC;YACzC,IAAI,CAAC,iBAAiB,GAAG,YAAY,CAAC;YACtC,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC;YAE7B,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAA;YACrC,YAAY,CAAC,QAAQ,CAAC,eAAe,GAAG,WAAW,CAAA;YACnD,YAAY,CAAC,QAAQ,CAAC,QAAQ,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,2CAA2C;YAElG,qEAAqE;YACrE,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YAElD,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;YAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,IAAI,CAAC,iBAAiB,CACpB,kBAAkB,UAAU,UAAU,EACtC,2DAA2D,GAAG,EAAE,EAChE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACK,iBAAiB,CAAC,KAAa,EAAE,MAAc,EAAE,OAAiC;QACxF,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QAErB,yEAAyE;QACzE,OAAO,CAAC,KAAK,CACX,kBAAkB,KAAK,KAAK,MAAM,EAAE;YAClC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;QAEF,gEAAgE;QAChE,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC;gBAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;YAC9D,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;QAED,4BAA4B;QAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC;IAES,kBAAkB,CAAC,MAAkB;QAC7C,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,sDAAsD;QACtD,IAAI,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,sEAAsE;YACtE,MAAM,EAAE,GAAG,IAAI,oBAAoB,EAAE,CAAC;YACtC,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAClD,qCAAqC;YACrC,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;gBAC1F,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;gBACtG,IAAI,CAAC,EAAE,CAAC;oBACN,8EAA8E;oBAC9E,QAAQ,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;wBACjC,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;4BACrC,MAAM;wBACR,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;4BACjD,MAAM;wBACR,KAAK,iBAAiB,CAAC,OAAO;4BAC5B,IAAI,GAAG,CAAC,KAAK,KAAK,OAAO,IAAI,GAAG,CAAC,KAAK,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;gCACxF,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;;gCAEjC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;4BAClC,MAAM;wBACR,KAAK,iBAAiB,CAAC,IAAI;4BACzB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;4BAC/C,MAAM;oBACV,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;aACI,CAAC;YACJ,oEAAoE;YACpE,MAAM,YAAY,GAAG,IAAI,CAAC,eAA0C,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;iBACtB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;iBAC5E,OAAO,CAAC,GAAG,CAAC,EAAE;gBACb,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;gBAC5F,IAAI,CAAC,EAAE,CAAC;oBACN,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;oBAChC,4CAA4C;oBAC5C,QAAQ,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;wBACjC,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;4BACzC,MAAM;wBACR,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;4BAC1F,MAAM;wBACR,KAAK,iBAAiB,CAAC,OAAO;4BAC5B,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;gCAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;4BACzB,CAAC;iCACI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gCACnC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;4BACjF,CAAC;iCACI,CAAC;gCACJ,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;4BAC3B,CAAC;4BACD,MAAM;wBACR,KAAK,iBAAiB,CAAC,IAAI;4BACzB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;4BACnF,MAAM;wBACR;4BACE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;4BACvB,MAAM;oBACV,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,IAAuB;QACnD,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAEhC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAC/B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,KAA0B,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EACrF,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,KAA4B,EAAE,EAAE;YAC3D,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzF,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,KAA0B;QACjD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,QAAQ;gBACX,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;gBACjH,MAAM;YACR,KAAK,YAAY;gBACf,4EAA4E;gBAC5E,wEAAwE;gBACxE,yEAAyE;gBACzE,qEAAqE;gBACrE,wEAAwE;gBACxE,+CAA+C;gBAC/C,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,KAAK,CAAC,UAAU,EAAE;oBAC3D,eAAe,EAAE,KAAK,CAAC,aAAa;oBACpC,WAAW,EAAE,IAAI;iBAClB,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,kBAAkB;gBACrB,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC5E,MAAM;YACR,KAAK,eAAe;gBAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;gBACrD,MAAM;QACV,CAAC;IACH,CAAC;IAEO,wBAAwB;QAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC/C,GAAG,CAAC,WAAW,EAAE,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,uBAAuB,GAAG,EAAE,CAAC;IACpC,CAAC;IAED,WAAW;QACT,oDAAoD;QACpD,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAEhC,qFAAqF;QACrF,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,mEAAmE;QACnE,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACnC,IAAI,CAAC,yBAAyB,CAAC,WAAW,EAAE,CAAC;YAC7C,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;QACxC,CAAC;QAED,4BAA4B;QAC5B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QAED,6DAA6D;QAC7D,IAAI,IAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;QAED,cAAc;QACd,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;IAC9B,CAAC;+GA5UU,qBAAqB;6DAArB,qBAAqB;2BACrB,SAAS;;;;;YClBtB,8FAAe;YAGf,uFAAkB;YAYlB,sFAAyB;;YAfzB,sCAEC;YACD,cAWC;YAXD,yCAWC;;;iFDGY,qBAAqB;cANjC,SAAS;6BACI,KAAK,YACP,kBAAkB;;kBAK3B,SAAS;mBAAC,SAAS,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC;;kBACnC,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBAEL,MAAM;;kBACN,MAAM;;kFAPI,qBAAqB","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 { 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 { Container } from '@memberjunction/ng-container-directives';\nimport { BaseFormComponent, FormNavigationEvent, FormNotificationEvent, InteractiveFormComponent } from '@memberjunction/ng-base-forms';\nimport { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';\nimport { FormResolverService } from '../services/form-resolver.service';\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\n private _primaryKey: CompositeKey = new CompositeKey();\n @Input()\n public set PrimaryKey(value: CompositeKey) {\n const changed = !this.compositeKeysEqual(this._primaryKey, value);\n this._primaryKey = value ?? new CompositeKey();\n if (!changed || !this._viewInitialized) {\n return;\n }\n // Skip reload when the incoming key just reflects the current record's now-saved PK.\n // After a new-record save, BaseResourceComponent updates parent.Data.ResourceRecordID,\n // the parent's PrimaryKey getter then returns a fresh CK with the saved ID, and Angular\n // pushes it down to us. The form already represents that record in-place — destroying\n // and rebuilding it here would lose edit state, refetch from DB, and spawn a redundant\n // GetRecordFavoriteStatus call. Genuine external swaps (parent rebinds to a different\n // record) still trigger the reload because `value` won't match `_currentRecord.PrimaryKey`.\n if (this._currentRecord && this.compositeKeysEqual(value, this._currentRecord.PrimaryKey)) {\n return;\n }\n this.reloadCurrentForm();\n }\n public get PrimaryKey(): CompositeKey {\n return this._primaryKey;\n }\n\n private _entityName: string | null = '';\n @Input()\n public set entityName(value: string | null) {\n const changed = this._entityName !== value;\n this._entityName = value;\n if (changed && this._viewInitialized) {\n this.reloadCurrentForm();\n }\n }\n public get entityName(): string | null {\n return this._entityName;\n }\n\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 * Emitted when the hosted form asks to be dismissed (e.g., user clicked Discard on\n * a brand-new record). Parent components should close the tab / route the user back.\n */\n @Output() public recordDismissed: EventEmitter<void> = new EventEmitter<void>();\n\n private recentAccessService: RecentAccessService;\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private cdr = inject(ChangeDetectorRef);\n private formResolver = inject(FormResolverService);\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 private _viewInitialized = false;\n\n ngOnInit(): void {\n }\n\n ngAfterViewInit() {\n this._viewInitialized = true;\n this.LoadForm(this._primaryKey, <string>this._entityName)\n }\n\n /**\n * Re-run LoadForm when an @Input changes after initial view init.\n *\n * Defense-in-depth: the tab/cache rekey on save (in TabContainerComponent) is the\n * primary fix that prevents stale-form bugs after creating a new record. This setter\n * path ensures we self-heal if a host ever swaps the bound inputs without recreating\n * the component — without it, LoadForm only ever runs once per component instance.\n *\n * Tears down the previous form (component, record, event handlers) before loading the\n * new one so we don't leak references or stack multiple forms in the container.\n */\n private reloadCurrentForm(): void {\n this.teardownActiveForm();\n if (!this._entityName) {\n return;\n }\n this.loading = true;\n this.LoadForm(this._primaryKey, this._entityName);\n }\n\n /**\n * Compare two CompositeKey instances by their key/value contents.\n * Two different instances representing the same key should NOT count as a change.\n *\n * Filters out KVPs with empty Values before comparing. Different callers represent\n * \"no key\" differently — some use an empty KeyValuePairs list, others use a single\n * KVP with an empty Value (e.g. `LoadFromURLSegment(entity, '')`). Both mean the\n * same thing semantically and must not trigger a change.\n */\n private compositeKeysEqual(a: CompositeKey | null | undefined, b: CompositeKey | null | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const aKvps = (a.KeyValuePairs || []).filter(kvp => String(kvp.Value ?? '').length > 0);\n const bKvps = (b.KeyValuePairs || []).filter(kvp => String(kvp.Value ?? '').length > 0);\n if (aKvps.length !== bKvps.length) return false;\n for (let i = 0; i < aKvps.length; i++) {\n if (aKvps[i].FieldName !== bKvps[i].FieldName) return false;\n if (String(aKvps[i].Value ?? '') !== String(bKvps[i].Value ?? '')) return false;\n }\n return true;\n }\n\n /**\n * Tear down the currently rendered form so a new one can be loaded in its place.\n * Mirrors the cleanup done in ngOnDestroy, minus the final state reset.\n */\n private teardownActiveForm(): void {\n this.cleanupFormSubscriptions();\n\n if (this._eventHandlerSubscription) {\n this._eventHandlerSubscription.unsubscribe();\n this._eventHandlerSubscription = null;\n }\n\n if (this._formComponentRef) {\n try { this._formComponentRef.destroy(); } catch { /* noop */ }\n this._formComponentRef = null;\n }\n\n this._currentRecord = null;\n\n if (this.formContainer?.viewContainerRef) {\n this.formContainer.viewContainerRef.clear();\n }\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 // Write through to backing fields directly. Going through the setters here would\n // re-trigger reloadCurrentForm() and recurse — we're already inside LoadForm.\n //\n // Use the parameter as-is rather than substituting `new CompositeKey()` for the\n // !HasValue branch. The parent's getter (e.g. EntityRecordResource.GetPrimaryKey)\n // builds its CK via `LoadFromURLSegment(entity, '')`, which yields `[{FieldName: 'ID',\n // Value: ''}]` for a new record — a single KVP with an empty value, NOT an empty\n // KVP list. Substituting an empty CK here creates a structural mismatch that makes\n // the setter's compositeKeysEqual see a phantom change on the very next CD cycle,\n // which triggers reload → LoadForm → CD → setter → reload (infinite loop).\n this._entityName = entityName;\n this._primaryKey = primaryKey ?? new CompositeKey();\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 // Resolve which form to render: User/Role/Global EntityFormOverride first,\n // then ClassFactory-registered Angular form, then nothing.\n const resolution = await this.formResolver.ResolveFormForEntity(entity, md.CurrentUser, md);\n\n if (resolution.kind === 'none') {\n this.failWithUserError(\n `No form is registered for \"${entityName}\".`,\n `MemberJunction could not find an EntityFormOverride or a class-based form 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, register a custom form via @RegisterClass(BaseFormComponent, '${entityName}'), or create an EntityFormOverride row pointing at a runtime Component.`,\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 // Generated forms expose properties (e.g. `userPermissions`) that aren't\n // on the abstract `BaseFormComponent`. Widen the instance type for the\n // setter surface we share across class-based and interactive forms.\n const componentRef: ComponentRef<BaseFormComponent> = resolution.kind === 'interactive'\n ? viewContainerRef.createComponent(InteractiveFormComponent)\n : viewContainerRef.createComponent(resolution.subClass);\n\n if (resolution.kind === 'interactive') {\n (componentRef as ComponentRef<InteractiveFormComponent>).instance.ComponentID = resolution.override.ComponentID;\n }\n\n // Track component and record for cleanup\n this._formComponentRef = componentRef;\n this._currentRecord = record;\n\n const instance = componentRef.instance as BaseFormComponent & { userPermissions?: unknown };\n instance.record = record;\n instance.userPermissions = permissions;\n instance.EditMode = !primaryKey.HasValue; // for new records go direct into edit mode\n\n // Push variant list + active selection into the form so the\n // record-form-container's picker renders. The resolver returns\n // every applicable override regardless of status, but the runtime\n // picker should only surface **Active** ones — Inactive rows are\n // historical (e.g. the previous Component version that an agent\n // refinement superseded) and Pending rows are AI-authored work\n // awaiting activation in Form Builder. Picking either does\n // nothing at runtime (pickActive requires Status='Active'), so\n // including them in the picker was misleading the user into\n // thinking \"I can switch to this\" when they actually can't.\n //\n // Authorship of Pending/Inactive overrides happens in the Form\n // Builder cockpit, which intentionally shows the full lifecycle.\n instance.Variants = (resolution.variants ?? [])\n .filter(v => v.Status === 'Active')\n .map(v => ({\n ID: v.ID,\n Label: v.Name ?? `Override ${v.ID.substring(0, 8)}`,\n Scope: v.Scope,\n Status: v.Status,\n }));\n instance.CurrentVariantID = resolution.kind === 'interactive' ? resolution.override.ID : null;\n // Wire the handler: persist the selection in localStorage and reload\n // the form. Reload uses the existing entry path so all the resolver's\n // tier/priority semantics apply (and the saved choice now overrides).\n instance.OnVariantChanged = (variantID: string | null) => {\n // null from the picker = user picked the \"Default form\" row →\n // store the explicit-default sentinel so the resolver skips ALL\n // overrides and falls back to the CodeGen / @RegisterClass form.\n // Without this, clearing the preference let the resolver auto-pick\n // the first Active override again, making Default unreachable from\n // the UI for entities that have any user-scope overrides.\n if (variantID === null) {\n this.formResolver.SetExplicitDefault(entityName);\n } else {\n this.formResolver.SetSelectedVariant(entityName, variantID);\n }\n // Re-run the load with the same key — the resolver will honour the\n // updated session-local selection.\n this.LoadForm(this.PrimaryKey, entityName);\n };\n\n // Subscribe to form @Output events and map them to Explorer services\n this.subscribeToFormEvents(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 case 'dismiss':\n // Form asked to be dismissed (typically Discard on a new record).\n // Re-emit so the parent (EntityRecordResource) can close the tab.\n this.recordDismissed.emit();\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"]}