@memberjunction/ng-explorer-core 0.9.14 → 0.9.15

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.
@@ -30,6 +30,9 @@ export declare class SkipData {
30
30
  ReportTitle: string;
31
31
  DisplayType: string;
32
32
  ChartOptions: SkipChartOptions;
33
+ DrillDownView?: string;
34
+ DrillDownBaseViewField?: string;
35
+ DrillDownReportValueField?: string;
33
36
  UserMessage?: string;
34
37
  ReportExplanation?: string;
35
38
  Analysis?: string;
@@ -1,5 +1,6 @@
1
- import { SeriesType } from '@progress/kendo-angular-charts';
1
+ import { SeriesType, CategoryAxisTitle } from '@progress/kendo-angular-charts';
2
2
  import { SkipColumnInfo, SkipData } from '../ask-skip/ask-skip.component';
3
+ import { SeriesClickEvent } from '@progress/kendo-angular-charts';
3
4
  import * as i0 from "@angular/core";
4
5
  export declare class DynamicChartComponent {
5
6
  private _data;
@@ -9,18 +10,24 @@ export declare class DynamicChartComponent {
9
10
  chartType: string;
10
11
  xAxis: string | string[] | null;
11
12
  yAxis: string | string[] | null;
13
+ xLabel: string;
14
+ yLabel: string;
12
15
  columns: SkipColumnInfo[];
13
16
  private _skipData;
14
17
  get SkipData(): SkipData | undefined;
15
18
  set SkipData(d: SkipData | undefined);
16
19
  get kendoSeriesType(): SeriesType;
17
20
  private _axes;
18
- get chartCategoryAxes(): any[];
21
+ get chartCategoryAxes(): {
22
+ categories: string[];
23
+ title: CategoryAxisTitle;
24
+ }[];
19
25
  protected convertAxisToArray(axis: string | string[] | null): string[];
20
26
  private _series;
21
27
  get chartSeries(): any[];
22
28
  private colors;
23
29
  getSeriesColor(item: any): string;
30
+ onChartSeriesClick(e: SeriesClickEvent): void;
24
31
  static ɵfac: i0.ɵɵFactoryDeclaration<DynamicChartComponent, never>;
25
- static ɵcmp: i0.ɵɵComponentDeclaration<DynamicChartComponent, "app-dynamic-chart", never, { "data": "data"; "chartTitle": "chartTitle"; "chartType": "chartType"; "xAxis": "xAxis"; "yAxis": "yAxis"; "columns": "columns"; "SkipData": "SkipData"; }, {}, never, never, false, never>;
32
+ static ɵcmp: i0.ɵɵComponentDeclaration<DynamicChartComponent, "app-dynamic-chart", never, { "data": "data"; "chartTitle": "chartTitle"; "chartType": "chartType"; "xAxis": "xAxis"; "yAxis": "yAxis"; "xLabel": "xLabel"; "yLabel": "yLabel"; "columns": "columns"; "SkipData": "SkipData"; }, {}, never, never, false, never>;
26
33
  }
@@ -1,5 +1,8 @@
1
1
  import { Component, Input } from '@angular/core';
2
- import { LogError } from '@memberjunction/core';
2
+ import { EntityFieldTSType, LogError, LogStatus, Metadata } from '@memberjunction/core';
3
+ import { MJEventType, MJGlobal } from '@memberjunction/global';
4
+ import { EventCodes, SharedService } from '../shared/shared.service';
5
+ import { ResourceData } from './base-resource-component';
3
6
  import * as i0 from "@angular/core";
4
7
  import * as i1 from "@angular/common";
5
8
  import * as i2 from "@progress/kendo-angular-charts";
@@ -8,16 +11,15 @@ function DynamicChartComponent_kendo_chart_category_axis_item_3_Template(rf, ctx
8
11
  i0.ɵɵelement(0, "kendo-chart-category-axis-item", 4);
9
12
  } if (rf & 2) {
10
13
  const axis_r2 = ctx.$implicit;
11
- i0.ɵɵproperty("categories", axis_r2.categories);
14
+ i0.ɵɵproperty("title", axis_r2.title)("categories", axis_r2.categories);
12
15
  } }
13
16
  function DynamicChartComponent_kendo_chart_series_item_5_Template(rf, ctx) { if (rf & 1) {
14
17
  i0.ɵɵelement(0, "kendo-chart-series-item", 5);
15
18
  } if (rf & 2) {
16
19
  const item_r3 = ctx.$implicit;
17
20
  const ctx_r1 = i0.ɵɵnextContext();
18
- i0.ɵɵproperty("type", ctx_r1.kendoSeriesType)("data", item_r3.data)("name", item_r3.name);
21
+ i0.ɵɵproperty("color", ctx_r1.getSeriesColor(item_r3))("type", ctx_r1.kendoSeriesType)("data", item_r3.data)("name", item_r3.name);
19
22
  } }
20
- // removed [color]="getSeriesColor(item)" from the kendo-chart-series-item below
21
23
  export class DynamicChartComponent {
22
24
  constructor() {
23
25
  this._data = [];
@@ -25,14 +27,16 @@ export class DynamicChartComponent {
25
27
  this.chartType = 'column';
26
28
  this.xAxis = null;
27
29
  this.yAxis = null;
30
+ this.xLabel = '';
31
+ this.yLabel = '';
28
32
  this.columns = [];
29
33
  this._axes = [];
30
34
  this._series = [];
35
+ // simple default colors
31
36
  this.colors = [
32
37
  '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe',
33
38
  '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080',
34
39
  '#000000', '#7cb9e8', '#c9ffe5', '#b284be', '#5d8aa8', '#00308f', '#72a0c1', '#e32636', '#c46210', '#efdecd'
35
- // ... add more colors if needed
36
40
  ];
37
41
  }
38
42
  get data() {
@@ -53,6 +57,8 @@ export class DynamicChartComponent {
53
57
  this.data = d.SQLResults.results;
54
58
  this.columns = d.SQLResults.columns;
55
59
  this.xAxis = d.ChartOptions.xAxis;
60
+ this.xLabel = d.ChartOptions.xLabel;
61
+ this.yLabel = d.ChartOptions.yLabel;
56
62
  this.yAxis = d.ChartOptions.yAxis;
57
63
  this.chartType = d.DisplayType;
58
64
  this.chartTitle = d.ReportTitle;
@@ -69,7 +75,10 @@ export class DynamicChartComponent {
69
75
  for (let i = 0; i < xArray.length; i++) {
70
76
  const xItem = xArray[i];
71
77
  if (xItem !== null && xItem !== undefined && xItem.length > 0)
72
- result.push({ categories: this.data.map(x => x[xItem]) });
78
+ result.push({
79
+ categories: this.data.map(x => x[xItem]),
80
+ title: { text: this.xLabel }
81
+ });
73
82
  }
74
83
  }
75
84
  this._axes = result;
@@ -115,26 +124,81 @@ export class DynamicChartComponent {
115
124
  }
116
125
  }
117
126
  getSeriesColor(item) {
127
+ var _a;
118
128
  try {
119
- // HILTON - this is where you use the newly provided SKIP data to determine the color of the series
120
- const index = this.chartSeries.indexOf(item);
121
- return this.colors[index % this.colors.length];
129
+ const c = (_a = this.SkipData) === null || _a === void 0 ? void 0 : _a.ChartOptions.color;
130
+ if (c && c.length > 0) {
131
+ // use the configured color since it was provided
132
+ return c;
133
+ }
134
+ else {
135
+ const index = this.chartSeries.indexOf(item);
136
+ return this.colors[index % this.colors.length];
137
+ }
122
138
  }
123
139
  catch (ex) {
124
140
  LogError(ex);
125
141
  return '#000000';
126
142
  }
127
143
  }
144
+ onChartSeriesClick(e) {
145
+ var _a, _b;
146
+ try {
147
+ const drillDownValue = e.category; // contains the category for the clicked series item
148
+ const ddBaseViewField = (_a = this.SkipData) === null || _a === void 0 ? void 0 : _a.DrillDownBaseViewField;
149
+ const ddV = (_b = this.SkipData) === null || _b === void 0 ? void 0 : _b.DrillDownView;
150
+ if (ddBaseViewField && ddV && ddBaseViewField.length > 0 && ddV.length > 0 && drillDownValue && drillDownValue.length > 0) {
151
+ // we have a valid situation to drill down where we have the configuration and we have a drill down value.
152
+ const md = new Metadata();
153
+ const e = md.Entities.find(x => x.BaseView.trim().toLowerCase() === ddV.trim().toLowerCase());
154
+ if (e) {
155
+ // we have a valid entity for the drill down view
156
+ // now that we've validated all of this, we can navigate to the drill down view
157
+ // which is simply a dynamic view for a given entity with a filter applied
158
+ const rd = new ResourceData();
159
+ const ef = e.Fields.find(ef => ef.Name.trim().toLowerCase() === ddBaseViewField.trim().toLowerCase());
160
+ // next, fix up the drill down value to wrap with quotes if we need if we are a string or date, and also if a string, escape any single quotes
161
+ let filterVal = drillDownValue;
162
+ if ((ef === null || ef === void 0 ? void 0 : ef.TSType) === EntityFieldTSType.String) {
163
+ filterVal = `'${filterVal.replace(/'/g, "''")}'`;
164
+ }
165
+ else if ((ef === null || ef === void 0 ? void 0 : ef.TSType) === EntityFieldTSType.Date) {
166
+ filterVal = `'${filterVal}'`;
167
+ }
168
+ rd.ResourceTypeID = SharedService.Instance.ViewResourceType.ID;
169
+ rd.ResourceRecordID = 0;
170
+ rd.Configuration = {
171
+ Entity: e.Name,
172
+ ExtraFilter: `${ddBaseViewField} = ${filterVal}`,
173
+ };
174
+ // now we've built up our ResourceData object, we can raise the event to navigate to the drill down view
175
+ LogStatus(`drilling down to ${ddV} with filter ${ddBaseViewField} = ${drillDownValue}`);
176
+ MJGlobal.Instance.RaiseEvent({
177
+ component: this,
178
+ event: MJEventType.ComponentEvent,
179
+ eventCode: EventCodes.ViewClicked,
180
+ args: rd
181
+ });
182
+ }
183
+ else
184
+ LogError(`Could not find entity for the specified DrillDownView: ${ddV}`);
185
+ }
186
+ }
187
+ catch (e) {
188
+ LogError(e);
189
+ }
190
+ }
128
191
  }
129
192
  DynamicChartComponent.ɵfac = function DynamicChartComponent_Factory(t) { return new (t || DynamicChartComponent)(); };
130
- DynamicChartComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DynamicChartComponent, selectors: [["app-dynamic-chart"]], inputs: { data: "data", chartTitle: "chartTitle", chartType: "chartType", xAxis: "xAxis", yAxis: "yAxis", columns: "columns", SkipData: "SkipData" }, decls: 6, vars: 3, consts: [["mjFillContainer", ""], [3, "text"], [3, "categories", 4, "ngFor", "ngForOf"], [3, "type", "data", "name", 4, "ngFor", "ngForOf"], [3, "categories"], [3, "type", "data", "name"]], template: function DynamicChartComponent_Template(rf, ctx) { if (rf & 1) {
193
+ DynamicChartComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DynamicChartComponent, selectors: [["app-dynamic-chart"]], inputs: { data: "data", chartTitle: "chartTitle", chartType: "chartType", xAxis: "xAxis", yAxis: "yAxis", xLabel: "xLabel", yLabel: "yLabel", columns: "columns", SkipData: "SkipData" }, decls: 6, vars: 3, consts: [["mjFillContainer", "", 3, "seriesClick"], [3, "text"], [3, "title", "categories", 4, "ngFor", "ngForOf"], [3, "color", "type", "data", "name", 4, "ngFor", "ngForOf"], [3, "title", "categories"], [3, "color", "type", "data", "name"]], template: function DynamicChartComponent_Template(rf, ctx) { if (rf & 1) {
131
194
  i0.ɵɵelementStart(0, "kendo-chart", 0);
195
+ i0.ɵɵlistener("seriesClick", function DynamicChartComponent_Template_kendo_chart_seriesClick_0_listener($event) { return ctx.onChartSeriesClick($event); });
132
196
  i0.ɵɵelement(1, "kendo-chart-title", 1);
133
197
  i0.ɵɵelementStart(2, "kendo-chart-category-axis");
134
- i0.ɵɵtemplate(3, DynamicChartComponent_kendo_chart_category_axis_item_3_Template, 1, 1, "kendo-chart-category-axis-item", 2);
198
+ i0.ɵɵtemplate(3, DynamicChartComponent_kendo_chart_category_axis_item_3_Template, 1, 2, "kendo-chart-category-axis-item", 2);
135
199
  i0.ɵɵelementEnd();
136
200
  i0.ɵɵelementStart(4, "kendo-chart-series");
137
- i0.ɵɵtemplate(5, DynamicChartComponent_kendo_chart_series_item_5_Template, 1, 3, "kendo-chart-series-item", 3);
201
+ i0.ɵɵtemplate(5, DynamicChartComponent_kendo_chart_series_item_5_Template, 1, 4, "kendo-chart-series-item", 3);
138
202
  i0.ɵɵelementEnd()();
139
203
  } if (rf & 2) {
140
204
  i0.ɵɵadvance(1);
@@ -149,13 +213,16 @@ DynamicChartComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: Dynam
149
213
  args: [{
150
214
  selector: 'app-dynamic-chart',
151
215
  template: `
152
- <kendo-chart mjFillContainer>
216
+ <kendo-chart mjFillContainer
217
+ (seriesClick)="onChartSeriesClick($event)"
218
+ >
153
219
  <kendo-chart-title [text]="chartTitle"></kendo-chart-title>
154
220
  <kendo-chart-category-axis>
155
- <kendo-chart-category-axis-item *ngFor="let axis of chartCategoryAxes" [categories]="axis.categories"></kendo-chart-category-axis-item>
221
+ <kendo-chart-category-axis-item *ngFor="let axis of chartCategoryAxes" [title]="axis.title" [categories]="axis.categories"></kendo-chart-category-axis-item>
156
222
  </kendo-chart-category-axis>
157
223
  <kendo-chart-series>
158
224
  <kendo-chart-series-item *ngFor="let item of chartSeries"
225
+ [color]="getSeriesColor(item)"
159
226
  [type]="kendoSeriesType"
160
227
  [data]="item.data"
161
228
  [name]="item.name"></kendo-chart-series-item>
@@ -173,6 +240,10 @@ DynamicChartComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: Dynam
173
240
  type: Input
174
241
  }], yAxis: [{
175
242
  type: Input
243
+ }], xLabel: [{
244
+ type: Input
245
+ }], yLabel: [{
246
+ type: Input
176
247
  }], columns: [{
177
248
  type: Input
178
249
  }], SkipData: [{
@@ -19,6 +19,7 @@ export declare class DynamicGridComponent implements AfterViewInit {
19
19
  ngAfterViewInit(): void;
20
20
  pageChange(event: PageChangeEvent): void;
21
21
  private loadGridView;
22
+ cellClick(event: any): void;
22
23
  static ɵfac: i0.ɵɵFactoryDeclaration<DynamicGridComponent, never>;
23
24
  static ɵcmp: i0.ɵɵComponentDeclaration<DynamicGridComponent, "app-dynamic-grid", never, { "data": "data"; "columns": "columns"; "pageSize": "pageSize"; "startingRow": "startingRow"; "SkipData": "SkipData"; }, {}, never, never, false, never>;
24
25
  }
@@ -1,5 +1,6 @@
1
1
  import { Component, Input } from '@angular/core';
2
2
  import { DecimalPipe, DatePipe } from '@angular/common';
3
+ import { LogStatus } from '@memberjunction/core';
3
4
  import * as i0 from "@angular/core";
4
5
  import * as i1 from "@angular/common";
5
6
  import * as i2 from "@progress/kendo-angular-grid";
@@ -82,11 +83,15 @@ export class DynamicGridComponent {
82
83
  total: this.data.length,
83
84
  };
84
85
  }
86
+ cellClick(event) {
87
+ LogStatus(`Cell clicked in DynamicGrid`, undefined, event);
88
+ LogStatus('Need to implement cellClick in DynamicGridComponent like DyanmicChartComponent to do drill down!');
89
+ }
85
90
  }
86
91
  DynamicGridComponent.ɵfac = function DynamicGridComponent_Factory(t) { return new (t || DynamicGridComponent)(i0.ɵɵdirectiveInject(i1.DecimalPipe), i0.ɵɵdirectiveInject(i1.DatePipe)); };
87
- DynamicGridComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DynamicGridComponent, selectors: [["app-dynamic-grid"]], inputs: { data: "data", columns: "columns", pageSize: "pageSize", startingRow: "startingRow", SkipData: "SkipData" }, features: [i0.ɵɵProvidersFeature([DecimalPipe, DatePipe])], decls: 2, vars: 8, consts: [["scrollable", "virtual", 3, "data", "skip", "pageSize", "rowHeight", "reorderable", "resizable", "navigable", "pageChange"], [4, "ngFor", "ngForOf"], [3, "field", "title", "headerStyle"], ["kendoGridCellTemplate", ""]], template: function DynamicGridComponent_Template(rf, ctx) { if (rf & 1) {
92
+ DynamicGridComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DynamicGridComponent, selectors: [["app-dynamic-grid"]], inputs: { data: "data", columns: "columns", pageSize: "pageSize", startingRow: "startingRow", SkipData: "SkipData" }, features: [i0.ɵɵProvidersFeature([DecimalPipe, DatePipe])], decls: 2, vars: 8, consts: [["scrollable", "virtual", 3, "data", "skip", "pageSize", "rowHeight", "reorderable", "resizable", "navigable", "pageChange", "cellClick"], [4, "ngFor", "ngForOf"], [3, "field", "title", "headerStyle"], ["kendoGridCellTemplate", ""]], template: function DynamicGridComponent_Template(rf, ctx) { if (rf & 1) {
88
93
  i0.ɵɵelementStart(0, "kendo-grid", 0);
89
- i0.ɵɵlistener("pageChange", function DynamicGridComponent_Template_kendo_grid_pageChange_0_listener($event) { return ctx.pageChange($event); });
94
+ i0.ɵɵlistener("pageChange", function DynamicGridComponent_Template_kendo_grid_pageChange_0_listener($event) { return ctx.pageChange($event); })("cellClick", function DynamicGridComponent_Template_kendo_grid_cellClick_0_listener($event) { return ctx.cellClick($event); });
90
95
  i0.ɵɵtemplate(1, DynamicGridComponent_ng_container_1_Template, 3, 4, "ng-container", 1);
91
96
  i0.ɵɵelementEnd();
92
97
  } if (rf & 2) {
@@ -107,6 +112,7 @@ DynamicGridComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: Dynami
107
112
  [reorderable]="true"
108
113
  [resizable]="true"
109
114
  (pageChange)="pageChange($event)"
115
+ (cellClick)="cellClick($event)"
110
116
  [navigable]="true">
111
117
  <ng-container *ngFor="let col of columns">
112
118
  <kendo-grid-column
@@ -72,6 +72,7 @@ export declare class NavigationComponent implements OnInit, OnDestroy, AfterView
72
72
  private getWorkspace;
73
73
  LoadWorkSpace(): Promise<void>;
74
74
  protected setAppTitle(title?: string): void;
75
+ protected checkForExistingTab(data: ResourceData): Tab | null;
75
76
  protected AddOrSelectTab(data: ResourceData): Promise<void>;
76
77
  private updateBrowserURL;
77
78
  scrollIntoView(): void;
@@ -466,28 +466,49 @@ export class NavigationComponent {
466
466
  else
467
467
  this.titleService.setTitle(title + ' (' + this.applicationName + ')');
468
468
  }
469
- AddOrSelectTab(data) {
470
- return __awaiter(this, void 0, void 0, function* () {
471
- const t = this.tabs;
472
- this.loader = true;
473
- let existingTab;
474
- if (data.ResourceType.trim().toLowerCase() === 'search results') {
475
- // we have a different matching logic for search results because we want to match on the search input as well as the entity
469
+ checkForExistingTab(data) {
470
+ let existingTab;
471
+ if (data.ResourceType.trim().toLowerCase() === 'search results') {
472
+ // we have a different matching logic for search results because we want to match on the search input as well as the entity
473
+ existingTab = this.tabs.find(t => t.data.ResourceTypeID === data.ResourceTypeID &&
474
+ t.data.Configuration.Entity === data.Configuration.Entity &&
475
+ t.data.Configuration.SearchInput === data.Configuration.SearchInput);
476
+ }
477
+ else if (data.ResourceType.trim().toLowerCase() === 'user views') {
478
+ // a viwe can be either saved (where we have a view id) or dyanmic (where we have an entity name, and optionally, an extra filter)
479
+ if (data.ResourceRecordID > 0) {
480
+ // saved view
476
481
  existingTab = this.tabs.find(t => t.data.ResourceTypeID === data.ResourceTypeID &&
477
- t.data.Configuration.Entity === data.Configuration.Entity &&
478
- t.data.Configuration.SearchInput === data.Configuration.SearchInput);
482
+ t.data.ResourceRecordID === data.ResourceRecordID &&
483
+ data.ResourceRecordID !== null &&
484
+ data.ResourceRecordID !== undefined &&
485
+ data.ResourceRecordID > 0 // make sure that we don't match on null/undefined/0 ResourceRecordID's - these should always be NEW tabs
486
+ );
479
487
  }
480
488
  else {
489
+ // dynamic view, compare entity name and if we have extra filter use that for comparison too
481
490
  existingTab = this.tabs.find(t => t.data.ResourceTypeID === data.ResourceTypeID &&
482
- t.data.ResourceRecordID === data.ResourceRecordID &&
483
- data.ResourceRecordID !== null && data.ResourceRecordID !== undefined && data.ResourceRecordID > 0 // make sure that we don't match on null/undefined/0 ResourceRecordID's - these should always be NEW tabs
484
- );
491
+ t.data.Configuration.Entity === data.Configuration.Entity &&
492
+ t.data.Configuration.ExtraFilter === data.Configuration.ExtraFilter);
485
493
  }
494
+ }
495
+ else {
496
+ existingTab = this.tabs.find(t => t.data.ResourceTypeID === data.ResourceTypeID &&
497
+ t.data.ResourceRecordID === data.ResourceRecordID &&
498
+ data.ResourceRecordID !== null && data.ResourceRecordID !== undefined && data.ResourceRecordID > 0 // make sure that we don't match on null/undefined/0 ResourceRecordID's - these should always be NEW tabs
499
+ );
500
+ }
501
+ return existingTab;
502
+ }
503
+ AddOrSelectTab(data) {
504
+ return __awaiter(this, void 0, void 0, function* () {
505
+ const t = this.tabs;
506
+ this.loader = true;
507
+ const existingTab = this.checkForExistingTab(data);
486
508
  if (existingTab) {
487
509
  const index = this.tabs.indexOf(existingTab);
488
510
  this.activeTabIndex = index + 1; // add one because the HOME tab is not in the tabs array but it IS part of our tab structure
489
511
  this.tabstrip.selectTab(this.activeTabIndex);
490
- //this.renderer.selectRootElement(this.tabstrip.wrapper.nativeElement.nativeElement).focus()
491
512
  this.scrollIntoView();
492
513
  if (existingTab.label)
493
514
  this.setAppTitle(existingTab.label);
@@ -526,12 +547,26 @@ export class NavigationComponent {
526
547
  }
527
548
  updateBrowserURL(tab, data) {
528
549
  // update the URL to reflect the current tab
550
+ var _a;
529
551
  // FIRST, construct the base URL based on the resource type
530
552
  const rt = this.sharedService.ResourceTypeByID(data.ResourceTypeID);
531
553
  let url = '/resource';
532
554
  switch (rt === null || rt === void 0 ? void 0 : rt.Name.toLowerCase().trim()) {
533
555
  case 'user views':
534
- url += `/view/${data.ResourceRecordID}`;
556
+ if (data.ResourceRecordID && !isNaN(data.ResourceRecordID) && data.ResourceRecordID > 0) {
557
+ url += `/view/${data.ResourceRecordID}`;
558
+ }
559
+ else if ((_a = data.Configuration) === null || _a === void 0 ? void 0 : _a.Entity) {
560
+ // we don't have a view id. This can occur when we're referring to a dyanmic view where our data.Configuration.Entity is set and data.Configuration.ExtraFilter is set
561
+ // so we need to construct a URL that will load up the dynamic view
562
+ url += `/view/0?Entity=${data.Configuration.Entity}&ExtraFilter=${data.Configuration.ExtraFilter}`;
563
+ }
564
+ else {
565
+ // we don't have a view ID and we also don't have an entity name, so this is an error condition
566
+ LogError(`Invalid view configuration. No view ID or entity name specified.`);
567
+ this.sharedService.CreateSimpleNotification(`Invalid view configuration. No view ID or entity name specified.`, "error", 5000);
568
+ return;
569
+ }
535
570
  break;
536
571
  case 'dashboards':
537
572
  url += `/dashboard/${data.ResourceRecordID}`;
@@ -24,20 +24,28 @@ export function LoadViewResource() {
24
24
  }
25
25
  let UserViewResource = class UserViewResource extends BaseResourceComponent {
26
26
  GetResourceDisplayName(data) {
27
+ var _a, _b, _c;
27
28
  return __awaiter(this, void 0, void 0, function* () {
28
29
  const md = new Metadata();
29
- const name = yield md.GetEntityRecordName('User Views', data.ResourceRecordID);
30
- return name ? name : 'View: ' + data.ResourceRecordID;
30
+ if (data.ResourceRecordID > 0) {
31
+ const name = yield md.GetEntityRecordName('User Views', data.ResourceRecordID);
32
+ return name ? name : 'View: ' + data.ResourceRecordID;
33
+ }
34
+ else if (((_a = data.Configuration) === null || _a === void 0 ? void 0 : _a.Entity) && ((_b = data.Configuration) === null || _b === void 0 ? void 0 : _b.Entity.length) > 0) {
35
+ return `${(_c = data.Configuration) === null || _c === void 0 ? void 0 : _c.Entity} [Dynamic${data.Configuration.ExtraFilter ? ' - Filtered' : ' - All'}]`;
36
+ }
37
+ else
38
+ return 'User Views [Error]';
31
39
  });
32
40
  }
33
41
  };
34
42
  UserViewResource.ɵfac = /*@__PURE__*/ function () { let ɵUserViewResource_BaseFactory; return function UserViewResource_Factory(t) { return (ɵUserViewResource_BaseFactory || (ɵUserViewResource_BaseFactory = i0.ɵɵgetInheritedFactory(UserViewResource)))(t || UserViewResource); }; }();
35
- UserViewResource.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UserViewResource, selectors: [["userview-resource"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 1, vars: 1, consts: [[3, "viewId", "loadComplete"]], template: function UserViewResource_Template(rf, ctx) { if (rf & 1) {
43
+ UserViewResource.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UserViewResource, selectors: [["userview-resource"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 1, vars: 4, consts: [[3, "viewId", "viewName", "entityName", "extraFilter", "loadComplete"]], template: function UserViewResource_Template(rf, ctx) { if (rf & 1) {
36
44
  i0.ɵɵelementStart(0, "app-single-view", 0);
37
45
  i0.ɵɵlistener("loadComplete", function UserViewResource_Template_app_single_view_loadComplete_0_listener() { return ctx.NotifyLoadComplete(); });
38
46
  i0.ɵɵelementEnd();
39
47
  } if (rf & 2) {
40
- i0.ɵɵproperty("viewId", ctx.Data.ResourceRecordID);
48
+ i0.ɵɵproperty("viewId", ctx.Data.ResourceRecordID)("viewName", ctx.Data.Configuration == null ? null : ctx.Data.Configuration.ViewName)("entityName", ctx.Data.Configuration == null ? null : ctx.Data.Configuration.Entity)("extraFilter", ctx.Data.Configuration == null ? null : ctx.Data.Configuration.ExtraFilter);
41
49
  } }, dependencies: [i1.SingleViewComponent], encapsulation: 2 });
42
50
  UserViewResource = __decorate([
43
51
  RegisterClass(BaseResourceComponent, 'User Views')
@@ -47,6 +55,11 @@ export { UserViewResource };
47
55
  type: Component,
48
56
  args: [{
49
57
  selector: 'userview-resource',
50
- template: `<app-single-view [viewId]="Data.ResourceRecordID" (loadComplete)="NotifyLoadComplete()"></app-single-view>`
58
+ template: `<app-single-view [viewId]="Data.ResourceRecordID"
59
+ [viewName]="Data.Configuration?.ViewName"
60
+ [entityName]="Data.Configuration?.Entity"
61
+ [extraFilter]="Data.Configuration?.ExtraFilter"
62
+ (loadComplete)="NotifyLoadComplete()">
63
+ </app-single-view>`
51
64
  }]
52
65
  }], null, null); })();
@@ -11,7 +11,10 @@ export declare class SingleViewComponent implements AfterViewInit, OnInit {
11
11
  private sharedService;
12
12
  viewGrid: UserViewGridComponent;
13
13
  viewId: number | null;
14
+ viewName: string | null;
14
15
  selectedView: UserViewEntity | null;
16
+ extraFilter: string | null;
17
+ entityName: string | null;
15
18
  loadComplete: EventEmitter<any>;
16
19
  selectedEntity: EntityInfo | null;
17
20
  showSearch: boolean;
@@ -24,11 +27,12 @@ export declare class SingleViewComponent implements AfterViewInit, OnInit {
24
27
  private initialLoad;
25
28
  handleRowClick(args: GridRowClickedEvent): Promise<void>;
26
29
  LoadView(viewInfo: UserViewEntity): Promise<void>;
30
+ LoadDynamicView(): Promise<void>;
27
31
  Refresh(): Promise<void>;
28
32
  onSearch(inputValue: string): void;
29
33
  private setupSearchDebounce;
30
34
  private search;
31
35
  viewPropertiesDialogClosed(args: any): void;
32
36
  static ɵfac: i0.ɵɵFactoryDeclaration<SingleViewComponent, never>;
33
- static ɵcmp: i0.ɵɵComponentDeclaration<SingleViewComponent, "app-single-view", never, { "viewId": "viewId"; "selectedView": "selectedView"; }, { "loadComplete": "loadComplete"; }, never, never, false, never>;
37
+ static ɵcmp: i0.ɵɵComponentDeclaration<SingleViewComponent, "app-single-view", never, { "viewId": "viewId"; "viewName": "viewName"; "selectedView": "selectedView"; "extraFilter": "extraFilter"; "entityName": "entityName"; }, { "loadComplete": "loadComplete"; }, never, never, false, never>;
34
38
  }
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { Component, ViewChild, Input, Output, EventEmitter } from '@angular/core';
11
11
  import { UserViewGridComponent } from '@memberjunction/ng-user-view-grid';
12
- import { Metadata } from '@memberjunction/core';
12
+ import { Metadata, LogError } from '@memberjunction/core';
13
13
  import { distinctUntilChanged, Subject } from "rxjs";
14
14
  import { debounceTime } from "rxjs/operators";
15
15
  import { ViewInfo } from '@memberjunction/core-entities';
@@ -23,21 +23,33 @@ import * as i6 from "@memberjunction/ng-container-directives";
23
23
  import * as i7 from "@memberjunction/ng-user-view-grid";
24
24
  import * as i8 from "../user-view-properties/view-properties-dialog.component";
25
25
  function SingleViewComponent_kendo_textbox_2_Template(rf, ctx) { if (rf & 1) {
26
- const _r2 = i0.ɵɵgetCurrentView();
26
+ const _r3 = i0.ɵɵgetCurrentView();
27
27
  i0.ɵɵelementStart(0, "kendo-textbox", 5);
28
- i0.ɵɵlistener("valueChange", function SingleViewComponent_kendo_textbox_2_Template_kendo_textbox_valueChange_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSearch($event)); })("ngModelChange", function SingleViewComponent_kendo_textbox_2_Template_kendo_textbox_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r3 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r3.searchText = $event); });
28
+ i0.ɵɵlistener("valueChange", function SingleViewComponent_kendo_textbox_2_Template_kendo_textbox_valueChange_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onSearch($event)); })("ngModelChange", function SingleViewComponent_kendo_textbox_2_Template_kendo_textbox_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.searchText = $event); });
29
29
  i0.ɵɵelementEnd();
30
30
  } if (rf & 2) {
31
31
  const ctx_r0 = i0.ɵɵnextContext();
32
32
  i0.ɵɵproperty("clearButton", true)("ngModel", ctx_r0.searchText);
33
33
  } }
34
+ function SingleViewComponent_app_view_properties_dialog_3_Template(rf, ctx) { if (rf & 1) {
35
+ const _r6 = i0.ɵɵgetCurrentView();
36
+ i0.ɵɵelementStart(0, "app-view-properties-dialog", 6);
37
+ i0.ɵɵlistener("dialogClosed", function SingleViewComponent_app_view_properties_dialog_3_Template_app_view_properties_dialog_dialogClosed_0_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.viewPropertiesDialogClosed($event)); });
38
+ i0.ɵɵelementEnd();
39
+ } if (rf & 2) {
40
+ const ctx_r1 = i0.ɵɵnextContext();
41
+ i0.ɵɵproperty("ViewID", ctx_r1.selectedView.ID);
42
+ } }
34
43
  export class SingleViewComponent {
35
44
  constructor(router, route, sharedService) {
36
45
  this.router = router;
37
46
  this.route = route;
38
47
  this.sharedService = sharedService;
39
- this.viewId = -1;
48
+ this.viewId = null;
49
+ this.viewName = null;
40
50
  this.selectedView = null;
51
+ this.extraFilter = null;
52
+ this.entityName = null;
41
53
  this.loadComplete = new EventEmitter();
42
54
  this.selectedEntity = null;
43
55
  this.showSearch = false;
@@ -53,18 +65,36 @@ export class SingleViewComponent {
53
65
  }
54
66
  initialLoad() {
55
67
  return __awaiter(this, void 0, void 0, function* () {
56
- if (this.viewId) {
57
- const md = new Metadata();
58
- const view = yield ViewInfo.GetViewEntity(this.viewId);
68
+ const md = new Metadata();
69
+ if (this.viewId || this.viewName) {
70
+ let view = null;
71
+ if (this.viewId)
72
+ view = (yield ViewInfo.GetViewEntity(this.viewId));
73
+ else if (this.viewName)
74
+ view = (yield ViewInfo.GetViewEntityByName(this.viewName));
59
75
  if (view) {
60
76
  yield this.LoadView(view);
61
- const e = md.Entities.find(e => e.ID === view.EntityID);
77
+ const e = md.Entities.find(e => e.ID === (view === null || view === void 0 ? void 0 : view.EntityID));
62
78
  if (e) {
63
79
  this.selectedEntity = e;
64
80
  this.showSearch = e.AllowUserSearchAPI;
65
81
  }
66
82
  }
67
83
  }
84
+ else if (this.entityName && this.entityName.length > 0) {
85
+ // we are running a dynamic view here, not a view by ID
86
+ const e = md.Entities.find(e => { var _a; return e.Name.trim().toLowerCase() === ((_a = this.entityName) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase()); });
87
+ if (e) {
88
+ this.selectedEntity = e;
89
+ this.showSearch = e.AllowUserSearchAPI;
90
+ yield this.LoadDynamicView();
91
+ }
92
+ else {
93
+ // problem, we don't have a valid entity name
94
+ LogError(`Invalid entity name: ${this.entityName}`);
95
+ this.sharedService.CreateSimpleNotification(`The entity name ${this.entityName} is not valid. Please check the URL and try again.`, "error", 5000);
96
+ }
97
+ }
68
98
  });
69
99
  }
70
100
  handleRowClick(args) {
@@ -86,10 +116,23 @@ export class SingleViewComponent {
86
116
  this.loadComplete.emit();
87
117
  });
88
118
  }
119
+ LoadDynamicView() {
120
+ return __awaiter(this, void 0, void 0, function* () {
121
+ this.selectedView = null;
122
+ yield this.viewGrid.Refresh({
123
+ EntityName: this.entityName,
124
+ ExtraFilter: this.extraFilter,
125
+ UserSearchString: this.searchText
126
+ });
127
+ this.loadComplete.emit();
128
+ });
129
+ }
89
130
  Refresh() {
90
131
  return __awaiter(this, void 0, void 0, function* () {
91
132
  if (this.selectedView)
92
133
  yield this.LoadView(this.selectedView);
134
+ else
135
+ yield this.LoadDynamicView();
93
136
  });
94
137
  }
95
138
  onSearch(inputValue) {
@@ -102,12 +145,10 @@ export class SingleViewComponent {
102
145
  });
103
146
  }
104
147
  search(inputValue) {
105
- if (this.selectedView && this.selectedView.ID && this.selectedView.ID > 0) {
106
- this.viewGrid.Refresh({
107
- ViewID: this.selectedView.ID,
108
- UserSearchString: inputValue
109
- });
110
- }
148
+ return __awaiter(this, void 0, void 0, function* () {
149
+ this.searchText = inputValue;
150
+ yield this.Refresh();
151
+ });
111
152
  }
112
153
  viewPropertiesDialogClosed(args) {
113
154
  if (args && args.Saved && args.ViewEntity) {
@@ -122,12 +163,11 @@ SingleViewComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SingleV
122
163
  } if (rf & 2) {
123
164
  let _t;
124
165
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewGrid = _t.first);
125
- } }, inputs: { viewId: "viewId", selectedView: "selectedView" }, outputs: { loadComplete: "loadComplete" }, decls: 5, vars: 3, consts: [["mjFillContainer", ""], [1, "searchBox"], ["id", "txtSearch", "placeholder", "Search here...", "kendoTextBox", "", 3, "clearButton", "ngModel", "valueChange", "ngModelChange", 4, "ngIf"], [3, "ViewID", "dialogClosed"], ["mjFillContainer", "", 3, "AutoNavigate", "rowClicked"], ["id", "txtSearch", "placeholder", "Search here...", "kendoTextBox", "", 3, "clearButton", "ngModel", "valueChange", "ngModelChange"]], template: function SingleViewComponent_Template(rf, ctx) { if (rf & 1) {
166
+ } }, inputs: { viewId: "viewId", viewName: "viewName", selectedView: "selectedView", extraFilter: "extraFilter", entityName: "entityName" }, outputs: { loadComplete: "loadComplete" }, decls: 5, vars: 3, consts: [["mjFillContainer", ""], [1, "searchBox"], ["id", "txtSearch", "placeholder", "Search here...", "kendoTextBox", "", 3, "clearButton", "ngModel", "valueChange", "ngModelChange", 4, "ngIf"], [3, "ViewID", "dialogClosed", 4, "ngIf"], ["mjFillContainer", "", 3, "AutoNavigate", "rowClicked"], ["id", "txtSearch", "placeholder", "Search here...", "kendoTextBox", "", 3, "clearButton", "ngModel", "valueChange", "ngModelChange"], [3, "ViewID", "dialogClosed"]], template: function SingleViewComponent_Template(rf, ctx) { if (rf & 1) {
126
167
  i0.ɵɵelementStart(0, "div", 0)(1, "div", 1);
127
168
  i0.ɵɵtemplate(2, SingleViewComponent_kendo_textbox_2_Template, 1, 2, "kendo-textbox", 2);
128
- i0.ɵɵelementStart(3, "app-view-properties-dialog", 3);
129
- i0.ɵɵlistener("dialogClosed", function SingleViewComponent_Template_app_view_properties_dialog_dialogClosed_3_listener($event) { return ctx.viewPropertiesDialogClosed($event); });
130
- i0.ɵɵelementEnd()();
169
+ i0.ɵɵtemplate(3, SingleViewComponent_app_view_properties_dialog_3_Template, 1, 1, "app-view-properties-dialog", 3);
170
+ i0.ɵɵelementEnd();
131
171
  i0.ɵɵelementStart(4, "mj-user-view-grid", 4);
132
172
  i0.ɵɵlistener("rowClicked", function SingleViewComponent_Template_mj_user_view_grid_rowClicked_4_listener($event) { return ctx.handleRowClick($event); });
133
173
  i0.ɵɵelementEnd()();
@@ -135,20 +175,26 @@ SingleViewComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SingleV
135
175
  i0.ɵɵadvance(2);
136
176
  i0.ɵɵproperty("ngIf", ctx.showSearch);
137
177
  i0.ɵɵadvance(1);
138
- i0.ɵɵproperty("ViewID", ctx.selectedView == null ? null : ctx.selectedView.ID);
178
+ i0.ɵɵproperty("ngIf", ctx.selectedView);
139
179
  i0.ɵɵadvance(1);
140
180
  i0.ɵɵproperty("AutoNavigate", false);
141
181
  } }, dependencies: [i3.NgIf, i4.NgControlStatus, i4.NgModel, i5.TextBoxComponent, i6.FillContainer, i7.UserViewGridComponent, i8.ViewPropertiesDialogComponent] });
142
182
  (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SingleViewComponent, [{
143
183
  type: Component,
144
- args: [{ selector: 'app-single-view', template: "<div mjFillContainer>\r\n <div class=\"card-container\" class=\"searchBox\">\r\n <kendo-textbox\r\n id=\"txtSearch\"\r\n *ngIf=\"showSearch\"\r\n placeholder=\"Search here...\"\r\n kendoTextBox\r\n (valueChange)=\"onSearch($event)\"\r\n [clearButton]=\"true\"\r\n [(ngModel)]=\"searchText\"\r\n ></kendo-textbox>\r\n\r\n <app-view-properties-dialog [ViewID]=\"selectedView?.ID\" (dialogClosed)=\"this.viewPropertiesDialogClosed($event)\" ></app-view-properties-dialog>\r\n </div>\r\n\r\n <mj-user-view-grid (rowClicked)=\"handleRowClick($event)\" [AutoNavigate]=\"false\" mjFillContainer></mj-user-view-grid>\r\n</div>\r\n" }]
184
+ args: [{ selector: 'app-single-view', template: "<div mjFillContainer>\r\n <div class=\"card-container\" class=\"searchBox\">\r\n <kendo-textbox\r\n id=\"txtSearch\"\r\n *ngIf=\"showSearch\"\r\n placeholder=\"Search here...\"\r\n kendoTextBox\r\n (valueChange)=\"onSearch($event)\"\r\n [clearButton]=\"true\"\r\n [(ngModel)]=\"searchText\"\r\n ></kendo-textbox>\r\n\r\n <app-view-properties-dialog *ngIf=\"selectedView\" [ViewID]=\"selectedView.ID\" (dialogClosed)=\"this.viewPropertiesDialogClosed($event)\" ></app-view-properties-dialog>\r\n </div>\r\n\r\n <mj-user-view-grid (rowClicked)=\"handleRowClick($event)\" [AutoNavigate]=\"false\" mjFillContainer></mj-user-view-grid>\r\n</div>\r\n" }]
145
185
  }], function () { return [{ type: i1.Router }, { type: i1.ActivatedRoute }, { type: i2.SharedService }]; }, { viewGrid: [{
146
186
  type: ViewChild,
147
187
  args: [UserViewGridComponent, { static: true }]
148
188
  }], viewId: [{
149
189
  type: Input
190
+ }], viewName: [{
191
+ type: Input
150
192
  }], selectedView: [{
151
193
  type: Input
194
+ }], extraFilter: [{
195
+ type: Input
196
+ }], entityName: [{
197
+ type: Input
152
198
  }], loadComplete: [{
153
199
  type: Output
154
200
  }] }); })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-explorer-core",
3
- "version": "0.9.14",
3
+ "version": "0.9.15",
4
4
  "description": "MemberJunction Explorer: Core Angular Components",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -27,12 +27,12 @@
27
27
  "@progress/kendo-angular-listview": "^12.1.0"
28
28
  },
29
29
  "dependencies": {
30
- "@memberjunction/global": "^0.9.74",
31
- "@memberjunction/core": "^0.9.71",
32
- "@memberjunction/ng-compare-records": "^0.9.73",
33
- "@memberjunction/ng-record-changes": "^0.9.11",
34
- "@memberjunction/ng-container-directives": "^0.9.49",
35
- "@memberjunction/ng-user-view-grid": "^0.9.53",
30
+ "@memberjunction/global": "^0.9.75",
31
+ "@memberjunction/core": "^0.9.72",
32
+ "@memberjunction/ng-compare-records": "^0.9.74",
33
+ "@memberjunction/ng-record-changes": "^0.9.12",
34
+ "@memberjunction/ng-container-directives": "^0.9.50",
35
+ "@memberjunction/ng-user-view-grid": "^0.9.54",
36
36
  "tslib": "^2.3.0"
37
37
  },
38
38
  "sideEffects": false