pict-application
Version:
Application base class for a pict view-based application
612 lines (464 loc) • 16.3 kB
Markdown
# 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.