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
| Signature | Returns | Description |
onDocumentReady(fn) | void | Registers fn; fires once after the full document bootstrap |
onTagReady(fn) | void | Registers fn; fires each time a yw-* element is initialized |
isReady() | boolean | true once the runtime bootstrap is complete |
onError(fn) | void | Registers a global error handler |
DOM — deep (crosses shadow DOM)
| Signature | Returns | Description |
firstTag(selector [, fromTag]) | Element | null | First match; searches from fromTag or document |
findTags(selector [, fromTag]) | Element[] | All matches; searches from fromTag or document |
walkTag(consumer [, fromTag]) | void | Depth-first walk of every element |
DOM — shallow (stops at section boundaries)
| Signature | Returns | Description |
firstSectionTag(selector [, fromTag]) | Element | null | First match within section boundaries |
findSectionTags(selector [, fromTag]) | Element[] | All matches within section boundaries |
walkSectionTag(consumer [, fromTag]) | void | Depth-first walk within section boundaries |
Utilities
| Signature | Returns | Description |
path(path [, options]) | string | Normalizes path and appends ?version=…; options.force=true also adds &date=… |
version() | string | Runtime 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
| Property | Type | Description |
sectionService | SectionService | Low-level section tree-traversal service |
controlerService | ControlerService | Low-level controller tree-traversal service |
config | object | Config object exported by YojaWeb.conf.js |
window.yojaWebApi global object
Aggregator exposing every service as a single namespace.
Services
| Property | Description |
httpClient | Promise-based HTTP client with interceptors and offline fallback |
webSocketService | WebSocket lifecycle management |
storageService | localStorage / sessionStorage wrappers with change events |
eventService | Pub/sub event bus |
navigationService | Client-side navigation and history management |
urlParameterService | Read and write URL query parameters |
responsiveService | Viewport breakpoint detection |
languageService | Internationalisation (i18n) |
cssSheetService | Dynamic CSS stylesheet management |
sectionService | Section tree traversal |
controlerService | Controller 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
| Property | Type | Description |
tag | Element | Root DOM element of this section |
shadowTag | ShadowRoot | null | Shadow root of the section element, if any |
DOM — shallow (within section boundaries)
| Signature | Returns | Description |
firstTag(selector) | Element | null | First match within the section, stops at nested section boundaries |
findTags(selector) | Element[] | All matches within the section |
walkTag(consumer) | void | Depth-first walk within the section |
DOM — deep (crosses shadow DOM)
| Signature | Returns | Description |
deepFirstTag(selector) | Element | null | First match across shadow DOM boundaries |
deepFindTags(selector) | Element[] | All matches across shadow DOM boundaries |
deepWalkTag(consumer) | void | Depth-first walk across shadow DOM |
Section navigation
| Signature | Returns | Description |
firstSection(predicate) | Section | null | First descendant section matching predicate |
findSections(predicate) | Section[] | All descendant sections matching predicate |
childrenSections(predicate) | Section[] | Direct child sections only |
closestSection(predicate) | Section | null | Nearest ancestor section matching predicate |
parentSection(predicate) | Section | null | Direct parent section, optionally filtered |
parentSections(predicate) | Section[] | All ancestor sections |
rootSection() | Section | Top-most section ancestor |
walkSection(consumer) | void | Depth-first walk of all descendant sections |
Controller navigation
| Signature | Returns | Description |
firstControler(predicate) | Controler | null | First descendant controller matching predicate |
findControlers(predicate) | Controler[] | All descendant controllers |
childrenControlers(predicate) | Controler[] | Direct child controllers only |
closestControler(predicate) | Controler | null | Nearest ancestor controller |
parentControler(predicate) | Controler | null | Direct parent controller, optionally filtered |
parentControlers(predicate) | Controler[] | All ancestor controllers |
rootControler() | Controler | Top-most controller ancestor |
walkControler(consumer) | void | Depth-first walk of all descendant controllers |
Lifecycle
| Signature | Returns | Description |
controlerReady(action) | void | Registers action; fires once the controller is initialized |
pageReady(action) | void | Registers action; fires once the full page bootstrap is complete |
Debug
| Signature | Returns | Description |
log() | void | Logs the section tree structure to the console |
logCss() | void | Logs 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
| Signature | Returns | Description |
get(request [, callback]) | Promise | GET — request is { url, headers? } or a URL string |
post(request, body [, callback]) | Promise | POST — Content-Type auto-detected from body type (JsonObject, Blob, string, …) |
load(request [, callback]) | Promise | GET that returns the response body as text |
fetch(request [, callback]) | Promise | Raw Fetch API passthrough |
Interceptors
| Signature | Returns | Description |
onRequest(callback) | void | Called before every outgoing request |
onResponse(callback) | void | Called after every response is received |
onFetch(callback) | void | Called for each Fetch-mode request |
addContentTypes(config) | void | Register custom MIME → parse-mode mappings (jsonContentTypes, textContentTypes, blobContentTypes, base64ContentTypes) |
Network state
| Signature | Returns | Description |
isOffline() | boolean | true when the browser has no network connection |
onOnline(callback) | void | Fires when the network connection is restored |
onOffline(callback) | void | Fires 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 value | Restored as |
Date | a Date instance (revived from ISO) |
| plain object / array | the same structure (deep JSON copy) |
primitive — string / number / boolean / null | the same primitive |
undefined | stored 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
| Signature | Returns | Description |
hasLocalItem(key) | boolean | Whether the key exists (distinguishes a stored falsy value from absent) |
getLocalItem(key) | any | Typed value, or undefined if absent |
getLocalItemKeys(query) | string[] | Sorted keys, optionally filtered by query |
setLocalItem(key, value) | void | Store a JSON-serializable value (see above) |
removeLocalItem(key) | void | Remove a single entry |
clearLocal() | void | Remove all local storage entries |
sessionStorage
| Signature | Returns | Description |
hasSessionItem(key) | boolean | Whether the key exists (distinguishes a stored falsy value from absent) |
getSessionItem(key) | any | Typed value, or undefined if absent |
getSessionItemKeys(query) | string[] | Sorted keys, optionally filtered by query |
setSessionItem(key, value) | void | Store a JSON-serializable value (see above) |
removeSessionItem(key) | void | Remove a single entry |
clearSession() | void | Remove all session storage entries |
Events
| Signature | Returns | Description |
on(scope, method, key, callback) | void | Per-key listener. scope: 'local'|'session'; method: 'get'|'set'|'has'|'remove'. callback(value) receives the value only. |
onEvent(callback) | void | Global 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
| Signature | Returns | Description |
on(eventId, action) | void | Subscribe action to eventId |
trigger(query, parameter) | void | Publish to all events matching query, passing parameter |
remove(query) | void | Unsubscribe all actions from matching events |
Control
| Signature | Returns | Description |
pause(query) | void | Pause matching events — triggers are silently dropped |
activate(query) | void | Resume paused events |
Introspection
| Signature | Returns | Description |
has(query) | boolean | Whether at least one matching event exists |
event(query) | Event | First matching event descriptor |
events(query) | Event[] | All matching event descriptors |
actions(query) | Action[] | All subscribed actions across matching events |
count(query) | number | Number of matching events |
countAction(query) | number | Total number of subscribed actions |
webSocketService service
WebSocket client. webSocket(request) opens a new connection or returns the existing one for the same URL.
Connection
| Signature | Returns | Description |
webSocket(request) | WebSocket | Open or reuse a connection; request is { url } or a URL string |
send(request, data) | Promise | Send text or binary data on the connection identified by request |
close(request [, code [, reason]]) | void | Close the connection |
Handlers
| Signature | Returns | Description |
onOpen(request, callback) | void | Fires when the socket is opened |
onMessage(request, callback) | void | Fires on each incoming message |
onClose(request, callback) | void | Fires when the socket is closed |
onError(request, callback) | void | Fires on socket error |
on(eventType, request, callback) | void | Raw event listener for any WebSocket event type |
navigationService service
Browser navigation and location helpers.
Navigation
| Signature | Returns | Description |
load(url [, parameters]) | void | Navigate to url (window.location.assign), optionally merging parameters |
reload() | void | Reload the current page |
back() | void | history.back() |
forward() | void | history.forward() |
go(delta) | void | history.go(delta) |
Location accessors
| Signature | Returns | Description |
path() | string | Current window.location.pathname |
fragment() | string | Current window.location.hash |
host() | string | Current hostname |
port() | string | Current port |
urlParameter() | UrlParameter | Parsed current query string |
urlParameterService service
Read and write URL query parameters with history management.
Read
| Signature | Returns | Description |
has(key [, value]) | boolean | Whether the key (optionally with value) is present |
get(key) | string | First 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() | UrlParameter | Current parsed URL parameters snapshot |
toUrlQuery() | string | Render current params as a raw query string |
getHash() | string | In-memory hash fragment (without the leading #) |
currentUrlHash() | string | Cleaned live window.location.hash |
isUpdated() | boolean | true when the live URL is in sync with the in-memory params and hash (nothing left to push/replace) |
state() | HistoryState | Current history state object |
Write
| Signature | Returns | Description |
set(key, value) | void | Set (replace) a parameter value |
append(key, value) | void | Add a value without removing existing ones |
remove(key [, value]) | void | Remove a parameter (or a specific value) |
clear() | void | Remove all parameters (also clears the hash) |
setHash(hash) | void | Set the in-memory hash fragment (leading # stripped) |
removeHash() | void | Clear the in-memory hash fragment |
push(data) | void | Commit current params + hash to a new history entry (optional state data) |
replace(data) | void | Commit current params + hash to the current history entry (optional state data) |
back() | void | Native history back |
forward() | void | Native history forward |
go(delta) | void | Native history go — integer delta only |
onChange(callback) | void | Fires 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
+ 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
| Signature | Returns | Description |
setLanguage(language) | void | Set the active language code (e.g. 'en', 'fr') |
getLanguage() | string | Active language code |
onLanguageChange(action) | void | Fires when the language is changed |
refresh([language]) | Promise | Re-translate the whole page; defaults to current language |
refreshFrom(tags [, language]) | Promise | Re-translate a specific set of elements |
onLanguageTranslate(action) | void | Fires after each translation pass |
loadTranslator(path) | void | Load an XML translation file at path |
log(path) | void | Log all translation keys defined in the file at path |
responsiveService service
Viewport breakpoint detection. Breakpoints are declared in YojaWeb.conf.js via initialize().
Methods
| Signature | Returns | Description |
getMedia() | string | Name of the currently active breakpoint |
getListOfMedia() | string[] | Names of all defined breakpoints |
findMedia(mediaName) | MediaDescription | Descriptor for the named breakpoint |
onMedia(callback) | void | Fires with the new breakpoint name on every viewport change |
onResize(callback) | void | Fires on every window resize event |
initialize(mediaDescriptions) | void | Configure 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.