pict-section-form-host
v1.0.0
Published
A cohesive programmatic container for embedding a pict-section-form into a div or iframe, with a single configuration surface for the form manifest, render destination, AppData marshaling location and lifecycle hooks.
Readme
pict-section-form-host
A cohesive programmatic container for embedding a
pict-section-form into a
div (or an iframe) with a single configuration surface.
pict-section-form is powerful, but standing a form up by hand means assembling
a metacontroller view, a DataBroker marshal destination, a pile of container
element conventions and the render / marshal / solve calls yourself, in several
different places. pict-section-form-host collapses all of that into one object
you configure with four questions:
- What form? (the manifest)
- Rendered where? (a
divor aniframe, your choice at runtime) - Where does the data live? (a Pict state address such as
AppData.MyForm) - What should run on lifecycle events? (hooks)
It then exposes a small, stable API -- loadForm, render, marshalToView,
marshalFromView, getFormData, setFormData, solve -- so the software using
it never has to reach into the internals of pict-section-form.
Install
npm install pict-section-form-hostpict-section-form and pict-application come along as dependencies.
Quick start
const libPictSectionFormHost = require('pict-section-form-host');
// Inside a Pict application (or against any pict instance):
const tmpFormHost = pict.addProvider('MyForm',
{
Form: require('./My-Form-Manifest.json'),
DestinationType: 'div', // 'div' (default) or 'iframe'
DestinationAddress: '#My-Form-Container', // where to mount
DataAddress: 'AppData.MyForm', // where the data lives
onSave: (pContext) => console.log('saved', pContext.Data)
}, libPictSectionFormHost);
// Because `Form` was supplied, the host loads and renders itself on initialize.
// From there, drive it programmatically:
tmpFormHost.setFormData({ FirstName: 'Ada' }); // push a record into the form
let tmpData = tmpFormHost.getFormData(); // read the bound data object
tmpFormHost.marshalFromView(); // pull the DOM back into AppData (+ onSave)
tmpFormHost.loadForm(anotherManifest); // swap the whole form config in placeIf you would rather not auto-load, omit Form and call loadForm(manifest)
when you are ready:
const tmpFormHost = pict.addProvider('MyForm',
{ DestinationAddress: '#My-Form-Container', DataAddress: 'AppData.MyForm' },
libPictSectionFormHost);
tmpFormHost.loadForm(require('./My-Form-Manifest.json'));The manifest is exactly the shape pict-section-form consumes (Descriptors,
Sections, Solvers, PickLists, ...). The host does not reinvent it -- it
embeds it.
Configuration
| Option | Default | Meaning |
|---|---|---|
| Form | false | The form manifest. Provide it to auto-load on initialize, or pass it later to loadForm(). |
| DestinationType | 'div' | 'div' renders into an element in the host page; 'iframe' renders into a same-origin iframe for CSS isolation. |
| DestinationAddress | '#Pict-Form-Host-Container' | CSS selector of the mount element (the div, or the element the iframe is placed in). |
| CreateDestinationElement | true | When the mount element is missing, append one to <body> rather than erroring. |
| DataAddress | 'AppData' | The Pict state address the form marshals to and from. Anything pict.resolveStateFromAddress understands: AppData, AppData.MyRecord, Bundle.Thing, ... The object is created if absent. |
| AutoRenderOnLoad | true | Render as soon as loadForm() completes. |
| AutoMarshalToViewAfterRender | true | Push the data at DataAddress into the inputs after each render. |
| PopulateDefaultObject | true | Fill DataAddress with the manifest's declared Default values for missing fields. |
| SolveOnLoad | true | Run the manifest solvers after render. |
| DistinctManifest | false | (div mode) Namespace the manifest's hashes / addresses per instance so the SAME manifest can be embedded by several hosts on one page. |
| IframeScripts | false | (iframe mode) Script URLs to load into the iframe realm so the child has window.Pict + window.PictSectionFormHost. When false, the host auto-clones the host document's external <script src> tags. |
| IframeSrc | false | (iframe mode) URL of an externally hosted child page for a cross-origin child (skips the generated same-origin srcdoc). |
| IframeTitle / IframeClass / IframeStyle / IframeDocumentCSS | see source | iframe-mode iframe element + document tuning. |
| MetacontrollerPrototype | false | Advanced: override the metacontroller class the host wraps. Defaults to pict-section-form's PictFormMetacontroller. |
Hooks can be provided as top-level options (onSave: fn) or grouped under a
Hooks: { onSave: fn } object, and added later with on(name, fn).
Lifecycle hooks
Every handler receives a single context object carrying at least
{ Host, HookName, DataAddress }, plus a hook-specific payload.
| Hook | Fires | Extra context |
|---|---|---|
| onBeforeLoad | Before a form is (re)loaded | Manifest, IsSwap |
| onAfterLoad | After a form is loaded (and rendered, if auto) | Manifest, IsSwap |
| onBeforeRender | Before each render | Manifest |
| onAfterRender | After each render | Manifest |
| onSave | After marshalFromView() / save() collects the DOM into data | Data |
tmpFormHost.on('onSave', (pContext) =>
{
persistToServer(pContext.Data);
});API
| Method | Description |
|---|---|
| loadForm(manifest?, overrides?) | Load (or swap) the form. overrides may carry { DataAddress, DestinationType, DestinationAddress }. Defaults to the configured Form. |
| setConfiguration(manifest) | Alias for loadForm(manifest) that reads as a config swap. |
| reload() | Reload the current manifest from scratch. |
| render() | Render (or re-render) the form. |
| marshalToView() | Push the data at DataAddress into the inputs (data -> DOM). |
| marshalFromView() / save() | Pull the input values back into the data, then fire onSave (DOM -> data). |
| solve() | Run the manifest solvers, then reflect computed values in the inputs. |
| getFormData() | The live data object at DataAddress. |
| setFormData(data) / loadData(data) | Replace the data object and push it into the view. |
| getValueByHash(hash) / setValueByHash(hash, value) | Read / write a single field by descriptor hash. |
| getMetacontroller() | The dedicated metacontroller instance driving this host. |
| getSectionViews() | The section-form views this host owns. |
| getDataAddress() / setDataAddress(address) | Read / retarget the data address. |
| getDestinationType() / getIframeElement() | Inspect the mount. |
| isLoaded() / isRendered() | State flags. |
| destroy() | Remove the section views + metacontroller and clean up (restores ContentAssignment in iframe mode). |
iframe mode
Set DestinationType: 'iframe' and the host boots a genuinely independent
child pict inside the iframe. The form renders in the child's own realm -- its
own document, its own CSS cascade, its own event handlers -- so it is truly
isolated from the host page. The parent host never reaches into the iframe; it
talks to the child over a small postMessage protocol and mirrors the form data
back into DataAddress.
pict.addProvider('MyForm',
{
Form: manifest,
DestinationType: 'iframe',
DestinationAddress: '#My-Form-Mount',
DataAddress: 'AppData.MyForm',
// Load the runtime into the iframe realm: pict, then this module's
// bundle (which carries pict-section-form).
IframeScripts: [ './pict.min.js', './pict-section-form-host.min.js' ],
onSave: (pContext) => console.log('saved', pContext.Data)
}, libPictSectionFormHost);How it works:
- The host builds the iframe (a same-origin
srcdocby default), injects the runtime scripts plus a small child bootstrap, and the child boots its own pict and renders the form with a plain div-mode host inside the frame. - The child needs
window.Pictandwindow.PictSectionFormHostpresent in its realm. Provide the script URLs withIframeScripts; when omitted, the host auto-clones the host document's own external<script src>tags (which works when the app loads those runtimes as separate script tags). - Data crosses the boundary over
postMessage: the child relays edits and saves out, the parent mirrors them intoDataAddress(sogetFormData()stays current) and firesonSavewhen the child reports a save.
Because the boundary is postMessage, iframe-mode data flow is asynchronous:
getFormData() reflects the most recent message from the child, and onSave /
onAfterLoad fire when the child reports the corresponding step. Div mode stays
fully synchronous.
For a cross-origin child, host a page carrying the child runtime elsewhere and
pass its URL as IframeSrc; the protocol is identical.
Multiple forms on one page
Add the host under different hashes to drive independent form regions:
pict.addProvider('LeftForm', { DestinationAddress: '#left', DataAddress: 'AppData.Left' }, libPictSectionFormHost);
pict.addProvider('RightForm', { DestinationAddress: '#right', DataAddress: 'AppData.Right' }, libPictSectionFormHost);Each host owns its own metacontroller, renders only its own sections and marshals
to its own data address. If two hosts must render the SAME manifest, set
DistinctManifest: true on both so their section hashes and data addresses are
namespaced per instance.
Examples
Two runnable example applications live in example_applications/:
simple_host-- one order form embedded in a div, bound to AppData, with live solver-computed totals and a lifecycle-hook status line. (Basic)iframe_switcher-- a form embedded in a style-isolated iframe, swapping between Contact / Feedback / Shipping configurations at runtime, each with its own AppData address. (Intermediate)
Build and open one with:
cd example_applications/simple_host
npm install
npm run build
# then serve ./dist and open index.htmlHow it works
The host is a pict-provider with two rendering strategies behind one API.
div mode renders in the host's own pict. On load it:
- Ensures a
PictApplicationand the pict-section-form dependency providers are present (adding a minimal default application if the host is dropped onto a bare pict instance). - Creates a dedicated metacontroller (a thin subclass of
PictFormMetacontroller) with per-instance template / renderable / destination hashes, scoped so it only ever renders and marshals its own sections. - Bootstraps the section views from the manifest, points them at
DataAddress, generates the container metatemplate and renders.
The dedicated, scoped metacontroller is what lets several div-mode hosts coexist without stepping on one another.
iframe mode renders in a child pict inside the iframe. On load it builds the
iframe, injects the runtime, and a small child bootstrap stands up its own pict
running a plain div-mode host in the frame's realm. The parent proxies the API to
the child over postMessage and mirrors the data back into DataAddress. The
child is genuinely isolated -- there is no parent monkey-patching of the child's
document.
License
MIT
