Skip to content

GeoLeaf.Table — Table Module Documentation ​

WARNING

The data table is no longer part of @geoleaf/core: it is provided by the MIT plugin @geoleaf-plugins/table. Install it with npm install @geoleaf-plugins/table, then load its script after geoleaf.esm.js and before GeoLeaf.boot().

The GeoLeaf.Table module provides a tabular view of map data, complementing the display on the MapLibre GL JS map.

It can:

  • Display the attributes of GeoJSON features in a table
  • Sort data by column (ascending → descending → none cycle)
  • Select features and synchronize with the map
  • Zoom to the selected features
  • Highlight the selection on the map
  • Export the selection (through an event)

1. Functional role of the Table ​

GeoLeaf.Table has five main responsibilities:

  1. Display the data of GeoJSON layers in tabular form
  2. Synchronize the selection between table and map
  3. Allow multi-column sorting with cycles (asc → desc → null)
  4. Handle multiple selection with export and zoom
  5. Integrate with the GeoJSON, Filters and Core modules

Important: the Table module requires GeoLeaf.GeoJSON to be loaded and configured.


2. Public API of GeoLeaf.Table ​

2.1 Initialization ​

The plugin initializes itself: loading it mounts GeoLeaf.Table, and the panel is wired when the map is ready. There is no init call to make, and GeoLeaf.Table has no init member.

The panel is built at startup when modules.table.defaultVisible is true, otherwise at the first click on the toolbar button.

Configuration — the modules.table block of the profile:

  • enabled: boolean — enable the module (default: true)
  • showButton: boolean — toolbar button (default: true)
  • defaultVisible: boolean — visible on startup (default: false)
  • maxRowsPerLayer: number — row limit (default: 30000)
  • enableExportButton: boolean — export button (default: true)
  • defaultHeight: string — default height (default: '40%')
  • minHeight: string — minimum height (default: '20%')
  • maxHeight: string — maximum height (default: '60%')
  • resizable: boolean — resizable (default: true)

Minimal example ​

js
// Nothing to initialize: pick a layer and show the panel
GeoLeaf.Table.setLayer("restaurants");
GeoLeaf.Table.show();

Example with configuration ​

json
{
    "modules": {
        "table": {
            "defaultVisible": true,
            "maxRowsPerLayer": 2000,
            "defaultHeight": "50%",
            "resizable": true
        }
    }
}

2.2 GeoLeaf.Table.show() ​

Shows the table.

js
GeoLeaf.Table.show();

Events emitted: geoleaf:table:opened

Example ​

js
document.getElementById("show-table-btn").addEventListener("click", () => {
    GeoLeaf.Table.show();
});

2.3 GeoLeaf.Table.hide() ​

Hides the table. Also turns off the active highlight.

js
GeoLeaf.Table.hide();

Events emitted: geoleaf:table:closed

Example ​

js
document.getElementById("hide-table-btn").addEventListener("click", () => {
    GeoLeaf.Table.hide();
});

2.4 GeoLeaf.Table.toggle() ​

Toggles table visibility (shows it if hidden, hides it if shown).

js
GeoLeaf.Table.toggle();

Example ​

js
document.getElementById("toggle-table-btn").addEventListener("click", () => {
    GeoLeaf.Table.toggle();
});

2.5 GeoLeaf.Table.setLayer(layerId) ​

Sets the GeoJSON layer to display in the table.

js
GeoLeaf.Table.setLayer(layerId);

Parameters:

  • layerId: string — ID of the GeoJSON layer (or null to clear)

Events emitted: geoleaf:table:layerChanged with { layerId }

Note: only layers declared in the GeoJSON configuration can be displayed. setLayer resets the selection and the sort.

Example ​

js
document.getElementById("layer-select").addEventListener("change", (e) => {
    GeoLeaf.Table.setLayer(e.target.value);
});

Layer configuration for Table ​

In geoleaf.config.json:

json
{
    "geojson": {
        "layers": [
            {
                "id": "restaurants",
                "url": "data/restaurants.geojson",
                "table": {
                    "enabled": true,
                    "columns": [
                        {
                            "field": "properties.name",
                            "label": "Nom",
                            "width": "30%",
                            "sortable": true
                        },
                        {
                            "field": "properties.category",
                            "label": "Catégorie",
                            "width": "20%",
                            "sortable": true
                        },
                        {
                            "field": "properties.rating",
                            "label": "Note",
                            "width": "15%",
                            "sortable": true
                        }
                    ],
                    "defaultSort": {
                        "field": "properties.name",
                        "direction": "asc"
                    }
                }
            }
        ]
    }
}

2.6 GeoLeaf.Table.refresh() ​

Refreshes the data displayed in the table.

js
GeoLeaf.Table.refresh();

Retrieves the features of the current layer through GeoLeaf.GeoJSON.getLayerData(), reapplies the sort and re-renders the table.

Example ​

js
// Refresh after a filter change
map.on("geoleaf:filters:changed", () => {
    GeoLeaf.Table.refresh();
});

2.7 GeoLeaf.Table.sortByField(field) ​

Changes the sort on a given column.

js
GeoLeaf.Table.sortByField(field);

Parameters:

  • field: string — field path (dot notation: properties.name)

Behavior: sort cycle on the same column:

  1. First call: ascending sort
  2. Second call: descending sort
  3. Third call: no sort (original order)

Events emitted: geoleaf:table:sortChanged with { field, direction }

Example ​

js
document.querySelectorAll(".table-header").forEach((header) => {
    header.addEventListener("click", () => {
        GeoLeaf.Table.sortByField(header.dataset.field);
    });
});

2.8 GeoLeaf.Table.setSelection(ids, add) ​

Selects or deselects features.

js
GeoLeaf.Table.setSelection(ids, add);

Parameters:

  • ids: Array<string> — IDs of the features to select
  • add: boolean — add to the existing selection (true) or replace it (false, default)

Events emitted: geoleaf:table:selectionChanged with { layerId, selectedIds }

Example ​

js
// Select specific features
GeoLeaf.Table.setSelection(["poi-1", "poi-5", "poi-12"]);

// Add to the existing selection
GeoLeaf.Table.setSelection(["poi-20"], true);

2.9 GeoLeaf.Table.getSelectedIds() ​

Returns the IDs of the selected features.

js
const selectedIds = GeoLeaf.Table.getSelectedIds();

Returns: Array<string> — list of selected IDs

Example ​

js
const selected = GeoLeaf.Table.getSelectedIds();
console.log(`${selected.length} selected features:`, selected);

2.10 GeoLeaf.Table.clearSelection() ​

Clears the whole selection.

js
GeoLeaf.Table.clearSelection();

Events emitted: geoleaf:table:selectionChanged with { layerId, selectedIds: [] }

Example ​

js
document.getElementById("clear-selection-btn").addEventListener("click", () => {
    GeoLeaf.Table.clearSelection();
});

2.11 GeoLeaf.Table.zoomToSelection() ​

Zooms to the selected features on the map.

js
GeoLeaf.Table.zoomToSelection();

Events emitted: geoleaf:table:zoomToSelection with { layerId, selectedIds }

Behavior: computes the bounds of the selected features and adjusts the MapLibre GL JS map view.

Example ​

js
document.getElementById("zoom-selection-btn").addEventListener("click", () => {
    const selected = GeoLeaf.Table.getSelectedIds();
    if (selected.length === 0) return;
    GeoLeaf.Table.zoomToSelection();
});

2.12 GeoLeaf.Table.highlightSelection(active) ​

Enables or disables the highlighting of the selected features on the map.

js
GeoLeaf.Table.highlightSelection(active);

Parameters:

  • active: boolean — enable (true) or disable (false)

Events emitted: geoleaf:table:highlightSelection with { layerId, selectedIds, active }

Example ​

js
let highlighted = false;

document.getElementById("highlight-btn").addEventListener("click", () => {
    highlighted = !highlighted;
    GeoLeaf.Table.highlightSelection(highlighted);
});

2.13 GeoLeaf.Table.exportSelection(format?, options?) ​

Exports the selected features to a file the browser downloads, then emits an event. With no selection it does nothing.

js
GeoLeaf.Table.exportSelection(); // GeoJSON
GeoLeaf.Table.exportSelection("csv", { csvSeparator: ";" });

format is "geojson" (default), "csv", "kml", "gpx" or "excel". options takes csvSeparator ("," or ";") and csvIncludeGeometry.

Events emitted: geoleaf:table:exportSelection with { layerId, format, selectedIds, rows } — rows are the exported features, as they go to the file.

The table writes the file itself: the event is there for a host that wants to follow the export, not to implement it. GeoLeaf.Table.exportLayer(format?, options?) does the same for the whole layer and emits geoleaf:table:exportLayer with { layerId, format, count }.

Example ​

js
document.getElementById("export-btn").addEventListener("click", () => {
    GeoLeaf.Table.exportSelection("csv");
});

// Follow the export
document.addEventListener("geoleaf:table:exportSelection", (e) => {
    console.log(`${e.detail.rows.length} features exported as ${e.detail.format}`);
});

3. Configuration in geoleaf.config.json ​

3.1 Global module configuration ​

The plugin reads the modules.table block of the profile. The values below are its defaults: a profile declares only what it changes.

json
{
    "modules": {
        "table": {
            "enabled": true,
            "defaultVisible": false,
            "maxRowsPerLayer": 30000,
            "enableExportButton": true,
            "defaultHeight": "40%",
            "minHeight": "20%",
            "maxHeight": "60%",
            "resizable": true
        }
    }
}

pageSize and virtualScrolling are not in this block: both are deprecated and read by nothing. The panel does not paginate, and it windows its rows by itself past a row-count threshold.

3.2 Per-layer configuration ​

json
{
    "geojson": {
        "layers": [
            {
                "id": "restaurants",
                "url": "data/restaurants.geojson",
                "table": {
                    "enabled": true,
                    "columns": [
                        {
                            "field": "properties.name",
                            "label": "Nom",
                            "width": "30%",
                            "sortable": true
                        },
                        {
                            "field": "properties.category",
                            "label": "Catégorie",
                            "width": "20%",
                            "sortable": true
                        },
                        {
                            "field": "properties.address",
                            "label": "Adresse",
                            "width": "35%",
                            "sortable": false
                        },
                        {
                            "field": "properties.rating",
                            "label": "Note",
                            "width": "15%",
                            "sortable": true
                        }
                    ],
                    "defaultSort": {
                        "field": "properties.rating",
                        "direction": "desc"
                    },
                    "searchFields": ["properties.name", "properties.category"]
                }
            }
        ]
    }
}

4. Events ​

The Table module emits the following events on the MapLibre GL JS map, and on document — subscribe to one of the two, not both:

EventDetailDescription
geoleaf:table:opened—Table shown
geoleaf:table:closed—Table hidden
geoleaf:table:layerChanged{ layerId }Displayed layer changed
geoleaf:table:sortChanged{ field, direction }Sort changed
geoleaf:table:selectionChanged{ layerId, selectedIds }Selection changed
geoleaf:table:zoomToSelection{ layerId, selectedIds }Zoom to selection triggered
geoleaf:table:highlightSelection{ layerId, selectedIds, active }Highlight enabled/disabled
geoleaf:table:exportSelection{ layerId, format, selectedIds, rows }Selection exported
geoleaf:table:exportLayer{ layerId, format, count }Whole layer exported
geoleaf:table:rowActivated{ layerId, featureId, properties }Row double-clicked, or Enter pressed on the focused row

geoleaf:table:rowActivated states an intention and opens nothing: the subscriber decides what an activation means. featureId is the identifier selectionChanged lists in selectedIds (a string, synthetic — __gl_row_<n> — when the feature has no identifier of its own), and properties are the feature's complete properties. A double-click leaves the selection as it was: its first click toggles it, the activation restores it, so two selectionChanged surround the gesture. The checkbox cell and the sort header never activate.

Listening example ​

js
const map = GeoLeaf.Core.getMap();

// Listen to selection changes
map.on("geoleaf:table:selectionChanged", (e) => {
    console.log(`${e.selectedIds.length} selected features`);
    highlightFeaturesOnMap(e.selectedIds);
});

// Listen to layer changes
map.on("geoleaf:table:layerChanged", (e) => {
    console.log(`Displayed layer: ${e.layerId}`);
    updateUIControls(e.layerId);
});

5. Integration with other modules ​

5.1 Integration with GeoJSON ​

The Table module displays the data of the GeoJSON layers loaded by the core:

js
// GeoJSON layers are configured in geoleaf.config.json
// and loaded automatically by GeoLeaf.GeoJSON at startup.
// Display a layer in the table:
GeoLeaf.Table.setLayer("restaurants");
GeoLeaf.Table.show();

5.2 Integration with Filters ​

The table synchronizes with filters automatically by listening to events:

js
// Apply a filter — a state replaces the whole panel selection
GeoLeaf.Filter.applyFilter({
    fields: [{ id: "categories", kind: "taxonomy", values: ["restaurant"] }],
});

// The table updates itself automatically
// (it listens to the geoleaf:filters:applied event internally)

5.3 Two-way map ↔ table synchronization ​

In MapLibre GL JS, synchronization relies on GeoLeaf events:

js
// Selection in the table → highlight on the map
map.on("geoleaf:table:selectionChanged", (e) => {
    const { selectedIds } = e;
    // Update the MapLibre style through setFeatureState or a paint expression
    // (the built-in highlighting uses highlightSelection())
    GeoLeaf.Table.highlightSelection(selectedIds.length > 0);
});

// Click on the map → selection in the table
map.on("click", (e) => {
    const features = map.queryRenderedFeatures(e.point);
    if (features.length > 0) {
        const featureId = features[0].id;
        GeoLeaf.Table.setSelection([String(featureId)], true);
    }
});

6. Practical use cases ​

6.1 Table with sorting ​

js
GeoLeaf.Table.setLayer("restaurants");
GeoLeaf.Table.show();

// Trigger a sort programmatically
GeoLeaf.Table.sortByField("properties.rating");

6.2 Multi-format export ​

js
map.on("geoleaf:table:exportSelection", (e) => {
    const { rows, layerId } = e;
    const format = prompt("Export format (csv/json/geojson):", "csv");

    switch (format) {
        case "csv":
            exportCSV(rows, `${layerId}.csv`);
            break;
        case "json":
            exportJSON(rows, `${layerId}.json`);
            break;
        case "geojson":
            exportGeoJSON(rows, `${layerId}.geojson`);
            break;
    }
});

6.3 Configuration for large tables ​

json
{
    "modules": {
        "table": {
            "maxRowsPerLayer": 5000
        }
    }
}

7. Internal architecture ​

7.1 Component modules ​

GeoLeaf.Table (public API — table-api.ts)
    ├── table-state.ts      (shared state: _map, _config, _currentLayerId, _selectedIds, etc.)
    ├── table-layer.ts      (feature retrieval, layer handling, attachMapEvents)
    ├── table-highlight.ts  (MapLibre highlighting, bounds computation)
    ├── table-selection.ts  (setSelection, clearSelection, zoomToSelection, exportSelection)
    ├── sort.ts             (sortInPlace, nextSortState — sort cycle)
    ├── export.ts           (resolveFeatureId)
    ├── panel.ts            (TablePanel — DOM container creation)
    └── renderer.ts         (TableRenderer — HTML table rendering)

7.2 Data flow ​

1. Initialization
   The plugin wires itself when the map is ready
   └── Reads the `modules.table` block, merged over the built-in defaults
   └── Creates the DOM container through TablePanel.create()
   └── Attaches map event listeners

2. Layer change
   GeoLeaf.Table.setLayer('restaurants')
   └── Resets selection, highlight and sort
   └── Applies defaultSort from the layer config
   └── Calls refresh()

3. Refresh
   GeoLeaf.Table.refresh()
   └── Retrieves features through GeoLeaf.GeoJSON.getLayerData()
   └── Builds _featureIdMap
   └── Applies the sort if defined (sortInPlace)
   └── Calls TableRenderer.render()

4. Sorting
   GeoLeaf.Table.sortByField('properties.name')
   └── nextSortState() → cycle asc → desc → null
   └── Calls refresh()
   └── Emits: geoleaf:table:sortChanged

5. Selection
   GeoLeaf.Table.setSelection(['id1', 'id2'])
   └── Updates _selectedIds (Set)
   └── Calls TableRenderer.updateSelection()
   └── Emits: geoleaf:table:selectionChanged

8. Best practices ​

Do ​

  • Limit the number of rows through maxRowsPerLayer (default: 30000). The cut warns the user once per layer and per session; 0 means zero
  • Virtual scrolling needs no key: past a row-count threshold the renderer windows automatically. ⚠️ virtualScrolling is deprecated and inert — it never commanded the switch
  • Declare only the relevant columns in columns
  • Use defaultSort for a better initial experience
  • Debounce frequent calls to refresh()

Avoid ​

  • Displaying every property without narrowing the columns
  • Loading more than 5000 rows without pagination or virtual scrolling
  • Calling refresh() too often without a debounce

9. Performance ​

Built-in optimizations ​

  • Automatic capping: maxRowsPerLayer prevents overload, and says so — a silently truncated table looks exactly like a complete one
  • Virtual scrolling: only the visible rows are rendered
  • Data caching: _cachedData avoids repeated queries
  • Optimized sorting: uses localeCompare for strings

Recommendations for very large tables (10k+ rows) ​

json
{
    "modules": {
        "table": {
            "maxRowsPerLayer": 5000
        }
    }
}

10. Troubleshooting ​

Issue: "Container not initialized" ​

Cause: TablePanel not loaded, or the module was not initialized. Solution: check that the plugin is loaded and that modules.table.enabled is not false — there is no init call to make.

Issue: empty columns ​

Cause: field does not match the GeoJSON structure. Solution: check the property paths (dot notation).

js
// GeoJSON structure
{ "properties": { "name": "Restaurant X", "info": { "category": "Italian" } } }

// Correct column configuration
{ "columns": [
    { "field": "properties.name", "label": "Nom" },           // OK
    { "field": "properties.info.category", "label": "Cat" },  // OK
    { "field": "category", "label": "Cat" }                   // Incorrect
] }

Issue: sorting does not work ​

Cause: the column is not marked sortable: true. Solution:

json
{ "columns": [{ "field": "properties.name", "label": "Nom", "sortable": true }] }

11. See also ​

Released under the MIT License.