DataView Designer Documentation
Overview (Non-Technical)
The DataView Designer enables users to configure how data is displayed, filtered, sorted, and navigated without writing code. Users select an entity (like "Customers" or "Orders"), choose which fields to show, add filters and sorting, and define actions users can take (view details, edit, delete). The system generates a fully-functional data table or custom view.
Key Purpose: Enable users to create powerful data viewing and manipulation interfaces that can handle filtering, sorting, searching, pagination, and bulk actions without any coding.
Standards
DataView Architecture
DataView Components:
┌─────────────────────────────────────────────────┐
│ DATAVIEW (Data Display) │
├─────────────────────────────────────────────────┤
│ │
│ Search Filters Sort [Actions] │
│ ┌──────────────────────────────────────────┐ │
│ │ Field1 │ Field2 │ Field3 │ Actions │ │
│ ├──────────────────────────────────────────┤ │
│ │ Value1 │ Value2 │ Value3 │ [View] │ │
│ │ Value1 │ Value2 │ Value3 │ [Edit] │ │
│ │ Value1 │ Value2 │ Value3 │ [Delete] │ │
│ │ Value1 │ Value2 │ Value3 │ [More] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Pagination: 1 2 3 ... [10-20 of 100 records] │
│ │
└─────────────────────────────────────────────────┘
Configuration Schema
{
"dataviewId": "string",
"dataviewName": "string",
"dataviewDescription": "string",
"version": "1.0",
"entityId": "string",
"displaySettings": {
"viewType": "table|grid|card|timeline|list",
"pageSize": 20,
"showPagination": true,
"showSearch": true,
"showFilters": true,
"showColumnSelector": true,
"showBulkActions": true,
"density": "compact|comfortable|spacious",
"striped": true
},
"columns": [
{
"columnId": "col-1",
"fieldId": "customer_name",
"fieldName": "Customer Name",
"dataType": "string",
"width": 200,
"sortable": true,
"filterable": true,
"visible": true,
"alignment": "left|center|right",
"format": "plain|currency|date|percentage|custom",
"formatConfig": {
"currency": "USD",
"dateFormat": "YYYY-MM-DD",
"decimalPlaces": 2
},
"customRenderer": null,
"searchable": true
}
],
"filters": [
{
"filterId": "filter-1",
"fieldId": "status",
"fieldName": "Status",
"filterType": "equals|contains|range|date|multi-select",
"defaultValue": "active",
"options": ["active", "inactive", "archived"],
"label": "Filter by Status",
"required": false
}
],
"sorting": {
"defaultSortField": "created_date",
"defaultSortOrder": "desc",
"multiSortEnabled": true
},
"actions": [
{
"actionId": "view",
"actionName": "View Details",
"actionType": "view|edit|delete|custom|navigate",
"icon": "eye",
"cssClass": "primary",
"target": "modal|page|dialog",
"targetUrl": "/records/${id}",
"requiresSelection": false,
"bulkApplicable": false
},
{
"actionId": "edit",
"actionName": "Edit",
"actionType": "edit",
"icon": "pencil",
"requiresSelection": false,
"bulkApplicable": true
}
],
"rowActions": {
"expandable": true,
"clickableRow": false,
"parentChildRelationship": {
"enabled": false,
"parentId": "",
"childViews": []
}
},
"styling": {
"alternateRowColor": true,
"highlightOnHover": true,
"borderStyle": "bordered|borderless",
"cssClass": "custom-dataview"
},
"metadata": {
"createdBy": "string",
"createdDate": "ISO8601",
"modifiedBy": "string",
"modifiedDate": "ISO8601"
}
}
Do's and Don'ts
✅ DO
- Show only necessary columns: Too many columns create clutter
- Make frequently-filtered fields filterable: Enable quick data discovery
- Set appropriate column widths: Based on content type and length
- Use semantic colors: Green for success, red for error/delete
- Implement search on key fields: Name, ID, email, etc.
- Add row actions inline: View, Edit, Delete icons in each row
- Use pagination for large datasets: Avoid loading thousands of rows
- Enable sorting on numeric/date columns: Always sortable
- Provide bulk actions carefully: Only for safe operations
- Show loading state: During data fetch
- Implement empty states: Show helpful message when no data
- Test with actual data: Verify column widths, formats, performance
❌ DON'T
- Display sensitive data in lists: Use view details modal instead
- Create overly wide tables: Causes horizontal scrolling
- Use cryptic column names: Be descriptive and user-friendly
- Enable bulk delete without confirmation: Add safety confirmation
- Overload with filters: Keep to 5-7 most important filters
- Hardcode filter values: Use configurable defaults
- Skip loading indicators: Users get confused with no feedback
- Use real-time updates for datasets > 1000 rows: Too expensive
- Mix action types inconsistently: View should always open details
- Forget to handle errors: Network errors, empty results, etc.
- Ignore mobile responsiveness: DataView must work on phones
- Create circular parent-child relationships: Causes infinite loops
Technical Details
View Types
1. Table View (Default):
export function TableDataView({ dataviewConfig, entityId }) {
const [page, setPage] = useState(1);
const [sortBy, setSortBy] = useState(dataviewConfig.sorting.defaultSortField);
const [sortOrder, setSortOrder] = useState(dataviewConfig.sorting.defaultSortOrder);
const [filters, setFilters] = useState(getDefaultFilters(dataviewConfig));
const { data, isLoading } = useQuery({
queryKey: ['records', entityId, page, sortBy, sortOrder, filters],
queryFn: () => api.record.list({
entityId,
page,
pageSize: dataviewConfig.displaySettings.pageSize,
sortBy,
sortOrder,
filters
})
});
if (isLoading) return <Skeleton rows={dataviewConfig.displaySettings.pageSize} />;
return (
<div>
<FilterBar
filters={dataviewConfig.filters}
onChange={setFilters}
/>
<table>
<thead>
{dataviewConfig.columns.map(col => (
<th key={col.columnId} onClick={() => toggleSort(col.fieldId)}>
{col.fieldName}
{sortBy === col.fieldId && (
<span>{sortOrder === 'asc' ? '↑' : '↓'}</span>
)}
</th>
))}
<th>Actions</th>
</thead>
<tbody>
{data.records.map(record => (
<tr key={record.id}>
{dataviewConfig.columns.map(col => (
<td key={col.columnId}>
<FieldRenderer
value={record[col.fieldId]}
format={col.format}
formatConfig={col.formatConfig}
/>
</td>
))}
<td>
<RowActionsMenu
record={record}
actions={dataviewConfig.actions}
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
current={page}
total={data.total}
pageSize={dataviewConfig.displaySettings.pageSize}
onChange={setPage}
/>
</div>
);
}
2. Card View:
export function CardDataView({ dataviewConfig, entityId }) {
// Similar data fetching as Table
return (
<div className="card-grid">
{data.records.map(record => (
<Card key={record.id}>
{dataviewConfig.columns.map(col => (
<CardField
key={col.columnId}
label={col.fieldName}
value={record[col.fieldId]}
format={col.format}
/>
))}
<CardActions actions={dataviewConfig.actions} record={record} />
</Card>
))}
</div>
);
}
3. Timeline View:
export function TimelineDataView({ dataviewConfig, entityId }) {
// Group records by date field
const groupedByDate = groupBy(data.records, dateField);
return (
<div className="timeline">
{Object.entries(groupedByDate).map(([date, records]) => (
<TimelineGroup key={date} date={date}>
{records.map(record => (
<TimelineItem key={record.id} record={record} />
))}
</TimelineGroup>
))}
</div>
);
}
Filter System
Filter Types & Implementation:
const filterImplementations = {
'equals': (field, value) => `${field} = '${value}'`,
'contains': (field, value) => `${field} LIKE '%${value}%'`,
'startsWith': (field, value) => `${field} LIKE '${value}%'`,
'endsWith': (field, value) => `${field} LIKE '%${value}'`,
'range': (field, min, max) => `${field} BETWEEN ${min} AND ${max}`,
'date': (field, operator, value) => {
const ops = {
'equals': '=',
'before': '<',
'after': '>',
'between': 'BETWEEN'
};
return `${field} ${ops[operator]} '${value}'`;
},
'multiSelect': (field, values) =>
`${field} IN (${values.map(v => `'${v}'`).join(',')})`,
'custom': (field, expression) => expression
};
// Filter UI Component
export function FilterBar({ filters, onChange }) {
return (
<div className="filter-bar">
{filters.map(filter => (
<FilterControl
key={filter.filterId}
filter={filter}
onChange={value => onChange(filter.filterId, value)}
/>
))}
</div>
);
}
Field Formatting
Format Types:
const fieldFormatters = {
'plain': (value) => String(value),
'currency': (value, config) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: config.currency || 'USD'
}).format(value),
'date': (value, config) =>
new Date(value).toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}),
'percentage': (value, config) =>
`${(value * 100).toFixed(config.decimalPlaces || 2)}%`,
'boolean': (value) => value ? '✓' : '✗',
'json': (value) => JSON.stringify(value, null, 2),
'email': (value) => (
<a href={`mailto:${value}`}>{value}</a>
),
'phone': (value) => (
<a href={`tel:${value}`}>{formatPhoneNumber(value)}</a>
)
};
Searching
Search Implementation:
// Frontend: Simple search across searchable columns
const handleSearch = (query) => {
const searchFields = dataviewConfig.columns
.filter(col => col.searchable)
.map(col => col.fieldId);
setFilters(prev => ({
...prev,
searchQuery: query,
searchFields
}));
};
// Middleware: Full-text search or LIKE operation
const records = await db.query(
`SELECT * FROM ${entityTable}
WHERE ${searchFields.map(f => `${f} LIKE @query`).join(' OR ')}
ORDER BY relevance DESC`,
{ query: `%${searchQuery}%` }
);
Parent-Child DataViews
Configuration:
{
"dataviewId": "orders-dataview",
"entityId": "orders",
"rowActions": {
"expandable": true,
"parentChildRelationship": {
"enabled": true,
"parentId": "orders",
"childViews": [
{
"relationshipId": "order-items",
"childEntityId": "order_items",
"childDataviewId": "order-items-dataview",
"foreignKeyField": "order_id",
"parentKeyField": "id"
}
]
}
}
}
Rendering:
export function ExpandableRow({ record, childViews, dataviewConfig }) {
const [expanded, setExpanded] = useState(false);
return (
<>
<tr>
<td>
<button onClick={() => setExpanded(!expanded)}>
{expanded ? '▼' : '▶'}
</button>
</td>
{/* Regular row cells */}
</tr>
{expanded && childViews.map(child => (
<tr key={child.relationshipId} className="child-row">
<td colSpan="100%">
<ChildDataView
parentId={record.id}
childDataviewConfig={child}
/>
</td>
</tr>
))}
</>
);
}
Bulk Actions
Bulk Action Support:
export function DataViewWithBulkActions({ dataviewConfig, entityId }) {
const [selectedIds, setSelectedIds] = useState([]);
const bulkActions = dataviewConfig.actions.filter(a => a.bulkApplicable);
const handleBulkAction = async (action) => {
const result = await api.record.bulkAction({
entityId,
recordIds: selectedIds,
action: action.actionId,
payload: action.payload
});
setSelectedIds([]); // Clear selection
refetchData(); // Reload table
};
return (
<>
{selectedIds.length > 0 && (
<BulkActionBar
recordCount={selectedIds.length}
actions={bulkActions}
onAction={handleBulkAction}
/>
)}
{/* DataView table with checkboxes */}
</>
);
}
Export Functionality
Export Support:
const exportHandlers = {
'csv': (records, columns) => {
const csv = [
columns.map(c => c.fieldName).join(','),
...records.map(r =>
columns.map(c => r[c.fieldId]).join(',')
)
].join('\n');
downloadFile(csv, 'export.csv', 'text/csv');
},
'excel': (records, columns) => {
const worksheet = XLSX.utils.json_to_sheet(records);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet);
XLSX.writeFile(workbook, 'export.xlsx');
},
'json': (records, columns) => {
downloadFile(
JSON.stringify(records, null, 2),
'export.json',
'application/json'
);
}
};
Key Files & Imports
| Purpose | File | Notes |
|---|---|---|
| DataView renderer | components/viewer/DataViewRenderer.tsx | Main component |
| Filter system | components/dataview/FilterBar.tsx | Filter UI |
| Sorting | components/dataview/SortingHeader.tsx | Column sorting |
| Pagination | components/dataview/Pagination.tsx | Page navigation |
| Actions | components/dataview/RowActions.tsx | Row buttons |
| Service layer | services/api.ts → record.* | API calls |
| Custom hooks | hooks/useDataView.ts | Data fetching, state |
Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
| Columns not showing | visible: false or not in config | Check column configuration, visible property |
| Filters not working | Filter condition incorrect | Verify entity has field, test SQL |
| Sorting broken | Non-sortable field | Enable sortable: true in column config |
| Search not finding results | Search fields not specified | Add searchable: true to columns |
| Pagination broken | Page size mismatch | Ensure pageSize matches query results |
| Large table slow | Loading too many rows | Implement pagination, add indexes |
| Parent-child infinite loop | Circular relationship | Verify relationship direction |
| Bulk action fails | Not marked bulkApplicable | Check action configuration |
| Export missing data | Custom renderer not exporting | Implement export logic in renderer |
DataView Creator Workflow
- Select Entity → Choose data source (Customers, Orders, etc.)
- Choose View Type → Table, Card, Timeline, Grid
- Select Columns → Choose fields to display, set order
- Configure Formatting → Number format, dates, currency
- Add Filters → Enable quick data filtering
- Set Sorting → Default sort order
- Define Actions → View, Edit, Delete, Custom
- Setup Search → Enable searching on key fields
- Test with Data → Verify display, filters, actions
- Configure Pagination → Set page size (default 20)
- Setup Exports → Enable CSV, Excel, JSON export
- Publish → Save version, make available
Related Documentation
- Page Designer Documentation - Form design
- Workflow Designer Documentation - Automation
- Renderers Documentation - Component engine