Table Views - zowe/zowe-explorer-vscode GitHub Wiki
Building tables
- To quickly build a table instance, use the
TableBuilder
facility exported from Zowe Explorer API. - The
TableBuilder
facility has multiple helper functions to facilitate creating tables. To start, create a new instance of aTableBuilder
.
NOTE: It's important to specify Zowe Explorer's extension path when using the table builder, or otherwise the table components are not accessible from your extension when rendering the webview.
const builder = new TableBuilder({
extensionPath: vscode.extensions.getExtension("zowe.vscode-extension-for-zowe").extensionPath,
} as vscode.ExtensionContext);
- Properties, sections of the table, and other options can be provided by calling functions directly on the builder:
const builder = new TableBuilder()
.options({ pagination: true, paginationPageSize: 100 })
.rows([
{ place: "(Not) the Apple", apple: 5, banana: 15, orange: 10 },
{ place: "Bananapalooza", apple: 20, banana: 30, orange: 0 },
{ place: "Orange of Fruits", apple: 0, banana: 0, orange: 30 },
])
.columns([
{ field: "place", headerName: "Marketplace" },
{ field: "apple", headerName: "Number of apples", filter: true, sort: "asc" },
{ field: "banana", headerName: "Number of bananas", filter: true },
{ field: "orange", headerName: "Number of oranges", filter: true },
])
.title("Fruits for Sale at Various Marketplaces");
- Once you're ready to build the table, you can call either:
build
: Builds a table instance and returns it to the developerbuildAndShare
: Builds a table instance, adds its view to the mediator, and returns it to the developer
- Only use the
buildAndShare
command if you want to expose a table to the public. Sharing the table allows it to be modified by other extenders, but only the creator of the table can delete the instance. - The tables themselves leverage AG Grid (community edition) for rendering. All supported AG Grid options for the table can be found here: Table.GridProperties
Actions
Actions are interactive buttons displayed in the table's action bar that allow users to perform operations on selected rows. They support both static and dynamic titles, conditional visibility, and different callback types.
Adding Actions
Use the addAction
method to add actions to specific rows or all rows:
// Add actions to all rows
await tableView.addAction("all", {
title: "Delete Item",
command: "delete-item",
type: "secondary",
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Handle deletion logic
console.log("Deleting item:", rowInfo.row);
},
},
});
// Add actions to specific row index
await tableView.addAction(0, {
title: "Edit First Item",
command: "edit-first-item",
type: "primary",
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Handle editing logic
},
},
});
Action Types
Actions support different visual types:
"primary"
: Emphasized button for main actions"secondary"
: Standard button for secondary actions"icon"
: Icon-only button for space-efficient actions
Callback Types
Actions support different callback types based on the selection requirements:
// Single row selection required
{
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// rowInfo contains: { index: number, row: RowData }
}
}
}
// Multiple rows selection supported
{
callback: {
typ: "multi-row",
fn: async (view, rows) => {
// rows is Record<number, RowData> of selected rows
}
}
}
// No selection required
{
callback: {
typ: "no-selection",
fn: async (view) => {
// Operates on the entire table
}
}
}
// Cell-specific actions
{
callback: {
typ: "cell",
fn: async (view, cellValue) => {
// cellValue contains the specific cell data
}
}
}
Dynamic Titles
Actions can have dynamic titles that change based on the selected data:
await tableView.addAction("all", {
title: (selectedData) => {
if (Array.isArray(selectedData)) {
return `Process ${selectedData.length} items`;
}
return `Process ${selectedData.name}`;
},
command: "process-items",
callback: {
typ: "multi-row",
fn: async (view, rows) => {
// Process selected rows
},
},
});
Action Conditions
condition
)
Enable Conditions (Controls whether an action is enabled (clickable) based on the selected data:
await tableView.addAction("all", {
title: "Archive Item",
command: "archive-item",
condition: (rowData) => {
// Enable only if status is not already archived
return rowData.status !== "archived";
},
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Archive logic
},
},
});
hideCondition
)
Hide Conditions (Controls whether an action is visible in the UI based on the selected data:
await tableView.addAction("all", {
title: "Admin Action",
command: "admin-action",
hideCondition: (rowData) => {
// Hide action if user is not admin
return !rowData.userRole?.includes("admin");
},
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Admin-only logic
},
},
});
String-based Conditions
For simple conditions, you can use string expressions:
await tableView.addAction("all", {
title: "Delete",
command: "delete-row",
condition: "data => data.deletable === true",
hideCondition: "data => data.protected === true",
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Delete logic
},
},
});
Context Menu Items
Context menu items appear when users right-click on table rows, providing contextual actions specific to the clicked row.
Adding Context Menu Items
Use the addContextOption
method to add context menu items:
// Add context menu items to all rows
await tableView.addContextOption("all", {
title: "Copy to Clipboard",
command: "copy-to-clipboard",
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
await vscode.env.clipboard.writeText(JSON.stringify(rowInfo.row));
},
},
});
// Add context menu items to specific row
await tableView.addContextOption(0, {
title: "Special Action",
command: "special-action",
condition: (rowData) => rowData.special === true,
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Special action logic
},
},
});
Context Menu Options
Context menu items support the same condition system as actions:
await tableView.addContextOption("all", {
title: "Edit Properties",
command: "edit-properties",
condition: (rowData) => rowData.editable === true,
hideCondition: (rowData) => rowData.locked === true,
callback: {
typ: "single-row",
fn: async (view, rowInfo) => {
// Edit properties logic
},
},
});
Built-in Context Menu Items
The table provides built-in context menu items:
- Copy cell: Copies the value of the right-clicked cell
- Copy row: Copies the entire row data as JSON
You can add custom items alongside these built-in options.
Content Management
The table view provides several methods to manage table content dynamically.
Adding Content
Add new rows to the existing table data:
// Add single row
await tableView.addContent({
name: "New Item",
value: 42,
status: "active",
});
// Add multiple rows
await tableView.addContent({ name: "Item 1", value: 10 }, { name: "Item 2", value: 20 }, { name: "Item 3", value: 30 });
Setting Content
Replace all existing table content:
const newData = [
{ name: "Fresh Item 1", value: 100 },
{ name: "Fresh Item 2", value: 200 },
];
await tableView.setContent(newData);
Getting Content
Retrieve current table content:
const currentRows = tableView.getContent();
console.log("Current table data:", currentRows);
Updating Individual Rows
Update or delete specific rows by index:
// Update row at index 0
await tableView.updateRow(0, {
name: "Updated Item",
value: 999,
status: "modified",
});
// Delete row at index 1
await tableView.updateRow(1, null);
Table instances vs. views
- Owned tables are represented by the
Table.Instance
class. - The "View" for a table is a superclass of
Table.Instance
, except that itsdispose
function is protected to prevent other extenders from calling it. - Generally, we recommend building tables using the
TableBuilder
class, but functions are also exposed on the table view to make modifications:
const view = new Table.View(extensionContext);
// Each setter function will send an update to the webview.
// To set data before rendering, pass the data into the constructor as the second argument.
await view.setTitle("Fruits for Sale");
await view.setOptions({ pagination: true, paginationPageSize: 100 });
await view.addColumns([
{ field: "place", headerName: "Marketplace" },
{ field: "apple", headerName: "Number of apples", filter: true, sort: "asc" },
{ field: "banana", headerName: "Number of bananas", filter: true },
{ field: "orange", headerName: "Number of oranges", filter: true },
]);
await view.addContent([
{ place: "(Not) the Apple", apple: 5, banana: 15, orange: 10 },
{ place: "Bananapalooza", apple: 20, banana: 30, orange: 0 },
{ place: "Orange of Fruits", apple: 0, banana: 0, orange: 30 },
]);
// Notice that this logic results in an identical table to the one composed with the TableBuilder.
- To dispose of a table instance, call its
dispose
function. This function will also remove the table from the mediator if it was previously added.
Table Mediator
- The table mediator is a controller class used for managing shared Zowe Explorer tables. It operates as a singleton and is exposed through the
ZoweExplorerExtenderApi
. - Extenders can share a table with the mediator by calling it's
addTable
function, or the extender can callbuildAndShare
on aTableBuilder
to build and expose a new table. - At any time, the source extender can remove a table instance by passing it to the mediator's
removeTable
function. Only developers with access to theTable.Instance
will be able to dispose of the table. - Other extenders can access tables from the mediator by calling its
getTable
function and providing the ID of the desired table.- To access the table ID, you can call the
getId
function on the table view. You can then share this ID with other extenders so that they can explicitly request it from the mediator.
- To access the table ID, you can call the
Table Options
The table view supports extensive configuration through AG Grid options. Use the setOptions
method to configure table behavior.
Basic Options
await tableView.setOptions({
// Pagination
pagination: true,
paginationPageSize: 50,
paginationAutoPageSize: false,
paginationPageSizeSelector: [10, 25, 50, 100],
// Selection
rowSelection: "multiple", // or "single"
suppressRowClickSelection: false,
selectEverything: false,
// Filtering
quickFilterText: "search term",
// Loading state
loading: true,
// Column management
maintainColumnOrder: true,
suppressMovableColumns: false,
// Performance
debug: false,
suppressColumnMoveAnimation: true,
});
Advanced Options
await tableView.setOptions({
// Auto-sizing strategies
autoSizeStrategy: {
type: "fitGridWidth",
defaultMinWidth: 100,
defaultMaxWidth: 500,
columnLimits: [{ colId: "name", minWidth: 150, maxWidth: 300 }],
},
// Custom tree mode for hierarchical data
customTreeMode: true,
customTreeColumnField: "name",
customTreeInitialExpansionDepth: 2,
// Header configuration
headerHeight: 40,
groupHeaderHeight: 30,
floatingFiltersHeight: 35,
// Localization
localeText: {
noRowsToShow: "No data available",
loading: "Loading...",
},
// Column menu
columnMenu: "new", // or "legacy"
suppressMenuHide: false,
// Drag and drop
suppressDragLeaveHidesColumns: true,
allowDragFromColumnsToolPanel: true,
});
Column Configuration
Configure individual columns when adding them:
await tableView.addColumns([
{
field: "name",
headerName: "Item Name",
width: 200,
pinned: "left",
sortable: true,
filter: true,
editable: true,
checkboxSelection: true,
headerCheckboxSelection: true,
},
{
field: "value",
headerName: "Value",
type: "numericColumn",
width: 120,
sort: "desc",
filter: true,
valueFormatter: (params) => `$${params.value}`,
},
{
field: "status",
headerName: "Status",
width: 100,
hide: false,
suppressMovable: true,
lockVisible: true,
},
]);
Grid State Management
The table view provides methods to save and restore grid state, enabling persistence of user preferences like column order, sizing, sorting, and filtering.
Getting Grid State
Retrieve the current state of the grid:
const gridState = await tableView.getGridState();
// Grid state includes:
// - Column order and widths
// - Sort model
// - Filter model
// - Pinned columns
// - Visible columns
// - Grouped columns
console.log("Current grid state:", gridState);
Setting Grid State
Restore a previously saved grid state:
const savedState = {
columnState: [
{ colId: "name", width: 250, sort: "asc", pinned: "left" },
{ colId: "value", width: 150, sort: null, pinned: null },
],
filterModel: {
name: { type: "contains", filter: "important" },
},
sortModel: [{ colId: "name", sort: "asc" }],
};
await tableView.setGridState(savedState);
Pagination State
Manage pagination state separately:
// Get current page information
const currentPage = await tableView.getPage();
const pageSize = await tableView.getPageSize();
console.log(`Currently on page ${currentPage + 1} with ${pageSize} items per page`);
// Set page and page size
await tableView.setPage(2); // Navigate to page 3 (0-indexed)
await tableView.setPageSize(25); // Show 25 items per page
Persistent State Example
Save and restore table state across sessions:
// Save state when table is modified
tableView.onTableDisplayChanged((displayedRows) => {
const saveState = async () => {
const state = await tableView.getGridState();
const page = await tableView.getPage();
const pageSize = await tableView.getPageSize();
// Save to VS Code workspace state
await context.workspaceState.update("tableState", {
gridState: state,
page,
pageSize,
});
};
saveState().catch(console.error);
});
// Restore state when table is created
const restoreState = async () => {
const savedState = context.workspaceState.get("tableState");
if (savedState) {
await tableView.setGridState(savedState.gridState);
await tableView.setPage(savedState.page);
await tableView.setPageSize(savedState.pageSize);
}
};
// Wait for API to be ready before restoring state
await tableView.waitForAPI();
await restoreState();
Pinned Rows
Manage pinned rows at the top of the grid:
// Pin specific rows to the top
const importantRows = [
{ name: "Critical Item", value: 999, status: "urgent" },
{ name: "High Priority", value: 500, status: "important" },
];
await tableView.pinRows(importantRows);
// Get currently pinned rows
const pinnedRows = await tableView.getPinnedRows();
console.log("Pinned rows:", pinnedRows);
// Replace all pinned rows
await tableView.setPinnedRows([{ name: "New Pinned Item", value: 100, status: "pinned" }]);
// Clear all pinned rows
await tableView.setPinnedRows([]);
// Unpin specific rows
await tableView.unpinRows(rowsToUnpin);
Event Handling
Listen for table events to react to user interactions:
// React to data changes
tableView.onTableDataReceived((changedData) => {
console.log("Table data updated:", changedData);
});
// React to display changes (filtering, sorting)
tableView.onTableDisplayChanged((displayedRows) => {
console.log("Displayed rows changed:", displayedRows);
});
// React to data editing
tableView.onTableDataEdited((editEvent) => {
console.log("Cell edited:", editEvent);
// editEvent contains: { rowIndex, field, value, oldValue }
});
// React to custom messages
tableView.onDidReceiveMessage((message) => {
console.log("Received message:", message);
});