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

event-system.mddocs/

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.