Skip to content

Pending Review

Pending Review from User Experience (UX)

Table Editor Component

Overview

The PWC Table Editor component provides an interactive tabular data editing interface with dynamic row management, column validation, and responsive design capabilities. Built with flexible column configuration system and form integration, it offers inline editing, add/remove functionality, and seamless PWC form system integration for complex data collection and management workflows.

Core Capabilities

  • Dynamic Row Management - Add and remove table rows with automatic data synchronization and form context integration
  • Flexible Column Configuration - Support for input and select column types with validation, hints, and responsive display controls
  • Inline Editing Interface - Direct cell editing with real-time validation feedback and form field integration
  • Responsive Column Display - Adaptive column visibility based on screen size with configurable breakpoint management
  • Form Integration - Seamless connection with PWC form systems for validation, submission, and data management
  • Loading State Support - Skeleton rendering and placeholder states during data loading and form initialization

When to use Table Editor:

  • Data collection forms requiring structured tabular input with multiple related fields per record
  • Configuration interfaces needing dynamic list management with add/remove functionality and inline editing
  • Bulk data entry scenarios where users need to input multiple records efficiently in a spreadsheet-like interface
  • Settings panels with repeating data structures that benefit from tabular organization and management

When not to use Table Editor:

  • Simple single-record forms where individual input fields provide better user experience and visual clarity
  • Read-only data display where table components without editing capabilities are more appropriate
  • Complex nested data structures requiring specialized input components beyond basic input and select fields

Basic Implementation

import { PwcTableEditor } from "@progress-i360/pwc-react";
import { useMemo, useState } from 'react';

function TeamMembersEditor() {
  const [teamData, setTeamData] = useState([
    {
      firstName: 'John',
      lastName: 'Doe',
      role: 'developer'
    }
  ]);

  const memberColumns = useMemo(
    () => [
      {
        type: 'input',
        field: 'firstName',
        label: 'First Name',
        required: true,
        flex: 1,
        hint: 'Employee first name'
      },
      {
        type: 'select',
        field: 'role',
        label: 'Role',
        options: [
          { label: 'Developer', value: 'developer' },
          { label: 'Designer', value: 'designer' }
        ],
        required: true,
        flex: 1
      }
    ],
    []
  );

  const handleDataChange = (newData: Array<Record<string, unknown>>) => {
    setTeamData(newData);
  };

  return (
    <PwcTableEditor
      name="teamMembers"
      columns={memberColumns}
      rows={teamData}
      placeholder="Click below to add your first team member"
      placeholderHeading="No team members yet"
      onChange={handleDataChange}
    />
  );
}
import { useEffect, useMemo, useRef, useState } from 'react';
import '@progress-i360/progress-web-components/button';
import '@progress-i360/progress-web-components/form';
import '@progress-i360/progress-web-components/table-editor';

type InventoryRow = {
  productName: string;
  price: string;
};

type TableEditorElement = HTMLElement & {
  columns?: Array<Record<string, unknown>>;
  rows?: InventoryRow[];
  placeholder?: string;
  placeholderHeading?: string;
};

type ButtonElement = HTMLElement & {
  label?: string;
  disabled?: boolean;
  variant?: string;
};

type PwcSubmitDetail = {
  payload: Record<string, unknown>;
  success: () => void;
};

function InventoryManagementForm() {
  const formRef = useRef<HTMLElement | null>(null);
  const tableEditorRef = useRef<TableEditorElement | null>(null);
  const submitButtonRef = useRef<ButtonElement | null>(null);

  const inventoryColumns = useMemo(
    () => [
      {
        type: 'input',
        field: 'productName',
        label: 'Product Name',
        required: true,
        flex: 2,
        hint: 'Enter a descriptive product name'
      },
      {
        type: 'input',
        field: 'price',
        label: 'Price ($)',
        inputType: 'number',
        required: true,
        min: '0.01',
        step: '0.01',
        flex: 1,
        hint: 'Price in USD'
      }
    ],
    []
  );

  const [inventoryData, setInventoryData] = useState<InventoryRow[]>([
    {
      productName: 'Wireless Headphones',
      price: '99.99'
    }
  ]);

  const [isSubmitting, setIsSubmitting] = useState(false);

  const totalValue = useMemo(() => {
    return inventoryData.reduce((sum, item) => sum + parseFloat(item.price || '0'), 0);
  }, [inventoryData]);

  useEffect(() => {
    const editor = tableEditorRef.current;
    if (!editor) {
      return;
    }

    editor.columns = inventoryColumns;
    editor.rows = inventoryData;
    editor.placeholder = 'Add your first product to start building your inventory';
    editor.placeholderHeading = 'No products in inventory';

    const handleChange = (event: Event) => {
      const detail = (event as CustomEvent<InventoryRow[]>).detail;
      if (Array.isArray(detail)) {
        setInventoryData(detail);
      }
    };

    editor.addEventListener('pwc-change', handleChange);
    return () => {
      editor.removeEventListener('pwc-change', handleChange);
    };
  }, [inventoryColumns, inventoryData]);

  useEffect(() => {
    const button = submitButtonRef.current;
    if (!button) {
      return;
    }

    button.label = isSubmitting ? 'Saving...' : 'Save Inventory';
    button.disabled = isSubmitting;
    button.variant = 'primary';
  }, [isSubmitting]);

  useEffect(() => {
    const formElement = formRef.current;
    if (!formElement) {
      return;
    }

    const handleSubmit = (event: Event) => {
      const submitDetail = (event as CustomEvent<PwcSubmitDetail>).detail;
      if (!submitDetail || isSubmitting) {
        return;
      }

      setIsSubmitting(true);
      setTimeout(() => {
        submitDetail.success();
        setIsSubmitting(false);
      }, 2000);
    };

    formElement.addEventListener('pwc-submit', handleSubmit as EventListener);
    return () => {
      formElement.removeEventListener('pwc-submit', handleSubmit as EventListener);
    };
  }, [isSubmitting]);

  return (
    <pwc-form ref={formRef}>
      <div style={{ marginBottom: '16px', fontSize: '0.875rem', color: '#666' }}>
        Total Value: ${totalValue.toFixed(2)}
      </div>

      <pwc-table-editor
        ref={tableEditorRef}
        name="inventory"
      ></pwc-table-editor>

      <pwc-button
        ref={submitButtonRef}
        type="submit"
        style={{ marginTop: '16px' }}
      ></pwc-button>
    </pwc-form>
  );
}
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@progress-i360/progress-web-components/button";
import "@progress-i360/progress-web-components/form-section";
import "@progress-i360/progress-web-components/table-editor";

@Component({
  selector: 'table-editor-demo',
  template: `
    <pwc-form-section heading="Team Management" hint="Manage team members and project assignments">
      <pwc-table-editor 
        name="teamMembers"
        [columns]="memberColumns"
        [rows]="teamData"
        placeholder="Add team members"
        placeholderHeading="No team members yet">
      </pwc-table-editor>

      <pwc-button 
        label="Add Member" 
        variant="primary"
        (pwc-click)="addTeamMember()">
      </pwc-button>
    </pwc-form-section>

    <pwc-form-section heading="Project Tasks" hint="Track project progress and assignments" divider="true">
      <pwc-table-editor 
        name="projectTasks"
        [columns]="taskColumns"
        [rows]="taskData"
        placeholder="Create your first task"
        placeholderHeading="No tasks created">
      </pwc-table-editor>

      <pwc-button 
        label="Add Task" 
        variant="outline"
        (pwc-click)="addTask()">
      </pwc-button>
    </pwc-form-section>

    <div class="summary" *ngIf="showSummary">
      <p>Team: {{ teamData.length }} members, {{ getTaskStats() }} tasks</p>
      <p>Progress: {{ getCompletionPercentage() }}% complete</p>
    </div>
  `,
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class TableEditorDemo {
  teamData = [
    { name: 'John Doe', role: 'developer', email: 'john@company.com' },
    { name: 'Jane Smith', role: 'designer', email: 'jane@company.com' }
  ];

  taskData = [
    { title: 'Setup Project', priority: 'high', assignee: 'John Doe', status: 'complete' },
    { title: 'Design Mockups', priority: 'medium', assignee: 'Jane Smith', status: 'in_progress' }
  ];

  memberColumns = [
    {
      type: 'input',
      field: 'name',
      label: 'Name',
      required: true,
      flex: 2,
      hint: 'Full name of team member'
    },
    {
      type: 'select',
      field: 'role',
      label: 'Role',
      options: [
        { label: 'Developer', value: 'developer' },
        { label: 'Designer', value: 'designer' },
        { label: 'Manager', value: 'manager' },
        { label: 'QA', value: 'qa' }
      ],
      required: true,
      flex: 1
    },
    {
      type: 'input',
      field: 'email',
      label: 'Email',
      inputType: 'email',
      required: true,
      flex: 2
    }
  ];

  taskColumns = [
    {
      type: 'input',
      field: 'title',
      label: 'Task Title',
      required: true,
      flex: 2,
      minLength: 5
    },
    {
      type: 'select',
      field: 'priority',
      label: 'Priority',
      options: [
        { label: 'Low', value: 'low' },
        { label: 'Medium', value: 'medium' },
        { label: 'High', value: 'high' }
      ],
      required: true,
      flex: 1
    },
    {
      type: 'select',
      field: 'assignee',
      label: 'Assignee',
      options: this.getAssigneeOptions(),
      required: true,
      flex: 1
    },
    {
      type: 'select',
      field: 'status',
      label: 'Status',
      options: [
        { label: 'Not Started', value: 'not_started' },
        { label: 'In Progress', value: 'in_progress' },
        { label: 'Complete', value: 'complete' }
      ],
      required: true,
      flex: 1
    }
  ];

  get showSummary() {
    return this.teamData.length > 0 || this.taskData.length > 0;
  }

  addTeamMember() {
    this.teamData = [...this.teamData, { name: '', role: 'developer', email: '' }];
    this.updateAssigneeOptions();
  }

  addTask() {
    this.taskData = [...this.taskData, { title: '', priority: 'medium', assignee: '', status: 'not_started' }];
  }

  getAssigneeOptions() {
    return this.teamData.map(member => ({
      label: member.name || 'Unnamed',
      value: member.name || 'unnamed'
    }));
  }

  updateAssigneeOptions() {
    // Update task column options when team changes
    const assigneeColumn = this.taskColumns.find(col => col.field === 'assignee');
    if (assigneeColumn && 'options' in assigneeColumn) {
      assigneeColumn.options = this.getAssigneeOptions();
    }
  }

  getTaskStats() {
    const total = this.taskData.length;
    const completed = this.taskData.filter(task => task.status === 'complete').length;
    return `${completed}/${total}`;
  }

  getCompletionPercentage(): number {
    if (this.taskData.length === 0) return 0;
    const completed = this.taskData.filter(task => task.status === 'complete').length;
    return Math.round((completed / this.taskData.length) * 100);
  }
}
import '@progress-i360/progress-web-components/table-editor';

// Create editable contact table with name and email columns
const contactTable = document.createElement('pwc-table-editor');
contactTable.name = 'contacts';
contactTable.placeholderHeading = 'No contacts yet';
contactTable.placeholder = 'Add your first contact below';

const columns = [
  { type: 'input', field: 'name', label: 'Full Name', flex: 1, required: true },
  { type: 'input', field: 'email', label: 'Email Address', flex: 1, inputType: 'email' }
];
contactTable.columns = columns;

contactTable.addEventListener('pwc-change', (event) => {
  console.log('Contact data updated:', event.detail);
});

document.body.appendChild(contactTable);

Usage Patterns

  • Data Collection Forms - Structured data entry for contacts, inventory, expenses, and other tabular information
  • Configuration Management - Settings and parameter management requiring multiple related fields per entry
  • Bulk Data Entry - Efficient input of multiple records with consistent field structure and validation
  • List Management Interfaces - Dynamic addition and removal of structured data items with inline editing capabilities

Best Practices

Content Strategy Guidelines

  • Clear Column Labels - Use descriptive, concise column headers that clearly indicate the expected data type and format
  • Appropriate Field Types - Choose input types (text, email, number, date) and select options that match data requirements and user expectations
  • Helpful Validation Messages - Provide clear hints and validation feedback that guide users toward successful data entry
  • Logical Column Ordering - Arrange columns in a natural flow that matches user mental models and data entry workflows

Performance Optimization

  • Responsive Column Management - Use display breakpoints (s, m, l, xl) strategically to maintain usability across different screen sizes
  • Efficient Validation Handling - Implement debounced validation and change handlers to optimize performance during rapid data entry
  • Memory Management - Properly manage row additions and deletions to prevent memory leaks and maintain application performance
  • Loading State Integration - Use skeleton states and loading indicators to provide feedback during data operations

Integration Architecture

  • Form Context Coordination - Integrate seamlessly with PWC form systems for validation, submission, and error handling workflows
  • Column Configuration Management - Design flexible column configurations that can be easily modified and extended for different use cases
  • Data Validation Strategies - Implement comprehensive validation patterns that work across different column types and data requirements
  • Accessibility Standards - Ensure proper ARIA attributes, keyboard navigation, and screen reader compatibility for table editing interfaces

Common Use Cases

Data Table Headers

  • Editable configuration tables with system parameters and settings that require structured input and validation
  • User management interfaces with role assignments, permissions, and profile information editing capabilities
  • Content management systems with metadata editing and structured content organization features

Search Result Sections

  • Bulk editing interfaces for search results with inline modification capabilities and batch operation support
  • Filter configuration tables with complex criteria setup and dynamic rule management functionality
  • Result annotation systems with structured tagging and categorization features for content organization

Dashboard Widget Headers

  • Configuration panels for dashboard widgets with parameter setup and display option management
  • Data source configuration tables with connection parameters and query setup functionality
  • Alert and notification management with criteria definition and action configuration capabilities

Troubleshooting

Common Issues

Actions Not Triggering

Symptoms: Add/remove row buttons don't work, cell editing doesn't save, or form validation doesn't update properly

Solutions:

  • Verify that column configurations include proper field names and validation rules for data binding and form integration

  • Check that form context is properly connected and table editor is receiving change events from input components

  • Ensure debounced functions are working correctly and not being blocked by rapid user interactions or state changes

Actions Not Visible

Symptoms: Table doesn't render, columns are missing, or action buttons don't appear in the interface

Solutions:

  • Confirm that column configurations contain valid type definitions (input or select) with appropriate field mappings

  • Check that responsive display settings (s, m, l, xl) are appropriate for current screen size and viewport constraints

  • Verify that placeholder content renders correctly when no data is present and loading states display properly

Layout Issues

Symptoms: Columns appear with incorrect widths, table doesn't fit container, or responsive behavior doesn't work properly

Solutions:

  • Use appropriate flex values and width constraints (minWidth, maxWidth) to control column sizing and responsive behavior

  • Check that table container styling accommodates horizontal scrolling when needed for narrow viewport widths

  • Ensure column display breakpoints are configured correctly for different screen sizes and device orientations

Icon Problems

Symptoms: Add/remove icons don't display, action buttons appear without icons, or loading indicators don't show properly

Solutions:

  • Verify that add, trash-can, and rotate-180 icons are available in your icon system and configured correctly

  • Check that button components render correctly with appropriate icon sizing and color theming for different states

  • Ensure loading spinner animations work properly and provide appropriate visual feedback during data operations

Implementation Support

  • Column Configuration Strategies - Best practices for designing flexible column definitions, validation rules, and responsive display patterns
  • Data Management Patterns - Strategies for handling large datasets, batch operations, and efficient data synchronization with backend systems
  • Accessibility Compliance - WCAG guidelines implementation, keyboard navigation patterns, and screen reader optimization for complex table editing interfaces

Resources

Storybook Documentation

For comprehensive API documentation, interactive examples, and testing tools: 📖 View Complete API Documentation in Storybook →


This guide provides high-level implementation guidance. For detailed API specifications and interactive examples, visit our Storybook documentation.