@memberjunction/ng-base-forms 1.4.0 → 1.4.1

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.
@@ -10,22 +10,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { Directive, ViewChildren, ViewChild } from '@angular/core';
11
11
  import { Subject, debounceTime, fromEvent } from 'rxjs';
12
12
  import { EntityInfo, EntityPermissionType, Metadata, LogError } from '@memberjunction/core';
13
- import { UserViewGridComponent } from '@memberjunction/ng-user-view-grid';
14
13
  import { BaseRecordComponent } from './base-record-component';
14
+ import { BaseFormComponentEventCodes } from '@memberjunction/ng-shared';
15
15
  import { MJTabStripComponent } from '@memberjunction/ng-tabstrip';
16
+ import { MJEventType, MJGlobal } from '@memberjunction/global';
16
17
  import * as i0 from "@angular/core";
17
18
  import * as i1 from "@memberjunction/ng-shared";
18
19
  import * as i2 from "@angular/router";
19
20
  const _c0 = ["topArea"];
20
21
  export class BaseFormComponent extends BaseRecordComponent {
21
- constructor(elementRef, sharedService, router, route) {
22
+ constructor(elementRef, sharedService, router, route, cdr) {
22
23
  super();
23
24
  this.elementRef = elementRef;
24
25
  this.sharedService = sharedService;
25
26
  this.router = router;
26
27
  this.route = route;
27
- this.activeTabIndex = 0;
28
- this.selectedTab = 0;
28
+ this.cdr = cdr;
29
29
  this.EditMode = false;
30
30
  this.BottomMargin = 10;
31
31
  this.TabHeight = '500px';
@@ -46,20 +46,22 @@ export class BaseFormComponent extends BaseRecordComponent {
46
46
  this.__debug = false;
47
47
  this.resizeSub = null;
48
48
  this._isFavorite = false;
49
- this._tabIndexes = [];
50
49
  this._tabMargin = 10;
51
50
  this.setupSplitterLayoutDebounce();
52
51
  }
52
+ get TabStripComponent() {
53
+ return this.tabComponent;
54
+ }
53
55
  ngOnInit() {
54
56
  return __awaiter(this, void 0, void 0, function* () {
55
57
  if (this.record) {
56
58
  const md = new Metadata();
57
- this._isFavorite = yield md.GetRecordFavoriteStatus(md.CurrentUser.ID, this.record.EntityInfo.Name, this.record.CompositeKey);
59
+ this._isFavorite = yield md.GetRecordFavoriteStatus(md.CurrentUser.ID, this.record.EntityInfo.Name, this.record.PrimaryKey);
58
60
  this.FavoriteInitDone = true;
59
61
  // DEBUG ONLY output to console our full record info for debugging
60
62
  if (this.__debug) {
61
63
  const start = new Date().getTime();
62
- console.log('Full Record Info: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.Value);
64
+ console.log('Full Record Info: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.ToString());
63
65
  const dataObject = yield this.record.GetDataObject({
64
66
  includeRelatedEntityData: true,
65
67
  oldValues: false,
@@ -82,17 +84,13 @@ export class BaseFormComponent extends BaseRecordComponent {
82
84
  ngAfterViewInit() {
83
85
  this.setTabHeight();
84
86
  this.route.queryParams.subscribe(params => {
87
+ var _a;
85
88
  if (!this._updatingBrowserUrl) {
86
89
  // only do this if WE didn't invoke the browser URL update
87
90
  const tabName = params['tab'];
88
91
  if (tabName && tabName.length > 0) {
89
92
  // Select the proper tab based on the tabName
90
- const tabIndex = this.GetTabIndex(tabName);
91
- if (tabIndex >= 0) {
92
- // found the tab index, if we don't find it, do nothing
93
- this.activeTabIndex = tabIndex;
94
- this.tabComponent.SelectedTabIndex = tabIndex;
95
- }
93
+ (_a = this.tabComponent) === null || _a === void 0 ? void 0 : _a.SelectTabByName(tabName);
96
94
  }
97
95
  }
98
96
  // now resize after a pause to allow the UI to settle
@@ -101,11 +99,6 @@ export class BaseFormComponent extends BaseRecordComponent {
101
99
  }, 250);
102
100
  });
103
101
  this.CalcTopAreaHeight();
104
- // finally, set a timer to set the SelectedTabIndex = SelectedTabIndex to force the tab to refresh
105
- setTimeout(() => {
106
- if (this.tabComponent)
107
- this.tabComponent.SelectedTabIndex = this.activeTabIndex;
108
- }, 100);
109
102
  }
110
103
  /**
111
104
  * This method can be called at anytime to re-calculate the height of the top area and set the TopAreaHeight property. This is useful if the top area height changes dynamically
@@ -136,13 +129,12 @@ export class BaseFormComponent extends BaseRecordComponent {
136
129
  get PendingRecords() {
137
130
  return this._pendingRecords;
138
131
  }
139
- onTabSelect(tabIndex) {
140
- this.activeTabIndex = tabIndex;
132
+ onTabSelect(e) {
133
+ var _a;
141
134
  this.sharedService.InvokeManualResize();
142
135
  // now that we've updated our state and re-sized, also update the browser URL to add the tab name as a query parameter to the URL
143
- const tabName = this._tabIndexes[this.activeTabIndex];
144
136
  this._updatingBrowserUrl = true;
145
- this.router.navigate([], { queryParams: { tab: tabName }, queryParamsHandling: 'merge' });
137
+ this.router.navigate([], { queryParams: { tab: (_a = e.tab) === null || _a === void 0 ? void 0 : _a.Name }, queryParamsHandling: 'merge' });
146
138
  this._updatingBrowserUrl = true;
147
139
  }
148
140
  StartEditMode() {
@@ -155,11 +147,11 @@ export class BaseFormComponent extends BaseRecordComponent {
155
147
  if (this.record) {
156
148
  const changes = this.record.GetAll(false, true);
157
149
  const oldValues = this.record.GetAll(true, true);
158
- console.log('Changes for: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.Value);
150
+ console.log('Changes for: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.ToString());
159
151
  console.log(changes);
160
152
  console.log('Old Values');
161
153
  console.log(oldValues);
162
- alert('Changes for: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.Value + '\n\n' + JSON.stringify(changes, null, 2) + '\n\nOld Values\n\n' + JSON.stringify(oldValues, null, 2));
154
+ alert('Changes for: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.ToString() + '\n\n' + JSON.stringify(changes, null, 2) + '\n\nOld Values\n\n' + JSON.stringify(oldValues, null, 2));
163
155
  }
164
156
  }
165
157
  RemoveFavorite() {
@@ -175,7 +167,7 @@ export class BaseFormComponent extends BaseRecordComponent {
175
167
  SetFavoriteStatus(isFavorite) {
176
168
  return __awaiter(this, void 0, void 0, function* () {
177
169
  const md = new Metadata();
178
- yield md.SetRecordFavoriteStatus(md.CurrentUser.ID, this.record.EntityInfo.Name, this.record.CompositeKey, isFavorite);
170
+ yield md.SetRecordFavoriteStatus(md.CurrentUser.ID, this.record.EntityInfo.Name, this.record.PrimaryKey, isFavorite);
179
171
  this._isFavorite = isFavorite;
180
172
  });
181
173
  }
@@ -209,44 +201,56 @@ export class BaseFormComponent extends BaseRecordComponent {
209
201
  GridEditMode() {
210
202
  return this.EditMode ? "Queue" : "None";
211
203
  }
212
- GetTabIndex(tabName) {
213
- return this._tabIndexes.indexOf(tabName);
214
- }
215
- RegisterTabs(tabs) {
216
- // copy the array, clear existing array and copy all elements
217
- this._tabIndexes.splice(0, this._tabIndexes.length);
218
- for (let i = 0; i < tabs.length; i++)
219
- this.RegisterTab(tabs[i]);
220
- }
221
- RegisterTab(tabName) {
222
- const currentIndex = this._tabIndexes.indexOf(tabName);
223
- if (currentIndex === -1) {
224
- this._tabIndexes.push(tabName);
225
- return this._tabIndexes.length - 1;
226
- }
227
- else
228
- return currentIndex;
229
- }
204
+ /**
205
+ * Returns true if the tabName specified is the currently displayed tab, otherwise returns false
206
+ * @param tabName
207
+ * @returns
208
+ */
230
209
  IsCurrentTab(tabName) {
231
- if (this._tabIndexes.length === 0 || this._tabIndexes.indexOf(tabName) === -1)
232
- return false;
233
- else
234
- return this.activeTabIndex === this.GetTabIndex(tabName);
235
- }
236
- RegisterAndCheckIfCurrentTab(tabName) {
237
- this.RegisterTab(tabName);
238
- return this.IsCurrentTab(tabName);
210
+ var _a, _b;
211
+ const tab = (_a = this.tabComponent) === null || _a === void 0 ? void 0 : _a.GetTabByName(tabName);
212
+ return (tab === null || tab === void 0 ? void 0 : tab.index) === ((_b = this.tabComponent) === null || _b === void 0 ? void 0 : _b.SelectedTabIndex);
239
213
  }
240
214
  BuildRelationshipViewParams(item) {
241
215
  return EntityInfo.BuildRelationshipViewParams(this.record, item); // new helper method in EntityInfo
242
216
  }
217
+ /**
218
+ * Builds a RunViewParams object for a related entity based on the relationship between the current entity and the related entity
219
+ * @param relatedEntityName
220
+ * @returns
221
+ */
243
222
  BuildRelationshipViewParamsByEntityName(relatedEntityName) {
223
+ const eri = this.GetEntityRelationshipByRelatedEntityName(relatedEntityName);
224
+ if (eri)
225
+ return this.BuildRelationshipViewParams(eri);
226
+ else
227
+ return {};
228
+ }
229
+ /**
230
+ * Looks up and returns the EntityRelationshipInfo object for the related entity name
231
+ * @param relatedEntityName
232
+ * @returns
233
+ */
234
+ GetEntityRelationshipByRelatedEntityName(relatedEntityName) {
244
235
  if (this.record) {
245
- const eri = this.record.EntityInfo.RelatedEntities.find(x => x.RelatedEntity === relatedEntityName);
246
- if (eri)
247
- return this.BuildRelationshipViewParams(eri);
236
+ return this.record.EntityInfo.RelatedEntities.find(x => x.RelatedEntity.trim().toLowerCase() === relatedEntityName.trim().toLowerCase());
248
237
  }
249
- return {};
238
+ return undefined;
239
+ }
240
+ /**
241
+ * Builds new record values for a related entity based on the relationship between the current entity and the related entity
242
+ * @param relatedEntityName
243
+ * @returns
244
+ */
245
+ NewRecordValues(relatedEntityName) {
246
+ const eri = this.GetEntityRelationshipByRelatedEntityName(relatedEntityName);
247
+ if (eri)
248
+ return this.NewRecordValuesByEntityRelationship(eri);
249
+ else
250
+ return {};
251
+ }
252
+ NewRecordValuesByEntityRelationship(item) {
253
+ return EntityInfo.BuildRelationshipNewRecordValues(this.record, item); // new helper method in EntityInfo
250
254
  }
251
255
  GetRelatedEntityTabDisplayName(relatedEntityName) {
252
256
  if (this.record) {
@@ -276,18 +280,31 @@ export class BaseFormComponent extends BaseRecordComponent {
276
280
  }
277
281
  return valResults;
278
282
  }
283
+ RaiseEvent(eventCode) {
284
+ const p = [];
285
+ const event = {
286
+ subEventCode: eventCode,
287
+ elementRef: this.elementRef,
288
+ returnValue: {
289
+ pendingRecords: p
290
+ }
291
+ };
292
+ MJGlobal.Instance.RaiseEvent({
293
+ event: MJEventType.ComponentEvent,
294
+ component: this,
295
+ eventCode: BaseFormComponentEventCodes.BASE_CODE,
296
+ args: event
297
+ });
298
+ return event.returnValue; // if the recipients of the event provided any values, we have them now
299
+ }
279
300
  SaveRecord(StopEditModeAfterSave) {
280
301
  return __awaiter(this, void 0, void 0, function* () {
281
302
  try {
282
303
  try {
283
304
  // atempt to blur the active element to force any pending changes to be committed
284
305
  document.activeElement.blur();
285
- if (this.userViewGridComponents.length > 0) {
286
- // tell all our grids to finish up their editing
287
- // if we have grids, create a slight delay, ensuring focus is lost on components that require that to update the model
288
- this.userViewGridComponents.forEach(grid => { grid.EditingComplete(); });
289
- yield this.Wait(100);
290
- }
306
+ // notify child components of this component that they must stop editing with an EDITING_COMPLETE message via MJ_global
307
+ this.RaiseEvent('EDITING_COMPLETE');
291
308
  }
292
309
  catch (e2) {
293
310
  // ignore
@@ -341,12 +358,7 @@ export class BaseFormComponent extends BaseRecordComponent {
341
358
  p.Revert();
342
359
  });
343
360
  }
344
- if (this.userViewGridComponents && this.userViewGridComponents.length > 0) {
345
- // we have grids, so we need to revert them as well
346
- this.userViewGridComponents.forEach(grid => {
347
- grid.RevertPendingChanges();
348
- });
349
- }
361
+ this.RaiseEvent('REVERT_PENDING_CHANGES');
350
362
  }
351
363
  // if we get here we are good to go
352
364
  this.EditMode = false;
@@ -366,21 +378,14 @@ export class BaseFormComponent extends BaseRecordComponent {
366
378
  PopulatePendingRecords() {
367
379
  return __awaiter(this, void 0, void 0, function* () {
368
380
  // this method is called by the parent class at the right time to populate all of the pending records for a transaction
369
- // all we do is talk to all of our grids and get their pending records and xfer them over to the PendingRecords array
381
+ // all we do is talk to all of our child compoennts and get their pending records and xfer them over to the PendingRecords array
370
382
  // the parent class will then take care of the rest...
371
383
  this._pendingRecords = []; // wipe out our array first
372
- const grids = this.userViewGridComponents;
373
- if (grids && grids.length > 0) {
374
- for (let i = 0; i < grids.length; i++) {
375
- const grid = grids.get(i);
376
- if (grid) {
377
- yield grid.EditingComplete(); // need to check this and wait for it to make sure grid editing is done before we try to save
378
- grid.PendingRecords.forEach(p => {
379
- // populate our pending grids array from the composite of all the grids
380
- this.PendingRecords.push(p.record);
381
- });
382
- }
383
- }
384
+ this.RaiseEvent('EDITING_COMPLETE'); // first tell the child components to finish up their editing
385
+ const result = this.RaiseEvent('POPULATE_PENDING_RECORDS');
386
+ if (result && result.pendingRecords) {
387
+ for (const p of result.pendingRecords)
388
+ this.PendingRecords.push(p);
384
389
  }
385
390
  });
386
391
  }
@@ -456,22 +461,12 @@ export class BaseFormComponent extends BaseRecordComponent {
456
461
  splitterLayoutChange() {
457
462
  this.splitterLayoutChangeSubject.next();
458
463
  }
459
- ResizeGrids() {
460
- // do with a little timeout to let the visible grid show up and the DOM settle
461
- setTimeout(() => {
462
- if (this.userViewGridComponents && this.userViewGridComponents.length > 0) {
463
- this.userViewGridComponents.forEach(grid => {
464
- //grid.ResizeGrid(); // automatic now via mjFillContainer
465
- });
466
- }
467
- }, 10);
468
- }
469
464
  ShowDependencies() {
470
465
  return __awaiter(this, void 0, void 0, function* () {
471
466
  // for now dump to console
472
467
  const md = new Metadata();
473
- const dep = yield md.GetRecordDependencies(this.record.EntityInfo.Name, this.record.CompositeKey);
474
- console.log('Dependencies for: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.Value);
468
+ const dep = yield md.GetRecordDependencies(this.record.EntityInfo.Name, this.record.PrimaryKey);
469
+ console.log('Dependencies for: ' + this.record.EntityInfo.Name + ' ' + this.record.PrimaryKey.ToString());
475
470
  console.log(dep);
476
471
  // if (confirm('Do you want to merge records test?') == true) {
477
472
  // const mergeResult = await md.MergeRecords({
@@ -486,7 +481,7 @@ export class BaseFormComponent extends BaseRecordComponent {
486
481
  GetRecordDependencies() {
487
482
  return __awaiter(this, void 0, void 0, function* () {
488
483
  const md = new Metadata();
489
- const dependencies = yield md.GetRecordDependencies(this.record.EntityInfo.Name, this.record.CompositeKey);
484
+ const dependencies = yield md.GetRecordDependencies(this.record.EntityInfo.Name, this.record.PrimaryKey);
490
485
  return dependencies;
491
486
  });
492
487
  }
@@ -504,26 +499,26 @@ export class BaseFormComponent extends BaseRecordComponent {
504
499
  }
505
500
  }
506
501
  }
507
- BaseFormComponent.ɵfac = function BaseFormComponent_Factory(t) { return new (t || BaseFormComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.SharedService), i0.ɵɵdirectiveInject(i2.Router), i0.ɵɵdirectiveInject(i2.ActivatedRoute)); };
502
+ BaseFormComponent.ɵfac = function BaseFormComponent_Factory(t) { return new (t || BaseFormComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.SharedService), i0.ɵɵdirectiveInject(i2.Router), i0.ɵɵdirectiveInject(i2.ActivatedRoute), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
508
503
  BaseFormComponent.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseFormComponent, viewQuery: function BaseFormComponent_Query(rf, ctx) { if (rf & 1) {
509
504
  i0.ɵɵviewQuery(MJTabStripComponent, 5);
510
505
  i0.ɵɵviewQuery(_c0, 5);
511
- i0.ɵɵviewQuery(UserViewGridComponent, 5);
506
+ i0.ɵɵviewQuery(MJTabStripComponent, 5);
512
507
  } if (rf & 2) {
513
508
  let _t;
514
509
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.tabComponent = _t.first);
515
510
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.topArea = _t.first);
516
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.userViewGridComponents = _t);
511
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.tabStrips = _t);
517
512
  } }, features: [i0.ɵɵInheritDefinitionFeature] });
518
513
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseFormComponent, [{
519
514
  type: Directive
520
- }], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }], { tabComponent: [{
515
+ }], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i0.ChangeDetectorRef }], { tabComponent: [{
521
516
  type: ViewChild,
522
517
  args: [MJTabStripComponent, { static: false }]
518
+ }], tabStrips: [{
519
+ type: ViewChildren,
520
+ args: [MJTabStripComponent]
523
521
  }], topArea: [{
524
522
  type: ViewChild,
525
523
  args: ['topArea']
526
- }], userViewGridComponents: [{
527
- type: ViewChildren,
528
- args: [UserViewGridComponent]
529
524
  }] }); })();
@@ -0,0 +1,42 @@
1
+ import { AfterViewInit, ElementRef } from "@angular/core";
2
+ import { BaseRecordComponent } from "./base-record-component";
3
+ import { BaseEntity, EntityFieldInfo, EntityInfo } from "@memberjunction/core";
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * This component is used to automatically generate a UI for any field in a given BaseEntity object. The CodeGen tool will generate forms and form sections that
7
+ * use this component. This component automatically determines the type of the field and generates the appropriate UI element for it. It is possible to use other
8
+ * elements to render a field as desired in a custom form, think of this component as a nice "base" component you can use for many cases, and you can create custom
9
+ * components for field rendering/editing when needed.
10
+ */
11
+ export declare class MJLinkField extends BaseRecordComponent implements AfterViewInit {
12
+ /**
13
+ * The record object that contains the field to be rendered. This object should be an instance of BaseEntity or a derived class.
14
+ */
15
+ record: BaseEntity;
16
+ /**
17
+ * The name of the field in the entity to be rendered.
18
+ */
19
+ FieldName: string;
20
+ /**
21
+ * For users of this component, if you already know the name of the record we are linking to, provide it here to reduce DB overhead
22
+ */
23
+ RecordName?: string;
24
+ RelatedEntityInfo: EntityInfo | undefined;
25
+ RecordLinked: boolean;
26
+ matchingRecords: BaseEntity[];
27
+ showMatchingRecords: boolean;
28
+ inputBox: ElementRef;
29
+ ngAfterViewInit(): Promise<void>;
30
+ protected AttemptToLinkValue(): Promise<void>;
31
+ get Value(): any;
32
+ set Value(val: any);
33
+ get EntityField(): EntityFieldInfo | undefined;
34
+ get DisplayName(): string;
35
+ onClearClicked(): void;
36
+ RelatedEntityNameField: string;
37
+ protected fetchMatchingRecords(query: string): Promise<void>;
38
+ onRecordSelected(linkedRecord: BaseEntity): void;
39
+ static ɵfac: i0.ɵɵFactoryDeclaration<MJLinkField, never>;
40
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJLinkField, "mj-link-field", never, { "record": { "alias": "record"; "required": false; }; "FieldName": { "alias": "FieldName"; "required": false; }; "RecordName": { "alias": "RecordName"; "required": false; }; }, {}, never, never, false, never>;
41
+ }
42
+ //# sourceMappingURL=link-field.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-field.component.d.ts","sourceRoot":"","sources":["../../src/lib/link-field.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAa,UAAU,EAAoB,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAgB,eAAe,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;;AAGhH;;;;;GAKG;AACH,qBAKa,WAAY,SAAQ,mBAAoB,YAAW,aAAa;IACzE;;OAEG;IACM,MAAM,EAAG,UAAU,CAAC;IAE7B;;OAEG;IACM,SAAS,EAAE,MAAM,CAAM;IAEhC;;OAEG;IACM,UAAU,CAAC,EAAE,MAAM,CAAM;IAE3B,iBAAiB,EAAE,UAAU,GAAG,SAAS,CAAa;IACtD,YAAY,EAAE,OAAO,CAAS;IAC9B,eAAe,EAAE,UAAU,EAAE,CAAM;IACnC,mBAAmB,EAAE,OAAO,CAAS;IAEH,QAAQ,EAAG,UAAU,CAAC;IAGzD,eAAe;cA4BL,kBAAkB;IAoBlC,IAAW,KAAK,IAAI,GAAG,CAEtB;IAED,IAAW,KAAK,CAAC,GAAG,EAAE,GAAG,EAGxB;IAED,IAAW,WAAW,IAAI,eAAe,GAAG,SAAS,CAEpD;IACD,IAAW,WAAW,IAAI,MAAM,CAM/B;IAGM,cAAc;IAOd,sBAAsB,EAAE,MAAM,CAAM;cAC3B,oBAAoB,CAAC,KAAK,EAAE,MAAM;IA0B3C,gBAAgB,CAAC,YAAY,EAAE,UAAU;yCA/HvC,WAAW;2CAAX,WAAW;CAqIvB"}
@@ -0,0 +1,207 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Component, Input, ViewChild } from "@angular/core";
11
+ import { BaseRecordComponent } from "./base-record-component";
12
+ import { CompositeKey, Metadata, RunView } from "@memberjunction/core";
13
+ import { debounceTime, fromEvent } from 'rxjs';
14
+ import * as i0 from "@angular/core";
15
+ import * as i1 from "@angular/common";
16
+ import * as i2 from "@angular/forms";
17
+ import * as i3 from "@progress/kendo-angular-buttons";
18
+ import * as i4 from "@progress/kendo-angular-inputs";
19
+ const _c0 = ["inputBox"];
20
+ function MJLinkField_ul_5_li_1_Template(rf, ctx) { if (rf & 1) {
21
+ const _r5 = i0.ɵɵgetCurrentView();
22
+ i0.ɵɵelementStart(0, "li", 6);
23
+ i0.ɵɵlistener("click", function MJLinkField_ul_5_li_1_Template_li_click_0_listener() { const restoredCtx = i0.ɵɵrestoreView(_r5); const record_r3 = restoredCtx.$implicit; const ctx_r4 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r4.onRecordSelected(record_r3)); });
24
+ i0.ɵɵtext(1);
25
+ i0.ɵɵelementEnd();
26
+ } if (rf & 2) {
27
+ const record_r3 = ctx.$implicit;
28
+ const ctx_r2 = i0.ɵɵnextContext(2);
29
+ i0.ɵɵadvance();
30
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.RelatedEntityNameField ? record_r3.Get(ctx_r2.RelatedEntityNameField) : "No Value", " ");
31
+ } }
32
+ function MJLinkField_ul_5_Template(rf, ctx) { if (rf & 1) {
33
+ i0.ɵɵelementStart(0, "ul", 4);
34
+ i0.ɵɵtemplate(1, MJLinkField_ul_5_li_1_Template, 2, 1, "li", 5);
35
+ i0.ɵɵelementEnd();
36
+ } if (rf & 2) {
37
+ const ctx_r1 = i0.ɵɵnextContext();
38
+ i0.ɵɵadvance();
39
+ i0.ɵɵproperty("ngForOf", ctx_r1.matchingRecords);
40
+ } }
41
+ /**
42
+ * This component is used to automatically generate a UI for any field in a given BaseEntity object. The CodeGen tool will generate forms and form sections that
43
+ * use this component. This component automatically determines the type of the field and generates the appropriate UI element for it. It is possible to use other
44
+ * elements to render a field as desired in a custom form, think of this component as a nice "base" component you can use for many cases, and you can create custom
45
+ * components for field rendering/editing when needed.
46
+ */
47
+ export class MJLinkField extends BaseRecordComponent {
48
+ constructor() {
49
+ super(...arguments);
50
+ /**
51
+ * The name of the field in the entity to be rendered.
52
+ */
53
+ this.FieldName = '';
54
+ /**
55
+ * For users of this component, if you already know the name of the record we are linking to, provide it here to reduce DB overhead
56
+ */
57
+ this.RecordName = '';
58
+ this.RelatedEntityInfo = undefined;
59
+ this.RecordLinked = false;
60
+ this.matchingRecords = [];
61
+ this.showMatchingRecords = false;
62
+ this.RelatedEntityNameField = '';
63
+ }
64
+ ngAfterViewInit() {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ if (!this.record)
67
+ throw new Error('record property is required');
68
+ if (!this.FieldName)
69
+ throw new Error('FieldName property is required');
70
+ yield this.AttemptToLinkValue();
71
+ fromEvent(this.inputBox.nativeElement, 'input')
72
+ .pipe(debounceTime(300))
73
+ .subscribe(() => {
74
+ if (!this.RecordLinked) {
75
+ this.showMatchingRecords = true;
76
+ this.fetchMatchingRecords(this.inputBox.nativeElement.value);
77
+ }
78
+ });
79
+ fromEvent(this.inputBox.nativeElement, 'keydown').subscribe((event) => {
80
+ if (this.RecordLinked && !['Backspace', 'Delete'].includes(event.key)) {
81
+ event.preventDefault();
82
+ }
83
+ else if (['Backspace', 'Delete'].includes(event.key)) {
84
+ this.RecordName = '';
85
+ this.Value = null;
86
+ this.RecordLinked = false;
87
+ }
88
+ });
89
+ });
90
+ }
91
+ AttemptToLinkValue() {
92
+ var _a;
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ const relatedEntityID = (_a = this.EntityField) === null || _a === void 0 ? void 0 : _a.RelatedEntityID;
95
+ const md = new Metadata();
96
+ this.RecordLinked = false;
97
+ if (relatedEntityID) {
98
+ this.RelatedEntityInfo = md.EntityByID(relatedEntityID);
99
+ if (!this.RecordName) {
100
+ if (this.Value) {
101
+ // at present only support single valued FOREIGN KEYs, will need to upgrade this soon
102
+ const pk = new CompositeKey([{ FieldName: this.RelatedEntityInfo.FirstPrimaryKey.Name, Value: this.Value }]);
103
+ this.RecordName = yield md.GetEntityRecordName(this.RelatedEntityInfo.Name, pk);
104
+ if (!this.RecordName)
105
+ this.RecordName = this.Value;
106
+ this.RecordLinked = true;
107
+ }
108
+ }
109
+ }
110
+ });
111
+ }
112
+ get Value() {
113
+ return this.record.Get(this.FieldName);
114
+ }
115
+ set Value(val) {
116
+ this.record.Set(this.FieldName, val);
117
+ this.AttemptToLinkValue();
118
+ }
119
+ get EntityField() {
120
+ var _a;
121
+ return (_a = this.record.Fields.find(f => f.Name === this.FieldName)) === null || _a === void 0 ? void 0 : _a.EntityFieldInfo;
122
+ }
123
+ get DisplayName() {
124
+ const ef = this.EntityField;
125
+ if (ef)
126
+ return ef.DisplayNameOrName;
127
+ else
128
+ return this.FieldName;
129
+ }
130
+ onClearClicked() {
131
+ this.RecordName = '';
132
+ this.RecordLinked = false;
133
+ this.Value = null;
134
+ this.showMatchingRecords = false;
135
+ }
136
+ fetchMatchingRecords(query) {
137
+ var _a, _b;
138
+ return __awaiter(this, void 0, void 0, function* () {
139
+ const relatedEntityID = (_a = this.EntityField) === null || _a === void 0 ? void 0 : _a.RelatedEntityID;
140
+ const md = new Metadata();
141
+ this.RecordLinked = false;
142
+ this.matchingRecords = [];
143
+ if (relatedEntityID) {
144
+ this.RelatedEntityInfo = md.EntityByID(relatedEntityID);
145
+ // do a lookup using runView for records that have the Name field that start with the query
146
+ this.RelatedEntityNameField = this.RelatedEntityInfo.NameField ? this.RelatedEntityInfo.NameField.Name : '';
147
+ if (this.RelatedEntityNameField) {
148
+ const filter = `[${this.RelatedEntityNameField}] LIKE '${query}%'`;
149
+ const rv = new RunView();
150
+ const result = yield rv.RunView({
151
+ EntityName: this.RelatedEntityInfo.Name,
152
+ ExtraFilter: filter,
153
+ OrderBy: this.RelatedEntityNameField,
154
+ ResultType: 'entity_object',
155
+ MaxRows: 5
156
+ });
157
+ if (result && ((_b = result.Results) === null || _b === void 0 ? void 0 : _b.length) > 0) {
158
+ this.matchingRecords = result.Results.filter(record => record[this.RelatedEntityNameField].toLowerCase().includes(query.toLowerCase()));
159
+ }
160
+ }
161
+ }
162
+ });
163
+ }
164
+ onRecordSelected(linkedRecord) {
165
+ this.RecordName = linkedRecord.Get(this.RelatedEntityNameField);
166
+ this.RecordLinked = true;
167
+ this.showMatchingRecords = false;
168
+ this.record.Set(this.FieldName, linkedRecord.FirstPrimaryKey.Value);
169
+ }
170
+ }
171
+ MJLinkField.ɵfac = /*@__PURE__*/ (() => { let ɵMJLinkField_BaseFactory; return function MJLinkField_Factory(t) { return (ɵMJLinkField_BaseFactory || (ɵMJLinkField_BaseFactory = i0.ɵɵgetInheritedFactory(MJLinkField)))(t || MJLinkField); }; })();
172
+ MJLinkField.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJLinkField, selectors: [["mj-link-field"]], viewQuery: function MJLinkField_Query(rf, ctx) { if (rf & 1) {
173
+ i0.ɵɵviewQuery(_c0, 7);
174
+ } if (rf & 2) {
175
+ let _t;
176
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.inputBox = _t.first);
177
+ } }, inputs: { record: "record", FieldName: "FieldName", RecordName: "RecordName" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 3, consts: [["type", "text", "kendoTextBox", "", 1, "input-box", 3, "ngClass", "ngModel", "ngModelChange"], ["inputBox", ""], ["kendoButton", "", 3, "click"], ["class", "matching-records", 4, "ngIf"], [1, "matching-records"], [3, "click", 4, "ngFor", "ngForOf"], [3, "click"]], template: function MJLinkField_Template(rf, ctx) { if (rf & 1) {
178
+ i0.ɵɵelementStart(0, "div")(1, "input", 0, 1);
179
+ i0.ɵɵtwoWayListener("ngModelChange", function MJLinkField_Template_input_ngModelChange_1_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.RecordName, $event) || (ctx.RecordName = $event); return $event; });
180
+ i0.ɵɵelementEnd();
181
+ i0.ɵɵelementStart(3, "button", 2);
182
+ i0.ɵɵlistener("click", function MJLinkField_Template_button_click_3_listener() { return ctx.onClearClicked(); });
183
+ i0.ɵɵtext(4, "Clear");
184
+ i0.ɵɵelementEnd();
185
+ i0.ɵɵtemplate(5, MJLinkField_ul_5_Template, 2, 1, "ul", 3);
186
+ i0.ɵɵelementEnd();
187
+ } if (rf & 2) {
188
+ i0.ɵɵadvance();
189
+ i0.ɵɵproperty("ngClass", ctx.RecordLinked ? "record-linked" : "");
190
+ i0.ɵɵtwoWayProperty("ngModel", ctx.RecordName);
191
+ i0.ɵɵadvance(4);
192
+ i0.ɵɵproperty("ngIf", ctx.showMatchingRecords);
193
+ } }, dependencies: [i1.NgClass, i1.NgForOf, i1.NgIf, i2.DefaultValueAccessor, i2.NgControlStatus, i2.NgModel, i3.ButtonComponent, i4.TextBoxDirective], styles: [".record-linked[_ngcontent-%COMP%] {\n color: darkblue;\n text-decoration: underline;\n}\n\n.input-box[_ngcontent-%COMP%] {\n width: 400px;\n}\n\nbutton[_ngcontent-%COMP%] {\n width: 50px;\n}\n\n\n.matching-records[_ngcontent-%COMP%] {\n width: 400px;\n list-style-type: none;\n margin: 0;\n padding: 0;\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid #ccc;\n}\n\n.matching-records[_ngcontent-%COMP%] li[_ngcontent-%COMP%] {\n padding: 8px;\n cursor: pointer;\n}\n\n.matching-records[_ngcontent-%COMP%] li[_ngcontent-%COMP%]:hover {\n background-color: #f0f0f0;\n}"] });
194
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJLinkField, [{
195
+ type: Component,
196
+ args: [{ selector: 'mj-link-field', template: "<!-- <input type=\"text\" class=\"input-box\" [ngClass]=\"RecordLinked ? 'record-linked' : ''\" [(ngModel)]=\"RecordName\" kendoTextBox /><button kendoButton (click)=\"onClearClicked()\">Clear</button>\n -->\n\n<div>\n <input \n #inputBox\n type=\"text\" \n class=\"input-box\" \n [ngClass]=\"RecordLinked ? 'record-linked' : ''\" \n [(ngModel)]=\"RecordName\" \n kendoTextBox \n />\n <button kendoButton (click)=\"onClearClicked()\">Clear</button>\n\n <ul *ngIf=\"showMatchingRecords\" class=\"matching-records\">\n <li *ngFor=\"let record of matchingRecords\" (click)=\"onRecordSelected(record)\">\n {{ RelatedEntityNameField ? record.Get(RelatedEntityNameField) : 'No Value' }}\n </li>\n </ul>\n</div>\n", styles: [".record-linked {\n color: darkblue;\n text-decoration: underline;\n}\n\n.input-box {\n width: 400px;\n}\n\nbutton {\n width: 50px;\n}\n\n\n.matching-records {\n width: 400px;\n list-style-type: none;\n margin: 0;\n padding: 0;\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid #ccc;\n}\n\n.matching-records li {\n padding: 8px;\n cursor: pointer;\n}\n\n.matching-records li:hover {\n background-color: #f0f0f0;\n}\n"] }]
197
+ }], null, { record: [{
198
+ type: Input
199
+ }], FieldName: [{
200
+ type: Input
201
+ }], RecordName: [{
202
+ type: Input
203
+ }], inputBox: [{
204
+ type: ViewChild,
205
+ args: ['inputBox', { static: true }]
206
+ }] }); })();
207
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJLinkField, { className: "MJLinkField", filePath: "src/lib/link-field.component.ts", lineNumber: 17 }); })();
@@ -24,7 +24,7 @@ export class SectionLoaderComponent {
24
24
  Promise.resolve().then(() => this.loadComponent());
25
25
  }
26
26
  loadComponent() {
27
- console.log("loading component?", `${this.Entity}.${this.Section}`);
27
+ //console.log("loading component?", `${this.Entity}.${this.Section}`);
28
28
  const sectionInfo = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormSectionComponent, `${this.Entity}.${this.Section}`);
29
29
  if (sectionInfo) {
30
30
  const componentRef = this.container.createComponent(sectionInfo.SubClass);
package/dist/module.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import * as i1 from "./lib/section-loader-component";
3
- import * as i2 from "./lib/form-toolbar";
4
- import * as i3 from "./lib/base-field-component";
3
+ import * as i2 from "./lib/base-field-component";
4
+ import * as i3 from "./lib/link-field.component";
5
5
  import * as i4 from "@angular/common";
6
6
  import * as i5 from "@angular/forms";
7
7
  import * as i6 from "@memberjunction/ng-tabstrip";
@@ -11,12 +11,14 @@ import * as i9 from "@progress/kendo-angular-inputs";
11
11
  import * as i10 from "@progress/kendo-angular-dateinputs";
12
12
  import * as i11 from "@progress/kendo-angular-dropdowns";
13
13
  import * as i12 from "@memberjunction/ng-link-directives";
14
- import * as i13 from "@memberjunction/ng-ask-skip";
15
- import * as i14 from "@progress/kendo-angular-dialog";
16
- import * as i15 from "@progress/kendo-angular-indicators";
14
+ import * as i13 from "@progress/kendo-angular-dialog";
15
+ import * as i14 from "@progress/kendo-angular-indicators";
16
+ import * as i15 from "@memberjunction/ng-container-directives";
17
+ import * as i16 from "@memberjunction/ng-shared";
18
+ import * as i17 from "ngx-markdown";
17
19
  export declare class BaseFormsModule {
18
20
  static ɵfac: i0.ɵɵFactoryDeclaration<BaseFormsModule, never>;
19
- static ɵmod: i0.ɵɵNgModuleDeclaration<BaseFormsModule, [typeof i1.SectionLoaderComponent, typeof i2.FormToolbarComponent, typeof i3.MJFormField], [typeof i4.CommonModule, typeof i5.FormsModule, typeof i6.MJTabStripModule, typeof i7.RecordChangesModule, typeof i8.ButtonsModule, typeof i9.InputsModule, typeof i10.DateInputsModule, typeof i11.DropDownsModule, typeof i12.LinkDirectivesModule, typeof i13.AskSkipModule, typeof i14.DialogsModule, typeof i15.IndicatorsModule], [typeof i1.SectionLoaderComponent, typeof i2.FormToolbarComponent, typeof i3.MJFormField]>;
21
+ static ɵmod: i0.ɵɵNgModuleDeclaration<BaseFormsModule, [typeof i1.SectionLoaderComponent, typeof i2.MJFormField, typeof i3.MJLinkField], [typeof i4.CommonModule, typeof i5.FormsModule, typeof i6.MJTabStripModule, typeof i7.RecordChangesModule, typeof i8.ButtonsModule, typeof i9.InputsModule, typeof i10.DateInputsModule, typeof i11.DropDownsModule, typeof i12.LinkDirectivesModule, typeof i13.DialogsModule, typeof i14.IndicatorsModule, typeof i15.ContainerDirectivesModule, typeof i16.MemberJunctionSharedModule, typeof i17.MarkdownModule], [typeof i1.SectionLoaderComponent, typeof i2.MJFormField, typeof i3.MJLinkField]>;
20
22
  static ɵinj: i0.ɵɵInjectorDeclaration<BaseFormsModule>;
21
23
  }
22
24
  //# sourceMappingURL=module.d.ts.map