0
# Plugin System
1
2
RevoGrid features an extensible plugin architecture that allows you to add custom functionality and extend grid capabilities. The system includes built-in plugins for common features and supports custom plugin development.
3
4
## Plugin Architecture
5
6
### Base Plugin Class
7
8
All plugins extend from the base plugin class:
9
10
```typescript { .api }
11
class BasePlugin {
12
protected revogrid: HTMLRevoGridElement;
13
private subscriptions: Map<string, (e: CustomEvent) => void> = new Map();
14
15
constructor(revogrid: HTMLRevoGridElement) {
16
this.revogrid = revogrid;
17
}
18
19
addEventListener(name: string, func: (e: CustomEvent) => void): void;
20
removeEventListener(type: string): void;
21
emit(eventName: string, detail?: any): CustomEvent;
22
clearSubscriptions(): void;
23
destroy(): void;
24
}
25
```
26
27
**Constructor** { .api }
28
```typescript
29
constructor(revogrid: HTMLRevoGridElement)
30
```
31
Initialize plugin with reference to the grid instance.
32
33
**Event Management** { .api }
34
```typescript
35
addEventListener(name: string, func: (e: CustomEvent) => void): void
36
removeEventListener(type: string): void
37
emit(eventName: string, detail?: any): CustomEvent
38
```
39
Methods for managing event subscriptions and emitting custom events.
40
41
**Lifecycle Methods** { .api }
42
```typescript
43
clearSubscriptions(): void
44
destroy(): void
45
```
46
Cleanup methods for proper resource management.
47
48
### Plugin Types
49
50
```typescript { .api }
51
namespace RevoPlugin {
52
interface Plugin extends BasePlugin {}
53
type PluginClass = typeof BasePlugin;
54
}
55
```
56
57
**Plugin Registration** { .api }
58
```typescript
59
plugins: RevoPlugin.PluginClass[]
60
```
61
Array of plugin classes to be instantiated when the grid initializes.
62
63
## Built-in Plugins
64
65
### AutoSize Plugin
66
67
Automatically resize columns based on content:
68
69
```typescript { .api }
70
interface AutoSizeColumnConfig {
71
// Enable auto-sizing
72
enable?: boolean;
73
74
// Auto-size mode
75
mode?: ColumnAutoSizeMode;
76
77
// Columns to include/exclude
78
includeColumns?: RevoGrid.ColumnProp[];
79
excludeColumns?: RevoGrid.ColumnProp[];
80
81
// Size constraints
82
maxSize?: number;
83
minSize?: number;
84
85
// Performance options
86
sampleSize?: number;
87
}
88
```
89
90
**ColumnAutoSizeMode** { .api }
91
```typescript
92
type ColumnAutoSizeMode =
93
| 'click' // Double-click header separator
94
| 'content' // Auto-size based on content
95
| 'header' // Auto-size based on header
96
| 'both'; // Auto-size based on both content and header
97
```
98
99
```typescript
100
// Enable basic auto-sizing
101
grid.autoSizeColumn = true;
102
103
// Advanced auto-size configuration
104
grid.autoSizeColumn = {
105
mode: 'both',
106
includeColumns: ['name', 'email', 'description'],
107
maxSize: 300,
108
minSize: 80,
109
sampleSize: 100 // Sample first 100 rows for performance
110
};
111
```
112
113
### Filter Plugin
114
115
Provide column filtering functionality:
116
117
```typescript { .api }
118
interface ColumnFilterConfig {
119
// Filter collection
120
collection?: FilterCollection;
121
122
// Available filter types
123
filterTypes?: Record<string, string[]>;
124
125
// UI customization
126
captions?: FilterCaptions;
127
128
// Custom filters
129
customFilters?: Record<string, CustomFilter>;
130
131
// Multi-column filter logic
132
multiFilterLogic?: 'and' | 'or';
133
}
134
```
135
136
**FilterCollection** { .api }
137
```typescript
138
interface FilterCollection {
139
[columnProp: string]: {
140
criteria: string;
141
value: any;
142
type?: string;
143
};
144
}
145
```
146
147
**Custom Filter Types** { .api }
148
```typescript
149
interface CustomFilter {
150
name: string;
151
func: LogicFunction;
152
extra?: any;
153
}
154
155
type LogicFunction = (value: any, criteria: any, extra?: any) => boolean;
156
```
157
158
```typescript
159
// Enable basic filtering
160
grid.filter = true;
161
162
// Advanced filter configuration
163
grid.filter = {
164
collection: {
165
name: { criteria: 'contains', value: 'John' },
166
age: { criteria: '>', value: 25 }
167
},
168
filterTypes: {
169
string: ['contains', 'equals', 'startsWith', 'endsWith'],
170
number: ['=', '>', '<', '>=', '<=', '!='],
171
date: ['=', '>', '<', 'between']
172
},
173
customFilters: {
174
'custom-range': {
175
name: 'Custom Range',
176
func: (value, criteria) => {
177
const [min, max] = criteria;
178
return value >= min && value <= max;
179
}
180
}
181
},
182
multiFilterLogic: 'and'
183
};
184
```
185
186
### Export Plugin
187
188
Enable data export functionality:
189
190
```typescript { .api }
191
interface DataInput {
192
// Data to export
193
data: RevoGrid.DataType[];
194
195
// Columns to include
196
columns: RevoGrid.ColumnRegular[];
197
198
// Export format options
199
format?: ExportFormat;
200
}
201
```
202
203
**Export Methods** { .api }
204
```typescript
205
class ExportPlugin extends BasePlugin {
206
exportString(): string;
207
exportBlob(): Blob;
208
exportFile(filename?: string): void;
209
}
210
```
211
212
```typescript
213
// Enable export functionality
214
grid.exporting = true;
215
216
// Programmatic export
217
const exportPlugin = grid.getPlugins().find(p => p instanceof ExportPlugin);
218
if (exportPlugin) {
219
// Export as string
220
const csvString = exportPlugin.exportString();
221
222
// Export as blob
223
const blob = exportPlugin.exportBlob();
224
225
// Download file
226
exportPlugin.exportFile('my-data.csv');
227
}
228
229
// Listen for export events
230
grid.addEventListener('beforeexport', (event) => {
231
const dataInput: DataInput = event.detail;
232
console.log('Exporting data:', dataInput);
233
234
// Modify export data if needed
235
dataInput.data = dataInput.data.filter(row => row.visible !== false);
236
});
237
```
238
239
### Grouping Plugin
240
241
Enable row grouping functionality:
242
243
```typescript { .api }
244
interface GroupingOptions {
245
// Properties to group by
246
props: RevoGrid.ColumnProp[];
247
248
// Initial expand state
249
expandedAll?: boolean;
250
251
// Custom group template
252
groupTemplate?: GroupTemplateFunc;
253
254
// Group aggregation functions
255
aggregations?: Record<RevoGrid.ColumnProp, AggregationFunc>;
256
}
257
```
258
259
```typescript
260
// Basic grouping
261
grid.grouping = {
262
props: ['department'],
263
expandedAll: true
264
};
265
266
// Advanced grouping with custom template
267
grid.grouping = {
268
props: ['department', 'team'],
269
expandedAll: false,
270
groupTemplate: (params) => {
271
const { model, column, data } = params;
272
const count = data.length;
273
const total = data.reduce((sum, item) => sum + (item.salary || 0), 0);
274
275
return `${model.value} (${count} employees, Total: $${total.toLocaleString()})`;
276
},
277
aggregations: {
278
salary: (group) => group.reduce((sum, item) => sum + item.salary, 0),
279
age: (group) => group.reduce((sum, item) => sum + item.age, 0) / group.length
280
}
281
};
282
```
283
284
### Sorting Plugin
285
286
Handle column sorting functionality:
287
288
```typescript
289
// Sorting is automatically enabled when columns have sortable: true
290
grid.columns = [
291
{ prop: 'name', name: 'Name', sortable: true },
292
{ prop: 'age', name: 'Age', sortable: true },
293
{ prop: 'email', name: 'Email', sortable: false }
294
];
295
296
// Listen for sorting events
297
grid.addEventListener('beforesorting', (event) => {
298
const { column, order, additive } = event.detail;
299
console.log(`Sorting ${column.name} in ${order} order`);
300
301
// Prevent sorting if needed
302
if (column.prop === 'restricted') {
303
event.preventDefault();
304
}
305
});
306
307
grid.addEventListener('beforesortingapply', (event) => {
308
const { column, order, additive } = event.detail;
309
310
// Custom sorting logic
311
if (column.prop === 'custom') {
312
event.preventDefault();
313
// Implement custom sort
314
this.customSort(column, order);
315
}
316
});
317
```
318
319
### Stretch Plugin
320
321
Automatically stretch columns to fill available space:
322
323
```typescript
324
// Enable column stretching
325
grid.stretch = true;
326
327
// Or use specific stretch strategy
328
grid.stretch = 'last'; // Stretch only the last column
329
grid.stretch = 'all'; // Stretch all columns proportionally
330
```
331
332
### Move Column Plugin
333
334
Enable column drag and drop reordering:
335
336
```typescript
337
// Enable column moving
338
grid.canMoveColumns = true;
339
340
// Listen for column reordering
341
grid.addEventListener('beforecolumnmove', (event) => {
342
const { from, to } = event.detail;
343
console.log(`Moving column from ${from} to ${to}`);
344
345
// Prevent moving certain columns
346
if (from === 0) { // Don't allow moving first column
347
event.preventDefault();
348
}
349
});
350
351
grid.addEventListener('aftercolumnmove', (event) => {
352
const { from, to, columns } = event.detail;
353
console.log('Column moved successfully:', { from, to });
354
355
// Save new column order
356
this.saveColumnOrder(columns);
357
});
358
```
359
360
## Custom Plugin Development
361
362
### Creating a Custom Plugin
363
364
```typescript
365
class CustomHighlightPlugin extends BasePlugin {
366
private highlightedCells: Set<string> = new Set();
367
368
constructor(revogrid: HTMLRevoGridElement) {
369
super(revogrid);
370
this.init();
371
}
372
373
private init() {
374
// Listen for cell events
375
this.addEventListener('afterfocus', (event) => {
376
this.handleCellFocus(event.detail);
377
});
378
379
// Listen for custom highlight events
380
this.addEventListener('highlight-cell', (event) => {
381
this.highlightCell(event.detail);
382
});
383
384
// Add custom styles
385
this.addStyles();
386
}
387
388
private handleCellFocus(focusData: any) {
389
const { cell, column } = focusData;
390
391
// Highlight related cells based on custom logic
392
if (column.type === 'related') {
393
this.highlightRelatedCells(cell, column);
394
}
395
}
396
397
private highlightCell(cellData: { x: number; y: number; type: string }) {
398
const cellId = `${cellData.x}-${cellData.y}`;
399
400
if (cellData.type === 'add') {
401
this.highlightedCells.add(cellId);
402
} else if (cellData.type === 'remove') {
403
this.highlightedCells.delete(cellId);
404
}
405
406
this.updateCellStyles();
407
}
408
409
private highlightRelatedCells(cell: Selection.Cell, column: RevoGrid.ColumnRegular) {
410
// Custom logic to find and highlight related cells
411
const relatedProp = column.relatedColumn;
412
if (relatedProp) {
413
// Find all cells in related column with same value
414
this.findAndHighlightBySameValue(cell, relatedProp);
415
}
416
}
417
418
private async findAndHighlightBySameValue(cell: Selection.Cell, relatedProp: string) {
419
const data = await this.revogrid.getSource();
420
const columns = await this.revogrid.getColumns();
421
422
const currentValue = data[cell.y][relatedProp];
423
const relatedColumnIndex = columns.findIndex(col => col.prop === relatedProp);
424
425
if (relatedColumnIndex >= 0) {
426
data.forEach((row, rowIndex) => {
427
if (row[relatedProp] === currentValue) {
428
const cellId = `${relatedColumnIndex}-${rowIndex}`;
429
this.highlightedCells.add(cellId);
430
}
431
});
432
433
this.updateCellStyles();
434
}
435
}
436
437
private updateCellStyles() {
438
// Apply highlighting styles to cells
439
const cells = this.revogrid.querySelectorAll('[data-cell-id]');
440
441
cells.forEach(cell => {
442
const cellId = cell.getAttribute('data-cell-id');
443
if (cellId && this.highlightedCells.has(cellId)) {
444
cell.classList.add('custom-highlight');
445
} else {
446
cell.classList.remove('custom-highlight');
447
}
448
});
449
}
450
451
private addStyles() {
452
const style = document.createElement('style');
453
style.textContent = `
454
.custom-highlight {
455
background-color: rgba(255, 255, 0, 0.3) !important;
456
border: 2px solid #ffcc00 !important;
457
}
458
`;
459
document.head.appendChild(style);
460
}
461
462
// Public API
463
public highlightCellsBy(predicate: (rowData: any, rowIndex: number) => boolean) {
464
this.revogrid.getSource().then(data => {
465
data.forEach((row, index) => {
466
if (predicate(row, index)) {
467
// Highlight all cells in this row
468
this.revogrid.getColumns().then(columns => {
469
columns.forEach((col, colIndex) => {
470
const cellId = `${colIndex}-${index}`;
471
this.highlightedCells.add(cellId);
472
});
473
this.updateCellStyles();
474
});
475
}
476
});
477
});
478
}
479
480
public clearHighlights() {
481
this.highlightedCells.clear();
482
this.updateCellStyles();
483
}
484
485
public destroy() {
486
this.clearHighlights();
487
super.destroy();
488
}
489
}
490
```
491
492
### Plugin with Configuration
493
494
```typescript
495
interface ValidationPluginConfig {
496
rules: Record<RevoGrid.ColumnProp, ValidationRule[]>;
497
showErrors: boolean;
498
errorStyle: 'border' | 'background' | 'icon';
499
realTimeValidation: boolean;
500
}
501
502
interface ValidationRule {
503
type: 'required' | 'min' | 'max' | 'pattern' | 'custom';
504
value?: any;
505
message: string;
506
validator?: (value: any, row: any) => boolean;
507
}
508
509
class ValidationPlugin extends BasePlugin {
510
private config: ValidationPluginConfig;
511
private errors: Map<string, string[]> = new Map();
512
513
constructor(revogrid: HTMLRevoGridElement, config: ValidationPluginConfig) {
514
super(revogrid);
515
this.config = config;
516
this.init();
517
}
518
519
private init() {
520
// Listen for edit events if real-time validation is enabled
521
if (this.config.realTimeValidation) {
522
this.addEventListener('beforeedit', (event) => {
523
this.validateEdit(event);
524
});
525
}
526
527
// Listen for range edits
528
this.addEventListener('beforerangeedit', (event) => {
529
this.validateRangeEdit(event);
530
});
531
532
// Add validation styles
533
this.addValidationStyles();
534
}
535
536
private validateEdit(event: CustomEvent) {
537
const detail = event.detail;
538
const errors = this.validateCell(detail.prop, detail.val, detail.model, detail.rowIndex);
539
540
if (errors.length > 0) {
541
event.preventDefault();
542
this.showValidationErrors(detail, errors);
543
} else {
544
this.clearCellErrors(detail.prop, detail.rowIndex);
545
}
546
}
547
548
private validateRangeEdit(event: CustomEvent) {
549
const { data } = event.detail;
550
let hasErrors = false;
551
552
data.forEach(change => {
553
const errors = this.validateCell(
554
change.prop,
555
change.val,
556
change.model,
557
change.rowIndex
558
);
559
560
if (errors.length > 0) {
561
hasErrors = true;
562
this.setCellErrors(change.prop, change.rowIndex, errors);
563
}
564
});
565
566
if (hasErrors) {
567
event.preventDefault();
568
this.showRangeValidationErrors();
569
}
570
}
571
572
private validateCell(
573
prop: RevoGrid.ColumnProp,
574
value: any,
575
row: any,
576
rowIndex: number
577
): string[] {
578
const rules = this.config.rules[prop] || [];
579
const errors: string[] = [];
580
581
rules.forEach(rule => {
582
if (!this.validateRule(rule, value, row)) {
583
errors.push(rule.message);
584
}
585
});
586
587
return errors;
588
}
589
590
private validateRule(rule: ValidationRule, value: any, row: any): boolean {
591
switch (rule.type) {
592
case 'required':
593
return value !== null && value !== undefined && value !== '';
594
595
case 'min':
596
return Number(value) >= rule.value;
597
598
case 'max':
599
return Number(value) <= rule.value;
600
601
case 'pattern':
602
return new RegExp(rule.value).test(String(value));
603
604
case 'custom':
605
return rule.validator ? rule.validator(value, row) : true;
606
607
default:
608
return true;
609
}
610
}
611
612
private setCellErrors(prop: RevoGrid.ColumnProp, rowIndex: number, errors: string[]) {
613
const cellKey = `${prop}-${rowIndex}`;
614
this.errors.set(cellKey, errors);
615
this.updateCellErrorDisplay(prop, rowIndex);
616
}
617
618
private clearCellErrors(prop: RevoGrid.ColumnProp, rowIndex: number) {
619
const cellKey = `${prop}-${rowIndex}`;
620
this.errors.delete(cellKey);
621
this.updateCellErrorDisplay(prop, rowIndex);
622
}
623
624
private updateCellErrorDisplay(prop: RevoGrid.ColumnProp, rowIndex: number) {
625
const cellKey = `${prop}-${rowIndex}`;
626
const hasErrors = this.errors.has(cellKey);
627
628
// Find and update cell element
629
const cell = this.findCellElement(prop, rowIndex);
630
if (cell) {
631
if (hasErrors) {
632
cell.classList.add('validation-error');
633
if (this.config.showErrors) {
634
this.showErrorTooltip(cell, this.errors.get(cellKey)!);
635
}
636
} else {
637
cell.classList.remove('validation-error');
638
this.hideErrorTooltip(cell);
639
}
640
}
641
}
642
643
private findCellElement(prop: RevoGrid.ColumnProp, rowIndex: number): HTMLElement | null {
644
// Implementation to find cell element in DOM
645
return this.revogrid.querySelector(`[data-prop="${prop}"][data-row="${rowIndex}"]`);
646
}
647
648
private showErrorTooltip(cell: HTMLElement, errors: string[]) {
649
const tooltip = document.createElement('div');
650
tooltip.className = 'validation-tooltip';
651
tooltip.textContent = errors.join(', ');
652
653
cell.appendChild(tooltip);
654
}
655
656
private hideErrorTooltip(cell: HTMLElement) {
657
const tooltip = cell.querySelector('.validation-tooltip');
658
if (tooltip) {
659
tooltip.remove();
660
}
661
}
662
663
private addValidationStyles() {
664
const style = document.createElement('style');
665
style.textContent = `
666
.validation-error {
667
${this.getErrorStyle()}
668
}
669
670
.validation-tooltip {
671
position: absolute;
672
background: #ff4444;
673
color: white;
674
padding: 4px 8px;
675
border-radius: 4px;
676
font-size: 12px;
677
z-index: 1000;
678
white-space: nowrap;
679
top: 100%;
680
left: 0;
681
}
682
`;
683
document.head.appendChild(style);
684
}
685
686
private getErrorStyle(): string {
687
switch (this.config.errorStyle) {
688
case 'border':
689
return 'border: 2px solid #ff4444 !important;';
690
case 'background':
691
return 'background-color: rgba(255, 68, 68, 0.2) !important;';
692
case 'icon':
693
return 'position: relative; &::after { content: "⚠"; color: #ff4444; position: absolute; right: 2px; top: 2px; }';
694
default:
695
return 'border: 2px solid #ff4444 !important;';
696
}
697
}
698
699
// Public API
700
public validateAll(): boolean {
701
let isValid = true;
702
this.errors.clear();
703
704
this.revogrid.getSource().then(data => {
705
this.revogrid.getColumns().then(columns => {
706
data.forEach((row, rowIndex) => {
707
columns.forEach(column => {
708
if (this.config.rules[column.prop]) {
709
const errors = this.validateCell(column.prop, row[column.prop], row, rowIndex);
710
if (errors.length > 0) {
711
isValid = false;
712
this.setCellErrors(column.prop, rowIndex, errors);
713
}
714
}
715
});
716
});
717
});
718
});
719
720
return isValid;
721
}
722
723
public getErrors(): Record<string, string[]> {
724
return Object.fromEntries(this.errors);
725
}
726
727
public clearAllErrors() {
728
this.errors.clear();
729
// Clear all error displays
730
const errorCells = this.revogrid.querySelectorAll('.validation-error');
731
errorCells.forEach(cell => {
732
cell.classList.remove('validation-error');
733
this.hideErrorTooltip(cell as HTMLElement);
734
});
735
}
736
}
737
```
738
739
## Plugin Registration and Management
740
741
### Registering Plugins
742
743
```typescript
744
// Register plugins during grid initialization
745
grid.plugins = [
746
CustomHighlightPlugin,
747
ValidationPlugin,
748
// Other custom plugins
749
];
750
751
// Or register plugins with configuration
752
class ConfigurablePlugin extends BasePlugin {
753
constructor(revogrid: HTMLRevoGridElement, config: any) {
754
super(revogrid);
755
// Use config in plugin
756
}
757
}
758
759
// Plugin factory for configuration
760
function createValidationPlugin(config: ValidationPluginConfig) {
761
return class extends ValidationPlugin {
762
constructor(revogrid: HTMLRevoGridElement) {
763
super(revogrid, config);
764
}
765
};
766
}
767
768
grid.plugins = [
769
createValidationPlugin({
770
rules: {
771
email: [
772
{ type: 'required', message: 'Email is required' },
773
{ type: 'pattern', value: '^[^@]+@[^@]+\\.[^@]+$', message: 'Invalid email format' }
774
],
775
age: [
776
{ type: 'min', value: 0, message: 'Age must be positive' },
777
{ type: 'max', value: 150, message: 'Age must be realistic' }
778
]
779
},
780
showErrors: true,
781
errorStyle: 'border',
782
realTimeValidation: true
783
})
784
];
785
```
786
787
### Plugin Lifecycle Management
788
789
```typescript
790
// Get active plugins
791
const plugins = await grid.getPlugins();
792
793
// Find specific plugin
794
const validationPlugin = plugins.find(p => p instanceof ValidationPlugin);
795
796
// Plugin communication
797
class PluginManager {
798
private grid: HTMLRevoGridElement;
799
private pluginInstances: Map<string, BasePlugin> = new Map();
800
801
constructor(grid: HTMLRevoGridElement) {
802
this.grid = grid;
803
this.setupPluginCommunication();
804
}
805
806
private setupPluginCommunication() {
807
// Central event hub for plugin communication
808
this.grid.addEventListener('plugin-message', (event) => {
809
this.handlePluginMessage(event.detail);
810
});
811
}
812
813
private handlePluginMessage(message: { from: string; to: string; data: any }) {
814
const targetPlugin = this.pluginInstances.get(message.to);
815
if (targetPlugin) {
816
targetPlugin.emit('plugin-message-received', {
817
from: message.from,
818
data: message.data
819
});
820
}
821
}
822
823
registerPlugin(name: string, plugin: BasePlugin) {
824
this.pluginInstances.set(name, plugin);
825
}
826
827
sendMessage(from: string, to: string, data: any) {
828
this.grid.emit('plugin-message', { from, to, data });
829
}
830
}
831
```
832
833
## Usage Examples
834
835
### Complete Plugin System Setup
836
837
```typescript
838
class GridPluginSystem {
839
private grid: HTMLRevoGridElement;
840
private pluginManager: PluginManager;
841
842
constructor(grid: HTMLRevoGridElement) {
843
this.grid = grid;
844
this.setupPlugins();
845
}
846
847
private setupPlugins() {
848
// Configure built-in plugins
849
this.grid.autoSizeColumn = {
850
mode: 'both',
851
maxSize: 300,
852
minSize: 80
853
};
854
855
this.grid.filter = {
856
filterTypes: {
857
string: ['contains', 'equals', 'startsWith', 'endsWith'],
858
number: ['=', '>', '<', '>=', '<=', '!='],
859
date: ['=', '>', '<', 'between']
860
},
861
multiFilterLogic: 'and'
862
};
863
864
this.grid.exporting = true;
865
this.grid.canMoveColumns = true;
866
867
// Register custom plugins
868
this.grid.plugins = [
869
CustomHighlightPlugin,
870
createValidationPlugin({
871
rules: {
872
email: [
873
{ type: 'required', message: 'Email is required' },
874
{ type: 'pattern', value: '^[^@]+@[^@]+\\.[^@]+$', message: 'Invalid email' }
875
]
876
},
877
showErrors: true,
878
errorStyle: 'border',
879
realTimeValidation: true
880
})
881
];
882
883
// Setup plugin manager
884
this.pluginManager = new PluginManager(this.grid);
885
this.setupPluginInteractions();
886
}
887
888
private async setupPluginInteractions() {
889
// Wait for plugins to initialize
890
await new Promise(resolve => setTimeout(resolve, 100));
891
892
const plugins = await this.grid.getPlugins();
893
894
// Register plugins with manager
895
plugins.forEach((plugin, index) => {
896
const pluginName = plugin.constructor.name;
897
this.pluginManager.registerPlugin(pluginName, plugin);
898
});
899
900
// Setup plugin interactions
901
this.setupHighlightValidationSync();
902
}
903
904
private setupHighlightValidationSync() {
905
// Sync highlighting with validation errors
906
this.grid.addEventListener('validation-error', (event) => {
907
const { prop, rowIndex, errors } = event.detail;
908
909
if (errors.length > 0) {
910
// Highlight invalid cells
911
this.pluginManager.sendMessage('ValidationPlugin', 'CustomHighlightPlugin', {
912
action: 'highlight',
913
cells: [{ x: this.getColumnIndex(prop), y: rowIndex }],
914
type: 'error'
915
});
916
} else {
917
// Remove highlight from valid cells
918
this.pluginManager.sendMessage('ValidationPlugin', 'CustomHighlightPlugin', {
919
action: 'unhighlight',
920
cells: [{ x: this.getColumnIndex(prop), y: rowIndex }]
921
});
922
}
923
});
924
}
925
926
private getColumnIndex(prop: RevoGrid.ColumnProp): number {
927
return this.grid.columns.findIndex(col => col.prop === prop);
928
}
929
}
930
931
// Initialize the complete system
932
const pluginSystem = new GridPluginSystem(grid);
933
```
934
935
The plugin system provides extensive capabilities for extending grid functionality with both built-in and custom plugins, enabling complex interactions and behaviors while maintaining clean separation of concerns.