Door
doors.Door
controls a dynamic container in the DOM tree that can be updated, replaced, or removed at runtime. It is a fundamental building block of doors framework, enabling reactive HTML updates without a virtual DOM.
By default doors.Door
does not affect layout (custom element with display: contents). However, some HTML tags expect only specific children (<table>
, for example). So, you can use any tag as door by providing Tag
field value:
door = doors.Door{
Tag: "tr", // now it can be used inside <table> and <tbody>
A: [string]any{"class":"row"}, // attributes (id will be overwritten)
}
✅ Specify the tag only if necessary Specify the tag only if necessary
Lifecycle
doors.Door
object always remains alive. However, it can be attached to or detached from the DOM.- If door is detached, you can still
Update
,Remove
,Replace
, orClear
it. When rendering occurs, only the latest state will be applied. - If door is
replaced
orremoved
before rendering, it stays detached even after rendering (acts like a static component) - If the door is untouched,
updated
, orcleared
before the render, it becomes attached after. - After the door becomes attached, calling its methods will affect the DOM.
Remove, Replace
methods make the door detached again. - If the door is rendered a second time while still being attached, the previous container will be completely removed from the DOM.
API
Update
Update
changes the content inside the container.
func (c *MyComponent) handleClick(ctx context.Context) {
c.contentDoor.Update(ctx, c.newContent())
}
templ (c *MyComponent) newContent() {
<p>Updated content at { time.Now().Format("15:04:05") }</p>
}
Replace
Replace
replaces the container with static content. The door behaves like a static component after replacement (until Update
or Clear
) and loses control over its initial DOM position (becomes detached).
c.contentDoor.Replace(ctx, c.staticContent())
Clear
Clear
empties the Doors’ container. Equivalent to calling Update(ctx, nil)
c.contentDoor.Clear(ctx)
Remove
Remove
removes the Door container with content from the DOM. Equivalent to calling Replace(ctx, nil)
c.contentDoor.Remove(ctx)
Rendering With Children
c.contentDoor {
@content()
}
is equivalent to
{{
c.contentDoor.Update(ctx, content())
}}
@c.contentDoor
Extra API
XReload(ctx context.Context) <-chan error
XUpdate(ctx context.Context, content templ.Component) <-chan error
XReplace(ctx context.Context, content templ.Component) <-chan error
XRemove(ctx context.Context) <-chan error
XClear(ctx context.Context) <-chan error
Do the same, but return a channel that can be used to track operation progress.
If the channel closes without a value, it means the
Door
is detached.If the channel returns nil, it means the frontend successfully applied the DOM change.
If a channel returns a non-nil value (error), it indicates that an error occurred during the operation (for example, if the operation was overwritten by a new one before it was applied).
It’s not recommended to use extended APIs outside a blocking-allowed context, because waiting on a channel in the render runtime environment can cause a deadlock. Use inside @doors.Go(func(context.Context)) (gorourine independent from the render runtime)
Example
type noticeFragment struct {
msg doors.Door
}
templ (n *noticeFragment) Render() {
<div>
@n.msg {
Press the button!
}
@n.onclick()
<button>Show message</button>
</div>
}
func (n *noticeFragment) onclick() doors.Attr {
return doors.AClick{
On: func(ctx context.Context, _ doors.REvent[doors.PointerEvent]) bool {
n.msg.Update(ctx, doors.Text("Hello there"))
// n.msg.Remove(ctx)
// n.msg.Replace(ctx, doors.Text("Hello there"))
return false
},
}
}
templ content() {
// spawn goroutine with blocking-allowed context
@doors.Go(func (context.Context) {
for {
err, ok := <- door.XUpdate(ctx, content())
if !ok {
break
}
if err != nil {
break
}
// do something
}
})
}