Bounded activity stream
Insert activities until the rendered list retains only its five newest rows.
The durable count keeps growing because the model owns the complete history;
the LiveStreamtype LiveStream = ([A] =>> streams.LiveStream[A]) only describes efficient DOM updates.
Delete removes an activity from both, while reset restores the initial history
and stream window.
final class ActivityStreamExample
extends LiveView[ActivityStreamExample.Msg, ActivityStreamExample.Model]:
import ActivityStreamExample.*
def mount(ctx: MountContext): Task[Model] =
initialModel(ctx)
def handleMessage(model: Model, ctx: MessageContext) =
case Msg.Add =>
val template = NewActivityTemplates((model.nextId - 1) % NewActivityTemplates.size)
val activity = Activity(model.nextId, template._1, template._2)
ctx.streams.insert(ActivityStreamDef, activity).map { stream =>
model.copy(
activities = model.activities :+ activity,
activityStream = stream,
nextId = model.nextId + 1
)
}
case Msg.Delete(activityId) =>
ctx.streams.delete(ActivityStreamDef, activityId).map { stream =>
model.copy(
activities = model.activities.filterNot(_.id == activityId),
activityStream = stream
)
}
case Msg.Reset =>
ctx.streams.reset(ActivityStreamDef, InitialActivities).map { stream =>
Model(InitialActivities, stream, InitialNextId)
}
def view(model: Signal[Model]): HtmlElement[Msg] =
div(
cls := "docs-activity-stream",
sectionTag(
cls := "docs-activity-summary",
aria.label := "Activity stream state",
div(
span(cls := "docs-activity-label", "Durable history"),
strong(
cls := "docs-activity-count",
dataAttr("activity-count") := "",
model.map(_.activities.size.toString)
)
),
p(
span("DOM window"),
"Five recent rows. Complete history remains in the model."
)
),
div(
cls := "docs-activity-controls",
button(
typ := "button",
dataAttr("add-activity") := "",
on.click(Msg.Add),
"Insert activity"
)
),
model
.map(_.activityStream).renderIn(
ol,
cls := "docs-activity-list",
aria.label := "Recent activity"
) { activity =>
li(
dataAttr("activity-row") := "",
div(
cls := "docs-activity-row-content",
span(cls := "docs-activity-category", activity.map(_.category)),
p(cls := "docs-activity-message", activity.map(_.summary)),
small(cls := "docs-activity-id", activity.map(value => s"Activity #${value.id}"))
),
button(
cls := "docs-activity-delete",
typ := "button",
dataAttr("delete-activity") := activity.map(_.id.toString),
aria.label := activity.map(value => s"Delete activity ${value.id}"),
on.click(activity.map(value => Msg.Delete(value.id))),
"Delete"
)
)
}
)
private def initialModel(ctx: MountContext): Task[Model] =
ctx.streams.create(ActivityStreamDef, InitialActivities).map { stream =>
Model(InitialActivities, stream, InitialNextId)
}
end ActivityStreamExample
object ActivityStreamExample:
final case class Activity(id: Int, category: String, summary: String)
final case class Model(
activities: Vector[Activity],
activityStream: LiveStream[Activity],
nextId: Int)
enum Msg:
case Add
case Delete(activityId: Int)
case Reset
private val ActivityStreamDef =
LiveStreamDef.byId[Activity, Int]("activity")(_.id).keepLast(5)
private val InitialActivities = Vector(
Activity(1, "Navigation", "Opened the typed search example"),
Activity(2, "Forms", "Validated a profile draft"),
Activity(3, "Rendering", "Applied a keyed collection patch"),
Activity(4, "Components", "Updated component props by stable ID")
)
private val InitialNextId = InitialActivities.map(_.id).max + 1
private val NewActivityTemplates = Vector(
"Streams" -> "Inserted a bounded activity row",
"Navigation" -> "Patched to another results page",
"Components" -> "Updated component props by stable ID",
"Async work" -> "Completed a deterministic report"
)
end ActivityStreamExampleView source (documentation/site/src/scalive/docs/examples/ActivityStreamExample.scala:8-129)
Live result
ConnectedReconnectingRead-onlyDOM windowFive recent rows. Complete history remains in the model.
- NavigationActivity #1
- FormsActivity #2
- RenderingActivity #3
- ComponentsActivity #4
Disconnected. Controls resume after reconnection.
Interaction inspector
See how actions in the live result above travel through typed messages, server state, protocol frames, and DOM updates.
The live result and its inspector resume when the connection returns.
Related guidance: use streams for collection updates.