URL state and navigation
URLs are an application boundary as well as navigation history. Declare a route once, let Scalive decode its parameters, and use that same declaration to build destinations:
import scalive.*
import zio.ZIO
import zio.http.URL
object Routes:
val search =
(live / "search").queryOptional[String]("q")
final case class Model(query: String)
enum Msg:
case ClearQuery
final class SearchLiveView
extends LiveView.Routed[Msg, Model, Option[String]]:
private def modelFrom(params: Option[String]) =
Model(params.map(_.trim.take(100)).filter(_.nonEmpty).getOrElse(""))
def mount(params: Option[String], ctx: MountContext) =
ZIO.succeed(modelFrom(params))
override def handleParams(
model: Model,
params: Option[String],
url: URL,
ctx: ParamsContext
) = ZIO.succeed(modelFrom(params))
def handleMessage(model: Model, ctx: MessageContext) =
case Msg.ClearQuery =>
ctx.nav.replacePatch(Routes.search.location(None)).as(model)
override def view(model: Signal[Model]) =
div(
p("Search: ", model.map(_.query)),
link.pushPatch(Routes.search.location(Some("scala")), "Search for Scala"),
button(on.click(Msg.ClearQuery), "Clear")
)
val routes = Live.router(
Routes.search -> SearchLiveView()
)Routes.search is typed and named. It decodes inbound query state to
Option[String], and location encodes the same type into a checked
LiveLocationclass LiveLocationAn encoded destination produced by a typed Live route.. A LiveLocation cannot be assembled from an
arbitrary string, so route changes remain compiler-visible at call sites.
Treat Decoded Parameters As Untrusted
Typed decoding establishes shape, not trust. Route parameters still came from a browser. Apply domain validation and bounds, and perform authorization against server-owned identity and data. The example trims and bounds the query before it enters the model; a decoded identifier would still need an authorized lookup.
mountdef mount(ctx: MountContext): zio.package.Task[Model] receives decoded parameters during each disconnected and connected mount.
handleParamsdef handleParams(model: Model, params: Params, url: zio.http.URL, ctx: ParamsContext): zio.package.Task[Model] receives them after mount and after every successful patch, which keeps
Back, Forward, and in-page URL changes synchronized with the model.
Choose The Navigation Semantics
Use a rendered link when the user can activate a destination directly. It
keeps a real href, so opening a new tab and disconnected navigation still
work. Use ctx.nav when a server-side transition decides the destination, as
the clear action does after handling a message.
Patch keeps the current routed LiveView mounted and calls
handleParams. It suits filters, tabs, and pagination owned by that view.Navigate changes to another routed LiveView while preserving live navigation where route and session boundaries permit it.
Redirect ends the current lifecycle. Use it for mount-time decisions or completed ordinary HTTP flows rather than in-view URL state.
Push adds a history entry, so Back returns to the previous state.
Replace overwrites the current history entry, which suits canonicalization or transient state.
Rendered links and ctx.nav provide the corresponding patch and navigate
choices. Prefer their checked LiveLocation APIs; raw-string Unsafe variants
give up route and encoding checks.
For parameter mappings, session boundaries, redirects, and every navigation variant, continue with the full routes and navigation guide.