Extracting Data

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

Field / MethodWhat it gives you
model.columnsVec<ColumnDef> — ordered column definitions
model.data.row_count()Total number of physical rows
model.data.get_cell(r, k)Cell value by physical row index and column key
model.data.get_cell_ref()Zero-copy Cow<str> variant (for in-memory sources)
model.sort_orderPhysical indices in current sort order (empty = natural order)
model.filtered_indicesPhysical indices passing all active filters (empty = no filter)
model.patchesEdited cell overrides (row, col_key) → value

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):

MethodDescription
export_patches() -> StringSerialise all edited cells as versioned TSV
import_patches(data: &str)Deserialise and apply patches, replacing existing ones

Format

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

ScenarioRecommendation
Small edits on a static datasetexport_patches — compact, fast
Full dataset persistenceIterate model.patches + your own serialisation
Server-side data sourceSend individual edits to your API via set_on_change