Skip to main contentMAF Configuration Practices

Graphite Page Component - Comprehensive Guide

What is a Page?

A Page in Graphite is a fundamental building block that represents a distinct view or screen in your application. It serves as a container that orchestrates UI components, data sources, and business logic to create a cohesive user experience.

Definition and Purpose

A Page is a declarative XML component that:

  • Defines a navigable view in your application with a unique route/path
  • Organizes UI components into a structured layout
  • Manages data sources specific to that view
  • Handles user interactions through controllers and event handlers
  • Controls lifecycle of components within its scope
  • Maintains state that persists during the page’s lifetime

Think of a Page as a self-contained module that encapsulates everything needed to display and interact with a specific part of your application.

Role in Application Architecture

Pages sit at the heart of Graphite’s component hierarchy:

Application
└── Pages (collection)
└── Page (individual view)
├── Datasources (page-specific data)
├── Controllers (page logic)
├── State (page variables)
├── UI Components (layout & widgets)
└── Dialogs (page-specific modals)

Key Architectural Roles:

  1. Navigation Target: Each page has a unique path that users navigate to
  2. Scope Boundary: Pages define the scope for datasources, state, and controllers
  3. Lifecycle Manager: Pages control when components are initialized, shown, hidden, and destroyed
  4. Event Hub: Pages coordinate events between UI components, datasources, and controllers
  5. State Container: Pages maintain their own state that can be accessed by child components

How Pages Relate to Other Components

Pages and Data Sources

  • Pages can define page-level datasources that are only available within that page
  • Datasources declared in a page are initialized when the page is first created
  • Page controllers can access and manipulate datasources through lifecycle events
  • When a page is destroyed, its datasources are cleaned up
<page id="workOrderDetail">
<datasources>
<maximo-datasource id="woDS" object-structure="MXAPIWODETAIL"/>
</datasources>
</page>

Pages and Controllers

  • Pages can have page controllers that handle page-specific logic
  • Controllers receive lifecycle events (pageInitialized, pageResumed, pagePaused)
  • Multiple controllers can be attached to a single page
  • Controllers can access page state, datasources, and the application context

Single Controller (using attribute - recommended for single controller):

<page id="workOrderDetail" controller="WorkOrderDetailController">
<!-- page content -->
</page>

Multiple Controllers (using controller components - required for multiple controllers):

<page id="workOrderDetail">
<controller src="WorkOrderDetailController.js"/>
<controller src="ValidationController.js"/>
<controller src="NotificationController.js"/>
<!-- page content -->
</page>

Best Practice:

  • Use the controller attribute when you have a single controller
  • Use <controller> components when you need multiple controllers on a page

Pages and UI Elements

  • Pages contain and organize UI components (buttons, lists, forms, etc.)
  • UI components can bind to page state and datasources
  • User interactions trigger events that flow through the page’s event system
  • Pages manage the rendering lifecycle of all child components

Pages and Application

  • Pages are children of the Application component
  • Pages can access application-level state and datasources
  • Navigation between pages is managed by the application router
  • Pages inherit application-level controllers and event handlers

Key Characteristics and Capabilities

1. Routing and Navigation

Each page has a unique path that defines its URL route:

<page id="workOrderList" path="workorders">
<!-- List view -->
</page>
<page id="workOrderDetail" path="workorders/:wonum">
<!-- Detail view with parameter -->
</page>

Path Parameters: Pages can accept dynamic parameters in their path (e.g., :wonum) that are accessible via page.params.

2. State Management

Pages maintain their own state object that persists during the page’s lifetime:

<page id="myPage">
<states>
<state id="selectedTab" value="details"/>
<state id="isEditing" value="false"/>
<state id="filterText" value=""/>
</states>
</page>

Access state in bindings: {page.state.selectedTab}

3. Lifecycle Control

Pages have a well-defined lifecycle with events that fire at specific times:

  • Initialization: When the page is first created
  • Resume: When the page becomes active (every time)
  • Pause: When navigating away from the page
  • Destruction: When the page is removed from memory

4. Event Handling

Pages participate in Graphite’s event flow:

  • Events bubble up from UI components → Datasource controllers → Page controllers → Application controllers
  • Pages can handle named events from child components
  • Page controllers can emit custom events

5. Breadcrumb Integration

Pages can customize their breadcrumb display using the page.updatePageStack() method:

pageResumed(page, app) {
// Update breadcrumb with dynamic content
page.updatePageStack({
label: `Work Order ${page.params.wonum}`,
path: page.path
});
}

Common Use Cases and Scenarios

1. List-Detail Pattern

Create a list page and a detail page with navigation between them:

<pages>
<page id="workOrderList" path="workorders">
<data-list datasource="woListDS">
<list-item on-click="navigateToDetail" on-click-arg="{item}">
<label label="{item.wonum}"/>
</list-item>
</data-list>
</page>

2. Form Entry Page

A page for creating or editing records:

<page id="createWorkOrder" path="workorders/new" controller="CreateWOController">
<datasources>
<maximo-datasource id="newWODS" object-structure="MXAPIWODETAIL"/>
</datasources>
<smart-input attribute="description" datasource="newWODS"/>
<button label="Save" on-click="saveWorkOrder"/>
</page>

3. Dashboard Page

A page that aggregates data from multiple sources:

<page id="dashboard" path="dashboard" controller="DashboardController">
<datasources>
<json-datasource id="metricsDS" src="metrics.js" pre-load="true"/>
<json-datasource id="alertsDS" src="alerts.js" pre-load="true"/>
</datasources>
<!-- Dashboard widgets -->
</page>

4. Wizard/Multi-Step Page

A page that guides users through multiple steps:

<page id="woWizard" path="workorders/wizard" controller="WizardController">
<states>
<state id="currentStep" value="1"/>
</states>
<!-- Step 1 content shown when page.state.currentStep === 1 -->
<!-- Step 2 content shown when page.state.currentStep === 2 -->
</page>

5. Search/Filter Page

A page focused on searching and filtering data:

<page id="assetSearch" path="assets/search" controller="AssetSearchController">
<datasources>
<maximo-datasource id="assetDS" object-structure="MXAPIASSET"/>
</datasources>
<search datasource="assetDS"/>
<qbe datasource="assetDS"/>
<data-list datasource="assetDS"/>
</page>

Page Life Cycle Events

Pages have a well-defined lifecycle with events that fire at specific stages. Understanding these events is crucial for implementing proper initialization, cleanup, and state management.

Lifecycle Event Flow

When navigating between pages, events fire in this order:

User navigates to new page
1. oldPage.pagePaused() ← Current page is being left
2. newPage.pageInitialized() ← New page created (first time only)
3. newPage.pageResumed() ← New page becomes active
4. Process datasource dynamic properties

Core Lifecycle Events

1. pageInitialized(page, app)

When: Called once when the page is first created and added to the application.

Timing:

  • Fires after the page XML is parsed
  • Fires after page datasources are initialized
  • Fires before the page is rendered
  • Only fires once per page instance

Purpose:

  • Perform one-time setup for the page
  • Store references to page and app
  • Initialize page state or variables
  • Set up event listeners
  • Configure page-level settings

Parameters:

  • page: The page instance being initialized
  • app: The application instance

What You Can Do:

  • Access and store page and app references
  • Initialize controller properties
  • Set up page state defaults
  • Access page datasources (but they may not be loaded yet)
  • Register event handlers

What You Should NOT Do:

  • Load datasources (use pageResumed instead)
  • Perform async operations
  • Access datasource items (they’re not loaded yet)
  • Navigate to other pages

Example:

class WorkOrderDetailController {
pageInitialized(page, app) {
// Store references for later use
this.page = page;
this.app = app;
console.log('Page initialized:', page.id);
console.log('Page path:', page.path);
console.log('Page params:', page.params);

2. pageResumed(page, app)

When: Called every time the page becomes active, including the first time.

Timing:

  • Fires after pageInitialized (on first visit)
  • Fires every time you navigate back to this page
  • Fires before datasources are loaded
  • Fires before the page is rendered

Purpose:

  • Load or reload data
  • Refresh page state
  • React to navigation parameters
  • Update UI based on current context
  • Resume any paused operations
  • Update breadcrumb navigation

Parameters:

  • page: The page instance
  • app: The application instance

What You Can Do:

  • Load datasources
  • Check and use page parameters (page.params)
  • Update page state based on navigation context
  • Refresh data from the server
  • Resume timers or polling
  • Show loading indicators
  • Update breadcrumb using page.updatePageStack()

What You Should NOT Do:

  • Perform one-time initialization (use pageInitialized)
  • Assume datasources are already loaded

Example:

class WorkOrderDetailController {
pageInitialized(page, app) {
this.page = page;
this.app = app;
this.woDS = page.datasources.woDS;
}
pageResumed(page, app) {
console.log('Page resumed:', page.id);

3. pagePaused(page, app)

When: Called when navigating away from the page.

Timing:

  • Fires before the new page’s pageInitialized or pageResumed
  • Fires when user navigates to a different page
  • Fires before the page is hidden

Purpose:

  • Save unsaved changes
  • Clean up resources
  • Stop timers or polling
  • Cancel pending operations
  • Store page state for later restoration

Parameters:

  • page: The page instance being paused
  • app: The application instance

What You Can Do:

  • Save draft data
  • Stop timers, intervals, or polling
  • Cancel pending API requests
  • Store scroll position or UI state
  • Show confirmation dialogs for unsaved changes
  • Clean up event listeners

What You Should NOT Do:

  • Perform long-running operations
  • Navigate to other pages
  • Assume the page will be destroyed (it might be cached)

Example:

class WorkOrderDetailController {
pageInitialized(page, app) {
this.page = page;
this.app = app;
this.woDS = page.datasources.woDS;
this.autoSaveTimer = null;
}
pageResumed(page, app) {

4. onPageSerialize({page}) and onPageDeserialize({page})

When: Called when the page state needs to be saved or restored.

Timing:

  • onPageSerialize: Before page state is saved (e.g., for mobile offline sync)
  • onPageDeserialize: After page state is restored

Purpose:

  • Control what page state is persisted
  • Transform state before saving
  • Restore state after loading

Parameters:

  • Object containing page: The page instance

Example:

class WorkOrderDetailController {
onPageSerialize({page}) {
console.log('Serializing page state');
// Add custom data to be serialized
page.state.serializationTimestamp = new Date().toISOString();
// Clean up any non-serializable data
delete page.state.temporaryData;

Lifecycle Event Summary Table

EventFires WhenFrequencyAsync AllowedCommon Use Cases
pageInitializedPage first createdOnceNoOne-time setup, store references, initialize state
pageResumedPage becomes activeEvery visitNo*Load data, refresh state, handle navigation params, update breadcrumb
pagePausedLeaving pageEvery exitNoSave changes, cleanup, stop timers
onPageSerializeSaving page stateAs neededNoControl state persistence
onPageDeserializeRestoring page stateAs neededNoRestore state after load

*Note: While async operations can be performed in pageResumed, the method itself should not be declared as async. Use promises without await at the method level.


Implementation Examples

Example 1: Page with Multiple Controllers

This example demonstrates using multiple controllers on a single page for separation of concerns.

XML Definition

<page id="workOrderDetail" path="workorders/:wonum">
<!-- Multiple controllers for different responsibilities -->
<controller src="WorkOrderDetailController.js"/>
<controller src="ValidationController.js"/>
<controller src="NotificationController.js"/>
<states>
<state id="isLoading" value="false"/>

Controller 1: Main Page Logic

// WorkOrderDetailController.js
class WorkOrderDetailController {
pageInitialized(page, app) {
this.page = page;
this.app = app;
this.woDS = page.datasources.woDS;
}
pageResumed(page, app) {

Controller 2: Validation Logic

// ValidationController.js
class ValidationController {
pageInitialized(page, app) {
this.page = page;
this.woDS = page.datasources.woDS;
// Listen for value changes
this.woDS.on('value-changed', (event) => {
this.validateField(event.field, event.newValue);

Controller 3: Notification Logic

// NotificationController.js
class NotificationController {
pageInitialized(page, app) {
this.app = app;
// Listen for custom events
app.eventManager.on('workOrderSaved', () => {
this.showSuccessNotification();
});

Example 2: Complete Work Order Detail Page

This example shows a full implementation with comprehensive lifecycle management.

XML Definition

<page id="workOrderDetail"
path="workorders/:wonum"
controller="WorkOrderDetailController">
<states>
<state id="isLoading" value="false"/>
<state id="isEditing" value="false"/>
<state id="hasChanges" value="false"/>
<state id="pageTitle" value="Work Order Details"/>

Controller Implementation

class WorkOrderDetailController {
constructor() {
this.autoSaveTimer = null;
this.refreshInterval = null;
}
pageInitialized(page, app) {
console.log('Page initialized:', page.id);

Best Practices

✅ Do’s

  1. Use the controller attribute for single controllers

    <page id="myPage" controller="MyPageController">
  2. Use <controller> components for multiple controllers

    <page id="myPage">
    <controller src="MainController.js"/>
    <controller src="ValidationController.js"/>
    </page>
  3. Use pageInitialized for one-time setup

    • Store references to page, app, and datasources
    • Initialize state defaults
    • Set up event listeners
  4. Use pageResumed for data loading and breadcrumb updates

    • Load or reload datasources
    • Check navigation parameters
    • Update breadcrumb using page.updatePageStack()
    • Refresh data when returning to the page
  5. Use pagePaused for cleanup

    • Save drafts
    • Stop timers and intervals
    • Cancel pending requests
  6. Keep lifecycle methods synchronous

    • Don’t declare lifecycle methods as async
    • Use promises without await at the method level
  7. Store references in pageInitialized

    pageInitialized(page, app) {
    this.page = page;
    this.app = app;
    this.myDS = page.datasources.myDS;
    }
  8. Update breadcrumb in pageResumed

    pageResumed(page, app) {
    page.updatePageStack({
    label: 'My Page Title',
    path: page.path
    });
    }

❌ Don’ts

  1. Don’t declare lifecycle methods as async

    // ❌ Bad
    async pageResumed(page, app) { }
    // ✅ Good
    pageResumed(page, app) {
    this.loadData(); // async method called inside
    }
  2. Don’t load data in pageInitialized

    // ❌ Bad
    pageInitialized(page, app) {
    this.myDS.load();
    }
    // ✅ Good
    pageResumed(page, app) {
    this.myDS.load();
    }
  3. Don’t use getPageStack() - use page.updatePageStack() instead

    // ❌ Bad - getPageStack is not the correct API
    getPageStack(stack, page) {
    stack.push({label: 'Title', path: '/path'});
    }
    // ✅ Good - use page.updatePageStack()
    pageResumed(page, app) {
    page.updatePageStack({
    label: 'Title',
  4. Don’t perform long-running operations in pagePaused

  5. Don’t assume page will be destroyed after pagePaused

  6. Don’t navigate in pageInitialized


Summary

Pages are the fundamental building blocks of Graphite applications. Key takeaways:

  1. Pages define views: Each page represents a distinct screen with its own route
  2. Controller flexibility: Use controller attribute for single controllers, <controller> components for multiple
  3. Lifecycle is predictable: Events fire in a consistent order
  4. Use the right event: pageInitialized for setup, pageResumed for loading, pagePaused for cleanup
  5. Keep it synchronous: Don’t use async lifecycle methods
  6. Manage state properly: Use page state for UI-driven values
  7. Update breadcrumbs: Use page.updatePageStack() in pageResumed to customize breadcrumb navigation

By understanding pages and their lifecycle, you can build robust, maintainable Graphite applications with proper initialization, data loading, and cleanup.

Page last updated: 05 November 2025