8.3 KiB
8.3 KiB
Page Layouts Feature
Overview
The Page Layouts feature allows administrators to customize how fields are displayed in detail and edit views using a visual drag-and-drop interface based on GridStack.js with a 6-column grid system.
Architecture
Backend Components
-
Database Schema (
migrations/tenant/20250126000008_create_page_layouts.js)page_layoutstable stores layout configurations- Fields:
id,name,object_id,is_default,layout_config,description - JSON-based
layout_configstores field positions and sizes
-
API Endpoints (
src/page-layout/)POST /page-layouts- Create a new page layoutGET /page-layouts?objectId={id}- Get all layouts for an objectGET /page-layouts/:id- Get a specific layoutGET /page-layouts/default/:objectId- Get the default layout for an objectPATCH /page-layouts/:id- Update a layoutDELETE /page-layouts/:id- Delete a layout
Frontend Components
-
PageLayoutEditor.vue - Visual editor for creating/editing layouts
- 6-column grid system using GridStack.js
- Drag and drop fields from sidebar
- Resize fields horizontally (1-6 columns width)
- Default width: 3 columns (2-column template effect)
- Save layout configuration
-
PageLayoutRenderer.vue - Renders fields based on saved layouts
- Used in detail and edit views
- Falls back to traditional 2-column layout if no layout configured
- Supports all field types
-
DetailViewEnhanced.vue & EditViewEnhanced.vue
- Enhanced versions of views with page layout support
- Automatically fetch and use default page layout
- Maintain backward compatibility with section-based layouts
Types
- PageLayout (
types/page-layout.ts)- Layout metadata and configuration
- Field position and size definitions
- Grid configuration options
Usage
1. Run Database Migration
cd backend
npm run migrate:tenant
2. Configure Page Layouts
Navigate to Setup → Objects → [Object Name] → Page Layouts tab:
- Click "New Layout" to create a layout
- Enter a layout name
- Drag fields from the right sidebar onto the 6-column grid
- Resize fields by dragging their edges (width only)
- Rearrange fields by dragging them to new positions
- Click "Save Layout" to persist changes
3. Use in Views
Option A: Use Enhanced Views (Recommended)
Replace existing views in your page:
<script setup>
import DetailViewEnhanced from '@/components/views/DetailViewEnhanced.vue'
import EditViewEnhanced from '@/components/views/EditViewEnhanced.vue'
const objectDefinition = ref(null)
// Fetch object definition...
</script>
<template>
<!-- Detail View -->
<DetailViewEnhanced
:config="detailConfig"
:data="currentRecord"
:object-id="objectDefinition.id"
@edit="handleEdit"
@delete="handleDelete"
@back="handleBack"
/>
<!-- Edit View -->
<EditViewEnhanced
:config="editConfig"
:data="currentRecord"
:object-id="objectDefinition.id"
@save="handleSave"
@cancel="handleCancel"
@back="handleBack"
/>
</template>
Option B: Use PageLayoutRenderer Directly
<script setup>
import PageLayoutRenderer from '@/components/PageLayoutRenderer.vue'
import { usePageLayouts } from '~/composables/usePageLayouts'
const { getDefaultPageLayout } = usePageLayouts()
const pageLayout = ref(null)
onMounted(async () => {
const layout = await getDefaultPageLayout(objectId)
if (layout) {
pageLayout.value = layout.layoutConfig
}
})
</script>
<template>
<PageLayoutRenderer
:fields="fields"
:layout="pageLayout"
:model-value="formData"
:readonly="false"
@update:model-value="formData = $event"
/>
</template>
4. Composable API
const {
getPageLayouts, // Get all layouts for an object
getPageLayout, // Get a specific layout
getDefaultPageLayout, // Get default layout for an object
createPageLayout, // Create new layout
updatePageLayout, // Update existing layout
deletePageLayout // Delete layout
} = usePageLayouts()
// Example: Create a layout
await createPageLayout({
name: 'Sales Layout',
objectId: 'uuid-here',
isDefault: true,
layoutConfig: {
fields: [
{ fieldId: 'field-1', x: 0, y: 0, w: 3, h: 1 },
{ fieldId: 'field-2', x: 3, y: 0, w: 3, h: 1 },
]
}
})
Grid System
Configuration
- Columns: 6
- Default field width: 3 columns (50% width)
- Min width: 1 column (16.67%)
- Max width: 6 columns (100%)
- Height: Fixed at 1 unit (80px), uniform across all fields
Layout Example
┌─────────────────────┬─────────────────────┐
│ Field 1 (w:3) │ Field 2 (w:3) │ ← Two 3-column fields
├─────────────────────┴─────────────────────┤
│ Field 3 (w:6) │ ← One full-width field
├──────────┬──────────┬──────────┬──────────┤
│ F4 (w:2) │ F5 (w:2) │ F6 (w:2) │ (empty) │ ← Three 2-column fields
└──────────┴──────────┴──────────┴──────────┘
Features
Editor
- ✅ 6-column responsive grid
- ✅ Drag fields from sidebar to grid
- ✅ Drag to reposition fields on grid
- ✅ Resize fields horizontally (1-6 columns)
- ✅ Remove fields from layout
- ✅ Save layout configuration
- ✅ Clear all fields
Renderer
- ✅ Renders fields based on saved layout
- ✅ Respects field positioning and sizing
- ✅ Supports all field types
- ✅ Falls back to 2-column layout if no layout configured
- ✅ Works in both readonly (detail) and edit modes
Layout Management
- ✅ Multiple layouts per object
- ✅ Default layout designation
- ✅ Create, read, update, delete layouts
- ✅ Tab-based interface in object setup
Backward Compatibility
The system maintains full backward compatibility:
- Objects without page layouts use traditional section-based views
- Existing DetailView and EditView components continue to work
- Enhanced views automatically detect and use page layouts when available
Technical Details
Layout Storage Format
{
"fields": [
{
"fieldId": "uuid-of-field",
"x": 0, // Column start (0-5)
"y": 0, // Row start (0-based)
"w": 3, // Width in columns (1-6)
"h": 1 // Height in rows (fixed at 1)
}
]
}
Field Component Mapping
The renderer automatically maps field types to appropriate components:
- TEXT → TextFieldView
- NUMBER → NumberFieldView
- DATE/DATETIME → DateFieldView
- BOOLEAN → BooleanFieldView
- PICKLIST → SelectFieldView
- EMAIL → EmailFieldView
- PHONE → PhoneFieldView
- URL → UrlFieldView
- CURRENCY → CurrencyFieldView
- PERCENT → PercentFieldView
- TEXTAREA → TextareaFieldView
Development
Adding New Field Types
- Create field view component in
components/fields/ - Add mapping in
PageLayoutRenderer.vue:
const componentMap: Record<string, any> = {
// ... existing mappings
NEW_TYPE: NewFieldView,
}
Customizing Grid Settings
Edit PageLayoutEditor.vue:
grid = GridStack.init({
column: 6, // Change column count
cellHeight: 80, // Change cell height
minRow: 1, // Minimum rows
float: true, // Allow floating
acceptWidgets: true,
animate: true,
})
Troubleshooting
Layout not appearing
- Ensure migration has been run
- Check that a default layout is set
- Verify objectId is passed to enhanced views
Fields not draggable
- Check GridStack CSS is loaded
- Verify draggable attribute on sidebar fields
- Check browser console for errors
Layout not saving
- Verify API endpoints are accessible
- Check authentication token
- Review backend logs for errors
Future Enhancements
- Variable field heights
- Field-level permissions in layouts
- Clone/duplicate layouts
- Layout templates
- Layout preview mode
- Responsive breakpoints
- Related list sections
- Custom components in layouts