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

data-management.mddocs/

0

# Data Management

1

2

RevoGrid provides comprehensive data management capabilities including reactive data sources, CRUD operations, and advanced data manipulation features.

3

4

## Data Source Types

5

6

### Primary Data Source

7

8

The main data source contains the core grid data:

9

10

```typescript { .api }

11

interface DataSourceConfig {

12

source: RevoGrid.DataType[];

13

}

14

```

15

16

**Basic Data Structure** { .api }

17

```typescript

18

type DataType = {[T in ColumnProp]: any};

19

type DataSource = DataType[];

20

type ColumnProp = string | number;

21

```

22

23

### Pinned Data Sources

24

25

Pinned rows remain visible during vertical scrolling:

26

27

```typescript { .api }

28

interface PinnedDataSources {

29

pinnedTopSource: RevoGrid.DataType[];

30

pinnedBottomSource: RevoGrid.DataType[];

31

}

32

```

33

34

**pinnedTopSource** { .api }

35

```typescript

36

pinnedTopSource: RevoGrid.DataType[] = []

37

```

38

Rows pinned to the top of the grid, useful for headers or summary rows.

39

40

**pinnedBottomSource** { .api }

41

```typescript

42

pinnedBottomSource: RevoGrid.DataType[] = []

43

```

44

Rows pinned to the bottom of the grid, useful for totals or footer information.

45

46

## Data Store System

47

48

### Observable Data Stores

49

50

RevoGrid uses reactive observable stores for state management:

51

52

```typescript { .api }

53

interface DataStore<T> extends Observable<T> {

54

get: (key: keyof T) => T[keyof T];

55

set: (key: keyof T, value: T[keyof T]) => void;

56

onChange: (key: keyof T, callback: (newValue: T[keyof T]) => void) => Subscription<T>;

57

}

58

```

59

60

### Row Source Store

61

62

Access the row data store for direct manipulation:

63

64

**getSourceStore** { .api }

65

```typescript

66

async getSourceStore(type?: RevoGrid.DimensionRows): Promise<RowSource>

67

```

68

69

```typescript { .api }

70

interface RowSource {

71

// Data access

72

getItems(): RevoGrid.DataType[];

73

setItems(items: RevoGrid.DataType[]): void;

74

75

// Individual row operations

76

getRow(index: number): RevoGrid.DataType;

77

setRow(index: number, data: RevoGrid.DataType): void;

78

79

// Batch operations

80

addRows(items: RevoGrid.DataType[], startIndex?: number): void;

81

removeRows(startIndex: number, count: number): void;

82

83

// Data transformation

84

refresh(): void;

85

clearData(): void;

86

}

87

```

88

89

### Data State Management

90

91

```typescript { .api }

92

interface DataSourceState<T, ST> {

93

// Core data

94

items: T[];

95

96

// Grouping information

97

groups: Groups;

98

99

// Trimmed/hidden rows

100

trimmed: Record<number, boolean>;

101

102

// Source metadata

103

type: ST;

104

105

// Reactive subscriptions

106

onChange: (callback: (state: DataSourceState<T, ST>) => void) => Subscription;

107

}

108

```

109

110

## CRUD Operations

111

112

### Reading Data

113

114

**Get Current Data** { .api }

115

```typescript

116

async getSource(type?: RevoGrid.DimensionRows): Promise<RevoGrid.DataType[]>

117

```

118

Retrieve the current data source for a specific dimension.

119

120

- **type**: Row dimension (`'rgRow'`, `'rowPinStart'`, `'rowPinEnd'`)

121

- **Returns**: Array of row data objects

122

123

**Get Visible Data** { .api }

124

```typescript

125

async getVisibleSource(type?: RevoGrid.DimensionRows): Promise<any[]>

126

```

127

Retrieve only visible data, excluding trimmed or filtered rows.

128

129

- **type**: Row dimension type

130

- **Returns**: Filtered array of visible row data

131

132

### Creating Data

133

134

```typescript

135

// Add new rows to the main source

136

const newRows = [

137

{ id: 101, name: 'New User', email: 'new@example.com' },

138

{ id: 102, name: 'Another User', email: 'another@example.com' }

139

];

140

141

// Direct assignment (triggers full refresh)

142

grid.source = [...grid.source, ...newRows];

143

144

// Using store for more control

145

const rowStore = await grid.getSourceStore();

146

rowStore.addRows(newRows);

147

```

148

149

### Updating Data

150

151

```typescript

152

// Update entire dataset

153

grid.source = updatedData;

154

155

// Update specific row via store

156

const rowStore = await grid.getSourceStore();

157

const updatedRow = { id: 1, name: 'Updated Name', email: 'updated@example.com' };

158

rowStore.setRow(0, updatedRow);

159

160

// Batch update multiple rows

161

const updates = [

162

{ index: 0, data: { id: 1, name: 'Updated 1' } },

163

{ index: 1, data: { id: 2, name: 'Updated 2' } }

164

];

165

166

updates.forEach(({ index, data }) => {

167

const currentRow = rowStore.getRow(index);

168

rowStore.setRow(index, { ...currentRow, ...data });

169

});

170

```

171

172

### Deleting Data

173

174

```typescript

175

// Remove rows by index

176

const rowStore = await grid.getSourceStore();

177

178

// Remove single row

179

rowStore.removeRows(5, 1); // Remove row at index 5

180

181

// Remove multiple rows

182

rowStore.removeRows(3, 3); // Remove 3 rows starting from index 3

183

184

// Clear all data

185

rowStore.clearData();

186

187

// Remove specific rows by condition

188

const filteredData = grid.source.filter(row => row.id !== targetId);

189

grid.source = filteredData;

190

```

191

192

## Reactive Data Updates

193

194

### Event-Driven Updates

195

196

Listen for data changes and respond accordingly:

197

198

```typescript

199

// Listen for data source changes

200

grid.addEventListener('aftersourceset', (event) => {

201

const { type, source } = event.detail;

202

console.log(`Data updated for ${type}:`, source);

203

});

204

205

// Listen for before data changes (can prevent)

206

grid.addEventListener('beforesourceset', (event) => {

207

const { type, source } = event.detail;

208

209

// Validate data before setting

210

if (!validateData(source)) {

211

event.preventDefault();

212

return;

213

}

214

});

215

```

216

217

### Store Subscriptions

218

219

Subscribe to store changes for reactive updates:

220

221

```typescript

222

const rowStore = await grid.getSourceStore();

223

224

// Subscribe to data changes

225

const subscription = rowStore.onChange((newState) => {

226

console.log('Data store updated:', newState);

227

228

// Perform side effects

229

updateExternalSystems(newState.items);

230

});

231

232

// Clean up subscription when done

233

subscription.unsubscribe();

234

```

235

236

## Data Transformation

237

238

### Sorting Data

239

240

**Programmatic Sorting** { .api }

241

```typescript

242

async updateColumnSorting(

243

column: RevoGrid.ColumnRegular,

244

index: number,

245

order: 'asc'|'desc',

246

additive: boolean

247

): Promise<RevoGrid.ColumnRegular>

248

```

249

250

```typescript

251

// Sort by single column

252

const nameColumn = grid.columns.find(col => col.prop === 'name');

253

await grid.updateColumnSorting(nameColumn, 0, 'asc', false);

254

255

// Add additional sort (multi-column)

256

const ageColumn = grid.columns.find(col => col.prop === 'age');

257

await grid.updateColumnSorting(ageColumn, 1, 'desc', true);

258

259

// Clear all sorting

260

await grid.clearSorting();

261

```

262

263

### Filtering Data

264

265

Configure filtering to show/hide rows based on criteria:

266

267

```typescript

268

// Enable basic filtering

269

grid.filter = true;

270

271

// Advanced filter configuration

272

grid.filter = {

273

collection: {

274

name: {

275

criteria: 'contains',

276

value: 'John'

277

},

278

age: {

279

criteria: '>',

280

value: 25

281

}

282

},

283

filterTypes: {

284

string: ['contains', 'equals', 'startsWith', 'endsWith'],

285

number: ['=', '>', '<', '>=', '<=', '!=']

286

}

287

};

288

```

289

290

### Trimming Rows

291

292

Hide specific rows without removing them from the data source:

293

294

**addTrimmed** { .api }

295

```typescript

296

async addTrimmed(

297

trimmed: Record<number, boolean>,

298

trimmedType?: string,

299

type?: RevoGrid.DimensionRows

300

): Promise<CustomEvent>

301

```

302

303

```typescript

304

// Hide specific rows by index

305

await grid.addTrimmed({

306

2: true, // Hide row at index 2

307

5: true, // Hide row at index 5

308

8: true // Hide row at index 8

309

});

310

311

// Set trimmed rows via property

312

grid.trimmedRows = {

313

0: true,

314

3: true,

315

7: true

316

};

317

318

// Listen for trimming events

319

grid.addEventListener('beforetrimmed', (event) => {

320

const { trimmed, trimmedType, type } = event.detail;

321

console.log('About to hide rows:', trimmed);

322

});

323

324

grid.addEventListener('aftertrimmed', (event) => {

325

console.log('Rows hidden successfully');

326

});

327

```

328

329

## Data Grouping

330

331

### Row Grouping Configuration

332

333

Group rows by shared property values:

334

335

```typescript { .api }

336

interface GroupingOptions {

337

props: RevoGrid.ColumnProp[];

338

expandedAll?: boolean;

339

groupTemplate?: GroupTemplateFunc;

340

}

341

```

342

343

```typescript

344

// Basic grouping by single property

345

grid.grouping = {

346

props: ['department'],

347

expandedAll: true

348

};

349

350

// Multi-level grouping

351

grid.grouping = {

352

props: ['department', 'team'],

353

expandedAll: false

354

};

355

356

// Custom group template

357

grid.grouping = {

358

props: ['category'],

359

expandedAll: true,

360

groupTemplate: (params) => {

361

const { model, column, data } = params;

362

return `${model.value} (${data.length} items)`;

363

}

364

};

365

```

366

367

### Groups State

368

369

Access grouping information through the data store:

370

371

```typescript { .api }

372

interface Groups {

373

[groupId: string]: {

374

ids: number[];

375

expanded: boolean;

376

level: number;

377

parent?: string;

378

};

379

}

380

```

381

382

## Advanced Data Operations

383

384

### Data Validation

385

386

Implement data validation for incoming changes:

387

388

```typescript

389

// Validate before edit

390

grid.addEventListener('beforeedit', (event) => {

391

const { model, prop, val } = event.detail;

392

393

// Custom validation logic

394

if (prop === 'email' && !isValidEmail(val)) {

395

event.preventDefault();

396

showError('Invalid email format');

397

return;

398

}

399

400

if (prop === 'age' && (val < 0 || val > 150)) {

401

event.preventDefault();

402

showError('Age must be between 0 and 150');

403

return;

404

}

405

});

406

407

// Validate before range edit

408

grid.addEventListener('beforerangeedit', (event) => {

409

const { data } = event.detail;

410

411

for (const change of data) {

412

if (!validateChange(change)) {

413

event.preventDefault();

414

return;

415

}

416

}

417

});

418

```

419

420

### Data Synchronization

421

422

Sync grid data with external systems:

423

424

```typescript

425

class DataSyncService {

426

private grid: HTMLRevoGridElement;

427

private apiClient: ApiClient;

428

429

constructor(grid: HTMLRevoGridElement, apiClient: ApiClient) {

430

this.grid = grid;

431

this.apiClient = apiClient;

432

this.setupSync();

433

}

434

435

private setupSync() {

436

// Sync after edits

437

this.grid.addEventListener('afteredit', async (event) => {

438

const { model, prop, val } = event.detail;

439

440

try {

441

await this.apiClient.updateRecord(model.id, { [prop]: val });

442

} catch (error) {

443

// Revert change on failure

444

await this.revertChange(model, prop);

445

showError('Failed to save changes');

446

}

447

});

448

449

// Periodic sync with server

450

setInterval(() => {

451

this.syncWithServer();

452

}, 30000);

453

}

454

455

private async syncWithServer() {

456

try {

457

const serverData = await this.apiClient.fetchData();

458

const currentData = await this.grid.getSource();

459

460

if (this.hasChanges(currentData, serverData)) {

461

this.grid.source = serverData;

462

}

463

} catch (error) {

464

console.error('Sync failed:', error);

465

}

466

}

467

468

private async revertChange(model: any, prop: string) {

469

const rowStore = await this.grid.getSourceStore();

470

const currentData = rowStore.getItems();

471

const rowIndex = currentData.findIndex(row => row.id === model.id);

472

473

if (rowIndex >= 0) {

474

// Restore original value

475

const originalValue = await this.getOriginalValue(model.id, prop);

476

const updatedRow = { ...currentData[rowIndex], [prop]: originalValue };

477

rowStore.setRow(rowIndex, updatedRow);

478

}

479

}

480

}

481

482

// Usage

483

const syncService = new DataSyncService(grid, apiClient);

484

```

485

486

### Data Caching

487

488

Implement intelligent data caching for performance:

489

490

```typescript

491

class DataCacheManager {

492

private cache = new Map<string, any>();

493

private grid: HTMLRevoGridElement;

494

495

constructor(grid: HTMLRevoGridElement) {

496

this.grid = grid;

497

this.setupCaching();

498

}

499

500

private setupCaching() {

501

// Cache data before changes

502

this.grid.addEventListener('beforesourceset', (event) => {

503

const { type, source } = event.detail;

504

const cacheKey = `${type}_backup`;

505

this.cache.set(cacheKey, [...source]);

506

});

507

508

// Clear cache after successful changes

509

this.grid.addEventListener('aftersourceset', (event) => {

510

const { type } = event.detail;

511

const cacheKey = `${type}_backup`;

512

// Keep backup for potential rollback

513

setTimeout(() => {

514

this.cache.delete(cacheKey);

515

}, 5000);

516

});

517

}

518

519

rollback(type: RevoGrid.DimensionRows = 'rgRow') {

520

const cacheKey = `${type}_backup`;

521

const cachedData = this.cache.get(cacheKey);

522

523

if (cachedData) {

524

if (type === 'rgRow') {

525

this.grid.source = cachedData;

526

} else if (type === 'rowPinStart') {

527

this.grid.pinnedTopSource = cachedData;

528

} else if (type === 'rowPinEnd') {

529

this.grid.pinnedBottomSource = cachedData;

530

}

531

}

532

}

533

534

clearCache() {

535

this.cache.clear();

536

}

537

}

538

```

539

540

## Usage Examples

541

542

### Complete Data Management Example

543

544

```typescript

545

class GridDataManager {

546

private grid: HTMLRevoGridElement;

547

private originalData: RevoGrid.DataType[] = [];

548

549

constructor(grid: HTMLRevoGridElement) {

550

this.grid = grid;

551

this.initialize();

552

}

553

554

async initialize() {

555

// Load initial data

556

const data = await this.fetchData();

557

this.originalData = [...data];

558

559

this.grid.source = data;

560

561

// Setup pinned rows

562

this.grid.pinnedTopSource = [

563

{ id: 'header', name: 'Summary', total: this.calculateTotal(data) }

564

];

565

566

// Configure grouping

567

this.grid.grouping = {

568

props: ['department'],

569

expandedAll: false

570

};

571

572

// Setup event handlers

573

this.setupEventHandlers();

574

}

575

576

private setupEventHandlers() {

577

// Handle data changes

578

this.grid.addEventListener('afteredit', (event) => {

579

this.handleDataChange(event.detail);

580

});

581

582

// Handle sorting changes

583

this.grid.addEventListener('beforesorting', (event) => {

584

this.handleSortChange(event.detail);

585

});

586

}

587

588

private async handleDataChange(detail: any) {

589

const { model, prop, val } = detail;

590

591

// Update external system

592

await this.saveToServer(model.id, { [prop]: val });

593

594

// Update summary

595

await this.updateSummary();

596

597

// Refresh dependent data

598

await this.refreshDependentData();

599

}

600

601

private async updateSummary() {

602

const currentData = await this.grid.getSource();

603

const total = this.calculateTotal(currentData);

604

605

this.grid.pinnedTopSource = [

606

{ id: 'header', name: 'Summary', total }

607

];

608

}

609

610

private calculateTotal(data: RevoGrid.DataType[]): number {

611

return data.reduce((sum, row) => sum + (row.amount || 0), 0);

612

}

613

614

async addNewRow(data: RevoGrid.DataType) {

615

const currentSource = await this.grid.getSource();

616

this.grid.source = [...currentSource, data];

617

618

// Scroll to new row

619

await this.grid.scrollToRow(currentSource.length);

620

621

// Focus on new row

622

await this.grid.setCellsFocus(

623

{ x: 0, y: currentSource.length }

624

);

625

}

626

627

async deleteRows(rowIndices: number[]) {

628

const currentSource = await this.grid.getSource();

629

const filteredData = currentSource.filter((_, index) =>

630

!rowIndices.includes(index)

631

);

632

633

this.grid.source = filteredData;

634

await this.updateSummary();

635

}

636

637

async restoreOriginalData() {

638

this.grid.source = [...this.originalData];

639

await this.updateSummary();

640

await this.grid.clearSorting();

641

}

642

}

643

```

644

645

The data management system in RevoGrid provides powerful capabilities for handling complex data scenarios with reactive updates, validation, and synchronization support.