UNPKG

pict-application

Version:

Application base class for a pict view-based application

612 lines (464 loc) 16.3 kB
# Pict-Application > Application base class for building pict view-based applications Pict-Application provides a structured foundation for building web, console, and other applications where the UI is primarily represented as text strings. It extends the Pict ecosystem by providing application lifecycle management, view coordination, and data marshaling capabilities. ## Features - **Lifecycle Management** - Complete initialization, solving, rendering, and marshaling lifecycles - **View Coordination** - Manages multiple views with automatic rendering and solving - **Data Marshaling** - Two-way data binding between views and application state - **Provider Integration** - Coordinates data providers for loading and saving - **Login Support** - Built-in authentication lifecycle hooks - **Async Support** - Both synchronous and asynchronous lifecycle methods ## Quick Start ```javascript const libPict = require('pict'); const libPictApplication = require('pict-application'); // Create a Pict instance const _Pict = new libPict(); // Add an application const myApp = _Pict.addApplication('MyApp', { Name: 'My Application', AutoRenderMainViewportViewAfterInitialize: true }, libPictApplication); // Initialize the application myApp.initialize(); ``` ## Installation ```bash npm install pict-application ``` ## Configuration ### Default Settings ```javascript const defaultPictSettings = { Name: 'DefaultPictApplication', // Main viewport configuration MainViewportViewIdentifier: 'Default-View', MainViewportRenderableHash: false, MainViewportDestinationAddress: false, MainViewportDefaultDataAddress: false, // Auto behaviors AutoSolveAfterInitialize: true, AutoRenderMainViewportViewAfterInitialize: true, AutoRenderViewsAfterInitialize: false, AutoLoginAfterInitialize: false, AutoLoadDataAfterLogin: false, // Views and Manifests ConfigurationOnlyViews: [], Manifests: {}, // Template addressing IdentifierAddressPrefix: 'PICT-' }; ``` ### Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `Name` | string | 'DefaultPictApplication' | Application name for logging | | `MainViewportViewIdentifier` | string | 'Default-View' | Default view to render | | `MainViewportRenderableHash` | string/boolean | false | Default renderable hash | | `MainViewportDestinationAddress` | string/boolean | false | DOM selector for main content | | `MainViewportDefaultDataAddress` | string/boolean | false | Data address for templates | | `AutoSolveAfterInitialize` | boolean | true | Run solve after init | | `AutoRenderMainViewportViewAfterInitialize` | boolean | true | Render main view after init | | `AutoRenderViewsAfterInitialize` | boolean | false | Render all auto-render views | | `AutoLoginAfterInitialize` | boolean | false | Trigger login flow on init | | `AutoLoadDataAfterLogin` | boolean | false | Load data after login | | `ConfigurationOnlyViews` | array | [] | Views defined by config only | | `Manifests` | object | {} | Data transformation manifests | ### Carried Over Configuration Configuration can be passed through the Pict settings: ```javascript const _Pict = new libPict({ PictApplicationConfiguration: { CustomValue: 'MyValue', AutoRenderMainViewportViewAfterInitialize: false } }); ``` ## Application Lifecycle Pict Application follows a structured lifecycle with hooks at each stage: ### Initialization Phase ``` onBeforeInitialize() -> onInitialize() -> [Provider Init] -> [View Init] -> onAfterInitialize() ↓ [AutoSolve] -> [AutoRender] -> onCompletionOfInitialize() ``` ### Solve Phase The solve phase processes data and prepares views for rendering: ``` onPreSolve() -> onBeforeSolve() -> [Provider Solve] -> [View Solve] -> onSolve() -> onAfterSolve() ``` ### Render Phase ``` onBeforeRender() -> onRender() -> [View Render] -> onAfterRender() ``` ### Login Phase ``` onBeforeLoginAsync() -> onLoginAsync() -> onAfterLoginAsync() -> [AutoLoadData] ``` ### Data Loading Phase ``` onBeforeLoadDataAsync() -> [Provider Load] -> onLoadDataAsync() -> onAfterLoadDataAsync() ``` ### Data Saving Phase ``` onBeforeSaveDataAsync() -> [Provider Save] -> onSaveDataAsync() -> onAfterSaveDataAsync() ``` ### Marshal Phases Data marshaling between views and AppData: ``` // From Views -> AppData onBeforeMarshalFromViews() -> [View Marshal] -> onMarshalFromViews() -> onAfterMarshalFromViews() // From AppData -> Views onBeforeMarshalToViews() -> [View Marshal] -> onMarshalToViews() -> onAfterMarshalToViews() ``` ## Creating an Application ### Basic Application ```javascript const libPictApplication = require('pict-application'); const defaultConfig = { Name: 'Simple Test Application' }; class SimpleApplication extends libPictApplication { constructor(pFable, pOptions, pServiceHash) { super(pFable, pOptions, pServiceHash); } } module.exports = SimpleApplication; module.exports.default_configuration = defaultConfig; ``` ### Application with Custom Initialization ```javascript class CustomApplication extends libPictApplication { constructor(pFable, pOptions, pServiceHash) { super(pFable, pOptions, pServiceHash); } onInitialize() { super.onInitialize(); // Custom initialization logic this.pict.AppData.CustomState = { initialized: true }; return true; } onAfterInitialize() { super.onAfterInitialize(); this.log.info('Application fully initialized'); return true; } } ``` ### Application with Login ```javascript class AuthenticatedApplication extends libPictApplication { constructor(pFable, pOptions, pServiceHash) { super(pFable, pOptions, pServiceHash); this._isAuthenticated = false; } isLoggedIn() { return this._isAuthenticated; } onLoginAsync(fCallback) { // Perform authentication logic this.log.info('Checking authentication...'); // Example: Check for stored token if (this.pict.AppData.AuthToken) { this._isAuthenticated = true; } return fCallback(); } } ``` ## Working with Views ### Adding Views ```javascript const libPictView = require('pict-view'); // Add a view to the application _Pict.addView('MyView', { ViewIdentifier: 'MyView', DefaultRenderable: 'Main-Content', DefaultDestinationAddress: '#app-container', AutoInitialize: true, AutoRender: true }, libPictView); ``` ### View Configuration ```javascript const viewConfig = { ViewIdentifier: 'UserProfile', DefaultRenderable: 'Profile-Main', DefaultDestinationAddress: '#user-profile', DefaultTemplateRecordAddress: 'AppData.CurrentUser', AutoInitialize: true, AutoInitializeOrdinal: 0, AutoRender: true, AutoRenderOrdinal: 0, AutoSolveWithApp: true, AutoSolveOrdinal: 0, Templates: [ { Hash: 'Profile-Template', Template: /*html*/` <div class="user-profile"> <h1>{~Data:Record.name~}</h1> <p>{~Data:Record.bio~}</p> </div> ` } ], Renderables: [ { RenderableHash: 'Profile-Main', TemplateHash: 'Profile-Template', DestinationAddress: '#user-profile', RenderMethod: 'replace' } ] }; ``` ### Custom View with Solver ```javascript const libPictView = require('pict-view'); class CustomView extends libPictView { constructor(pFable, pOptions, pServiceHash) { super(pFable, pOptions, pServiceHash); } onBeforeSolve() { super.onBeforeSolve(); // Initialize state if needed if (!('SolveCount' in this.pict.AppData)) { this.pict.AppData.SolveCount = 0; } } onSolve() { super.onSolve(); this.pict.AppData.SolveCount++; } onAfterSolve() { this.pict.log.info(`SolveCount: ${this.pict.AppData.SolveCount}`); } } module.exports = CustomView; ``` ## Renderables Renderables define how templates are rendered to the DOM. ### Renderable Structure ```javascript { RenderableHash: 'Content-List', // Unique identifier TemplateHash: 'Content-Template', // Template to render TemplateRecordAddress: 'AppData.Items', // Data source DestinationAddress: '#container', // DOM target RenderMethod: 'replace' // How to insert content } ``` ### Render Methods | Method | Description | |--------|-------------| | `replace` | Replace content at destination outright | | `append` | Append content after existing content | | `prepend` | Prepend content before existing content | | `append_once` | Append only once (checks for existing ID) | | `virtual-assignment` | Deferred assignment for complex transactions | ### Adding Renderables at Runtime ```javascript // Method signature myView.addRenderable( pRenderableHash, pTemplateHash, pDefaultTemplateDataAddress, pDefaultDestinationAddress, pRenderMethod ); // Example myView.addRenderable( 'Dynamic-Content', 'Dynamic-Template', 'AppData.DynamicData', '#dynamic-container', 'append' ); // Or pass a renderable object myView.addRenderable({ RenderableHash: 'Dynamic-Content', TemplateHash: 'Dynamic-Template', TemplateRecordAddress: 'AppData.DynamicData', DestinationAddress: '#dynamic-container', RenderMethod: 'append' }); ``` ## Providers Providers are services that can participate in the application lifecycle. ### Creating a Provider ```javascript const libPictProvider = require('pict-provider'); class DataProvider extends libPictProvider { constructor(pFable, pOptions, pServiceHash) { super(pFable, pOptions, pServiceHash); } onBeforeInitializeAsync(fCallback) { super.onBeforeInitialize(); // Load configuration, setup connections, etc. return fCallback(); } onLoadDataAsync(fCallback) { // Fetch data from API fetch('/api/data') .then(response => response.json()) .then(data => { this.pict.AppData.ExternalData = data; fCallback(); }) .catch(fCallback); } onSaveDataAsync(fCallback) { // Save data to API fetch('/api/data', { method: 'POST', body: JSON.stringify(this.pict.AppData.ExternalData) }) .then(() => fCallback()) .catch(fCallback); } } module.exports = DataProvider; ``` ### Provider Configuration ```javascript _Pict.addProvider('DataProvider', { AutoInitialize: true, AutoInitializeOrdinal: 0, AutoSolveWithApp: true, AutoSolveOrdinal: 0, AutoLoadDataWithApp: true, AutoLoadDataOrdinal: 0, AutoSaveDataWithApp: true, AutoSaveDataOrdinal: 0 }, DataProvider); ``` ## API Reference ### Properties | Property | Type | Description | |----------|------|-------------| | `pict` | object | Reference to the Pict instance | | `fable` | object | Reference to the Fable instance | | `AppData` | object | Application data store | | `Bundle` | object | Pict bundle reference | | `UUID` | string | Unique identifier | | `Hash` | string | Service hash | | `options` | object | Configuration options | | `log` | object | Logger instance | | `initializeTimestamp` | number | When initialized | | `lastSolvedTimestamp` | number | Last solve time | | `lastLoginTimestamp` | number | Last login time | | `lastMarshalFromViewsTimestamp` | number | Last marshal from views | | `lastMarshalToViewsTimestamp` | number | Last marshal to views | | `lastAutoRenderTimestamp` | number | Last auto render | | `lastLoadDataTimestamp` | number | Last data load | ### Synchronous Methods | Method | Returns | Description | |--------|---------|-------------| | `initialize()` | boolean | Initialize the application | | `solve()` | boolean | Run solve phase | | `render(viewId?, renderableHash?, destAddress?, dataAddress?)` | boolean | Render a view | | `renderMainViewport()` | boolean | Render main viewport | | `renderAutoViews()` | void | Render all auto-render views | | `marshalFromViews()` | boolean | Marshal data from all views | | `marshalToViews()` | boolean | Marshal data to all views | | `isLoggedIn()` | boolean | Check login state | ### Asynchronous Methods | Method | Description | |--------|-------------| | `initializeAsync(callback)` | Async initialization | | `solveAsync(callback)` | Async solve phase | | `renderAsync(viewId?, renderableHash?, destAddress?, dataAddress?, callback)` | Async render | | `renderMainViewportAsync(callback)` | Async render main viewport | | `renderAutoViewsAsync(callback)` | Async render auto views | | `loginAsync(callback)` | Async login flow | | `loadDataAsync(callback)` | Async data loading | | `saveDataAsync(callback)` | Async data saving | | `marshalFromViewsAsync(callback)` | Async marshal from views | | `marshalToViewsAsync(callback)` | Async marshal to views | ### Lifecycle Hooks All lifecycle hooks have both sync and async variants: **Initialization:** - `onBeforeInitialize()` / `onBeforeInitializeAsync(callback)` - `onInitialize()` / `onInitializeAsync(callback)` - `onAfterInitialize()` / `onAfterInitializeAsync(callback)` - `onCompletionOfInitialize()` / `onCompletionOfInitializeAsync(callback)` **Solve:** - `onPreSolve()` / `onPreSolveAsync(callback)` - `onBeforeSolve()` / `onBeforeSolveAsync(callback)` - `onSolve()` / `onSolveAsync(callback)` - `onAfterSolve()` / `onAfterSolveAsync(callback)` **Render:** - `onBeforeRender()` / `onBeforeRenderAsync(callback)` - `onRender()` / `onRenderAsync(callback)` - `onAfterRender()` / `onAfterRenderAsync(callback)` **Login:** - `onBeforeLoginAsync(callback)` - `onLoginAsync(callback)` - `onAfterLoginAsync(callback)` **Data Loading:** - `onBeforeLoadDataAsync(callback)` - `onLoadDataAsync(callback)` - `onAfterLoadDataAsync(callback)` **Data Saving:** - `onBeforeSaveDataAsync(callback)` - `onSaveDataAsync(callback)` - `onAfterSaveDataAsync(callback)` **Marshal:** - `onBeforeMarshalFromViews()` / `onBeforeMarshalFromViewsAsync(callback)` - `onMarshalFromViews()` / `onMarshalFromViewsAsync(callback)` - `onAfterMarshalFromViews()` / `onAfterMarshalFromViewsAsync(callback)` - `onBeforeMarshalToViews()` / `onBeforeMarshalToViewsAsync(callback)` - `onMarshalToViews()` / `onMarshalToViewsAsync(callback)` - `onAfterMarshalToViews()` / `onAfterMarshalToViewsAsync(callback)` ## Template Syntax Pict uses a template syntax with special markers: | Syntax | Description | Example | |--------|-------------|---------| | `{~Data:path~}` | Output data value | `{~Data:Record.name~}` | | `{~TS:hash:address~}` | Template set (loop) | `{~TS:Row-Template:AppData.Items~}` | | `{~Digits:path~}` | Format as digits | `{~Digits:Record.count~}` | ## Examples ### Example Applications The repository includes several example applications: 1. **Simple Example** - Basic application setup 2. **Bookstore Example** - Views with external data 3. **Historic Events Example** - Complex data marshaling 4. **Postcard Example** - Navigation and multiple views ### Running Examples ```bash # Install dependencies npm install # Navigate to examples folder cd example_applications # Build examples ./Build-Examples.sh # Serve examples node ServeExamples.js ``` ## Testing ```bash # Run all tests npm test # Run specific test npm run tests -- "Object Initialization" # Run with coverage npm run coverage ``` ## Building ```bash # Build distribution files npm run build # Generate TypeScript declarations npm run types # Lint source code npm run lint ``` ## Related Packages - [pict](https://github.com/stevenvelozo/pict) - Core Pict framework - [pict-view](https://github.com/stevenvelozo/pict-view) - View base class - [pict-provider](https://github.com/stevenvelozo/pict-provider) - Provider base class - [fable](https://github.com/stevenvelozo/fable) - Service provider framework - [quackage](https://github.com/stevenvelozo/quackage) - Build tooling ## License MIT License - see [LICENSE](../LICENSE) for details.