JavaScript DSL
JWeb provides type-safe JavaScript generation. Write JS in Java with full IDE support, compile-time checks, and no context switching.
Import Statements
// One import — Actions and Js are the same surface now: script building,
// DOM, events, async/fetch, and the high-level UI actions
import static jweb.Js.*;Two Approaches
Use Actions DSL for common UI patterns, or JS DSL for custom logic.
Actions DSL (High-Level)
Declarative builders for common interactions.
long id = 42;
// Form submission with loading state
onSubmit("login-form")
.loading("Signing in...")
.post("/api/login").withFormData()
.ok(navigateTo("/dashboard"))
.fail(showMessage("error").text("Invalid credentials"))
// Button click with confirmation
onClick("delete-btn")
.confirm("Delete this item?")
.post("/api/delete/" + id)
.ok(reload())
// Input change handler
onChange("search-input")
.then(fetch("/api/search?q=").appendVar("this.value")
.ok(setInnerHtml("results").fromVar("_data.html")))JS DSL (Low-Level)
Full control over generated JavaScript.
// Variables and values
script()
.var_("count", 0)
.let("name", str("John"))
.const_("PI", 3.14159)
// Variable references
variable("count") // count
str("hello") // 'hello'
array(1, 2, 3) // [1,2,3]
obj("a", 1, "b", 2) // {a:1,b:2}
// Functions
func("greet", "name")
.var_("msg", str("Hello, ").plus(variable("name")))
.ret(variable("msg"))
// Callbacks (anonymous functions)
callback("item")
.ret(variable("item").times(2))Core JS DSL
Fundamental JavaScript generation with type-safe methods.
DOM Access
// Get element by ID
byId("myId") // document.getElementById('myId')
$("myId") // Shorthand
// Query selectors
dom(".my-class") // document.querySelector('.my-class')
domAll(".items") // document.querySelectorAll('.items')
// Element manipulation
byId("btn")
.addClass("active")
.removeClass("disabled")
.setAttribute("data-id", "123")
.setStyle("color", "red")
// Content
byId("output").setText(v("result"))
byId("container").setHtml(v("template"))
// Visibility
byId("modal").show()
byId("tooltip").hide()Control Flow
// If/else — the body is inline, nothing closes it
func("checkAge", "age")
.if_(v("age").gte(18), return_(str("adult")))
.else_(return_(str("minor")))
// For loop
func("sum", "items")
.let("total", 0)
.forOf("item", v("items"))
.body(v("total").addAssign(v("item")))
.endFor()
.return_(v("total"))
// While loop
func("countdown", "n")
.while_(v("n").gt(0))
.body("n--", call("console.log", v("n")))
.endWhile()
// Switch
func("handleAction", "action")
.switch_(v("action"))
.case_("add").then(call("add"), "break")
.case_("remove").then(call("remove"), "break")
.default_().then(call("noop"))
.endSwitch()Array Methods
30+ fluent array operations.
long userId = 42;
// Filter and map
v("items")
.filter(callback("x").return_(v("x").gt(5)))
.map(callback("x").return_(v("x").times(2)))
.join(", ")
// Find
v("users")
.find(callback("u").return_(v("u").dot("id").eq(userId)))
// Reduce
v("numbers").reduce(
callback("acc", "n").return_(v("acc").plus(v("n"))),
0
)// Other methods
.forEach(callback) // Iterate
.some(predicate) // Any match?
.every(predicate) // All match?
.includes(value) // Contains?
.indexOf(value) // Find index
.slice(start, end) // Get slice
.concat(other) // Combine
.reverse() // Reverse
.sort(comparator) // Sort
.flat() // Flatten
.first() // First element
.last() // Last elementString Methods
20+ string operations.
v("text")
.substring(0, 10)
.toLowerCase()
.trim()
// Search
.indexOf("search")
.includes("sub")
.startsWith("prefix")
.endsWith("suffix")
// Transform
.replace("old", "new")
.replaceAll("pattern", "replacement")
.split(",")
.repeat(3)
.padStart(5, "0")
.padEnd(10, " ")Object Methods
// Get properties
v("obj").keys() // Object.keys(obj)
v("obj").values() // Object.values(obj)
v("obj").entries() // Object.entries(obj)
// Check property
v("obj").hasOwnProperty("key")
// Static methods
objectAssign(v("target"), v("source"))
objectFreeze(v("config"))
objectFromEntries(v("entries"))Operators
import static jweb.js.JSOperators.*;
// Comparison
v("a").eq(v("b")) // a === b
v("a").neq(v("b")) // a !== b
v("a").gt(5) // a > 5
v("a").gte(5) // a >= 5
v("a").lt(10) // a < 10
v("a").lte(10) // a <= 10
// Logical
v("a").and(v("b")) // a && b
v("a").or(v("b")) // a || b
v("flag").not() // !flag
// Arithmetic
v("a").plus(v("b")) // a + b
v("a").minus(5) // a - 5
v("a").times(2) // a * 2
v("a").div(10) // a / 10
v("a").mod(2) // a % 2
// Optional chaining
optionalChain(v("user"), "address", "city") // user?.address?.city
// Nullish coalescing
nullishCoalesce(v("name"), str("Anonymous")) // name ?? 'Anonymous'Actions DSL
High-level builders for common UI interactions.
Form Submission
// Basic form submit
onSubmit("contact-form")
.post("/api/contact").withFormData()
.ok(showMessage("status").text("Sent!"))
.fail(showMessage("status").text("Error"))
// With loading state
onSubmit("login-form")
.loading("Signing in...") // Disable form, show text
.post("/api/login").withFormData()
.ok(navigateTo("/dashboard"))
.fail(showMessage("error").text("Invalid credentials"))
// Reset the form after success
onSubmit("register-form")
.loading("Creating account...")
.post("/api/register").withFormData()
.ok(all(
resetForm("register-form"),
showMessage("success").text("Account created!")
))Click Handlers
long id = 42;
// Simple click
onClick("save-btn")
.post("/api/save")
.ok(showMessage("status").text("Saved!"))
// With confirmation
onClick("delete-btn")
.confirm("Are you sure you want to delete this?")
.post("/api/delete/" + id)
.ok(reload())
// Chain actions
onClick("logout-btn")
.then(all(
hide("user-menu"),
navigateTo("/login")
))UI Manipulation
// Show/hide elements
show("panel")
hide("loader")
toggle("dropdown")
// Set content
setText("label", "Updated text")
setHtml("container", "<strong>Updated</strong>")
setInnerHtml("container").fromVar("htmlVar")
// CSS classes
addClass("card", "highlighted")
removeClass("card", "disabled")
toggleClass("menu", "open")
// Multiple actions
all(
hide("loading"),
show("content"),
addClass("card", "loaded")
)Navigation
// Navigate to URL
navigateTo("/dashboard")
// Reload page
reload()
// Trigger a file download
download("/files/report.pdf")
// Smooth-scroll to element
dom("#section-id").scrollIntoView()Fetch Requests
String token = "abc123";
// GET request (fetch defaults to GET)
fetch("/api/users")
.ok(setInnerHtml("user-list").fromVar("_data.html"))
.fail(showMessage("error").text("Failed to load"))
// POST with JSON body
fetch("/api/users")
.post()
.json("{\"name\":\"John\",\"email\":\"john@example.com\"}")
.ok(call("addUserToList", "_data.user"))
// POST with form data
fetch("/api/upload")
.post()
.formData("upload-form")
.ok(showMessage("status").text("Uploaded!"))
// With headers
fetch("/api/protected")
.header("Authorization", "Bearer " + token)
.ok(setTextFromResponse("data", "value"))Modals & Dialogs
long id = 42;
// Show modal
showModal("confirm-dialog")
hideModal("confirm-dialog")
// Confirmation dialog
confirmDialog("modal")
.message("Delete this item?")
.danger()
.onConfirm(fetch("/api/delete/" + id).post().ok(reload()))
.onCancel(log("Cancelled"))
// Alert modal (modal id + body element ref)
alertModal("modal", "modal-body")
.success("Operation completed!")
alertModal("modal", "modal-body")
.error("Something went wrong")Messages & Toasts
// Show message in element
showMessage("status-div")
.text("Success!")
// From response
showMessage("result")
.fromResponse("message")
// Styled variants
showMessage("notification")
.success("Saved!")
showMessage("notification")
.error("Failed to save")Conditional Actions
// Based on condition
when("isLoggedIn")
.then(navigateTo("/dashboard"))
.otherwise(navigateTo("/login"))
// Based on response
whenResponse("success")
.then(showMessage("status").text("Done!"))
.otherwise(showMessage("error").text("Failed"))
// Based on variable (builds a JS ternary expression)
String targetUrl = whenVar("status")
.equals("approved").thenUrl("/success")
.elseUrl("/pending");
assign("nextUrl", targetUrl)Behaviors
A behavior is a chunk of client work that installs itself once and keeps running — a copy button, a scroll spy, a draggable split. Each is an Action, so it attaches to an element or goes on the page through actions().does(...). The implementations live in the client runtime, so the generated code is one call.
Copy to Clipboard
navigator.clipboard with the execCommand fallback built in, so it also works on http origins and in browsers that never got the async API.
// A literal
button(onClick(copy("npm i jweb").feedback("Copied!")), "Copy")
// The nearest matching element's text: the trigger's own subtree,
// then each ancestor's, then the document
button(class_("code-copy-btn"),
onClick(copyFrom("pre")
.feedback("Copied!", 1600) // label swap, then back
.feedbackClass("copied") // class while it shows
.failText("Copy failed")),
"Copy")
// Delegated — the buttons are re-rendered, the listener is not
delegate(".docs-layout", "click", ".code-copy-btn").handler(
callback("e", "t").does(
copyFrom("pre").trigger(v("t")).feedback("Copied!")))Client-Side Navigation
navigate(url) is the runtime's swap as an action. Links marked up with swap(...)/swapPush(...) need no script at all — prefetch(...) and activeLink(...) are the two things markup cannot say.
// Markup: fetch the fragment, fill the target, push the URL
a(href("/docs?section=state"),
swap("/docs/content?section=state", ".docs-content"),
swapPush("/docs?section=state"),
class_("docs-nav-link"),
"State")
// Script: warm the cache on hover, keep the current link marked
inlineScript(actions().does(
prefetch(".docs-nav-link").within(".docs-sidebar").delay(50).cache(300_000),
activeLink(".docs-nav-link")
).build())
// Or navigate from any handler
button(onClick(navigate("/docs/content?section=state")
.target(".docs-content")
.push("/docs?section=state")
.cache(300_000)),
"State")
// prefetch() and navigate().cache(ttl) share one store, so a link
// warmed on hover swaps without a round trip. onVisible() prefetches
// when the link scrolls into view instead.Scroll Spy
Builds a link per heading, marks the one you are reading, and rebuilds itself whenever the content is swapped.
inlineScript(actions().does(
scrollSpy("#toc", "h2, h3")
.within(".docs-content") // the scrolling element
.linkClass("toc-link") // class on each generated link
.activeClass("active") // class on the one in view
.hasHeadingsClass("has-headings") // on the nav's parent, while any exist
.scrollMargin(24) // space above a clicked heading
).build())
// Each link carries data-level (2 for h2, 3 for h3) and data-index,
// so indentation and size are CSS:
// .toc-link[data-level="3"] { padding-left: 1.5rem }
// Without .offset(px) the activation line sits 25% down the visible
// area, capped at 160px; at the very bottom the last heading wins.Editors
inlineScript(actions().does(
// Wrap-accurate line numbers: line 7 in the gutter is line 7 in
// the error message. Re-measures on every width change.
lineGutter("#editor", "#lines").mirror("#mirror").errorClass("errline"),
// A draggable divider; flex: 0 0 <pct>% on the left pane
splitPane("#gutter", "#code")
.container("#split").minPercent(20).maxPercent(80).persist("split"),
// A textarea that grows with what is typed into it
resizeToContent("textarea.grow")
).build())
// After replacing the text in code, or to mark a compiler error:
relineGutter("#editor")
markLine("#editor", v("lineNumber")) // 0 clears the mark
insertText("#editor", " ") // at the caret, undo intactReading Server State on the Client
The runtime keeps the client's copy of every server State. syncState reads it as an expression; onStateChange runs when it changes.
State<Integer> count = useState(0);
// An expression — JWeb.getState('<id>')
button(onClick(setText("total", syncState(count).plus(1))), "+1")
// A listener — the callback takes the new value and the old one
inlineScript(actions().does(
onStateChange(count, callback("now", "before")
.log("count went from", v("before"), "to", v("now")))
).build())Web Components
inlineScript(actions().does(
customElement("user-card")
.observedAttributes("name")
.shadow() // or .shadow("closed")
.template(div(class_("card"), slot()))
.connected(callback().log("mounted"))
.disconnected(callback().log("gone"))
.attributeChanged(callback("name", "oldValue", "newValue")
.does(dom(".card").setText(v("newValue"))))
).build())
// Shadow DOM and slots as expressions
attachShadow(v("host")) // host.attachShadow({mode:'open'})
shadowRoot(v("host"))
assignSlot(v("child"), "footer") // child.slot = 'footer'
assignedElements(v("slot"))
onSlotChange(v("slot"), callback("e").log("slot changed"))
// The definition is guarded: re-running it is a no-op, not the
// NotSupportedError a second define of the same name throws.Progressive Web App
import jweb.js.Manifest;
import static jweb.js.Pwa.*;
static final Manifest APP = manifest("JWeb Demo")
.shortName("JWeb").description("The Java web framework")
.display("standalone").startUrl("/").scope("/")
.themeColor("#4f46e5").background("#ffffff")
.icon("/icon-192.png", "192x192")
.icon("/icon-512.png", "512x512");
// Routes — publishes /manifest.webmanifest
serve(app, APP);
// Page head
head(title("JWeb"), link())
// Page script
inlineScript(actions().does(registerServiceWorker("/sw.js")).build())Event Handling
Advanced event patterns for interactive UIs.
Event Delegation
Efficient handling for dynamic lists.
// Delegate clicks within a container
delegate("todo-list", "click", "li")
.handler(callback("e", "target")
.call("toggleTodo", v("target").dot("dataset").dot("id"))
)
// Works for dynamically added items
// Single listener on parent, not one per childDebouncing
Delay execution until user stops typing/scrolling.
// debounce(ms, handler) is a self-contained function expression -
// no timer variable to name, and it attaches like any listener
byId("search-input").addEventListener("input",
debounce(300, callback("e").call("runSearch"))
)Throttling
Limit execution frequency.
// Runs at most every 100ms
byId("feed").addEventListener("scroll",
throttle(100, callback("e").call("updateScrollPosition"))
)Keyboard Events
// Key combinations - onKeyCombo prevents the browser default for you
onKeyCombo("ctrl+s", callback("e").call("save"))
onKeyCombo("ctrl+shift+p", callback("e")
.call("openCommandPalette")
)
// Single keys
onEscape(callback().call("closeModal"))
onEnter(callback().call("submit"))
onKey("Delete", callback().call("deleteSelected"))
// Arrow navigation
onKey("ArrowLeft", callback().call("prevItem"))
onKey("ArrowRight", callback().call("nextItem"))Touch & Swipe
// Swipe gestures
swipe(v("carousel"))
.threshold(100) // Minimum distance in pixels
.onLeft(callback().call("nextSlide"))
.onRight(callback().call("prevSlide"))
.onUp(callback().call("openFullscreen"))
.onDown(callback().call("closeFullscreen"))
.build()
// Touch events
onTouchStart(byId("canvas"), callback("e")
.let("touch", firstTouch(v("e")))
.call("startDrag", v("touch"))
)Server-Sent Events (SSE)
Real-time server push.
// Connect to SSE endpoint
sse("/api/notifications")
.onMessage(callback("e")
.let("data", JSJson.parse(v("e").dot("data")))
.call("showNotification", v("data"))
)
.onError(callback()
.log("SSE connection error")
)
.build()
// Named server events - SseEvent.of("notification", data)
sse("/api/events")
.onOpen(callback().log("Connected"))
.on("notification", callback("e")
.call("showNotification", v("e").dot("data")))
.build()Custom Events
long itemId = 42;
String itemName = "Widget";
// Create and dispatch custom event (target element first)
dispatchCustomEvent(
byId("item-list"),
"item-selected",
obj("id", itemId, "name", itemName)
)
// Listen for custom event
onCustomEvent(byId("item-list"), "item-selected", callback("e")
.call("handleSelection", eventDetail(v("e")))
)Event Utilities
// Inside any callback, the event is named e - these are statements
// on it, so they go straight into does(...)
callback("e").does(preventDefault(), call("save"))
callback("e").does(stopPropagation())
// Or, on an event you hold as an expression
preventDefault(v("someEvent"))
stopPropagation(v("someEvent"))
// Once - remove after first call
once(byId("button"), "click", callback()
.call("initializeOnce")
)
// Remove listener
byId("button").removeEventListener("click", v("handler"))Async/Await
Build asynchronous JavaScript with type-safe async functions.
import static jweb.Js.*;
Action processData() { return log("Data loaded"); }
// Simple await
await(fetch("/api/data").ok(processData()))
// Async function
asyncFunc("loadDashboard")
.does(
assign("isLoading", "true"),
await(fetch("/api/user").ok(assign("user", "_data"))),
await(fetch("/api/stats").ok(assign("stats", "_data"))),
assign("isLoading", "false"),
call("renderDashboard")
)Try-Catch-Finally
Handle errors in async operations.
Action processData() { return log("Data loaded"); }
asyncTry(
await(fetch("/api/data").ok(processData()))
)
.catch_(all(
log("Load failed"),
showMessage("status").error("Failed to load")
))
.finally_(all(
hide("loading"),
assign("isLoading", "false")
))Promise.all
Execute multiple async operations in parallel.
// Parallel requests
promiseAll(
fetch("/api/users").ok(assign("users", "_data")),
fetch("/api/posts").ok(assign("posts", "_data")),
fetch("/api/comments").ok(assign("comments", "_data"))
)
// With error handling
asyncFunc("loadAll")
.does(
show("loading"),
asyncTry(
promiseAll(
fetch("/api/a").ok(assign("a", "_data")),
fetch("/api/b").ok(assign("b", "_data"))
),
call("renderAll")
)
.catch_(showMessage("error").error("Load failed"))
.finally_(hide("loading"))
)Sleep/Delay
Add delays for debouncing or animations.
// Simple delay
sleep(1000) // Wait 1 second
// Delay in sequence
asyncFunc("showWithDelay")
.does(
addClass("element", "fade-in"),
sleep(300),
setText("status", "Ready!")
)
// Debounced search - if_(condition, return_()) is the early-return
// guard, and it is a statement like any other
asyncFunc("search")
.params("query")
.does(
assign("searchQuery", "query"),
sleep(300),
if_(v("searchQuery").neq(v("query")), return_()),
await(fetch("/api/search?q=").appendVar("query")
.ok(call("showResults", "_data")))
)Browser APIs
Type-safe access to browser APIs.
Storage
import static jweb.js.JSStorage.*;
// localStorage
local().set("token", "abc123")
local().get("token")
local().remove("token")
local().clear()
// JSON storage
local().setJson("user", obj("name", "John", "id", 123))
local().getJson("user")
local().getJsonOr("user", obj()) // With fallback
// sessionStorage
session().set("temp", "value")
session().get("temp")
// Cross-tab communication
onStorageChange(callback("e")
.if_(v("e").dot("key").eq("theme"),
call("updateTheme", v("e").dot("newValue")))
)WebSocket
import static jweb.js.JSWebSocket.*;
// Create connection
webSocket("/ws/chat")
.onOpen(callback().log("Connected"))
.onMessage(callback("e")
.call("handleMessage", v("e").dot("data"))
)
.onClose(callback().log("Disconnected"))
.onError(callback("e").log(v("e")))
.autoReconnect(3000) // Auto-reconnect every 3s
.build("ws")
// Send messages
send(v("ws"), v("message"))
sendJson(v("ws"), obj("type", "chat", "text", v("msg")))
// Close
close(v("ws"))Clipboard
import static jweb.js.JSClipboard.*;
// Write to clipboard
copyText("Copied text")
// Copy an element's text or value
copyElementText("code-snippet")
copyElementValue("share-url")
// Read (returns promise)
readText().then(callback("text").log(v("text")))Notifications
import static jweb.js.JSNotification.*;
// Check permission
permission()
hasPermission()
// Request permission
requestPermission()
// Show notification
notification("New Message")
.body("You have a new message")
.icon("/icon.png")
.build()Geolocation
import static jweb.js.JSGeolocation.*;
// Get current position
getCurrentPosition()
.onSuccess(callback("pos")
.let("lat", v("pos").dot("coords").dot("latitude"))
.let("lng", v("pos").dot("coords").dot("longitude"))
.call("showOnMap", v("lat"), v("lng"))
)
.onError(callback("err").log(v("err")))
.build()
// Watch position (continuous updates)
watchPosition()
.onSuccess(callback("pos")
.call("updatePosition", v("pos").dot("coords"))
)
.build("watchId")Web Share
import static jweb.js.JSShare.*;
// Share content
share()
.title("Check this out")
.text("Interesting article")
.url("/article/42")
.build()
// Quick URL share
shareUrl("/article/42")
// Check if sharing is supported
canShare() // boolean Val for use in conditionsFullscreen
import static jweb.js.JSFullscreen.*;
// Enter fullscreen
requestFullscreen(v("videoElem"))
// Exit fullscreen
exitFullscreen()
// Check state
isFullscreen()Page Visibility
import static jweb.js.JSVisibility.*;
// Check if visible
isVisible()
isHidden()
// Handle visibility change
onVisibilityChange(callback()
.if_(isVisible())
.then(call("resumeVideo"))
.else_(call("pauseVideo"))
)
// Or use the dedicated helpers
onVisible(callback().call("resumeVideo"))
onHidden(callback().call("pauseVideo"))Observers
import static jweb.js.JSObservers.*;
// IntersectionObserver (lazy loading, infinite scroll)
intersection()
.onIntersect(callback("entries")
.call("lazyLoadVisible", v("entries"))
)
.threshold(0.5)
.observe("hero-img", "heroObserver")
// ResizeObserver
resize()
.onResize(callback("entries")
.call("handleResize", v("entries"))
)
.observe("container", "containerObserver")
// MutationObserver
mutation()
.onMutate(callback("mutations")
.call("handleDOMChanges", v("mutations"))
)
.childList()
.subtree()
.observe("content", "contentObserver")Advanced JavaScript
Powerful APIs for complex applications.
Promise Utilities
import static jweb.js.JSPromise.*;
Val promise1 = v("promise1");
Val promise2 = v("promise2");
Val promises = v("promises");
// Promise combinators (qualified — Async on the Js chain already has these names)
JSPromise.promiseAll(
fetch(str("/api/users")).get().toVal(),
fetch(str("/api/posts")).get().toVal()
).then(callback("results").call("renderAll", v("results")))
JSPromise.promiseRace(promise1, promise2) // First to finish
JSPromise.promiseAny(promise1, promise2) // First successful
JSPromise.promiseAllSettled(promises) // All, regardless of success
// Retry with exponential backoff
retry(v("apiCall"), 3) // Max 3 attempts
.delay(1000) // 1s initial delay
.exponentialBackoff() // Double delay each retry
.onRetry(callback("attempt").log("Retry:", v("attempt")))
.shouldRetry(callback("err").return_(v("err").dot("status").eq(503)))
.build()
// Timeout
timeout(fetch(str("/api/slow")).get().toVal(), 3000)
.errorMessage("Request timed out")
.build()
// Cancellable promise
cancellable(fetch(str("/api/data")).get().toVal())
.controller("abortCtrl")
.timeout(5000)
.build("result")Web Workers
import static jweb.js.JSWorker.*;
Val bigData = v("bigData");
// Create dedicated worker
worker("/worker.js")
.onMessage(callback("e").call("handleResult", v("e").dot("data")))
.onError(callback("err").log(v("err")))
.build("worker")
// Send data to worker
postMessage(v("worker"), obj("task", "compute", "data", bigData))
// Terminate worker
terminate(v("worker"))
// SharedWorker (multiple tabs)
sharedWorker("/shared-worker.js").build("sw")Service Workers
import static jweb.js.JSServiceWorker.*;
// Register service worker (qualified — Css.register(...) shares the name)
JSServiceWorker.register("/sw.js")
.onSuccess(callback("reg").log("SW registered"))
.onError(callback("err").log(v("err")))
.build()
// Check for updates
update(v("registration"))
// Unregister
unregister(v("registration"))Web Crypto
import static jweb.js.JSCrypto.*;
// Random values
randomUUID() // UUID v4
randomBytes(16) // Random bytes
// Hashing
sha256(v("data")) // Returns promise
digest().sha512().data(v("data")).build()
// Encryption (AES-GCM)
encrypt().aesGcm(v("key"), v("iv"))
.data(v("data"))
.build()
decrypt().aesGcm(v("key"), v("iv"))
.data(v("encrypted"))
.build()
// Generate key
generateKey().aesGcm(256).build()Canvas 2D
import static jweb.js.JSCanvas.*;
int width = 800;
int height = 600;
// Get context
getContext2D(v("canvas"))
// Drawing
fillRect(v("ctx"), 0, 0, 100, 100)
strokeRect(v("ctx"), 10, 10, 80, 80)
clearRect(v("ctx"), 0, 0, width, height)
// Text
fillText(v("ctx"), "Hello", 50, 50)
strokeText(v("ctx"), "World", 50, 80)
// Path
beginPath(v("ctx"))
moveTo(v("ctx"), 0, 0)
lineTo(v("ctx"), 100, 100)
arc(v("ctx"), 50, 50, 30, 0, Math.PI * 2)
stroke(v("ctx"))
fill(v("ctx"))
// Image
drawImage(v("ctx"), v("img"), 0, 0)Performance API
import static jweb.js.JSPerformance.*;
// High-resolution timing
now()
// User timing marks
mark("start")
// ... operation ...
mark("end")
measure("operation", "start", "end")
// Read recorded entries
getEntriesByName("operation")
clearMarks()
clearMeasures()JSON & Data
import static jweb.js.JSJson.*;
// Parse/stringify
parse(v("jsonString"))
stringify(v("obj"))
stringify(v("obj"), 2) // pretty-printed
safeParse(v("jsonString"), obj()) // with fallback
// FormData
import static jweb.js.JSFormData.*;
formData("checkout-form") // from a form element
formData() // empty FormData
append(v("fd"), "key", str("value"))
// URL (url(...) qualified — Css.url(...) shares the name)
import static jweb.js.JSUrl.*;
JSUrl.url("/path?q=search")
currentUrl()
JSUrl.pathname(JSUrl.url("/path?q=search"))Internationalization
import static jweb.js.JSIntl.*;
// Number formatting
formatCurrency(v("amount"), "USD", "en-US")
// Output: $1,234.56
formatNumber(v("num"), "en-US")
formatPercent(v("ratio"), "en-US")
formatCompact(v("big"), "en-US") // 1.2M
// Date formatting
formatDateTime(v("date"), "en-US", "long", "short")
// Output: January 21, 2026 at 3:30 PM
// Relative time
formatRelativeTime(-1, "day", "en") // "yesterday"New Browser APIs
Additional browser APIs for offline storage, navigation, drag-and-drop, pointer input, and speech.
IndexedDB
Client-side database for structured offline storage.
import static jweb.js.JSIndexedDB.*;
// Open a database (creates if not exists). onUpgrade takes a lambda:
// the database is the parameter, the store work is the return value.
openDB("myApp", 1)
.onUpgrade(db -> createStore(db, "users")
.keyPath("id").autoIncrement()
.index("email", "email")
.uniqueIndex("username", "username")
.build())
.onSuccess(callback("db").log(str("DB opened")))
.build()
// Transaction: add and read data
transaction(v("db"), "users", "readwrite")
.put(obj("id", 1, "name", str("John")))
.getAll()
.onComplete(callback("results").log(v("results")))
.build()
// Cursor iteration with key range
cursorQuery(v("db"), "users")
.index("email")
.bound("a", "m")
.onEach(callback("cursor")
.log(v("cursor").dot("value")))
.build()
// Delete a database
deleteDB("myApp")History API
Browser navigation control for SPA routing patterns.
import static jweb.js.JSHistory.*;
// pushState is qualified below — Js already has pushState(String) and
// most of the rest of the History API through the same ancestry
JSHistory.pushState(obj("page", str("dashboard")), "/dashboard")
JSHistory.pushState("/users/42") // URL-only
// Replace current entry (no back button)
replaceState("/settings")
// Go back/forward
back()
forward()
go(-2) // Go back 2 steps
// Listen for back/forward navigation (qualified — Js already has onPopState(Func))
JSHistory.onPopState(callback("e")
.log(v("e").dot("state")))
// Navigation guard (prompt before leaving)
navigationGuard("You have unsaved changes. Leave?")
// Query parameter helpers (qualified — JSUrl has the same name)
JSHistory.getQueryParam("page") // Read ?page=...
setQueryParam("sort", "name") // Update URL param
removeQueryParam("filter") // Remove param
queryParamsObject() // All params as objectDrag and Drop
Native HTML5 drag-and-drop with builder pattern.
import static jweb.js.JSDragDrop.*;
// Make an element draggable
draggable(byId("card-1"))
.data("text/plain", "Card 1 data")
.effectAllowed("move")
.onDragStart(callback("e").log(str("dragging")))
.onDragEnd(callback("e").log(str("done")))
.build()
// Create a drop zone
dropZone(byId("drop-area"))
.dropEffect("move")
.onDrop(callback("e")
.log(getData(v("e"), "text/plain")))
.onDragEnter(callback("e")
.log(str("entered drop zone")))
.build()
// DataTransfer helpers
getData(v("e"), "text/plain")
getFiles(v("e"))
getTypes(v("e"))Pointer Events
Unified mouse, touch, and pen input handling.
import static jweb.js.JSPointer.*;
// Listen for pointer events
onPointerDown(byId("canvas"), callback("e")
.log(pointerId(v("e"))))
onPointerMove(byId("canvas"), callback("e")
.log(pressure(v("e"))))
onPointerUp(byId("canvas"), callback("e")
.log(str("released")))
// Capture pointer (receive events outside element)
setPointerCapture(byId("slider"), v("e").dot("pointerId"))
releasePointerCapture(byId("slider"), v("e").dot("pointerId"))
// Access pointer properties
pointerId(v("e")) // Unique pointer ID
pointerType(v("e")) // "mouse", "touch", "pen"
pressure(v("e")) // 0.0 to 1.0
tiltX(v("e")) // Pen tilt angle
isPrimary(v("e")) // Is primary pointer?
// Multi-pointer tracking (pinch, multi-touch)
multiPointerTracker(byId("canvas"))
.onMove(callback("pointers")
.log(v("pointers").dot("size")))
.build()Web Speech
Text-to-speech and speech recognition APIs.
import static jweb.js.JSSpeech.*;
// Text-to-Speech (simple)
speak("Hello, welcome to JWeb!")
// Text-to-Speech (with options)
speakBuilder("Hello, welcome to JWeb!")
.lang("en-US")
.rate(1.0).pitch(1.0).volume(0.8)
.onEnd(callback("e").log(str("Done speaking")))
.build()
// Control speech
pauseSpeech()
resumeSpeech()
cancelSpeech()
// Speech Recognition (speech-to-text)
recognizer()
.lang("en-US")
.continuous(true)
.interimResults(true)
.onResult(callback("e")
.log(transcript(v("e"))))
.onError(callback("e")
.log(v("e").dot("error")))
.build("recognizer")
// Control recognition
startRecognition(v("recognizer"))
stopRecognition(v("recognizer"))
// Result helpers
transcript(v("e")) // Get text result
confidence(v("e")) // Confidence score
isFinal(v("e")) // Is result final?