or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cell-editing.mdcolumn-system.mdcore-grid.mddata-management.mdevent-system.mdindex.mdnavigation-scrolling.mdplugin-system.mdselection-focus.mdtypes-interfaces.md

plugin-system.mddocs/

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.