@memberjunction/ng-user-view-grid 0.9.37 → 0.9.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/ng-user-view-grid.component.css +0 -9
- package/src/lib/ng-user-view-grid.component.html +0 -88
- package/src/lib/ng-user-view-grid.component.ts +0 -667
- package/src/lib/ng-user-view-grid.module.ts +0 -36
- package/src/public-api.ts +0 -6
- package/tsconfig.json +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-user-view-grid",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.39",
|
|
4
4
|
"description": "MemberJunction: Angular Grid to display dynamic and saved user views for any entity in MemberJunction.",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
<div class="user-view-grid-wrap" mjFillContainer>
|
|
2
|
-
<kendo-grid #kendoGrid
|
|
3
|
-
mjFillContainer
|
|
4
|
-
[resizable]="true"
|
|
5
|
-
[data]="gridView"
|
|
6
|
-
[skip]="skip"
|
|
7
|
-
[pageSize]="pageSize"
|
|
8
|
-
scrollable="virtual"
|
|
9
|
-
[rowHeight]="36"
|
|
10
|
-
(pageChange)="pageChange($event)"
|
|
11
|
-
[loading]="isLoading"
|
|
12
|
-
[height]="gridHeight"
|
|
13
|
-
[sortable]="true"
|
|
14
|
-
[sort]="sortSettings"
|
|
15
|
-
[resizable]="true"
|
|
16
|
-
[reorderable]="true"
|
|
17
|
-
[selectable]="true"
|
|
18
|
-
kendoGridSelectBy
|
|
19
|
-
[(selectedKeys)]="selectedKeys"
|
|
20
|
-
(cellClick)="cellClickHandler($event)"
|
|
21
|
-
(cellClose)="cellCloseHandler($event)"
|
|
22
|
-
(columnReorder)="columnReorder($event)"
|
|
23
|
-
(columnResize)="columnResize($event)"
|
|
24
|
-
(selectionChange)="selectionChange($event)"
|
|
25
|
-
(sortChange)="sortChanged($event)"
|
|
26
|
-
>
|
|
27
|
-
<ng-template kendoGridToolbarTemplate>
|
|
28
|
-
<button kendoButton icon="file-excel" [disabled]="compareMode" (click)="doExcelExport()" >Export to Excel</button>
|
|
29
|
-
<button (click)="enableCompare(true)" *ngIf="compareMode" class="k-button k-button-md k-rounded-md k-button-solid-base k-button-solid">Cancel</button>
|
|
30
|
-
<button (click)="enableCompare()" [disabled]="compareMode && compareRecords.length < 2" class="k-button k-button-md k-rounded-md k-button-solid-base k-button-solid">Compare</button>
|
|
31
|
-
<button
|
|
32
|
-
kendoButton
|
|
33
|
-
[primary]="true"
|
|
34
|
-
icon="refresh"
|
|
35
|
-
*ngIf="showRefreshButton"
|
|
36
|
-
(click)="RefreshFromSavedParams()">
|
|
37
|
-
Refresh
|
|
38
|
-
</button>
|
|
39
|
-
</ng-template>
|
|
40
|
-
|
|
41
|
-
<kendo-grid-checkbox-column
|
|
42
|
-
*ngIf="compareMode"
|
|
43
|
-
[width]="50"
|
|
44
|
-
[headerStyle]="{ 'font-weight' : 'bold', 'background-color': '#a9c2af' }"
|
|
45
|
-
[style]="{'text-align': 'center', 'vertical-align': 'center'}">
|
|
46
|
-
</kendo-grid-checkbox-column>
|
|
47
|
-
|
|
48
|
-
<kendo-grid-column
|
|
49
|
-
*ngFor="let item of visibleColumns"
|
|
50
|
-
[field]="item.Name"
|
|
51
|
-
[title]="GetColumnTitle(item)"
|
|
52
|
-
[width]="item.width ? item.width : 100"
|
|
53
|
-
[editable]="item.EntityField.AllowUpdateAPI"
|
|
54
|
-
[editor]="getEditor(item.EntityField)"
|
|
55
|
-
[headerStyle]="{ 'font-weight' : 'bold', 'background-color': '#a9c2af' }"
|
|
56
|
-
[style]="this.GetColumnCellStyle(item)"
|
|
57
|
-
>
|
|
58
|
-
<ng-template *ngIf="item===visibleColumns[0]" kendoGridFooterTemplate >
|
|
59
|
-
{{this.viewData.length | number}}{{this.totalRowCount > this.viewData.length ? ' of ' + (this.totalRowCount | number) : ' rows'}}<br/><span style="font-size: smaller; font-weight: normal;">{{viewExecutionTime | number:'1.2-2'}} seconds</span>
|
|
60
|
-
</ng-template>
|
|
61
|
-
</kendo-grid-column>
|
|
62
|
-
|
|
63
|
-
<kendo-excelexport #excelExport [data]="exportData" [fileName]="(_viewEntity ? _viewEntity.Get('Name') : _entityInfo?.Name) + '.xlsx'">
|
|
64
|
-
<kendo-excelexport-column *ngFor="let exportCol of exportColumns" [field]="exportCol.Name" [title]="exportCol.Name">
|
|
65
|
-
</kendo-excelexport-column>
|
|
66
|
-
</kendo-excelexport>
|
|
67
|
-
</kendo-grid>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div *ngIf="isDialogOpened">
|
|
71
|
-
<div class="k-overlay"></div>
|
|
72
|
-
<kendo-window
|
|
73
|
-
[width]="900"
|
|
74
|
-
[minHeight]="300"
|
|
75
|
-
[minWidth]="400"
|
|
76
|
-
[resizable]="true"
|
|
77
|
-
(close)="closeDialog()"
|
|
78
|
-
title="Compare Records"
|
|
79
|
-
*ngIf="isDialogOpened"
|
|
80
|
-
>
|
|
81
|
-
<div class="k-d-flex k-flex-col k-justify-content-between k-h-full">
|
|
82
|
-
<div>
|
|
83
|
-
<mj-record-compare [recordsToCompare]="compareRecords" [visibleColumns]="visibleColumns" ></mj-record-compare>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
</kendo-window>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
@@ -1,667 +0,0 @@
|
|
|
1
|
-
import { Component, ViewChild, ElementRef, Output, EventEmitter, OnInit, Input, AfterViewInit } from '@angular/core';
|
|
2
|
-
import { FormBuilder, FormGroup } from '@angular/forms';
|
|
3
|
-
import { Router } from '@angular/router'
|
|
4
|
-
|
|
5
|
-
import { Metadata, BaseEntity, RunView, RunViewParams, EntityFieldInfo, EntityFieldTSType, EntityInfo, LogError } from '@memberjunction/core';
|
|
6
|
-
import { ViewInfo, ViewGridState, ViewColumnInfo, UserViewEntityExtended } from '@memberjunction/core-entities';
|
|
7
|
-
|
|
8
|
-
import { CellClickEvent, GridDataResult, PageChangeEvent, GridComponent, CellCloseEvent,
|
|
9
|
-
ColumnReorderEvent, ColumnResizeArgs, ColumnComponent, SelectionEvent, SelectableSettings} from "@progress/kendo-angular-grid";
|
|
10
|
-
import { Keys } from '@progress/kendo-angular-common';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { Subject } from 'rxjs';
|
|
14
|
-
import { ExcelExportComponent } from '@progress/kendo-angular-excel-export';
|
|
15
|
-
import { DisplaySimpleNotificationRequestData, MJEventType, MJGlobal } from '@memberjunction/global';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export type GridRowClickedEvent = {
|
|
19
|
-
entityId: number;
|
|
20
|
-
entityName: string;
|
|
21
|
-
recordId: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type GridRowEditedEvent = {
|
|
25
|
-
record: BaseEntity;
|
|
26
|
-
row: number;
|
|
27
|
-
saved: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type GridPendingRecordItem = {
|
|
31
|
-
record: BaseEntity;
|
|
32
|
-
row: number;
|
|
33
|
-
dataItem: any;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@Component({
|
|
37
|
-
selector: 'mj-user-view-grid',
|
|
38
|
-
templateUrl: './ng-user-view-grid.component.html',
|
|
39
|
-
styleUrls: ['./ng-user-view-grid.component.css']
|
|
40
|
-
})
|
|
41
|
-
export class UserViewGridComponent implements OnInit, AfterViewInit {
|
|
42
|
-
title = 'UserViewGrid';
|
|
43
|
-
@Input() Params: RunViewParams | undefined;
|
|
44
|
-
@Input() BottomMargin: number = 0;
|
|
45
|
-
@Input() InEditMode: boolean = false;
|
|
46
|
-
@Input() EditMode: "None" | "Save" | "Queue" = "None"
|
|
47
|
-
@Input() AutoNavigate: boolean = true;
|
|
48
|
-
|
|
49
|
-
@Output() rowClicked = new EventEmitter<GridRowClickedEvent>();
|
|
50
|
-
@Output() rowEdited = new EventEmitter<GridRowEditedEvent>();
|
|
51
|
-
|
|
52
|
-
@ViewChild('kendoGrid', { read: GridComponent }) kendoGridElement: GridComponent | null = null;
|
|
53
|
-
@ViewChild('kendoGrid', { read: ElementRef }) kendoGridElementRef: ElementRef | null = null;
|
|
54
|
-
@ViewChild('excelExport', { read: ExcelExportComponent }) kendoExcelExport: ExcelExportComponent | null = null;
|
|
55
|
-
|
|
56
|
-
private _pendingRecords: GridPendingRecordItem[] = [];
|
|
57
|
-
|
|
58
|
-
public viewData: [] = [];
|
|
59
|
-
public totalRowCount: number = 0;
|
|
60
|
-
public formattedData: { [key: string]: any }[] = [];
|
|
61
|
-
public viewColumns: ViewColumnInfo[] = [];
|
|
62
|
-
public visibleColumns: ViewColumnInfo[] = [];
|
|
63
|
-
public sortSettings: any[] = [];
|
|
64
|
-
public entityRecord: BaseEntity | null = null;
|
|
65
|
-
public skip: number = 0;
|
|
66
|
-
public pageSize: number = 40;
|
|
67
|
-
public isLoading: boolean = false;
|
|
68
|
-
public gridView: GridDataResult = { data: [], total: 0 };
|
|
69
|
-
public gridHeight: number = 750;
|
|
70
|
-
|
|
71
|
-
public _viewEntity: UserViewEntityExtended | undefined
|
|
72
|
-
public _entityInfo: EntityInfo | undefined;
|
|
73
|
-
private _newGridState: ViewGridState = {};
|
|
74
|
-
|
|
75
|
-
private editModeEnded = new Subject<void>();
|
|
76
|
-
public compareMode: boolean = false;
|
|
77
|
-
public compareRecords: BaseEntity[] = [];
|
|
78
|
-
public selectableSettings: SelectableSettings = {
|
|
79
|
-
enabled: false
|
|
80
|
-
};
|
|
81
|
-
public selectedKeys: any[] = [];
|
|
82
|
-
public isDialogOpened: boolean = false;
|
|
83
|
-
public showRefreshButton: boolean = true;
|
|
84
|
-
|
|
85
|
-
public viewExecutionTime: number = 0;
|
|
86
|
-
|
|
87
|
-
public get PendingRecords(): GridPendingRecordItem[] {
|
|
88
|
-
return this._pendingRecords;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
protected StartEditMode() {
|
|
92
|
-
this.InEditMode = true;
|
|
93
|
-
}
|
|
94
|
-
protected EndEditMode() {
|
|
95
|
-
this.InEditMode = false;
|
|
96
|
-
this.editModeEnded.next();
|
|
97
|
-
}
|
|
98
|
-
public EditingComplete(): Promise<boolean> {
|
|
99
|
-
if (this.InEditMode) {
|
|
100
|
-
// we need to wait for edit mode to end before we can return true
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
const subscription = this.editModeEnded.subscribe(() => {
|
|
103
|
-
resolve(true);
|
|
104
|
-
subscription.unsubscribe();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
else
|
|
109
|
-
return Promise.resolve(true); // not in edit mode, so editing is complete!
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public IsDynamicView(): boolean {
|
|
113
|
-
return !this._viewEntity; // if we have a viewEntity it is a stored view
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public pageChange(event: PageChangeEvent): void {
|
|
117
|
-
this.skip = event.skip;
|
|
118
|
-
this.virtualLoadData();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private virtualLoadData(): void {
|
|
122
|
-
// check to see if we have already formatted the slice of the data we need right now
|
|
123
|
-
// we are storing the formattted data in the formattedData array and it has same set
|
|
124
|
-
// of indexes as the viewData array (raw unformatted data). When we first get viewData
|
|
125
|
-
// from the server we create an array of the same length as viewData, but have nulls in all of the
|
|
126
|
-
// indexes. As we format each row of viewData we store the formatted row in the same index
|
|
127
|
-
// in the formattedData array. So if we have already formatted the data we need for the current
|
|
128
|
-
// page, we just use that data, otherwise we format the data we need for the current page
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
// check to see if we have already formatted the data we need for the current page
|
|
132
|
-
for (let i = this.skip; (i < (this.skip + this.pageSize)) && (i < this.viewData.length); i++) {
|
|
133
|
-
if (!this.formattedData[i]) {
|
|
134
|
-
// we have not formatted this row yet, so format it
|
|
135
|
-
const r = this.viewColumns.map((c) => {
|
|
136
|
-
if (c && c.EntityField && this.viewData[i] && this.viewData[i][c.EntityField.Name]) {
|
|
137
|
-
if (!c.hidden && c.EntityField.Name !== 'ID') {
|
|
138
|
-
const ef = c.EntityField;
|
|
139
|
-
return {field: c.EntityField.Name, value: ef.FormatValue(this.viewData[i][c.EntityField.Name], 0, undefined, 300)}
|
|
140
|
-
}
|
|
141
|
-
else
|
|
142
|
-
return {field: c.EntityField.Name, value: this.viewData[i][c.EntityField.Name]} // hidden column, so just return the value, don't bother formatting
|
|
143
|
-
}
|
|
144
|
-
else
|
|
145
|
-
return {field: c.Name, value: null};
|
|
146
|
-
});
|
|
147
|
-
// now r is an array of {field: string, value: any} objects, so we need to convert it to an object
|
|
148
|
-
// with the field names as the keys and the values as the values
|
|
149
|
-
const row: { [key: string]: any } = {};
|
|
150
|
-
for (let j = 0; j < r.length; j++) {
|
|
151
|
-
if (r[j] && r[j].field && r[j].field.length > 0)
|
|
152
|
-
row[r[j].field] = r[j].value;
|
|
153
|
-
}
|
|
154
|
-
this.formattedData[i] = row;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// now that we have made sure current page of data is formatted, we can return it
|
|
159
|
-
this.gridView = {
|
|
160
|
-
data: this.formattedData.slice(this.skip, this.skip + this.pageSize),
|
|
161
|
-
total: this.viewData.length,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
catch (e) {
|
|
165
|
-
LogError(e);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
constructor(private formBuilder: FormBuilder,
|
|
170
|
-
private router: Router) {
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private _saveTimeout: any;
|
|
175
|
-
private SaveView() {
|
|
176
|
-
// debounced outer function...
|
|
177
|
-
clearTimeout(this._saveTimeout);
|
|
178
|
-
this._saveTimeout = setTimeout(async ()=> {
|
|
179
|
-
// when we actually call inner save view we do await
|
|
180
|
-
await this.innerSaveView()
|
|
181
|
-
}, 5000); // 5 seconds delay
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
private _viewDirty: boolean = false;
|
|
186
|
-
public async innerSaveView() {
|
|
187
|
-
if (this._viewDirty) {
|
|
188
|
-
const md = new Metadata()
|
|
189
|
-
if (this._viewEntity &&
|
|
190
|
-
this._viewEntity.Get('UserID') === md.CurrentUser.ID) {
|
|
191
|
-
// this view is a saved view, AND it belongs to the current user
|
|
192
|
-
|
|
193
|
-
// update the grid state if we have settings updates for columns and/or sorts
|
|
194
|
-
const tempGridState: ViewGridState = JSON.parse(this._viewEntity.Get('GridState'));
|
|
195
|
-
const tempColSettings = this._newGridState.columnSettings ? this._newGridState.columnSettings : tempGridState.columnSettings;
|
|
196
|
-
tempColSettings.forEach((col: ViewColumnInfo) => {col.DisplayName, col.ID, col.Name, col.hidden, col.orderIndex, col.width}); // remove EntityFieldInfo from the column settings
|
|
197
|
-
tempGridState.columnSettings = tempColSettings;
|
|
198
|
-
tempGridState.sortSettings = this._newGridState.sortSettings ? this._newGridState.sortSettings : tempGridState.sortSettings;
|
|
199
|
-
|
|
200
|
-
// now stringify the grid state and save it
|
|
201
|
-
this._viewEntity.Set('GridState', JSON.stringify(tempGridState));
|
|
202
|
-
const newSortState = tempGridState.sortSettings.map((s: any) => {return {field: s.field, direction: s.dir === 'asc' ? 1 : 2}})
|
|
203
|
-
const oldSortState = JSON.parse(this._viewEntity.Get('SortState'));
|
|
204
|
-
this._viewEntity.Set('SortState', JSON.stringify(newSortState));
|
|
205
|
-
|
|
206
|
-
if (await this._viewEntity.Save()) {
|
|
207
|
-
// check to see if sort state changed and if so, refresh the grid
|
|
208
|
-
if (JSON.stringify(newSortState) !== JSON.stringify(oldSortState)) {
|
|
209
|
-
if (this.Params) // makes sure we have params before we refresh
|
|
210
|
-
this.Refresh(this.Params);
|
|
211
|
-
}
|
|
212
|
-
this._viewDirty = false;
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
this.CreateSimpleNotification('Unable to save view settings', 'error', 5000)
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
protected CreateSimpleNotification(message: string, style: "none" | "success" | "error" | "warning" | "info", duration: number) {
|
|
222
|
-
const data: DisplaySimpleNotificationRequestData = {
|
|
223
|
-
message: message,
|
|
224
|
-
style: style,
|
|
225
|
-
DisplayDuration: duration
|
|
226
|
-
}
|
|
227
|
-
MJGlobal.Instance.RaiseEvent({
|
|
228
|
-
component: this,
|
|
229
|
-
event: MJEventType.DisplaySimpleNotificationRequest,
|
|
230
|
-
eventCode: "",
|
|
231
|
-
args: data
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
public async columnReorder(args: ColumnReorderEvent) {
|
|
236
|
-
// Remove the column from the original position
|
|
237
|
-
// need to find the column in the viewColumns array because args.old/new Indexes are from the visibleColumns array
|
|
238
|
-
const fieldName = (<any>args.column).field;
|
|
239
|
-
if (fieldName) {
|
|
240
|
-
const vcOldIndex = this.viewColumns.findIndex((vc: ViewColumnInfo) => vc.Name === fieldName);
|
|
241
|
-
const vcNewIndex = this.viewColumns.findIndex((vc: ViewColumnInfo) => vc.orderIndex === args.newIndex);
|
|
242
|
-
if (vcOldIndex >= 0) {
|
|
243
|
-
// got the index, now remove the element
|
|
244
|
-
const element = this.viewColumns.splice(vcOldIndex, 1)[0];
|
|
245
|
-
|
|
246
|
-
// Insert it at the new position
|
|
247
|
-
this.viewColumns.splice(vcNewIndex, 0, element);
|
|
248
|
-
|
|
249
|
-
// go through all of the columns and set orderIndex as that isn't done automatically
|
|
250
|
-
let visColIndex = 0;
|
|
251
|
-
for (let i = 0; i < this.viewColumns.length; i++) {
|
|
252
|
-
if (!this.viewColumns[i].hidden) {
|
|
253
|
-
this.viewColumns[i].orderIndex = visColIndex;
|
|
254
|
-
visColIndex++;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
// now loop through all of the HIDDEN columns and set their orderIndex, done in second loop because we want first loop to give us total number of visible columns
|
|
258
|
-
for (let i = 0; i < this.viewColumns.length; i++) {
|
|
259
|
-
if (this.viewColumns[i].hidden) {
|
|
260
|
-
this.viewColumns[i].orderIndex = visColIndex;
|
|
261
|
-
visColIndex++;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// make sure that _newGridState.columnSettings is set
|
|
266
|
-
this._newGridState.columnSettings = this.viewColumns;
|
|
267
|
-
|
|
268
|
-
this._viewDirty = true;
|
|
269
|
-
this.SaveView();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
public async columnResize(args: ColumnResizeArgs[]) {
|
|
277
|
-
for (const col of args) {
|
|
278
|
-
const c = col.column as ColumnComponent;
|
|
279
|
-
const viewCol = this.viewColumns.find(vc => vc.Name === c.field);
|
|
280
|
-
const visCol = this.visibleColumns.find(vc => vc.Name === c.field);
|
|
281
|
-
const visCols = this.visibleColumns;
|
|
282
|
-
if (viewCol)
|
|
283
|
-
viewCol.width = col.newWidth;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
this._newGridState.columnSettings = this.viewColumns.map(vc => {
|
|
287
|
-
return { // only pass back the stuff that should persist, we don't want EntityField persisting, waste of space!
|
|
288
|
-
Name: vc.Name,
|
|
289
|
-
DisplayName: vc.DisplayName,
|
|
290
|
-
width: vc.width,
|
|
291
|
-
orderIndex: vc.orderIndex,
|
|
292
|
-
hidden: vc.hidden
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
this._viewDirty = true;
|
|
296
|
-
this.SaveView();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
public async sortChanged(sort: any) {
|
|
300
|
-
this._newGridState.sortSettings = sort;
|
|
301
|
-
this.sortSettings = sort; // for the UI display - grid binding to this shows that the sort is applied via arrows in the column headers
|
|
302
|
-
|
|
303
|
-
if (this.IsDynamicView()) {
|
|
304
|
-
// Dynamic View, we have this.Params and can add an OrderBy and then just Refresh() the entire component
|
|
305
|
-
// that will result in going to the server for a refreshed set of data
|
|
306
|
-
if (this.Params) {
|
|
307
|
-
this.Params.OrderBy = sort[0].field + ' ' + (sort[0].dir === 'asc' ? 'ASC' : 'DESC');
|
|
308
|
-
this.Refresh(this.Params);
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
LogError("sortChanged() called but this.Params is null or undefined") // should never get here
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
// Saved view - we do this on the server side only
|
|
316
|
-
this._viewDirty = true;
|
|
317
|
-
this.innerSaveView(); // for sort changes we call innerSaveView() directly, not through SaveView() which is debounced
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
public async cellClickHandler(args: CellClickEvent) {
|
|
322
|
-
if(this.compareMode) return;
|
|
323
|
-
|
|
324
|
-
if (this._entityInfo) {
|
|
325
|
-
this.rowClicked.emit({
|
|
326
|
-
entityId: this._entityInfo.ID,
|
|
327
|
-
entityName: this._entityInfo.Name,
|
|
328
|
-
recordId: args.dataItem.ID
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
if (this._entityInfo.AllowUpdateAPI &&
|
|
332
|
-
this.EditMode !== "None" ) {
|
|
333
|
-
const perm = this._entityInfo.GetUserPermisions(new Metadata().CurrentUser)
|
|
334
|
-
if (perm.CanUpdate) {
|
|
335
|
-
this.StartEditMode();
|
|
336
|
-
args.sender.editCell(args.rowIndex, args.columnIndex, this.createFormGroup(args.dataItem));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (!this.InEditMode && this.AutoNavigate) {
|
|
341
|
-
// tell app router to go to this record
|
|
342
|
-
this.router.navigate(['resource', 'record', args.dataItem.ID], { queryParams: { Entity: this._entityInfo.Name } })
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
public createFormGroup(dataItem: any): FormGroup {
|
|
348
|
-
const groupFields: any = {};
|
|
349
|
-
this.viewColumns.forEach((vc: ViewColumnInfo) => {
|
|
350
|
-
if (vc.EntityField.AllowUpdateAPI &&
|
|
351
|
-
vc.EntityField.IsVirtual === false &&
|
|
352
|
-
vc.EntityField.AllowUpdateInView)
|
|
353
|
-
groupFields[vc.Name] = dataItem[vc.Name];
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return this.formBuilder.group(groupFields);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
public getEditor(ef: EntityFieldInfo): "boolean" | "text" | "numeric" | "date" {
|
|
361
|
-
switch (ef.TSType) {
|
|
362
|
-
case EntityFieldTSType.Boolean:
|
|
363
|
-
return "boolean";
|
|
364
|
-
case EntityFieldTSType.Date:
|
|
365
|
-
return "date";
|
|
366
|
-
case EntityFieldTSType.Number:
|
|
367
|
-
return "numeric";
|
|
368
|
-
default:
|
|
369
|
-
return "text";
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
public async cellCloseHandler(args: CellCloseEvent) {
|
|
374
|
-
try {
|
|
375
|
-
if (this._entityInfo && this.EditMode !== "None") {
|
|
376
|
-
const { formGroup, dataItem } = args;
|
|
377
|
-
|
|
378
|
-
if (!formGroup.valid) {
|
|
379
|
-
// prevent closing the edited cell if there are invalid values.
|
|
380
|
-
args.preventDefault();
|
|
381
|
-
}
|
|
382
|
-
else if (formGroup.dirty) {
|
|
383
|
-
if (args.originalEvent && args.originalEvent.keyCode === Keys.Escape)
|
|
384
|
-
return; // user hit escape, so don't save their changes
|
|
385
|
-
|
|
386
|
-
// update the data item with the new values - this drives UI refresh while we save the record...
|
|
387
|
-
|
|
388
|
-
Object.assign(dataItem, formGroup.value);
|
|
389
|
-
|
|
390
|
-
const md = new Metadata();
|
|
391
|
-
let record: BaseEntity | undefined;
|
|
392
|
-
let bSaved: boolean = false;
|
|
393
|
-
if (this.EditMode === "Save") {
|
|
394
|
-
record = await md.GetEntityObject(this._entityInfo.Name);
|
|
395
|
-
await record.Load(dataItem.ID);
|
|
396
|
-
record.SetMany(formGroup.value);
|
|
397
|
-
bSaved = await record.Save();
|
|
398
|
-
if (!bSaved)
|
|
399
|
-
this.CreateSimpleNotification("Error saving record: " + record.ID, 'error', 5000)
|
|
400
|
-
}
|
|
401
|
-
else {
|
|
402
|
-
record = this._pendingRecords.find((r: GridPendingRecordItem) => r.record.ID === dataItem.ID)?.record;
|
|
403
|
-
if (!record) { // haven't edited this one before
|
|
404
|
-
record = await md.GetEntityObject(this._viewEntity!.Get('Entity'));
|
|
405
|
-
await record.Load(dataItem.ID);
|
|
406
|
-
this._pendingRecords.push({record,
|
|
407
|
-
row: args.rowIndex,
|
|
408
|
-
dataItem}); // don't save - put the changed record on a queue for saving later by our container
|
|
409
|
-
}
|
|
410
|
-
// go through the formGroup and only set the values that exist as columns in the grid
|
|
411
|
-
const keys = Object.keys(formGroup.value);
|
|
412
|
-
keys.forEach((k: string) => {
|
|
413
|
-
const vc = this.viewColumns.find((vc: ViewColumnInfo) => vc.Name === k && vc.hidden === false);
|
|
414
|
-
if (vc) {
|
|
415
|
-
record!.Set(k, formGroup.value[k]);
|
|
416
|
-
}
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
//record.SetMany(formGroup.value);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
this.rowEdited.emit({
|
|
423
|
-
record: record,
|
|
424
|
-
row: args.rowIndex,
|
|
425
|
-
saved: bSaved
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
catch (e) {
|
|
432
|
-
console.error(e);
|
|
433
|
-
}
|
|
434
|
-
finally {
|
|
435
|
-
this.EndEditMode();
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// this handles reverting pending cahnges to records WITHIN the grid, not the user view settings, unrelated to that.
|
|
441
|
-
public RevertPendingChanges(): void {
|
|
442
|
-
if (this._pendingRecords && this._pendingRecords.length > 0) {
|
|
443
|
-
this._pendingRecords.forEach((r: GridPendingRecordItem) => {
|
|
444
|
-
r.record!.Revert();
|
|
445
|
-
Object.assign(r.dataItem, r.record!.GetAll()); // copy the original values back to the data Item which gets the grid to display the old values again...
|
|
446
|
-
})
|
|
447
|
-
this._pendingRecords = [];
|
|
448
|
-
if (this.Params)
|
|
449
|
-
this.Refresh(this.Params);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
ngOnInit(): void {
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
ngAfterViewInit(): void {
|
|
459
|
-
//this.setGridHeight();
|
|
460
|
-
if (this.Params)
|
|
461
|
-
this.Refresh(this.Params)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
private _deferLoadCount: number = 0;
|
|
465
|
-
private _allowLoad: boolean = true;
|
|
466
|
-
@Input() public get AllowLoad(): boolean {
|
|
467
|
-
return this._allowLoad;
|
|
468
|
-
}
|
|
469
|
-
public set AllowLoad(value: boolean) {
|
|
470
|
-
this._allowLoad = value
|
|
471
|
-
if (value === true && this._deferLoadCount === 0) {
|
|
472
|
-
this._deferLoadCount++; // only do this one time
|
|
473
|
-
if (this.Params)
|
|
474
|
-
this.Refresh(this.Params)
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
async RefreshFromSavedParams() {
|
|
480
|
-
if (this.Params)
|
|
481
|
-
this.Refresh(this.Params)
|
|
482
|
-
}
|
|
483
|
-
async Refresh(params: RunViewParams) {
|
|
484
|
-
this.Params = params;
|
|
485
|
-
|
|
486
|
-
if (this.AllowLoad === false) {
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
if (params && (params.ViewEntity || params.ViewID || params.ViewName || (params.EntityName && params.ExtraFilter))) {
|
|
490
|
-
const startTime = new Date().getTime();
|
|
491
|
-
this.isLoading = true
|
|
492
|
-
|
|
493
|
-
const md = new Metadata();
|
|
494
|
-
const rv = new RunView();
|
|
495
|
-
|
|
496
|
-
// get the view entity first so we can pass it in, otherwise it will end up getting loaded inside of RunView() which is inefficient as we need it too
|
|
497
|
-
// this is done for performance purposes
|
|
498
|
-
if (!params.ViewEntity && (params.ViewID || params.ViewName)) {
|
|
499
|
-
if (params.ViewID && params.ViewID > 0) {
|
|
500
|
-
this._viewEntity = <UserViewEntityExtended>await ViewInfo.GetViewEntity(params.ViewID);
|
|
501
|
-
this._entityInfo = md.Entities.find(x => x.ID === this._viewEntity?.Get('EntityID'));
|
|
502
|
-
}
|
|
503
|
-
else if (params.ViewName) {
|
|
504
|
-
this._viewEntity = <UserViewEntityExtended>await ViewInfo.GetViewEntityByName(params.ViewName);
|
|
505
|
-
this._entityInfo = md.Entities.find(x => x.ID === this._viewEntity?.Get('EntityID'));
|
|
506
|
-
}
|
|
507
|
-
params.ViewEntity = this._viewEntity;
|
|
508
|
-
}
|
|
509
|
-
else
|
|
510
|
-
this._viewEntity = <UserViewEntityExtended>params.ViewEntity; //passed in via params in the Refresh() function
|
|
511
|
-
|
|
512
|
-
const rvResult = await rv.RunView(params);
|
|
513
|
-
if (!rvResult.Success) {
|
|
514
|
-
// it failed
|
|
515
|
-
this.CreateSimpleNotification("Error running view:\n\n" + rvResult.ErrorMessage, 'error', 5000)
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
// it worked
|
|
519
|
-
this.viewData = rvResult.Results;
|
|
520
|
-
|
|
521
|
-
this.totalRowCount = rvResult.TotalRowCount;
|
|
522
|
-
this.formattedData = new Array(this.viewData.length);
|
|
523
|
-
|
|
524
|
-
if (this._viewEntity) {
|
|
525
|
-
// we were passed the view entity for efficiency, no need to load it again!
|
|
526
|
-
this._entityInfo = md.Entities.find(x => x.ID === this._viewEntity?.Get('EntityID'));
|
|
527
|
-
}
|
|
528
|
-
else
|
|
529
|
-
this._entityInfo = md.Entities.find(x => x.Name === params.EntityName);
|
|
530
|
-
|
|
531
|
-
let cols: ViewColumnInfo[] | undefined;
|
|
532
|
-
if (this._viewEntity)
|
|
533
|
-
cols = this._viewEntity.Columns
|
|
534
|
-
else
|
|
535
|
-
cols = this._entityInfo?.Fields.filter((f: EntityFieldInfo) => f.DefaultInView ).map((f: EntityFieldInfo) => {
|
|
536
|
-
return {
|
|
537
|
-
ID: f.ID,
|
|
538
|
-
Name: f.Name,
|
|
539
|
-
DisplayName: f.DisplayName,
|
|
540
|
-
EntityField: f,
|
|
541
|
-
hidden: false,
|
|
542
|
-
orderIndex: f.Sequence,
|
|
543
|
-
width: f.DefaultColumnWidth ? f.DefaultColumnWidth : 100,
|
|
544
|
-
} as ViewColumnInfo
|
|
545
|
-
});
|
|
546
|
-
if (cols) {
|
|
547
|
-
this.viewColumns = cols
|
|
548
|
-
const tempCols = cols.filter(x => x.hidden === false).sort((a,b) => {
|
|
549
|
-
const aOrder = a.orderIndex != null ? a.orderIndex : 9999;
|
|
550
|
-
const bOrder = b.orderIndex != null ? b.orderIndex : 9999;
|
|
551
|
-
return aOrder - bOrder;
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
this.visibleColumns = tempCols
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// sorting setup
|
|
558
|
-
if (this._viewEntity) {
|
|
559
|
-
const temp = this._viewEntity.ViewSortInfo;
|
|
560
|
-
const kendoSortSettings = temp.map((s: any) => {return {field: s.field, dir: s.direction === 1 ? 'asc' : 'desc'}})
|
|
561
|
-
this.sortSettings = kendoSortSettings;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
this.skip = 0;
|
|
565
|
-
this.virtualLoadData();
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
this.viewExecutionTime = (new Date().getTime() - startTime) / 1000; // in seconds
|
|
569
|
-
this.isLoading = false
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
LogError("Refresh(params) must have ViewID or ViewName or (EntityName and ExtraFilter)")
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
GetColumnTitle(col: ViewColumnInfo) {
|
|
577
|
-
if (col.DisplayName)
|
|
578
|
-
return col.DisplayName; // use view's display name first if it exists
|
|
579
|
-
else if (col.EntityField.DisplayName )
|
|
580
|
-
return col.EntityField.DisplayName; // then use entity display name, if that exist
|
|
581
|
-
else
|
|
582
|
-
return col.Name; // otherwise just use the column name
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
GetColumnCellStyle(col: ViewColumnInfo) {
|
|
586
|
-
switch (col.EntityField.Type.trim().toLowerCase()) {
|
|
587
|
-
case "money":
|
|
588
|
-
case 'decimal':
|
|
589
|
-
case 'real':
|
|
590
|
-
case 'float':
|
|
591
|
-
case 'int':
|
|
592
|
-
return {'text-align': 'right', 'vertical-align': 'top'}; // right align numbers,
|
|
593
|
-
default:
|
|
594
|
-
return {'text-align': 'left', 'vertical-align': 'top'}; // left align everything else
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
selectionChange(args: SelectionEvent) {
|
|
599
|
-
if(this.compareRecords.length && args?.deselectedRows?.length){
|
|
600
|
-
const tempRow = args?.deselectedRows[0]?.dataItem;
|
|
601
|
-
this.compareRecords = this.compareRecords.filter(record => record.ID !== tempRow?.ID);
|
|
602
|
-
} else if(args?.selectedRows?.length){
|
|
603
|
-
const tempRow = args?.selectedRows[0]?.dataItem;
|
|
604
|
-
this.compareRecords.push(tempRow);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
enableCompare(cancel: boolean = false){
|
|
609
|
-
if(!cancel && this.selectedKeys.length){
|
|
610
|
-
this.isDialogOpened = true;
|
|
611
|
-
}
|
|
612
|
-
this.compareMode = !this.compareMode;
|
|
613
|
-
this.selectedKeys = [];
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
closeDialog(){
|
|
617
|
-
this.isDialogOpened = false;
|
|
618
|
-
this.compareRecords = [];
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
// Export Functionality
|
|
623
|
-
public exportColumns: ViewColumnInfo[] = [];
|
|
624
|
-
public exportData: any[] = [];
|
|
625
|
-
public async doExcelExport() {
|
|
626
|
-
if (this.kendoExcelExport === null)
|
|
627
|
-
throw new Error("kendoExcelExport is null, cannot export data");
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
this.CreateSimpleNotification("Working on the export, will notify you when it is complete...", 'info', 2000)
|
|
631
|
-
const data = await this.getExportData();
|
|
632
|
-
// we have the data.
|
|
633
|
-
const cols = this.viewColumns.filter((vc: ViewColumnInfo) => vc.hidden === false)
|
|
634
|
-
this.exportColumns = cols;
|
|
635
|
-
this.exportData = data;
|
|
636
|
-
// before we call the save, we need to let Angular do its thing that will result in the kendoExcelExport component binding properly to
|
|
637
|
-
// the exportColumns and exportData arrays. So we wait for the next tick before we call save()
|
|
638
|
-
setTimeout(() => {
|
|
639
|
-
this.kendoExcelExport!.save();
|
|
640
|
-
this.CreateSimpleNotification("Excel Export Complete", 'success', 2000)
|
|
641
|
-
}, 100);
|
|
642
|
-
}
|
|
643
|
-
catch (e) {
|
|
644
|
-
this.CreateSimpleNotification("Error exporting data", 'error', 5000)
|
|
645
|
-
LogError(e)
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
protected async getExportData(): Promise<any[]> {
|
|
650
|
-
// Get the data for the ENTIRE view, not just the current page
|
|
651
|
-
const md = new Metadata();
|
|
652
|
-
const rv = new RunView();
|
|
653
|
-
const p = {
|
|
654
|
-
...this.Params!,
|
|
655
|
-
IgnoreMaxRows: true,
|
|
656
|
-
ForceAuditLog: true,
|
|
657
|
-
AuditLogDescription: `Export of Data From ${this._viewEntity ? '"' + this._viewEntity.Get('Name') + '"' : ''} View for User ${md.CurrentUser.Email}`
|
|
658
|
-
}
|
|
659
|
-
const result = await rv.RunView(p);
|
|
660
|
-
if (result && result.Success) {
|
|
661
|
-
return result.Results;
|
|
662
|
-
}
|
|
663
|
-
else
|
|
664
|
-
throw new Error("Unable to get export data");
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { NgModule } from '@angular/core';
|
|
2
|
-
import { UserViewGridComponent } from './ng-user-view-grid.component';
|
|
3
|
-
import { CommonModule } from '@angular/common';
|
|
4
|
-
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
5
|
-
import { RouterModule } from '@angular/router';
|
|
6
|
-
|
|
7
|
-
// Kendo UI Angular imports
|
|
8
|
-
import { GridModule } from '@progress/kendo-angular-grid';
|
|
9
|
-
import { ExcelExportModule } from '@progress/kendo-angular-excel-export';
|
|
10
|
-
import { DialogsModule } from "@progress/kendo-angular-dialog";
|
|
11
|
-
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
|
12
|
-
|
|
13
|
-
import { CompareRecordsModule } from '@memberjunction/ng-compare-records';
|
|
14
|
-
import { ContainerDirectivesModule } from '@memberjunction/ng-container-directives';
|
|
15
|
-
|
|
16
|
-
@NgModule({
|
|
17
|
-
declarations: [
|
|
18
|
-
UserViewGridComponent
|
|
19
|
-
],
|
|
20
|
-
imports: [
|
|
21
|
-
CommonModule,
|
|
22
|
-
FormsModule,
|
|
23
|
-
ReactiveFormsModule,
|
|
24
|
-
RouterModule,
|
|
25
|
-
GridModule,
|
|
26
|
-
DialogsModule,
|
|
27
|
-
ExcelExportModule,
|
|
28
|
-
ButtonsModule,
|
|
29
|
-
CompareRecordsModule,
|
|
30
|
-
ContainerDirectivesModule
|
|
31
|
-
],
|
|
32
|
-
exports: [
|
|
33
|
-
UserViewGridComponent
|
|
34
|
-
]
|
|
35
|
-
})
|
|
36
|
-
export class UserViewGridModule { }
|
package/src/public-api.ts
DELETED
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es2015",
|
|
4
|
-
"module": "es2020",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"experimentalDecorators": true,
|
|
8
|
-
"outDir": "./dist",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"lib": [
|
|
11
|
-
"es2018",
|
|
12
|
-
"dom"
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
"angularCompilerOptions": {
|
|
16
|
-
"skipTemplateCodegen": true,
|
|
17
|
-
"strictMetadataEmit": true,
|
|
18
|
-
"enableResourceInlining": true
|
|
19
|
-
},
|
|
20
|
-
"include": [
|
|
21
|
-
"src/**/*.ts"
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
|