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 a TableBuilder.

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 developer
    • buildAndShare: 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

Enable Conditions (condition)

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
    },
  },
});

Hide Conditions (hideCondition)

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 its dispose 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 call buildAndShare on a TableBuilder 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 the Table.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.

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);
});