v0.5.7 beta
Back-end UI Framework

for feature-rich, secure, and fast web apps in Go

Docs

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, or Clear it. When rendering occurs, only the latest state will be applied.
  • If door is replaced or removed before rendering, it stays detached even after rendering (acts like a static component)
  • If the door is untouched, updated, or cleared 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
    }
  })
}