@memberjunction/ng-entity-viewer 2.131.0 → 2.133.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -124
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +792 -0
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -0
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +3778 -0
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -0
- package/dist/lib/entity-data-grid/events/grid-events.d.ts +398 -0
- package/dist/lib/entity-data-grid/events/grid-events.d.ts.map +1 -0
- package/dist/lib/entity-data-grid/events/grid-events.js +556 -0
- package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -0
- package/dist/lib/entity-data-grid/models/grid-types.d.ts +437 -0
- package/dist/lib/entity-data-grid/models/grid-types.d.ts.map +1 -0
- package/dist/lib/entity-data-grid/models/grid-types.js +37 -0
- package/dist/lib/entity-data-grid/models/grid-types.js.map +1 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +92 -2
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +255 -92
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/types.d.ts +14 -31
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts +363 -0
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -0
- package/dist/lib/view-config-panel/view-config-panel.component.js +2006 -0
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -0
- package/dist/module.d.ts +16 -13
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +24 -14
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +4 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +10 -6
- package/dist/lib/entity-grid/entity-grid.component.d.ts +0 -216
- package/dist/lib/entity-grid/entity-grid.component.d.ts.map +0 -1
- package/dist/lib/entity-grid/entity-grid.component.js +0 -676
- package/dist/lib/entity-grid/entity-grid.component.js.map +0 -1
|
@@ -0,0 +1,3778 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
|
2
|
+
import { trigger, transition, style, animate } from '@angular/animations';
|
|
3
|
+
import { Subject } from 'rxjs';
|
|
4
|
+
import { debounceTime, takeUntil } from 'rxjs/operators';
|
|
5
|
+
import { BaseEntity, RunView, RunViewParams, Metadata } from '@memberjunction/core';
|
|
6
|
+
import { ViewInfo, UserViewEngine, UserInfoEngine } from '@memberjunction/core-entities';
|
|
7
|
+
import { ModuleRegistry, AllCommunityModule, themeAlpine } from 'ag-grid-community';
|
|
8
|
+
// Use the existing HighlightUtil from entity-viewer package
|
|
9
|
+
import { HighlightUtil } from '../utils/highlight.util';
|
|
10
|
+
import { DEFAULT_VISUAL_CONFIG } from './models/grid-types';
|
|
11
|
+
import { BeforeRowSelectEventArgs, AfterRowSelectEventArgs, BeforeRowDeselectEventArgs, AfterRowDeselectEventArgs, BeforeRowClickEventArgs, AfterRowClickEventArgs, BeforeRowDoubleClickEventArgs, AfterRowDoubleClickEventArgs, BeforeDataLoadEventArgs, AfterDataLoadEventArgs, BeforeDataRefreshEventArgs, AfterDataRefreshEventArgs, BeforeSortEventArgs, AfterSortEventArgs } from './events/grid-events';
|
|
12
|
+
import * as i0 from "@angular/core";
|
|
13
|
+
import * as i1 from "@memberjunction/ng-export-service";
|
|
14
|
+
import * as i2 from "@angular/common";
|
|
15
|
+
import * as i3 from "ag-grid-angular";
|
|
16
|
+
import * as i4 from "@memberjunction/ng-shared-generic";
|
|
17
|
+
const _c0 = ["gridContainer"];
|
|
18
|
+
function EntityDataGridComponent_div_2_div_2_button_3_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
20
|
+
i0.ɵɵelementStart(0, "button", 36);
|
|
21
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_2_button_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.FilterText = ""); });
|
|
22
|
+
i0.ɵɵelement(1, "i", 37);
|
|
23
|
+
i0.ɵɵelementEnd();
|
|
24
|
+
} }
|
|
25
|
+
function EntityDataGridComponent_div_2_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
26
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
27
|
+
i0.ɵɵelementStart(0, "div", 32);
|
|
28
|
+
i0.ɵɵelement(1, "i", 33);
|
|
29
|
+
i0.ɵɵelementStart(2, "input", 34);
|
|
30
|
+
i0.ɵɵlistener("input", function EntityDataGridComponent_div_2_div_2_Template_input_input_2_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.FilterText = $event.target.value); });
|
|
31
|
+
i0.ɵɵelementEnd();
|
|
32
|
+
i0.ɵɵtemplate(3, EntityDataGridComponent_div_2_div_2_button_3_Template, 2, 0, "button", 35);
|
|
33
|
+
i0.ɵɵelementEnd();
|
|
34
|
+
} if (rf & 2) {
|
|
35
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
36
|
+
i0.ɵɵadvance(2);
|
|
37
|
+
i0.ɵɵproperty("placeholder", ctx_r2.ToolbarConfig.searchPlaceholder || "Search...")("value", ctx_r2.FilterText);
|
|
38
|
+
i0.ɵɵadvance();
|
|
39
|
+
i0.ɵɵproperty("ngIf", ctx_r2.FilterText);
|
|
40
|
+
} }
|
|
41
|
+
function EntityDataGridComponent_div_2_ng_container_3_button_1_i_1_Template(rf, ctx) { if (rf & 1) {
|
|
42
|
+
i0.ɵɵelement(0, "i");
|
|
43
|
+
} if (rf & 2) {
|
|
44
|
+
const button_r6 = i0.ɵɵnextContext(2).$implicit;
|
|
45
|
+
i0.ɵɵclassMap(button_r6.icon);
|
|
46
|
+
} }
|
|
47
|
+
function EntityDataGridComponent_div_2_ng_container_3_button_1_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
48
|
+
i0.ɵɵelementStart(0, "span");
|
|
49
|
+
i0.ɵɵtext(1);
|
|
50
|
+
i0.ɵɵelementEnd();
|
|
51
|
+
} if (rf & 2) {
|
|
52
|
+
const button_r6 = i0.ɵɵnextContext(2).$implicit;
|
|
53
|
+
i0.ɵɵadvance();
|
|
54
|
+
i0.ɵɵtextInterpolate(button_r6.text);
|
|
55
|
+
} }
|
|
56
|
+
function EntityDataGridComponent_div_2_ng_container_3_button_1_Template(rf, ctx) { if (rf & 1) {
|
|
57
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
58
|
+
i0.ɵɵelementStart(0, "button", 39);
|
|
59
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_ng_container_3_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const button_r6 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onToolbarButtonClick(button_r6)); });
|
|
60
|
+
i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_3_button_1_i_1_Template, 1, 2, "i", 40)(2, EntityDataGridComponent_div_2_ng_container_3_button_1_span_2_Template, 2, 1, "span", 41);
|
|
61
|
+
i0.ɵɵelementEnd();
|
|
62
|
+
} if (rf & 2) {
|
|
63
|
+
const button_r6 = i0.ɵɵnextContext().$implicit;
|
|
64
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
65
|
+
i0.ɵɵclassMap(button_r6.cssClass);
|
|
66
|
+
i0.ɵɵproperty("disabled", ctx_r2.isButtonDisabled(button_r6))("title", button_r6.tooltip || "");
|
|
67
|
+
i0.ɵɵadvance();
|
|
68
|
+
i0.ɵɵproperty("ngIf", button_r6.icon);
|
|
69
|
+
i0.ɵɵadvance();
|
|
70
|
+
i0.ɵɵproperty("ngIf", button_r6.text);
|
|
71
|
+
} }
|
|
72
|
+
function EntityDataGridComponent_div_2_ng_container_3_Template(rf, ctx) { if (rf & 1) {
|
|
73
|
+
i0.ɵɵelementContainerStart(0);
|
|
74
|
+
i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_3_button_1_Template, 3, 6, "button", 38);
|
|
75
|
+
i0.ɵɵelementContainerEnd();
|
|
76
|
+
} if (rf & 2) {
|
|
77
|
+
const button_r6 = ctx.$implicit;
|
|
78
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
79
|
+
i0.ɵɵadvance();
|
|
80
|
+
i0.ɵɵproperty("ngIf", button_r6.position !== "right" && ctx_r2.isButtonVisible(button_r6));
|
|
81
|
+
} }
|
|
82
|
+
function EntityDataGridComponent_div_2_span_5_Template(rf, ctx) { if (rf & 1) {
|
|
83
|
+
i0.ɵɵelementStart(0, "span", 42);
|
|
84
|
+
i0.ɵɵtext(1);
|
|
85
|
+
i0.ɵɵelementEnd();
|
|
86
|
+
} if (rf & 2) {
|
|
87
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
88
|
+
i0.ɵɵadvance();
|
|
89
|
+
i0.ɵɵtextInterpolate2(" ", ctx_r2.totalRowCount, " ", ctx_r2.totalRowCount === 1 ? "row" : "rows", " ");
|
|
90
|
+
} }
|
|
91
|
+
function EntityDataGridComponent_div_2_span_6_Template(rf, ctx) { if (rf & 1) {
|
|
92
|
+
i0.ɵɵelementStart(0, "span", 43);
|
|
93
|
+
i0.ɵɵtext(1);
|
|
94
|
+
i0.ɵɵelementEnd();
|
|
95
|
+
} if (rf & 2) {
|
|
96
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
97
|
+
i0.ɵɵadvance();
|
|
98
|
+
i0.ɵɵtextInterpolate1(" (", ctx_r2.SelectedKeys.length, " selected) ");
|
|
99
|
+
} }
|
|
100
|
+
function EntityDataGridComponent_div_2_button_8_Template(rf, ctx) { if (rf & 1) {
|
|
101
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
102
|
+
i0.ɵɵelementStart(0, "button", 44);
|
|
103
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onAddClick()); });
|
|
104
|
+
i0.ɵɵelement(1, "i", 45);
|
|
105
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
106
|
+
i0.ɵɵtext(3, "New");
|
|
107
|
+
i0.ɵɵelementEnd()();
|
|
108
|
+
} }
|
|
109
|
+
function EntityDataGridComponent_div_2_button_9_Template(rf, ctx) { if (rf & 1) {
|
|
110
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
111
|
+
i0.ɵɵelementStart(0, "button", 47);
|
|
112
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_9_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onRefreshClick()); });
|
|
113
|
+
i0.ɵɵelement(1, "i", 48);
|
|
114
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
115
|
+
i0.ɵɵtext(3, "Refresh");
|
|
116
|
+
i0.ɵɵelementEnd()();
|
|
117
|
+
} if (rf & 2) {
|
|
118
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
119
|
+
i0.ɵɵproperty("disabled", ctx_r2.loading);
|
|
120
|
+
i0.ɵɵadvance();
|
|
121
|
+
i0.ɵɵclassProp("fa-spin", ctx_r2.loading);
|
|
122
|
+
} }
|
|
123
|
+
function EntityDataGridComponent_div_2_button_10_Template(rf, ctx) { if (rf & 1) {
|
|
124
|
+
const _r9 = i0.ɵɵgetCurrentView();
|
|
125
|
+
i0.ɵɵelementStart(0, "button", 49);
|
|
126
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r9); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onExportClick()); });
|
|
127
|
+
i0.ɵɵelement(1, "i", 50);
|
|
128
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
129
|
+
i0.ɵɵtext(3, "Export");
|
|
130
|
+
i0.ɵɵelementEnd()();
|
|
131
|
+
} }
|
|
132
|
+
function EntityDataGridComponent_div_2_button_11_Template(rf, ctx) { if (rf & 1) {
|
|
133
|
+
const _r10 = i0.ɵɵgetCurrentView();
|
|
134
|
+
i0.ɵɵelementStart(0, "button", 51);
|
|
135
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_11_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDeleteClick()); });
|
|
136
|
+
i0.ɵɵelement(1, "i", 52);
|
|
137
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
138
|
+
i0.ɵɵtext(3, "Delete");
|
|
139
|
+
i0.ɵɵelementEnd()();
|
|
140
|
+
} }
|
|
141
|
+
function EntityDataGridComponent_div_2_button_12_Template(rf, ctx) { if (rf & 1) {
|
|
142
|
+
const _r11 = i0.ɵɵgetCurrentView();
|
|
143
|
+
i0.ɵɵelementStart(0, "button", 53);
|
|
144
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_12_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r11); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCompareClick()); });
|
|
145
|
+
i0.ɵɵelement(1, "i", 54);
|
|
146
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
147
|
+
i0.ɵɵtext(3, "Compare");
|
|
148
|
+
i0.ɵɵelementEnd()();
|
|
149
|
+
} if (rf & 2) {
|
|
150
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
151
|
+
i0.ɵɵproperty("disabled", !ctx_r2.HasMultipleSelection);
|
|
152
|
+
} }
|
|
153
|
+
function EntityDataGridComponent_div_2_button_13_Template(rf, ctx) { if (rf & 1) {
|
|
154
|
+
const _r12 = i0.ɵɵgetCurrentView();
|
|
155
|
+
i0.ɵɵelementStart(0, "button", 55);
|
|
156
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_13_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r12); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onMergeClick()); });
|
|
157
|
+
i0.ɵɵelement(1, "i", 56);
|
|
158
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
159
|
+
i0.ɵɵtext(3, "Merge");
|
|
160
|
+
i0.ɵɵelementEnd()();
|
|
161
|
+
} if (rf & 2) {
|
|
162
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
163
|
+
i0.ɵɵproperty("disabled", !ctx_r2.HasMultipleSelection);
|
|
164
|
+
} }
|
|
165
|
+
function EntityDataGridComponent_div_2_button_14_Template(rf, ctx) { if (rf & 1) {
|
|
166
|
+
const _r13 = i0.ɵɵgetCurrentView();
|
|
167
|
+
i0.ɵɵelementStart(0, "button", 57);
|
|
168
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_14_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r13); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onAddToListClick()); });
|
|
169
|
+
i0.ɵɵelement(1, "i", 58);
|
|
170
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
171
|
+
i0.ɵɵtext(3, "Add to List");
|
|
172
|
+
i0.ɵɵelementEnd()();
|
|
173
|
+
} if (rf & 2) {
|
|
174
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
175
|
+
i0.ɵɵproperty("disabled", !ctx_r2.HasSelection);
|
|
176
|
+
} }
|
|
177
|
+
function EntityDataGridComponent_div_2_button_15_Template(rf, ctx) { if (rf & 1) {
|
|
178
|
+
const _r14 = i0.ɵɵgetCurrentView();
|
|
179
|
+
i0.ɵɵelementStart(0, "button", 59);
|
|
180
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_15_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r14); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDuplicateSearchClick()); });
|
|
181
|
+
i0.ɵɵelement(1, "i", 60);
|
|
182
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
183
|
+
i0.ɵɵtext(3, "Find Duplicates");
|
|
184
|
+
i0.ɵɵelementEnd()();
|
|
185
|
+
} if (rf & 2) {
|
|
186
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
187
|
+
i0.ɵɵproperty("disabled", !ctx_r2.HasMultipleSelection);
|
|
188
|
+
} }
|
|
189
|
+
function EntityDataGridComponent_div_2_button_16_Template(rf, ctx) { if (rf & 1) {
|
|
190
|
+
const _r15 = i0.ɵɵgetCurrentView();
|
|
191
|
+
i0.ɵɵelementStart(0, "button", 61);
|
|
192
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_16_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r15); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCommunicationClick()); });
|
|
193
|
+
i0.ɵɵelement(1, "i", 62);
|
|
194
|
+
i0.ɵɵelementStart(2, "span", 46);
|
|
195
|
+
i0.ɵɵtext(3, "Send Message");
|
|
196
|
+
i0.ɵɵelementEnd()();
|
|
197
|
+
} if (rf & 2) {
|
|
198
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
199
|
+
i0.ɵɵproperty("disabled", !ctx_r2.HasSelection);
|
|
200
|
+
} }
|
|
201
|
+
function EntityDataGridComponent_div_2_button_17_Template(rf, ctx) { if (rf & 1) {
|
|
202
|
+
const _r16 = i0.ɵɵgetCurrentView();
|
|
203
|
+
i0.ɵɵelementStart(0, "button", 63);
|
|
204
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_17_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onAddClick()); });
|
|
205
|
+
i0.ɵɵelement(1, "i", 45);
|
|
206
|
+
i0.ɵɵelementEnd();
|
|
207
|
+
} }
|
|
208
|
+
function EntityDataGridComponent_div_2_button_18_Template(rf, ctx) { if (rf & 1) {
|
|
209
|
+
const _r17 = i0.ɵɵgetCurrentView();
|
|
210
|
+
i0.ɵɵelementStart(0, "button", 64);
|
|
211
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_18_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r17); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onRefreshClick()); });
|
|
212
|
+
i0.ɵɵelement(1, "i", 65);
|
|
213
|
+
i0.ɵɵelementEnd();
|
|
214
|
+
} if (rf & 2) {
|
|
215
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
216
|
+
i0.ɵɵproperty("disabled", ctx_r2.loading);
|
|
217
|
+
i0.ɵɵadvance();
|
|
218
|
+
i0.ɵɵclassProp("fa-spin", ctx_r2.loading);
|
|
219
|
+
} }
|
|
220
|
+
function EntityDataGridComponent_div_2_button_19_Template(rf, ctx) { if (rf & 1) {
|
|
221
|
+
const _r18 = i0.ɵɵgetCurrentView();
|
|
222
|
+
i0.ɵɵelementStart(0, "button", 66);
|
|
223
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_19_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r18); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDeleteClick()); });
|
|
224
|
+
i0.ɵɵelement(1, "i", 52);
|
|
225
|
+
i0.ɵɵelementEnd();
|
|
226
|
+
} }
|
|
227
|
+
function EntityDataGridComponent_div_2_button_20_Template(rf, ctx) { if (rf & 1) {
|
|
228
|
+
const _r19 = i0.ɵɵgetCurrentView();
|
|
229
|
+
i0.ɵɵelementStart(0, "button", 67);
|
|
230
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_20_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r19); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onExportClick()); });
|
|
231
|
+
i0.ɵɵelement(1, "i", 68);
|
|
232
|
+
i0.ɵɵelementEnd();
|
|
233
|
+
} }
|
|
234
|
+
function EntityDataGridComponent_div_2_button_21_Template(rf, ctx) { if (rf & 1) {
|
|
235
|
+
const _r20 = i0.ɵɵgetCurrentView();
|
|
236
|
+
i0.ɵɵelementStart(0, "button", 69);
|
|
237
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_button_21_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r20); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onColumnChooserClick()); });
|
|
238
|
+
i0.ɵɵelement(1, "i", 70);
|
|
239
|
+
i0.ɵɵelementEnd();
|
|
240
|
+
} }
|
|
241
|
+
function EntityDataGridComponent_div_2_ng_container_22_button_1_i_1_Template(rf, ctx) { if (rf & 1) {
|
|
242
|
+
i0.ɵɵelement(0, "i");
|
|
243
|
+
} if (rf & 2) {
|
|
244
|
+
const button_r22 = i0.ɵɵnextContext(2).$implicit;
|
|
245
|
+
i0.ɵɵclassMap(button_r22.icon);
|
|
246
|
+
} }
|
|
247
|
+
function EntityDataGridComponent_div_2_ng_container_22_button_1_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
248
|
+
i0.ɵɵelementStart(0, "span");
|
|
249
|
+
i0.ɵɵtext(1);
|
|
250
|
+
i0.ɵɵelementEnd();
|
|
251
|
+
} if (rf & 2) {
|
|
252
|
+
const button_r22 = i0.ɵɵnextContext(2).$implicit;
|
|
253
|
+
i0.ɵɵadvance();
|
|
254
|
+
i0.ɵɵtextInterpolate(button_r22.text);
|
|
255
|
+
} }
|
|
256
|
+
function EntityDataGridComponent_div_2_ng_container_22_button_1_Template(rf, ctx) { if (rf & 1) {
|
|
257
|
+
const _r21 = i0.ɵɵgetCurrentView();
|
|
258
|
+
i0.ɵɵelementStart(0, "button", 39);
|
|
259
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_ng_container_22_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r21); const button_r22 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onToolbarButtonClick(button_r22)); });
|
|
260
|
+
i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_22_button_1_i_1_Template, 1, 2, "i", 40)(2, EntityDataGridComponent_div_2_ng_container_22_button_1_span_2_Template, 2, 1, "span", 41);
|
|
261
|
+
i0.ɵɵelementEnd();
|
|
262
|
+
} if (rf & 2) {
|
|
263
|
+
const button_r22 = i0.ɵɵnextContext().$implicit;
|
|
264
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
265
|
+
i0.ɵɵclassMap(button_r22.cssClass);
|
|
266
|
+
i0.ɵɵproperty("disabled", ctx_r2.isButtonDisabled(button_r22))("title", button_r22.tooltip || "");
|
|
267
|
+
i0.ɵɵadvance();
|
|
268
|
+
i0.ɵɵproperty("ngIf", button_r22.icon);
|
|
269
|
+
i0.ɵɵadvance();
|
|
270
|
+
i0.ɵɵproperty("ngIf", button_r22.text);
|
|
271
|
+
} }
|
|
272
|
+
function EntityDataGridComponent_div_2_ng_container_22_Template(rf, ctx) { if (rf & 1) {
|
|
273
|
+
i0.ɵɵelementContainerStart(0);
|
|
274
|
+
i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_ng_container_22_button_1_Template, 3, 6, "button", 38);
|
|
275
|
+
i0.ɵɵelementContainerEnd();
|
|
276
|
+
} if (rf & 2) {
|
|
277
|
+
const button_r22 = ctx.$implicit;
|
|
278
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
279
|
+
i0.ɵɵadvance();
|
|
280
|
+
i0.ɵɵproperty("ngIf", button_r22.position === "right" && ctx_r2.isButtonVisible(button_r22));
|
|
281
|
+
} }
|
|
282
|
+
function EntityDataGridComponent_div_2_div_23_div_3_button_1_Template(rf, ctx) { if (rf & 1) {
|
|
283
|
+
const _r24 = i0.ɵɵgetCurrentView();
|
|
284
|
+
i0.ɵɵelementStart(0, "button", 78);
|
|
285
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r24); const ctx_r2 = i0.ɵɵnextContext(4); ctx_r2.onExportClick(); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
|
|
286
|
+
i0.ɵɵelement(1, "i", 50);
|
|
287
|
+
i0.ɵɵelementStart(2, "span");
|
|
288
|
+
i0.ɵɵtext(3, "Export to Excel");
|
|
289
|
+
i0.ɵɵelementEnd()();
|
|
290
|
+
} }
|
|
291
|
+
function EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_button_4_Template(rf, ctx) { if (rf & 1) {
|
|
292
|
+
const _r25 = i0.ɵɵgetCurrentView();
|
|
293
|
+
i0.ɵɵelementStart(0, "button", 82);
|
|
294
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_button_4_Template_button_click_0_listener() { const action_r26 = i0.ɵɵrestoreView(_r25).$implicit; const ctx_r2 = i0.ɵɵnextContext(5); ctx_r2.onEntityActionClick(action_r26); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
|
|
295
|
+
i0.ɵɵelement(1, "i");
|
|
296
|
+
i0.ɵɵelementStart(2, "span");
|
|
297
|
+
i0.ɵɵtext(3);
|
|
298
|
+
i0.ɵɵelementEnd()();
|
|
299
|
+
} if (rf & 2) {
|
|
300
|
+
const action_r26 = ctx.$implicit;
|
|
301
|
+
const ctx_r2 = i0.ɵɵnextContext(5);
|
|
302
|
+
i0.ɵɵproperty("disabled", !ctx_r2.isEntityActionEnabled(action_r26));
|
|
303
|
+
i0.ɵɵadvance();
|
|
304
|
+
i0.ɵɵclassMap(action_r26.icon || "fa-solid fa-bolt");
|
|
305
|
+
i0.ɵɵadvance(2);
|
|
306
|
+
i0.ɵɵtextInterpolate(action_r26.name);
|
|
307
|
+
} }
|
|
308
|
+
function EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_Template(rf, ctx) { if (rf & 1) {
|
|
309
|
+
i0.ɵɵelementContainerStart(0);
|
|
310
|
+
i0.ɵɵelement(1, "div", 79);
|
|
311
|
+
i0.ɵɵelementStart(2, "div", 80);
|
|
312
|
+
i0.ɵɵtext(3, "Actions");
|
|
313
|
+
i0.ɵɵelementEnd();
|
|
314
|
+
i0.ɵɵtemplate(4, EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_button_4_Template, 4, 4, "button", 81);
|
|
315
|
+
i0.ɵɵelementContainerEnd();
|
|
316
|
+
} if (rf & 2) {
|
|
317
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
318
|
+
i0.ɵɵadvance(4);
|
|
319
|
+
i0.ɵɵproperty("ngForOf", ctx_r2.EntityActions);
|
|
320
|
+
} }
|
|
321
|
+
function EntityDataGridComponent_div_2_div_23_div_3_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
322
|
+
i0.ɵɵelement(0, "div", 79);
|
|
323
|
+
} }
|
|
324
|
+
function EntityDataGridComponent_div_2_div_23_div_3_button_4_Template(rf, ctx) { if (rf & 1) {
|
|
325
|
+
const _r27 = i0.ɵɵgetCurrentView();
|
|
326
|
+
i0.ɵɵelementStart(0, "button", 78);
|
|
327
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_button_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r2 = i0.ɵɵnextContext(4); ctx_r2.onColumnChooserClick(); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
|
|
328
|
+
i0.ɵɵelement(1, "i", 70);
|
|
329
|
+
i0.ɵɵelementStart(2, "span");
|
|
330
|
+
i0.ɵɵtext(3, "Manage Columns");
|
|
331
|
+
i0.ɵɵelementEnd()();
|
|
332
|
+
} }
|
|
333
|
+
function EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_button_2_Template(rf, ctx) { if (rf & 1) {
|
|
334
|
+
const _r28 = i0.ɵɵgetCurrentView();
|
|
335
|
+
i0.ɵɵelementStart(0, "button", 78);
|
|
336
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_button_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r28); const ctx_r2 = i0.ɵɵnextContext(5); ctx_r2.onCommunicationClick(); return i0.ɵɵresetView(ctx_r2.closeOverflowMenu()); });
|
|
337
|
+
i0.ɵɵelement(1, "i", 62);
|
|
338
|
+
i0.ɵɵelementStart(2, "span");
|
|
339
|
+
i0.ɵɵtext(3, "Send Message");
|
|
340
|
+
i0.ɵɵelementEnd()();
|
|
341
|
+
} }
|
|
342
|
+
function EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_Template(rf, ctx) { if (rf & 1) {
|
|
343
|
+
i0.ɵɵelementContainerStart(0);
|
|
344
|
+
i0.ɵɵelement(1, "div", 79);
|
|
345
|
+
i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_button_2_Template, 4, 0, "button", 76);
|
|
346
|
+
i0.ɵɵelementContainerEnd();
|
|
347
|
+
} if (rf & 2) {
|
|
348
|
+
const ctx_r2 = i0.ɵɵnextContext(4);
|
|
349
|
+
i0.ɵɵadvance(2);
|
|
350
|
+
i0.ɵɵproperty("ngIf", ctx_r2.showCommunicationInOverflow);
|
|
351
|
+
} }
|
|
352
|
+
function EntityDataGridComponent_div_2_div_23_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
353
|
+
i0.ɵɵelementStart(0, "div", 75);
|
|
354
|
+
i0.ɵɵtemplate(1, EntityDataGridComponent_div_2_div_23_div_3_button_1_Template, 4, 0, "button", 76)(2, EntityDataGridComponent_div_2_div_23_div_3_ng_container_2_Template, 5, 1, "ng-container", 41)(3, EntityDataGridComponent_div_2_div_23_div_3_div_3_Template, 1, 0, "div", 77)(4, EntityDataGridComponent_div_2_div_23_div_3_button_4_Template, 4, 0, "button", 76)(5, EntityDataGridComponent_div_2_div_23_div_3_ng_container_5_Template, 3, 1, "ng-container", 41);
|
|
355
|
+
i0.ɵɵelementEnd();
|
|
356
|
+
} if (rf & 2) {
|
|
357
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
358
|
+
i0.ɵɵproperty("@fadeIn", undefined);
|
|
359
|
+
i0.ɵɵadvance();
|
|
360
|
+
i0.ɵɵproperty("ngIf", ctx_r2.showExportInOverflow);
|
|
361
|
+
i0.ɵɵadvance();
|
|
362
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowEntityActionButtons && ctx_r2.EntityActions.length > 0);
|
|
363
|
+
i0.ɵɵadvance();
|
|
364
|
+
i0.ɵɵproperty("ngIf", ctx_r2.showColumnChooserInOverflow);
|
|
365
|
+
i0.ɵɵadvance();
|
|
366
|
+
i0.ɵɵproperty("ngIf", ctx_r2.showColumnChooserInOverflow);
|
|
367
|
+
i0.ɵɵadvance();
|
|
368
|
+
i0.ɵɵproperty("ngIf", ctx_r2.HasSelection && ctx_r2.hasSelectionDependentOverflowActions);
|
|
369
|
+
} }
|
|
370
|
+
function EntityDataGridComponent_div_2_div_23_Template(rf, ctx) { if (rf & 1) {
|
|
371
|
+
const _r23 = i0.ɵɵgetCurrentView();
|
|
372
|
+
i0.ɵɵelementStart(0, "div", 71);
|
|
373
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r23); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
374
|
+
i0.ɵɵelementStart(1, "button", 72);
|
|
375
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_2_div_23_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r23); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.toggleOverflowMenu()); });
|
|
376
|
+
i0.ɵɵelement(2, "i", 73);
|
|
377
|
+
i0.ɵɵelementEnd();
|
|
378
|
+
i0.ɵɵtemplate(3, EntityDataGridComponent_div_2_div_23_div_3_Template, 6, 6, "div", 74);
|
|
379
|
+
i0.ɵɵelementEnd();
|
|
380
|
+
} if (rf & 2) {
|
|
381
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
382
|
+
i0.ɵɵadvance(3);
|
|
383
|
+
i0.ɵɵproperty("ngIf", ctx_r2.showOverflowMenu);
|
|
384
|
+
} }
|
|
385
|
+
function EntityDataGridComponent_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
386
|
+
i0.ɵɵelementStart(0, "div", 9)(1, "div", 10);
|
|
387
|
+
i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_div_2_Template, 4, 3, "div", 11)(3, EntityDataGridComponent_div_2_ng_container_3_Template, 2, 1, "ng-container", 12);
|
|
388
|
+
i0.ɵɵelementEnd();
|
|
389
|
+
i0.ɵɵelementStart(4, "div", 13);
|
|
390
|
+
i0.ɵɵtemplate(5, EntityDataGridComponent_div_2_span_5_Template, 2, 2, "span", 14)(6, EntityDataGridComponent_div_2_span_6_Template, 2, 1, "span", 15);
|
|
391
|
+
i0.ɵɵelementEnd();
|
|
392
|
+
i0.ɵɵelementStart(7, "div", 16);
|
|
393
|
+
i0.ɵɵtemplate(8, EntityDataGridComponent_div_2_button_8_Template, 4, 0, "button", 17)(9, EntityDataGridComponent_div_2_button_9_Template, 4, 3, "button", 18)(10, EntityDataGridComponent_div_2_button_10_Template, 4, 0, "button", 19)(11, EntityDataGridComponent_div_2_button_11_Template, 4, 0, "button", 20)(12, EntityDataGridComponent_div_2_button_12_Template, 4, 1, "button", 21)(13, EntityDataGridComponent_div_2_button_13_Template, 4, 1, "button", 22)(14, EntityDataGridComponent_div_2_button_14_Template, 4, 1, "button", 23)(15, EntityDataGridComponent_div_2_button_15_Template, 4, 1, "button", 24)(16, EntityDataGridComponent_div_2_button_16_Template, 4, 1, "button", 25)(17, EntityDataGridComponent_div_2_button_17_Template, 2, 0, "button", 26)(18, EntityDataGridComponent_div_2_button_18_Template, 2, 3, "button", 27)(19, EntityDataGridComponent_div_2_button_19_Template, 2, 0, "button", 28)(20, EntityDataGridComponent_div_2_button_20_Template, 2, 0, "button", 29)(21, EntityDataGridComponent_div_2_button_21_Template, 2, 0, "button", 30)(22, EntityDataGridComponent_div_2_ng_container_22_Template, 2, 1, "ng-container", 12)(23, EntityDataGridComponent_div_2_div_23_Template, 4, 1, "div", 31);
|
|
394
|
+
i0.ɵɵelementEnd()();
|
|
395
|
+
} if (rf & 2) {
|
|
396
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
397
|
+
i0.ɵɵadvance(2);
|
|
398
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowSearch);
|
|
399
|
+
i0.ɵɵadvance();
|
|
400
|
+
i0.ɵɵproperty("ngForOf", ctx_r2.ToolbarConfig.customButtons);
|
|
401
|
+
i0.ɵɵadvance(2);
|
|
402
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showRowCount !== false);
|
|
403
|
+
i0.ɵɵadvance();
|
|
404
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showSelectionCount && ctx_r2.SelectedKeys.length > 0);
|
|
405
|
+
i0.ɵɵadvance(2);
|
|
406
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowNewButton);
|
|
407
|
+
i0.ɵɵadvance();
|
|
408
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowRefreshButton);
|
|
409
|
+
i0.ɵɵadvance();
|
|
410
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowExportButton);
|
|
411
|
+
i0.ɵɵadvance();
|
|
412
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowDeleteButton && ctx_r2.HasSelection);
|
|
413
|
+
i0.ɵɵadvance();
|
|
414
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowCompareButton);
|
|
415
|
+
i0.ɵɵadvance();
|
|
416
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowMergeButton);
|
|
417
|
+
i0.ɵɵadvance();
|
|
418
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowAddToListButton);
|
|
419
|
+
i0.ɵɵadvance();
|
|
420
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowDuplicateSearchButton);
|
|
421
|
+
i0.ɵɵadvance();
|
|
422
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ShowCommunicationButton);
|
|
423
|
+
i0.ɵɵadvance();
|
|
424
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showAdd && ctx_r2.AllowAdd && !ctx_r2.ShowNewButton);
|
|
425
|
+
i0.ɵɵadvance();
|
|
426
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showRefresh !== false && !ctx_r2.ShowRefreshButton);
|
|
427
|
+
i0.ɵɵadvance();
|
|
428
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showDelete && ctx_r2.AllowDelete && ctx_r2.HasSelection && !ctx_r2.ShowDeleteButton);
|
|
429
|
+
i0.ɵɵadvance();
|
|
430
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showExport && !ctx_r2.ShowExportButton);
|
|
431
|
+
i0.ɵɵadvance();
|
|
432
|
+
i0.ɵɵproperty("ngIf", ctx_r2.ToolbarConfig.showColumnChooser && ctx_r2.AllowColumnToggle);
|
|
433
|
+
i0.ɵɵadvance();
|
|
434
|
+
i0.ɵɵproperty("ngForOf", ctx_r2.ToolbarConfig.customButtons);
|
|
435
|
+
i0.ɵɵadvance();
|
|
436
|
+
i0.ɵɵproperty("ngIf", ctx_r2.hasOverflowMenuItems);
|
|
437
|
+
} }
|
|
438
|
+
function EntityDataGridComponent_div_4_Template(rf, ctx) { if (rf & 1) {
|
|
439
|
+
i0.ɵɵelementStart(0, "div", 83);
|
|
440
|
+
i0.ɵɵelement(1, "mj-loading", 84);
|
|
441
|
+
i0.ɵɵelementEnd();
|
|
442
|
+
} }
|
|
443
|
+
function EntityDataGridComponent_div_5_Template(rf, ctx) { if (rf & 1) {
|
|
444
|
+
const _r29 = i0.ɵɵgetCurrentView();
|
|
445
|
+
i0.ɵɵelementStart(0, "div", 85);
|
|
446
|
+
i0.ɵɵelement(1, "i", 86);
|
|
447
|
+
i0.ɵɵelementStart(2, "span");
|
|
448
|
+
i0.ɵɵtext(3);
|
|
449
|
+
i0.ɵɵelementEnd();
|
|
450
|
+
i0.ɵɵelementStart(4, "button", 87);
|
|
451
|
+
i0.ɵɵlistener("click", function EntityDataGridComponent_div_5_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r29); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.Refresh()); });
|
|
452
|
+
i0.ɵɵtext(5, "Retry");
|
|
453
|
+
i0.ɵɵelementEnd()();
|
|
454
|
+
} if (rf & 2) {
|
|
455
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
456
|
+
i0.ɵɵadvance(3);
|
|
457
|
+
i0.ɵɵtextInterpolate(ctx_r2.errorMessage);
|
|
458
|
+
} }
|
|
459
|
+
function EntityDataGridComponent_div_6_Template(rf, ctx) { if (rf & 1) {
|
|
460
|
+
i0.ɵɵelementStart(0, "div", 88);
|
|
461
|
+
i0.ɵɵelement(1, "i", 89);
|
|
462
|
+
i0.ɵɵelementStart(2, "span");
|
|
463
|
+
i0.ɵɵtext(3, "No data to display");
|
|
464
|
+
i0.ɵɵelementEnd()();
|
|
465
|
+
} }
|
|
466
|
+
function EntityDataGridComponent_ag_grid_angular_7_Template(rf, ctx) { if (rf & 1) {
|
|
467
|
+
const _r30 = i0.ɵɵgetCurrentView();
|
|
468
|
+
i0.ɵɵelementStart(0, "ag-grid-angular", 90);
|
|
469
|
+
i0.ɵɵlistener("gridReady", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_gridReady_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onGridReady($event)); })("rowClicked", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_rowClicked_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowClicked($event)); })("rowDoubleClicked", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_rowDoubleClicked_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowDoubleClicked($event)); })("sortChanged", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_sortChanged_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSortChanged($event)); })("selectionChanged", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_selectionChanged_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSelectionChanged($event)); })("columnResized", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_columnResized_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnResized($event)); })("columnMoved", function EntityDataGridComponent_ag_grid_angular_7_Template_ag_grid_angular_columnMoved_0_listener($event) { i0.ɵɵrestoreView(_r30); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnMoved($event)); });
|
|
470
|
+
i0.ɵɵelementEnd();
|
|
471
|
+
} if (rf & 2) {
|
|
472
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
473
|
+
i0.ɵɵproperty("theme", ctx_r2.agGridTheme)("columnDefs", ctx_r2.agColumnDefs)("rowData", ctx_r2.rowData)("defaultColDef", ctx_r2.defaultColDef)("rowSelection", ctx_r2.agRowSelection)("getRowId", ctx_r2.getRowId)("suppressCellFocus", true)("rowHeight", ctx_r2.RowHeight)("headerHeight", ctx_r2.ShowHeader ? undefined : 0)("rowMultiSelectWithClick", ctx_r2.SelectionMode === "multiple");
|
|
474
|
+
} }
|
|
475
|
+
function EntityDataGridComponent_ag_grid_angular_8_Template(rf, ctx) { if (rf & 1) {
|
|
476
|
+
const _r31 = i0.ɵɵgetCurrentView();
|
|
477
|
+
i0.ɵɵelementStart(0, "ag-grid-angular", 91);
|
|
478
|
+
i0.ɵɵlistener("gridReady", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_gridReady_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onGridReady($event)); })("rowClicked", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_rowClicked_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowClicked($event)); })("rowDoubleClicked", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_rowDoubleClicked_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgRowDoubleClicked($event)); })("sortChanged", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_sortChanged_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSortChanged($event)); })("selectionChanged", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_selectionChanged_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgSelectionChanged($event)); })("columnResized", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_columnResized_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnResized($event)); })("columnMoved", function EntityDataGridComponent_ag_grid_angular_8_Template_ag_grid_angular_columnMoved_0_listener($event) { i0.ɵɵrestoreView(_r31); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onAgColumnMoved($event)); });
|
|
479
|
+
i0.ɵɵelementEnd();
|
|
480
|
+
} if (rf & 2) {
|
|
481
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
482
|
+
i0.ɵɵproperty("theme", ctx_r2.agGridTheme)("columnDefs", ctx_r2.agColumnDefs)("defaultColDef", ctx_r2.defaultColDef)("rowSelection", ctx_r2.agRowSelection)("getRowId", ctx_r2.getRowId)("suppressCellFocus", true)("rowHeight", ctx_r2.RowHeight)("headerHeight", ctx_r2.ShowHeader ? undefined : 0)("rowMultiSelectWithClick", ctx_r2.SelectionMode === "multiple")("rowModelType", "infinite")("cacheBlockSize", ctx_r2.CacheBlockSize)("maxBlocksInCache", ctx_r2.MaxBlocksInCache)("infiniteInitialRowCount", 1)("cacheOverflowSize", 2);
|
|
483
|
+
} }
|
|
484
|
+
// Register AG Grid modules (required for v34+)
|
|
485
|
+
ModuleRegistry.registerModules([AllCommunityModule]);
|
|
486
|
+
/**
|
|
487
|
+
* A modern, flexible data grid component for displaying and managing entity data.
|
|
488
|
+
* Built on AG Grid Community edition with a rich Before/After cancelable event system.
|
|
489
|
+
*
|
|
490
|
+
* Features:
|
|
491
|
+
* - RunView-based data loading with automatic refresh
|
|
492
|
+
* - AG Grid for high-performance rendering
|
|
493
|
+
* - Rich event system with Before/After cancelable patterns
|
|
494
|
+
* - Column configuration with type-aware formatters
|
|
495
|
+
* - Row selection (single, multiple, checkbox modes)
|
|
496
|
+
* - Inline cell and row editing
|
|
497
|
+
* - Column reordering, resizing, and visibility toggle
|
|
498
|
+
* - State persistence to User Settings
|
|
499
|
+
* - Compatible with ViewGridStateConfig from User Views
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```html
|
|
503
|
+
* <mj-entity-data-grid
|
|
504
|
+
* [entityName]="'Contacts'"
|
|
505
|
+
* [showToolbar]="true"
|
|
506
|
+
* [allowSorting]="true"
|
|
507
|
+
* [selectionMode]="'multiple'"
|
|
508
|
+
* (afterRowDoubleClick)="onRowDoubleClick($event)">
|
|
509
|
+
* </mj-entity-data-grid>
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
export class EntityDataGridComponent {
|
|
513
|
+
cdr;
|
|
514
|
+
elementRef;
|
|
515
|
+
exportService;
|
|
516
|
+
// ========================================
|
|
517
|
+
// Data Source Inputs (RunViewParams-based)
|
|
518
|
+
// ========================================
|
|
519
|
+
_params = null;
|
|
520
|
+
/**
|
|
521
|
+
* RunViewParams for data loading - the primary way to specify data source.
|
|
522
|
+
* Supports stored views (ViewID, ViewName, ViewEntity) and dynamic views (EntityName + filters).
|
|
523
|
+
* Mutually exclusive with the legacy entityName/extraFilter inputs.
|
|
524
|
+
* When Params is provided, it takes precedence over legacy inputs.
|
|
525
|
+
*/
|
|
526
|
+
set Params(value) {
|
|
527
|
+
const previousValue = this._params;
|
|
528
|
+
this._params = value;
|
|
529
|
+
// Use deep comparison to avoid triggering changes when the same params are passed
|
|
530
|
+
// as a new object reference (common with template bindings like BuildRelationshipViewParamsByEntityName)
|
|
531
|
+
if (!RunViewParams.Equals(value, previousValue)) {
|
|
532
|
+
this.onParamsChanged();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
get Params() {
|
|
536
|
+
return this._params;
|
|
537
|
+
}
|
|
538
|
+
_allowLoad = true;
|
|
539
|
+
/**
|
|
540
|
+
* Controls whether data loading is allowed.
|
|
541
|
+
* Set to false to defer loading until ready (useful for complex forms where params may change multiple times).
|
|
542
|
+
* When set to true and Params is provided, triggers a data load.
|
|
543
|
+
*/
|
|
544
|
+
set AllowLoad(value) {
|
|
545
|
+
const previousValue = this._allowLoad;
|
|
546
|
+
this._allowLoad = value;
|
|
547
|
+
if (value && !previousValue && this._params) {
|
|
548
|
+
// AllowLoad was just enabled and we have params - load now
|
|
549
|
+
this.loadData(false);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
get AllowLoad() {
|
|
553
|
+
return this._allowLoad;
|
|
554
|
+
}
|
|
555
|
+
_autoRefreshOnParamsChange = true;
|
|
556
|
+
/**
|
|
557
|
+
* When true, automatically refreshes data when Params changes.
|
|
558
|
+
* When false, you must call Refresh() manually after changing Params.
|
|
559
|
+
*/
|
|
560
|
+
set AutoRefreshOnParamsChange(value) {
|
|
561
|
+
this._autoRefreshOnParamsChange = value;
|
|
562
|
+
}
|
|
563
|
+
get AutoRefreshOnParamsChange() {
|
|
564
|
+
return this._autoRefreshOnParamsChange;
|
|
565
|
+
}
|
|
566
|
+
// ========================================
|
|
567
|
+
// Pagination Inputs
|
|
568
|
+
// ========================================
|
|
569
|
+
_paginationMode = 'client';
|
|
570
|
+
/**
|
|
571
|
+
* Pagination mode for the grid:
|
|
572
|
+
* - 'client': All data loaded upfront (default, legacy behavior)
|
|
573
|
+
* - 'infinite': Server-side pagination with infinite scroll (recommended for large datasets)
|
|
574
|
+
*/
|
|
575
|
+
set PaginationMode(value) {
|
|
576
|
+
this._paginationMode = value;
|
|
577
|
+
}
|
|
578
|
+
get PaginationMode() {
|
|
579
|
+
return this._paginationMode;
|
|
580
|
+
}
|
|
581
|
+
_pageSize = 100;
|
|
582
|
+
/**
|
|
583
|
+
* Number of rows to fetch per page when using infinite pagination mode.
|
|
584
|
+
* Default is 100 rows per page.
|
|
585
|
+
*/
|
|
586
|
+
set PageSize(value) {
|
|
587
|
+
this._pageSize = value;
|
|
588
|
+
}
|
|
589
|
+
get PageSize() {
|
|
590
|
+
return this._pageSize;
|
|
591
|
+
}
|
|
592
|
+
_cacheBlockSize = 100;
|
|
593
|
+
/**
|
|
594
|
+
* Size of each cache block in infinite scroll mode.
|
|
595
|
+
* Should match or be a multiple of PageSize for optimal performance.
|
|
596
|
+
*/
|
|
597
|
+
set CacheBlockSize(value) {
|
|
598
|
+
this._cacheBlockSize = value;
|
|
599
|
+
}
|
|
600
|
+
get CacheBlockSize() {
|
|
601
|
+
return this._cacheBlockSize;
|
|
602
|
+
}
|
|
603
|
+
_maxBlocksInCache = 10;
|
|
604
|
+
/**
|
|
605
|
+
* Maximum number of blocks to keep in cache.
|
|
606
|
+
* When exceeded, oldest blocks are purged.
|
|
607
|
+
*/
|
|
608
|
+
set MaxBlocksInCache(value) {
|
|
609
|
+
this._maxBlocksInCache = value;
|
|
610
|
+
}
|
|
611
|
+
get MaxBlocksInCache() {
|
|
612
|
+
return this._maxBlocksInCache;
|
|
613
|
+
}
|
|
614
|
+
// ========================================
|
|
615
|
+
// External Data Input
|
|
616
|
+
// ========================================
|
|
617
|
+
_data = [];
|
|
618
|
+
/**
|
|
619
|
+
* Pre-loaded data (bypass RunView, use provided data).
|
|
620
|
+
* When provided, the grid displays this data instead of loading via RunView.
|
|
621
|
+
* Parent component is responsible for data loading and passing results here.
|
|
622
|
+
*/
|
|
623
|
+
set Data(value) {
|
|
624
|
+
const hadData = this._data.length > 0;
|
|
625
|
+
this._data = value || [];
|
|
626
|
+
this._useExternalData = this._data.length > 0;
|
|
627
|
+
if (this._useExternalData || hadData) {
|
|
628
|
+
this.processData();
|
|
629
|
+
// Reapply sort state to grid after data changes to maintain visual indicators
|
|
630
|
+
// Use microtask to ensure AG Grid has processed the new row data first
|
|
631
|
+
if (this.gridApi && this._sortState.length > 0) {
|
|
632
|
+
Promise.resolve().then(() => {
|
|
633
|
+
this.applySortStateToGrid();
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
get Data() {
|
|
639
|
+
return this._data;
|
|
640
|
+
}
|
|
641
|
+
// ========================================
|
|
642
|
+
// Column Configuration Inputs
|
|
643
|
+
// ========================================
|
|
644
|
+
_columns = [];
|
|
645
|
+
/**
|
|
646
|
+
* Column definitions - if not provided, auto-generates from entity metadata
|
|
647
|
+
*/
|
|
648
|
+
set Columns(value) {
|
|
649
|
+
this._columns = value || [];
|
|
650
|
+
if (this._columns.length > 0) {
|
|
651
|
+
this.initializeColumnStates();
|
|
652
|
+
this.buildAgColumnDefs();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
get Columns() {
|
|
656
|
+
return this._columns;
|
|
657
|
+
}
|
|
658
|
+
_gridState = null;
|
|
659
|
+
/**
|
|
660
|
+
* Grid state from a User View - controls columns, widths, order, sort
|
|
661
|
+
* When provided, this takes precedence over auto-generated columns
|
|
662
|
+
*/
|
|
663
|
+
set GridState(value) {
|
|
664
|
+
if (!!value) {
|
|
665
|
+
const previousValue = this._gridState;
|
|
666
|
+
this._gridState = value;
|
|
667
|
+
if (value !== previousValue) {
|
|
668
|
+
this.onGridStateChanged();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
get GridState() {
|
|
673
|
+
return this._gridState;
|
|
674
|
+
}
|
|
675
|
+
_allowColumnReorder = true;
|
|
676
|
+
set AllowColumnReorder(value) {
|
|
677
|
+
this._allowColumnReorder = value;
|
|
678
|
+
}
|
|
679
|
+
get AllowColumnReorder() {
|
|
680
|
+
return this._allowColumnReorder;
|
|
681
|
+
}
|
|
682
|
+
_allowColumnResize = true;
|
|
683
|
+
set AllowColumnResize(value) {
|
|
684
|
+
this._allowColumnResize = value;
|
|
685
|
+
}
|
|
686
|
+
get AllowColumnResize() {
|
|
687
|
+
return this._allowColumnResize;
|
|
688
|
+
}
|
|
689
|
+
_allowColumnToggle = true;
|
|
690
|
+
set AllowColumnToggle(value) {
|
|
691
|
+
this._allowColumnToggle = value;
|
|
692
|
+
}
|
|
693
|
+
get AllowColumnToggle() {
|
|
694
|
+
return this._allowColumnToggle;
|
|
695
|
+
}
|
|
696
|
+
_showHeader = true;
|
|
697
|
+
set ShowHeader(value) {
|
|
698
|
+
this._showHeader = value;
|
|
699
|
+
}
|
|
700
|
+
get ShowHeader() {
|
|
701
|
+
return this._showHeader;
|
|
702
|
+
}
|
|
703
|
+
// ========================================
|
|
704
|
+
// Sorting & Filtering Inputs
|
|
705
|
+
// ========================================
|
|
706
|
+
_allowSorting = true;
|
|
707
|
+
set AllowSorting(value) {
|
|
708
|
+
this._allowSorting = value;
|
|
709
|
+
}
|
|
710
|
+
get AllowSorting() {
|
|
711
|
+
return this._allowSorting;
|
|
712
|
+
}
|
|
713
|
+
_allowMultiSort = true;
|
|
714
|
+
set AllowMultiSort(value) {
|
|
715
|
+
this._allowMultiSort = value;
|
|
716
|
+
}
|
|
717
|
+
get AllowMultiSort() {
|
|
718
|
+
return this._allowMultiSort;
|
|
719
|
+
}
|
|
720
|
+
_serverSideSorting = true;
|
|
721
|
+
/**
|
|
722
|
+
* Whether sorting is handled server-side
|
|
723
|
+
* When true, sort changes trigger a new data load
|
|
724
|
+
*/
|
|
725
|
+
set ServerSideSorting(value) {
|
|
726
|
+
this._serverSideSorting = value;
|
|
727
|
+
}
|
|
728
|
+
get ServerSideSorting() {
|
|
729
|
+
return this._serverSideSorting;
|
|
730
|
+
}
|
|
731
|
+
_allowColumnFilters = false;
|
|
732
|
+
set AllowColumnFilters(value) {
|
|
733
|
+
this._allowColumnFilters = value;
|
|
734
|
+
}
|
|
735
|
+
get AllowColumnFilters() {
|
|
736
|
+
return this._allowColumnFilters;
|
|
737
|
+
}
|
|
738
|
+
_showSearch = true;
|
|
739
|
+
set ShowSearch(value) {
|
|
740
|
+
this._showSearch = value;
|
|
741
|
+
}
|
|
742
|
+
get ShowSearch() {
|
|
743
|
+
return this._showSearch;
|
|
744
|
+
}
|
|
745
|
+
// ========================================
|
|
746
|
+
// Selection Inputs
|
|
747
|
+
// ========================================
|
|
748
|
+
_selectionMode = 'single';
|
|
749
|
+
set SelectionMode(value) {
|
|
750
|
+
this._selectionMode = value;
|
|
751
|
+
this.updateAgRowSelection();
|
|
752
|
+
if (value === 'none') {
|
|
753
|
+
this.ClearSelection();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
get SelectionMode() {
|
|
757
|
+
return this._selectionMode;
|
|
758
|
+
}
|
|
759
|
+
_selectedKeys = [];
|
|
760
|
+
set SelectedKeys(value) {
|
|
761
|
+
this._selectedKeys = value || [];
|
|
762
|
+
this.updateRowSelectionState();
|
|
763
|
+
}
|
|
764
|
+
get SelectedKeys() {
|
|
765
|
+
return this._selectedKeys;
|
|
766
|
+
}
|
|
767
|
+
_keyField = 'ID';
|
|
768
|
+
set KeyField(value) {
|
|
769
|
+
this._keyField = value || 'ID';
|
|
770
|
+
}
|
|
771
|
+
get KeyField() {
|
|
772
|
+
return this._keyField;
|
|
773
|
+
}
|
|
774
|
+
// ========================================
|
|
775
|
+
// Editing Inputs
|
|
776
|
+
// ========================================
|
|
777
|
+
_editMode = 'none';
|
|
778
|
+
set EditMode(value) {
|
|
779
|
+
this._editMode = value;
|
|
780
|
+
}
|
|
781
|
+
get EditMode() {
|
|
782
|
+
return this._editMode;
|
|
783
|
+
}
|
|
784
|
+
_allowAdd = false;
|
|
785
|
+
set AllowAdd(value) {
|
|
786
|
+
this._allowAdd = value;
|
|
787
|
+
}
|
|
788
|
+
get AllowAdd() {
|
|
789
|
+
return this._allowAdd;
|
|
790
|
+
}
|
|
791
|
+
_allowDelete = false;
|
|
792
|
+
set AllowDelete(value) {
|
|
793
|
+
this._allowDelete = value;
|
|
794
|
+
}
|
|
795
|
+
get AllowDelete() {
|
|
796
|
+
return this._allowDelete;
|
|
797
|
+
}
|
|
798
|
+
// ========================================
|
|
799
|
+
// Display Inputs
|
|
800
|
+
// ========================================
|
|
801
|
+
_height = 'auto';
|
|
802
|
+
set Height(value) {
|
|
803
|
+
this._height = value;
|
|
804
|
+
}
|
|
805
|
+
get Height() {
|
|
806
|
+
return this._height;
|
|
807
|
+
}
|
|
808
|
+
_rowHeight = 40;
|
|
809
|
+
set RowHeight(value) {
|
|
810
|
+
this._rowHeight = value;
|
|
811
|
+
}
|
|
812
|
+
get RowHeight() {
|
|
813
|
+
return this._rowHeight;
|
|
814
|
+
}
|
|
815
|
+
_virtualScroll = true;
|
|
816
|
+
set VirtualScroll(value) {
|
|
817
|
+
this._virtualScroll = value;
|
|
818
|
+
}
|
|
819
|
+
get VirtualScroll() {
|
|
820
|
+
return this._virtualScroll;
|
|
821
|
+
}
|
|
822
|
+
_showRowNumbers = false;
|
|
823
|
+
set ShowRowNumbers(value) {
|
|
824
|
+
this._showRowNumbers = value;
|
|
825
|
+
this.buildAgColumnDefs();
|
|
826
|
+
}
|
|
827
|
+
get ShowRowNumbers() {
|
|
828
|
+
return this._showRowNumbers;
|
|
829
|
+
}
|
|
830
|
+
_striped = true;
|
|
831
|
+
set Striped(value) {
|
|
832
|
+
this._striped = value;
|
|
833
|
+
}
|
|
834
|
+
get Striped() {
|
|
835
|
+
return this._striped;
|
|
836
|
+
}
|
|
837
|
+
_gridLines = 'horizontal';
|
|
838
|
+
set GridLines(value) {
|
|
839
|
+
this._gridLines = value;
|
|
840
|
+
}
|
|
841
|
+
get GridLines() {
|
|
842
|
+
return this._gridLines;
|
|
843
|
+
}
|
|
844
|
+
// ========================================
|
|
845
|
+
// Visual Customization Inputs
|
|
846
|
+
// ========================================
|
|
847
|
+
_visualConfig = {};
|
|
848
|
+
/**
|
|
849
|
+
* Visual configuration for the grid appearance.
|
|
850
|
+
* Allows customization of header styles, row colors, cell formatting, and more.
|
|
851
|
+
* All properties are optional - unset properties use attractive defaults.
|
|
852
|
+
*/
|
|
853
|
+
set VisualConfig(value) {
|
|
854
|
+
this._visualConfig = value || {};
|
|
855
|
+
this.applyVisualConfig();
|
|
856
|
+
}
|
|
857
|
+
get VisualConfig() {
|
|
858
|
+
return this._visualConfig;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Get the effective visual config (user config merged with defaults)
|
|
862
|
+
*/
|
|
863
|
+
get effectiveVisualConfig() {
|
|
864
|
+
return { ...DEFAULT_VISUAL_CONFIG, ...this._visualConfig };
|
|
865
|
+
}
|
|
866
|
+
// ========================================
|
|
867
|
+
// Toolbar Inputs
|
|
868
|
+
// ========================================
|
|
869
|
+
_showToolbar = true;
|
|
870
|
+
set ShowToolbar(value) {
|
|
871
|
+
this._showToolbar = value;
|
|
872
|
+
}
|
|
873
|
+
get ShowToolbar() {
|
|
874
|
+
return this._showToolbar;
|
|
875
|
+
}
|
|
876
|
+
_toolbarConfig = {};
|
|
877
|
+
set ToolbarConfig(value) {
|
|
878
|
+
this._toolbarConfig = value || {};
|
|
879
|
+
}
|
|
880
|
+
get ToolbarConfig() {
|
|
881
|
+
return this._toolbarConfig;
|
|
882
|
+
}
|
|
883
|
+
// ========================================
|
|
884
|
+
// State Inputs
|
|
885
|
+
// ========================================
|
|
886
|
+
_stateKey = '';
|
|
887
|
+
set StateKey(value) {
|
|
888
|
+
this._stateKey = value || '';
|
|
889
|
+
if (this._stateKey) {
|
|
890
|
+
this.loadPersistedState();
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
get StateKey() {
|
|
894
|
+
return this._stateKey;
|
|
895
|
+
}
|
|
896
|
+
_autoPersistState = true;
|
|
897
|
+
/**
|
|
898
|
+
* When true and using a stored view (ViewID/ViewName/ViewEntity in Params),
|
|
899
|
+
* automatically persists grid state changes (column widths, order, sort) to the UserView entity.
|
|
900
|
+
* Only saves if the user has edit permission on the view.
|
|
901
|
+
* For dynamic views, only emits the gridStateChanged event.
|
|
902
|
+
*/
|
|
903
|
+
set AutoPersistState(value) {
|
|
904
|
+
this._autoPersistState = value;
|
|
905
|
+
}
|
|
906
|
+
get AutoPersistState() {
|
|
907
|
+
return this._autoPersistState;
|
|
908
|
+
}
|
|
909
|
+
_statePersistDebounce = 5000;
|
|
910
|
+
/**
|
|
911
|
+
* Debounce time in milliseconds before persisting state changes to the server.
|
|
912
|
+
* Default is 5000ms (5 seconds) to avoid excessive server calls during rapid column adjustments.
|
|
913
|
+
*/
|
|
914
|
+
set StatePersistDebounce(value) {
|
|
915
|
+
this._statePersistDebounce = value;
|
|
916
|
+
// Re-setup the debounce subscription with new timing
|
|
917
|
+
this.setupStatePersistDebounce();
|
|
918
|
+
}
|
|
919
|
+
get StatePersistDebounce() {
|
|
920
|
+
return this._statePersistDebounce;
|
|
921
|
+
}
|
|
922
|
+
_refreshDebounce = 300;
|
|
923
|
+
set RefreshDebounce(value) {
|
|
924
|
+
this._refreshDebounce = value;
|
|
925
|
+
}
|
|
926
|
+
get RefreshDebounce() {
|
|
927
|
+
return this._refreshDebounce;
|
|
928
|
+
}
|
|
929
|
+
// ========================================
|
|
930
|
+
// Highlighting Input
|
|
931
|
+
// ========================================
|
|
932
|
+
_filterText = '';
|
|
933
|
+
/**
|
|
934
|
+
* Text to highlight in grid cells.
|
|
935
|
+
* Supports SQL-style % wildcards for pattern matching.
|
|
936
|
+
*/
|
|
937
|
+
set FilterText(value) {
|
|
938
|
+
const previousValue = this._filterText;
|
|
939
|
+
this._filterText = value || '';
|
|
940
|
+
if (value !== previousValue) {
|
|
941
|
+
this.onFilterTextChanged();
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
get FilterText() {
|
|
945
|
+
return this._filterText;
|
|
946
|
+
}
|
|
947
|
+
// ========================================
|
|
948
|
+
// Predefined Toolbar Button Inputs
|
|
949
|
+
// ========================================
|
|
950
|
+
_showNewButton = true;
|
|
951
|
+
/**
|
|
952
|
+
* Show the "New" button in toolbar (creates new record)
|
|
953
|
+
*/
|
|
954
|
+
set ShowNewButton(value) {
|
|
955
|
+
this._showNewButton = value;
|
|
956
|
+
}
|
|
957
|
+
get ShowNewButton() {
|
|
958
|
+
return this._showNewButton;
|
|
959
|
+
}
|
|
960
|
+
_showRefreshButton = true;
|
|
961
|
+
/**
|
|
962
|
+
* Show the "Refresh" button in toolbar
|
|
963
|
+
*/
|
|
964
|
+
set ShowRefreshButton(value) {
|
|
965
|
+
this._showRefreshButton = value;
|
|
966
|
+
}
|
|
967
|
+
get ShowRefreshButton() {
|
|
968
|
+
return this._showRefreshButton;
|
|
969
|
+
}
|
|
970
|
+
_showExportButton = true;
|
|
971
|
+
/**
|
|
972
|
+
* Show the "Export to Excel" button in toolbar
|
|
973
|
+
*/
|
|
974
|
+
set ShowExportButton(value) {
|
|
975
|
+
this._showExportButton = value;
|
|
976
|
+
}
|
|
977
|
+
get ShowExportButton() {
|
|
978
|
+
return this._showExportButton;
|
|
979
|
+
}
|
|
980
|
+
_showDeleteButton = false;
|
|
981
|
+
/**
|
|
982
|
+
* Show the "Delete" button in toolbar (deletes selected rows)
|
|
983
|
+
*/
|
|
984
|
+
set ShowDeleteButton(value) {
|
|
985
|
+
this._showDeleteButton = value;
|
|
986
|
+
}
|
|
987
|
+
get ShowDeleteButton() {
|
|
988
|
+
return this._showDeleteButton;
|
|
989
|
+
}
|
|
990
|
+
_showCompareButton = false;
|
|
991
|
+
/**
|
|
992
|
+
* Show the "Compare" button in toolbar (compare selected records)
|
|
993
|
+
*/
|
|
994
|
+
set ShowCompareButton(value) {
|
|
995
|
+
this._showCompareButton = value;
|
|
996
|
+
}
|
|
997
|
+
get ShowCompareButton() {
|
|
998
|
+
return this._showCompareButton;
|
|
999
|
+
}
|
|
1000
|
+
_showMergeButton = false;
|
|
1001
|
+
/**
|
|
1002
|
+
* Show the "Merge" button in toolbar (merge selected records)
|
|
1003
|
+
*/
|
|
1004
|
+
set ShowMergeButton(value) {
|
|
1005
|
+
this._showMergeButton = value;
|
|
1006
|
+
}
|
|
1007
|
+
get ShowMergeButton() {
|
|
1008
|
+
return this._showMergeButton;
|
|
1009
|
+
}
|
|
1010
|
+
_showAddToListButton = false;
|
|
1011
|
+
/**
|
|
1012
|
+
* Show the "Add to List" button in toolbar
|
|
1013
|
+
*/
|
|
1014
|
+
set ShowAddToListButton(value) {
|
|
1015
|
+
this._showAddToListButton = value;
|
|
1016
|
+
}
|
|
1017
|
+
get ShowAddToListButton() {
|
|
1018
|
+
return this._showAddToListButton;
|
|
1019
|
+
}
|
|
1020
|
+
_showDuplicateSearchButton = false;
|
|
1021
|
+
/**
|
|
1022
|
+
* Show the "Search for Duplicates" button in toolbar
|
|
1023
|
+
*/
|
|
1024
|
+
set ShowDuplicateSearchButton(value) {
|
|
1025
|
+
this._showDuplicateSearchButton = value;
|
|
1026
|
+
}
|
|
1027
|
+
get ShowDuplicateSearchButton() {
|
|
1028
|
+
return this._showDuplicateSearchButton;
|
|
1029
|
+
}
|
|
1030
|
+
_showCommunicationButton = false;
|
|
1031
|
+
/**
|
|
1032
|
+
* Show the "Send Message" button in toolbar (if entity supports communication)
|
|
1033
|
+
*/
|
|
1034
|
+
set ShowCommunicationButton(value) {
|
|
1035
|
+
this._showCommunicationButton = value;
|
|
1036
|
+
}
|
|
1037
|
+
get ShowCommunicationButton() {
|
|
1038
|
+
return this._showCommunicationButton;
|
|
1039
|
+
}
|
|
1040
|
+
// ========================================
|
|
1041
|
+
// Navigation Inputs
|
|
1042
|
+
// ========================================
|
|
1043
|
+
_autoNavigate = true;
|
|
1044
|
+
/**
|
|
1045
|
+
* When true, clicking or double-clicking a row will emit a navigation request.
|
|
1046
|
+
* The parent component can handle the navigationRequested event to open the record.
|
|
1047
|
+
*/
|
|
1048
|
+
set AutoNavigate(value) {
|
|
1049
|
+
this._autoNavigate = value;
|
|
1050
|
+
}
|
|
1051
|
+
get AutoNavigate() {
|
|
1052
|
+
return this._autoNavigate;
|
|
1053
|
+
}
|
|
1054
|
+
_navigateOnDoubleClick = true;
|
|
1055
|
+
/**
|
|
1056
|
+
* When true, navigation is triggered on double-click only.
|
|
1057
|
+
* When false, navigation is triggered on single click.
|
|
1058
|
+
* Only applies when AutoNavigate is true.
|
|
1059
|
+
*/
|
|
1060
|
+
set NavigateOnDoubleClick(value) {
|
|
1061
|
+
this._navigateOnDoubleClick = value;
|
|
1062
|
+
}
|
|
1063
|
+
get NavigateOnDoubleClick() {
|
|
1064
|
+
return this._navigateOnDoubleClick;
|
|
1065
|
+
}
|
|
1066
|
+
_createRecordMode = 'Tab';
|
|
1067
|
+
/**
|
|
1068
|
+
* How to open new records when the "New" button is clicked:
|
|
1069
|
+
* - 'Tab': Emit event to open in a new tab (default)
|
|
1070
|
+
* - 'Dialog': Emit event to open in a dialog
|
|
1071
|
+
*/
|
|
1072
|
+
set CreateRecordMode(value) {
|
|
1073
|
+
this._createRecordMode = value;
|
|
1074
|
+
}
|
|
1075
|
+
get CreateRecordMode() {
|
|
1076
|
+
return this._createRecordMode;
|
|
1077
|
+
}
|
|
1078
|
+
_newRecordValues = {};
|
|
1079
|
+
/**
|
|
1080
|
+
* Default values to set on new records.
|
|
1081
|
+
* These values are passed with the newRecordDialogRequested event.
|
|
1082
|
+
*/
|
|
1083
|
+
set NewRecordValues(value) {
|
|
1084
|
+
this._newRecordValues = value || {};
|
|
1085
|
+
}
|
|
1086
|
+
get NewRecordValues() {
|
|
1087
|
+
return this._newRecordValues;
|
|
1088
|
+
}
|
|
1089
|
+
// ========================================
|
|
1090
|
+
// Entity Actions Inputs
|
|
1091
|
+
// ========================================
|
|
1092
|
+
_showEntityActionButtons = false;
|
|
1093
|
+
/**
|
|
1094
|
+
* When true, displays entity action buttons in the toolbar overflow menu.
|
|
1095
|
+
* Entity actions are loaded dynamically based on the current entity.
|
|
1096
|
+
* The entityActionsLoaded event is emitted when actions are ready.
|
|
1097
|
+
*/
|
|
1098
|
+
set ShowEntityActionButtons(value) {
|
|
1099
|
+
this._showEntityActionButtons = value;
|
|
1100
|
+
if (value && this._entityInfo) {
|
|
1101
|
+
this.LoadEntityActionsRequested.emit({ entityInfo: this._entityInfo });
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
get ShowEntityActionButtons() {
|
|
1105
|
+
return this._showEntityActionButtons;
|
|
1106
|
+
}
|
|
1107
|
+
_entityActions = [];
|
|
1108
|
+
/**
|
|
1109
|
+
* Array of entity actions to display in the toolbar.
|
|
1110
|
+
* Set this after receiving the entityActionsLoaded event.
|
|
1111
|
+
*/
|
|
1112
|
+
set EntityActions(value) {
|
|
1113
|
+
this._entityActions = value || [];
|
|
1114
|
+
}
|
|
1115
|
+
get EntityActions() {
|
|
1116
|
+
return this._entityActions;
|
|
1117
|
+
}
|
|
1118
|
+
// ========================================
|
|
1119
|
+
// Event Outputs
|
|
1120
|
+
// ========================================
|
|
1121
|
+
// Row Selection
|
|
1122
|
+
BeforeRowSelect = new EventEmitter();
|
|
1123
|
+
AfterRowSelect = new EventEmitter();
|
|
1124
|
+
BeforeRowDeselect = new EventEmitter();
|
|
1125
|
+
AfterRowDeselect = new EventEmitter();
|
|
1126
|
+
SelectionChange = new EventEmitter();
|
|
1127
|
+
// Row Click
|
|
1128
|
+
BeforeRowClick = new EventEmitter();
|
|
1129
|
+
AfterRowClick = new EventEmitter();
|
|
1130
|
+
BeforeRowDoubleClick = new EventEmitter();
|
|
1131
|
+
AfterRowDoubleClick = new EventEmitter();
|
|
1132
|
+
// Editing
|
|
1133
|
+
BeforeCellEdit = new EventEmitter();
|
|
1134
|
+
AfterCellEditBegin = new EventEmitter();
|
|
1135
|
+
BeforeCellEditCommit = new EventEmitter();
|
|
1136
|
+
AfterCellEditCommit = new EventEmitter();
|
|
1137
|
+
BeforeCellEditCancel = new EventEmitter();
|
|
1138
|
+
AfterCellEditCancel = new EventEmitter();
|
|
1139
|
+
BeforeRowSave = new EventEmitter();
|
|
1140
|
+
AfterRowSave = new EventEmitter();
|
|
1141
|
+
BeforeRowDelete = new EventEmitter();
|
|
1142
|
+
AfterRowDelete = new EventEmitter();
|
|
1143
|
+
// Data Loading
|
|
1144
|
+
BeforeDataLoad = new EventEmitter();
|
|
1145
|
+
AfterDataLoad = new EventEmitter();
|
|
1146
|
+
BeforeDataRefresh = new EventEmitter();
|
|
1147
|
+
AfterDataRefresh = new EventEmitter();
|
|
1148
|
+
// Sorting
|
|
1149
|
+
BeforeSort = new EventEmitter();
|
|
1150
|
+
AfterSort = new EventEmitter();
|
|
1151
|
+
// Column Management
|
|
1152
|
+
BeforeColumnReorder = new EventEmitter();
|
|
1153
|
+
AfterColumnReorder = new EventEmitter();
|
|
1154
|
+
BeforeColumnResize = new EventEmitter();
|
|
1155
|
+
AfterColumnResize = new EventEmitter();
|
|
1156
|
+
BeforeColumnVisibilityChange = new EventEmitter();
|
|
1157
|
+
AfterColumnVisibilityChange = new EventEmitter();
|
|
1158
|
+
// Grid State
|
|
1159
|
+
GridStateChanged = new EventEmitter();
|
|
1160
|
+
// Toolbar Actions (legacy names)
|
|
1161
|
+
AddRequested = new EventEmitter();
|
|
1162
|
+
DeleteRequested = new EventEmitter();
|
|
1163
|
+
ExportRequested = new EventEmitter();
|
|
1164
|
+
// Predefined Toolbar Button Events
|
|
1165
|
+
NewButtonClick = new EventEmitter();
|
|
1166
|
+
RefreshButtonClick = new EventEmitter();
|
|
1167
|
+
ExportButtonClick = new EventEmitter();
|
|
1168
|
+
DeleteButtonClick = new EventEmitter();
|
|
1169
|
+
CompareButtonClick = new EventEmitter();
|
|
1170
|
+
MergeButtonClick = new EventEmitter();
|
|
1171
|
+
AddToListButtonClick = new EventEmitter();
|
|
1172
|
+
DuplicateSearchButtonClick = new EventEmitter();
|
|
1173
|
+
CommunicationButtonClick = new EventEmitter();
|
|
1174
|
+
// Navigation Events
|
|
1175
|
+
/**
|
|
1176
|
+
* Emitted when a row is clicked/double-clicked and AutoNavigate is enabled.
|
|
1177
|
+
* Parent components should handle this to open the entity record.
|
|
1178
|
+
*/
|
|
1179
|
+
NavigationRequested = new EventEmitter();
|
|
1180
|
+
/**
|
|
1181
|
+
* Emitted when the "New" button is clicked and CreateRecordMode is 'Dialog'.
|
|
1182
|
+
* Parent components should handle this to show a new record dialog.
|
|
1183
|
+
*/
|
|
1184
|
+
NewRecordDialogRequested = new EventEmitter();
|
|
1185
|
+
/**
|
|
1186
|
+
* Emitted when the "New" button is clicked and CreateRecordMode is 'Tab'.
|
|
1187
|
+
* Parent components should handle this to open a new record in a tab.
|
|
1188
|
+
*/
|
|
1189
|
+
NewRecordTabRequested = new EventEmitter();
|
|
1190
|
+
// Dialog Request Events (for Explorer-specific dialogs)
|
|
1191
|
+
/**
|
|
1192
|
+
* Emitted when Compare Records functionality is requested.
|
|
1193
|
+
* Parent components should handle this to show the compare dialog.
|
|
1194
|
+
*/
|
|
1195
|
+
CompareRecordsRequested = new EventEmitter();
|
|
1196
|
+
/**
|
|
1197
|
+
* Emitted when Merge Records functionality is requested.
|
|
1198
|
+
* Parent components should handle this to show the merge dialog.
|
|
1199
|
+
*/
|
|
1200
|
+
MergeRecordsRequested = new EventEmitter();
|
|
1201
|
+
/**
|
|
1202
|
+
* Emitted when Communication/Send Message functionality is requested.
|
|
1203
|
+
* Parent components should handle this to show the communication dialog.
|
|
1204
|
+
*/
|
|
1205
|
+
CommunicationRequested = new EventEmitter();
|
|
1206
|
+
/**
|
|
1207
|
+
* Emitted when duplicate search functionality is requested.
|
|
1208
|
+
* Parent components should handle this to show the duplicate search results.
|
|
1209
|
+
*/
|
|
1210
|
+
DuplicateSearchRequested = new EventEmitter();
|
|
1211
|
+
/**
|
|
1212
|
+
* Emitted when the Add to List button is clicked.
|
|
1213
|
+
* Parent components should handle this to show the list management dialog.
|
|
1214
|
+
*/
|
|
1215
|
+
AddToListRequested = new EventEmitter();
|
|
1216
|
+
// Entity Action Events
|
|
1217
|
+
/**
|
|
1218
|
+
* Emitted when entity actions need to be loaded.
|
|
1219
|
+
* Parent components should load actions and set the EntityActions input.
|
|
1220
|
+
*/
|
|
1221
|
+
LoadEntityActionsRequested = new EventEmitter();
|
|
1222
|
+
/**
|
|
1223
|
+
* Emitted when an entity action is selected for execution.
|
|
1224
|
+
* Parent components should handle the action execution.
|
|
1225
|
+
*/
|
|
1226
|
+
EntityActionRequested = new EventEmitter();
|
|
1227
|
+
// ========================================
|
|
1228
|
+
// View Children
|
|
1229
|
+
// ========================================
|
|
1230
|
+
gridContainer;
|
|
1231
|
+
// ========================================
|
|
1232
|
+
// AG Grid Properties
|
|
1233
|
+
// ========================================
|
|
1234
|
+
/** AG Grid column definitions */
|
|
1235
|
+
agColumnDefs = [];
|
|
1236
|
+
/** AG Grid row data */
|
|
1237
|
+
rowData = [];
|
|
1238
|
+
/** AG Grid API reference */
|
|
1239
|
+
gridApi = null;
|
|
1240
|
+
/** AG Grid theme (v34+) with custom selection colors */
|
|
1241
|
+
agGridTheme = themeAlpine.withParams({
|
|
1242
|
+
selectedRowBackgroundColor: '#fff3cd', // More visible mellow yellow selection
|
|
1243
|
+
rowHoverColor: '#f5f5f5'
|
|
1244
|
+
});
|
|
1245
|
+
/** AG Grid row selection configuration */
|
|
1246
|
+
agRowSelection = { mode: 'singleRow' };
|
|
1247
|
+
/** Default column settings */
|
|
1248
|
+
defaultColDef = {
|
|
1249
|
+
sortable: true,
|
|
1250
|
+
filter: false,
|
|
1251
|
+
resizable: true,
|
|
1252
|
+
minWidth: 80
|
|
1253
|
+
};
|
|
1254
|
+
/** Get row ID function for AG Grid */
|
|
1255
|
+
getRowId = (params) => params.data['__pk'];
|
|
1256
|
+
/** Suppress sort changed events during programmatic updates */
|
|
1257
|
+
suppressSortEvents = false;
|
|
1258
|
+
/** AG Grid options for infinite scroll mode */
|
|
1259
|
+
gridOptions = {};
|
|
1260
|
+
/** Datasource for infinite scroll mode */
|
|
1261
|
+
infiniteDatasource = null;
|
|
1262
|
+
// ========================================
|
|
1263
|
+
// Internal State
|
|
1264
|
+
// ========================================
|
|
1265
|
+
_useExternalData = false;
|
|
1266
|
+
_allData = [];
|
|
1267
|
+
_rowDataMap = new Map();
|
|
1268
|
+
_entityInfo = null;
|
|
1269
|
+
_viewEntity = null;
|
|
1270
|
+
_columnStates = [];
|
|
1271
|
+
_sortState = [];
|
|
1272
|
+
_filterState = [];
|
|
1273
|
+
_editingRowKey = null;
|
|
1274
|
+
_editingField = null;
|
|
1275
|
+
_pendingChanges = [];
|
|
1276
|
+
// ========================================
|
|
1277
|
+
// Public Read-Only Properties
|
|
1278
|
+
// ========================================
|
|
1279
|
+
/**
|
|
1280
|
+
* The loaded view entity if using a stored view (ViewID, ViewName, or ViewEntity in Params).
|
|
1281
|
+
* Null for dynamic views or when using legacy entityName input.
|
|
1282
|
+
*/
|
|
1283
|
+
get ViewEntity() {
|
|
1284
|
+
return this._viewEntity;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* The entity metadata for the current data source.
|
|
1288
|
+
* Available after Params is set and data is loaded.
|
|
1289
|
+
*/
|
|
1290
|
+
get EntityInfo() {
|
|
1291
|
+
return this._entityInfo;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* True if using a dynamic view (EntityName only) rather than a stored view.
|
|
1295
|
+
* False if using ViewID, ViewName, ViewEntity in Params, or viewEntity input.
|
|
1296
|
+
*/
|
|
1297
|
+
get IsDynamicView() {
|
|
1298
|
+
// If we have a viewEntity (from either Params or input), it's a stored view
|
|
1299
|
+
if (this._viewEntity)
|
|
1300
|
+
return false;
|
|
1301
|
+
// Check Params for stored view indicators
|
|
1302
|
+
if (this._params) {
|
|
1303
|
+
return !this._params.ViewID && !this._params.ViewName && !this._params.ViewEntity;
|
|
1304
|
+
}
|
|
1305
|
+
// Legacy mode without viewEntity is considered dynamic
|
|
1306
|
+
return true;
|
|
1307
|
+
}
|
|
1308
|
+
// Loading state
|
|
1309
|
+
loading = false;
|
|
1310
|
+
errorMessage = '';
|
|
1311
|
+
totalRowCount = 0;
|
|
1312
|
+
_loadDataPromise = null;
|
|
1313
|
+
// Cleanup
|
|
1314
|
+
destroy$ = new Subject();
|
|
1315
|
+
refreshSubject = new Subject();
|
|
1316
|
+
statesSaveSubject = new Subject();
|
|
1317
|
+
statePersistSubject = new Subject();
|
|
1318
|
+
userDefaultsPersistSubject = new Subject();
|
|
1319
|
+
// Persist state tracking
|
|
1320
|
+
pendingStateToSave = null;
|
|
1321
|
+
isSavingState = false;
|
|
1322
|
+
/**
|
|
1323
|
+
* Pending state waiting to be persisted (for flushing on destroy).
|
|
1324
|
+
* Tracks state for both saved views and user defaults separately.
|
|
1325
|
+
*/
|
|
1326
|
+
_pendingViewStateToPersist = null;
|
|
1327
|
+
_pendingUserDefaultsToPersist = null;
|
|
1328
|
+
/**
|
|
1329
|
+
* Flag to suppress state persistence during view transitions.
|
|
1330
|
+
* When true, emitGridStateChanged will not trigger persistence.
|
|
1331
|
+
* This prevents the old view's column state from being saved to the new view.
|
|
1332
|
+
*/
|
|
1333
|
+
_suppressPersist = false;
|
|
1334
|
+
/**
|
|
1335
|
+
* Dirty flag to track if grid state has actually been modified by user actions.
|
|
1336
|
+
* Only set to true when: column resize, column move, or sort change.
|
|
1337
|
+
* Reset to false: when view loads, after successful persistence.
|
|
1338
|
+
* This prevents emitting gridStateChanged events when no real change occurred.
|
|
1339
|
+
*/
|
|
1340
|
+
_isGridStateDirty = false;
|
|
1341
|
+
/**
|
|
1342
|
+
* Whether the current user can edit the view.
|
|
1343
|
+
* For dynamic views, always true (persists to user settings).
|
|
1344
|
+
* For saved views, depends on view ownership and permissions.
|
|
1345
|
+
*/
|
|
1346
|
+
get canEditCurrentView() {
|
|
1347
|
+
if (this.IsDynamicView) {
|
|
1348
|
+
return true; // Dynamic views persist to user settings
|
|
1349
|
+
}
|
|
1350
|
+
return this._viewEntity?.UserCanEdit ?? false;
|
|
1351
|
+
}
|
|
1352
|
+
// Overflow menu state
|
|
1353
|
+
showOverflowMenu = false;
|
|
1354
|
+
// Export dialog state
|
|
1355
|
+
showExportDialog = false;
|
|
1356
|
+
exportDialogConfig = null;
|
|
1357
|
+
constructor(cdr, elementRef, exportService) {
|
|
1358
|
+
this.cdr = cdr;
|
|
1359
|
+
this.elementRef = elementRef;
|
|
1360
|
+
this.exportService = exportService;
|
|
1361
|
+
}
|
|
1362
|
+
// ========================================
|
|
1363
|
+
// Lifecycle Hooks
|
|
1364
|
+
// ========================================
|
|
1365
|
+
async ngOnInit() {
|
|
1366
|
+
this.setupRefreshDebounce();
|
|
1367
|
+
this.setupStatePersistDebounce();
|
|
1368
|
+
this.setupUserDefaultsPersistDebounce();
|
|
1369
|
+
this.updateAgRowSelection();
|
|
1370
|
+
}
|
|
1371
|
+
ngOnDestroy() {
|
|
1372
|
+
// Flush any pending state persistence immediately before destroying
|
|
1373
|
+
this.flushPendingPersistence();
|
|
1374
|
+
this.destroy$.next();
|
|
1375
|
+
this.destroy$.complete();
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Immediately persists any pending state changes without waiting for debounce.
|
|
1379
|
+
* Called on component destroy to ensure changes aren't lost.
|
|
1380
|
+
*/
|
|
1381
|
+
flushPendingPersistence() {
|
|
1382
|
+
// Flush pending view state
|
|
1383
|
+
if (this._pendingViewStateToPersist && this._viewEntity) {
|
|
1384
|
+
this.persistGridStateToView(this._pendingViewStateToPersist);
|
|
1385
|
+
this._pendingViewStateToPersist = null;
|
|
1386
|
+
}
|
|
1387
|
+
// Flush pending user defaults
|
|
1388
|
+
if (this._pendingUserDefaultsToPersist && this._entityInfo) {
|
|
1389
|
+
this.persistUserDefaultGridState(this._pendingUserDefaultsToPersist);
|
|
1390
|
+
this._pendingUserDefaultsToPersist = null;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
// ========================================
|
|
1394
|
+
// Setup Methods
|
|
1395
|
+
// ========================================
|
|
1396
|
+
setupRefreshDebounce() {
|
|
1397
|
+
this.refreshSubject.pipe(debounceTime(this._refreshDebounce), takeUntil(this.destroy$)).subscribe(() => {
|
|
1398
|
+
this.loadData(true);
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
setupStatePersistDebounce() {
|
|
1402
|
+
this.statePersistSubject.pipe(debounceTime(this._statePersistDebounce), takeUntil(this.destroy$)).subscribe((state) => {
|
|
1403
|
+
this.persistGridStateToView(state);
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
setupUserDefaultsPersistDebounce() {
|
|
1407
|
+
this.userDefaultsPersistSubject.pipe(debounceTime(this._statePersistDebounce), takeUntil(this.destroy$)).subscribe((state) => {
|
|
1408
|
+
this.persistUserDefaultGridState(state);
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Called when Params input changes. Handles loading view entity metadata
|
|
1413
|
+
* and triggering data load if allowed.
|
|
1414
|
+
*/
|
|
1415
|
+
async onParamsChanged() {
|
|
1416
|
+
if (!this._params) {
|
|
1417
|
+
// Params cleared - reset view entity
|
|
1418
|
+
this._viewEntity = null;
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
// Suppress persistence during view transition to prevent old view's state
|
|
1422
|
+
// from being saved to the new view's storage
|
|
1423
|
+
this._suppressPersist = true;
|
|
1424
|
+
// Reset dirty flag - no changes have been made to the new view yet
|
|
1425
|
+
this._isGridStateDirty = false;
|
|
1426
|
+
// Reset internal grid state when params change - this ensures we don't
|
|
1427
|
+
// carry over column/sort settings from a previous view when switching views
|
|
1428
|
+
this._gridState = null;
|
|
1429
|
+
this._sortState = [];
|
|
1430
|
+
try {
|
|
1431
|
+
// If using a stored view, load the view entity first
|
|
1432
|
+
if (this._params.ViewEntity) {
|
|
1433
|
+
// ViewEntity was provided directly
|
|
1434
|
+
this._viewEntity = this._params.ViewEntity;
|
|
1435
|
+
this._entityInfo = this._viewEntity.ViewEntityInfo;
|
|
1436
|
+
this.applyViewEntitySettings();
|
|
1437
|
+
}
|
|
1438
|
+
else if (this._params.ViewID) {
|
|
1439
|
+
// Load view entity by ID from engine
|
|
1440
|
+
const cachedView = this.getViewFromEngine(this._params.ViewID);
|
|
1441
|
+
if (cachedView) {
|
|
1442
|
+
this._viewEntity = cachedView;
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
// View not in cache - use ViewInfo (which also uses engine)
|
|
1446
|
+
this._viewEntity = await ViewInfo.GetViewEntity(this._params.ViewID);
|
|
1447
|
+
}
|
|
1448
|
+
this._entityInfo = this._viewEntity.ViewEntityInfo;
|
|
1449
|
+
this.applyViewEntitySettings();
|
|
1450
|
+
}
|
|
1451
|
+
else if (this._params.ViewName) {
|
|
1452
|
+
// Load view entity by name from engine
|
|
1453
|
+
const cachedView = this.getViewFromEngineByName(this._params.ViewName);
|
|
1454
|
+
if (cachedView) {
|
|
1455
|
+
this._viewEntity = cachedView;
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
// View not in cache - use ViewInfo (which also uses engine)
|
|
1459
|
+
this._viewEntity = await ViewInfo.GetViewEntityByName(this._params.ViewName);
|
|
1460
|
+
}
|
|
1461
|
+
this._entityInfo = this._viewEntity.ViewEntityInfo;
|
|
1462
|
+
this.applyViewEntitySettings();
|
|
1463
|
+
}
|
|
1464
|
+
else if (this._params.EntityName) {
|
|
1465
|
+
// Dynamic view - just get entity metadata
|
|
1466
|
+
this._viewEntity = null;
|
|
1467
|
+
const md = new Metadata();
|
|
1468
|
+
this._entityInfo = md.Entities.find(e => e.Name === this._params.EntityName) || null;
|
|
1469
|
+
// Reset columns to force regeneration from metadata when switching to dynamic view
|
|
1470
|
+
// This ensures we don't carry over column config from a previous saved view
|
|
1471
|
+
this._columns = [];
|
|
1472
|
+
// For dynamic views, try to load user's saved defaults
|
|
1473
|
+
if (this._entityInfo && this._autoPersistState) {
|
|
1474
|
+
this.loadUserDefaultGridState(this._entityInfo.Name);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
// Generate columns if not already set
|
|
1478
|
+
if (this._columns.length === 0 && this._entityInfo) {
|
|
1479
|
+
this.generateColumnsFromMetadata();
|
|
1480
|
+
}
|
|
1481
|
+
// Rebuild AG Grid column definitions to reflect the new view's settings
|
|
1482
|
+
this.buildAgColumnDefs();
|
|
1483
|
+
// Load data if auto-refresh is enabled
|
|
1484
|
+
if (this._autoRefreshOnParamsChange) {
|
|
1485
|
+
await this.loadData(false);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
catch (error) {
|
|
1489
|
+
this.errorMessage = error instanceof Error ? error.message : 'Failed to load view';
|
|
1490
|
+
this.cdr.detectChanges();
|
|
1491
|
+
}
|
|
1492
|
+
finally {
|
|
1493
|
+
// Re-enable persistence now that the new view is fully loaded
|
|
1494
|
+
this._suppressPersist = false;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Gets a view from the UserViewEngine cache by ID.
|
|
1499
|
+
* Returns undefined if not found or engine not initialized.
|
|
1500
|
+
*/
|
|
1501
|
+
getViewFromEngine(viewId) {
|
|
1502
|
+
try {
|
|
1503
|
+
return UserViewEngine.Instance.GetViewById(viewId);
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
// Engine may not be initialized yet
|
|
1507
|
+
return undefined;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Gets a view from the UserViewEngine cache by name.
|
|
1512
|
+
* Returns undefined if not found or engine not initialized.
|
|
1513
|
+
*/
|
|
1514
|
+
getViewFromEngineByName(viewName) {
|
|
1515
|
+
try {
|
|
1516
|
+
return UserViewEngine.Instance.GetViewByName(viewName);
|
|
1517
|
+
}
|
|
1518
|
+
catch {
|
|
1519
|
+
// Engine may not be initialized yet
|
|
1520
|
+
return undefined;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Loads user's saved grid state defaults for a dynamic view.
|
|
1525
|
+
* Uses UserInfoEngine to retrieve settings stored with key format: "default-view-setting/{entityName}"
|
|
1526
|
+
*/
|
|
1527
|
+
loadUserDefaultGridState(entityName) {
|
|
1528
|
+
try {
|
|
1529
|
+
const settingKey = `default-view-setting/${entityName}`;
|
|
1530
|
+
const savedState = UserInfoEngine.Instance.GetSetting(settingKey);
|
|
1531
|
+
if (savedState) {
|
|
1532
|
+
const gridState = JSON.parse(savedState);
|
|
1533
|
+
// Only apply if not already set via props (props take precedence)
|
|
1534
|
+
if (!this._gridState && gridState.columnSettings?.length) {
|
|
1535
|
+
this._gridState = {
|
|
1536
|
+
columnSettings: gridState.columnSettings,
|
|
1537
|
+
sortSettings: gridState.sortSettings || []
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
// Apply sort state if not already set
|
|
1541
|
+
if (this._sortState.length === 0 && gridState.sortSettings?.length) {
|
|
1542
|
+
this._sortState = gridState.sortSettings.map((s, index) => ({
|
|
1543
|
+
field: s.field,
|
|
1544
|
+
direction: s.dir,
|
|
1545
|
+
index
|
|
1546
|
+
}));
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
catch (error) {
|
|
1551
|
+
console.error('[entity-data-grid] Failed to load user default grid state:', error);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Persists the current grid state as user defaults for a dynamic view.
|
|
1556
|
+
* Uses UserInfoEngine to store settings with key format: "default-view-setting/{entityName}"
|
|
1557
|
+
*/
|
|
1558
|
+
async persistUserDefaultGridState(state) {
|
|
1559
|
+
if (!this._entityInfo) {
|
|
1560
|
+
this._pendingUserDefaultsToPersist = null;
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
try {
|
|
1564
|
+
const settingKey = `default-view-setting/${this._entityInfo.Name}`;
|
|
1565
|
+
const gridStateJson = {
|
|
1566
|
+
columnSettings: state.columnSettings,
|
|
1567
|
+
sortSettings: state.sortSettings
|
|
1568
|
+
};
|
|
1569
|
+
await UserInfoEngine.Instance.SetSetting(settingKey, JSON.stringify(gridStateJson));
|
|
1570
|
+
// Clear pending state and reset dirty flag after successful save
|
|
1571
|
+
this._pendingUserDefaultsToPersist = null;
|
|
1572
|
+
this._isGridStateDirty = false;
|
|
1573
|
+
}
|
|
1574
|
+
catch (error) {
|
|
1575
|
+
console.error('[entity-data-grid] Failed to persist user default grid state:', error);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Applies settings from a loaded ViewEntity (column configuration, sort state, etc.)
|
|
1580
|
+
*
|
|
1581
|
+
* Precedence rules (highest to lowest):
|
|
1582
|
+
* 1. Props passed directly (gridState, orderBy, etc.) - always take precedence
|
|
1583
|
+
* 2. Settings from ViewEntity.GridState (persisted column/sort configuration)
|
|
1584
|
+
* 3. Settings from ViewEntity.SortInfo (legacy sort configuration)
|
|
1585
|
+
* 4. Auto-generated defaults from entity metadata
|
|
1586
|
+
*/
|
|
1587
|
+
applyViewEntitySettings() {
|
|
1588
|
+
if (!this._viewEntity)
|
|
1589
|
+
return;
|
|
1590
|
+
// Only apply grid state from view entity if not already set via props
|
|
1591
|
+
// (gridState input takes precedence)
|
|
1592
|
+
if (!this._gridState && this._viewEntity.GridState) {
|
|
1593
|
+
try {
|
|
1594
|
+
const gridState = JSON.parse(this._viewEntity.GridState);
|
|
1595
|
+
if (gridState.columnSettings?.length) {
|
|
1596
|
+
this._gridState = {
|
|
1597
|
+
columnSettings: gridState.columnSettings,
|
|
1598
|
+
sortSettings: gridState.sortSettings || []
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
catch (e) {
|
|
1603
|
+
console.warn('Failed to parse view GridState:', e);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
// Only apply sort state if:
|
|
1607
|
+
// 1. No explicit OrderBy in Params
|
|
1608
|
+
// 2. No sort already set via gridState (either prop or from view)
|
|
1609
|
+
const hasExplicitOrderBy = this._params?.OrderBy && this._params.OrderBy.trim().length > 0;
|
|
1610
|
+
const hasSortFromGridState = this._gridState?.sortSettings && this._gridState.sortSettings.length > 0;
|
|
1611
|
+
if (!hasExplicitOrderBy && !hasSortFromGridState && this._sortState.length === 0) {
|
|
1612
|
+
const sortInfo = this._viewEntity.ViewSortInfo;
|
|
1613
|
+
if (sortInfo?.length) {
|
|
1614
|
+
this._sortState = sortInfo.map((s, index) => ({
|
|
1615
|
+
field: s.field,
|
|
1616
|
+
direction: s.direction?.toLowerCase() === 'desc' ? 'desc' : 'asc',
|
|
1617
|
+
index
|
|
1618
|
+
}));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
onGridStateChanged() {
|
|
1623
|
+
if (this._gridState && this._entityInfo) {
|
|
1624
|
+
this.buildAgColumnDefs();
|
|
1625
|
+
// Update AG Grid with new column definitions to apply header styles
|
|
1626
|
+
if (this.gridApi) {
|
|
1627
|
+
this.gridApi.setGridOption('columnDefs', this.agColumnDefs);
|
|
1628
|
+
// Refresh header to apply new header styles
|
|
1629
|
+
this.gridApi.refreshHeader();
|
|
1630
|
+
}
|
|
1631
|
+
// Apply sort if present
|
|
1632
|
+
if (this._gridState.sortSettings?.length && this.gridApi) {
|
|
1633
|
+
const sortSetting = this._gridState.sortSettings[0];
|
|
1634
|
+
this._sortState = [{
|
|
1635
|
+
field: sortSetting.field,
|
|
1636
|
+
direction: sortSetting.dir,
|
|
1637
|
+
index: 0
|
|
1638
|
+
}];
|
|
1639
|
+
this.applySortStateToGrid();
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
onFilterTextChanged() {
|
|
1644
|
+
// Rebuild column defs to update cell renderers with new filter text
|
|
1645
|
+
this.buildAgColumnDefs();
|
|
1646
|
+
// Update AG Grid with new column definitions and refresh cells
|
|
1647
|
+
if (this.gridApi) {
|
|
1648
|
+
// Update the column definitions in AG Grid
|
|
1649
|
+
this.gridApi.setGridOption('columnDefs', this.agColumnDefs);
|
|
1650
|
+
// Force refresh all cells to apply new highlighting
|
|
1651
|
+
this.gridApi.refreshCells({ force: true });
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
generateColumnsFromMetadata() {
|
|
1655
|
+
if (!this._entityInfo)
|
|
1656
|
+
return;
|
|
1657
|
+
this._columns = this._entityInfo.Fields
|
|
1658
|
+
.filter(f => this.shouldShowField(f))
|
|
1659
|
+
.map(field => ({
|
|
1660
|
+
field: field.Name,
|
|
1661
|
+
title: field.DisplayName || field.Name,
|
|
1662
|
+
type: this.mapFieldTypeToGridType(field.Type),
|
|
1663
|
+
sortable: true,
|
|
1664
|
+
filterable: true,
|
|
1665
|
+
visible: true,
|
|
1666
|
+
width: this.estimateColumnWidth(field)
|
|
1667
|
+
}));
|
|
1668
|
+
this.initializeColumnStates();
|
|
1669
|
+
this.buildAgColumnDefs();
|
|
1670
|
+
}
|
|
1671
|
+
shouldShowField(field) {
|
|
1672
|
+
if (field.Name.startsWith('__mj_'))
|
|
1673
|
+
return false;
|
|
1674
|
+
if (field.IsPrimaryKey && field.SQLFullType?.toLowerCase() === 'uniqueidentifier') {
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
if (field.DefaultInView === true)
|
|
1678
|
+
return true;
|
|
1679
|
+
if (field.Length > 500)
|
|
1680
|
+
return false;
|
|
1681
|
+
return true;
|
|
1682
|
+
}
|
|
1683
|
+
estimateColumnWidth(field) {
|
|
1684
|
+
const fieldNameLower = field.Name.toLowerCase();
|
|
1685
|
+
const displayNameLower = (field.DisplayName || field.Name).toLowerCase();
|
|
1686
|
+
// Fixed-width types
|
|
1687
|
+
if (field.TSType === 'boolean')
|
|
1688
|
+
return 80;
|
|
1689
|
+
if (field.TSType === 'Date')
|
|
1690
|
+
return 120;
|
|
1691
|
+
// Numeric fields - compact
|
|
1692
|
+
if (field.TSType === 'number') {
|
|
1693
|
+
if (fieldNameLower.includes('year') || fieldNameLower.includes('age'))
|
|
1694
|
+
return 80;
|
|
1695
|
+
if (fieldNameLower.includes('amount') || fieldNameLower.includes('price') || fieldNameLower.includes('total'))
|
|
1696
|
+
return 120;
|
|
1697
|
+
return 100;
|
|
1698
|
+
}
|
|
1699
|
+
// ID fields - compact
|
|
1700
|
+
if (fieldNameLower.endsWith('id') && field.Length <= 50)
|
|
1701
|
+
return 80;
|
|
1702
|
+
// Email - needs more space
|
|
1703
|
+
if (fieldNameLower.includes('email'))
|
|
1704
|
+
return 220;
|
|
1705
|
+
// Phone numbers
|
|
1706
|
+
if (fieldNameLower.includes('phone') || fieldNameLower.includes('mobile') || fieldNameLower.includes('fax'))
|
|
1707
|
+
return 130;
|
|
1708
|
+
// Name fields - medium width
|
|
1709
|
+
if (fieldNameLower.includes('name') || fieldNameLower.includes('title')) {
|
|
1710
|
+
if (fieldNameLower === 'firstname' || fieldNameLower === 'lastname' || fieldNameLower === 'first name' || fieldNameLower === 'last name')
|
|
1711
|
+
return 120;
|
|
1712
|
+
return 160;
|
|
1713
|
+
}
|
|
1714
|
+
// Location fields
|
|
1715
|
+
if (fieldNameLower.includes('city'))
|
|
1716
|
+
return 120;
|
|
1717
|
+
if (fieldNameLower.includes('state') || fieldNameLower.includes('country'))
|
|
1718
|
+
return 100;
|
|
1719
|
+
if (fieldNameLower.includes('zip') || fieldNameLower.includes('postal'))
|
|
1720
|
+
return 90;
|
|
1721
|
+
if (fieldNameLower.includes('address'))
|
|
1722
|
+
return 200;
|
|
1723
|
+
// Date-like strings
|
|
1724
|
+
if (fieldNameLower.includes('date') || fieldNameLower.includes('time'))
|
|
1725
|
+
return 120;
|
|
1726
|
+
// Status/Type fields - usually short values
|
|
1727
|
+
if (fieldNameLower.includes('status') || fieldNameLower.includes('type') || fieldNameLower.includes('category'))
|
|
1728
|
+
return 110;
|
|
1729
|
+
// Code/abbreviation fields
|
|
1730
|
+
if (fieldNameLower.includes('code') || fieldNameLower.includes('abbr'))
|
|
1731
|
+
return 100;
|
|
1732
|
+
// Long text fields - limit width, they'll truncate
|
|
1733
|
+
if (field.Length > 500)
|
|
1734
|
+
return 150;
|
|
1735
|
+
if (field.Length > 200)
|
|
1736
|
+
return 180;
|
|
1737
|
+
// Default: estimate based on field length but with tighter bounds
|
|
1738
|
+
const estimatedChars = Math.min(field.Length, 50);
|
|
1739
|
+
const charWidth = 7;
|
|
1740
|
+
const padding = 24;
|
|
1741
|
+
return Math.min(Math.max(estimatedChars * charWidth / 2 + padding, 80), 200);
|
|
1742
|
+
}
|
|
1743
|
+
mapFieldTypeToGridType(fieldType) {
|
|
1744
|
+
switch (fieldType.toLowerCase()) {
|
|
1745
|
+
case 'int':
|
|
1746
|
+
case 'bigint':
|
|
1747
|
+
case 'smallint':
|
|
1748
|
+
case 'tinyint':
|
|
1749
|
+
case 'decimal':
|
|
1750
|
+
case 'numeric':
|
|
1751
|
+
case 'float':
|
|
1752
|
+
case 'real':
|
|
1753
|
+
case 'money':
|
|
1754
|
+
case 'smallmoney':
|
|
1755
|
+
return 'number';
|
|
1756
|
+
case 'bit':
|
|
1757
|
+
return 'boolean';
|
|
1758
|
+
case 'date':
|
|
1759
|
+
return 'date';
|
|
1760
|
+
case 'datetime':
|
|
1761
|
+
case 'datetime2':
|
|
1762
|
+
case 'smalldatetime':
|
|
1763
|
+
case 'datetimeoffset':
|
|
1764
|
+
return 'datetime';
|
|
1765
|
+
default:
|
|
1766
|
+
return 'string';
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
initializeColumnStates() {
|
|
1770
|
+
this._columnStates = this._columns.map((config, index) => ({
|
|
1771
|
+
config,
|
|
1772
|
+
computedWidth: typeof config.width === 'number' ? config.width : 150,
|
|
1773
|
+
sortDirection: config.sortDirection || 'none',
|
|
1774
|
+
sortIndex: config.sortIndex || 0,
|
|
1775
|
+
filterValue: undefined,
|
|
1776
|
+
visible: config.visible !== false,
|
|
1777
|
+
order: index
|
|
1778
|
+
}));
|
|
1779
|
+
}
|
|
1780
|
+
// ========================================
|
|
1781
|
+
// AG Grid Column Building
|
|
1782
|
+
// ========================================
|
|
1783
|
+
buildAgColumnDefs() {
|
|
1784
|
+
if (this._gridState?.columnSettings?.length && this._entityInfo) {
|
|
1785
|
+
this.agColumnDefs = this.buildAgColumnDefsFromGridState(this._gridState.columnSettings);
|
|
1786
|
+
}
|
|
1787
|
+
else if (this._columns.length > 0) {
|
|
1788
|
+
this.agColumnDefs = this._columns.map(col => this.mapColumnConfigToColDef(col));
|
|
1789
|
+
}
|
|
1790
|
+
else if (this._entityInfo) {
|
|
1791
|
+
this.agColumnDefs = this.generateAgColumnDefs(this._entityInfo);
|
|
1792
|
+
}
|
|
1793
|
+
else {
|
|
1794
|
+
this.agColumnDefs = [];
|
|
1795
|
+
}
|
|
1796
|
+
// Add row number column if enabled
|
|
1797
|
+
if (this._showRowNumbers && this.agColumnDefs.length > 0) {
|
|
1798
|
+
this.agColumnDefs.unshift({
|
|
1799
|
+
headerName: '#',
|
|
1800
|
+
field: '__rowNumber',
|
|
1801
|
+
width: 60,
|
|
1802
|
+
minWidth: 50,
|
|
1803
|
+
maxWidth: 80,
|
|
1804
|
+
sortable: false,
|
|
1805
|
+
resizable: false,
|
|
1806
|
+
valueGetter: (params) => params.node ? params.node.rowIndex + 1 : ''
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
buildAgColumnDefsFromGridState(columnSettings) {
|
|
1811
|
+
if (!this._entityInfo)
|
|
1812
|
+
return [];
|
|
1813
|
+
const sortedColumns = [...columnSettings].sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0));
|
|
1814
|
+
const cols = [];
|
|
1815
|
+
for (const colConfig of sortedColumns) {
|
|
1816
|
+
if (colConfig.hidden)
|
|
1817
|
+
continue;
|
|
1818
|
+
const field = this._entityInfo.Fields.find(f => f.Name.toLowerCase() === colConfig.Name.toLowerCase());
|
|
1819
|
+
if (!field)
|
|
1820
|
+
continue;
|
|
1821
|
+
const colDef = {
|
|
1822
|
+
field: field.Name,
|
|
1823
|
+
// Use userDisplayName if set, otherwise fall back to DisplayName or field name
|
|
1824
|
+
headerName: colConfig.userDisplayName || colConfig.DisplayName || field.DisplayNameOrName,
|
|
1825
|
+
width: colConfig.width || this.estimateColumnWidth(field),
|
|
1826
|
+
sortable: this._allowSorting,
|
|
1827
|
+
resizable: this._allowColumnResize
|
|
1828
|
+
};
|
|
1829
|
+
// Add type-specific formatters with optional custom format
|
|
1830
|
+
this.applyFieldFormatter(colDef, field, colConfig.format);
|
|
1831
|
+
cols.push(colDef);
|
|
1832
|
+
}
|
|
1833
|
+
return cols;
|
|
1834
|
+
}
|
|
1835
|
+
mapColumnConfigToColDef(col) {
|
|
1836
|
+
const colDef = {
|
|
1837
|
+
field: col.field,
|
|
1838
|
+
headerName: col.title || col.field,
|
|
1839
|
+
width: typeof col.width === 'number' ? col.width : undefined,
|
|
1840
|
+
minWidth: col.minWidth,
|
|
1841
|
+
maxWidth: col.maxWidth,
|
|
1842
|
+
sortable: col.sortable !== false && this._allowSorting,
|
|
1843
|
+
filter: false,
|
|
1844
|
+
resizable: col.resizable !== false && this._allowColumnResize,
|
|
1845
|
+
hide: col.visible === false
|
|
1846
|
+
};
|
|
1847
|
+
// Apply field formatter for highlighting and type-specific formatting
|
|
1848
|
+
// if we have entity metadata for this field
|
|
1849
|
+
if (this._entityInfo) {
|
|
1850
|
+
const field = this._entityInfo.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());
|
|
1851
|
+
if (field) {
|
|
1852
|
+
this.applyFieldFormatter(colDef, field);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
return colDef;
|
|
1856
|
+
}
|
|
1857
|
+
generateAgColumnDefs(entity) {
|
|
1858
|
+
const cols = [];
|
|
1859
|
+
const visibleFields = entity.Fields.filter(f => this.shouldShowField(f));
|
|
1860
|
+
for (const field of visibleFields) {
|
|
1861
|
+
const colDef = {
|
|
1862
|
+
field: field.Name,
|
|
1863
|
+
headerName: field.DisplayNameOrName,
|
|
1864
|
+
width: this.estimateColumnWidth(field),
|
|
1865
|
+
sortable: this._allowSorting,
|
|
1866
|
+
resizable: this._allowColumnResize
|
|
1867
|
+
};
|
|
1868
|
+
this.applyFieldFormatter(colDef, field);
|
|
1869
|
+
cols.push(colDef);
|
|
1870
|
+
}
|
|
1871
|
+
return cols;
|
|
1872
|
+
}
|
|
1873
|
+
applyFieldFormatter(colDef, field, customFormat) {
|
|
1874
|
+
// Store type info for use in cell renderer
|
|
1875
|
+
const fieldType = field.TSType;
|
|
1876
|
+
const fieldNameLower = field.Name.toLowerCase();
|
|
1877
|
+
const extendedType = field.ExtendedType?.toLowerCase() || '';
|
|
1878
|
+
const vc = this.effectiveVisualConfig;
|
|
1879
|
+
// Determine special field types using ExtendedType metadata first, then field name patterns as fallback
|
|
1880
|
+
const isCurrency = customFormat?.type === 'currency' ||
|
|
1881
|
+
fieldNameLower.includes('amount') ||
|
|
1882
|
+
fieldNameLower.includes('price') ||
|
|
1883
|
+
fieldNameLower.includes('cost') ||
|
|
1884
|
+
fieldNameLower.includes('total');
|
|
1885
|
+
// Use ExtendedType='Email' from metadata, fallback to field name pattern
|
|
1886
|
+
const isEmail = extendedType === 'email' ||
|
|
1887
|
+
(!extendedType && fieldNameLower.includes('email'));
|
|
1888
|
+
// Use ExtendedType='URL' from metadata, fallback to field name pattern
|
|
1889
|
+
const isUrl = extendedType === 'url' ||
|
|
1890
|
+
(!extendedType && (fieldNameLower.includes('url') ||
|
|
1891
|
+
fieldNameLower.includes('website') ||
|
|
1892
|
+
fieldNameLower.includes('link')));
|
|
1893
|
+
// Use ExtendedType='Tel' from metadata, fallback to field name pattern
|
|
1894
|
+
const isPhone = extendedType === 'tel' ||
|
|
1895
|
+
(!extendedType && (fieldNameLower.includes('phone') ||
|
|
1896
|
+
fieldNameLower.includes('mobile') ||
|
|
1897
|
+
fieldNameLower.includes('fax')));
|
|
1898
|
+
// Apply alignment - use custom format alignment if provided, otherwise default to right for numbers
|
|
1899
|
+
const customAlign = customFormat?.align;
|
|
1900
|
+
if (customAlign) {
|
|
1901
|
+
colDef.cellClass = `cell-align-${customAlign}`;
|
|
1902
|
+
colDef.headerClass = `header-align-${customAlign}`;
|
|
1903
|
+
}
|
|
1904
|
+
else if (vc.rightAlignNumbers && (fieldType === 'number' || isCurrency)) {
|
|
1905
|
+
colDef.cellClass = 'cell-align-right';
|
|
1906
|
+
colDef.headerClass = 'header-align-right';
|
|
1907
|
+
}
|
|
1908
|
+
// Apply custom header style if provided
|
|
1909
|
+
if (customFormat?.headerStyle) {
|
|
1910
|
+
const headerStyle = this.buildCssStyle(customFormat.headerStyle);
|
|
1911
|
+
if (headerStyle) {
|
|
1912
|
+
colDef.headerClass = (colDef.headerClass || '') + ' custom-header-style';
|
|
1913
|
+
// AG Grid uses headerStyle for inline styles
|
|
1914
|
+
colDef.headerStyle = this.buildStyleObject(customFormat.headerStyle);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
// Use cellRenderer for highlighting support and enhanced formatting
|
|
1918
|
+
colDef.cellRenderer = (params) => {
|
|
1919
|
+
if (params.value === null || params.value === undefined) {
|
|
1920
|
+
return '<span class="cell-empty">—</span>';
|
|
1921
|
+
}
|
|
1922
|
+
let displayValue = '';
|
|
1923
|
+
let extraClass = '';
|
|
1924
|
+
let inlineStyle = '';
|
|
1925
|
+
let isHtmlContent = false; // Track if displayValue contains HTML (icons, links)
|
|
1926
|
+
// Build inline style from custom cell style
|
|
1927
|
+
if (customFormat?.cellStyle) {
|
|
1928
|
+
inlineStyle = this.buildCssStyle(customFormat.cellStyle);
|
|
1929
|
+
}
|
|
1930
|
+
// Check if custom format is provided and has a non-auto type
|
|
1931
|
+
const useCustomFormat = customFormat && customFormat.type && customFormat.type !== 'auto';
|
|
1932
|
+
if (useCustomFormat) {
|
|
1933
|
+
// Use custom formatting
|
|
1934
|
+
displayValue = this.formatValueWithCustomFormat(params.value, customFormat);
|
|
1935
|
+
// Check if formatCustomBoolean returned HTML (icon or checkbox)
|
|
1936
|
+
if (customFormat.type === 'boolean' &&
|
|
1937
|
+
(customFormat.booleanDisplay === 'icon' || customFormat.booleanDisplay === 'checkbox')) {
|
|
1938
|
+
isHtmlContent = true;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
// Use default formatting based on field type
|
|
1943
|
+
// Boolean formatting
|
|
1944
|
+
if (fieldType === 'boolean') {
|
|
1945
|
+
if (vc.booleanIcons) {
|
|
1946
|
+
const iconClass = params.value
|
|
1947
|
+
? 'fa-solid fa-check cell-boolean-true'
|
|
1948
|
+
: 'fa-solid fa-xmark cell-boolean-false';
|
|
1949
|
+
displayValue = `<i class="${iconClass}"></i>`;
|
|
1950
|
+
isHtmlContent = true;
|
|
1951
|
+
}
|
|
1952
|
+
else {
|
|
1953
|
+
displayValue = params.value ? 'Yes' : 'No';
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
// Date formatting
|
|
1957
|
+
else if (fieldType === 'Date') {
|
|
1958
|
+
const date = params.value instanceof Date ? params.value : new Date(params.value);
|
|
1959
|
+
if (isNaN(date.getTime())) {
|
|
1960
|
+
displayValue = String(params.value);
|
|
1961
|
+
}
|
|
1962
|
+
else if (vc.friendlyDates) {
|
|
1963
|
+
displayValue = date.toLocaleDateString(undefined, {
|
|
1964
|
+
month: 'short',
|
|
1965
|
+
day: 'numeric',
|
|
1966
|
+
year: 'numeric'
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
else {
|
|
1970
|
+
displayValue = date.toISOString().split('T')[0];
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
// Currency formatting
|
|
1974
|
+
else if (fieldType === 'number' && isCurrency) {
|
|
1975
|
+
const num = Number(params.value);
|
|
1976
|
+
displayValue = isNaN(num) ? String(params.value) : `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
1977
|
+
}
|
|
1978
|
+
// Regular number formatting
|
|
1979
|
+
else if (fieldType === 'number') {
|
|
1980
|
+
const num = Number(params.value);
|
|
1981
|
+
displayValue = isNaN(num) ? String(params.value) : num.toLocaleString();
|
|
1982
|
+
}
|
|
1983
|
+
// Email formatting
|
|
1984
|
+
else if (isEmail && vc.clickableEmails) {
|
|
1985
|
+
const email = String(params.value);
|
|
1986
|
+
const escapedEmail = HighlightUtil.escapeHtml(email);
|
|
1987
|
+
if (this._filterText) {
|
|
1988
|
+
const highlighted = HighlightUtil.highlight(email, this._filterText, true);
|
|
1989
|
+
return this.wrapWithStyle(`<a href="mailto:${escapedEmail}" class="cell-link cell-email" onclick="event.stopPropagation()">${highlighted}</a>`, inlineStyle);
|
|
1990
|
+
}
|
|
1991
|
+
return this.wrapWithStyle(`<a href="mailto:${escapedEmail}" class="cell-link cell-email" onclick="event.stopPropagation()">${escapedEmail}</a>`, inlineStyle);
|
|
1992
|
+
}
|
|
1993
|
+
// URL formatting
|
|
1994
|
+
else if (isUrl && vc.clickableUrls) {
|
|
1995
|
+
let url = String(params.value);
|
|
1996
|
+
if (url && !url.startsWith('http')) {
|
|
1997
|
+
url = 'https://' + url;
|
|
1998
|
+
}
|
|
1999
|
+
const displayUrl = url.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
2000
|
+
const escapedDisplay = HighlightUtil.escapeHtml(displayUrl);
|
|
2001
|
+
if (this._filterText) {
|
|
2002
|
+
const highlighted = HighlightUtil.highlight(displayUrl, this._filterText, true);
|
|
2003
|
+
return this.wrapWithStyle(`<a href="${HighlightUtil.escapeHtml(url)}" target="_blank" class="cell-link cell-url" onclick="event.stopPropagation()">${highlighted}</a>`, inlineStyle);
|
|
2004
|
+
}
|
|
2005
|
+
return this.wrapWithStyle(`<a href="${HighlightUtil.escapeHtml(url)}" target="_blank" class="cell-link cell-url" onclick="event.stopPropagation()">${escapedDisplay}</a>`, inlineStyle);
|
|
2006
|
+
}
|
|
2007
|
+
// Phone formatting
|
|
2008
|
+
else if (isPhone) {
|
|
2009
|
+
displayValue = String(params.value);
|
|
2010
|
+
extraClass = 'cell-phone';
|
|
2011
|
+
}
|
|
2012
|
+
// Default string formatting
|
|
2013
|
+
else {
|
|
2014
|
+
displayValue = String(params.value);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
// Build the span with optional style and class
|
|
2018
|
+
const styleAttr = inlineStyle ? ` style="${inlineStyle}"` : '';
|
|
2019
|
+
const classAttr = extraClass ? ` class="${extraClass}"` : '';
|
|
2020
|
+
// Handle content that's already HTML (icons, etc.)
|
|
2021
|
+
if (isHtmlContent) {
|
|
2022
|
+
if (inlineStyle) {
|
|
2023
|
+
return `<span${styleAttr}>${displayValue}</span>`;
|
|
2024
|
+
}
|
|
2025
|
+
return displayValue;
|
|
2026
|
+
}
|
|
2027
|
+
// Apply highlighting if filterText is set
|
|
2028
|
+
if (this._filterText && displayValue) {
|
|
2029
|
+
return `<span${classAttr}${styleAttr}>${HighlightUtil.highlight(displayValue, this._filterText, true)}</span>`;
|
|
2030
|
+
}
|
|
2031
|
+
return `<span${classAttr}${styleAttr}>${HighlightUtil.escapeHtml(displayValue)}</span>`;
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Helper to wrap content with an inline style span
|
|
2036
|
+
*/
|
|
2037
|
+
wrapWithStyle(content, style) {
|
|
2038
|
+
if (!style)
|
|
2039
|
+
return content;
|
|
2040
|
+
return `<span style="${style}">${content}</span>`;
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Build a CSS style string from a ColumnTextStyle object
|
|
2044
|
+
*/
|
|
2045
|
+
buildCssStyle(style) {
|
|
2046
|
+
const parts = [];
|
|
2047
|
+
if (style.bold)
|
|
2048
|
+
parts.push('font-weight: bold');
|
|
2049
|
+
if (style.italic)
|
|
2050
|
+
parts.push('font-style: italic');
|
|
2051
|
+
if (style.underline)
|
|
2052
|
+
parts.push('text-decoration: underline');
|
|
2053
|
+
if (style.color)
|
|
2054
|
+
parts.push(`color: ${style.color}`);
|
|
2055
|
+
if (style.backgroundColor)
|
|
2056
|
+
parts.push(`background-color: ${style.backgroundColor}`);
|
|
2057
|
+
return parts.join('; ');
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Build a style object for AG Grid from a ColumnTextStyle object
|
|
2061
|
+
*/
|
|
2062
|
+
buildStyleObject(style) {
|
|
2063
|
+
const obj = {};
|
|
2064
|
+
if (style.bold)
|
|
2065
|
+
obj['fontWeight'] = 'bold';
|
|
2066
|
+
if (style.italic)
|
|
2067
|
+
obj['fontStyle'] = 'italic';
|
|
2068
|
+
if (style.underline)
|
|
2069
|
+
obj['textDecoration'] = 'underline';
|
|
2070
|
+
if (style.color)
|
|
2071
|
+
obj['color'] = style.color;
|
|
2072
|
+
if (style.backgroundColor)
|
|
2073
|
+
obj['backgroundColor'] = style.backgroundColor;
|
|
2074
|
+
return obj;
|
|
2075
|
+
}
|
|
2076
|
+
/**
|
|
2077
|
+
* Format a value using custom ColumnFormat settings
|
|
2078
|
+
*/
|
|
2079
|
+
formatValueWithCustomFormat(value, format) {
|
|
2080
|
+
if (value == null)
|
|
2081
|
+
return '—';
|
|
2082
|
+
switch (format.type) {
|
|
2083
|
+
case 'number':
|
|
2084
|
+
return this.formatCustomNumber(value, format);
|
|
2085
|
+
case 'currency':
|
|
2086
|
+
return this.formatCustomCurrency(value, format);
|
|
2087
|
+
case 'percent':
|
|
2088
|
+
return this.formatCustomPercent(value, format);
|
|
2089
|
+
case 'date':
|
|
2090
|
+
case 'datetime':
|
|
2091
|
+
return this.formatCustomDate(value, format);
|
|
2092
|
+
case 'boolean':
|
|
2093
|
+
return this.formatCustomBoolean(value, format);
|
|
2094
|
+
default:
|
|
2095
|
+
return String(value);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
formatCustomNumber(value, format) {
|
|
2099
|
+
const num = Number(value);
|
|
2100
|
+
if (isNaN(num))
|
|
2101
|
+
return String(value);
|
|
2102
|
+
const options = {
|
|
2103
|
+
minimumFractionDigits: format.decimals ?? 0,
|
|
2104
|
+
maximumFractionDigits: format.decimals ?? 0,
|
|
2105
|
+
useGrouping: format.thousandsSeparator ?? true
|
|
2106
|
+
};
|
|
2107
|
+
return new Intl.NumberFormat('en-US', options).format(num);
|
|
2108
|
+
}
|
|
2109
|
+
formatCustomCurrency(value, format) {
|
|
2110
|
+
const num = Number(value);
|
|
2111
|
+
if (isNaN(num))
|
|
2112
|
+
return String(value);
|
|
2113
|
+
const options = {
|
|
2114
|
+
style: 'currency',
|
|
2115
|
+
currency: format.currencyCode || 'USD',
|
|
2116
|
+
minimumFractionDigits: format.decimals ?? 2,
|
|
2117
|
+
maximumFractionDigits: format.decimals ?? 2
|
|
2118
|
+
};
|
|
2119
|
+
return new Intl.NumberFormat('en-US', options).format(num);
|
|
2120
|
+
}
|
|
2121
|
+
formatCustomPercent(value, format) {
|
|
2122
|
+
const num = Number(value);
|
|
2123
|
+
if (isNaN(num))
|
|
2124
|
+
return String(value);
|
|
2125
|
+
const options = {
|
|
2126
|
+
style: 'percent',
|
|
2127
|
+
minimumFractionDigits: format.decimals ?? 0,
|
|
2128
|
+
maximumFractionDigits: format.decimals ?? 0
|
|
2129
|
+
};
|
|
2130
|
+
// Assume value is already a percentage (e.g., 50 = 50%), divide by 100
|
|
2131
|
+
return new Intl.NumberFormat('en-US', options).format(num / 100);
|
|
2132
|
+
}
|
|
2133
|
+
formatCustomDate(value, format) {
|
|
2134
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
2135
|
+
if (isNaN(date.getTime()))
|
|
2136
|
+
return String(value);
|
|
2137
|
+
// Parse format string - check for weekday variants
|
|
2138
|
+
const formatStr = format.dateFormat || 'medium';
|
|
2139
|
+
const includeWeekday = formatStr.includes('-weekday');
|
|
2140
|
+
const baseFormat = formatStr.replace('-weekday', '');
|
|
2141
|
+
let options;
|
|
2142
|
+
// Intl.DateTimeFormat doesn't allow combining dateStyle with weekday
|
|
2143
|
+
// So we must use individual components when weekday is requested
|
|
2144
|
+
if (includeWeekday) {
|
|
2145
|
+
if (baseFormat === 'short') {
|
|
2146
|
+
options = { weekday: 'short', month: 'numeric', day: 'numeric', year: '2-digit' };
|
|
2147
|
+
}
|
|
2148
|
+
else if (baseFormat === 'long') {
|
|
2149
|
+
options = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' };
|
|
2150
|
+
}
|
|
2151
|
+
else {
|
|
2152
|
+
// medium
|
|
2153
|
+
options = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' };
|
|
2154
|
+
}
|
|
2155
|
+
if (format.type === 'datetime') {
|
|
2156
|
+
options.hour = 'numeric';
|
|
2157
|
+
options.minute = '2-digit';
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
else {
|
|
2161
|
+
// No weekday - can use dateStyle shorthand
|
|
2162
|
+
options = {
|
|
2163
|
+
dateStyle: baseFormat === 'short' ? 'short' : baseFormat === 'long' ? 'long' : 'medium'
|
|
2164
|
+
};
|
|
2165
|
+
if (format.type === 'datetime') {
|
|
2166
|
+
options.timeStyle = 'short';
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return new Intl.DateTimeFormat('en-US', options).format(date);
|
|
2170
|
+
}
|
|
2171
|
+
formatCustomBoolean(value, format) {
|
|
2172
|
+
if (format.booleanDisplay === 'icon') {
|
|
2173
|
+
const iconClass = value
|
|
2174
|
+
? 'fa-solid fa-check cell-boolean-true'
|
|
2175
|
+
: 'fa-solid fa-xmark cell-boolean-false';
|
|
2176
|
+
return `<i class="${iconClass}"></i>`;
|
|
2177
|
+
}
|
|
2178
|
+
if (format.booleanDisplay === 'checkbox') {
|
|
2179
|
+
return value
|
|
2180
|
+
? '<i class="fa-regular fa-square-check cell-boolean-true"></i>'
|
|
2181
|
+
: '<i class="fa-regular fa-square cell-boolean-false"></i>';
|
|
2182
|
+
}
|
|
2183
|
+
return value ? (format.trueLabel || 'Yes') : (format.falseLabel || 'No');
|
|
2184
|
+
}
|
|
2185
|
+
// ========================================
|
|
2186
|
+
// AG Grid Row Selection Configuration
|
|
2187
|
+
// ========================================
|
|
2188
|
+
updateAgRowSelection() {
|
|
2189
|
+
switch (this._selectionMode) {
|
|
2190
|
+
case 'none':
|
|
2191
|
+
this.agRowSelection = { mode: 'singleRow', enableClickSelection: false, checkboxes: false };
|
|
2192
|
+
break;
|
|
2193
|
+
case 'single':
|
|
2194
|
+
this.agRowSelection = { mode: 'singleRow', enableClickSelection: true, checkboxes: false };
|
|
2195
|
+
break;
|
|
2196
|
+
case 'multiple':
|
|
2197
|
+
this.agRowSelection = { mode: 'multiRow', enableClickSelection: true, checkboxes: false };
|
|
2198
|
+
break;
|
|
2199
|
+
case 'checkbox':
|
|
2200
|
+
this.agRowSelection = {
|
|
2201
|
+
mode: 'multiRow',
|
|
2202
|
+
enableClickSelection: true,
|
|
2203
|
+
checkboxes: true,
|
|
2204
|
+
headerCheckbox: true
|
|
2205
|
+
};
|
|
2206
|
+
break;
|
|
2207
|
+
}
|
|
2208
|
+
// Update the grid if it's already initialized
|
|
2209
|
+
if (this.gridApi) {
|
|
2210
|
+
this.gridApi.setGridOption('rowSelection', this.agRowSelection);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
// ========================================
|
|
2214
|
+
// Data Loading
|
|
2215
|
+
// ========================================
|
|
2216
|
+
async loadData(isAutoRefresh = false) {
|
|
2217
|
+
if (this._useExternalData) {
|
|
2218
|
+
this.processData();
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
// Check if we have a valid data source via Params
|
|
2222
|
+
const hasDataSource = this._params && (this._params.ViewID ||
|
|
2223
|
+
this._params.ViewName ||
|
|
2224
|
+
this._params.ViewEntity ||
|
|
2225
|
+
this._params.EntityName);
|
|
2226
|
+
if (!hasDataSource) {
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
// Check AllowLoad for deferred loading
|
|
2230
|
+
if (!this._allowLoad) {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
// For infinite scroll mode, setup or refresh the datasource
|
|
2234
|
+
if (this._paginationMode === 'infinite') {
|
|
2235
|
+
if (this.gridApi) {
|
|
2236
|
+
if (!this.infiniteDatasource) {
|
|
2237
|
+
this.setupInfiniteScroll();
|
|
2238
|
+
}
|
|
2239
|
+
else {
|
|
2240
|
+
this.refreshInfiniteCache();
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
// In infinite mode, the datasource handles loading, so we're done
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
// If a load is already in progress, return the existing promise
|
|
2247
|
+
// This prevents redundant API calls when loadData is called multiple times
|
|
2248
|
+
if (this._loadDataPromise) {
|
|
2249
|
+
return this._loadDataPromise;
|
|
2250
|
+
}
|
|
2251
|
+
// Wrap the actual loading logic in a promise we can track
|
|
2252
|
+
this._loadDataPromise = this.executeLoadData(isAutoRefresh);
|
|
2253
|
+
try {
|
|
2254
|
+
await this._loadDataPromise;
|
|
2255
|
+
}
|
|
2256
|
+
finally {
|
|
2257
|
+
this._loadDataPromise = null;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Internal method that performs the actual data loading.
|
|
2262
|
+
* Called by loadData() which handles promise deduplication.
|
|
2263
|
+
*/
|
|
2264
|
+
async executeLoadData(isAutoRefresh) {
|
|
2265
|
+
// Client-side mode - load all data upfront
|
|
2266
|
+
const beforeRefreshEvent = new BeforeDataRefreshEventArgs(this, isAutoRefresh);
|
|
2267
|
+
this.BeforeDataRefresh.emit(beforeRefreshEvent);
|
|
2268
|
+
if (beforeRefreshEvent.cancel) {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
// Build the RunViewParams - prefer new Params input over legacy inputs
|
|
2272
|
+
const runViewParams = this.buildRunViewParams();
|
|
2273
|
+
// Create GridRunViewParams for events (backward compatibility)
|
|
2274
|
+
const gridParams = {
|
|
2275
|
+
entityName: runViewParams.EntityName || this._entityInfo?.Name || '',
|
|
2276
|
+
extraFilter: runViewParams.ExtraFilter || '',
|
|
2277
|
+
orderBy: runViewParams.OrderBy || '',
|
|
2278
|
+
maxRows: runViewParams.MaxRows || 0,
|
|
2279
|
+
fields: runViewParams.Fields,
|
|
2280
|
+
searchString: runViewParams.UserSearchString || ''
|
|
2281
|
+
};
|
|
2282
|
+
const beforeLoadEvent = new BeforeDataLoadEventArgs(this, gridParams);
|
|
2283
|
+
this.BeforeDataLoad.emit(beforeLoadEvent);
|
|
2284
|
+
if (beforeLoadEvent.cancel) {
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
// Apply any modifications from beforeLoadEvent
|
|
2288
|
+
if (beforeLoadEvent.modifiedParams) {
|
|
2289
|
+
if (beforeLoadEvent.modifiedParams.extraFilter) {
|
|
2290
|
+
runViewParams.ExtraFilter = beforeLoadEvent.modifiedParams.extraFilter;
|
|
2291
|
+
}
|
|
2292
|
+
if (beforeLoadEvent.modifiedParams.orderBy) {
|
|
2293
|
+
runViewParams.OrderBy = beforeLoadEvent.modifiedParams.orderBy;
|
|
2294
|
+
}
|
|
2295
|
+
if (beforeLoadEvent.modifiedParams.maxRows) {
|
|
2296
|
+
runViewParams.MaxRows = beforeLoadEvent.modifiedParams.maxRows;
|
|
2297
|
+
}
|
|
2298
|
+
if (beforeLoadEvent.modifiedParams.fields) {
|
|
2299
|
+
runViewParams.Fields = beforeLoadEvent.modifiedParams.fields;
|
|
2300
|
+
}
|
|
2301
|
+
if (beforeLoadEvent.modifiedParams.searchString) {
|
|
2302
|
+
runViewParams.UserSearchString = beforeLoadEvent.modifiedParams.searchString;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
this.loading = true;
|
|
2306
|
+
this.errorMessage = '';
|
|
2307
|
+
this.cdr.detectChanges();
|
|
2308
|
+
const startTime = performance.now();
|
|
2309
|
+
try {
|
|
2310
|
+
const rv = new RunView();
|
|
2311
|
+
const result = await rv.RunView({
|
|
2312
|
+
...runViewParams,
|
|
2313
|
+
ResultType: 'entity_object'
|
|
2314
|
+
});
|
|
2315
|
+
const loadTimeMs = performance.now() - startTime;
|
|
2316
|
+
if (result.Success) {
|
|
2317
|
+
this._allData = result.Results || [];
|
|
2318
|
+
this.totalRowCount = result.TotalRowCount || this._allData.length;
|
|
2319
|
+
this.processData();
|
|
2320
|
+
// Reapply sort state to grid after data load to maintain visual indicators
|
|
2321
|
+
// Use Promise.resolve() to defer until after Angular's change detection cycle
|
|
2322
|
+
// has completed and AG Grid has processed the new row data
|
|
2323
|
+
if (this._sortState.length > 0) {
|
|
2324
|
+
Promise.resolve().then(() => {
|
|
2325
|
+
this.applySortStateToGrid();
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, true, this.totalRowCount, this._allData.length, loadTimeMs);
|
|
2329
|
+
this.AfterDataLoad.emit(afterLoadEvent);
|
|
2330
|
+
const afterRefreshEvent = new AfterDataRefreshEventArgs(this, true, this.totalRowCount, loadTimeMs);
|
|
2331
|
+
this.AfterDataRefresh.emit(afterRefreshEvent);
|
|
2332
|
+
}
|
|
2333
|
+
else {
|
|
2334
|
+
this.errorMessage = result.ErrorMessage || 'Failed to load data';
|
|
2335
|
+
const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, false, 0, 0, loadTimeMs, this.errorMessage);
|
|
2336
|
+
this.AfterDataLoad.emit(afterLoadEvent);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
catch (error) {
|
|
2340
|
+
const loadTimeMs = performance.now() - startTime;
|
|
2341
|
+
this.errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
2342
|
+
const afterLoadEvent = new AfterDataLoadEventArgs(this, gridParams, false, 0, 0, loadTimeMs, this.errorMessage);
|
|
2343
|
+
this.AfterDataLoad.emit(afterLoadEvent);
|
|
2344
|
+
}
|
|
2345
|
+
finally {
|
|
2346
|
+
this.loading = false;
|
|
2347
|
+
this.cdr.detectChanges();
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Builds RunViewParams from the Params input.
|
|
2352
|
+
* Requires Params to be set - the grid needs a data source configured.
|
|
2353
|
+
*/
|
|
2354
|
+
buildRunViewParams() {
|
|
2355
|
+
if (!this._params) {
|
|
2356
|
+
throw new Error('Params must be set before loading data');
|
|
2357
|
+
}
|
|
2358
|
+
const params = { ...this._params };
|
|
2359
|
+
// Apply sort state (may be modified by user interactions)
|
|
2360
|
+
const orderBy = this.buildOrderByClause();
|
|
2361
|
+
if (orderBy) {
|
|
2362
|
+
params.OrderBy = orderBy;
|
|
2363
|
+
}
|
|
2364
|
+
return params;
|
|
2365
|
+
}
|
|
2366
|
+
buildOrderByClause() {
|
|
2367
|
+
if (this._sortState.length > 0) {
|
|
2368
|
+
return this._sortState
|
|
2369
|
+
.sort((a, b) => a.index - b.index)
|
|
2370
|
+
.map(s => `${s.field} ${s.direction.toUpperCase()}`)
|
|
2371
|
+
.join(', ');
|
|
2372
|
+
}
|
|
2373
|
+
// Fall back to OrderBy from Params if set
|
|
2374
|
+
return this._params?.OrderBy || '';
|
|
2375
|
+
}
|
|
2376
|
+
// ========================================
|
|
2377
|
+
// Infinite Scroll Data Source
|
|
2378
|
+
// ========================================
|
|
2379
|
+
/**
|
|
2380
|
+
* Creates and returns an AG Grid IDatasource for infinite scroll mode.
|
|
2381
|
+
* This datasource fetches data in pages from the server as the user scrolls.
|
|
2382
|
+
*/
|
|
2383
|
+
createInfiniteDatasource() {
|
|
2384
|
+
return {
|
|
2385
|
+
getRows: async (params) => {
|
|
2386
|
+
const startRow = params.startRow;
|
|
2387
|
+
const endRow = params.endRow;
|
|
2388
|
+
const blockSize = endRow - startRow;
|
|
2389
|
+
try {
|
|
2390
|
+
// Build params with pagination
|
|
2391
|
+
const runViewParams = this.buildRunViewParams();
|
|
2392
|
+
runViewParams.StartRow = startRow;
|
|
2393
|
+
runViewParams.MaxRows = blockSize;
|
|
2394
|
+
const rv = new RunView();
|
|
2395
|
+
const result = await rv.RunView({
|
|
2396
|
+
...runViewParams,
|
|
2397
|
+
ResultType: 'entity_object'
|
|
2398
|
+
});
|
|
2399
|
+
if (result.Success) {
|
|
2400
|
+
const entities = result.Results || [];
|
|
2401
|
+
// Process entities into row data
|
|
2402
|
+
const rowData = entities.map((entity, index) => {
|
|
2403
|
+
return this.entityToRowData(entity, startRow + index);
|
|
2404
|
+
});
|
|
2405
|
+
// Update total row count
|
|
2406
|
+
this.totalRowCount = result.TotalRowCount || 0;
|
|
2407
|
+
this.cdr.detectChanges();
|
|
2408
|
+
// Determine if we've reached the last row
|
|
2409
|
+
const lastRow = result.TotalRowCount != null && startRow + entities.length >= result.TotalRowCount
|
|
2410
|
+
? result.TotalRowCount
|
|
2411
|
+
: undefined;
|
|
2412
|
+
params.successCallback(rowData, lastRow);
|
|
2413
|
+
}
|
|
2414
|
+
else {
|
|
2415
|
+
this.errorMessage = result.ErrorMessage || 'Failed to load data';
|
|
2416
|
+
this.cdr.detectChanges();
|
|
2417
|
+
params.failCallback();
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
catch (error) {
|
|
2421
|
+
this.errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
2422
|
+
this.cdr.detectChanges();
|
|
2423
|
+
params.failCallback();
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Converts a BaseEntity to AG Grid row data format.
|
|
2430
|
+
* This is used by both client and infinite scroll modes for consistent data formatting.
|
|
2431
|
+
*/
|
|
2432
|
+
entityToRowData(entity, index) {
|
|
2433
|
+
const key = this.getRowKey(entity);
|
|
2434
|
+
// Store in row data map for later retrieval
|
|
2435
|
+
const rowData = {
|
|
2436
|
+
key,
|
|
2437
|
+
index,
|
|
2438
|
+
entity,
|
|
2439
|
+
selected: this._selectedKeys.includes(key),
|
|
2440
|
+
editing: this._editingRowKey === key,
|
|
2441
|
+
dirty: this._pendingChanges.some(c => c.rowKey === key),
|
|
2442
|
+
cssClasses: this.computeRowClasses(index, entity)
|
|
2443
|
+
};
|
|
2444
|
+
this._rowDataMap.set(key, rowData);
|
|
2445
|
+
// Build AG Grid row data
|
|
2446
|
+
const row = {
|
|
2447
|
+
__pk: key
|
|
2448
|
+
};
|
|
2449
|
+
if (this._entityInfo) {
|
|
2450
|
+
for (const field of this._entityInfo.Fields) {
|
|
2451
|
+
row[field.Name] = entity.Get(field.Name);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return row;
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Sets up the grid for infinite scroll mode.
|
|
2458
|
+
* Called when PaginationMode is 'infinite'.
|
|
2459
|
+
*/
|
|
2460
|
+
setupInfiniteScroll() {
|
|
2461
|
+
if (!this.gridApi)
|
|
2462
|
+
return;
|
|
2463
|
+
// Create datasource
|
|
2464
|
+
this.infiniteDatasource = this.createInfiniteDatasource();
|
|
2465
|
+
// Set datasource on grid
|
|
2466
|
+
this.gridApi.setGridOption('datasource', this.infiniteDatasource);
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Refreshes the infinite scroll cache.
|
|
2470
|
+
* Call this when data source parameters change.
|
|
2471
|
+
*/
|
|
2472
|
+
refreshInfiniteCache() {
|
|
2473
|
+
if (!this.gridApi || this._paginationMode !== 'infinite')
|
|
2474
|
+
return;
|
|
2475
|
+
// Clear the row data map since we're refreshing
|
|
2476
|
+
this._rowDataMap.clear();
|
|
2477
|
+
// Purge cache and refresh
|
|
2478
|
+
this.gridApi.refreshInfiniteCache();
|
|
2479
|
+
// Reapply sort state to grid after refresh to maintain visual indicators
|
|
2480
|
+
// Use Promise.resolve() to defer until after AG Grid has processed the cache refresh
|
|
2481
|
+
if (this._sortState.length > 0) {
|
|
2482
|
+
Promise.resolve().then(() => {
|
|
2483
|
+
this.applySortStateToGrid();
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
processData() {
|
|
2488
|
+
this._rowDataMap.clear();
|
|
2489
|
+
const dataSource = this._useExternalData ? this._data : this._allData;
|
|
2490
|
+
this.rowData = dataSource.map((entity, index) => {
|
|
2491
|
+
const key = this.getRowKey(entity);
|
|
2492
|
+
const rowData = {
|
|
2493
|
+
key,
|
|
2494
|
+
index,
|
|
2495
|
+
entity,
|
|
2496
|
+
selected: this._selectedKeys.includes(key),
|
|
2497
|
+
editing: this._editingRowKey === key,
|
|
2498
|
+
dirty: this._pendingChanges.some(c => c.rowKey === key),
|
|
2499
|
+
cssClasses: this.computeRowClasses(index, entity)
|
|
2500
|
+
};
|
|
2501
|
+
this._rowDataMap.set(key, rowData);
|
|
2502
|
+
// Build AG Grid row data
|
|
2503
|
+
const row = {
|
|
2504
|
+
__pk: key
|
|
2505
|
+
};
|
|
2506
|
+
if (this._entityInfo) {
|
|
2507
|
+
for (const field of this._entityInfo.Fields) {
|
|
2508
|
+
row[field.Name] = entity.Get(field.Name);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
return row;
|
|
2512
|
+
});
|
|
2513
|
+
this.cdr.detectChanges();
|
|
2514
|
+
}
|
|
2515
|
+
getRowKey(entity) {
|
|
2516
|
+
// Use composite key if available
|
|
2517
|
+
if (entity.PrimaryKey) {
|
|
2518
|
+
return entity.PrimaryKey.ToConcatenatedString();
|
|
2519
|
+
}
|
|
2520
|
+
const keyValue = entity.Get(this._keyField);
|
|
2521
|
+
return keyValue != null ? String(keyValue) : '';
|
|
2522
|
+
}
|
|
2523
|
+
computeRowClasses(index, _entity) {
|
|
2524
|
+
const classes = [];
|
|
2525
|
+
if (this._striped && index % 2 === 1) {
|
|
2526
|
+
classes.push('grid-row-alt');
|
|
2527
|
+
}
|
|
2528
|
+
return classes;
|
|
2529
|
+
}
|
|
2530
|
+
// ========================================
|
|
2531
|
+
// AG Grid Event Handlers
|
|
2532
|
+
// ========================================
|
|
2533
|
+
onGridReady(event) {
|
|
2534
|
+
this.gridApi = event.api;
|
|
2535
|
+
this.updateSelection();
|
|
2536
|
+
if (this._sortState.length > 0) {
|
|
2537
|
+
this.applySortStateToGrid();
|
|
2538
|
+
}
|
|
2539
|
+
// Smart column sizing: use our estimated widths but ensure grid fills available space
|
|
2540
|
+
// without making columns excessively wide
|
|
2541
|
+
this.autoSizeColumnsSmartly(event.api);
|
|
2542
|
+
// Setup infinite scroll if in that mode and we have params
|
|
2543
|
+
if (this._paginationMode === 'infinite' && this._allowLoad && this._params) {
|
|
2544
|
+
this.setupInfiniteScroll();
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Smart column auto-sizing that respects our width estimates
|
|
2549
|
+
* while ensuring the grid fills available space appropriately
|
|
2550
|
+
*/
|
|
2551
|
+
autoSizeColumnsSmartly(api) {
|
|
2552
|
+
// Get total estimated width from our column definitions
|
|
2553
|
+
const totalEstimatedWidth = this.agColumnDefs.reduce((sum, col) => sum + (col.width || 150), 0);
|
|
2554
|
+
// Get available width from the grid container
|
|
2555
|
+
const gridElement = this.elementRef.nativeElement.querySelector('.mj-ag-grid');
|
|
2556
|
+
const availableWidth = gridElement?.clientWidth || 0;
|
|
2557
|
+
if (availableWidth > 0 && totalEstimatedWidth < availableWidth) {
|
|
2558
|
+
// If our columns are narrower than available space, stretch proportionally
|
|
2559
|
+
// but cap individual column growth to prevent excessive widths
|
|
2560
|
+
const ratio = availableWidth / totalEstimatedWidth;
|
|
2561
|
+
const maxRatio = 1.5; // Don't let columns grow more than 50%
|
|
2562
|
+
if (ratio <= maxRatio) {
|
|
2563
|
+
// Moderate stretch - use sizeColumnsToFit
|
|
2564
|
+
api.sizeColumnsToFit();
|
|
2565
|
+
}
|
|
2566
|
+
else {
|
|
2567
|
+
// Too much stretch - just use our estimated widths
|
|
2568
|
+
// The grid will have some empty space on the right, which is fine
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
// If our columns are wider than available space, let the user scroll horizontally
|
|
2572
|
+
// (AG Grid handles this automatically)
|
|
2573
|
+
}
|
|
2574
|
+
onAgRowClicked(event) {
|
|
2575
|
+
if (!this._entityInfo || !event.data)
|
|
2576
|
+
return;
|
|
2577
|
+
const pkString = event.data['__pk'];
|
|
2578
|
+
const rowData = this._rowDataMap.get(pkString);
|
|
2579
|
+
if (!rowData)
|
|
2580
|
+
return;
|
|
2581
|
+
// Fire before click event
|
|
2582
|
+
const mouseEvent = event.event;
|
|
2583
|
+
const beforeEvent = new BeforeRowClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
|
|
2584
|
+
this.BeforeRowClick.emit(beforeEvent);
|
|
2585
|
+
if (beforeEvent.cancel)
|
|
2586
|
+
return;
|
|
2587
|
+
// Fire after click event
|
|
2588
|
+
const afterEvent = new AfterRowClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
|
|
2589
|
+
this.AfterRowClick.emit(afterEvent);
|
|
2590
|
+
// Auto-navigate on single click (if enabled and not navigating on double-click only)
|
|
2591
|
+
if (this._autoNavigate && !this._navigateOnDoubleClick && this._editMode === 'none') {
|
|
2592
|
+
this.emitNavigationRequest(rowData.entity, pkString);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
onAgRowDoubleClicked(event) {
|
|
2596
|
+
if (!this._entityInfo || !event.data)
|
|
2597
|
+
return;
|
|
2598
|
+
const pkString = event.data['__pk'];
|
|
2599
|
+
const rowData = this._rowDataMap.get(pkString);
|
|
2600
|
+
if (!rowData)
|
|
2601
|
+
return;
|
|
2602
|
+
// Fire before double-click event
|
|
2603
|
+
const mouseEvent = event.event;
|
|
2604
|
+
const beforeEvent = new BeforeRowDoubleClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
|
|
2605
|
+
this.BeforeRowDoubleClick.emit(beforeEvent);
|
|
2606
|
+
if (beforeEvent.cancel)
|
|
2607
|
+
return;
|
|
2608
|
+
// Fire after double-click event
|
|
2609
|
+
const afterEvent = new AfterRowDoubleClickEventArgs(this, rowData.entity, rowData.index, pkString, mouseEvent, undefined, undefined);
|
|
2610
|
+
this.AfterRowDoubleClick.emit(afterEvent);
|
|
2611
|
+
// Auto-navigate on double-click (if enabled)
|
|
2612
|
+
if (this._autoNavigate && this._navigateOnDoubleClick && this._editMode === 'none') {
|
|
2613
|
+
this.emitNavigationRequest(rowData.entity, pkString);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Emits a navigation request for the given entity record.
|
|
2618
|
+
*/
|
|
2619
|
+
emitNavigationRequest(entity, compositeKey) {
|
|
2620
|
+
if (!this._entityInfo)
|
|
2621
|
+
return;
|
|
2622
|
+
this.NavigationRequested.emit({
|
|
2623
|
+
entityInfo: this._entityInfo,
|
|
2624
|
+
record: entity,
|
|
2625
|
+
compositeKey
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
onAgSortChanged(event) {
|
|
2629
|
+
if (this.suppressSortEvents)
|
|
2630
|
+
return;
|
|
2631
|
+
const sortModel = event.api.getColumnState()
|
|
2632
|
+
.filter(col => col.sort)
|
|
2633
|
+
.map(col => ({
|
|
2634
|
+
field: col.colId,
|
|
2635
|
+
direction: col.sort,
|
|
2636
|
+
index: col.sortIndex ?? 0
|
|
2637
|
+
}));
|
|
2638
|
+
if (sortModel.length > 0) {
|
|
2639
|
+
// Find the column config
|
|
2640
|
+
const column = this._columnStates.find(c => c.config.field === sortModel[0].field);
|
|
2641
|
+
if (column) {
|
|
2642
|
+
// Fire before sort event
|
|
2643
|
+
const beforeEvent = new BeforeSortEventArgs(this, column.config, sortModel[0].direction, sortModel.length > 1, this._sortState);
|
|
2644
|
+
this.BeforeSort.emit(beforeEvent);
|
|
2645
|
+
if (beforeEvent.cancel) {
|
|
2646
|
+
// Revert to previous sort state
|
|
2647
|
+
this.applySortStateToGrid();
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
this._sortState = sortModel;
|
|
2652
|
+
if (column) {
|
|
2653
|
+
// Fire after sort event
|
|
2654
|
+
const afterEvent = new AfterSortEventArgs(this, column.config, sortModel[0].direction, this._sortState);
|
|
2655
|
+
this.AfterSort.emit(afterEvent);
|
|
2656
|
+
}
|
|
2657
|
+
// User changed sort - mark as dirty and emit grid state changed
|
|
2658
|
+
this._isGridStateDirty = true;
|
|
2659
|
+
this.emitGridStateChanged('sort');
|
|
2660
|
+
// Determine if we need to reload data from server or can sort client-side
|
|
2661
|
+
// Client-side sorting is only possible if we have ALL the data loaded
|
|
2662
|
+
if (this._serverSideSorting && !this._useExternalData) {
|
|
2663
|
+
this.loadData(true);
|
|
2664
|
+
}
|
|
2665
|
+
else if (this._useExternalData) {
|
|
2666
|
+
// Using external data - check if we have all data or just a page
|
|
2667
|
+
// If totalRowCount > current data length, we only have partial data and parent must handle sorting
|
|
2668
|
+
// Parent receives afterSort event and can reload with new sort order
|
|
2669
|
+
// If we have all data, AG Grid handles client-side sorting automatically
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
else {
|
|
2673
|
+
this._sortState = [];
|
|
2674
|
+
// User cleared sort - mark as dirty and emit grid state changed
|
|
2675
|
+
this._isGridStateDirty = true;
|
|
2676
|
+
this.emitGridStateChanged('sort');
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
onAgSelectionChanged(event) {
|
|
2680
|
+
const selectedNodes = event.api.getSelectedNodes();
|
|
2681
|
+
const previousSelection = [...this._selectedKeys];
|
|
2682
|
+
const newSelection = selectedNodes.map(node => node.data['__pk']);
|
|
2683
|
+
// Find newly selected rows
|
|
2684
|
+
const addedKeys = newSelection.filter(k => !previousSelection.includes(k));
|
|
2685
|
+
const removedKeys = previousSelection.filter(k => !newSelection.includes(k));
|
|
2686
|
+
// Handle deselections
|
|
2687
|
+
for (const key of removedKeys) {
|
|
2688
|
+
const rowData = this._rowDataMap.get(key);
|
|
2689
|
+
if (rowData) {
|
|
2690
|
+
const beforeEvent = new BeforeRowDeselectEventArgs(this, rowData.entity, rowData.index, key, this._selectedKeys);
|
|
2691
|
+
this.BeforeRowDeselect.emit(beforeEvent);
|
|
2692
|
+
// Note: Can't cancel AG Grid's selection, but we emit the event
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
// Handle selections
|
|
2696
|
+
for (const key of addedKeys) {
|
|
2697
|
+
const rowData = this._rowDataMap.get(key);
|
|
2698
|
+
if (rowData) {
|
|
2699
|
+
const beforeEvent = new BeforeRowSelectEventArgs(this, rowData.entity, rowData.index, key, addedKeys.length > 1, this._selectedKeys);
|
|
2700
|
+
this.BeforeRowSelect.emit(beforeEvent);
|
|
2701
|
+
// Note: Can't cancel AG Grid's selection, but we emit the event
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
this._selectedKeys = newSelection;
|
|
2705
|
+
this.updateRowSelectionState();
|
|
2706
|
+
// Fire after events
|
|
2707
|
+
for (const key of removedKeys) {
|
|
2708
|
+
const rowData = this._rowDataMap.get(key);
|
|
2709
|
+
if (rowData) {
|
|
2710
|
+
const afterEvent = new AfterRowDeselectEventArgs(this, rowData.entity, rowData.index, key, this._selectedKeys, previousSelection);
|
|
2711
|
+
this.AfterRowDeselect.emit(afterEvent);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
for (const key of addedKeys) {
|
|
2715
|
+
const rowData = this._rowDataMap.get(key);
|
|
2716
|
+
if (rowData) {
|
|
2717
|
+
const afterEvent = new AfterRowSelectEventArgs(this, rowData.entity, rowData.index, key, addedKeys.length > 1, this._selectedKeys, previousSelection);
|
|
2718
|
+
this.AfterRowSelect.emit(afterEvent);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
this.SelectionChange.emit(this._selectedKeys);
|
|
2722
|
+
}
|
|
2723
|
+
onAgColumnResized(event) {
|
|
2724
|
+
if (event.finished && event.source !== 'api') {
|
|
2725
|
+
// User manually resized a column - mark as dirty
|
|
2726
|
+
this._isGridStateDirty = true;
|
|
2727
|
+
this.emitGridStateChanged('columns');
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
onAgColumnMoved(event) {
|
|
2731
|
+
if (event.finished && event.source !== 'api') {
|
|
2732
|
+
// User manually moved a column - mark as dirty
|
|
2733
|
+
this._isGridStateDirty = true;
|
|
2734
|
+
this.emitGridStateChanged('columns');
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
// ========================================
|
|
2738
|
+
// Grid State Management
|
|
2739
|
+
// ========================================
|
|
2740
|
+
emitGridStateChanged(changeType) {
|
|
2741
|
+
if (!this.gridApi || !this._entityInfo)
|
|
2742
|
+
return;
|
|
2743
|
+
// Only emit and persist if we're dirty (user made actual changes)
|
|
2744
|
+
// This prevents emitting stale state during view transitions
|
|
2745
|
+
if (!this._isGridStateDirty) {
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
const currentState = this.buildCurrentGridState();
|
|
2749
|
+
// Emit the event for external consumers
|
|
2750
|
+
this.GridStateChanged.emit({
|
|
2751
|
+
gridState: currentState,
|
|
2752
|
+
changeType
|
|
2753
|
+
});
|
|
2754
|
+
// Schedule auto-persist if enabled, but NOT during view transitions
|
|
2755
|
+
// (suppressPersist prevents old view's state from being saved to new view)
|
|
2756
|
+
if (this._autoPersistState && !this._suppressPersist && this.canEditCurrentView) {
|
|
2757
|
+
if (!this.IsDynamicView && this._viewEntity) {
|
|
2758
|
+
// Stored view - persist to UserView.GridState (debounced)
|
|
2759
|
+
// Track pending state for flush on destroy
|
|
2760
|
+
this._pendingViewStateToPersist = currentState;
|
|
2761
|
+
this.statePersistSubject.next(currentState);
|
|
2762
|
+
}
|
|
2763
|
+
else if (this.IsDynamicView) {
|
|
2764
|
+
// Dynamic view - persist to User Settings as defaults (debounced via same subject)
|
|
2765
|
+
// Track pending state for flush on destroy
|
|
2766
|
+
this._pendingUserDefaultsToPersist = currentState;
|
|
2767
|
+
this.userDefaultsPersistSubject.next(currentState);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
/**
|
|
2772
|
+
* Persists the grid state to the UserView entity.
|
|
2773
|
+
* Only saves if the user has edit permission on the view.
|
|
2774
|
+
*/
|
|
2775
|
+
async persistGridStateToView(state) {
|
|
2776
|
+
if (!this._viewEntity || this.isSavingState) {
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
// Check permission before saving
|
|
2780
|
+
if (!this._viewEntity.UserCanEdit) {
|
|
2781
|
+
this._pendingViewStateToPersist = null; // Clear pending since we can't save anyway
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
try {
|
|
2785
|
+
this.isSavingState = true;
|
|
2786
|
+
this.pendingStateToSave = state;
|
|
2787
|
+
// Build the grid state JSON matching ViewGridState format
|
|
2788
|
+
const gridStateJson = {
|
|
2789
|
+
columnSettings: state.columnSettings,
|
|
2790
|
+
sortSettings: state.sortSettings
|
|
2791
|
+
};
|
|
2792
|
+
// Update the view entity's GridState
|
|
2793
|
+
this._viewEntity.GridState = JSON.stringify(gridStateJson);
|
|
2794
|
+
// Update SortState separately if changed
|
|
2795
|
+
if (state.sortSettings?.length) {
|
|
2796
|
+
this._viewEntity.SortState = JSON.stringify(state.sortSettings.map(s => ({
|
|
2797
|
+
field: s.field,
|
|
2798
|
+
dir: s.dir
|
|
2799
|
+
})));
|
|
2800
|
+
}
|
|
2801
|
+
// Save the view entity
|
|
2802
|
+
const success = await this._viewEntity.Save();
|
|
2803
|
+
if (!success) {
|
|
2804
|
+
console.warn('[entity-data-grid] Failed to save view state:', this._viewEntity.LatestResult?.Message);
|
|
2805
|
+
}
|
|
2806
|
+
else {
|
|
2807
|
+
// Clear pending state and reset dirty flag after successful save
|
|
2808
|
+
this._pendingViewStateToPersist = null;
|
|
2809
|
+
this._isGridStateDirty = false;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
catch (error) {
|
|
2813
|
+
console.error('[entity-data-grid] Error persisting grid state:', error);
|
|
2814
|
+
}
|
|
2815
|
+
finally {
|
|
2816
|
+
this.isSavingState = false;
|
|
2817
|
+
this.pendingStateToSave = null;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
buildCurrentGridState() {
|
|
2821
|
+
if (!this.gridApi || !this._entityInfo || !this._entityInfo.Fields) {
|
|
2822
|
+
return { columnSettings: [], sortSettings: [] };
|
|
2823
|
+
}
|
|
2824
|
+
const columnState = this.gridApi.getColumnState();
|
|
2825
|
+
if (!columnState) {
|
|
2826
|
+
return { columnSettings: [], sortSettings: [] };
|
|
2827
|
+
}
|
|
2828
|
+
// Build lookup maps for existing custom properties to preserve them
|
|
2829
|
+
// AG Grid's column state doesn't track our custom format/userDisplayName properties,
|
|
2830
|
+
// so we need to carry them over from the current gridState
|
|
2831
|
+
const existingFormatsByName = new Map();
|
|
2832
|
+
const existingUserDisplayNames = new Map();
|
|
2833
|
+
if (this._gridState?.columnSettings) {
|
|
2834
|
+
for (const col of this._gridState.columnSettings) {
|
|
2835
|
+
const keyLower = col.Name.toLowerCase();
|
|
2836
|
+
if (col.format) {
|
|
2837
|
+
existingFormatsByName.set(keyLower, col.format);
|
|
2838
|
+
}
|
|
2839
|
+
if (col.userDisplayName) {
|
|
2840
|
+
existingUserDisplayNames.set(keyLower, col.userDisplayName);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
const columnSettings = [];
|
|
2845
|
+
const sortSettings = [];
|
|
2846
|
+
for (let i = 0; i < columnState.length; i++) {
|
|
2847
|
+
const col = columnState[i];
|
|
2848
|
+
if (col.colId === '__rowNumber')
|
|
2849
|
+
continue; // Skip row number column
|
|
2850
|
+
const field = this._entityInfo.Fields.find(f => f.Name === col.colId);
|
|
2851
|
+
if (field) {
|
|
2852
|
+
const keyLower = field.Name.toLowerCase();
|
|
2853
|
+
const colConfig = {
|
|
2854
|
+
ID: field.ID,
|
|
2855
|
+
Name: field.Name,
|
|
2856
|
+
DisplayName: field.DisplayNameOrName,
|
|
2857
|
+
hidden: col.hide ?? false,
|
|
2858
|
+
width: col.width ?? undefined,
|
|
2859
|
+
orderIndex: i
|
|
2860
|
+
};
|
|
2861
|
+
// Preserve format from existing gridState if present
|
|
2862
|
+
const existingFormat = existingFormatsByName.get(keyLower);
|
|
2863
|
+
if (existingFormat) {
|
|
2864
|
+
colConfig.format = existingFormat;
|
|
2865
|
+
}
|
|
2866
|
+
// Preserve userDisplayName from existing gridState if present
|
|
2867
|
+
const existingUserDisplayName = existingUserDisplayNames.get(keyLower);
|
|
2868
|
+
if (existingUserDisplayName) {
|
|
2869
|
+
colConfig.userDisplayName = existingUserDisplayName;
|
|
2870
|
+
}
|
|
2871
|
+
columnSettings.push(colConfig);
|
|
2872
|
+
}
|
|
2873
|
+
if (col.sort) {
|
|
2874
|
+
sortSettings.push({
|
|
2875
|
+
field: col.colId,
|
|
2876
|
+
dir: col.sort
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return { columnSettings, sortSettings };
|
|
2881
|
+
}
|
|
2882
|
+
applySortStateToGrid() {
|
|
2883
|
+
if (!this.gridApi || this._sortState.length === 0)
|
|
2884
|
+
return;
|
|
2885
|
+
const currentColumnState = this.gridApi.getColumnState();
|
|
2886
|
+
if (!currentColumnState)
|
|
2887
|
+
return;
|
|
2888
|
+
this.suppressSortEvents = true;
|
|
2889
|
+
try {
|
|
2890
|
+
const columnState = currentColumnState.map(col => {
|
|
2891
|
+
const sort = this._sortState.find(s => s.field === col.colId);
|
|
2892
|
+
return {
|
|
2893
|
+
...col,
|
|
2894
|
+
sort: sort ? sort.direction : null,
|
|
2895
|
+
sortIndex: sort ? sort.index : null
|
|
2896
|
+
};
|
|
2897
|
+
});
|
|
2898
|
+
this.gridApi.applyColumnState({ state: columnState });
|
|
2899
|
+
}
|
|
2900
|
+
finally {
|
|
2901
|
+
this.suppressSortEvents = false;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
updateSelection() {
|
|
2905
|
+
if (!this.gridApi || this._selectedKeys.length === 0) {
|
|
2906
|
+
this.gridApi?.deselectAll();
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
for (const key of this._selectedKeys) {
|
|
2910
|
+
const node = this.gridApi.getRowNode(key);
|
|
2911
|
+
if (node) {
|
|
2912
|
+
node.setSelected(true);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
updateRowSelectionState() {
|
|
2917
|
+
for (const rowData of this._rowDataMap.values()) {
|
|
2918
|
+
rowData.selected = this._selectedKeys.includes(rowData.key);
|
|
2919
|
+
}
|
|
2920
|
+
this.cdr.detectChanges();
|
|
2921
|
+
}
|
|
2922
|
+
// ========================================
|
|
2923
|
+
// Selection Public Methods
|
|
2924
|
+
// ========================================
|
|
2925
|
+
SelectRows(keys, additive = false) {
|
|
2926
|
+
if (!this.gridApi)
|
|
2927
|
+
return;
|
|
2928
|
+
if (!additive) {
|
|
2929
|
+
this.gridApi.deselectAll();
|
|
2930
|
+
}
|
|
2931
|
+
for (const key of keys) {
|
|
2932
|
+
const node = this.gridApi.getRowNode(key);
|
|
2933
|
+
if (node) {
|
|
2934
|
+
node.setSelected(true, !additive);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
DeselectRows(keys) {
|
|
2939
|
+
if (!this.gridApi)
|
|
2940
|
+
return;
|
|
2941
|
+
for (const key of keys) {
|
|
2942
|
+
const node = this.gridApi.getRowNode(key);
|
|
2943
|
+
if (node) {
|
|
2944
|
+
node.setSelected(false);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
SelectAll() {
|
|
2949
|
+
this.gridApi?.selectAll();
|
|
2950
|
+
}
|
|
2951
|
+
ClearSelection() {
|
|
2952
|
+
this.gridApi?.deselectAll();
|
|
2953
|
+
}
|
|
2954
|
+
GetSelectedRows() {
|
|
2955
|
+
return this._selectedKeys
|
|
2956
|
+
.map(key => this._rowDataMap.get(key)?.entity)
|
|
2957
|
+
.filter((e) => e !== undefined);
|
|
2958
|
+
}
|
|
2959
|
+
IsRowSelected(key) {
|
|
2960
|
+
return this._selectedKeys.includes(key);
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Scrolls the grid to make the specified row visible.
|
|
2964
|
+
* @param key The primary key of the row to scroll to
|
|
2965
|
+
* @param position Where to position the row: 'top', 'middle', or 'bottom'
|
|
2966
|
+
*/
|
|
2967
|
+
EnsureRowVisible(key, position = 'middle') {
|
|
2968
|
+
if (!this.gridApi)
|
|
2969
|
+
return;
|
|
2970
|
+
const node = this.gridApi.getRowNode(key);
|
|
2971
|
+
if (node) {
|
|
2972
|
+
this.gridApi.ensureNodeVisible(node, position);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
// ========================================
|
|
2976
|
+
// Public Methods
|
|
2977
|
+
// ========================================
|
|
2978
|
+
async Refresh() {
|
|
2979
|
+
await this.loadData(false);
|
|
2980
|
+
}
|
|
2981
|
+
Clear() {
|
|
2982
|
+
this._allData = [];
|
|
2983
|
+
this._data = [];
|
|
2984
|
+
this._rowDataMap.clear();
|
|
2985
|
+
this._selectedKeys = [];
|
|
2986
|
+
this.rowData = [];
|
|
2987
|
+
this.totalRowCount = 0;
|
|
2988
|
+
this.cdr.detectChanges();
|
|
2989
|
+
}
|
|
2990
|
+
GetData() {
|
|
2991
|
+
return this._useExternalData ? [...this._data] : [...this._allData];
|
|
2992
|
+
}
|
|
2993
|
+
GetRowByKey(key) {
|
|
2994
|
+
return this._rowDataMap.get(key)?.entity;
|
|
2995
|
+
}
|
|
2996
|
+
GetRowByIndex(index) {
|
|
2997
|
+
const dataSource = this._useExternalData ? this._data : this._allData;
|
|
2998
|
+
return dataSource[index];
|
|
2999
|
+
}
|
|
3000
|
+
GetState() {
|
|
3001
|
+
return {
|
|
3002
|
+
columns: this._columnStates.map(c => ({
|
|
3003
|
+
field: c.config.field,
|
|
3004
|
+
width: c.computedWidth,
|
|
3005
|
+
visible: c.visible,
|
|
3006
|
+
order: c.order
|
|
3007
|
+
})),
|
|
3008
|
+
sort: [...this._sortState],
|
|
3009
|
+
filters: [...this._filterState],
|
|
3010
|
+
selection: [...this._selectedKeys]
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
SetState(state) {
|
|
3014
|
+
for (const colState of state.columns) {
|
|
3015
|
+
const column = this._columnStates.find(c => c.config.field === colState.field);
|
|
3016
|
+
if (column) {
|
|
3017
|
+
column.computedWidth = colState.width;
|
|
3018
|
+
column.visible = colState.visible;
|
|
3019
|
+
column.order = colState.order;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
this._sortState = [...state.sort];
|
|
3023
|
+
for (const sort of this._sortState) {
|
|
3024
|
+
const column = this._columnStates.find(c => c.config.field === sort.field);
|
|
3025
|
+
if (column) {
|
|
3026
|
+
column.sortDirection = sort.direction;
|
|
3027
|
+
column.sortIndex = sort.index;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
this._filterState = [...state.filters];
|
|
3031
|
+
this._selectedKeys = [...state.selection];
|
|
3032
|
+
this.updateRowSelectionState();
|
|
3033
|
+
this.buildAgColumnDefs();
|
|
3034
|
+
this.cdr.detectChanges();
|
|
3035
|
+
}
|
|
3036
|
+
ResetState() {
|
|
3037
|
+
this.initializeColumnStates();
|
|
3038
|
+
this._sortState = [];
|
|
3039
|
+
this._filterState = [];
|
|
3040
|
+
this.ClearSelection();
|
|
3041
|
+
this.buildAgColumnDefs();
|
|
3042
|
+
this.cdr.detectChanges();
|
|
3043
|
+
}
|
|
3044
|
+
// ========================================
|
|
3045
|
+
// State Persistence
|
|
3046
|
+
// ========================================
|
|
3047
|
+
scheduleStateSave() {
|
|
3048
|
+
if (!this._stateKey)
|
|
3049
|
+
return;
|
|
3050
|
+
this.statesSaveSubject.next();
|
|
3051
|
+
}
|
|
3052
|
+
async loadPersistedState() {
|
|
3053
|
+
// TODO: Load state from User Settings entity
|
|
3054
|
+
}
|
|
3055
|
+
// ========================================
|
|
3056
|
+
// Toolbar Click Handlers
|
|
3057
|
+
// ========================================
|
|
3058
|
+
onAddClick() {
|
|
3059
|
+
// Emit legacy events for backward compatibility
|
|
3060
|
+
this.AddRequested.emit();
|
|
3061
|
+
this.NewButtonClick.emit();
|
|
3062
|
+
// Emit navigation events based on CreateRecordMode
|
|
3063
|
+
if (this._entityInfo) {
|
|
3064
|
+
if (this._createRecordMode === 'Dialog') {
|
|
3065
|
+
this.NewRecordDialogRequested.emit({
|
|
3066
|
+
entityInfo: this._entityInfo,
|
|
3067
|
+
defaultValues: this._newRecordValues
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
else {
|
|
3071
|
+
this.NewRecordTabRequested.emit({
|
|
3072
|
+
entityInfo: this._entityInfo,
|
|
3073
|
+
defaultValues: this._newRecordValues
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
onDeleteClick() {
|
|
3079
|
+
const selectedRows = this.GetSelectedRows();
|
|
3080
|
+
if (selectedRows.length > 0) {
|
|
3081
|
+
this.DeleteRequested.emit(selectedRows);
|
|
3082
|
+
this.DeleteButtonClick.emit(selectedRows);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
onExportClick() {
|
|
3086
|
+
this.ExportRequested.emit();
|
|
3087
|
+
this.ExportButtonClick.emit();
|
|
3088
|
+
// Show the export dialog
|
|
3089
|
+
this.showExportDialogForCurrentData();
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Shows the export dialog for the current grid data
|
|
3093
|
+
*/
|
|
3094
|
+
showExportDialogForCurrentData() {
|
|
3095
|
+
const data = this.getExportData();
|
|
3096
|
+
const columns = this.getExportColumns();
|
|
3097
|
+
const fileName = this.getDefaultExportFileName();
|
|
3098
|
+
this.exportDialogConfig = {
|
|
3099
|
+
data,
|
|
3100
|
+
columns,
|
|
3101
|
+
defaultFileName: fileName,
|
|
3102
|
+
availableFormats: ['excel', 'csv', 'json'],
|
|
3103
|
+
defaultFormat: 'excel',
|
|
3104
|
+
showSamplingOptions: true,
|
|
3105
|
+
defaultSamplingMode: 'all',
|
|
3106
|
+
dialogTitle: `Export ${this._entityInfo?.Name || 'Data'}`
|
|
3107
|
+
};
|
|
3108
|
+
this.showExportDialog = true;
|
|
3109
|
+
this.cdr.detectChanges();
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* Handle export dialog close
|
|
3113
|
+
*/
|
|
3114
|
+
onExportDialogClosed(result) {
|
|
3115
|
+
this.showExportDialog = false;
|
|
3116
|
+
this.exportDialogConfig = null;
|
|
3117
|
+
this.cdr.detectChanges();
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Export grid data directly without showing dialog.
|
|
3121
|
+
* Use this for programmatic export with specific options.
|
|
3122
|
+
* @param options Export options (format, sampling, etc.)
|
|
3123
|
+
* @param download If true, automatically downloads the file. Default true.
|
|
3124
|
+
* @returns Export result with data buffer and metadata
|
|
3125
|
+
*/
|
|
3126
|
+
async Export(options, download = true) {
|
|
3127
|
+
const data = this.getExportData();
|
|
3128
|
+
const columns = this.getExportColumns();
|
|
3129
|
+
const fileName = options?.fileName || this.getDefaultExportFileName();
|
|
3130
|
+
const exportOptions = {
|
|
3131
|
+
format: 'excel',
|
|
3132
|
+
fileName,
|
|
3133
|
+
columns,
|
|
3134
|
+
includeHeaders: true,
|
|
3135
|
+
...options
|
|
3136
|
+
};
|
|
3137
|
+
const result = await this.exportService.export(data, exportOptions);
|
|
3138
|
+
if (result.success && download) {
|
|
3139
|
+
this.exportService.downloadResult(result);
|
|
3140
|
+
}
|
|
3141
|
+
return result;
|
|
3142
|
+
}
|
|
3143
|
+
/**
|
|
3144
|
+
* Export to Excel format directly (convenience method)
|
|
3145
|
+
* @param download If true, automatically downloads the file. Default true.
|
|
3146
|
+
*/
|
|
3147
|
+
async ExportToExcel(download = true) {
|
|
3148
|
+
return this.Export({ format: 'excel' }, download);
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* Export to CSV format directly (convenience method)
|
|
3152
|
+
* @param download If true, automatically downloads the file. Default true.
|
|
3153
|
+
*/
|
|
3154
|
+
async ExportToCSV(download = true) {
|
|
3155
|
+
return this.Export({ format: 'csv' }, download);
|
|
3156
|
+
}
|
|
3157
|
+
/**
|
|
3158
|
+
* Export to JSON format directly (convenience method)
|
|
3159
|
+
* @param download If true, automatically downloads the file. Default true.
|
|
3160
|
+
*/
|
|
3161
|
+
async ExportToJSON(download = true) {
|
|
3162
|
+
return this.Export({ format: 'json' }, download);
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Get the current grid data formatted for export
|
|
3166
|
+
*/
|
|
3167
|
+
getExportData() {
|
|
3168
|
+
// Convert BaseEntity[] to plain objects for export
|
|
3169
|
+
return this.rowData.map(row => {
|
|
3170
|
+
if (row instanceof BaseEntity) {
|
|
3171
|
+
return row.GetAll();
|
|
3172
|
+
}
|
|
3173
|
+
return row;
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
/**
|
|
3177
|
+
* Get column definitions for export based on current grid columns
|
|
3178
|
+
*/
|
|
3179
|
+
getExportColumns() {
|
|
3180
|
+
if (!this._entityInfo) {
|
|
3181
|
+
// Fallback: use AG Grid column definitions
|
|
3182
|
+
return this.agColumnDefs
|
|
3183
|
+
.filter(col => col.field && !col.hide)
|
|
3184
|
+
.map(col => ({
|
|
3185
|
+
name: col.field,
|
|
3186
|
+
displayName: (col.headerName || col.field)
|
|
3187
|
+
}));
|
|
3188
|
+
}
|
|
3189
|
+
// Use entity field info for better column metadata
|
|
3190
|
+
return this._columns
|
|
3191
|
+
.filter(col => col.visible !== false)
|
|
3192
|
+
.map(col => {
|
|
3193
|
+
const field = this._entityInfo?.Fields.find(f => f.Name === col.field);
|
|
3194
|
+
return {
|
|
3195
|
+
name: col.field,
|
|
3196
|
+
displayName: col.title || field?.DisplayName || col.field,
|
|
3197
|
+
dataType: this.mapFieldTypeToExportType(field?.Type),
|
|
3198
|
+
width: typeof col.width === 'number' ? col.width : undefined
|
|
3199
|
+
};
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
/**
|
|
3203
|
+
* Map MemberJunction field types to export column types
|
|
3204
|
+
*/
|
|
3205
|
+
mapFieldTypeToExportType(fieldType) {
|
|
3206
|
+
if (!fieldType)
|
|
3207
|
+
return 'string';
|
|
3208
|
+
const type = fieldType.toLowerCase();
|
|
3209
|
+
if (type.includes('int') || type.includes('decimal') || type.includes('float') || type.includes('numeric')) {
|
|
3210
|
+
return 'number';
|
|
3211
|
+
}
|
|
3212
|
+
if (type.includes('date') || type.includes('time')) {
|
|
3213
|
+
return 'date';
|
|
3214
|
+
}
|
|
3215
|
+
if (type.includes('bit') || type.includes('bool')) {
|
|
3216
|
+
return 'boolean';
|
|
3217
|
+
}
|
|
3218
|
+
if (type.includes('money') || type.includes('currency')) {
|
|
3219
|
+
return 'currency';
|
|
3220
|
+
}
|
|
3221
|
+
return 'string';
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Generate default file name for export
|
|
3225
|
+
*/
|
|
3226
|
+
getDefaultExportFileName() {
|
|
3227
|
+
const entityName = this._entityInfo?.Name || 'export';
|
|
3228
|
+
const timestamp = new Date().toISOString().slice(0, 10);
|
|
3229
|
+
return `${entityName}_${timestamp}`;
|
|
3230
|
+
}
|
|
3231
|
+
onRefreshClick() {
|
|
3232
|
+
this.RefreshButtonClick.emit();
|
|
3233
|
+
this.Refresh();
|
|
3234
|
+
}
|
|
3235
|
+
onCompareClick() {
|
|
3236
|
+
const selectedRows = this.GetSelectedRows();
|
|
3237
|
+
if (selectedRows.length >= 2) {
|
|
3238
|
+
// Emit legacy event for backward compatibility
|
|
3239
|
+
this.CompareButtonClick.emit(selectedRows);
|
|
3240
|
+
// Emit new structured event for Explorer dialogs
|
|
3241
|
+
if (this._entityInfo) {
|
|
3242
|
+
this.CompareRecordsRequested.emit({
|
|
3243
|
+
entityInfo: this._entityInfo,
|
|
3244
|
+
records: selectedRows
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
onMergeClick() {
|
|
3250
|
+
const selectedRows = this.GetSelectedRows();
|
|
3251
|
+
if (selectedRows.length >= 2) {
|
|
3252
|
+
// Emit legacy event for backward compatibility
|
|
3253
|
+
this.MergeButtonClick.emit(selectedRows);
|
|
3254
|
+
// Emit new structured event for Explorer dialogs
|
|
3255
|
+
if (this._entityInfo) {
|
|
3256
|
+
this.MergeRecordsRequested.emit({
|
|
3257
|
+
entityInfo: this._entityInfo,
|
|
3258
|
+
records: selectedRows
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
onAddToListClick() {
|
|
3264
|
+
const selectedRows = this.GetSelectedRows();
|
|
3265
|
+
if (selectedRows.length > 0) {
|
|
3266
|
+
// Emit legacy event for backward compatibility
|
|
3267
|
+
this.AddToListButtonClick.emit(selectedRows);
|
|
3268
|
+
// Emit new structured event with record IDs for list management
|
|
3269
|
+
if (this._entityInfo) {
|
|
3270
|
+
const recordIds = selectedRows.map(r => {
|
|
3271
|
+
return r.PrimaryKey?.ToConcatenatedString() || String(r.Get(this._keyField));
|
|
3272
|
+
});
|
|
3273
|
+
this.AddToListRequested.emit({
|
|
3274
|
+
entityInfo: this._entityInfo,
|
|
3275
|
+
records: selectedRows,
|
|
3276
|
+
recordIds
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
onDuplicateSearchClick() {
|
|
3282
|
+
const selectedRows = this.GetSelectedRows();
|
|
3283
|
+
if (selectedRows.length >= 2) {
|
|
3284
|
+
// Emit legacy event for backward compatibility
|
|
3285
|
+
this.DuplicateSearchButtonClick.emit(selectedRows);
|
|
3286
|
+
// Emit new structured event for Explorer dialogs
|
|
3287
|
+
if (this._entityInfo) {
|
|
3288
|
+
this.DuplicateSearchRequested.emit({
|
|
3289
|
+
entityInfo: this._entityInfo,
|
|
3290
|
+
records: selectedRows
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
onCommunicationClick() {
|
|
3296
|
+
const selectedRows = this.GetSelectedRows();
|
|
3297
|
+
if (selectedRows.length > 0) {
|
|
3298
|
+
// Emit legacy event for backward compatibility
|
|
3299
|
+
this.CommunicationButtonClick.emit(selectedRows);
|
|
3300
|
+
// Emit new structured event for Explorer dialogs
|
|
3301
|
+
if (this._entityInfo) {
|
|
3302
|
+
this.CommunicationRequested.emit({
|
|
3303
|
+
entityInfo: this._entityInfo,
|
|
3304
|
+
records: selectedRows,
|
|
3305
|
+
viewParams: this._params
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
onColumnChooserClick() {
|
|
3311
|
+
// TODO: Implement column chooser dialog
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Handles entity action click from the overflow menu
|
|
3315
|
+
*/
|
|
3316
|
+
onEntityActionClick(action) {
|
|
3317
|
+
if (!this._entityInfo)
|
|
3318
|
+
return;
|
|
3319
|
+
this.EntityActionRequested.emit({
|
|
3320
|
+
entityInfo: this._entityInfo,
|
|
3321
|
+
action,
|
|
3322
|
+
selectedRecords: this.GetSelectedRows()
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
/**
|
|
3326
|
+
* Checks if an entity action is currently enabled based on selection requirements
|
|
3327
|
+
*/
|
|
3328
|
+
isEntityActionEnabled(action) {
|
|
3329
|
+
if (!action.requiresSelection)
|
|
3330
|
+
return true;
|
|
3331
|
+
const selectedCount = this._selectedKeys.length;
|
|
3332
|
+
if (selectedCount === 0)
|
|
3333
|
+
return false;
|
|
3334
|
+
if (action.minSelectedRecords && selectedCount < action.minSelectedRecords)
|
|
3335
|
+
return false;
|
|
3336
|
+
if (action.maxSelectedRecords && selectedCount > action.maxSelectedRecords)
|
|
3337
|
+
return false;
|
|
3338
|
+
return true;
|
|
3339
|
+
}
|
|
3340
|
+
// ========================================
|
|
3341
|
+
// Overflow Menu Methods
|
|
3342
|
+
// ========================================
|
|
3343
|
+
toggleOverflowMenu() {
|
|
3344
|
+
this.showOverflowMenu = !this.showOverflowMenu;
|
|
3345
|
+
this.cdr.detectChanges();
|
|
3346
|
+
// Add click outside listener when menu is open
|
|
3347
|
+
if (this.showOverflowMenu) {
|
|
3348
|
+
setTimeout(() => {
|
|
3349
|
+
document.addEventListener('click', this.handleOutsideClick);
|
|
3350
|
+
}, 0);
|
|
3351
|
+
}
|
|
3352
|
+
else {
|
|
3353
|
+
document.removeEventListener('click', this.handleOutsideClick);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
closeOverflowMenu() {
|
|
3357
|
+
this.showOverflowMenu = false;
|
|
3358
|
+
document.removeEventListener('click', this.handleOutsideClick);
|
|
3359
|
+
this.cdr.detectChanges();
|
|
3360
|
+
}
|
|
3361
|
+
handleOutsideClick = () => {
|
|
3362
|
+
this.closeOverflowMenu();
|
|
3363
|
+
};
|
|
3364
|
+
// Overflow menu visibility helpers
|
|
3365
|
+
get hasOverflowMenuItems() {
|
|
3366
|
+
return this.showExportInOverflow ||
|
|
3367
|
+
this.showColumnChooserInOverflow ||
|
|
3368
|
+
(this._showEntityActionButtons && this._entityActions.length > 0) ||
|
|
3369
|
+
this.hasSelectionDependentOverflowActions;
|
|
3370
|
+
}
|
|
3371
|
+
get showExportInOverflow() {
|
|
3372
|
+
// Export is in overflow when it's not shown as a primary button
|
|
3373
|
+
return !this.ShowExportButton && !!this.ToolbarConfig.showExport;
|
|
3374
|
+
}
|
|
3375
|
+
get showColumnChooserInOverflow() {
|
|
3376
|
+
return this.AllowColumnToggle && !this.ToolbarConfig.showColumnChooser;
|
|
3377
|
+
}
|
|
3378
|
+
get hasSelectionDependentOverflowActions() {
|
|
3379
|
+
return this.showCommunicationInOverflow;
|
|
3380
|
+
}
|
|
3381
|
+
get showCommunicationInOverflow() {
|
|
3382
|
+
// Communication is in overflow when it's not shown as a primary button
|
|
3383
|
+
return !this.ShowCommunicationButton && this.HasSelection;
|
|
3384
|
+
}
|
|
3385
|
+
// ========================================
|
|
3386
|
+
// Toolbar Button State Helpers
|
|
3387
|
+
// ========================================
|
|
3388
|
+
get HasSelection() {
|
|
3389
|
+
return this._selectedKeys.length > 0;
|
|
3390
|
+
}
|
|
3391
|
+
get HasMultipleSelection() {
|
|
3392
|
+
return this._selectedKeys.length >= 2;
|
|
3393
|
+
}
|
|
3394
|
+
// ========================================
|
|
3395
|
+
// CSS Helpers
|
|
3396
|
+
// ========================================
|
|
3397
|
+
get gridContainerClasses() {
|
|
3398
|
+
const classes = ['mj-grid-container'];
|
|
3399
|
+
classes.push(`grid-lines-${this._gridLines}`);
|
|
3400
|
+
if (this._striped)
|
|
3401
|
+
classes.push('grid-striped');
|
|
3402
|
+
// Add visual config classes
|
|
3403
|
+
const vc = this.effectiveVisualConfig;
|
|
3404
|
+
classes.push(`header-style-${vc.headerStyle}`);
|
|
3405
|
+
if (vc.headerShadow)
|
|
3406
|
+
classes.push('header-shadow');
|
|
3407
|
+
if (vc.alternateRows)
|
|
3408
|
+
classes.push(`alternate-rows-${vc.alternateRowContrast}`);
|
|
3409
|
+
if (vc.hoverTransitions)
|
|
3410
|
+
classes.push('hover-transitions');
|
|
3411
|
+
classes.push(`cell-padding-${vc.cellPadding}`);
|
|
3412
|
+
if (vc.checkboxStyle !== 'default')
|
|
3413
|
+
classes.push(`checkbox-style-${vc.checkboxStyle}`);
|
|
3414
|
+
return classes;
|
|
3415
|
+
}
|
|
3416
|
+
/**
|
|
3417
|
+
* Apply visual configuration by setting CSS custom properties
|
|
3418
|
+
*/
|
|
3419
|
+
applyVisualConfig() {
|
|
3420
|
+
const vc = this.effectiveVisualConfig;
|
|
3421
|
+
const el = this.elementRef.nativeElement;
|
|
3422
|
+
// Set CSS custom properties for dynamic values
|
|
3423
|
+
if (vc.headerBackground) {
|
|
3424
|
+
el.style.setProperty('--grid-header-bg', vc.headerBackground);
|
|
3425
|
+
}
|
|
3426
|
+
if (vc.headerTextColor) {
|
|
3427
|
+
el.style.setProperty('--grid-header-text', vc.headerTextColor);
|
|
3428
|
+
}
|
|
3429
|
+
if (vc.selectionIndicatorColor) {
|
|
3430
|
+
el.style.setProperty('--grid-selection-indicator-color', vc.selectionIndicatorColor);
|
|
3431
|
+
}
|
|
3432
|
+
if (vc.selectionIndicatorWidth) {
|
|
3433
|
+
el.style.setProperty('--grid-selection-indicator-width', `${vc.selectionIndicatorWidth}px`);
|
|
3434
|
+
}
|
|
3435
|
+
if (vc.selectionBackground) {
|
|
3436
|
+
el.style.setProperty('--grid-row-selected-bg', vc.selectionBackground);
|
|
3437
|
+
}
|
|
3438
|
+
if (vc.checkboxColor) {
|
|
3439
|
+
el.style.setProperty('--grid-checkbox-color', vc.checkboxColor);
|
|
3440
|
+
}
|
|
3441
|
+
if (vc.borderRadius !== undefined) {
|
|
3442
|
+
el.style.setProperty('--grid-border-radius', `${vc.borderRadius}px`);
|
|
3443
|
+
}
|
|
3444
|
+
if (vc.accentColor) {
|
|
3445
|
+
el.style.setProperty('--grid-accent-color', vc.accentColor);
|
|
3446
|
+
el.style.setProperty('--grid-sort-indicator-color', vc.accentColor);
|
|
3447
|
+
}
|
|
3448
|
+
if (vc.hoverTransitionDuration) {
|
|
3449
|
+
el.style.setProperty('--grid-hover-transition', `${vc.hoverTransitionDuration}ms`);
|
|
3450
|
+
}
|
|
3451
|
+
// Rebuild column defs if formatting options changed
|
|
3452
|
+
if (this._entityInfo) {
|
|
3453
|
+
this.buildAgColumnDefs();
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
get gridHeightStyle() {
|
|
3457
|
+
if (typeof this._height === 'number') {
|
|
3458
|
+
return `${this._height}px`;
|
|
3459
|
+
}
|
|
3460
|
+
return this._height === 'auto' ? '100%' : 'fit-content';
|
|
3461
|
+
}
|
|
3462
|
+
// ========================================
|
|
3463
|
+
// Toolbar Button Helpers
|
|
3464
|
+
// ========================================
|
|
3465
|
+
isButtonVisible(button) {
|
|
3466
|
+
if (button.visible === undefined)
|
|
3467
|
+
return true;
|
|
3468
|
+
if (typeof button.visible === 'boolean')
|
|
3469
|
+
return button.visible;
|
|
3470
|
+
if (typeof button.visible === 'function')
|
|
3471
|
+
return button.visible();
|
|
3472
|
+
return true;
|
|
3473
|
+
}
|
|
3474
|
+
isButtonDisabled(button) {
|
|
3475
|
+
if (button.disabled === undefined)
|
|
3476
|
+
return false;
|
|
3477
|
+
if (typeof button.disabled === 'boolean')
|
|
3478
|
+
return button.disabled;
|
|
3479
|
+
if (typeof button.disabled === 'function')
|
|
3480
|
+
return button.disabled();
|
|
3481
|
+
return false;
|
|
3482
|
+
}
|
|
3483
|
+
onToolbarButtonClick(button) {
|
|
3484
|
+
if (button.onClick) {
|
|
3485
|
+
button.onClick();
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
// ========================================
|
|
3489
|
+
// Public Getters
|
|
3490
|
+
// ========================================
|
|
3491
|
+
get VisibleRows() {
|
|
3492
|
+
return Array.from(this._rowDataMap.values());
|
|
3493
|
+
}
|
|
3494
|
+
get VisibleColumnStates() {
|
|
3495
|
+
return this._columnStates
|
|
3496
|
+
.filter(c => c.visible)
|
|
3497
|
+
.sort((a, b) => a.order - b.order);
|
|
3498
|
+
}
|
|
3499
|
+
static ɵfac = function EntityDataGridComponent_Factory(t) { return new (t || EntityDataGridComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.ExportService)); };
|
|
3500
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityDataGridComponent, selectors: [["mj-entity-data-grid"]], viewQuery: function EntityDataGridComponent_Query(rf, ctx) { if (rf & 1) {
|
|
3501
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
3502
|
+
} if (rf & 2) {
|
|
3503
|
+
let _t;
|
|
3504
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.gridContainer = _t.first);
|
|
3505
|
+
} }, inputs: { Params: "Params", AllowLoad: "AllowLoad", AutoRefreshOnParamsChange: "AutoRefreshOnParamsChange", PaginationMode: "PaginationMode", PageSize: "PageSize", CacheBlockSize: "CacheBlockSize", MaxBlocksInCache: "MaxBlocksInCache", Data: "Data", Columns: "Columns", GridState: "GridState", AllowColumnReorder: "AllowColumnReorder", AllowColumnResize: "AllowColumnResize", AllowColumnToggle: "AllowColumnToggle", ShowHeader: "ShowHeader", AllowSorting: "AllowSorting", AllowMultiSort: "AllowMultiSort", ServerSideSorting: "ServerSideSorting", AllowColumnFilters: "AllowColumnFilters", ShowSearch: "ShowSearch", SelectionMode: "SelectionMode", SelectedKeys: "SelectedKeys", KeyField: "KeyField", EditMode: "EditMode", AllowAdd: "AllowAdd", AllowDelete: "AllowDelete", Height: "Height", RowHeight: "RowHeight", VirtualScroll: "VirtualScroll", ShowRowNumbers: "ShowRowNumbers", Striped: "Striped", GridLines: "GridLines", VisualConfig: "VisualConfig", ShowToolbar: "ShowToolbar", ToolbarConfig: "ToolbarConfig", StateKey: "StateKey", AutoPersistState: "AutoPersistState", StatePersistDebounce: "StatePersistDebounce", RefreshDebounce: "RefreshDebounce", FilterText: "FilterText", ShowNewButton: "ShowNewButton", ShowRefreshButton: "ShowRefreshButton", ShowExportButton: "ShowExportButton", ShowDeleteButton: "ShowDeleteButton", ShowCompareButton: "ShowCompareButton", ShowMergeButton: "ShowMergeButton", ShowAddToListButton: "ShowAddToListButton", ShowDuplicateSearchButton: "ShowDuplicateSearchButton", ShowCommunicationButton: "ShowCommunicationButton", AutoNavigate: "AutoNavigate", NavigateOnDoubleClick: "NavigateOnDoubleClick", CreateRecordMode: "CreateRecordMode", NewRecordValues: "NewRecordValues", ShowEntityActionButtons: "ShowEntityActionButtons", EntityActions: "EntityActions" }, outputs: { BeforeRowSelect: "BeforeRowSelect", AfterRowSelect: "AfterRowSelect", BeforeRowDeselect: "BeforeRowDeselect", AfterRowDeselect: "AfterRowDeselect", SelectionChange: "SelectionChange", BeforeRowClick: "BeforeRowClick", AfterRowClick: "AfterRowClick", BeforeRowDoubleClick: "BeforeRowDoubleClick", AfterRowDoubleClick: "AfterRowDoubleClick", BeforeCellEdit: "BeforeCellEdit", AfterCellEditBegin: "AfterCellEditBegin", BeforeCellEditCommit: "BeforeCellEditCommit", AfterCellEditCommit: "AfterCellEditCommit", BeforeCellEditCancel: "BeforeCellEditCancel", AfterCellEditCancel: "AfterCellEditCancel", BeforeRowSave: "BeforeRowSave", AfterRowSave: "AfterRowSave", BeforeRowDelete: "BeforeRowDelete", AfterRowDelete: "AfterRowDelete", BeforeDataLoad: "BeforeDataLoad", AfterDataLoad: "AfterDataLoad", BeforeDataRefresh: "BeforeDataRefresh", AfterDataRefresh: "AfterDataRefresh", BeforeSort: "BeforeSort", AfterSort: "AfterSort", BeforeColumnReorder: "BeforeColumnReorder", AfterColumnReorder: "AfterColumnReorder", BeforeColumnResize: "BeforeColumnResize", AfterColumnResize: "AfterColumnResize", BeforeColumnVisibilityChange: "BeforeColumnVisibilityChange", AfterColumnVisibilityChange: "AfterColumnVisibilityChange", GridStateChanged: "GridStateChanged", AddRequested: "AddRequested", DeleteRequested: "DeleteRequested", ExportRequested: "ExportRequested", NewButtonClick: "NewButtonClick", RefreshButtonClick: "RefreshButtonClick", ExportButtonClick: "ExportButtonClick", DeleteButtonClick: "DeleteButtonClick", CompareButtonClick: "CompareButtonClick", MergeButtonClick: "MergeButtonClick", AddToListButtonClick: "AddToListButtonClick", DuplicateSearchButtonClick: "DuplicateSearchButtonClick", CommunicationButtonClick: "CommunicationButtonClick", NavigationRequested: "NavigationRequested", NewRecordDialogRequested: "NewRecordDialogRequested", NewRecordTabRequested: "NewRecordTabRequested", CompareRecordsRequested: "CompareRecordsRequested", MergeRecordsRequested: "MergeRecordsRequested", CommunicationRequested: "CommunicationRequested", DuplicateSearchRequested: "DuplicateSearchRequested", AddToListRequested: "AddToListRequested", LoadEntityActionsRequested: "LoadEntityActionsRequested", EntityActionRequested: "EntityActionRequested" }, decls: 10, vars: 12, consts: [["gridContainer", ""], ["class", "mj-grid-toolbar", 4, "ngIf"], [1, "mj-grid-content"], ["class", "mj-grid-loading-overlay", 4, "ngIf"], ["class", "mj-grid-error", 4, "ngIf"], ["class", "mj-grid-empty", 4, "ngIf"], ["class", "mj-ag-grid ag-theme-alpine", 3, "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowMultiSelectWithClick", "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", 4, "ngIf"], ["class", "mj-ag-grid ag-theme-alpine", 3, "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowMultiSelectWithClick", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize", "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", 4, "ngIf"], [3, "closed", "visible", "config"], [1, "mj-grid-toolbar"], [1, "toolbar-left"], ["class", "toolbar-search", 4, "ngIf"], [4, "ngFor", "ngForOf"], [1, "toolbar-center"], ["class", "row-count", 4, "ngIf"], ["class", "selection-count", 4, "ngIf"], [1, "toolbar-right"], ["class", "toolbar-button", "title", "Create new record", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Refresh data", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Export to Excel", 3, "click", 4, "ngIf"], ["class", "toolbar-button toolbar-button-danger", "title", "Delete selected records", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Compare selected records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Merge selected records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Add selected records to a list", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Search for duplicate records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Send message to selected records", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Add New", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Refresh", 3, "disabled", "click", 4, "ngIf"], ["class", "toolbar-button toolbar-button-danger", "title", "Delete Selected", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Export", 3, "click", 4, "ngIf"], ["class", "toolbar-button", "title", "Column Chooser", 3, "click", 4, "ngIf"], ["class", "toolbar-overflow", 3, "click", 4, "ngIf"], [1, "toolbar-search"], [1, "fa-solid", "fa-search", "search-icon"], ["type", "text", 1, "search-input", 3, "input", "placeholder", "value"], ["class", "search-clear", 3, "click", 4, "ngIf"], [1, "search-clear", 3, "click"], [1, "fa-solid", "fa-times"], ["class", "toolbar-button", 3, "class", "disabled", "title", "click", 4, "ngIf"], [1, "toolbar-button", 3, "click", "disabled", "title"], [3, "class", 4, "ngIf"], [4, "ngIf"], [1, "row-count"], [1, "selection-count"], ["title", "Create new record", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "button-text"], ["title", "Refresh data", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-arrows-rotate"], ["title", "Export to Excel", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-file-excel"], ["title", "Delete selected records", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], [1, "fa-solid", "fa-trash"], ["title", "Compare selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-compare"], ["title", "Merge selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-merge"], ["title", "Add selected records to a list", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-list-check"], ["title", "Search for duplicate records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-magnifying-glass-plus"], ["title", "Send message to selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-envelope"], ["title", "Add New", 1, "toolbar-button", 3, "click"], ["title", "Refresh", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-refresh"], ["title", "Delete Selected", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], ["title", "Export", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-download"], ["title", "Column Chooser", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-columns"], [1, "toolbar-overflow", 3, "click"], ["title", "More actions", 1, "toolbar-button", "overflow-trigger", 3, "click"], [1, "fa-solid", "fa-ellipsis-vertical"], ["class", "overflow-menu", 4, "ngIf"], [1, "overflow-menu"], ["class", "overflow-item", 3, "click", 4, "ngIf"], ["class", "overflow-divider", 4, "ngIf"], [1, "overflow-item", 3, "click"], [1, "overflow-divider"], [1, "overflow-section-label"], ["class", "overflow-item", 3, "disabled", "click", 4, "ngFor", "ngForOf"], [1, "overflow-item", 3, "click", "disabled"], [1, "mj-grid-loading-overlay"], ["text", "Loading..."], [1, "mj-grid-error"], [1, "fa-solid", "fa-exclamation-triangle"], [1, "error-retry", 3, "click"], [1, "mj-grid-empty"], [1, "fa-solid", "fa-inbox"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowMultiSelectWithClick"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowMultiSelectWithClick", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"]], template: function EntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
|
|
3506
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
3507
|
+
i0.ɵɵelementStart(0, "div", null, 0);
|
|
3508
|
+
i0.ɵɵtemplate(2, EntityDataGridComponent_div_2_Template, 24, 20, "div", 1);
|
|
3509
|
+
i0.ɵɵelementStart(3, "div", 2);
|
|
3510
|
+
i0.ɵɵtemplate(4, EntityDataGridComponent_div_4_Template, 2, 0, "div", 3)(5, EntityDataGridComponent_div_5_Template, 6, 1, "div", 4)(6, EntityDataGridComponent_div_6_Template, 4, 0, "div", 5)(7, EntityDataGridComponent_ag_grid_angular_7_Template, 1, 10, "ag-grid-angular", 6)(8, EntityDataGridComponent_ag_grid_angular_8_Template, 1, 14, "ag-grid-angular", 7);
|
|
3511
|
+
i0.ɵɵelementEnd()();
|
|
3512
|
+
i0.ɵɵelementStart(9, "mj-export-dialog", 8);
|
|
3513
|
+
i0.ɵɵlistener("closed", function EntityDataGridComponent_Template_mj_export_dialog_closed_9_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onExportDialogClosed($event)); });
|
|
3514
|
+
i0.ɵɵelementEnd();
|
|
3515
|
+
} if (rf & 2) {
|
|
3516
|
+
i0.ɵɵclassMap(ctx.gridContainerClasses.join(" "));
|
|
3517
|
+
i0.ɵɵstyleProp("height", ctx.gridHeightStyle);
|
|
3518
|
+
i0.ɵɵadvance(2);
|
|
3519
|
+
i0.ɵɵproperty("ngIf", ctx.ShowToolbar);
|
|
3520
|
+
i0.ɵɵadvance(2);
|
|
3521
|
+
i0.ɵɵproperty("ngIf", ctx.loading && ctx.rowData.length === 0);
|
|
3522
|
+
i0.ɵɵadvance();
|
|
3523
|
+
i0.ɵɵproperty("ngIf", ctx.errorMessage && !ctx.loading);
|
|
3524
|
+
i0.ɵɵadvance();
|
|
3525
|
+
i0.ɵɵproperty("ngIf", !ctx.loading && !ctx.errorMessage && ctx.rowData.length === 0);
|
|
3526
|
+
i0.ɵɵadvance();
|
|
3527
|
+
i0.ɵɵproperty("ngIf", !ctx.errorMessage && ctx.PaginationMode === "client" && (ctx.rowData.length > 0 || ctx.loading));
|
|
3528
|
+
i0.ɵɵadvance();
|
|
3529
|
+
i0.ɵɵproperty("ngIf", !ctx.errorMessage && ctx.PaginationMode === "infinite");
|
|
3530
|
+
i0.ɵɵadvance();
|
|
3531
|
+
i0.ɵɵproperty("visible", ctx.showExportDialog)("config", ctx.exportDialogConfig);
|
|
3532
|
+
} }, dependencies: [i2.NgForOf, i2.NgIf, i3.AgGridAngular, i4.LoadingComponent, i1.ExportDialogComponent], styles: ["\n\n\n\n\n[_nghost-%COMP%] {\n \n\n --grid-border-color: #e0e0e0;\n --grid-border-radius: 0px;\n --grid-background: #ffffff;\n\n \n\n --grid-header-bg: #fafafa;\n --grid-header-text: #333333;\n --grid-header-font-weight: 600;\n --grid-header-height: 40px;\n --grid-header-border-color: #e0e0e0;\n\n \n\n --grid-row-height: 40px;\n --grid-row-bg: #ffffff;\n --grid-row-bg-alt: #fafafa;\n --grid-row-hover-bg: #f5f5f5;\n --grid-row-selected-bg: #fff9e6;\n --grid-row-selected-hover-bg: #fff3cc;\n\n \n\n --grid-cell-padding: 8px 12px;\n --grid-cell-text: #333333;\n --grid-cell-border-color: #f0f0f0;\n\n \n\n --grid-checkbox-color: #2196F3;\n --grid-selection-indicator-color: #f9a825;\n\n \n\n --grid-edit-cell-bg: #ffffff;\n --grid-edit-cell-border: #2196F3;\n --grid-edit-cell-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n\n \n\n --grid-sort-indicator-color: #2196F3;\n\n \n\n --grid-toolbar-bg: #ffffff;\n --grid-toolbar-height: 48px;\n --grid-toolbar-border-color: #e0e0e0;\n\n \n\n --grid-loading-overlay-bg: rgba(255, 255, 255, 0.8);\n\n \n\n --grid-empty-text-color: #999999;\n --grid-empty-icon-color: #cccccc;\n\n display: block;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n}\n\n\n\n\n\n\n.mj-grid-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--grid-border-color);\n border-radius: var(--grid-border-radius);\n background: var(--grid-background);\n overflow: hidden;\n}\n\n\n\n\n\n\n.mj-grid-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: var(--grid-toolbar-height);\n padding: 0 12px;\n background: var(--grid-toolbar-bg);\n border-bottom: 1px solid var(--grid-toolbar-border-color);\n gap: 12px;\n}\n\n.toolbar-left[_ngcontent-%COMP%], \n.toolbar-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-center[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #666;\n font-size: 13px;\n}\n\n.toolbar-search[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.search-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 10px;\n color: #999;\n font-size: 13px;\n}\n\n.search-input[_ngcontent-%COMP%] {\n padding: 6px 30px 6px 32px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n width: 200px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.search-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--grid-selection-indicator-color);\n box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);\n}\n\n.search-clear[_ngcontent-%COMP%] {\n position: absolute;\n right: 6px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: #999;\n font-size: 12px;\n}\n\n.search-clear[_ngcontent-%COMP%]:hover {\n color: #666;\n}\n\n.toolbar-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n color: #333;\n transition: background-color 0.2s, border-color 0.2s;\n}\n\n.toolbar-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #eee;\n border-color: #ccc;\n}\n\n.toolbar-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.toolbar-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n.toolbar-button-danger[_ngcontent-%COMP%] {\n color: #d32f2f;\n}\n\n.toolbar-button-danger[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #ffebee;\n border-color: #ffcdd2;\n}\n\n.row-count[_ngcontent-%COMP%], \n.selection-count[_ngcontent-%COMP%] {\n font-weight: 500;\n}\n\n\n\n\n\n\n.mj-grid-content[_ngcontent-%COMP%] {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n\n.mj-grid-scroll-container[_ngcontent-%COMP%] {\n height: 100%;\n overflow: auto;\n}\n\n\n\n\n\n\n.mj-grid-loading-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--grid-loading-overlay-bg);\n z-index: 10;\n}\n\n\n\n\n\n\n.mj-grid-error[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: #d32f2f;\n gap: 12px;\n}\n\n.mj-grid-error[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 32px;\n}\n\n.error-retry[_ngcontent-%COMP%] {\n padding: 8px 16px;\n background: #d32f2f;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n}\n\n.error-retry[_ngcontent-%COMP%]:hover {\n background: #c62828;\n}\n\n\n\n\n\n\n.mj-grid-empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--grid-empty-text-color);\n gap: 12px;\n}\n\n.mj-grid-empty[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--grid-empty-icon-color);\n}\n\n\n\n\n\n\n.mj-grid-header[_ngcontent-%COMP%] {\n display: flex;\n min-height: var(--grid-header-height);\n background: var(--grid-header-bg);\n border-bottom: 2px solid var(--grid-header-border-color);\n position: sticky;\n top: 0;\n z-index: 5;\n}\n\n.mj-grid-header-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n font-weight: var(--grid-header-font-weight);\n color: var(--grid-header-text);\n font-size: 13px;\n user-select: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right: 1px solid var(--grid-cell-border-color);\n flex-shrink: 0;\n}\n\n.mj-grid-header-cell[_ngcontent-%COMP%]:last-child {\n border-right: none;\n}\n\n.mj-grid-header-cell.sortable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.mj-grid-header-cell.sortable[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.header-text[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sort-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n margin-left: 6px;\n color: var(--grid-sort-indicator-color);\n}\n\n.sort-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n.sort-index[_ngcontent-%COMP%] {\n font-size: 10px;\n margin-left: 2px;\n font-weight: normal;\n}\n\n\n\n\n\n\n.mj-grid-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: stretch;\n background: var(--grid-row-bg);\n transition: background-color 0.15s;\n cursor: default;\n}\n\n.mj-grid-row[_ngcontent-%COMP%]:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-alt[_ngcontent-%COMP%] {\n background: var(--grid-row-bg-alt);\n}\n\n.mj-grid-row.grid-row-alt[_ngcontent-%COMP%]:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-selected[_ngcontent-%COMP%] {\n background: var(--grid-row-selected-bg);\n}\n\n.mj-grid-row.grid-row-selected[_ngcontent-%COMP%]:hover {\n background: var(--grid-row-selected-hover-bg);\n}\n\n.mj-grid-row.grid-row-editing[_ngcontent-%COMP%] {\n background: #fffde7;\n}\n\n.mj-grid-row.grid-row-dirty[_ngcontent-%COMP%] {\n border-left: 3px solid #ff9800;\n}\n\n\n\n\n\n\n.mj-grid-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n color: var(--grid-cell-text);\n font-size: 13px;\n overflow: hidden;\n border-right: 1px solid transparent;\n flex-shrink: 0;\n}\n\n.mj-grid-cell[_ngcontent-%COMP%]:last-child {\n border-right: none;\n}\n\n\n\n.grid-lines-horizontal[_ngcontent-%COMP%] .mj-grid-row[_ngcontent-%COMP%] {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-vertical[_ngcontent-%COMP%] .mj-grid-cell[_ngcontent-%COMP%] {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both[_ngcontent-%COMP%] .mj-grid-row[_ngcontent-%COMP%] {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both[_ngcontent-%COMP%] .mj-grid-cell[_ngcontent-%COMP%] {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n\n\n.mj-grid-cell.align-left[_ngcontent-%COMP%] {\n justify-content: flex-start;\n}\n\n.mj-grid-cell.align-center[_ngcontent-%COMP%] {\n justify-content: center;\n}\n\n.mj-grid-cell.align-right[_ngcontent-%COMP%] {\n justify-content: flex-end;\n}\n\n.cell-content[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n\n.row-number-cell[_ngcontent-%COMP%] {\n width: 50px;\n min-width: 50px;\n max-width: 50px;\n justify-content: center;\n color: #999;\n font-size: 12px;\n background: var(--grid-header-bg);\n}\n\n.checkbox-cell[_ngcontent-%COMP%] {\n width: 40px;\n min-width: 40px;\n max-width: 40px;\n justify-content: center;\n}\n\n.checkbox-cell[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--grid-checkbox-color);\n}\n\n\n\n\n\n\n.mj-grid-virtual-spacer[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n\n\n\n@media (max-width: 768px) {\n .mj-grid-toolbar[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n padding: 8px;\n }\n\n .toolbar-search[_ngcontent-%COMP%] {\n order: 3;\n width: 100%;\n margin-top: 8px;\n }\n\n .search-input[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .toolbar-center[_ngcontent-%COMP%] {\n order: 2;\n }\n\n \n\n .toolbar-button[_ngcontent-%COMP%] .button-text[_ngcontent-%COMP%] {\n display: none;\n }\n}\n\n\n\n\n\n\n.toolbar-button[_ngcontent-%COMP%] .button-text[_ngcontent-%COMP%] {\n font-size: 13px;\n}\n\n\n\n\n\n\n.toolbar-overflow[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.overflow-trigger[_ngcontent-%COMP%] {\n padding: 6px 8px !important;\n}\n\n.overflow-trigger[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n\n.overflow-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 220px;\n background: #ffffff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);\n z-index: 1000;\n overflow: hidden;\n}\n\n.overflow-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 10px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n text-align: left;\n gap: 12px;\n transition: background-color 0.15s;\n}\n\n.overflow-item[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #f5f5f5;\n}\n\n.overflow-item[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.overflow-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 18px;\n font-size: 14px;\n color: #666;\n text-align: center;\n}\n\n.overflow-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.overflow-divider[_ngcontent-%COMP%] {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.overflow-section-label[_ngcontent-%COMP%] {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: #999;\n letter-spacing: 0.5px;\n}\n\n\n\n.overflow-item.action-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196F3;\n}\n\n\n\n\n\n\n .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n padding: 0 1px;\n}\n\n\n\n\n\n\n.mj-ag-grid[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n}\n\n .ag-theme-alpine {\n \n\n --ag-row-hover-color: var(--grid-row-hover-bg);\n --ag-selected-row-background-color: var(--grid-row-selected-bg);\n --ag-header-background-color: var(--grid-header-bg);\n\n \n\n --ag-range-selection-background-color: rgba(249, 168, 37, 0.15);\n --ag-range-selection-border-color: #f9a825;\n\n \n\n --ag-borders: none;\n --ag-row-border-color: var(--grid-cell-border-color);\n}\n\n\n\n .ag-row-selected {\n box-shadow: inset 4px 0 0 0 #f9a825;\n}\n\n\n\n .ag-theme-alpine .ag-checkbox-input-wrapper {\n width: 18px;\n height: 18px;\n}\n\n .ag-theme-alpine .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--grid-checkbox-color, #2196F3);\n}\n\n\n\n .ag-theme-alpine .ag-row:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f0f7ff);\n}\n\n\n\n\n\n\n\n\n.header-style-flat[_ngcontent-%COMP%] .ag-header {\n background: var(--grid-header-bg, #fafafa);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n\n\n.header-style-elevated[_ngcontent-%COMP%] .ag-header {\n background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n.header-style-elevated.header-shadow[_ngcontent-%COMP%] .ag-header {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n\n\n.header-style-gradient[_ngcontent-%COMP%] .ag-header {\n background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);\n border-bottom: none;\n}\n\n.header-style-gradient.header-shadow[_ngcontent-%COMP%] .ag-header {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header {\n background: linear-gradient(180deg, #37474f 0%, #263238 100%);\n border-bottom: none;\n}\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header-cell-text {\n color: #ffffff;\n font-weight: 600;\n}\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header-icon {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.header-style-bold[_ngcontent-%COMP%] .ag-header-cell:hover {\n background: rgba(255, 255, 255, 0.1);\n}\n\n\n\n .ag-header-cell-sorted-asc .ag-icon-asc, \n .ag-header-cell-sorted-desc .ag-icon-desc {\n color: var(--grid-accent-color, var(--grid-sort-indicator-color, #2196F3));\n}\n\n\n\n\n\n\n\n\n.alternate-rows-subtle[_ngcontent-%COMP%] .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.015);\n}\n\n.alternate-rows-subtle[_ngcontent-%COMP%] .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n\n\n.alternate-rows-medium[_ngcontent-%COMP%] .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.025);\n}\n\n.alternate-rows-medium[_ngcontent-%COMP%] .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n\n\n.alternate-rows-strong[_ngcontent-%COMP%] .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.alternate-rows-strong[_ngcontent-%COMP%] .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n\n\n\n\n\n.hover-transitions[_ngcontent-%COMP%] .ag-row {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n.hover-transitions[_ngcontent-%COMP%] .ag-cell {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n\n\n\n\n\n.cell-padding-compact[_ngcontent-%COMP%] .ag-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-compact[_ngcontent-%COMP%] .ag-header-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-normal[_ngcontent-%COMP%] .ag-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-normal[_ngcontent-%COMP%] .ag-header-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-comfortable[_ngcontent-%COMP%] .ag-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n.cell-padding-comfortable[_ngcontent-%COMP%] .ag-header-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n\n\n\n\n\n\n\n.checkbox-style-rounded[_ngcontent-%COMP%] .ag-checkbox-input-wrapper {\n border-radius: 4px;\n}\n\n.checkbox-style-rounded[_ngcontent-%COMP%] .ag-checkbox-input-wrapper::after {\n border-radius: 3px;\n}\n\n\n\n.checkbox-style-filled[_ngcontent-%COMP%] .ag-checkbox-input-wrapper.ag-checked {\n background-color: var(--grid-checkbox-color, #2196F3);\n border-color: var(--grid-checkbox-color, #2196F3);\n}\n\n.checkbox-style-filled[_ngcontent-%COMP%] .ag-checkbox-input-wrapper.ag-checked::after {\n color: #ffffff;\n}\n\n\n\n\n\n\n\n\n .cell-align-right {\n text-align: right;\n justify-content: flex-end;\n}\n\n .header-align-right .ag-header-cell-label {\n justify-content: flex-end;\n}\n\n\n\n .cell-empty {\n color: #bdbdbd;\n font-style: normal;\n}\n\n\n\n .cell-boolean-true {\n color: #43a047;\n font-size: 14px;\n}\n\n .cell-boolean-false {\n color: #bdbdbd;\n font-size: 14px;\n}\n\n\n\n .cell-link {\n color: var(--grid-accent-color, #2196F3);\n text-decoration: none;\n transition: color 0.15s;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n\n .cell-link:hover {\n color: #1976D2;\n text-decoration: underline;\n}\n\n\n\n .cell-email {\n font-family: inherit;\n font-size: 13px;\n}\n\n\n\n .cell-url {\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n font-size: 13px;\n}\n\n\n\n .cell-phone {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n}\n\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n\n.skeleton-row[_ngcontent-%COMP%] {\n display: flex;\n height: 40px;\n align-items: center;\n padding: 0 12px;\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n.skeleton-cell[_ngcontent-%COMP%] {\n height: 16px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n #f0f0f0 0px,\n #e8e8e8 40px,\n #f0f0f0 80px\n );\n background-size: 200px 100%;\n animation: _ngcontent-%COMP%_skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-cell-short[_ngcontent-%COMP%] {\n width: 60px;\n}\n\n.skeleton-cell-medium[_ngcontent-%COMP%] {\n width: 120px;\n}\n\n.skeleton-cell-long[_ngcontent-%COMP%] {\n width: 180px;\n}\n\n\n\n .ag-header-select-all {\n margin-right: 0;\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-row {\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n .ag-theme-alpine .ag-row:last-child {\n border-bottom: none;\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-cell-focus {\n border: none !important;\n outline: none !important;\n}\n\n .ag-theme-alpine .ag-header-cell:focus {\n outline: 2px solid var(--grid-accent-color, #2196F3);\n outline-offset: -2px;\n}\n\n\n\n\n\n\n .ag-body-viewport::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n .ag-body-viewport::-webkit-scrollbar-track {\n background: #f5f5f5;\n border-radius: 4px;\n}\n\n .ag-body-viewport::-webkit-scrollbar-thumb {\n background: #c0c0c0;\n border-radius: 4px;\n}\n\n .ag-body-viewport::-webkit-scrollbar-thumb:hover {\n background: #a0a0a0;\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-pinned-left-cols-container {\n border-right: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n .ag-theme-alpine .ag-pinned-right-cols-container {\n border-left: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n\n\n\n\n\n .ag-theme-alpine .ag-header-cell {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: #546e7a;\n}\n\n .ag-theme-alpine .ag-header-cell:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n .ag-theme-alpine .ag-header-cell-sortable:hover .ag-header-cell-label {\n color: var(--grid-accent-color, #2196F3);\n}\n\n\n\n .ag-theme-alpine .ag-sort-indicator-icon {\n transition: transform 0.2s ease;\n}\n\n .ag-theme-alpine .ag-header-cell:hover .ag-sort-indicator-icon {\n transform: scale(1.1);\n}"], data: { animation: [
|
|
3533
|
+
trigger('fadeIn', [
|
|
3534
|
+
transition(':enter', [
|
|
3535
|
+
style({ opacity: 0, transform: 'translateY(-8px)' }),
|
|
3536
|
+
animate('150ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
|
|
3537
|
+
]),
|
|
3538
|
+
transition(':leave', [
|
|
3539
|
+
animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
|
|
3540
|
+
])
|
|
3541
|
+
])
|
|
3542
|
+
] } });
|
|
3543
|
+
}
|
|
3544
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityDataGridComponent, [{
|
|
3545
|
+
type: Component,
|
|
3546
|
+
args: [{ selector: 'mj-entity-data-grid', animations: [
|
|
3547
|
+
trigger('fadeIn', [
|
|
3548
|
+
transition(':enter', [
|
|
3549
|
+
style({ opacity: 0, transform: 'translateY(-8px)' }),
|
|
3550
|
+
animate('150ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
|
|
3551
|
+
]),
|
|
3552
|
+
transition(':leave', [
|
|
3553
|
+
animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
|
|
3554
|
+
])
|
|
3555
|
+
])
|
|
3556
|
+
], template: "<!-- Grid Container -->\n<div\n #gridContainer\n [class]=\"gridContainerClasses.join(' ')\"\n [style.height]=\"gridHeightStyle\">\n\n <!-- Toolbar -->\n <div *ngIf=\"ShowToolbar\" class=\"mj-grid-toolbar\">\n <div class=\"toolbar-left\">\n <!-- Search -->\n <div *ngIf=\"ShowSearch\" class=\"toolbar-search\">\n <i class=\"fa-solid fa-search search-icon\"></i>\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"ToolbarConfig.searchPlaceholder || 'Search...'\"\n [value]=\"FilterText\"\n (input)=\"FilterText = $any($event.target).value\" />\n <button\n *ngIf=\"FilterText\"\n class=\"search-clear\"\n (click)=\"FilterText = ''\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Custom Left Buttons -->\n <ng-container *ngFor=\"let button of ToolbarConfig.customButtons\">\n <button\n *ngIf=\"button.position !== 'right' && isButtonVisible(button)\"\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n <i *ngIf=\"button.icon\" [class]=\"button.icon\"></i>\n <span *ngIf=\"button.text\">{{ button.text }}</span>\n </button>\n </ng-container>\n </div>\n\n <div class=\"toolbar-center\">\n <!-- Row Count -->\n <span *ngIf=\"ToolbarConfig.showRowCount !== false\" class=\"row-count\">\n {{ totalRowCount }} {{ totalRowCount === 1 ? 'row' : 'rows' }}\n </span>\n\n <!-- Selection Count -->\n <span *ngIf=\"ToolbarConfig.showSelectionCount && SelectedKeys.length > 0\" class=\"selection-count\">\n ({{ SelectedKeys.length }} selected)\n </span>\n </div>\n\n <div class=\"toolbar-right\">\n <!-- New/Add Button (predefined) -->\n <button\n *ngIf=\"ShowNewButton\"\n class=\"toolbar-button\"\n title=\"Create new record\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n <span class=\"button-text\">New</span>\n </button>\n\n <!-- Refresh Button (predefined) -->\n <button\n *ngIf=\"ShowRefreshButton\"\n class=\"toolbar-button\"\n title=\"Refresh data\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-arrows-rotate\" [class.fa-spin]=\"loading\"></i>\n <span class=\"button-text\">Refresh</span>\n </button>\n\n <!-- Export Button (predefined) -->\n <button\n *ngIf=\"ShowExportButton\"\n class=\"toolbar-button\"\n title=\"Export to Excel\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid fa-file-excel\"></i>\n <span class=\"button-text\">Export</span>\n </button>\n\n <!-- Delete Button (predefined) -->\n <button\n *ngIf=\"ShowDeleteButton && HasSelection\"\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete selected records\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"button-text\">Delete</span>\n </button>\n\n <!-- Compare Button (predefined) -->\n <button\n *ngIf=\"ShowCompareButton\"\n class=\"toolbar-button\"\n title=\"Compare selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onCompareClick()\">\n <i class=\"fa-solid fa-code-compare\"></i>\n <span class=\"button-text\">Compare</span>\n </button>\n\n <!-- Merge Button (predefined) -->\n <button\n *ngIf=\"ShowMergeButton\"\n class=\"toolbar-button\"\n title=\"Merge selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onMergeClick()\">\n <i class=\"fa-solid fa-code-merge\"></i>\n <span class=\"button-text\">Merge</span>\n </button>\n\n <!-- Add to List Button (predefined) -->\n <button\n *ngIf=\"ShowAddToListButton\"\n class=\"toolbar-button\"\n title=\"Add selected records to a list\"\n [disabled]=\"!HasSelection\"\n (click)=\"onAddToListClick()\">\n <i class=\"fa-solid fa-list-check\"></i>\n <span class=\"button-text\">Add to List</span>\n </button>\n\n <!-- Search for Duplicates Button (predefined) -->\n <button\n *ngIf=\"ShowDuplicateSearchButton\"\n class=\"toolbar-button\"\n title=\"Search for duplicate records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onDuplicateSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass-plus\"></i>\n <span class=\"button-text\">Find Duplicates</span>\n </button>\n\n <!-- Communication Button (predefined) -->\n <button\n *ngIf=\"ShowCommunicationButton\"\n class=\"toolbar-button\"\n title=\"Send message to selected records\"\n [disabled]=\"!HasSelection\"\n (click)=\"onCommunicationClick()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span class=\"button-text\">Send Message</span>\n </button>\n\n <!-- Legacy ToolbarConfig buttons -->\n <!-- Add Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showAdd && AllowAdd && !ShowNewButton\"\n class=\"toolbar-button\"\n title=\"Add New\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n\n <!-- Refresh Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showRefresh !== false && !ShowRefreshButton\"\n class=\"toolbar-button\"\n title=\"Refresh\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-refresh\" [class.fa-spin]=\"loading\"></i>\n </button>\n\n <!-- Delete Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showDelete && AllowDelete && HasSelection && !ShowDeleteButton\"\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete Selected\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n\n <!-- Export Button (legacy) -->\n <button\n *ngIf=\"ToolbarConfig.showExport && !ShowExportButton\"\n class=\"toolbar-button\"\n title=\"Export\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid fa-download\"></i>\n </button>\n\n <!-- Column Chooser Button -->\n <button\n *ngIf=\"ToolbarConfig.showColumnChooser && AllowColumnToggle\"\n class=\"toolbar-button\"\n title=\"Column Chooser\"\n (click)=\"onColumnChooserClick()\">\n <i class=\"fa-solid fa-columns\"></i>\n </button>\n\n <!-- Custom Right Buttons -->\n <ng-container *ngFor=\"let button of ToolbarConfig.customButtons\">\n <button\n *ngIf=\"button.position === 'right' && isButtonVisible(button)\"\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n <i *ngIf=\"button.icon\" [class]=\"button.icon\"></i>\n <span *ngIf=\"button.text\">{{ button.text }}</span>\n </button>\n </ng-container>\n\n <!-- Overflow Menu -->\n <div *ngIf=\"hasOverflowMenuItems\" class=\"toolbar-overflow\" (click)=\"$event.stopPropagation()\">\n <button\n class=\"toolbar-button overflow-trigger\"\n title=\"More actions\"\n (click)=\"toggleOverflowMenu()\">\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </button>\n\n <div *ngIf=\"showOverflowMenu\" class=\"overflow-menu\" [@fadeIn]>\n <!-- Export (if in overflow) -->\n <button *ngIf=\"showExportInOverflow\" class=\"overflow-item\" (click)=\"onExportClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-file-excel\"></i>\n <span>Export to Excel</span>\n </button>\n\n <!-- Entity Actions -->\n <ng-container *ngIf=\"ShowEntityActionButtons && EntityActions.length > 0\">\n <div class=\"overflow-divider\"></div>\n <div class=\"overflow-section-label\">Actions</div>\n <button\n *ngFor=\"let action of EntityActions\"\n class=\"overflow-item\"\n [disabled]=\"!isEntityActionEnabled(action)\"\n (click)=\"onEntityActionClick(action); closeOverflowMenu()\">\n <i [class]=\"action.icon || 'fa-solid fa-bolt'\"></i>\n <span>{{ action.name }}</span>\n </button>\n </ng-container>\n\n <!-- Column Chooser (if in overflow) -->\n <div *ngIf=\"showColumnChooserInOverflow\" class=\"overflow-divider\"></div>\n <button *ngIf=\"showColumnChooserInOverflow\" class=\"overflow-item\" (click)=\"onColumnChooserClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-columns\"></i>\n <span>Manage Columns</span>\n </button>\n\n <!-- Selection-dependent actions in overflow -->\n <ng-container *ngIf=\"HasSelection && hasSelectionDependentOverflowActions\">\n <div class=\"overflow-divider\"></div>\n <button *ngIf=\"showCommunicationInOverflow\" class=\"overflow-item\" (click)=\"onCommunicationClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span>Send Message</span>\n </button>\n </ng-container>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Grid Content -->\n <div class=\"mj-grid-content\">\n <!-- Loading Overlay -->\n <div *ngIf=\"loading && rowData.length === 0\" class=\"mj-grid-loading-overlay\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n\n <!-- Error State -->\n <div *ngIf=\"errorMessage && !loading\" class=\"mj-grid-error\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ errorMessage }}</span>\n <button class=\"error-retry\" (click)=\"Refresh()\">Retry</button>\n </div>\n\n <!-- Empty State -->\n <div *ngIf=\"!loading && !errorMessage && rowData.length === 0\" class=\"mj-grid-empty\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No data to display</span>\n </div>\n\n <!-- AG Grid (Client-side mode) -->\n <ag-grid-angular\n *ngIf=\"!errorMessage && PaginationMode === 'client' && (rowData.length > 0 || loading)\"\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [rowData]=\"rowData\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n [rowMultiSelectWithClick]=\"SelectionMode === 'multiple'\"\n (gridReady)=\"onGridReady($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n\n <!-- AG Grid (Infinite Scroll mode) -->\n <ag-grid-angular\n *ngIf=\"!errorMessage && PaginationMode === 'infinite'\"\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n [rowMultiSelectWithClick]=\"SelectionMode === 'multiple'\"\n [rowModelType]=\"'infinite'\"\n [cacheBlockSize]=\"CacheBlockSize\"\n [maxBlocksInCache]=\"MaxBlocksInCache\"\n [infiniteInitialRowCount]=\"1\"\n [cacheOverflowSize]=\"2\"\n (gridReady)=\"onGridReady($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n </div>\n</div>\n\n<!-- Export Dialog -->\n<mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n</mj-export-dialog>\n", styles: ["/* ========================================\n CSS Custom Properties (Theme Variables)\n ======================================== */\n\n:host {\n /* Grid container */\n --grid-border-color: #e0e0e0;\n --grid-border-radius: 0px;\n --grid-background: #ffffff;\n\n /* Header */\n --grid-header-bg: #fafafa;\n --grid-header-text: #333333;\n --grid-header-font-weight: 600;\n --grid-header-height: 40px;\n --grid-header-border-color: #e0e0e0;\n\n /* Rows */\n --grid-row-height: 40px;\n --grid-row-bg: #ffffff;\n --grid-row-bg-alt: #fafafa;\n --grid-row-hover-bg: #f5f5f5;\n --grid-row-selected-bg: #fff9e6;\n --grid-row-selected-hover-bg: #fff3cc;\n\n /* Cells */\n --grid-cell-padding: 8px 12px;\n --grid-cell-text: #333333;\n --grid-cell-border-color: #f0f0f0;\n\n /* Selection - mellow yellow to avoid conflict with blue hyperlinks */\n --grid-checkbox-color: #2196F3;\n --grid-selection-indicator-color: #f9a825;\n\n /* Editing */\n --grid-edit-cell-bg: #ffffff;\n --grid-edit-cell-border: #2196F3;\n --grid-edit-cell-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n\n /* Sorting */\n --grid-sort-indicator-color: #2196F3;\n\n /* Toolbar */\n --grid-toolbar-bg: #ffffff;\n --grid-toolbar-height: 48px;\n --grid-toolbar-border-color: #e0e0e0;\n\n /* Loading */\n --grid-loading-overlay-bg: rgba(255, 255, 255, 0.8);\n\n /* Empty state */\n --grid-empty-text-color: #999999;\n --grid-empty-icon-color: #cccccc;\n\n display: block;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n}\n\n/* ========================================\n Grid Container\n ======================================== */\n\n.mj-grid-container {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--grid-border-color);\n border-radius: var(--grid-border-radius);\n background: var(--grid-background);\n overflow: hidden;\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.mj-grid-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: var(--grid-toolbar-height);\n padding: 0 12px;\n background: var(--grid-toolbar-bg);\n border-bottom: 1px solid var(--grid-toolbar-border-color);\n gap: 12px;\n}\n\n.toolbar-left,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-center {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #666;\n font-size: 13px;\n}\n\n.toolbar-search {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.search-icon {\n position: absolute;\n left: 10px;\n color: #999;\n font-size: 13px;\n}\n\n.search-input {\n padding: 6px 30px 6px 32px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n width: 200px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.search-input:focus {\n outline: none;\n border-color: var(--grid-selection-indicator-color);\n box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);\n}\n\n.search-clear {\n position: absolute;\n right: 6px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: #999;\n font-size: 12px;\n}\n\n.search-clear:hover {\n color: #666;\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n color: #333;\n transition: background-color 0.2s, border-color 0.2s;\n}\n\n.toolbar-button:hover:not(:disabled) {\n background: #eee;\n border-color: #ccc;\n}\n\n.toolbar-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n.toolbar-button-danger {\n color: #d32f2f;\n}\n\n.toolbar-button-danger:hover:not(:disabled) {\n background: #ffebee;\n border-color: #ffcdd2;\n}\n\n.row-count,\n.selection-count {\n font-weight: 500;\n}\n\n/* ========================================\n Grid Content\n ======================================== */\n\n.mj-grid-content {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n\n.mj-grid-scroll-container {\n height: 100%;\n overflow: auto;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.mj-grid-loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--grid-loading-overlay-bg);\n z-index: 10;\n}\n\n/* ========================================\n Error State\n ======================================== */\n\n.mj-grid-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n color: #d32f2f;\n gap: 12px;\n}\n\n.mj-grid-error i {\n font-size: 32px;\n}\n\n.error-retry {\n padding: 8px 16px;\n background: #d32f2f;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n}\n\n.error-retry:hover {\n background: #c62828;\n}\n\n/* ========================================\n Empty State\n ======================================== */\n\n.mj-grid-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--grid-empty-text-color);\n gap: 12px;\n}\n\n.mj-grid-empty i {\n font-size: 48px;\n color: var(--grid-empty-icon-color);\n}\n\n/* ========================================\n Header Row\n ======================================== */\n\n.mj-grid-header {\n display: flex;\n min-height: var(--grid-header-height);\n background: var(--grid-header-bg);\n border-bottom: 2px solid var(--grid-header-border-color);\n position: sticky;\n top: 0;\n z-index: 5;\n}\n\n.mj-grid-header-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n font-weight: var(--grid-header-font-weight);\n color: var(--grid-header-text);\n font-size: 13px;\n user-select: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right: 1px solid var(--grid-cell-border-color);\n flex-shrink: 0;\n}\n\n.mj-grid-header-cell:last-child {\n border-right: none;\n}\n\n.mj-grid-header-cell.sortable {\n cursor: pointer;\n}\n\n.mj-grid-header-cell.sortable:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.header-text {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sort-indicator {\n display: flex;\n align-items: center;\n margin-left: 6px;\n color: var(--grid-sort-indicator-color);\n}\n\n.sort-indicator i {\n font-size: 12px;\n}\n\n.sort-index {\n font-size: 10px;\n margin-left: 2px;\n font-weight: normal;\n}\n\n/* ========================================\n Data Rows\n ======================================== */\n\n.mj-grid-row {\n display: flex;\n align-items: stretch;\n background: var(--grid-row-bg);\n transition: background-color 0.15s;\n cursor: default;\n}\n\n.mj-grid-row:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-alt {\n background: var(--grid-row-bg-alt);\n}\n\n.mj-grid-row.grid-row-alt:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-selected {\n background: var(--grid-row-selected-bg);\n}\n\n.mj-grid-row.grid-row-selected:hover {\n background: var(--grid-row-selected-hover-bg);\n}\n\n.mj-grid-row.grid-row-editing {\n background: #fffde7;\n}\n\n.mj-grid-row.grid-row-dirty {\n border-left: 3px solid #ff9800;\n}\n\n/* ========================================\n Data Cells\n ======================================== */\n\n.mj-grid-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n color: var(--grid-cell-text);\n font-size: 13px;\n overflow: hidden;\n border-right: 1px solid transparent;\n flex-shrink: 0;\n}\n\n.mj-grid-cell:last-child {\n border-right: none;\n}\n\n/* Grid lines modes */\n.grid-lines-horizontal .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-vertical .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n/* Cell alignment */\n.mj-grid-cell.align-left {\n justify-content: flex-start;\n}\n\n.mj-grid-cell.align-center {\n justify-content: center;\n}\n\n.mj-grid-cell.align-right {\n justify-content: flex-end;\n}\n\n.cell-content {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Special cells */\n.row-number-cell {\n width: 50px;\n min-width: 50px;\n max-width: 50px;\n justify-content: center;\n color: #999;\n font-size: 12px;\n background: var(--grid-header-bg);\n}\n\n.checkbox-cell {\n width: 40px;\n min-width: 40px;\n max-width: 40px;\n justify-content: center;\n}\n\n.checkbox-cell input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--grid-checkbox-color);\n}\n\n/* ========================================\n Virtual Scrolling\n ======================================== */\n\n.mj-grid-virtual-spacer {\n flex-shrink: 0;\n}\n\n/* ========================================\n Responsive Adjustments\n ======================================== */\n\n@media (max-width: 768px) {\n .mj-grid-toolbar {\n flex-wrap: wrap;\n padding: 8px;\n }\n\n .toolbar-search {\n order: 3;\n width: 100%;\n margin-top: 8px;\n }\n\n .search-input {\n width: 100%;\n }\n\n .toolbar-center {\n order: 2;\n }\n\n /* Hide button text on mobile */\n .toolbar-button .button-text {\n display: none;\n }\n}\n\n/* ========================================\n Toolbar Button Text\n ======================================== */\n\n.toolbar-button .button-text {\n font-size: 13px;\n}\n\n/* ========================================\n Overflow Menu\n ======================================== */\n\n.toolbar-overflow {\n position: relative;\n}\n\n.overflow-trigger {\n padding: 6px 8px !important;\n}\n\n.overflow-trigger i {\n font-size: 16px;\n}\n\n.overflow-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 220px;\n background: #ffffff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);\n z-index: 1000;\n overflow: hidden;\n}\n\n.overflow-item {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 10px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n text-align: left;\n gap: 12px;\n transition: background-color 0.15s;\n}\n\n.overflow-item:hover:not(:disabled) {\n background: #f5f5f5;\n}\n\n.overflow-item:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.overflow-item i {\n width: 18px;\n font-size: 14px;\n color: #666;\n text-align: center;\n}\n\n.overflow-item span {\n flex: 1;\n}\n\n.overflow-divider {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.overflow-section-label {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: #999;\n letter-spacing: 0.5px;\n}\n\n/* Entity Actions submenu styling */\n.overflow-item.action-item i {\n color: #2196F3;\n}\n\n/* ========================================\n Highlight Matches\n ======================================== */\n\n::ng-deep .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n padding: 0 1px;\n}\n\n/* ========================================\n AG Grid Customizations\n ======================================== */\n\n.mj-ag-grid {\n width: 100%;\n height: 100%;\n}\n\n::ng-deep .ag-theme-alpine {\n /* Row colors */\n --ag-row-hover-color: var(--grid-row-hover-bg);\n --ag-selected-row-background-color: var(--grid-row-selected-bg);\n --ag-header-background-color: var(--grid-header-bg);\n\n /* Selection accent colors - mellow yellow */\n --ag-range-selection-background-color: rgba(249, 168, 37, 0.15);\n --ag-range-selection-border-color: #f9a825;\n\n /* Ensure borders are visible */\n --ag-borders: none;\n --ag-row-border-color: var(--grid-cell-border-color);\n}\n\n/* Selected row styling - left indicator bar only, background handled by AG Grid theme */\n::ng-deep .ag-row-selected {\n box-shadow: inset 4px 0 0 0 #f9a825;\n}\n\n/* Selection checkbox styling */\n::ng-deep .ag-theme-alpine .ag-checkbox-input-wrapper {\n width: 18px;\n height: 18px;\n}\n\n::ng-deep .ag-theme-alpine .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--grid-checkbox-color, #2196F3);\n}\n\n/* Row hover effect */\n::ng-deep .ag-theme-alpine .ag-row:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f0f7ff);\n}\n\n/* ========================================\n Visual Config: Header Styles\n ======================================== */\n\n/* Flat header (minimal) */\n.header-style-flat ::ng-deep .ag-header {\n background: var(--grid-header-bg, #fafafa);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n/* Elevated header (default - subtle shadow) */\n.header-style-elevated ::ng-deep .ag-header {\n background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);\n border-bottom: 1px solid var(--grid-header-border-color, #e0e0e0);\n}\n\n.header-style-elevated.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n/* Gradient header (more prominent) */\n.header-style-gradient ::ng-deep .ag-header {\n background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);\n border-bottom: none;\n}\n\n.header-style-gradient.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n/* Bold header (high contrast) */\n.header-style-bold ::ng-deep .ag-header {\n background: linear-gradient(180deg, #37474f 0%, #263238 100%);\n border-bottom: none;\n}\n\n.header-style-bold ::ng-deep .ag-header-cell-text {\n color: #ffffff;\n font-weight: 600;\n}\n\n.header-style-bold ::ng-deep .ag-header-icon {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.header-style-bold ::ng-deep .ag-header-cell:hover {\n background: rgba(255, 255, 255, 0.1);\n}\n\n/* Header sort icons enhancement */\n::ng-deep .ag-header-cell-sorted-asc .ag-icon-asc,\n::ng-deep .ag-header-cell-sorted-desc .ag-icon-desc {\n color: var(--grid-accent-color, var(--grid-sort-indicator-color, #2196F3));\n}\n\n/* ========================================\n Visual Config: Zebra Striping\n ======================================== */\n\n/* Subtle contrast */\n.alternate-rows-subtle ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.015);\n}\n\n.alternate-rows-subtle ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n/* Medium contrast (default) */\n.alternate-rows-medium ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.025);\n}\n\n.alternate-rows-medium ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n/* Strong contrast */\n.alternate-rows-strong ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.alternate-rows-strong ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, #f5f5f5);\n}\n\n/* ========================================\n Visual Config: Hover Transitions\n ======================================== */\n\n.hover-transitions ::ng-deep .ag-row {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n.hover-transitions ::ng-deep .ag-cell {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n/* ========================================\n Visual Config: Cell Padding\n ======================================== */\n\n.cell-padding-compact ::ng-deep .ag-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-compact ::ng-deep .ag-header-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-normal ::ng-deep .ag-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-normal ::ng-deep .ag-header-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-header-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n/* ========================================\n Visual Config: Checkbox Styles\n ======================================== */\n\n/* Rounded checkbox */\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper {\n border-radius: 4px;\n}\n\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper::after {\n border-radius: 3px;\n}\n\n/* Filled checkbox */\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked {\n background-color: var(--grid-checkbox-color, #2196F3);\n border-color: var(--grid-checkbox-color, #2196F3);\n}\n\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked::after {\n color: #ffffff;\n}\n\n/* ========================================\n Cell Content Formatting\n ======================================== */\n\n/* Right-aligned cells (numbers) */\n::ng-deep .cell-align-right {\n text-align: right;\n justify-content: flex-end;\n}\n\n::ng-deep .header-align-right .ag-header-cell-label {\n justify-content: flex-end;\n}\n\n/* Empty cell placeholder */\n::ng-deep .cell-empty {\n color: #bdbdbd;\n font-style: normal;\n}\n\n/* Boolean icons */\n::ng-deep .cell-boolean-true {\n color: #43a047;\n font-size: 14px;\n}\n\n::ng-deep .cell-boolean-false {\n color: #bdbdbd;\n font-size: 14px;\n}\n\n/* Clickable links */\n::ng-deep .cell-link {\n color: var(--grid-accent-color, #2196F3);\n text-decoration: none;\n transition: color 0.15s;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n\n::ng-deep .cell-link:hover {\n color: #1976D2;\n text-decoration: underline;\n}\n\n/* Email cells */\n::ng-deep .cell-email {\n font-family: inherit;\n font-size: 13px;\n}\n\n/* URL cells */\n::ng-deep .cell-url {\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n font-size: 13px;\n}\n\n/* Phone cells */\n::ng-deep .cell-phone {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n}\n\n/* ========================================\n Skeleton Loading Animation\n ======================================== */\n\n@keyframes skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n\n.skeleton-row {\n display: flex;\n height: 40px;\n align-items: center;\n padding: 0 12px;\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n.skeleton-cell {\n height: 16px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n #f0f0f0 0px,\n #e8e8e8 40px,\n #f0f0f0 80px\n );\n background-size: 200px 100%;\n animation: skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-cell-short {\n width: 60px;\n}\n\n.skeleton-cell-medium {\n width: 120px;\n}\n\n.skeleton-cell-long {\n width: 180px;\n}\n\n/* Selection checkbox column header */\n::ng-deep .ag-header-select-all {\n margin-right: 0;\n}\n\n/* ========================================\n Row Border Enhancement\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-row {\n border-bottom: 1px solid var(--grid-cell-border-color, #f0f0f0);\n}\n\n::ng-deep .ag-theme-alpine .ag-row:last-child {\n border-bottom: none;\n}\n\n/* ========================================\n Focus States\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-cell-focus {\n border: none !important;\n outline: none !important;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:focus {\n outline: 2px solid var(--grid-accent-color, #2196F3);\n outline-offset: -2px;\n}\n\n/* ========================================\n Scrollbar Styling\n ======================================== */\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-track {\n background: #f5f5f5;\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb {\n background: #c0c0c0;\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb:hover {\n background: #a0a0a0;\n}\n\n/* ========================================\n Pinned Column Styling\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-pinned-left-cols-container {\n border-right: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n::ng-deep .ag-theme-alpine .ag-pinned-right-cols-container {\n border-left: 2px solid var(--grid-border-color, #e0e0e0);\n}\n\n/* ========================================\n Header Cell Enhancements\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-header-cell {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: #546e7a;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell-sortable:hover .ag-header-cell-label {\n color: var(--grid-accent-color, #2196F3);\n}\n\n/* Sort icon animation */\n::ng-deep .ag-theme-alpine .ag-sort-indicator-icon {\n transition: transform 0.2s ease;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover .ag-sort-indicator-icon {\n transform: scale(1.1);\n}\n"] }]
|
|
3557
|
+
}], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i1.ExportService }], { Params: [{
|
|
3558
|
+
type: Input
|
|
3559
|
+
}], AllowLoad: [{
|
|
3560
|
+
type: Input
|
|
3561
|
+
}], AutoRefreshOnParamsChange: [{
|
|
3562
|
+
type: Input
|
|
3563
|
+
}], PaginationMode: [{
|
|
3564
|
+
type: Input
|
|
3565
|
+
}], PageSize: [{
|
|
3566
|
+
type: Input
|
|
3567
|
+
}], CacheBlockSize: [{
|
|
3568
|
+
type: Input
|
|
3569
|
+
}], MaxBlocksInCache: [{
|
|
3570
|
+
type: Input
|
|
3571
|
+
}], Data: [{
|
|
3572
|
+
type: Input
|
|
3573
|
+
}], Columns: [{
|
|
3574
|
+
type: Input
|
|
3575
|
+
}], GridState: [{
|
|
3576
|
+
type: Input
|
|
3577
|
+
}], AllowColumnReorder: [{
|
|
3578
|
+
type: Input
|
|
3579
|
+
}], AllowColumnResize: [{
|
|
3580
|
+
type: Input
|
|
3581
|
+
}], AllowColumnToggle: [{
|
|
3582
|
+
type: Input
|
|
3583
|
+
}], ShowHeader: [{
|
|
3584
|
+
type: Input
|
|
3585
|
+
}], AllowSorting: [{
|
|
3586
|
+
type: Input
|
|
3587
|
+
}], AllowMultiSort: [{
|
|
3588
|
+
type: Input
|
|
3589
|
+
}], ServerSideSorting: [{
|
|
3590
|
+
type: Input
|
|
3591
|
+
}], AllowColumnFilters: [{
|
|
3592
|
+
type: Input
|
|
3593
|
+
}], ShowSearch: [{
|
|
3594
|
+
type: Input
|
|
3595
|
+
}], SelectionMode: [{
|
|
3596
|
+
type: Input
|
|
3597
|
+
}], SelectedKeys: [{
|
|
3598
|
+
type: Input
|
|
3599
|
+
}], KeyField: [{
|
|
3600
|
+
type: Input
|
|
3601
|
+
}], EditMode: [{
|
|
3602
|
+
type: Input
|
|
3603
|
+
}], AllowAdd: [{
|
|
3604
|
+
type: Input
|
|
3605
|
+
}], AllowDelete: [{
|
|
3606
|
+
type: Input
|
|
3607
|
+
}], Height: [{
|
|
3608
|
+
type: Input
|
|
3609
|
+
}], RowHeight: [{
|
|
3610
|
+
type: Input
|
|
3611
|
+
}], VirtualScroll: [{
|
|
3612
|
+
type: Input
|
|
3613
|
+
}], ShowRowNumbers: [{
|
|
3614
|
+
type: Input
|
|
3615
|
+
}], Striped: [{
|
|
3616
|
+
type: Input
|
|
3617
|
+
}], GridLines: [{
|
|
3618
|
+
type: Input
|
|
3619
|
+
}], VisualConfig: [{
|
|
3620
|
+
type: Input
|
|
3621
|
+
}], ShowToolbar: [{
|
|
3622
|
+
type: Input
|
|
3623
|
+
}], ToolbarConfig: [{
|
|
3624
|
+
type: Input
|
|
3625
|
+
}], StateKey: [{
|
|
3626
|
+
type: Input
|
|
3627
|
+
}], AutoPersistState: [{
|
|
3628
|
+
type: Input
|
|
3629
|
+
}], StatePersistDebounce: [{
|
|
3630
|
+
type: Input
|
|
3631
|
+
}], RefreshDebounce: [{
|
|
3632
|
+
type: Input
|
|
3633
|
+
}], FilterText: [{
|
|
3634
|
+
type: Input
|
|
3635
|
+
}], ShowNewButton: [{
|
|
3636
|
+
type: Input
|
|
3637
|
+
}], ShowRefreshButton: [{
|
|
3638
|
+
type: Input
|
|
3639
|
+
}], ShowExportButton: [{
|
|
3640
|
+
type: Input
|
|
3641
|
+
}], ShowDeleteButton: [{
|
|
3642
|
+
type: Input
|
|
3643
|
+
}], ShowCompareButton: [{
|
|
3644
|
+
type: Input
|
|
3645
|
+
}], ShowMergeButton: [{
|
|
3646
|
+
type: Input
|
|
3647
|
+
}], ShowAddToListButton: [{
|
|
3648
|
+
type: Input
|
|
3649
|
+
}], ShowDuplicateSearchButton: [{
|
|
3650
|
+
type: Input
|
|
3651
|
+
}], ShowCommunicationButton: [{
|
|
3652
|
+
type: Input
|
|
3653
|
+
}], AutoNavigate: [{
|
|
3654
|
+
type: Input
|
|
3655
|
+
}], NavigateOnDoubleClick: [{
|
|
3656
|
+
type: Input
|
|
3657
|
+
}], CreateRecordMode: [{
|
|
3658
|
+
type: Input
|
|
3659
|
+
}], NewRecordValues: [{
|
|
3660
|
+
type: Input
|
|
3661
|
+
}], ShowEntityActionButtons: [{
|
|
3662
|
+
type: Input
|
|
3663
|
+
}], EntityActions: [{
|
|
3664
|
+
type: Input
|
|
3665
|
+
}], BeforeRowSelect: [{
|
|
3666
|
+
type: Output
|
|
3667
|
+
}], AfterRowSelect: [{
|
|
3668
|
+
type: Output
|
|
3669
|
+
}], BeforeRowDeselect: [{
|
|
3670
|
+
type: Output
|
|
3671
|
+
}], AfterRowDeselect: [{
|
|
3672
|
+
type: Output
|
|
3673
|
+
}], SelectionChange: [{
|
|
3674
|
+
type: Output
|
|
3675
|
+
}], BeforeRowClick: [{
|
|
3676
|
+
type: Output
|
|
3677
|
+
}], AfterRowClick: [{
|
|
3678
|
+
type: Output
|
|
3679
|
+
}], BeforeRowDoubleClick: [{
|
|
3680
|
+
type: Output
|
|
3681
|
+
}], AfterRowDoubleClick: [{
|
|
3682
|
+
type: Output
|
|
3683
|
+
}], BeforeCellEdit: [{
|
|
3684
|
+
type: Output
|
|
3685
|
+
}], AfterCellEditBegin: [{
|
|
3686
|
+
type: Output
|
|
3687
|
+
}], BeforeCellEditCommit: [{
|
|
3688
|
+
type: Output
|
|
3689
|
+
}], AfterCellEditCommit: [{
|
|
3690
|
+
type: Output
|
|
3691
|
+
}], BeforeCellEditCancel: [{
|
|
3692
|
+
type: Output
|
|
3693
|
+
}], AfterCellEditCancel: [{
|
|
3694
|
+
type: Output
|
|
3695
|
+
}], BeforeRowSave: [{
|
|
3696
|
+
type: Output
|
|
3697
|
+
}], AfterRowSave: [{
|
|
3698
|
+
type: Output
|
|
3699
|
+
}], BeforeRowDelete: [{
|
|
3700
|
+
type: Output
|
|
3701
|
+
}], AfterRowDelete: [{
|
|
3702
|
+
type: Output
|
|
3703
|
+
}], BeforeDataLoad: [{
|
|
3704
|
+
type: Output
|
|
3705
|
+
}], AfterDataLoad: [{
|
|
3706
|
+
type: Output
|
|
3707
|
+
}], BeforeDataRefresh: [{
|
|
3708
|
+
type: Output
|
|
3709
|
+
}], AfterDataRefresh: [{
|
|
3710
|
+
type: Output
|
|
3711
|
+
}], BeforeSort: [{
|
|
3712
|
+
type: Output
|
|
3713
|
+
}], AfterSort: [{
|
|
3714
|
+
type: Output
|
|
3715
|
+
}], BeforeColumnReorder: [{
|
|
3716
|
+
type: Output
|
|
3717
|
+
}], AfterColumnReorder: [{
|
|
3718
|
+
type: Output
|
|
3719
|
+
}], BeforeColumnResize: [{
|
|
3720
|
+
type: Output
|
|
3721
|
+
}], AfterColumnResize: [{
|
|
3722
|
+
type: Output
|
|
3723
|
+
}], BeforeColumnVisibilityChange: [{
|
|
3724
|
+
type: Output
|
|
3725
|
+
}], AfterColumnVisibilityChange: [{
|
|
3726
|
+
type: Output
|
|
3727
|
+
}], GridStateChanged: [{
|
|
3728
|
+
type: Output
|
|
3729
|
+
}], AddRequested: [{
|
|
3730
|
+
type: Output
|
|
3731
|
+
}], DeleteRequested: [{
|
|
3732
|
+
type: Output
|
|
3733
|
+
}], ExportRequested: [{
|
|
3734
|
+
type: Output
|
|
3735
|
+
}], NewButtonClick: [{
|
|
3736
|
+
type: Output
|
|
3737
|
+
}], RefreshButtonClick: [{
|
|
3738
|
+
type: Output
|
|
3739
|
+
}], ExportButtonClick: [{
|
|
3740
|
+
type: Output
|
|
3741
|
+
}], DeleteButtonClick: [{
|
|
3742
|
+
type: Output
|
|
3743
|
+
}], CompareButtonClick: [{
|
|
3744
|
+
type: Output
|
|
3745
|
+
}], MergeButtonClick: [{
|
|
3746
|
+
type: Output
|
|
3747
|
+
}], AddToListButtonClick: [{
|
|
3748
|
+
type: Output
|
|
3749
|
+
}], DuplicateSearchButtonClick: [{
|
|
3750
|
+
type: Output
|
|
3751
|
+
}], CommunicationButtonClick: [{
|
|
3752
|
+
type: Output
|
|
3753
|
+
}], NavigationRequested: [{
|
|
3754
|
+
type: Output
|
|
3755
|
+
}], NewRecordDialogRequested: [{
|
|
3756
|
+
type: Output
|
|
3757
|
+
}], NewRecordTabRequested: [{
|
|
3758
|
+
type: Output
|
|
3759
|
+
}], CompareRecordsRequested: [{
|
|
3760
|
+
type: Output
|
|
3761
|
+
}], MergeRecordsRequested: [{
|
|
3762
|
+
type: Output
|
|
3763
|
+
}], CommunicationRequested: [{
|
|
3764
|
+
type: Output
|
|
3765
|
+
}], DuplicateSearchRequested: [{
|
|
3766
|
+
type: Output
|
|
3767
|
+
}], AddToListRequested: [{
|
|
3768
|
+
type: Output
|
|
3769
|
+
}], LoadEntityActionsRequested: [{
|
|
3770
|
+
type: Output
|
|
3771
|
+
}], EntityActionRequested: [{
|
|
3772
|
+
type: Output
|
|
3773
|
+
}], gridContainer: [{
|
|
3774
|
+
type: ViewChild,
|
|
3775
|
+
args: ['gridContainer']
|
|
3776
|
+
}] }); })();
|
|
3777
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber: 150 }); })();
|
|
3778
|
+
//# sourceMappingURL=entity-data-grid.component.js.map
|