LocationEncoder

doors.LocationEncoder is a one-method interface for custom navigation models:

type LocationEncoder interface {
	Encode() (doors.Location, error)
}

Implement it on a domain type when you have one-off URL shapes that don't justify a full path-model struct, or when an existing type already knows its own URL form. It pairs naturally with RouteDerive on Location for the matching side — together they round-trip a custom value through the URL.

type CustomRoute struct {
	ID  string
	Tab string
}

func (r CustomRoute) Encode() (doors.Location, error) {
	q := url.Values{}
	if r.Tab != "" {
		q.Set("tab", r.Tab)
	}
	return doors.Location{
		Segments: []string{"custom", r.ID},
		Query:    q,
	}, nil
}

CustomRoute{ID: "a", Tab: "info"} now works wherever a model is accepted.

Fragment

Use Fragment to append #... to the generated URL:

<>
	~>doors.ALink{
		Model: Path{Section: SectionDocs},
		Fragment: "api",
	} <a>API</a>
</>

Active

Use Active when a link should reflect the current location.

<>
	~>doors.ALink{
		Model: Path{
			Section: SectionDashboard,
			ID:      f.id,
		},
		Active: doors.Active{
			Indicator: doors.IndicateAttr("aria-current", "page"),
		},
	} <a>Current page</a>
</>

When the current location matches, Doors applies the indicator to the link element.

If Active.Indicator is empty, there is no active-link behavior.

Path

Active.PathMatcher controls how the path is compared.

  • doors.PathMatcherFull() — full path (default)
  • doors.PathMatcherStarts() — current path starts with the link path
  • doors.PathMatcherSegments(i...) — only listed segment indexes (zero-based)

Query

Active.QueryMatcher controls query-string matching. The matchers are applied in order, then Doors compares any remaining query parameters.

  • doors.QueryMatcherIgnoreSome(params...) — drop the listed keys from comparison
  • doors.QueryMatcherIgnoreAll() — drop all remaining keys
  • doors.QueryMatcherSome(params...) — compare only the listed keys at this step
  • doors.QueryMatcherIfPresent(params...) — compare the listed keys only when present

Chain matchers with .And(...) when several steps are needed:

Active: doors.Active{
	QueryMatcher: doors.QueryMatcherSome("mode").And(doors.QueryMatcherIgnoreAll()),
	Indicator:    doors.IndicateClass("active"),
}

This example compares only mode and ignores every other query parameter.

Fragment Match

Active.FragmentMatch includes #... in matching. Off by default.

Hook Fields

ALink is a dynamic hook, so it supports the same request-lifecycle fields as event attrs:

  • Scope — request scheduling and de-duplication. See Scopes.
  • Indicator — UI feedback while the request is in flight. See Indication.
  • Before — actions to run before the request.
  • After — actions to run after a successful request.
  • OnError — actions to run on failure. Defaults to ActionLocationReload{} if nil.

For action types, see Actions.

Programmatic

For navigation that doesn't fit an anchor (button, wizard step, form flow), update that Source directly.

The source you get from doors.RouteModel(...) is a typed view of the current URL. Updating it encodes the new path model back into the URL, like an ALink click.

For raw locations, update doors.Router(ctx) directly.

Update

When you already know the full target model:

a.path.Update(ctx, Path{
	Section: SectionDashboard,
	ID:      cityID,
})

Mutate

When you want to preserve part of the current path and change the rest:

type App struct {
	path doors.Source[Path]
}

elem (a *App) goToCity(cityID int) {
	<button
		(doors.AClick{
			On: func(ctx context.Context, r doors.RequestEvent[doors.PointerEvent]) bool {
				a.path.Mutate(ctx, func(p Path) Path {
					p.Section = SectionDashboard
					p.ID = cityID
					return p
				})
				return false
			},
		})>
		Open city
	</button>
}