Skip to main content

Overview

Custom renderers are JavaScript functions that receive cell context and modify how cell content appears in the grid. They execute on the client-side during cell rendering and have access to the cell element, grid state, and work item data.

Renderer Configuration

Custom renderers are configured in the RISKSHEET JSON configuration:
{
  "columns": [
    {
      "id": "customColumn",
      "header": "Custom Display",
      "binding": "customField",
      "cellRenderer": "myCustomRenderer"
    }
  ],
  "cellRenderers": {
    "myCustomRenderer": "function(grid, cell, item, value) { /* rendering logic */ }"
  }
}
PropertyTypeRequiredDescription
cellRendererStringYesIdentifier key for custom renderer function
cellRenderers[key]StringYesJavaScript function code (as string or reference)
cellCssStringNoCSS class applied to cell for styling
readOnlyBooleanNoWhether cell is editable (prevents editor if true)

Custom Renderer Function Signature

All custom renderers receive four parameters:
function(grid, cell, item, value) {
  // grid:  FlexGrid instance
  // cell:  Cell HTML element (HTMLTableCellElement)
  // item:  Work item data object
  // value: Current cell value
}

Parameter Details

ParameterTypePropertiesExample
gridFlexGrid.itemsSource, .columns, .currentCell, .selectionAccess grid state, selected rows
cellHTMLElement.innerHTML, .textContent, .className, .styleModify cell HTML content and styling
itemObjectProperties from work item (binding-dependent)item.id, item.name, item.severity
valueAnyCurrent cell value from dataNumber, string, object, array

Implementation Patterns

Pattern 1: Simple Text Transformation

Transform value before display:
function renderPriority(grid, cell, item, value) {
  const priorities = {
    1: 'Critical',
    2: 'High',
    3: 'Medium',
    4: 'Low'
  };
  cell.textContent = priorities[value] || value;
}

Pattern 2: HTML Content with Styling

Render formatted HTML with custom styles:
function renderStatus(grid, cell, item, value) {
  const statusColors = {
    'Open': '#ff6b6b',
    'In Progress': '#ffd93d',
    'Resolved': '#6bcf7f',
    'Closed': '#4d96ff'
  };
  
  const color = statusColors[value] || '#cccccc';
  cell.innerHTML = `<span style="
    background-color: ${color}; 
    color: white; 
    padding: 4px 8px; 
    border-radius: 3px; 
    font-weight: bold;
  ">${value}</span>`;
}

Pattern 3: Conditional Cell Decoration

Add CSS classes based on data conditions:
function renderSeverity(grid, cell, item, value) {
  cell.textContent = value;
  
  // Remove previous severity classes
  cell.classList.remove('severity-critical', 'severity-high', 'severity-medium', 'severity-low');
  
  // Apply class based on value
  if (value === 'Critical') cell.classList.add('severity-critical');
  else if (value === 'High') cell.classList.add('severity-high');
  else if (value === 'Medium') cell.classList.add('severity-medium');
  else cell.classList.add('severity-low');
}
Render data from linked work items:
function renderOwnerInfo(grid, cell, item, value) {
  // value contains owner ID
  // Look up owner details from item context
  if (!item.ownerDetails) {
    cell.textContent = value || '—';
    return;
  }
  
  const owner = item.ownerDetails;
  cell.innerHTML = `
    <div class="owner-card">
      <strong>${owner.name}</strong><br/>
      <small>${owner.email}</small>
    </div>
  `;
}

Pattern 5: Dynamic Content Based on Grid State

Render differently based on selection or comparison mode:
function renderRowStatus(grid, cell, item, value) {
  const isSelected = grid.selection && grid.selection.contains(grid.itemsSource.indexOf(item));
  const isInCompare = grid.isInCompareMode ? true : false;
  
  let content = value || '—';
  
  if (isInCompare) {
    content = `<span class="compared">${content}</span>`;
  }
  
  if (isSelected) {
    cell.classList.add('selected-row');
  }
  
  cell.textContent = content;
}

Pattern 6: Visual Indicator (Progress Bar, Risk Indicator)

Render visual representation of numeric data:
function renderRiskLevel(grid, cell, item, value) {
  // value is numeric risk score (0-100)
  const percentage = Math.min(Math.max(value || 0, 0), 100);
  const color = percentage > 75 ? '#ff6b6b' : percentage > 50 ? '#ffd93d' : '#6bcf7f';
  
  cell.innerHTML = `
    <div class="risk-bar" style="
      background: linear-gradient(to right, ${color} ${percentage}%, #eee ${percentage}%);
      height: 20px;
      border-radius: 3px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #333;
      font-size: 12px;
      font-weight: bold;
    ">
      ${percentage}%
    </div>
  `;
}

Cell Context Object Structure

The item parameter contains the work item data:
item = {
  id: "REQ-001",                    // Work item ID
  type: "Requirement",              // Work item type
  title: "System shall log errors",  // Title/name
  description: "Detailed description", // Description
  severity: "High",                 // Risk parameter (if applicable)
  occurrence: "Medium",             // Risk parameter (if applicable)
  status: "Open",                   // Current status
  assignee: "john.smith",           // Assigned user
  ...,                              // All configured column bindings
  _link: "<a href=...>",            // HTML link (for link columns)
  _compare: { /* comparison state */ }  // Comparison metadata (if in compare mode)
}
Access grid state via the grid parameter: grid.itemsSource (all items), grid.columns (column definitions), grid.currentCell (active cell), grid.selection (selected range), grid.isReadOnly (readonly mode).

Styling Custom Renderers

CSS Classes

Custom renderers should use semantic CSS classes:
/* Cell styling */
.severity-critical {
  background-color: #ff6b6b;
  color: white;
  font-weight: bold;
}

.severity-high {
  background-color: #ffd93d;
  color: #333;
}

.severity-medium {
  background-color: #fff7a8;
  color: #333;
}

.severity-low {
  background-color: #6bcf7f;
  color: white;
}

/* Custom content */
.owner-card {
  padding: 4px;
  border-left: 3px solid #4d96ff;
  background: #f0f7ff;
  border-radius: 3px;
}

.risk-bar {
  height: 20px;
  border-radius: 3px;
  transition: background 0.2s ease;
}

.risk-bar:hover {
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
Apply classes via configuration:
{
  "columns": [
    {
      "id": "severity",
      "cellRenderer": "renderSeverity",
      "cellCss": "severity-cell"
    }
  ]
}

Special Considerations

Comparison Mode

When in comparison/baseline mode, renderers should respect comparison highlighting:
function renderWithComparison(grid, cell, item, value) {
  const isComparing = item._compare ? true : false;
  const changed = item._compare?.changed === true;
  
  if (isComparing && changed) {
    cell.classList.add('compared-changed');
  }
  
  cell.textContent = value || '—';
}

Performance

Renderers are called during every grid render cycle. Keep logic efficient:
// ❌ AVOID: Heavy computation on every render
function expensiveRenderer(grid, cell, item, value) {
  const result = performHeavyCalculation(value); // Slow!
  cell.textContent = result;
}

// ✅ GOOD: Cache or precompute
function efficientRenderer(grid, cell, item, value) {
  // Use precomputed value if available
  const result = item._cached || value;
  cell.textContent = result;
}

HTML Injection Safety

Sanitize user-provided content when using innerHTML:
function renderUserContent(grid, cell, item, value) {
  // Create text node first (safe from HTML injection)
  const textNode = document.createTextNode(value || '');
  cell.textContent = ''; // Clear
  cell.appendChild(textNode);
  
  // OR use textContent instead of innerHTML
  cell.textContent = value;
}

Comparison Matrix: Renderer vs. Column Type

RequirementColumn TypeCustom RendererBest For
Display transformation❌ Limited✅ YesText formatting, icons
Conditional styling⚠️ CSS only✅ YesStatus indicators, risk levels
Related item data❌ No✅ YesOwner info, parent item details
HTML content❌ No✅ YesRich formatting, progress bars
Editor control✅ Yes❌ NoDropdown, date picker, validation
Data validation✅ Yes❌ NoType checking, constraints
Performance critical✅ Yes⚠️ OverheadSimple columns, large datasets

Example: Complete Custom Renderer Configuration

{
  "columns": [
    {
      "id": "riskScore",
      "header": "Risk Score",
      "binding": "riskScore",
      "width": 150,
      "cellRenderer": "renderRiskScore",
      "cellCss": "risk-score-cell"
    },
    {
      "id": "owner",
      "header": "Owner",
      "binding": "ownerId",
      "cellRenderer": "renderOwnerCard"
    }
  ],
  "cellRenderers": {
    "renderRiskScore": "function(grid, cell, item, value) { const pct = Math.min(Math.max(value||0, 0), 100); const color = pct > 75 ? '#ff6b6b' : pct > 50 ? '#ffd93d' : '#6bcf7f'; cell.innerHTML = '<div style=\"background: linear-gradient(to right, ' + color + ' ' + pct + '%, #eee ' + pct + '%); height: 20px; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-weight: bold;\">' + pct + '%</div>'; }",
    "renderOwnerCard": "function(grid, cell, item, value) { if (!item.ownerEmail) { cell.textContent = value || '—'; return; } cell.innerHTML = '<div class=\"owner-card\"><strong>' + value + '</strong><br/><small>' + item.ownerEmail + '</small></div>'; }"
  },
  "styles": [
    {
      "class": "risk-score-cell",
      "rules": "padding: 4px; text-align: center; font-weight: bold;"
    },
    {
      "class": "owner-card",
      "rules": "padding: 8px; border-left: 3px solid #4d96ff; background: #f0f7ff; border-radius: 3px; font-size: 12px;"
    }
  ]
}
Refer to Create Custom Renderers for implementation steps and Cell Decorators for additional styling reference.
Source Code
  • AppConfig.ts
  • WorkItemBasedReview.java
  • DocumentConfigProvider.java
  • CellPreviewFormatter.ts