Skip to main content

Overview

The PageController extends SingleController and is specifically designed for handling page-type documents. It inherits all single-document behavior while setting a specific client-side rendering mode for pages.

Class Definition

import SingleController from './single-controller.js';

export default class PageController extends SingleController {
  client = 'page';
  
  constructor(props) {
    super(props);
  }
}
Source: packages/loopar/core/controller/page-controller.js:5

When to Use

  • For creating custom page views in your application
  • When building landing pages or informational pages
  • For dashboard or portal pages
  • When you need single-instance page behavior with specialized rendering

Key Features

  • Inherits all SingleController functionality
  • Sets client-side rendering mode to ‘page’
  • Automatic single-instance behavior (no list views)
  • Integrated with web app navigation system
  • Optimized for page-type document rendering

Constructor

props
object
required
Configuration object containing controller initialization parameters:
  • action: The current action being performed
  • document: The document type (typically a Page)
  • data: Request data
  • req: HTTP request object
  • res: HTTP response object
  • Other inherited controller properties
Behavior: Inherits SingleController behavior, which automatically redirects list actions to update.

Properties

client
string
default:"page"
Client-side rendering mode, set to ‘page’ for page-specific rendering logic
action
string
Current action being executed (inherited)
document
string
Document type name being controlled
defaultAction
string
default:"list"
Default action, automatically redirected to ‘update’ for single documents
hasSidebar
boolean
default:"true"
Whether to display sidebar navigation

Inherited Methods

PageController inherits all methods from:
  • SingleController - Single document management
  • BaseController - CRUD operations
  • CoreController - Rendering, error handling
  • AuthController - Authentication and authorization

Core Methods Available

From SingleController

async getParent()
Retrieves the parent menu item for the current page.Returns: Promise<string> - Parent page identifier
async sendAction(action)
Handles action dispatching with custom logic.Parameters:
  • action (string) - Action name to execute
Returns: Promise<object> - Action execution result
async actionView()
Renders the page document.Returns: Promise<object> - Rendered page response
async sendDocument(action = this.document)
Retrieves and renders the page with navigation context.Returns: Promise<object> - Rendered page with metadata

From BaseController

async actionUpdate(document)
Updates the page document with provided data.Returns: Promise<object> - Success message or rendered form
async actionDelete()
Deletes the page document.Returns: Promise<object> - Redirect to list

From CoreController

async render(meta)
Renders the page with client-side entry point set to ‘page’ mode.Returns: Promise<object> - Rendered response with metadata
async success(message, options = {})
Returns a success response.Returns: Promise<object> - Success response with notification
async error(message, options, status)
Returns an error response.Returns: Promise<object> - Error response with notification

Usage Examples

Creating a Custom Page Controller

import PageController from '@loopar/core/controller/page-controller';

export default class DashboardController extends PageController {
  constructor(props) {
    super(props);
  }
  
  // Override to add custom data loading
  async actionView() {
    const document = await super.actionView();
    
    // Add custom dashboard data
    document.stats = await this.loadDashboardStats();
    document.recentActivity = await this.loadRecentActivity();
    
    return document;
  }
  
  async loadDashboardStats() {
    return {
      totalUsers: await loopar.db.getCount('User'),
      activeProjects: await loopar.db.getCount('Project', { status: 'Active' }),
      pendingTasks: await loopar.db.getCount('Task', { status: 'Pending' })
    };
  }
  
  async loadRecentActivity() {
    return await loopar.db.getList('Activity Log', {
      limit: 10,
      orderBy: 'creation DESC'
    });
  }
}

Creating a Landing Page

import PageController from '@loopar/core/controller/page-controller';

export default class HomeController extends PageController {
  constructor(props) {
    super(props);
  }
  
  async actionView() {
    const page = await loopar.getDocument('Page', 'Home');
    
    // Add dynamic content
    page.featuredProducts = await this.getFeaturedProducts();
    page.testimonials = await this.getTestimonials();
    
    return await this.render(page);
  }
  
  async getFeaturedProducts() {
    return await loopar.getList('Product', {
      data: { featured: 1 },
      limit: 6
    });
  }
  
  async getTestimonials() {
    return await loopar.getList('Testimonial', {
      data: { status: 'Published' },
      limit: 3,
      orderBy: 'creation DESC'
    });
  }
}

Custom Action for Page Analytics

import PageController from '@loopar/core/controller/page-controller';

export default class ContentPageController extends PageController {
  constructor(props) {
    super(props);
  }
  
  // Track page views
  async actionView() {
    await this.trackPageView();
    return await super.actionView();
  }
  
  async trackPageView() {
    const analytics = await loopar.newDocument('Page Analytics');
    analytics.page_name = this.document;
    analytics.user_id = loopar.currentUser?.name;
    analytics.timestamp = new Date();
    await analytics.save();
  }
  
  // Custom action to get analytics
  async actionAnalytics() {
    const views = await loopar.db.getList('Page Analytics', {
      data: { page_name: this.document },
      fields: ['COUNT(*) as total_views', 'DATE(timestamp) as date'],
      groupBy: 'date',
      orderBy: 'date DESC',
      limit: 30
    });
    
    return this.success('Analytics retrieved', { analytics: views });
  }
}

Integrating with Web App Navigation

import PageController from '@loopar/core/controller/page-controller';

export default class AboutPageController extends PageController {
  constructor(props) {
    super(props);
  }
  
  async actionView() {
    const document = await loopar.getDocument('Page', 'About');
    const parent = await this.getParent();
    
    // Use parent for breadcrumb navigation
    document.breadcrumbs = await this.buildBreadcrumbs(parent);
    
    return await this.render(document);
  }
  
  async buildBreadcrumbs(parentPage) {
    const breadcrumbs = [{ label: 'Home', link: '/' }];
    
    if (parentPage) {
      const parent = await loopar.getDocument('Page', parentPage);
      breadcrumbs.push({ label: parent.title, link: parent.route });
    }
    
    breadcrumbs.push({ label: 'About', link: '/about' });
    return breadcrumbs;
  }
}

Client-Side Rendering

The client = 'page' property affects how the page is rendered on the client side:
// In CoreController's clientImporter method
const getClient = () => {
  if(["Page", "View"].includes(Document.Entity.type)) return "view";
  if (this.client) return this.client; // Returns 'page' for PageController
  // ... other logic
}
This ensures the correct client-side entry point is used for page rendering.

Best Practices

Page Documents: Use PageController for documents that represent actual pages in your application, not for data entities.
PageController inherits SingleController behavior. You cannot create multiple instances of a page document through the standard UI.

Do’s

  • Use for landing pages, dashboards, and custom views
  • Override actionView() to add custom data loading
  • Implement custom actions for page-specific functionality
  • Leverage the parent menu system for navigation
  • Track page analytics by extending actionView

Don’ts

  • Don’t use for data entities that need CRUD operations
  • Don’t expect list views to work (they redirect to update)
  • Don’t use for documents that need multiple instances
  • Avoid complex business logic in page controllers

Comparison with Other Controllers

FeaturePageControllerSingleControllerBaseControllerFormController
Multiple instancesNoNoYesYes
List viewRedirectedRedirectedYesRedirected
Client mode’page’InheritedDynamicN/A
Best forPages/ViewsSettingsData entitiesPublic forms
Single instanceYesYesNoNo

Common Use Cases

  1. Dashboard Pages: Main application dashboards with aggregated data
  2. Landing Pages: Public-facing pages with dynamic content
  3. About/Help Pages: Static or semi-static content pages
  4. Portal Pages: User-specific portal pages
  5. Settings Pages: Configuration pages (though SingleController might be more appropriate)

Troubleshooting

This is expected behavior for single documents. Ensure your route uses the ‘view’ action explicitly:
/desk/MyPage/view
Check that the document Entity type is set correctly and that no other controller properties override the client setting.
PageController inherits from SingleController, which prevents multiple instances. Use BaseController for multi-instance documents.

Build docs developers (and LLMs) love