Home / Modules / yoja-web

yoja-web

JavaScript / CSS / HTML framework for modular web apps. Section-based components, routing, i18n, HTTP, WebSocket, storage — all in native ES6 modules. No build toolchain, no node_modules, no virtual DOM.

Why yoja-web

  • Zero build toolchain. Drop a JS file, reload the page.
  • Native ES6 modules — no magic. Standard import, served as-is.
  • Built-in cache busting — no content hashing. A version declared in YojaWeb.conf.js is appended as ?version=… to every imported module, CSS and resource URL. Bump it and browsers re-fetch instead of serving stale files. The config file itself is always loaded uncached (?date=<timestamp>), so a new version takes effect on the very next page load.
  • Native Shadow DOM for CSS scoping (when you want it).
  • HTML-first architecture. Components are declared with attributes (yw-*), hydrated from JS classes.
  • Gradual adoption. Drop yoja-web into an existing page; nothing else has to change.
  • Works well with AI. Plain standard HTML / CSS / ES6 with no hidden build step, no JSX and no virtual DOM — so AI coding assistants read the project as-is and generate code that runs without a transpilation round-trip.

Installation

Yoja-Web ships as a jar packaged at com.easygoingapi.yoja.web. Mount it as a static resource on a yoja-http-server router and reference it from your HTML:

HttpRouter.builder()
    .webResource(WebApp.of(WebApp.Type.jar,
                           "com.easygoingapi.yoja.web",
                           "/yoja"),
                 "/*")
    .webResource(WebApp.jar("my.app.webapp"), "/*")
    .build();
<script type="module"
        src="/yoja/YojaWeb.js"
        yw-config-path="/YojaWeb.conf.js"></script>

Configuration

The config file is declared via the yw-config-path attribute on the <script> tag. The framework dynamically imports that path as an ES6 module and reads its default export. If the attribute is absent the framework starts with an empty config. If the file cannot be loaded a console.warn is emitted and the framework starts with an empty config.

// /YojaWeb.conf.js
export default {
    version: '1.0.1',
    defaultLanguage: 'fr',
    mediaDescriptions: [
        { name: 'mobile', maxWidth: 767 },
        { name: 'tablet', maxWidth: 1023 },
        { name: 'desktop' }
    ]
};
  • version — app version surfaced in debug helpers.
  • defaultLanguage — fallback locale when the browser doesn't advertise a supported one.
  • mediaDescriptions — ordered breakpoints fed to responsiveService.

HTML directives

A tag becomes a section — a managed component boundary — as soon as it carries at least one of yw-controler, yw-language, yw-css or yw-slot. yw-include is separate: it grafts external HTML but does not create a section. Directive paths are resolved relative to the file that declares them.

yw-controler attribute

Attach a JavaScript controller to the element (making it a section). The value is the path to an ES6 module whose default export is a class; its constructor receives the Section handle. (Note the single-l spelling controler.)

<section yw-controler="./sections/header/HeaderControler.js">
  <!-- header markup -->
</section>

// HeaderControler.js
export default class HeaderControler {
    constructor(section) {
        section.controlerReady(() => { /* controller instantiated */ });
        section.pageReady(() => { /* whole page loaded */ });
    }
}

yw-language attribute

Declare the XML translation file for the section's i18n (making it a section). Translations are applied to descendants via the yw-i18n* attributes and re-applied on language change. Combinable with the other directives.

<div yw-controler="./AppControler.js" yw-language="./i18n/en.xml"></div>

yw-css attribute

Attach a CSS file scoped to the section via adopted stylesheets (making it a section). An empty value (yw-css with no path) makes the section inherit its parent section's stylesheet instead of loading its own.

<!-- own scoped stylesheet -->
<div yw-css="./card.css"></div>

<!-- inherit the parent section's stylesheet -->
<div yw-controler="./CardControler.js" yw-css></div>

yw-slot attribute

Point at an HTML template file used as the section's slot for content projected from a parent section (making it a section).

<div yw-slot="./slot.html"></div>

yw-include attribute

Graft an external HTML fragment onto the element. This does not create a section; the included file's own yw-* directives are wired up after grafting, relative to its own location.

<!-- outer (default): the host <div> is replaced by the loaded HTML -->
<div yw-include="./sections/taskdetail/taskdetail.html"></div>

<!-- inner: the host <div> is kept, loaded HTML becomes its children -->
<div yw-include="./sidebar.html" yw-include-mode="inner"></div>

yw-include-mode takes outer or inner; any other value — or omitting it — falls back to outer.

yojaWeb: Global API

window.yojaWeb global object

Runtime entry point injected before any controller runs.

Lifecycle
SignatureReturnsDescription
onDocumentReady(fn)voidRegisters fn; fires once after the full document bootstrap
onTagReady(fn)voidRegisters fn; fires each time a yw-* element is initialized
isReady()booleantrue once the runtime bootstrap is complete
onError(fn)voidRegisters a global error handler
DOM — deep (crosses shadow DOM)
SignatureReturnsDescription
firstTag(selector [, fromTag])Element | nullFirst match; searches from fromTag or document
findTags(selector [, fromTag])Element[]All matches; searches from fromTag or document
walkTag(consumer [, fromTag])voidDepth-first walk of every element
DOM — shallow (stops at section boundaries)
SignatureReturnsDescription
firstSectionTag(selector [, fromTag])Element | nullFirst match within section boundaries
findSectionTags(selector [, fromTag])Element[]All matches within section boundaries
walkSectionTag(consumer [, fromTag])voidDepth-first walk within section boundaries
Utilities
SignatureReturnsDescription
path(path [, options])stringNormalizes path and appends ?version=…; options.force=true also adds &date=…
version()stringRuntime version string
append(tag, path)Promise<HTMLElement[]>Fetches HTML at path, appends all children to tag (or its shadow root)
prepend(tag, path)Promise<HTMLElement[]>Same as append, but prepended
Services & Config
PropertyTypeDescription
sectionServiceSectionServiceLow-level section tree-traversal service
controlerServiceControlerServiceLow-level controller tree-traversal service
configobjectConfig object exported by YojaWeb.conf.js

window.yojaWebApi global object

Aggregator exposing every service as a single namespace.

Services
PropertyDescription
httpClientPromise-based HTTP client with interceptors and offline fallback
webSocketServiceWebSocket lifecycle management
storageServicelocalStorage / sessionStorage wrappers with change events
eventServicePub/sub event bus
navigationServiceClient-side navigation and history management
urlParameterServiceRead and write URL query parameters
responsiveServiceViewport breakpoint detection
languageServiceInternationalisation (i18n)
cssSheetServiceDynamic CSS stylesheet management
sectionServiceSection tree traversal
controlerServiceController tree traversal

Section: Component Lifecycle

Section class (injected, not exported)

Handle injected into every controller constructor. One Section per DOM element carrying a yw-* directive.

Properties
PropertyTypeDescription
tagElementRoot DOM element of this section
shadowTagShadowRoot | nullShadow root of the section element, if any
DOM — shallow (within section boundaries)
SignatureReturnsDescription
firstTag(selector)Element | nullFirst match within the section, stops at nested section boundaries
findTags(selector)Element[]All matches within the section
walkTag(consumer)voidDepth-first walk within the section
DOM — deep (crosses shadow DOM)
SignatureReturnsDescription
deepFirstTag(selector)Element | nullFirst match across shadow DOM boundaries
deepFindTags(selector)Element[]All matches across shadow DOM boundaries
deepWalkTag(consumer)voidDepth-first walk across shadow DOM
Section navigation
SignatureReturnsDescription
firstSection(predicate)Section | nullFirst descendant section matching predicate
findSections(predicate)Section[]All descendant sections matching predicate
childrenSections(predicate)Section[]Direct child sections only
closestSection(predicate)Section | nullNearest ancestor section matching predicate
parentSection(predicate)Section | nullDirect parent section, optionally filtered
parentSections(predicate)Section[]All ancestor sections
rootSection()SectionTop-most section ancestor
walkSection(consumer)voidDepth-first walk of all descendant sections
Controller navigation
SignatureReturnsDescription
firstControler(predicate)Controler | nullFirst descendant controller matching predicate
findControlers(predicate)Controler[]All descendant controllers
childrenControlers(predicate)Controler[]Direct child controllers only
closestControler(predicate)Controler | nullNearest ancestor controller
parentControler(predicate)Controler | nullDirect parent controller, optionally filtered
parentControlers(predicate)Controler[]All ancestor controllers
rootControler()ControlerTop-most controller ancestor
walkControler(consumer)voidDepth-first walk of all descendant controllers
Lifecycle
SignatureReturnsDescription
controlerReady(action)voidRegisters action; fires once the controller is initialized
pageReady(action)voidRegisters action; fires once the full page bootstrap is complete
Debug
SignatureReturnsDescription
log()voidLogs the section tree structure to the console
logCss()voidLogs the resolved CSS inheritance chain for this section

YojaWebApi

httpClient service

Promise-based HTTP client. Content-Type is auto-detected from the body's runtime type. Exposes interceptors and an online/offline fallback.

Requests
SignatureReturnsDescription
get(request [, callback])PromiseGET — request is { url, headers? } or a URL string
post(request, body [, callback])PromisePOST — Content-Type auto-detected from body type (JsonObject, Blob, string, …)
load(request [, callback])PromiseGET that returns the response body as text
fetch(request [, callback])PromiseRaw Fetch API passthrough
Interceptors
SignatureReturnsDescription
onRequest(callback)voidCalled before every outgoing request
onResponse(callback)voidCalled after every response is received
onFetch(callback)voidCalled for each Fetch-mode request
addContentTypes(config)voidRegister custom MIME → parse-mode mappings (jsonContentTypes, textContentTypes, blobContentTypes, base64ContentTypes)
Network state
SignatureReturnsDescription
isOffline()booleantrue when the browser has no network connection
onOnline(callback)voidFires when the network connection is restored
onOffline(callback)voidFires when the network connection is lost

storageService service

Wrappers for localStorage and sessionStorage with typed accessors and change events. Item keys are namespaced (yojaWebItemKey__) and a per-scope key index is maintained.

Values are JSON-serialized with a type tag so the type is restored on read:

Stored valueRestored as
Datea Date instance (revived from ISO)
plain object / arraythe same structure (deep JSON copy)
primitive — string / number / boolean / nullthe same primitive
undefinedstored as null

Values must be JSON-serializable — function / Map / Set / Symbol are dropped, and a Date nested inside an object is restored as its ISO string.

localStorage
SignatureReturnsDescription
hasLocalItem(key)booleanWhether the key exists (distinguishes a stored falsy value from absent)
getLocalItem(key)anyTyped value, or undefined if absent
getLocalItemKeys(query)string[]Sorted keys, optionally filtered by query
setLocalItem(key, value)voidStore a JSON-serializable value (see above)
removeLocalItem(key)voidRemove a single entry
clearLocal()voidRemove all local storage entries
sessionStorage
SignatureReturnsDescription
hasSessionItem(key)booleanWhether the key exists (distinguishes a stored falsy value from absent)
getSessionItem(key)anyTyped value, or undefined if absent
getSessionItemKeys(query)string[]Sorted keys, optionally filtered by query
setSessionItem(key, value)voidStore a JSON-serializable value (see above)
removeSessionItem(key)voidRemove a single entry
clearSession()voidRemove all session storage entries
Events
SignatureReturnsDescription
on(scope, method, key, callback)voidPer-key listener. scope: 'local'|'session'; method: 'get'|'set'|'has'|'remove'. callback(value) receives the value only.
onEvent(callback)voidGlobal listener. callback(scope, method, key, value) — positional args; method also includes 'clear' (fired as (scope, 'clear'), no key/value).

eventService service

App-wide publish/subscribe bus. Events are identified by an eventId string; queries can target one or several events.

Subscribe / publish
SignatureReturnsDescription
on(eventId, action)voidSubscribe action to eventId
trigger(query, parameter)voidPublish to all events matching query, passing parameter
remove(query)voidUnsubscribe all actions from matching events
Control
SignatureReturnsDescription
pause(query)voidPause matching events — triggers are silently dropped
activate(query)voidResume paused events
Introspection
SignatureReturnsDescription
has(query)booleanWhether at least one matching event exists
event(query)EventFirst matching event descriptor
events(query)Event[]All matching event descriptors
actions(query)Action[]All subscribed actions across matching events
count(query)numberNumber of matching events
countAction(query)numberTotal number of subscribed actions

webSocketService service

WebSocket client. webSocket(request) opens a new connection or returns the existing one for the same URL.

Connection
SignatureReturnsDescription
webSocket(request)WebSocketOpen or reuse a connection; request is { url } or a URL string
send(request, data)PromiseSend text or binary data on the connection identified by request
close(request [, code [, reason]])voidClose the connection
Handlers
SignatureReturnsDescription
onOpen(request, callback)voidFires when the socket is opened
onMessage(request, callback)voidFires on each incoming message
onClose(request, callback)voidFires when the socket is closed
onError(request, callback)voidFires on socket error
on(eventType, request, callback)voidRaw event listener for any WebSocket event type

Browser navigation and location helpers.

Navigation
SignatureReturnsDescription
load(url [, parameters])voidNavigate to url (window.location.assign), optionally merging parameters
reload()voidReload the current page
back()voidhistory.back()
forward()voidhistory.forward()
go(delta)voidhistory.go(delta)
Location accessors
SignatureReturnsDescription
path()stringCurrent window.location.pathname
fragment()stringCurrent window.location.hash
host()stringCurrent hostname
port()stringCurrent port
urlParameter()UrlParameterParsed current query string

urlParameterService service

Read and write URL query parameters with history management.

Read
SignatureReturnsDescription
has(key [, value])booleanWhether the key (optionally with value) is present
get(key)stringFirst value for the given key
getAll(key)string[]All values for the given key
keys()string[]All parameter names
entries()Entry[]All key/value entries
currentUrlParameter()UrlParameterCurrent parsed URL parameters snapshot
toUrlQuery()stringRender current params as a raw query string
getHash()stringIn-memory hash fragment (without the leading #)
currentUrlHash()stringCleaned live window.location.hash
isUpdated()booleantrue when the live URL is in sync with the in-memory params and hash (nothing left to push/replace)
state()HistoryStateCurrent history state object
Write
SignatureReturnsDescription
set(key, value)voidSet (replace) a parameter value
append(key, value)voidAdd a value without removing existing ones
remove(key [, value])voidRemove a parameter (or a specific value)
clear()voidRemove all parameters (also clears the hash)
setHash(hash)voidSet the in-memory hash fragment (leading # stripped)
removeHash()voidClear the in-memory hash fragment
push(data)voidCommit current params + hash to a new history entry (optional state data)
replace(data)voidCommit current params + hash to the current history entry (optional state data)
back()voidNative history back
forward()voidNative history forward
go(delta)voidNative history go — integer delta only
onChange(callback)voidFires with an event object on every change; return false from a before-* event to cancel it

UrlParameter type

Ordered, multi-valued query-string model returned by currentUrlParameter() and navigationService.urlParameter(). An Entry is { key: string, value: string }; empty values are normalised to null.

UrlParameter
- urlParameter : Entry[]
+ constructor(urlParameter)
+ has(key [, value]) : boolean
+ keys() : string[]
+ get(key) : string
+ getAll(key) : string[]
+ entries() : Entry[]
+ size() : number
+ toUrlQuery() : string
+ clear() : void
+ append(key, value) : void
+ set(key, value) : void
+ remove(key [, value]) : void
+ equals(other) : boolean
+ toJson() : object
+ clone() : UrlParameter
+ toString() : string

HistoryState type

Immutable snapshot stored in the History API by push() / replace() and returned by state(). Holds the user data payload, a cloned UrlParameter, the hash fragment, and the resolved URL.

HistoryState
- data : any
- urlParameter : UrlParameter
- hash : string
- url : string
+ constructor(data, urlParameter, hash, url)
+ get data() : any
+ get url() : string
+ get hash() : string
+ get urlParameter() : UrlParameter
+ clone() : HistoryState
+ toJson() : object
+ toString() : string

languageService service

Internationalisation. Loads XML translation files and retranslates the page on language change.

Language
SignatureReturnsDescription
setLanguage(language)voidSet the active language code (e.g. 'en', 'fr')
getLanguage()stringActive language code
onLanguageChange(action)voidFires when the language is changed
refresh([language])PromiseRe-translate the whole page; defaults to current language
refreshFrom(tags [, language])PromiseRe-translate a specific set of elements
onLanguageTranslate(action)voidFires after each translation pass
loadTranslator(path)voidLoad an XML translation file at path
log(path)voidLog all translation keys defined in the file at path

responsiveService service

Viewport breakpoint detection. Breakpoints are declared in YojaWeb.conf.js via initialize().

Methods
SignatureReturnsDescription
getMedia()stringName of the currently active breakpoint
getListOfMedia()string[]Names of all defined breakpoints
findMedia(mediaName)MediaDescriptionDescriptor for the named breakpoint
onMedia(callback)voidFires with the new breakpoint name on every viewport change
onResize(callback)voidFires on every window resize event
initialize(mediaDescriptions)voidConfigure breakpoints — typically called once from YojaWeb.conf.js

Module README (much more depth): yoja-web/README.md. It walks through every directive, the section lifecycle, the full service surface, and the path-resolution rules.