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:
- Navigation Target: Each page has a unique path that users navigate to
- Scope Boundary: Pages define the scope for datasources, state, and controllers
- Lifecycle Manager: Pages control when components are initialized, shown, hidden, and destroyed
- Event Hub: Pages coordinate events between UI components, datasources, and controllers
- 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
controllerattribute 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 contentpage.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 initializedapp: The application instance
What You Can Do:
- Access and store
pageandappreferences - 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
pageResumedinstead) - 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 usethis.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 instanceapp: 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
pageInitializedorpageResumed - 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 pausedapp: 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 serializedpage.state.serializationTimestamp = new Date().toISOString();// Clean up any non-serializable datadelete page.state.temporaryData;
Lifecycle Event Summary Table
| Event | Fires When | Frequency | Async Allowed | Common Use Cases |
|---|---|---|---|---|
pageInitialized | Page first created | Once | No | One-time setup, store references, initialize state |
pageResumed | Page becomes active | Every visit | No* | Load data, refresh state, handle navigation params, update breadcrumb |
pagePaused | Leaving page | Every exit | No | Save changes, cleanup, stop timers |
onPageSerialize | Saving page state | As needed | No | Control state persistence |
onPageDeserialize | Restoring page state | As needed | No | Restore 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.jsclass WorkOrderDetailController {pageInitialized(page, app) {this.page = page;this.app = app;this.woDS = page.datasources.woDS;}pageResumed(page, app) {
Controller 2: Validation Logic
// ValidationController.jsclass ValidationController {pageInitialized(page, app) {this.page = page;this.woDS = page.datasources.woDS;// Listen for value changesthis.woDS.on('value-changed', (event) => {this.validateField(event.field, event.newValue);
Controller 3: Notification Logic
// NotificationController.jsclass NotificationController {pageInitialized(page, app) {this.app = app;// Listen for custom eventsapp.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
Use the
controllerattribute for single controllers<page id="myPage" controller="MyPageController">Use
<controller>components for multiple controllers<page id="myPage"><controller src="MainController.js"/><controller src="ValidationController.js"/></page>Use
pageInitializedfor one-time setup- Store references to page, app, and datasources
- Initialize state defaults
- Set up event listeners
Use
pageResumedfor data loading and breadcrumb updates- Load or reload datasources
- Check navigation parameters
- Update breadcrumb using
page.updatePageStack() - Refresh data when returning to the page
Use
pagePausedfor cleanup- Save drafts
- Stop timers and intervals
- Cancel pending requests
Keep lifecycle methods synchronous
- Don’t declare lifecycle methods as
async - Use promises without await at the method level
- Don’t declare lifecycle methods as
Store references in
pageInitializedpageInitialized(page, app) {this.page = page;this.app = app;this.myDS = page.datasources.myDS;}Update breadcrumb in
pageResumedpageResumed(page, app) {page.updatePageStack({label: 'My Page Title',path: page.path});}
❌ Don’ts
Don’t declare lifecycle methods as async
// ❌ Badasync pageResumed(page, app) { }// ✅ GoodpageResumed(page, app) {this.loadData(); // async method called inside}Don’t load data in
pageInitialized// ❌ BadpageInitialized(page, app) {this.myDS.load();}// ✅ GoodpageResumed(page, app) {this.myDS.load();}Don’t use
getPageStack()- usepage.updatePageStack()instead// ❌ Bad - getPageStack is not the correct APIgetPageStack(stack, page) {stack.push({label: 'Title', path: '/path'});}// ✅ Good - use page.updatePageStack()pageResumed(page, app) {page.updatePageStack({label: 'Title',Don’t perform long-running operations in
pagePausedDon’t assume page will be destroyed after
pagePausedDon’t navigate in
pageInitialized
Summary
Pages are the fundamental building blocks of Graphite applications. Key takeaways:
- Pages define views: Each page represents a distinct screen with its own route
- Controller flexibility: Use
controllerattribute for single controllers,<controller>components for multiple - Lifecycle is predictable: Events fire in a consistent order
- Use the right event:
pageInitializedfor setup,pageResumedfor loading,pagePausedfor cleanup - Keep it synchronous: Don’t use async lifecycle methods
- Manage state properly: Use page state for UI-driven values
- Update breadcrumbs: Use
page.updatePageStack()inpageResumedto customize breadcrumb navigation
By understanding pages and their lifecycle, you can build robust, maintainable Graphite applications with proper initialization, data loading, and cleanup.