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)
}
override 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)
Result
ConnectedReconnectingRead-onlyDurable history4
DOM windowFive recent rows. Complete history remains in the model.
- NavigationActivity #1
- FormsActivity #2
- RenderingActivity #3
- ComponentsActivity #4
Disconnected. Controls resume after reconnection.
Related guidance: use streams for collection updates.