Skip to content

Instantly share code, notes, and snippets.

@lancewalton
Last active November 20, 2024 09:18
Show Gist options
  • Save lancewalton/e7aefb1e6ba54c842162a60bba28c46b to your computer and use it in GitHub Desktop.
Save lancewalton/e7aefb1e6ba54c842162a60bba28c46b to your computer and use it in GitHub Desktop.
A little Laminar component that emits an event periodically, and also can be poked to emit an event.
// @raquo has suggested a much better way (I've left my original solution below the cut so that
// anybody interested can see the difference). There is now no need for a binder. Just use
// the observer to poke it, and the events to get the periodic/poked events.
import com.raquo.airstream.core.{EventStream, Observer}
class PeriodicAndPokeable(val observer: Observer[Unit], val events: EventStream[Int])
object PeriodicAndPokeable {
def apply(periodicMillis: Int): PeriodicAndPokeable = {
val (stream, observer) = EventStream.withObserver[Unit]
val resultStream = EventStream
.merge(
EventStream.periodic(periodicMillis),
stream
)
.scanLeft(0) { case (acc, _) => acc + 1 }
.changes
new PeriodicAndPokeable(observer, resultStream)
}
}
----
// A little Laminar component that emits an event periodically, and also can be poked to emit an event.
// This is useful, for example, when you want something on a page to be periodically reloaded, and also to
// be reloaded in response to a user event.
import com.raquo.airstream.core.{EventStream, Observer}
import com.raquo.airstream.core.Source.EventSource
import com.raquo.airstream.eventbus.{EventBus, WriteBus}
import com.raquo.airstream.ownership.DynamicSubscription
import com.raquo.laminar.modifiers.Binder
import com.raquo.laminar.nodes.ReactiveElement
import scala.util.Try
class PeriodicAndPokeable(periodicMillis: Int) extends Binder.Base with EventSource[Int] with Observer[Unit] {
private val periodic = EventStream.periodic(periodicMillis)
private val pokeable: EventBus[Unit] = EventBus[Unit]()
override def bind(element: ReactiveElement.Base): DynamicSubscription =
Binder(ReactiveElement.bindSink(_, periodic.mapTo(()).toObservable)(pokeable)).bind(element)
override def toObserver: Observer[Unit] = this
override def toObservable: EventStream[Int] =
EventStream
.merge(EventStream.fromValue(0), pokeable.events.scanLeft(0) { case (acc, _) => acc + 1 }.changes)
.toObservable
override def onNext(nextValue: Unit): Unit =
writer.onNext(nextValue)
override def onError(err: Throwable): Unit =
writer.onError(err)
override def onTry(nextValue: Try[Unit]): Unit =
writer.onTry(nextValue)
def events: EventStream[Int] = toObservable
def writer: WriteBus[Unit] = pokeable.writer
}
object PeriodicAndPokeable {
def apply(periodicMillis: Int): PeriodicAndPokeable = new PeriodicAndPokeable(periodicMillis)
}
----
// An example of use. This will create an <h1> element with a number that increase every 10000ms.
// There is also be a button that, when clicked, will also cause the number to increase, independently of the
// periodic increase.
val pap = PeriodicAndPokeable(10000)
div(
child <-- pap.events.map(n => h1(n.toString)),
button(
tpe := "button",
onClick.mapTo(()) --> pap.writer,
"I can't wait!"
),
pap // Bind the component, so that it starts ticking periodically, starting immediately the enclosing div is mounted
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment