Form Layout Feature Implementation Guide
Implementation guide for the form layout feature with sections and grid-based layouts
Form Layout Feature Implementation Guide
Status: Backend Complete ✅ | Frontend Pending ⏳
Overview
This document provides the implementation plan for the form layout feature that allows users to organize workflow forms into sections with grid-based layouts and control field visibility per workflow step.
✅ Completed: Backend Implementation
1. Parser Updates (src/app/core/approvalml/parser.py)
New Models Added:
class ResponsiveLayout(BaseModel):
tablet: Optional[int] = None # Max columns on tablet
mobile: Optional[int] = None # Max columns on mobile
class FormSection(BaseModel):
id: str
title: str
description: Optional[str] = None
initial: bool = False
grid: list[list[str]] # Grid layout: rows with field names
class FormLayout(BaseModel):
sections: list[FormSection]
responsive: Optional[ResponsiveLayout] = NoneApprovalProcess Enhanced:
- Added
form_layout: Optional[FormLayout]field - Extracts layout from
form.layoutin YAML - Validates section references in workflow steps
- Validates field references in section grids
WorkflowStep Enhanced:
- Added
view_sections: Optional[list[str]]- sections shown as readonly - Added
edit_sections: Optional[list[str]]- sections shown as editable - Default behavior: if both are None, all sections shown in view mode
Field Types Added:
EMAIL = "email"- Email validationRADIO = "radio"- Radio button group with optionaldisplay_as: "buttons"
Validation Logic:
- Ensures section IDs are unique
- Validates grid field references exist in form.fields
- Validates workflow step section references exist in layout.sections
- Validates responsive breakpoint values
2. AI Reference Updates (src/app/core/approvalml/syntax_reference.py)
Documentation Added:
- Complete "Form Layout (Optional)" section with examples
- Section visibility in workflow steps documentation
- Updated field type list to include
emailandradio - Updated STEP_TYPES to include
view_sectionsandedit_sections
3. Test Results
Example YAML successfully parses with validation:
✅ Parsing successful!
Workflow name: Employee Onboarding
Has layout: True
Number of sections: 3
- personal_info: Employee Details (initial=True, rows=3)
- it_setup: IT Equipment Setup (initial=False, rows=3)
- hr_verification: HR Verification (initial=False, rows=2)
Responsive settings: tablet=2, mobile=1
Workflow steps:
- it_provisioning: view=['personal_info'], edit=['it_setup']
- hr_finalization: view=['personal_info', 'it_setup'], edit=['hr_verification']
✅ Validation: True⏳ TODO: Frontend Implementation
Phase 1: Core Form Rendering Updates
File: frontend/src/components/workflows/DynamicFormHookForm.jsx
Current Behavior:
- Renders fields in a simple list
- No section grouping
- No grid layout support
Required Changes:
- Add Layout Detection:
const hasLayout = workflow?.workflow_definition?.form_layout;
const formLayout = workflow?.workflow_definition?.form_layout;- Create Section Renderer Component:
const FormSection = ({ section, fields, control, register, watch, setValue, errors, mode = 'edit' }) => {
const sectionFields = {};
// Build a map of field names to field definitions
fields.forEach(field => {
sectionFields[field.name] = field;
});
const isReadonly = mode === 'view';
return (
<div className="form-section mb-8">
{/* Section Header */}
<div className="section-header bg-gradient-to-r from-blue-50 to-blue-100 p-4 rounded-t-lg border-b-2 border-blue-200">
<h3 className="text-lg font-semibold text-gray-800">{section.title}</h3>
{section.description && (
<p className="text-sm text-gray-600 mt-1">{section.description}</p>
)}
</div>
{/* Section Content with Grid */}
<div className="section-content bg-white p-6 rounded-b-lg border border-gray-200">
{section.grid.map((row, rowIndex) => (
<div
key={rowIndex}
className={`grid gap-4 mb-4 ${getGridClass(row.length, formLayout?.responsive)}`}
>
{row.map(fieldName => {
const field = sectionFields[fieldName];
if (!field) return null;
return (
<div key={fieldName} className="field-wrapper">
{renderField(field, {
control,
register,
watch,
setValue,
errors,
readonly: isReadonly
})}
</div>
);
})}
</div>
))}
</div>
</div>
);
};- Grid CSS Class Generator:
const getGridClass = (columnCount, responsive) => {
// Default desktop grid
let gridClass = `grid-cols-${columnCount}`;
// Apply responsive breakpoints
if (responsive) {
if (responsive.tablet) {
gridClass += ` md:grid-cols-${Math.min(columnCount, responsive.tablet)}`;
}
if (responsive.mobile) {
gridClass += ` sm:grid-cols-${Math.min(columnCount, responsive.mobile)}`;
}
}
return gridClass;
};- Main Render Logic Update:
// In DynamicFormHookForm main render
if (hasLayout) {
// Determine which sections to show based on current step
const sectionsToShow = getSectionsForCurrentStep();
return (
<form onSubmit={handleSubmit(onSubmit)}>
{sectionsToShow.map(({ section, mode }) => (
<FormSection
key={section.id}
section={section}
fields={allFields}
control={control}
register={register}
watch={watch}
setValue={setValue}
errors={errors}
mode={mode}
/>
))}
</form>
);
} else {
// Fall back to current simple list rendering
return (/* existing code */);
}- Section Visibility Logic:
const getSectionsForCurrentStep = () => {
const currentStep = getCurrentWorkflowStep(); // From context or props
const layout = formLayout;
if (!layout || !currentStep) {
// Show all sections in edit mode if no step info
return layout.sections.map(section => ({ section, mode: 'edit' }));
}
const result = [];
// Add view sections
if (currentStep.view_sections) {
currentStep.view_sections.forEach(sectionId => {
const section = layout.sections.find(s => s.id === sectionId);
if (section) {
result.push({ section, mode: 'view' });
}
});
}
// Add edit sections
if (currentStep.edit_sections) {
currentStep.edit_sections.forEach(sectionId => {
const section = layout.sections.find(s => s.id === sectionId);
if (section) {
result.push({ section, mode: 'edit' });
}
});
}
// Default: if no sections specified, show all in view mode
if (result.length === 0) {
return layout.sections.map(section => ({ section, mode: 'view' }));
}
return result;
};File: frontend/src/components/workflows/ApprovalPageUI.jsx
Required Changes:
- Update Field Display Logic:
- Check if form has layout
- Group fields by section
- Render sections with banners
- Respect readonly mode for view sections
- Section-Based Rendering:
const renderFormWithLayout = () => {
const layout = workflowDefinition?.form_layout;
const currentStep = getCurrentStep();
if (!layout) {
return renderFormFields(); // Existing logic
}
return (
<div className="form-sections">
{layout.sections.map(section => {
const isViewMode = currentStep?.view_sections?.includes(section.id);
const isEditMode = currentStep?.edit_sections?.includes(section.id);
const shouldShow = !currentStep || isViewMode || isEditMode;
if (!shouldShow) return null;
return (
<div key={section.id} className="section-display mb-6">
<div className="section-header bg-gray-100 px-4 py-3 rounded-t-lg">
<h4 className="font-semibold text-gray-800">{section.title}</h4>
{section.description && (
<p className="text-sm text-gray-600">{section.description}</p>
)}
</div>
<div className="section-fields bg-white p-4 rounded-b-lg border border-gray-200">
{renderSectionFields(section, isViewMode)}
</div>
</div>
);
})}
</div>
);
};Phase 2: Initial Submission Form
File: frontend/src/components/workflows/WorkflowSubmissionForm.jsx (or similar)
Required Changes:
- Show Only Initial Sections:
const getInitialSections = () => {
const layout = workflow?.workflow_definition?.form_layout;
if (!layout) return null;
return layout.sections.filter(section => section.initial);
};- Render Initial Sections:
- Show banner for each initial section
- Render fields in grid layout
- All fields editable (not readonly)
Phase 3: Test Mode Updates
File: frontend/src/components/admin/WorkflowTestMode.jsx
Required Changes:
- Display Section Information:
<div className="test-workflow-info">
{workflow.form_layout && (
<div className="layout-info mb-4">
<h4 className="font-semibold">Form Layout</h4>
<div className="sections-list">
{workflow.form_layout.sections.map(section => (
<div key={section.id} className="section-item flex items-center gap-2 py-1">
<span className="text-sm">{section.title}</span>
{section.initial && (
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded">
Initial
</span>
)}
</div>
))}
</div>
</div>
)}
</div>- Show Section Visibility for Each Step:
<div className="step-sections-info text-sm text-gray-600">
{step.view_sections && (
<div>View: {step.view_sections.join(', ')}</div>
)}
{step.edit_sections && (
<div>Edit: {step.edit_sections.join(', ')}</div>
)}
</div>Phase 4: CSS and Responsive Styling
File: frontend/src/styles/workflow-layout.css (new file)
/* Section Styling */
.form-section {
@apply mb-6;
}
.section-header {
@apply bg-gradient-to-r from-blue-50 to-indigo-50 px-6 py-4 rounded-t-lg border-l-4 border-blue-500;
}
.section-header h3 {
@apply text-lg font-semibold text-gray-800 mb-1;
}
.section-header p {
@apply text-sm text-gray-600;
}
.section-content {
@apply bg-white p-6 rounded-b-lg border border-gray-200 shadow-sm;
}
/* Grid Layout */
.section-grid {
@apply grid gap-4;
}
/* Responsive grid classes - generate for 1-6 columns */
@layer utilities {
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 768px) {
.sm\\:grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)) !important; }
.sm\\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
}
@media (max-width: 1024px) and (min-width: 769px) {
.md\\:grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)) !important; }
.md\\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
.md\\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
}
}
/* Readonly field styling */
.field-readonly {
@apply bg-gray-50 cursor-not-allowed opacity-75;
}
.field-readonly input,
.field-readonly textarea,
.field-readonly select {
@apply bg-gray-50 cursor-not-allowed;
}Phase 5: Documentation Updates
File: frontend/content/docs/markup-language/formtypes.md
Updates Needed:
- Add comprehensive section about Form Layouts
- Include grid syntax examples
- Document initial section behavior
- Explain view_sections and edit_sections
- Add responsive breakpoint documentation
- Include complete working examples
Suggested Structure:
# Form Layout and Sections
## Overview
The layout feature allows you to organize form fields into sections with custom grid layouts...
## Basic Syntax
[Layout YAML example]
## Section Properties
- `id`: Unique identifier
- `title`: Display title
- `description`: Optional description
- `initial`: Show on initial submission
- `grid`: Array of field name arrays
## Grid Layout
[Grid examples with 1, 2, 3+ columns]
## Responsive Behavior
[Tablet and mobile breakpoint examples]
## Workflow Integration
[view_sections and edit_sections examples]
## Complete Examples
[Full working examples]Testing Checklist
Unit Tests
- Parser validates layout structure
- Parser catches invalid section references
- Parser catches invalid field references in grids
- Workflow step validation works
Integration Tests
- Layout renders correctly with 1 column
- Layout renders correctly with 2 columns
- Layout renders correctly with 3+ columns
- Responsive breakpoints work on mobile
- Responsive breakpoints work on tablet
- Initial sections show on form creation
- View sections are readonly in approval steps
- Edit sections are editable in approval steps
- Section banners display correctly
- No layout fallback works (legacy forms)
E2E Tests
- Create workflow with layout
- Submit initial form (only initial sections)
- Approve step with view/edit sections
- Complete full workflow with multiple steps
- Test mode displays layout correctly
- Mobile responsive works end-to-end
Example YAML for Testing
Located in: docs/examples/advanced_layout_test.yaml
This file contains a complete working example with:
- 3 sections (personal_info, it_setup, hr_verification)
- Grid layouts with varying column counts
- Responsive settings (tablet: 2, mobile: 1)
- Workflow steps with view_sections and edit_sections
- Initial section marked (personal_info)
Migration Guide
For Existing Workflows (Backwards Compatibility)
No layout specified:
- Works as before
- All fields display in simple list
- No breaking changes
With layout specified:
- Fields must be referenced in section grids
- Unreferenced fields won't display (validation catches this)
- Initial submission shows only
initial: truesections
Performance Considerations
- Grid Rendering: CSS Grid is performant for up to 100+ fields
- Section Visibility: Computed once per render, not per field
- Responsive: Uses CSS media queries, not JavaScript
- Validation: Happens at parse time, not render time
Browser Support
- Modern browsers (Chrome, Firefox, Safari, Edge)
- CSS Grid support required (all browsers since 2017)
- Mobile responsive works on iOS Safari and Chrome Android
Future Enhancements (Not in Scope)
- Conditional section visibility (show section if condition)
- Section-level permissions
- Nested sections
- Custom section styling themes
- Drag-and-drop field reordering
- Visual layout editor
Contact / Questions
For questions about this implementation:
- Backend: Parser and validation logic
- Frontend: Component rendering and styling
- Documentation: Markup language docs
Last Updated: 2025-10-07 Status: Backend complete, frontend implementation pending Estimated Frontend Work: 8-12 hours