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 */ }"
}
}
Property Type Required Description cellRendererString Yes Identifier key for custom renderer function cellRenderers[key]String Yes JavaScript function code (as string or reference) cellCssString No CSS class applied to cell for styling readOnlyBoolean No Whether 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
Parameter Type Properties Example gridFlexGrid .itemsSource, .columns, .currentCell, .selectionAccess grid state, selected rows cellHTMLElement .innerHTML, .textContent, .className, .styleModify cell HTML content and styling itemObject Properties from work item (binding-dependent) item.id, item.name, item.severityvalueAny Current cell value from data Number, 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 : 4 px ;
border-left : 3 px solid #4d96ff ;
background : #f0f7ff ;
border-radius : 3 px ;
}
.risk-bar {
height : 20 px ;
border-radius : 3 px ;
transition : background 0.2 s ease ;
}
.risk-bar:hover {
box-shadow : 0 2 px 4 px 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 || '—' ;
}
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
Requirement Column Type Custom Renderer Best For Display transformation ❌ Limited ✅ Yes Text formatting, icons Conditional styling ⚠️ CSS only ✅ Yes Status indicators, risk levels Related item data ❌ No ✅ Yes Owner info, parent item details HTML content ❌ No ✅ Yes Rich formatting, progress bars Editor control ✅ Yes ❌ No Dropdown, date picker, validation Data validation ✅ Yes ❌ No Type checking, constraints Performance critical ✅ Yes ⚠️ Overhead Simple 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