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.