Files
neo/docs/PAGE_LAYOUTS_GUIDE.md
2026-01-05 07:48:22 +01:00

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

  1. Database Schema (migrations/tenant/20250126000008_create_page_layouts.js)

    • page_layouts table stores layout configurations
    • Fields: id, name, object_id, is_default, layout_config, description
    • JSON-based layout_config stores field positions and sizes
  2. API Endpoints (src/page-layout/)

    • POST /page-layouts - Create a new page layout
    • GET /page-layouts?objectId={id} - Get all layouts for an object
    • GET /page-layouts/:id - Get a specific layout
    • GET /page-layouts/default/:objectId - Get the default layout for an object
    • PATCH /page-layouts/:id - Update a layout
    • DELETE /page-layouts/:id - Delete a layout

Frontend Components

  1. 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
  2. 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
  3. 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:

  1. Click "New Layout" to create a layout
  2. Enter a layout name
  3. Drag fields from the right sidebar onto the 6-column grid
  4. Resize fields by dragging their edges (width only)
  5. Rearrange fields by dragging them to new positions
  6. Click "Save Layout" to persist changes

3. Use in Views

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

  1. Create field view component in components/fields/
  2. 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