0
# Event System
1
2
RevoGrid provides a comprehensive event system that allows you to listen for and respond to various grid interactions, data changes, and lifecycle events. The system uses native DOM events with detailed event data.
3
4
## Event Categories
5
6
### Edit Events
7
8
Events related to cell editing and data modification:
9
10
**beforeeditstart** { .api }
11
```typescript
12
grid.addEventListener('beforeeditstart', (event: CustomEvent<Edition.BeforeSaveDataDetails>) => {
13
const detail = event.detail;
14
console.log('About to start editing:', detail);
15
16
// Prevent editing if needed
17
if (!canEditCell(detail)) {
18
event.preventDefault();
19
}
20
});
21
```
22
23
**beforeedit** { .api }
24
```typescript
25
grid.addEventListener('beforeedit', (event: CustomEvent<Edition.BeforeSaveDataDetails>) => {
26
const detail = event.detail;
27
console.log('Before saving edit:', detail);
28
29
// Validate and prevent if invalid
30
if (!validateEdit(detail)) {
31
event.preventDefault();
32
showValidationError();
33
}
34
});
35
```
36
37
**afteredit** { .api }
38
```typescript
39
grid.addEventListener('afteredit', (event: CustomEvent<Edition.BeforeSaveDataDetails>) => {
40
const detail = event.detail;
41
console.log('Edit completed:', detail);
42
43
// Handle post-edit actions
44
updateCalculations(detail);
45
saveToServer(detail);
46
});
47
```
48
49
**beforerangeedit** { .api }
50
```typescript
51
grid.addEventListener('beforerangeedit', (event: CustomEvent<Edition.BeforeRangeSaveDataDetails>) => {
52
const { data, range, source } = event.detail;
53
console.log(`Range edit: ${data.length} cells via ${source}`);
54
55
// Validate all changes in range
56
const isValid = data.every(change => validateChange(change));
57
if (!isValid) {
58
event.preventDefault();
59
}
60
});
61
```
62
63
**beforeautofill** { .api }
64
```typescript
65
grid.addEventListener('beforeautofill', (event: CustomEvent<Selection.ChangedRange>) => {
66
const rangeChange = event.detail;
67
console.log('Autofill operation:', rangeChange);
68
69
// Customize autofill behavior
70
if (!allowAutofill(rangeChange)) {
71
event.preventDefault();
72
}
73
});
74
```
75
76
### Event Data Types
77
78
```typescript { .api }
79
namespace Edition {
80
interface BeforeSaveDataDetails {
81
model: RevoGrid.DataType; // Original row data
82
prop: RevoGrid.ColumnProp; // Column property being edited
83
val: SaveData; // New value being saved
84
rowIndex: number; // Row index in data source
85
column: RevoGrid.ColumnRegular; // Column definition
86
type: RevoGrid.DimensionRows; // Row dimension type
87
}
88
89
interface BeforeRangeSaveDataDetails {
90
data: BeforeSaveDataDetails[]; // Array of all changes
91
range: Selection.RangeArea; // Range area being edited
92
source: 'paste' | 'autofill' | 'edit'; // Source of range edit
93
}
94
}
95
```
96
97
### Focus Events
98
99
Events related to cell focus and navigation:
100
101
**beforecellfocus** { .api }
102
```typescript
103
grid.addEventListener('beforecellfocus', (event: CustomEvent<Edition.BeforeSaveDataDetails>) => {
104
const detail = event.detail;
105
console.log('About to focus cell:', detail);
106
107
// Prevent focus change if needed
108
if (shouldPreventFocus(detail)) {
109
event.preventDefault();
110
}
111
});
112
```
113
114
**beforefocuslost** { .api }
115
```typescript
116
grid.addEventListener('beforefocuslost', (event: CustomEvent<FocusedData | null>) => {
117
const focusedData = event.detail;
118
console.log('About to lose focus:', focusedData);
119
120
// Prevent focus loss (e.g., validation failed)
121
if (hasUnsavedChanges()) {
122
event.preventDefault();
123
showSavePrompt();
124
}
125
});
126
```
127
128
**afterfocus** { .api }
129
```typescript
130
grid.addEventListener('afterfocus', (event: CustomEvent<{model: any, column: RevoGrid.ColumnRegular}>) => {
131
const { model, column } = event.detail;
132
console.log('Focus changed:', { model, column });
133
134
// Update UI based on focused cell
135
updatePropertyPanel(model, column);
136
updateFormula(model, column);
137
});
138
```
139
140
**Focus Event Data** { .api }
141
```typescript
142
interface FocusedData {
143
cell: Selection.Cell; // Cell coordinates
144
model: any; // Row data
145
column: RevoGrid.ColumnRegular; // Column definition
146
rowType: RevoGrid.DimensionRows; // Row dimension type
147
colType: RevoGrid.DimensionCols; // Column dimension type
148
}
149
```
150
151
### Data Events
152
153
Events related to data source changes:
154
155
**beforesourceset** { .api }
156
```typescript
157
grid.addEventListener('beforesourceset', (event: CustomEvent<{type: RevoGrid.DimensionRows, source: RevoGrid.DataType[]}>) => {
158
const { type, source } = event.detail;
159
console.log(`About to set ${type} data:`, source);
160
161
// Validate data before setting
162
if (!validateDataSource(source)) {
163
event.preventDefault();
164
}
165
});
166
```
167
168
**aftersourceset** { .api }
169
```typescript
170
grid.addEventListener('aftersourceset', (event: CustomEvent<{type: RevoGrid.DimensionRows, source: RevoGrid.DataType[]}>) => {
171
const { type, source } = event.detail;
172
console.log(`Data set for ${type}:`, source);
173
174
// Update dependent systems
175
updateSummaryCalculations(source);
176
refreshDashboard();
177
});
178
```
179
180
### Column Events
181
182
Events related to column configuration and interactions:
183
184
**beforecolumnsset** { .api }
185
```typescript
186
grid.addEventListener('beforecolumnsset', (event: CustomEvent<ColumnCollection>) => {
187
const columns = event.detail;
188
console.log('About to set columns:', columns);
189
190
// Modify columns before they're applied
191
const processedColumns = preprocessColumns(columns);
192
// Note: Modifying event.detail may not always work, use updateColumns() method instead
193
});
194
```
195
196
**beforecolumnapplied** { .api }
197
```typescript
198
grid.addEventListener('beforecolumnapplied', (event: CustomEvent<ColumnCollection>) => {
199
const columns = event.detail;
200
console.log('Columns about to be applied:', columns);
201
});
202
```
203
204
**aftercolumnsset** { .api }
205
```typescript
206
grid.addEventListener('aftercolumnsset', (event: CustomEvent<{columns: ColumnCollection, order: Record<RevoGrid.ColumnProp, 'asc'|'desc'>}>) => {
207
const { columns, order } = event.detail;
208
console.log('Columns applied:', { columns, order });
209
210
// Save column configuration
211
saveColumnConfiguration(columns, order);
212
});
213
```
214
215
**aftercolumnresize** { .api }
216
```typescript
217
grid.addEventListener('aftercolumnresize', (event: CustomEvent<Record<RevoGrid.ColumnProp, RevoGrid.ColumnRegular>>) => {
218
const resizedColumns = event.detail;
219
console.log('Columns resized:', resizedColumns);
220
221
// Save new column widths
222
saveColumnWidths(resizedColumns);
223
});
224
```
225
226
**headerclick** { .api }
227
```typescript
228
grid.addEventListener('headerclick', (event: CustomEvent<RevoGrid.ColumnRegular>) => {
229
const column = event.detail;
230
console.log('Header clicked:', column);
231
232
// Handle header interactions
233
if (column.prop === 'actions') {
234
showColumnMenu(column);
235
} else if (event.ctrlKey) {
236
toggleColumnSelection(column);
237
}
238
});
239
```
240
241
### Sorting Events
242
243
Events related to data sorting:
244
245
**beforesorting** { .api }
246
```typescript
247
grid.addEventListener('beforesorting', (event: CustomEvent<{column: RevoGrid.ColumnRegular, order: 'desc'|'asc', additive: boolean}>) => {
248
const { column, order, additive } = event.detail;
249
console.log(`About to sort ${column.name} in ${order} order`);
250
251
// Prevent sorting for certain columns
252
if (column.prop === 'actions') {
253
event.preventDefault();
254
}
255
});
256
```
257
258
**beforesortingapply** { .api }
259
```typescript
260
grid.addEventListener('beforesortingapply', (event: CustomEvent<{column: RevoGrid.ColumnRegular, order: 'desc'|'asc', additive: boolean}>) => {
261
const { column, order, additive } = event.detail;
262
console.log('About to apply sort:', { column, order, additive });
263
264
// Custom sorting logic
265
if (column.customSort) {
266
event.preventDefault();
267
applyCustomSort(column, order, additive);
268
}
269
});
270
```
271
272
**beforesourcesortingapply** { .api }
273
```typescript
274
grid.addEventListener('beforesourcesortingapply', (event: CustomEvent) => {
275
console.log('About to apply source sorting');
276
277
// Prepare for data reorganization
278
prepareForSort();
279
});
280
```
281
282
### Filter Events
283
284
Events related to data filtering:
285
286
**beforefilterapply** { .api }
287
```typescript
288
grid.addEventListener('beforefilterapply', (event: CustomEvent<{collection: FilterCollection}>) => {
289
const { collection } = event.detail;
290
console.log('About to apply filters:', collection);
291
292
// Validate filter criteria
293
if (!validateFilters(collection)) {
294
event.preventDefault();
295
}
296
});
297
```
298
299
**beforefiltertrimmed** { .api }
300
```typescript
301
grid.addEventListener('beforefiltertrimmed', (event: CustomEvent<{collection: FilterCollection, itemsToFilter: Record<number, boolean>}>) => {
302
const { collection, itemsToFilter } = event.detail;
303
console.log('About to hide filtered rows:', itemsToFilter);
304
305
// Log filtering operation
306
logFilterOperation(collection, itemsToFilter);
307
});
308
```
309
310
### Row Events
311
312
Events related to row operations:
313
314
**rowdragstart** { .api }
315
```typescript
316
grid.addEventListener('rowdragstart', (event: CustomEvent<{pos: RevoGrid.PositionItem, text: string}>) => {
317
const { pos, text } = event.detail;
318
console.log('Row drag started:', { pos, text });
319
320
// Custom drag behavior
321
setupCustomDragBehavior(pos);
322
});
323
```
324
325
**roworderchanged** { .api }
326
```typescript
327
grid.addEventListener('roworderchanged', (event: CustomEvent<{from: number, to: number}>) => {
328
const { from, to } = event.detail;
329
console.log(`Row moved from ${from} to ${to}`);
330
331
// Update data order
332
updateRowOrder(from, to);
333
334
// Save new order
335
saveRowOrder();
336
});
337
```
338
339
**beforetrimmed** { .api }
340
```typescript
341
grid.addEventListener('beforetrimmed', (event: CustomEvent<{trimmed: Record<number, boolean>, trimmedType: string, type: string}>) => {
342
const { trimmed, trimmedType, type } = event.detail;
343
console.log('About to trim rows:', { trimmed, trimmedType, type });
344
345
// Log trimming operation
346
logTrimOperation(trimmed, trimmedType);
347
});
348
```
349
350
**aftertrimmed** { .api }
351
```typescript
352
grid.addEventListener('aftertrimmed', (event: CustomEvent) => {
353
console.log('Rows trimmed successfully');
354
355
// Update UI after trimming
356
updateRowCountDisplay();
357
recalculateAggregates();
358
});
359
```
360
361
### Viewport Events
362
363
Events related to scrolling and viewport changes:
364
365
**viewportscroll** { .api }
366
```typescript
367
grid.addEventListener('viewportscroll', (event: CustomEvent<RevoGrid.ViewPortScrollEvent>) => {
368
const scrollEvent = event.detail;
369
console.log('Viewport scrolled:', scrollEvent);
370
371
// Handle lazy loading
372
handleLazyLoading(scrollEvent);
373
374
// Sync with external components
375
syncScrollPosition(scrollEvent);
376
});
377
```
378
379
**ViewPortScrollEvent** { .api }
380
```typescript
381
interface ViewPortScrollEvent {
382
scrollTop: number; // Current vertical scroll position
383
scrollLeft: number; // Current horizontal scroll position
384
clientHeight: number; // Viewport height
385
clientWidth: number; // Viewport width
386
scrollHeight: number; // Total scrollable height
387
scrollWidth: number; // Total scrollable width
388
range: { // Visible range information
389
rowStart: number;
390
rowEnd: number;
391
colStart: number;
392
colEnd: number;
393
};
394
}
395
```
396
397
### Export Events
398
399
Events related to data export:
400
401
**beforeexport** { .api }
402
```typescript
403
grid.addEventListener('beforeexport', (event: CustomEvent<DataInput>) => {
404
const dataInput = event.detail;
405
console.log('About to export:', dataInput);
406
407
// Modify export data
408
dataInput.data = preprocessExportData(dataInput.data);
409
410
// Add metadata
411
dataInput.metadata = {
412
exportDate: new Date().toISOString(),
413
user: getCurrentUser(),
414
filters: getActiveFilters()
415
};
416
});
417
```
418
419
### Range Events
420
421
Events related to range selection:
422
423
**beforerange** { .api }
424
```typescript
425
grid.addEventListener('beforerange', (event: CustomEvent<Selection.ChangedRange>) => {
426
const rangeChange = event.detail;
427
console.log('Range selection changing:', rangeChange);
428
429
// Custom range selection logic
430
if (rangeChange.newRange && isRestrictedArea(rangeChange.newRange)) {
431
event.preventDefault();
432
showAccessDeniedMessage();
433
}
434
});
435
```
436
437
**ChangedRange** { .api }
438
```typescript
439
interface ChangedRange {
440
type: 'range' | 'focus';
441
newRange: RangeArea;
442
oldRange?: RangeArea;
443
source: 'keyboard' | 'mouse' | 'api';
444
}
445
```
446
447
## Event Management Patterns
448
449
### Event Handler Registration
450
451
```typescript
452
class GridEventManager {
453
private grid: HTMLRevoGridElement;
454
private eventHandlers: Map<string, Function[]> = new Map();
455
456
constructor(grid: HTMLRevoGridElement) {
457
this.grid = grid;
458
this.setupEventListeners();
459
}
460
461
private setupEventListeners() {
462
// Edit events
463
this.registerHandler('beforeedit', this.handleBeforeEdit.bind(this));
464
this.registerHandler('afteredit', this.handleAfterEdit.bind(this));
465
this.registerHandler('beforerangeedit', this.handleBeforeRangeEdit.bind(this));
466
467
// Focus events
468
this.registerHandler('beforecellfocus', this.handleBeforeCellFocus.bind(this));
469
this.registerHandler('afterfocus', this.handleAfterFocus.bind(this));
470
471
// Data events
472
this.registerHandler('beforesourceset', this.handleBeforeSourceSet.bind(this));
473
this.registerHandler('aftersourceset', this.handleAfterSourceSet.bind(this));
474
475
// Column events
476
this.registerHandler('aftercolumnresize', this.handleColumnResize.bind(this));
477
this.registerHandler('headerclick', this.handleHeaderClick.bind(this));
478
479
// Viewport events
480
this.registerHandler('viewportscroll', this.handleViewportScroll.bind(this));
481
}
482
483
private registerHandler(eventName: string, handler: Function) {
484
// Store handler reference for cleanup
485
if (!this.eventHandlers.has(eventName)) {
486
this.eventHandlers.set(eventName, []);
487
}
488
this.eventHandlers.get(eventName)!.push(handler);
489
490
// Register with grid
491
this.grid.addEventListener(eventName, handler as EventListener);
492
}
493
494
private handleBeforeEdit(event: CustomEvent<Edition.BeforeSaveDataDetails>) {
495
const detail = event.detail;
496
497
// Validation
498
if (!this.validateEdit(detail)) {
499
event.preventDefault();
500
return;
501
}
502
503
// Audit logging
504
this.logEditAttempt(detail);
505
506
// Business logic checks
507
if (!this.hasEditPermission(detail)) {
508
event.preventDefault();
509
this.showPermissionError();
510
return;
511
}
512
}
513
514
private handleAfterEdit(event: CustomEvent<Edition.BeforeSaveDataDetails>) {
515
const detail = event.detail;
516
517
// Update calculations
518
this.updateDependentCalculations(detail);
519
520
// Save to server
521
this.saveChangesToServer(detail);
522
523
// Notify other systems
524
this.broadcastDataChange(detail);
525
526
// Update audit trail
527
this.logSuccessfulEdit(detail);
528
}
529
530
private handleBeforeRangeEdit(event: CustomEvent<Edition.BeforeRangeSaveDataDetails>) {
531
const { data, range, source } = event.detail;
532
533
// Validate all changes in range
534
const invalidChanges = data.filter(change => !this.validateEdit(change));
535
536
if (invalidChanges.length > 0) {
537
event.preventDefault();
538
this.showRangeValidationErrors(invalidChanges);
539
return;
540
}
541
542
// Check permissions for range
543
if (!this.hasRangeEditPermission(range)) {
544
event.preventDefault();
545
this.showPermissionError();
546
return;
547
}
548
549
// Log range edit operation
550
this.logRangeEditAttempt(data, range, source);
551
}
552
553
private handleBeforeCellFocus(event: CustomEvent<Edition.BeforeSaveDataDetails>) {
554
const detail = event.detail;
555
556
// Custom focus restrictions
557
if (this.isCellFocusRestricted(detail)) {
558
event.preventDefault();
559
return;
560
}
561
}
562
563
private handleAfterFocus(event: CustomEvent<{model: any, column: RevoGrid.ColumnRegular}>) {
564
const { model, column } = event.detail;
565
566
// Update context panels
567
this.updateContextPanel(model, column);
568
569
// Update formula bar
570
this.updateFormulaBar(model, column);
571
572
// Trigger dependent UI updates
573
this.triggerFocusEvents(model, column);
574
}
575
576
private handleViewportScroll(event: CustomEvent<RevoGrid.ViewPortScrollEvent>) {
577
const scrollEvent = event.detail;
578
579
// Lazy loading
580
this.handleLazyLoading(scrollEvent);
581
582
// Sync external scrollbars
583
this.syncExternalScrollbars(scrollEvent);
584
585
// Performance monitoring
586
this.monitorScrollPerformance(scrollEvent);
587
}
588
589
// Utility methods
590
private validateEdit(detail: Edition.BeforeSaveDataDetails): boolean {
591
// Implementation details...
592
return true;
593
}
594
595
private hasEditPermission(detail: Edition.BeforeSaveDataDetails): boolean {
596
// Permission checking logic...
597
return true;
598
}
599
600
private updateDependentCalculations(detail: Edition.BeforeSaveDataDetails) {
601
// Update calculated fields, summaries, etc.
602
}
603
604
private saveChangesToServer(detail: Edition.BeforeSaveDataDetails) {
605
// API call to save changes
606
}
607
608
public destroy() {
609
// Clean up all event listeners
610
this.eventHandlers.forEach((handlers, eventName) => {
611
handlers.forEach(handler => {
612
this.grid.removeEventListener(eventName, handler as EventListener);
613
});
614
});
615
this.eventHandlers.clear();
616
}
617
}
618
```
619
620
### Event-Driven Architecture
621
622
```typescript
623
class EventDrivenGridSystem {
624
private grid: HTMLRevoGridElement;
625
private eventBus: EventBus;
626
627
constructor(grid: HTMLRevoGridElement) {
628
this.grid = grid;
629
this.eventBus = new EventBus();
630
this.setupEventForwarding();
631
}
632
633
private setupEventForwarding() {
634
// Forward grid events to event bus
635
const eventsToForward = [
636
'beforeedit', 'afteredit', 'beforerangeedit',
637
'beforecellfocus', 'afterfocus',
638
'beforesourceset', 'aftersourceset',
639
'beforecolumnsset', 'aftercolumnsset',
640
'viewportscroll'
641
];
642
643
eventsToForward.forEach(eventName => {
644
this.grid.addEventListener(eventName, (event) => {
645
this.eventBus.emit(`grid.${eventName}`, event.detail);
646
});
647
});
648
}
649
650
// Public API for subscribing to events
651
public on(eventName: string, handler: Function) {
652
this.eventBus.on(eventName, handler);
653
}
654
655
public off(eventName: string, handler: Function) {
656
this.eventBus.off(eventName, handler);
657
}
658
659
public once(eventName: string, handler: Function) {
660
this.eventBus.once(eventName, handler);
661
}
662
}
663
664
class EventBus {
665
private listeners: Map<string, Function[]> = new Map();
666
667
on(eventName: string, handler: Function) {
668
if (!this.listeners.has(eventName)) {
669
this.listeners.set(eventName, []);
670
}
671
this.listeners.get(eventName)!.push(handler);
672
}
673
674
off(eventName: string, handler: Function) {
675
const handlers = this.listeners.get(eventName);
676
if (handlers) {
677
const index = handlers.indexOf(handler);
678
if (index > -1) {
679
handlers.splice(index, 1);
680
}
681
}
682
}
683
684
once(eventName: string, handler: Function) {
685
const onceHandler = (...args: any[]) => {
686
handler(...args);
687
this.off(eventName, onceHandler);
688
};
689
this.on(eventName, onceHandler);
690
}
691
692
emit(eventName: string, data?: any) {
693
const handlers = this.listeners.get(eventName);
694
if (handlers) {
695
handlers.forEach(handler => handler(data));
696
}
697
}
698
}
699
```
700
701
### Event Middleware System
702
703
```typescript
704
interface EventMiddleware {
705
name: string;
706
priority: number;
707
handle(eventName: string, eventData: any, next: () => void): void;
708
}
709
710
class EventMiddlewareManager {
711
private middlewares: EventMiddleware[] = [];
712
private grid: HTMLRevoGridElement;
713
714
constructor(grid: HTMLRevoGridElement) {
715
this.grid = grid;
716
this.setupEventInterception();
717
}
718
719
addMiddleware(middleware: EventMiddleware) {
720
this.middlewares.push(middleware);
721
this.middlewares.sort((a, b) => b.priority - a.priority);
722
}
723
724
private setupEventInterception() {
725
const originalAddEventListener = this.grid.addEventListener.bind(this.grid);
726
727
this.grid.addEventListener = (eventName: string, handler: EventListener) => {
728
const wrappedHandler = (event: Event) => {
729
this.processEventThroughMiddleware(eventName, event, () => {
730
handler(event);
731
});
732
};
733
734
originalAddEventListener(eventName, wrappedHandler);
735
};
736
}
737
738
private processEventThroughMiddleware(
739
eventName: string,
740
eventData: any,
741
finalHandler: () => void
742
) {
743
let currentIndex = 0;
744
745
const next = () => {
746
if (currentIndex < this.middlewares.length) {
747
const middleware = this.middlewares[currentIndex++];
748
middleware.handle(eventName, eventData, next);
749
} else {
750
finalHandler();
751
}
752
};
753
754
next();
755
}
756
}
757
758
// Example middleware implementations
759
class LoggingMiddleware implements EventMiddleware {
760
name = 'logging';
761
priority = 100;
762
763
handle(eventName: string, eventData: any, next: () => void) {
764
console.log(`[${new Date().toISOString()}] Event: ${eventName}`, eventData);
765
next();
766
}
767
}
768
769
class ValidationMiddleware implements EventMiddleware {
770
name = 'validation';
771
priority = 200;
772
773
handle(eventName: string, eventData: any, next: () => void) {
774
if (eventName === 'beforeedit') {
775
if (!this.validateEdit(eventData.detail)) {
776
// Stop event propagation
777
eventData.preventDefault();
778
return;
779
}
780
}
781
next();
782
}
783
784
private validateEdit(detail: Edition.BeforeSaveDataDetails): boolean {
785
// Validation logic
786
return true;
787
}
788
}
789
790
class PermissionMiddleware implements EventMiddleware {
791
name = 'permission';
792
priority = 300;
793
794
handle(eventName: string, eventData: any, next: () => void) {
795
if (this.requiresPermissionCheck(eventName)) {
796
if (!this.hasPermission(eventName, eventData)) {
797
eventData.preventDefault();
798
return;
799
}
800
}
801
next();
802
}
803
804
private requiresPermissionCheck(eventName: string): boolean {
805
return ['beforeedit', 'beforerangeedit', 'beforecolumnmove'].includes(eventName);
806
}
807
808
private hasPermission(eventName: string, eventData: any): boolean {
809
// Permission checking logic
810
return true;
811
}
812
}
813
```
814
815
## Usage Examples
816
817
### Complete Event System Implementation
818
819
```typescript
820
class CompleteGridEventSystem {
821
private grid: HTMLRevoGridElement;
822
private eventManager: GridEventManager;
823
private eventBus: EventDrivenGridSystem;
824
private middlewareManager: EventMiddlewareManager;
825
826
constructor(grid: HTMLRevoGridElement) {
827
this.grid = grid;
828
this.initialize();
829
}
830
831
private initialize() {
832
// Setup core event management
833
this.eventManager = new GridEventManager(this.grid);
834
this.eventBus = new EventDrivenGridSystem(this.grid);
835
836
// Setup middleware
837
this.middlewareManager = new EventMiddlewareManager(this.grid);
838
this.setupMiddlewares();
839
840
// Setup custom event handlers
841
this.setupCustomEventHandlers();
842
}
843
844
private setupMiddlewares() {
845
this.middlewareManager.addMiddleware(new LoggingMiddleware());
846
this.middlewareManager.addMiddleware(new ValidationMiddleware());
847
this.middlewareManager.addMiddleware(new PermissionMiddleware());
848
}
849
850
private setupCustomEventHandlers() {
851
// Data synchronization
852
this.eventBus.on('grid.afteredit', (detail) => {
853
this.syncDataWithServer(detail);
854
});
855
856
// Real-time collaboration
857
this.eventBus.on('grid.beforeedit', (detail) => {
858
this.checkForConflicts(detail);
859
});
860
861
// Analytics tracking
862
this.eventBus.on('grid.viewportscroll', (detail) => {
863
this.trackScrollBehavior(detail);
864
});
865
866
// Auto-save functionality
867
this.setupAutoSave();
868
}
869
870
private setupAutoSave() {
871
let saveTimer: number;
872
let pendingChanges: Edition.BeforeSaveDataDetails[] = [];
873
874
this.eventBus.on('grid.afteredit', (detail: Edition.BeforeSaveDataDetails) => {
875
pendingChanges.push(detail);
876
877
// Clear existing timer
878
if (saveTimer) {
879
clearTimeout(saveTimer);
880
}
881
882
// Set new timer for auto-save
883
saveTimer = window.setTimeout(() => {
884
this.performAutoSave(pendingChanges);
885
pendingChanges = [];
886
}, 2000); // Auto-save after 2 seconds of inactivity
887
});
888
}
889
890
private async syncDataWithServer(detail: Edition.BeforeSaveDataDetails) {
891
try {
892
await this.apiClient.updateRecord(detail.model.id, {
893
[detail.prop]: detail.val
894
});
895
896
this.showSaveIndicator('success');
897
} catch (error) {
898
console.error('Sync failed:', error);
899
this.showSaveIndicator('error');
900
901
// Revert change on failure
902
await this.revertChange(detail);
903
}
904
}
905
906
private checkForConflicts(detail: Edition.BeforeSaveDataDetails) {
907
// Check if another user has modified this cell
908
const lastModified = this.getLastModified(detail.model.id, detail.prop);
909
const serverVersion = this.getServerVersion(detail.model.id, detail.prop);
910
911
if (lastModified !== serverVersion) {
912
// Show conflict resolution dialog
913
this.showConflictDialog(detail, serverVersion);
914
}
915
}
916
917
private trackScrollBehavior(detail: RevoGrid.ViewPortScrollEvent) {
918
// Send analytics data
919
this.analytics.track('grid_scroll', {
920
scrollTop: detail.scrollTop,
921
scrollLeft: detail.scrollLeft,
922
visibleRows: detail.range.rowEnd - detail.range.rowStart,
923
visibleCols: detail.range.colEnd - detail.range.colStart
924
});
925
}
926
927
private async performAutoSave(changes: Edition.BeforeSaveDataDetails[]) {
928
try {
929
await this.apiClient.batchUpdate(changes);
930
this.showSaveIndicator('auto-saved');
931
} catch (error) {
932
console.error('Auto-save failed:', error);
933
this.showSaveIndicator('auto-save-failed');
934
}
935
}
936
937
// Public API
938
public onDataChange(handler: (detail: Edition.BeforeSaveDataDetails) => void) {
939
this.eventBus.on('grid.afteredit', handler);
940
}
941
942
public onFocusChange(handler: (detail: any) => void) {
943
this.eventBus.on('grid.afterfocus', handler);
944
}
945
946
public onScroll(handler: (detail: RevoGrid.ViewPortScrollEvent) => void) {
947
this.eventBus.on('grid.viewportscroll', handler);
948
}
949
950
public destroy() {
951
this.eventManager.destroy();
952
// Clean up other components...
953
}
954
}
955
956
// Usage
957
const eventSystem = new CompleteGridEventSystem(grid);
958
959
// Subscribe to events
960
eventSystem.onDataChange((detail) => {
961
console.log('Data changed:', detail);
962
});
963
964
eventSystem.onFocusChange((detail) => {
965
console.log('Focus changed:', detail);
966
});
967
968
eventSystem.onScroll((detail) => {
969
console.log('Scrolled:', detail);
970
});
971
```
972
973
The event system provides comprehensive control over grid behavior and enables building complex, interactive applications with real-time features, validation, and external system integration.