rs-grid does not include a built-in CSV/JSON export. Instead, GridModel
exposes all the data you need to build your own export in a few lines of code.
Key API surface
Basic example: export all rows
let row_count = model.data.row_count();
for row in 0..row_count {
for col in &model.columns {
// Check patches first, then fall back to data source
let value = model
.patches
.get(&(row, col.key.clone()))
.cloned()
.or_else(|| model.data.get_cell(row, &col.key))
.unwrap_or_default();
// Write `value` to your output (CSV writer, JSON array, etc.)
}
}
Respecting sort and filter
If you want the export to match what the user sees in the grid, iterate over
filtered_indices (if active) or sort_order (if active) instead of raw
row indices:
let indices: Vec<u64> = if !model.filtered_indices.is_empty() {
// Filtered indices are already in sort order
model.filtered_indices.clone()
} else if !model.sort_order.is_empty() {
model.sort_order.clone()
} else {
(0..model.data.row_count()).collect()
};
for &phys in &indices {
for col in &model.columns {
let value = model
.patches
.get(&(phys, col.key.clone()))
.cloned()
.or_else(|| model.data.get_cell(phys, &col.key))
.unwrap_or_default();
// ...
}
}
Column headers
Use ColumnDef fields for header labels:
let headers: Vec<&str> = model
.columns
.iter()
.map(|c| c.label.as_str())
.collect();
Server-side data sources
For PageCacheDataSource or custom server-side sources, only rows that have
been fetched into the local cache are available. Check cell_status() before
reading:
use rs_grid_core::datasource::CellStatus;
match model.data.cell_status(row, &col.key) {
CellStatus::Ready(val) => { /* use val */ }
CellStatus::Loading => { /* page not yet fetched */ }
CellStatus::Absent => { /* no value */ }
}
For a full export of server-side data, fetch all pages from your backend
directly rather than reading through the grid.
Persisting edits
GridCanvas exposes two methods to save and restore the patch layer
(the set of cells the user has edited):
The exported string starts with a version header followed by one line per
edited cell:
rs-grid-patches/v1
0 salary 42000
3 name Alice
7 country FR
Each data line is physical_row\tcol_key\tvalue. Tab, newline, and
backslash characters inside keys and values are escaped as \t, \n,
\\. The version header lets future releases migrate saved data without
silently corrupting it.
Note
import_patches also accepts legacy data without a version header
(produced before this format was introduced), so previously saved
patches remain loadable after an upgrade.
LocalStorage example (Leptos)
const LS_KEY: &str = "my-grid-patches";
// Save on every edit
let gc2 = gc.clone();
gc.set_on_change(move || {
if let Some(storage) = local_storage() {
let _ = storage.set_item(LS_KEY, &gc2.export_patches());
}
});
// Restore on mount
if let Some(storage) = local_storage() {
if let Ok(Some(data)) = storage.get_item(LS_KEY) {
gc.import_patches(&data);
}
}
File download / upload (Vanilla JS)
// Export → download
const tsv = grid.export_patches();
const url = URL.createObjectURL(
new Blob([tsv], { type: "text/tab-separated-values" })
);
Object.assign(document.createElement("a"), {
href: url, download: "patches.tsv"
}).click();
URL.revokeObjectURL(url);
// Import ← file input
fileInput.addEventListener("change", async (e) => {
const text = await e.target.files[0].text();
grid.import_patches(text);
});
When to use patches vs a full save