JavaScript
For on-page interactivity, extended event handling, and integration with frontend frameworks.
$d
Reference
The magic $d
variable provides access to framework features:
$d.data(name: string): any
Retrieve data provided viadoors.AData
.$d.hook(name: string, arg: any): Promise<any>
Call a Go hook defined bydoors.AHook
, serialize/deserialize input/output.$d.rawHook(name: string, arg: any): Promise<Response>
Call a Go hook (doors.AHook
ordoors.ARawHook
) and get the rawResponse
.$d.HookErr
Reference to the error class thrown by hooks. You can check errors with
if (err instanceof $d.HookErr) { … }
.$d.on(name: string, handler: (arg: any, err?: $d.HookErr) => any): void
Register a handler callable from Go withActionEmit
orCall
.
(No async support in the handler itself.)$d.G: { [key: string]: any }
Persistent global object to share state between scripts.$d.clean(handler: () => void | Promise<void>): void
Register a cleanup function to run when the script is removed.
Script Component
The Script Component integrates inline scripts with framework features:
- Provides the magic
$d
variable - Converts inline scripts into loadable, cacheable resources
- Minifies by default
- Compiles TypeScript (when
type="application/typescript"
or"text/typescript"
) - Wraps code in an async IIFE, so you can safely
await
and avoid polluting global scope
@doors.Script() {
<script>
console.log("hello world!")
</script>
}
⚠️
type="module"
is not supported indoors.Script()
.
For ES modules, use Imports (see ref/imports article) and load viaawait import("specifier")
.
Variants
doors.Script()
— public, cacheable script resource
doors.ScriptPrivate()
— session-protected, cached script resource
doors.ScriptDisposable()
— session-protected, not cached, avoids leaks with dynamic scripts
Pass Data To JavaScript
Use doors.AData to expose server-side values:
type AData struct {
// Name of the data entry, accessed in JS via $d.data(name).
// Required.
Name string
// Value to expose. Marshaled to JSON.
// Required.
Value any
}
Example:
@doors.Script() {
@doors.AData{
Name: "user_profile",
Value: user, // any Go struct
}
<script>
const user = $d.data("user_profile") // parsed JSON
console.log(user.name)
</script>
}
Call Go from JavaScript
Structured Hook
doors.AHook
handles Go hooks with automatic JSON (un)marshaling:
type AHook[T any] struct {
// Name of the hook to call from JavaScript via $d.hook(name, ...).
// Required.
Name string
// Defines how the hook is scheduled (e.g. blocking, debounce).
// Optional.
Scope []Scope
// Visual indicators while the hook is running.
// Optional.
Indicator []Indicator
// Backend handler for the hook.
// Receives typed input (T, unmarshaled from JSON) through RHook,
// and returns any output which will be marshaled to JSON.
// Should return true when the hook is complete and can be removed.
// Required.
On func(ctx context.Context, r RHook[T]) (any, bool)
}
Example
@doors.AHook[string]{
Name: "length",
On: func(ctx context.Context, r doors.RHook[string]) (any, bool) {
return len(r.Data()), false
},
}
@doors.Script() {
<script>
const length = await $d.hook("length", "hello!")
console.log(length) // 6
</script>
}
⚠️ On errors, hooks throw
$d.HookErr
. Handle exceptions explicitly.
Raw Hook
doors.ARawHook
provides low-level access to the HTTP request:
type ARawHook struct {
// Hook name, called with $d.rawHook(name, arg).
// Required.
Name string
// Backend handler with direct access to body/form.
// Required.
On func(ctx context.Context, r RRawHook) bool
// Optional scope control.
Scope []Scope
// Optional visual indicators.
Indicator []Indicator
}
Data Conversion Rules
When calling $d.hook
or $d.rawHook
, the arg
is converted to the request body:
undefined
→ no body
FormData
→ multipart body
URLSearchParams
→ form-urlencoded body
Blob
→ raw blob withContent-Type
File
/ReadableStream
→ octet-stream
- Any other value → JSON
ℹ️
$d.rawHook
(JS) is not related todoors.ARawHook
(Go).
Call JavaScript from Go
You can invoke JS handlers registered with $d.on
.
⚠️ Handlers must be synchronous. For long tasks, use hooks to report back.
Register a Handler
@doors.Script() {
<script>
$d.on("alert", (msg) => {
alert(msg)
return true
})
</script>
}
Invoke a Handler
From Go, use ActionEmit
or the Call API:
// Fire-and-forget
doors.Call(ctx, ActionEmit{Name: "alert", Arg: "Hello!"})
// Await a return value (best-effort cancelation)
ch, cancel := doors.XCall[string](ctx, ActionEmit{Name: "alert", Arg: "Hello!"})
defer cancel()
res := <-ch
if res.Err == nil {
log.Println("Handler returned:", res.Ok)
}
Call Lookup Rules
Handler resolution is scoped to the closest dynamic parent (Door).
When invoking:
- A hook can only call handlers in its parent door or above
- Inner handlers shadow outer ones if names collide
Clean Up
Use $d.clean
for cleanup when a script is removed:
<script>
const ctrl = new AbortController()
$d.clean(() => ctrl.abort())
// start background process
</script>
HookErr
Reference
Hook errors thrown in JS are instances of $d.HookErr
(subclass of Error
). If you need
Error reasons:
- Canceled by scope (suppressed in event hooks)
- Not Found - hook expired, 404 code (suppressed in event hooks)
- Unauthorized - cause page reload (401 and 410 codes)
- Bad Request - not expected to occur (400 code)
- Other - other 4XX code (not used by framework, if you issued)
- **Network Error **
- Server Error (5XX code)
- Capture Error - not expected to occur (means js side event handling issue)
Fields
status: number | undefined
— HTTP status code (if available)
cause: Error | undefined
— original error object
kind: "canceled" | "unauthorized" | "not_found" | "network" | "server" | "bad_request" | "capture"
message: string
`
Methods
Convenience type checks:
canceled(): bool
unauthorized(): bool
notFound(): bool
network(): bool
server(): bool
badRequest(): bool
capture(): bool
isOther(): bool