or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-element.mdevents.mdindex.mdplugin-development.mdwasm-init.md
tile.json

events.mddocs/

Event System

Custom events dispatched by the perspective-viewer for user interactions and configuration changes.

Capabilities

Event Registration

Register event listeners for perspective-viewer custom events.

/**
 * Add event listener for configuration updates
 */
addEventListener(
  name: "perspective-config-update",
  cb: (e: CustomEvent<ViewerConfigUpdate>) => void,
  options?: { signal: AbortSignal }
): void;

/**
 * Add event listener for data selection changes
 */
addEventListener(
  name: "perspective-select", 
  cb: (e: CustomEvent<PerspectiveSelectEventDetail>) => void,
  options?: { signal: AbortSignal }
): void;

/**
 * Add event listener for settings panel toggle
 */
addEventListener(
  name: "perspective-toggle-settings",
  cb: (e: CustomEvent<boolean>) => void,
  options?: { signal: AbortSignal }
): void;

/**
 * Add event listener for column settings toggle
 */
addEventListener(
  name: "perspective-toggle-column-settings",
  cb: (e: CustomEvent<{open: boolean, column_name?: string}>) => void,
  options?: { signal: AbortSignal }
): void;

/**
 * Add event listener for user click events on data
 */
addEventListener(
  name: "perspective-click",
  cb: (e: CustomEvent<PerspectiveClickEventDetail>) => void,
  options?: { signal: AbortSignal }
): void;

/**
 * Remove event listeners
 */
removeEventListener(name: "perspective-config-update", cb: any): void;
removeEventListener(name: "perspective-select", cb: any): void;
removeEventListener(name: "perspective-toggle-settings", cb: any): void;
removeEventListener(name: "perspective-toggle-column-settings", cb: any): void;
removeEventListener(name: "perspective-click", cb: any): void;

Configuration Update Events

Fired when the viewer configuration changes through user interaction or programmatic updates.

/**
 * Event detail for configuration updates
 * Contains the complete ViewerConfigUpdate object
 */
interface PerspectiveConfigUpdateEvent extends CustomEvent {
  detail: ViewerConfigUpdate;
}

Usage:

import "@finos/perspective-viewer";

const viewer = document.createElement("perspective-viewer");

viewer.addEventListener("perspective-config-update", (event) => {
  const config = event.detail;
  console.log("Configuration changed:", config);
  
  // Save configuration automatically
  localStorage.setItem("viewerConfig", JSON.stringify(config));
  
  // React to specific changes
  if (config.plugin) {
    console.log(`Plugin changed to: ${config.plugin}`);
  }
  
  if (config.filter) {
    console.log(`Filters applied: ${config.filter.length}`);
  }
});

Selection Events

Fired when user selects data in the viewer or selection is changed programmatically.

/**
 * Event detail for selection changes
 */
interface PerspectiveSelectEventDetail {
  view_window: ViewWindow;
}

interface ViewWindow {
  start_row: number;
  end_row: number;
  start_col: number;
  end_col: number;
}

Usage:

viewer.addEventListener("perspective-select", (event) => {
  const { view_window } = event.detail;
  
  console.log(`Selected rows: ${view_window.start_row} to ${view_window.end_row}`);
  console.log(`Selected columns: ${view_window.start_col} to ${view_window.end_col}`);
  
  // Get selected data
  viewer.getView().then(async (view) => {
    const selectedData = await view.to_json({
      start_row: view_window.start_row,
      end_row: view_window.end_row,
      start_col: view_window.start_col,
      end_col: view_window.end_col
    });
    
    console.log("Selected data:", selectedData);
  });
});

// Clear selection
viewer.addEventListener("perspective-select", (event) => {
  if (!event.detail.view_window) {
    console.log("Selection cleared");
  }
});

Click Events

Fired when user clicks on data cells or visualization elements.

/**
 * Event detail for click events
 * Contains clicked row data and configuration context
 */
interface PerspectiveClickEvent extends CustomEvent {
  detail: PerspectiveClickEventDetail;
}

Usage:

viewer.addEventListener("perspective-click", (event) => {
  const { config, row, column_names } = event.detail;
  
  console.log("Data clicked:", row);
  console.log("Column names:", column_names);
  console.log("Current config:", config);
  
  // Example: Show details in a popup
  showDetailsPopup(row, column_names);
  
  // Example: Filter other viewers based on clicked data
  filterOtherViewers(row);
  
  // Example: Navigate to detail page
  const id = row.id;
  if (id) {
    window.location.href = `/details/${id}`;
  }
});

function showDetailsPopup(row: Record<string, any>, columns: Array<string | Array<string>>) {
  const popup = document.createElement("div");
  popup.className = "data-popup";
  
  const content = Object.entries(row)
    .map(([key, value]) => `<div><strong>${key}:</strong> ${value}</div>`)
    .join("");
    
  popup.innerHTML = `
    <h3>Row Details</h3>
    ${content}
    <button onclick="this.parentElement.remove()">Close</button>
  `;
  
  document.body.appendChild(popup);
}

Settings Panel Events

Fired when the settings panel is opened or closed.

/**
 * Event detail for settings panel toggle
 * Boolean indicates whether panel is open (true) or closed (false)
 */
interface PerspectiveToggleSettingsEvent extends CustomEvent {
  detail: boolean;
}

Usage:

viewer.addEventListener("perspective-toggle-settings", (event) => {
  const isOpen = event.detail;
  
  if (isOpen) {
    console.log("Settings panel opened");
    // Maybe adjust layout or show help text
  } else {
    console.log("Settings panel closed");
    // Maybe hide help text or expand chart area
  }
});

Column Settings Events

Fired when column-specific settings panel is opened or closed.

/**
 * Event detail for column settings toggle
 */
interface PerspectiveToggleColumnSettingsEvent extends CustomEvent {
  detail: {
    open: boolean;
    column_name?: string;
  };
}

Usage:

viewer.addEventListener("perspective-toggle-column-settings", (event) => {
  const { open, column_name } = event.detail;
  
  if (open && column_name) {
    console.log(`Column settings opened for: ${column_name}`);
    // Show column-specific help or information
  } else {
    console.log("Column settings closed");
  }
});

Event Handling Examples

Configuration Synchronization

import "@finos/perspective-viewer";

class DashboardManager {
  private viewers: HTMLPerspectiveViewerElement[] = [];
  
  constructor() {
    this.setupEventHandlers();
  }
  
  addViewer(viewer: HTMLPerspectiveViewerElement) {
    this.viewers.push(viewer);
    this.setupViewerEvents(viewer);
  }
  
  private setupViewerEvents(viewer: HTMLPerspectiveViewerElement) {
    // Sync theme changes across all viewers
    viewer.addEventListener("perspective-config-update", (event) => {
      const config = event.detail;
      
      if (config.theme) {
        this.synchronizeTheme(config.theme, viewer);
      }
    });
    
    // Handle selection for cross-filtering
    viewer.addEventListener("perspective-select", (event) => {
      this.handleCrossFilter(event.detail.view_window, viewer);
    });
  }
  
  private synchronizeTheme(theme: string, sourceViewer: HTMLPerspectiveViewerElement) {
    this.viewers.forEach(async (viewer) => {
      if (viewer !== sourceViewer) {
        const config = await viewer.save();
        await viewer.restore({ ...config, theme });
      }
    });
  }
  
  private async handleCrossFilter(selection: ViewWindow, sourceViewer: HTMLPerspectiveViewerElement) {
    // Get selected data from source viewer
    const sourceView = await sourceViewer.getView();
    const selectedData = await sourceView.to_json({
      start_row: selection.start_row,
      end_row: selection.end_row
    });
    
    // Apply filters to other viewers based on selection
    // (Implementation depends on your cross-filtering logic)
  }
}

Auto-Save Configuration

class AutoSaveManager {
  private saveTimeout: number | null = null;
  private readonly SAVE_DELAY = 1000; // 1 second debounce
  
  constructor(private viewer: HTMLPerspectiveViewerElement, private storageKey: string) {
    this.setupAutoSave();
  }
  
  private setupAutoSave() {
    this.viewer.addEventListener("perspective-config-update", (event) => {
      // Debounce saves to avoid excessive storage writes
      if (this.saveTimeout) {
        clearTimeout(this.saveTimeout);
      }
      
      this.saveTimeout = window.setTimeout(() => {
        this.saveConfiguration(event.detail);
      }, this.SAVE_DELAY);
    });
  }
  
  private saveConfiguration(config: ViewerConfigUpdate) {
    try {
      localStorage.setItem(this.storageKey, JSON.stringify(config));
      console.log("Configuration auto-saved");
    } catch (error) {
      console.error("Failed to save configuration:", error);
    }
  }
  
  async loadConfiguration(): Promise<boolean> {
    try {
      const saved = localStorage.getItem(this.storageKey);
      if (saved) {
        const config = JSON.parse(saved);
        await this.viewer.restore(config);
        return true;
      }
    } catch (error) {
      console.error("Failed to load configuration:", error);
    }
    return false;
  }
}

// Usage
const viewer = document.createElement("perspective-viewer");
const autoSave = new AutoSaveManager(viewer, "dashboard-config");

// Load saved configuration on startup
autoSave.loadConfiguration();

Selection-Based Actions

class SelectionHandler {
  constructor(private viewer: HTMLPerspectiveViewerElement) {
    this.setupSelectionHandler();
  }
  
  private setupSelectionHandler() {
    this.viewer.addEventListener("perspective-select", async (event) => {
      const { view_window } = event.detail;
      
      if (view_window) {
        await this.handleSelection(view_window);
      } else {
        this.clearSelection();
      }
    });
  }
  
  private async handleSelection(selection: ViewWindow) {
    const view = await this.viewer.getView();
    
    // Get selected data
    const selectedData = await view.to_json({
      start_row: selection.start_row,
      end_row: selection.end_row,
      start_col: selection.start_col,
      end_col: selection.end_col
    });
    
    // Show selection details
    this.showSelectionDetails(selectedData, selection);
    
    // Enable selection-based actions
    this.enableSelectionActions(selectedData);
  }
  
  private showSelectionDetails(data: any[], selection: ViewWindow) {
    const rowCount = selection.end_row - selection.start_row + 1;
    const colCount = selection.end_col - selection.start_col + 1;
    
    console.log(`Selected ${rowCount} rows and ${colCount} columns`);
    console.log("Selected data:", data);
    
    // Update UI to show selection info
    document.getElementById("selection-info")!.textContent = 
      `${rowCount} rows × ${colCount} columns selected`;
  }
  
  private enableSelectionActions(data: any[]) {
    // Enable export selected data button
    const exportBtn = document.getElementById("export-selection") as HTMLButtonElement;
    exportBtn.disabled = false;
    exportBtn.onclick = () => this.exportSelection(data);
    
    // Enable other selection-based actions
    this.setupDetailView(data);
  }
  
  private clearSelection() {
    document.getElementById("selection-info")!.textContent = "No selection";
    (document.getElementById("export-selection") as HTMLButtonElement).disabled = true;
  }
  
  private exportSelection(data: any[]) {
    const csv = this.convertToCSV(data);
    this.downloadCSV(csv, "selection.csv");
  }
  
  private convertToCSV(data: any[]): string {
    if (data.length === 0) return "";
    
    const headers = Object.keys(data[0]);
    const csvContent = [
      headers.join(","),
      ...data.map(row => headers.map(header => `"${row[header]}"`).join(","))
    ].join("\n");
    
    return csvContent;
  }
  
  private downloadCSV(content: string, filename: string) {
    const blob = new Blob([content], { type: "text/csv" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
  }
  
  private setupDetailView(data: any[]) {
    // Create detailed view of selected data
    // (Implementation depends on your UI framework)
  }
}

Event Cleanup

class EventManager {
  private abortController = new AbortController();
  
  constructor(private viewer: HTMLPerspectiveViewerElement) {
    this.setupEvents();
  }
  
  private setupEvents() {
    const { signal } = this.abortController;
    
    // All event listeners will be automatically removed when aborted
    this.viewer.addEventListener("perspective-config-update", (event) => {
      this.handleConfigUpdate(event.detail);
    }, { signal });
    
    this.viewer.addEventListener("perspective-select", (event) => {
      this.handleSelection(event.detail);
    }, { signal });
    
    this.viewer.addEventListener("perspective-toggle-settings", (event) => {
      this.handleSettingsToggle(event.detail);
    }, { signal });
  }
  
  private handleConfigUpdate(config: ViewerConfigUpdate) {
    // Handle configuration updates
  }
  
  private handleSelection(detail: PerspectiveSelectEventDetail) {
    // Handle selection changes
  }
  
  private handleSettingsToggle(isOpen: boolean) {
    // Handle settings panel toggle
  }
  
  // Clean up all event listeners
  destroy() {
    this.abortController.abort();
  }
}

// Usage
const viewer = document.createElement("perspective-viewer");
const eventManager = new EventManager(viewer);

// Clean up when component unmounts
window.addEventListener("beforeunload", () => {
  eventManager.destroy();
});

Event Types Reference

// All event detail types
type PerspectiveConfigUpdateEvent = CustomEvent<ViewerConfigUpdate>;
type PerspectiveSelectEvent = CustomEvent<PerspectiveSelectEventDetail>;
type PerspectiveClickEvent = CustomEvent<PerspectiveClickEventDetail>;
type PerspectiveToggleSettingsEvent = CustomEvent<boolean>;
type PerspectiveToggleColumnSettingsEvent = CustomEvent<{
  open: boolean;
  column_name?: string;
}>;

// Event detail interfaces
interface PerspectiveClickEventDetail {
  config: ViewerConfigUpdate;
  row: Record<string, any>;
  column_names: Array<string | Array<string>>;
}

// Event listener type helpers
type ConfigUpdateListener = (event: PerspectiveConfigUpdateEvent) => void;
type SelectListener = (event: PerspectiveSelectEvent) => void;
type ClickListener = (event: PerspectiveClickEvent) => void;
type SettingsToggleListener = (event: PerspectiveToggleSettingsEvent) => void;
type ColumnSettingsToggleListener = (event: PerspectiveToggleColumnSettingsEvent) => void;