Developer Guide
Generated from
doc/developer-guide.md. Edit the source file, then rerunnode jog-docs/scripts/sync-docs.mjs.
JOG V2 Developer Guide
Status
This document describes the JOG V2 code that exists now under v2/. It is a living document and must be updated whenever the runtime or example apps change.
Current v2/ layout:
v2/runtime/: framework runtime sourcev2/apps/: first-party example application scriptsv2/packages/: browser-ready third-party control packagesv2/packages-src/: source for bundled third-party packages that wrap external librariesv2/examples/: browser entry HTML files for the examples
What JOG V2 Is
JOG V2 is a browser UI runtime for developers who want to build front ends in straight JavaScript using controls, containers, windows, dialogs, state, and events instead of writing HTML or touching the DOM directly.
The authoring model is explicit:
- create controls with
new - set properties directly
- compose with
Add - handle events with
OnXmethods such asOnClickandOnChange - bind input controls to a
JOG.Store
JOG owns rendering, DOM creation, styling injection, and event wiring.
What Exists Today
Implemented public surface in v2/runtime/JOG.js:
- theme API:
JOG.SetTheme(),JOG.GetTheme(),JOG.Theme,Application.Theme - browser helpers:
JOG.Browser.OpenTextFile(),JOG.Browser.SaveTextFile() - application runtime:
Application,Page - base types:
Component,Control,Container - layout containers:
Panel,DockPanel,WorkspaceShell,SplitPanel,StackPanel,Repeater,SectionPanel,Grid - shell controls:
MenuBar,ToolBar,StatusBar,PageHeader,TabControl,TabPage - windows:
Window,Dialog - controls:
DataGrid,Label,ValidationMessage,ValidationSummary,Button,TextBox,TextArea,CheckBox,RadioButton,DropDownList,ListBox - state:
Store,Collection,FormState - event payload type:
EventArgs - control-level validation state:
Invalid,ErrorText,SetError(),ClearError(),BindError(),ValidationMessage,ValidationSummary - application diagnostics:
Debug,DumpTree(),LogTree() - third-party extensibility:
JOG.Version,JOG.RegisterControl(),JOG.GetRegisteredControl(),JOG.ListRegisteredControls(),JOG.DumpRegisteredControls(),JOG.IsVersionCompatible(),JOG.RegisterStyleBlock(),JOG.DefineControlProperty()
Test entrypoint:
node test/run-v2-tests.jsnpm run benchmark:renderserves the repeatable browser render benchmark atv2/examples/render-benchmark.html. It compares JOGDataGridwith an equivalent hand-written DOM grid and exports browser-local JSON results.
Example apps:
- v2/examples/hello-world.html
- v2/examples/example.html
- v2/examples/notepad.html
- v2/examples/customer-admin.html
- v2/examples/form-demo.html
- v2/examples/opportunity-board.html
- v2/examples/third-party-demo.html
- v2/examples/weather-window-planner.html
The weather window planner keeps external data access in WeatherWindowPlannerData.js and the JOG application logic in WeatherWindowPlannerApp.js. It demonstrates an explicit-refresh workflow: seeded data keeps the demo usable offline, successful refreshes replace it, and failed refreshes retain the last usable forecast. Its location dialog uses the same adapter to search Open-Meteo’s city and town index before filling a coordinate-based site record. The Operating Sites sidebar embeds LeafletJOG.Map: marker clicks select existing sites, while map-background clicks open the location dialog with coordinates prefilled. The Analysis tab composes the bundled ChartJOG.BarChart and FlatpickrJOG.DatePicker controls with the same adapter’s historical endpoint. It renders three horizontal forecast-versus-historical bar charts, one each for maximum temperature, total precipitation, and maximum wind. The Notes tab uses LexicalJOG.LexicalPlainTextBox for per-site session notes and JOG.Browser.SaveTextFile() for a plain-text weather briefing.
Distribution build:
- source runtime at v2/runtime/JOG.js
- minified browser artifact at
dist/JOG.min.js - source map at
dist/JOG.min.js.map - starter release bundle at
dist/starter/
Third-party control extensibility status:
- JOG now exposes a first-pass public registration and extension surface for third-party controls
- the current contract is implemented in
v2/runtime/JOG.jsand exercised by AcmeJOG.Controls.js, BeaconJOG.Controls.js, ChartJOG.Controls.js, FlatpickrJOG.Controls.js, LeafletJOG.Controls.js, and LexicalJOG.Controls.js - the broader direction and remaining gaps are documented in third-party-control-spec.md
Application Model
The normal boot pattern is:
var app = new JOG.Application();
var page = new JOG.Page();
page.Title = "My App";
page.Add(someControl);
app.Run(page);
Application.Run(page) attaches the runtime to document.body, injects framework styles, attaches the page to the runtime, marks the page dirty, and flushes the initial render.
It also schedules one follow-up viewport layout pass on the next animation frame so fill-based shell layouts can settle against real browser dimensions.
Page is the root container. It also sets document.title from page.Title.
When the application starts, JOG also resets the browser’s default document margin and padding so the page root can own the full viewport surface consistently.
Direct children added to Page now use normal flow layout by default. That means a plain Label, Button, or SectionPanel added directly to the page will render like a normal block in document flow. Window and Dialog still render with absolute positioning so desktop-style floating surfaces continue to work.
Smallest runnable example in this repo:
var app = new JOG.Application();
var page = new JOG.Page();
var helloLabel = new JOG.Label();
page.Title = "Hello World";
helloLabel.Text = "Hello world from JOG.";
page.Add(helloLabel);
app.Run(page);
See v2/apps/HelloWorldApp.js and v2/examples/hello-world.html.
Theme Model
JOG now exposes a small public theme API instead of relying only on hardcoded built-in styling.
Global theme usage:
JOG.SetTheme({
colors: {
appBackground: "#f8f5ef",
primary: "#7c3f00",
primaryText: "#fff7ed"
},
typography: {
fontFamily: "\"IBM Plex Sans\", Arial, sans-serif"
}
});
Per-application overrides:
var app = new JOG.Application();
app.Theme = {
colors: {
primary: "#0f766e",
primaryText: "#f0fdfa"
}
};
Behavior implemented now:
- global themes merge with built-in defaults
Application.Thememerges over the global theme for that app only- the runtime applies resolved values through CSS custom properties on the page root
- the modal overlay is also themed even though it renders outside the page subtree
Supported theme groups today:
colorstypographyradiusspacingshadow
Install Model
JOG V2 is currently a direct browser runtime, not an npm-installed application framework. Npm packaging is intentionally deferred until the current release-asset automation proves insufficient.
The practical install story today is:
- treat GitHub Releases as the primary distribution channel for browser-ready artifacts
- ship
v2/runtime/JOG.jsas a readable development build, or shipdist/JOG.min.jsas a release build - include the runtime with a
<script>tag - load app code that constructs controls and calls
Application.Run(page)
Generate the minified distribution with:
npm install
npm run build:release
This currently writes:
dist/JOG.min.jsdist/JOG.min.js.mapdist/starter/index.htmldist/starter/StarterApp.jsdist/release/JOG.min.jsdist/release/JOG.min.js.mapdist/release/jog-starter-index.htmldist/release/jog-starter-app.js
The dist/release/ files are the exact assets that should be attached to GitHub Releases today. The current release process is documented in doc/release-guide.md, and the upload step is automated in release-artifacts.yml.
This is the appropriate release packaging model for the current state of the project. It keeps the browser delivery story simple while the runtime API continues to evolve, and now includes a minimal starter bundle you can copy and rename.
Browser Helpers
JOG now exposes a very small browser-helper surface for text file open and save flows:
JOG.Browser.OpenTextFile({
types: [
{
description: "Text files",
accept: {
"text/plain": [".txt", ".md"]
}
}
]
}).then(function(result) {
if (!result) {
return;
}
console.log(result.name, result.text);
});
JOG.Browser.SaveTextFile({
text: "hello world",
suggestedName: "notes.txt"
}).then(function(result) {
if (!result) {
return;
}
console.log(result.name, result.method);
});
Behavior implemented now:
OpenTextFile()prefers the modern picker API when availableOpenTextFile()falls back to a runtime-managed hidden native file input when neededSaveTextFile()reuses an existing file handle when one is available andsaveAsis not requestedSaveTextFile()otherwise prefers the modern save picker, then falls back to a download link- both helpers return
nullon user cancel instead of treating cancel as an error
This should stay narrow. The intent is to remove obvious browser DOM escape hatches from app code, not to turn JOG into a broad browser-services layer.
Choosing A Control Authoring Path
JOG now has enough extension capability that contributors need a simple decision rule before they start writing a new control.
Use these paths:
- compose from existing JOG controls when the behavior is mostly shell, layout, validation, and state wiring
- wrap an external library when another package already owns the DOM, interaction model, or specialized behavior
- build a new low-level JOG control when the behavior is truly native to JOG and cannot be expressed cleanly through composition
If you choose the wrong path, the code usually gets harder fast. The most common mistake is building a new low-level control when a composite Container would have been enough.
Path 1: Compose From Existing JOG Controls
Choose this when:
- the feature can be built from existing JOG controls and containers
- the control mostly coordinates layout, state, validation, or command wiring
- you do not need a third-party DOM subtree or custom native input behavior
Typical base type:
JOG.Container
Typical repo shape:
- add the composite control to a package under
v2/packages-src/if it is meant to prove or ship as a third-party package - add it to
v2/runtime/JOG.jsonly if it belongs in the core runtime itself - add or update an example in
v2/apps/andv2/examples/
Recommended implementation steps:
- define a narrow public surface with plain properties,
OnXevents, and real container methods only if the control truly hosts children - compose existing JOG controls inside the container instead of creating raw DOM by hand
- keep state explicit through stores, collections, or direct property state
- register the control with
JOG.RegisterControl()if it lives outside the core runtime - add a focused regression test and an example that exercises the control clearly
Current repo examples:
AcmeJOG.InspectorCardin AcmeJOG.Controls.jsBeaconJOG.MetricCardin BeaconJOG.Controls.js
Path 2: Wrap An External Library
Choose this when:
- the third-party package already owns complex DOM, focus, popup, visualization, or editor behavior
- the value of the control is reuse of an existing library, not recreation of it
- app authors should interact with a JOG-native shell instead of the raw library object
Typical base type:
JOG.Controlfor a single-surface wrapperJOG.Containeronly if the wrapper genuinely needs JOG child composition around the library surfaceJOG.WindoworJOG.Dialogonly when the wrapped concept is inherently a floating shell
Required architectural line:
- the JOG control owns public properties, validation state, lifecycle hooks, and normalized events
- a package-local adapter owns the external library instance and library-specific DOM work
- app code never talks directly to the wrapped library object
Typical repo shape:
- editable source in
v2/packages-src/ - browser-ready bundle in
v2/packages/ - one rebuild script in
scripts/ - one example app flow proving normal JOG usage from outside
v2/runtime/JOG.js
Recommended implementation steps:
- start with a minimal public interface, usually
Value, a few literal options, andOnChange - implement
CreateDom(),OnAttached(),ApplyState(), andOnDisposed()against documented hooks only - define public properties with
JOG.DefineControlProperty()so state writes stay inside the normal dirty-render model - use
JOG.RegisterStyleBlock()for package CSS instead of ad hoc DOM style injection - normalize library callbacks into JOG events through
RaiseEvent(...) - add explicit binding helpers only when the pattern is repeated enough to justify them
- test attach, detach, disposal, store-driven updates, and loop suppression
Current repo examples:
ChartJOG.BarChartin ChartJOG.Controls.source.jsFlatpickrJOG.DatePickerin FlatpickrJOG.Controls.source.jsLeafletJOG.Mapin LeafletJOG.Controls.source.jsLexicalJOG.LexicalPlainTextBoxandLexicalJOG.LexicalRichTextBoxin LexicalJOG.Controls.source.js- shared wrapper mechanics in ThirdPartyJOG.Helpers.js
Path 3: Build A New Low-Level JOG Control
Choose this when:
- the control needs custom native DOM behavior that existing JOG controls cannot express
- there is no external library worth wrapping
- the control belongs to JOG’s core programming model or to a package-specific primitive surface
Typical base type:
JOG.Control
Use this path sparingly. Low-level controls create long-term maintenance cost because they own more lifecycle, DOM, accessibility, and state detail directly.
Recommended implementation steps:
- define the exact public contract first: properties, events, methods, validation behavior, and limits
- build against documented lifecycle hooks such as
CreateDom(),ApplyState(),BindDomEvents(),OnAttached(), andOnDisposed() - keep direct DOM ownership inside the control instead of leaking DOM nodes into app code
- use
JOG.DefineControlProperty()for public state where appropriate - register the control if it is third-party, then add example coverage and regression tests
Current repo examples:
AcmeJOG.TagPickerin AcmeJOG.Controls.jsBeaconJOG.ViewSwitchin BeaconJOG.Controls.js
Minimum Contributor Checklist For New Controls
Before calling a control done, make sure all of this is true:
- the public API follows the normal JOG shape:
new, plain properties,OnX, andAddonly when it is a real container - the control uses documented hooks instead of private runtime fields
- validation, visibility, enabled state, and focus behavior are clear
- tests cover the behavior most likely to regress
- an example shows the control in normal app code
- docs describe what is implemented now, not what the control may support later
Setting Up A Standalone Third-Party Control Repo
If you are creating your own repo for a JOG control package, treat it as a small standalone package with one browser-ready artifact plus one readable source tree.
That guidance holds regardless of authoring path. The internals change between composition, external-library wrappers, and new low-level controls, but the repo shape should stay simple and predictable.
Recommended layout:
my-jog-control/
README.md
LICENSE
package.json
src/
MyJOG.Controls.source.js
MyJOG.Helpers.js
dist/
MyJOG.Controls.js
examples/
demo.html
DemoApp.js
scripts/
build-package.js
test/
run-tests.js
Notes:
src/holds the readable source for your control packagedist/holds the browser-ready bundle a JOG app loads afterJOG.min.jsexamples/proves the package from normal app code, not from internal helper codescripts/holds the bundle or rebuild scripttest/holds the package-level regression checksMyJOG.Helpers.jsis optional and should exist only if repeated package-internal mechanics justify it
Minimum browser load order:
<script src="JOG.min.js"></script>
<script src="MyJOG.Controls.js"></script>
<script src="DemoApp.js"></script>
Core package rules:
- ship one browser-ready control bundle that a JOG app can load directly
- keep the app-facing contract in the control layer, plain properties,
OnX, andAddonly for real containers - register every exported control with
JOG.RegisterControl(...) - register package CSS through
JOG.RegisterStyleBlock(...) - keep raw DOM or library-instance details private to the package
- include at least one example showing normal JOG usage
- test attach, update, disposal, bindings, and any wrapper-specific loop suppression
Minimum registration shape:
JOG.RegisterControl({
fullName: "MyJOG.MyControl",
version: "1.0.0",
jogVersionRange: "^2.0.0",
constructor: MyControl,
metadata: {
baseType: "Control",
properties: ["Value"],
events: ["OnChange"],
methods: ["Focus"]
}
});
What changes by authoring path:
- composed packages usually keep most logic inside a
JOG.Containerand reuse existing JOG child controls directly - external-library wrappers should split into a JOG-facing shell plus a private adapter around the third-party library instance
- new low-level controls still use the same repo shape, but more of the implementation lives in
CreateDom(),ApplyState(), and DOM-event handling
Starter Checklist For A New Third-Party Control Repo
Before publishing the repo, make sure all of this is true:
- the README states which JOG version range the package targets
- the README explains whether the package is composed, wrapped, or low-level
- the README documents any required external scripts or styles if the package wraps another library
dist/contains a browser-ready bundle that matches the readable source- the package exposes a small example that can be loaded directly in a browser
- the package never requires app code to reach into private DOM nodes or third-party objects
- the tests cover initial attach, state updates, disposal, and error-prone binding flows
- version compatibility is explicit through
jogVersionRange
Third-Party Controls
JOG now includes a first-pass public extension contract for third-party controls.
Current public extension entrypoints:
JOG.VersionJOG.RegisterControl(definition)JOG.GetRegisteredControl(nameOrConstructor)JOG.ListRegisteredControls()JOG.DumpRegisteredControls()JOG.IsVersionCompatible(range)JOG.RegisterStyleBlock(name, cssText)JOG.DefineControlProperty(target, propertyName, options)
Stable base-class hooks available to third-party authors:
CreateDom(doc)ApplyState(prevState, nextState)BindDomEvents()OnAttached()OnDisposed()GetChildHostNode()RegisterEvent(name, listener)RaiseEvent(name, originalEvent, extras)GetStateValue(key)SetStateValue(key, value)MarkDirty()TrackBinding(unsubscribe)GetRegistration()
Window-specific helper available to third-party floating shells:
GetWindowShell()
Registration shape implemented now:
JOG.RegisterControl({
fullName: "AcmeJOG.TagPicker",
version: "1.0.0",
jogVersionRange: "^2.0.0",
constructor: TagPicker,
metadata: {
baseType: "Control",
properties: ["Items", "Value", "Placeholder"],
events: ["OnChange"],
methods: ["BindValue"],
capabilities: {
supportsValidation: true
}
}
});
Behavior implemented now:
- registration enforces unique full names
- registration rejects unsupported JOG version ranges against
JOG.Version - diagnostics and tree dumps use registered third-party full names
- diagnostics include third-party package version information on runtime event and render failures
- style blocks can be registered once per package through
JOG.RegisterStyleBlock() - third-party controls can define plain properties without reaching into private runtime fields through
JOG.DefineControlProperty() - the sample
AcmeJOG.TagPickernow also demonstrates first-pass keyboard selection and radio-group accessibility semantics JOG.Window.GetWindowShell()now gives third-partyWindowandDialogsubclasses a stable way to reuse the built-in chrome without reaching into private fields
Current sample package:
- AcmeJOG.Controls.js registers
AcmeJOG.TagPickeras a primitive control andAcmeJOG.InspectorCardas a composite container with a custom child host - AcmeJOG.Controls.js also registers
AcmeJOG.CommandPaletteDialogas a third-party dialog that reuses the built-in window shell throughGetWindowShell() - BeaconJOG.Controls.js registers
BeaconJOG.ViewSwitchas a second primitive control andBeaconJOG.MetricCardas a composite control built largely from existing JOG controls - ChartJOG.Controls.js registers
ChartJOG.BarChartas a Chart.js wrapper with array-backedItems, field-based series mapping, optionalHorizontalorientation,BindCollection(), and projectedOnPointClick()events - ChartJOG.Controls.source.js is the editable source for that bundle, and build-chart-package.js rebuilds the browser-ready package
- FlatpickrJOG.Controls.js registers
FlatpickrJOG.DatePickeras a popup date-picker wrapper around Flatpickr, with canonical stringValue,MinDate,MaxDate,BindValue(), and normal JOG validation behavior - FlatpickrJOG.Controls.source.js is the editable source for that bundle, and build-flatpickr-package.js rebuilds the browser-ready package
- LeafletJOG.Controls.js registers
LeafletJOG.Mapas an interactive-map wrapper with explicit viewport, marker, tile, and attribution properties plus projectedOnMapClick(),OnMarkerClick(), andOnViewportChange()events - LeafletJOG.Controls.source.js is the editable source for that bundle, and build-leaflet-package.js rebuilds the browser-ready package
- LexicalJOG.Controls.js now registers both
LexicalJOG.LexicalPlainTextBoxandLexicalJOG.LexicalRichTextBox, keeping Lexical JSON as the canonicalValue, preservingGetPlainText(),SetPlainText(text), andBindPlainText()for ordinary form wiring, tracking editable-host accessibility state for read-only and invalid transitions, and treating whitespace-only content as empty for required-field validation - LexicalJOG.Controls.source.js is the editable source for that bundle, and build-lexical-package.js rebuilds the browser-ready package
- ThirdPartyJOG.Helpers.js now holds the shared internal bridge used by the external-library wrappers for value synchronization, collection binding, store binding, and detached-popup theme-variable propagation
- ThirdPartyDemoApp.js and third-party-demo.html show all five packages used side by side from outside
v2/runtime/JOG.js, including the current baseline validation pattern for third-party inputs throughBindError(),FormState.Watch(...),ValidationSummary.BindErrors(...), andFocus()on invalid submit, plus both aToolBar-based button row using the Lexical convenience methods and a plain button row callingFormatText(formatType)directly
Current limits:
- the public extension contract is first-pass, not yet frozen as a long-term compatibility promise
- the sample primitive control now proves first-pass keyboard interaction, but not yet deeper screen-reader validation across real browsers
- the Flatpickr wrapper proves popup-library integration and canonical string-value mapping, but does not yet cover richer date-range, time, or locale-specific formatting modes
- the Lexical wrapper is intentionally plain-text-first, with no rich-text toolbar, plugin surface, or HTML-first persistence contract yet
- there is not yet higher-level tooling for serialization, designers, or package discovery
The current wrapper pattern is narrow and deliberate:
- JOG owns the public control shell and public state.
- A package-local adapter owns the third-party library instance.
- Adapter callbacks normalize library events into JOG
RaiseEvent()payloads. - Public properties push state back through the adapter without exposing the raw library object.
The repo now also includes a small shared helper for this pattern in ThirdPartyJOG.Helpers.js. It is intentionally internal and narrow. It covers only the mechanics both wrappers already needed:
- canonical value bridging between adapter and JOG state
- loop suppression for store-driven adapter writes
- explicit
BindValue()wiring - detached popup theme-variable propagation for body-mounted external UI
Library-specific DOM, adapter options, and public control APIs still stay inside each package source file.
That pattern is now proven against four different external-library categories:
- visualization control through
ChartJOG.BarChart - popup input control through
FlatpickrJOG.DatePicker - interactive-map control through
LeafletJOG.Map - editor controls through
LexicalJOG.LexicalPlainTextBoxandLexicalJOG.LexicalRichTextBox
Control Model
Every control is a JavaScript object with internal state. State changes do not write to the DOM immediately. Instead:
- a property setter updates the control state
- the runtime marks the control dirty
- the runtime flushes dirty controls in a microtask
- each control applies current state to its DOM node
This is the core architectural shift from V1. It separates control state from direct DOM mutation.
The runtime now also exposes a narrow Fill flag for controls and containers that need to stretch inside shell layouts. This is not a general flexbox abstraction. It exists so shells and tab workspaces can reduce manual width and height math.
When a control is already dock-managed inside a DockPanel, Fill now avoids forcing raw 100% width and height so dock geometry remains authoritative.
DockPanel now also honors Gap as shell spacing between docked regions. A docked child can override that spacing with its own Gap when one region needs a different separation than the rest of the shell. WorkspaceShell builds on that dock behavior with explicit Header, Sidebar, and Content slots for the most common app-shell composition, and now also exposes a shell-owned SidebarLayout helper for repeated responsive sidebar sizing and collapse patterns.
Data Controls
JOG now has a first-pass data-centric layer aimed at CRUD and internal-tool screens.
JOG.Collection is the runtime state model for row-oriented data. It currently handles:
- stable row identity through a configurable
idKey - explicit row insert, update, upsert, and remove operations
- single-row selection through
Select()andClearSelection() - dirty tracking for updated rows and deleted baseline rows
- derived summaries through named summary functions
JOG.DataGrid is the first control built on top of that model. The implemented surface today is still intentionally narrow, but it now covers first-pass view-level sorting, filtering, and inline editing:
- explicit column definitions with
key,title,width,align, and optionalformatter - optional sortable headers,
sortValue, and view-levelSortKeyplusSortDirection - explicit filter text plus optional
FilterColumnsandFilterPredicate - per-column overflow modes for truncate, wrap, or clip
- first-pass inline editing for text, textarea, and select cells, including committing the current edit when the user moves directly to another editable cell
- bounded pixel-width resizing through per-column
minWidthandmaxWidth - bounded flexible columns through
minWidthplusmaxWidthwithout forcing a fixed pixel width - rows provided by a bound
JOG.Collection - single-row selection
- row command buttons through
RowCommands - optional mouse-driven header resizing through
ResizableColumnsfor pixel-width columns - empty-state text
- dirty-row and selected-row styling
What it does not do yet:
- column reordering
- virtualization
- touch resizing
- accessibility-grade keyboard navigation
The current design goal is credibility, not completeness. It gives business-app examples a first-class repeated-item surface without trying to solve the full grid problem in one pass.
Shell Controls
JOG now includes a first minimal shell control:
MenuBarToolBarStatusBarPageHeaderTabControl
MenuBar is a horizontal command strip for page-level application chrome. The current implementation is intentionally narrow:
- items are provided through
menuBar.Items - each item supports
key,text, andenabled - click handling is exposed through
menuBar.OnItemClick(listener) - nested menus, keyboard shortcuts, and dropdown popouts are not implemented yet
Example:
var menuBar = new JOG.MenuBar();
menuBar.Items = [
{ key: "file", text: "File" },
{ key: "view", text: "View" },
{ key: "help", text: "Help" }
];
menuBar.OnItemClick(function(args) {
console.log("Menu clicked:", args.Key);
});
ToolBar is a horizontal container for existing controls such as buttons, labels, and future command widgets. The current implementation:
- hosts ordinary JOG child controls directly
- uses flow layout for its children
- provides shell styling for command rows
- does not yet implement overflow handling, separators, or icon conventions
StatusBar is a horizontal container for low-priority application state and readouts. The current implementation:
- hosts ordinary JOG child controls directly
- uses flow layout for its children
- provides shell styling for footer-style status content
- does not yet implement grip areas, segmented regions, or automatic spring spacing
PageHeader is a narrow shell header primitive for page title and subtitle chrome. The current implementation:
- exposes
TitleTextandSubtitleText - stacks those values in normal flow instead of requiring absolute label coordinates
- sizes itself from content instead of requiring a fixed explicit height
- works cleanly as a
Dock = "top"region insideDockPanel
TabControl is a page-region container for switching between multiple child panels. The current implementation uses explicit TabPage children rather than a loose item array:
TabControlhostsTabPagechildren only- each
TabPagecan defineTitleandTabKey ActiveTabselects the visible pageOnTabChange(listener)reports user tab switches- inactive pages are hidden cleanly
- closable tabs, drag reordering, overflow handling, and nested docking behavior are not implemented yet
Example:
var tabs = new JOG.TabControl();
var detailsTab = new JOG.TabPage();
detailsTab.TabKey = "details";
detailsTab.Title = "Details";
detailsTab.Add(detailsLayout);
var activityTab = new JOG.TabPage();
activityTab.TabKey = "activity";
activityTab.Title = "Activity";
activityTab.Add(activityLayout);
tabs.Add(detailsTab);
tabs.Add(activityTab);
tabs.ActiveTab = "details";
Diagnostics
JOG now has a small built-in diagnostics layer on Application.
Available surface:
app.Debug = trueapp.DebugTopics = ["event", "lifecycle"]app.DumpTree()app.DumpTree({ detailed: true })app.LogTree()app.LogTree({ detailed: true })
What Debug = true does today:
- logs dirty queue activity
- logs render and mount lifecycle work
- logs event dispatch with handler counts
What DebugTopics does today:
- optionally limits debug logging to selected categories
- accepted categories are
dirty,flush,lifecycle, andevent - accepts an array such as
["event", "lifecycle"]or a comma-separated string such as"event,lifecycle"
What runtime error formatting does today:
- reports render failures with the control name and stack when available
- reports event handler failures with the control name, event name, handler index, and stack when available
- rethrows the original error after logging it
What tree dump does:
- prints the current control hierarchy
- includes control type, control name or id, visible state, enabled state, lifecycle state, and basic size or position data when present
- detailed mode also includes richer state such as title, text, placeholder, dock, grid coordinates, span, invalid state, error text, and child counts when relevant
Typical usage:
var app = new JOG.Application();
app.Debug = true;
app.DebugTopics = ["event", "lifecycle"];
app.Run(page);
console.log(app.DumpTree());
console.log(app.DumpTree({ detailed: true }));
Use LogTree() when you just want the tree in the console without formatting it yourself. Pass { detailed: true } when you need a richer state dump.
Tests
JOG V2 now has a small zero-dependency Node test harness.
Run it with:
node test/run-v2-tests.js
What it covers today:
- store subscription and unsubscribe behavior
- duplicate child name protection
- setter normalization and disposed-control lifecycle guards
- error binding and disposal behavior
- remove and clear disposal behavior
- control tree dump output
- richer window resize behavior
- modal stacking and window lifecycle events
- example-level integration flows for customer-admin selection, dialog editing, alternate dialog close paths, and form validation/reset bindings
This is intentionally lightweight. It does not replace browser-level verification, but it does give the repo a fast regression check for core runtime behavior.
Layout Model
JOG V2 currently supports three useful layout styles and one low-level container.
Panel
Panel is the simplest container. It uses absolute positioning for child controls unless the child is inside a flow-layout container higher up.
Use it when you need explicit Left and Top placement.
StackPanel
StackPanel is a flex-based flow container.
Orientation = "vertical"or"horizontal"GaporSpacingcontrols item spacingResponsivecan switch orientation and spacing by breakpoint
Use it for simple linear layouts, toolbars, vertical forms, and button rows.
DockPanel
DockPanel is the closest current equivalent to a desktop-style shell layout.
Children can set:
Dock = "top"Dock = "bottom"Dock = "left"Dock = "right"Dock = "fill"
The container also respects container Padding, container Gap, child Margin, and docked-child Gap overrides.
Responsive dock behavior can now be driven through inherited ResponsiveLayout on the container and its children. This is the current way to collapse a left sidebar into a top region on smaller widths.
Use it for app shells, sidebars, top bars, and detail regions.
WorkspaceShell
WorkspaceShell is a higher-level dock-based shell container for the common header plus sidebar plus content pattern.
Headerdefaults toDock = "top"Sidebardefaults toDock = "left"Contentdefaults toDock = "fill"SidebarLayoutcan own the repeated responsive dock, width, height, and gap pattern for the sidebar slot- keeps those slotted children ordered correctly even if you assign them in a different sequence
- still honors
Padding,Gap, and childResponsiveLayoutso sidebars can collapse to top regions on narrow widths
Use it when you want a page shell primitive instead of wiring DockPanel child order and default docking by hand.
SplitPanel
SplitPanel is the new higher-level workspace container for two-pane layouts.
Orientation = "horizontal"or"vertical"FirstPaneSizefixes the first pane in pixels when setSecondPaneSizefixes the second pane in pixels when setGapcontrols the separation between panesResponsivecan switch orientation, gap, and pane sizes by breakpoint
Use it for left-nav-plus-content shells, inspector layouts, and stacked mobile fallbacks where the outer shell should stay simple.
SectionPanel
SectionPanel wraps content in a framed card with an optional title header and a padded body.
Use it to create visually separated regions such as forms, sidebars, summaries, and detail panels.
SectionPanel now also supports a narrow Responsive surface for breakpoint-driven title and body-padding changes.
Example:
sidebar.Responsive = {
base: {
title: "Pipeline",
padding: 10
},
md: {
title: "Pipeline Snapshot",
padding: 12
}
};
Grid
Grid is the newest layout primitive. It uses CSS grid internally and is intended for desktop-style form layout.
Grid container properties:
ColumnsRowsAreasAutoRowsAutoFlowColumnGapRowGapResponsive
Child placement properties:
GridColumnGridRowGridAreaColumnSpanRowSpanResponsiveGrid
Breakpoint keys implemented now:
basesmat640pxmdat768pxlgat1024pxxlat1280px
Example:
formGrid.Responsive = {
base: { columns: ["1fr"] },
md: { columns: ["160px", "1fr"] }
};
nameInput.ResponsiveGrid = {
base: { column: 1, row: 2 },
md: { column: 2, row: 1 }
};
Component-level layout changes can also use ResponsiveLayout:
sidebar.ResponsiveLayout = {
base: { dock: "top", height: 220, margin: { bottom: 16 } },
md: { dock: "left", width: 270, margin: { top: 0, right: 24, bottom: 0, left: 0 } }
};
Current limitation: Grid now supports explicit placement, named areas, automatic row sizing, and breakpoint-based responsive overrides, while StackPanel, DockPanel, WorkspaceShell, and SplitPanel now cover the main shell patterns. There is still no container-query model or deeper multi-pane workspace manager.
Window and Dialog Model
Window is a container with a title bar and content area.
Current behaviors:
- absolute positioning
- modal overlay support
- stacked modal overlay support across multiple visible dialogs
- modal focus trap that keeps
TabandShift+Tabinside the top modal window - focus restoration to the previous control when the last modal closes, and back to the underlying dialog when nested modals unwind
- draggable by title bar
- close button
- escape-to-close when enabled
- load, show, hide, and close lifecycle hooks
- z-index stacking through the runtime
Dialog is a Window that defaults Modal = true.
Useful window properties today:
TitleModalDraggableResizableCloseButtonVisibleCloseButtonTextCloseOnEscapeWidth,Height,MinWidth,MinHeightLeft,Top
When Resizable = true, the window can be resized from edges and corners. Resize behavior currently respects MinWidth and MinHeight.
Theme Presets
JOG now has a small built-in preset surface on ThemePreset.
Implemented presets today:
Button:primary,danger,quietLabel:primary,strongSectionPanel:primary,muted
Example:
saveButton.ThemePreset = "primary";
deleteButton.ThemePreset = "danger";
sidebar.ThemePreset = "primary";
titleLabel.ThemePreset = "strong";
This is intentionally narrow. It gives app code a stable presentation layer without opening arbitrary per-control style objects yet.
Store and Binding Model
JOG.Store is intentionally small.
var store = new JOG.Store({
name: "Atlas Bio",
active: true
});
Available store methods:
Get(key)Set(key, value)Subscribe(key, listener)Derive(key, dependencyKeys, compute)
Subscribe returns an unsubscribe function. Control bindings register these unsubscribers and clean them up on Dispose().
Derive also returns an unsubscribe function. It keeps one store key in sync from other store keys through an explicit compute function.
Current explicit binding helpers:
Label.BindText(store, key, formatter)ValidationMessage.BindMessage(store, key, formatter)ValidationSummary.BindSummary(store, key, formatter)ValidationSummary.BindErrors(store, keys, formatter)TextBox.BindText(store, key)TextArea.BindText(store, key)CheckBox.BindChecked(store, key)RadioButton.BindSelectedValue(store, key)DropDownList.BindSelectedValue(store, key)ListBox.BindSelectedValue(store, key)Component.BindVisible(store, key, transform)Component.BindEnabled(store, key, transform)
Current explicit app-state helper:
Store.Derive(key, dependencyKeys, compute)FormState(store, options)
Binding is explicit and per-control. There is no expression language or selector syntax. Store.Derive() and FormState stay intentionally narrow. App code still decides which keys are derived, when validation runs, and what those keys mean.
Collection Model
JOG.Collection is the current state primitive for repeated business records.
var deals = new JOG.Collection({
idKey: "id",
rows: [
{ id: 1, account: "Northwind", value: 145000 }
],
summaryDefinitions: {
totalValue: function(rows) {
return rows.reduce(function(sum, row) {
return sum + row.value;
}, 0);
}
}
});
Available collection methods:
GetIdKey()GetRowId(row)GetRows()GetRow(id)SetRows(rows)Insert(row, index)Update(id, updaterOrPatch)Upsert(row)Remove(id)Select(id)SetSelectedIds(ids)ToggleSelected(id)ClearSelection()GetSelectedId()GetSelectedIds()GetSelectedRows()GetDirtyRowIds()GetDeletedRowIds()GetDirtyState()IsDirty(id)HasDirtyRows()MarkClean(ids)SetSummaryDefinitions(definitions)GetSummary(key)GetSummaries()Subscribe(key, listener)BindStore(store, key, eventKeys, compute)
Supported subscription keys today:
rowsselectiondirtysummarychange
BindStore(store, key, eventKeys, compute) is the narrow bridge from collection state into store-backed page state. It is useful when a page wants labels, button enabled state, or status text that depend on collection summaries, selection, or dirty state without hand-written subscription glue.
For simple repeated collection-backed UI, use JOG.Repeater with BindCollection(collection, renderer). The renderer stays explicit and returns normal JOG controls, while the repeater handles re-rendering when the collection changes.
The collection API stays explicit. App code decides when records become clean again, usually after a persistence step or a deliberate local snapshot reset.
Validation Model
JOG now has a small control-level validation surface.
Available on components and controls:
InvalidErrorTextSetError(message)ClearError()BindError(store, key)BindVisible(store, key, transform)BindEnabled(store, key, transform)
What this does today:
- toggles invalid styling on supported input controls
- sets
aria-invalid - uses the error text as the control tooltip
- can bind a control’s error state directly to a store key
- can bind page-level summary and inline error visibility directly to a store key
- provides small first-class validation display controls for inline messages and summary blocks
What it does not do:
- it does not provide a validator DSL
- it does not render inline error text automatically
- it does not decide which validations your form needs
The intended usage is explicit. App code decides when validation runs, stores error messages where useful, and can show inline error labels beside controls.
The recommended pattern now is:
- keep error strings in the store
- call
control.BindError(store, "someErrorKey") - have validation code set or clear that store key
- render inline error labels only where you want them
JOG.FormState now covers the narrow repeated pattern where a form needs to validate a fixed set of store keys, write field errors back into the store, and optionally maintain one summary key plus one valid-state key.
var formState = new JOG.FormState(store, {
summaryKey: "validationSummary",
validations: [
{
errorKey: "nameError",
validate: function(currentStore) {
return (currentStore.Get("name") || "").trim() ? "" : "Enter a name.";
}
}
]
});
saveButton.OnClick(function() {
if (!formState.Validate()) {
return;
}
// persist record
});
FormState.Watch(keys) re-runs validation only after errors exist, which keeps the model explicit without forcing eager validation on every keystroke. When your summary is just a composition of field-level error strings and you do not need a dedicated form helper, ValidationSummary.BindErrors(store, ["nameError", "statusError"]) is still the smaller option.
The same pattern now backs the third-party demo’s FlatpickrJOG.DatePicker and LexicalJOG editor fields. The recommended baseline for third-party required fields is:
- bind the control value normally with
BindValue()orBindPlainText() - bind invalid state with
BindError(store, errorKey) - run one narrow
FormStatevalidator per field or per small form section - call
Watch([...])so a field only revalidates after it has first failed - use
ValidationSummary.BindErrors(...)when you want one visible summary across multiple third-party controls - focus the invalid control in the submit handler after
Validate()fails
For radio-group validation, bind the error key to the StackPanel that owns the radio buttons. The built-in invalid styling now propagates from that row container to the radio captions.
Events
Controls expose event registration methods instead of raw DOM listeners.
Current event registration rule:
- use
OnX(listener)as the standard documented style - shorthand aliases such as
Click(listener)andChange(listener)remain supported for compatibility Focus()is reserved for imperative browser focus, so focus event registration usesOnFocus(listener)
Current event methods on Control:
Click(listener)Change(listener)OnFocus(listener)Blur(listener)KeyDown(listener)KeyUp(listener)OnClick(listener)OnChange(listener)OnBlur(listener)OnKeyDown(listener)OnKeyUp(listener)
Window-specific:
OnLoad(listener)OnShow(listener)OnHide(listener)OnClose(listener)
Events receive a JOG.EventArgs instance with:
SourceTypeOriginalEventHandledValuewhen applicableKeywhen applicable
Third-party controls may also attach additional documented event fields through RaiseEvent(name, originalEvent, extras). Those extra fields are preserved on the delivered JOG.EventArgs instance as long as they do not collide with the core event-property names.
Current limitation: the shorthand and OnX styles both still exist. The runtime now treats OnX as the preferred public style, while the older shorthand remains as a compatibility layer.
Lifecycle and Rendering
Important lifecycle states in the current runtime:
CreatedShownHiddenDisposed
Calling Dispose():
- unsubscribes store bindings
- removes the DOM node
- disposes children for containers
Calling setters on a disposed control throws.
Current Example Coverage
Example App
v2/apps/ExampleApp.js demonstrates:
- page boot
- button and label usage
- opening a dialog
Customer Admin App
v2/apps/CustomerAdminApp.js demonstrates:
DockPanelshell layoutSplitPanelleft-nav-plus-content workspace compositionSectionPanelregions- inline store-driven updates
- modal dialog editing
- list/detail CRUD-style interaction
Form Demo
v2/apps/FormApp.js demonstrates:
Gridform layout- breakpoint-based responsive
Gridlayout - text input binding
- dropdown binding
- checkbox binding
- radio group binding
- list box binding
- textarea binding
- computed summary updates from store subscriptions
- explicit save-time validation
- invalid control styling
BindError()for field-level invalid stateLabel.BindText()andBindVisible()for reusable page-level validation wiring- inline error labels driven by store state
- validation summary region driven either by a dedicated store key or directly from multiple field error keys
- checkbox and radio-group validation with invalid-state styling
v2/apps/CustomerAdminApp.js now also demonstrates:
- one shared validation routine reused across inline save and modal save
PageHeaderreplacing manual fixed-height shell title layout- field-level error binding on text inputs in both page and dialog contexts
- summary-level validation messaging reused across multiple save entry points
- live revalidation after an invalid edit begins to be corrected
v2/apps/ExampleApp.js now also demonstrates:
- switching the public theme API at runtime between the built-in default and two distinct palettes
- opening one modal dialog directly
- opening a second modal on top of the first
- verifying that the shared overlay stays between the top modal and lower modal windows
v2/apps/OpportunityBoardApp.js now also demonstrates:
- breakpoint-aware dialog form layout inside the opportunity editor
- responsive
DockPanelshell behavior for the board sidebar PageHeaderreplacing manual fixed-height shell title layout- responsive
StackPanelaction rows DataGridrow commands with collection-backed updates- first-pass header drag resizing for pixel-width columns
- built-in
ThemePresetusage on buttons, labels, and sections
v2/apps/NotepadApp.js now also demonstrates:
- docked shell chrome without manual viewport resize math
Fill = trueinside a tab workspace for full-height editor composition- runtime-managed browser text file open and save flows through
JOG.Browser - JOG dialog-based file-operation error reporting instead of browser
alert()calls
Guidance for Developers
- Prefer explicit JavaScript object composition over clever helper layers.
- Treat
v2/runtime/JOG.jsas the runtime truth. - Treat
doc/v2-spec.mdas direction, not implementation truth. - Update this guide, the API reference, and the roadmap whenever the framework changes.
Known Gaps
Not implemented yet:
- menus, tabs, grids for data, trees, toolbars
- richer diagnostics tooling beyond debug logging and tree dumps
- accessibility pass
Partially implemented:
- theming is now public and token-based, and built-in theme presets exist, but arbitrary per-control style objects are still not implemented
- validation exists at the control level, but there is no first-class form validation API yet
- inline error presentation is possible, but app code must render the error labels explicitly
- responsive layout helpers now exist for
Grid, and in narrower form forStackPanelandDockPanel, but not forSectionPanel