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