diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala new file mode 100644 index 0000000..6596de1 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala @@ -0,0 +1,21 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +/** A way to add effect processor to component. + * + * To ensure that the lifecycle of the effect processor is bound to the + * lifecycle of the component, the bind is done in the component itself. + * + * There is a default implementation for components that return HtmlElement. If + * the component uses different type, it needs to provide its own + * implementation. + */ +trait HasEffectHook[O]: + extension (o: O) def amend(mod: HtmlMod): O + +object HasEffectHook: + given HasEffectHook[HtmlElement] with + extension (e: HtmlElement) + def amend(mod: HtmlMod): HtmlElement = + e.amend(mod) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala new file mode 100644 index 0000000..6596de1 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala @@ -0,0 +1,21 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +/** A way to add effect processor to component. + * + * To ensure that the lifecycle of the effect processor is bound to the + * lifecycle of the component, the bind is done in the component itself. + * + * There is a default implementation for components that return HtmlElement. If + * the component uses different type, it needs to provide its own + * implementation. + */ +trait HasEffectHook[O]: + extension (o: O) def amend(mod: HtmlMod): O + +object HasEffectHook: + given HasEffectHook[HtmlElement] with + extension (e: HtmlElement) + def amend(mod: HtmlMod): HtmlElement = + e.amend(mod) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala index 46e826d..652555b 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala @@ -3,12 +3,14 @@ import com.raquo.laminar.api.L.{*, given} -abstract class LaminarComponent[M, A, E]( +/** Hook the action -> effect cycle into a component. + */ +abstract class LaminarComponent[M, A, +E, +O: HasEffectHook]( effectHandler: EffectHandler[E, A] ) extends Module[M, A, E]: - def render(m: Signal[M], actions: Observer[A]): HtmlElement + def render(m: Signal[M], actions: Observer[A]): O - val element: HtmlElement = + val view: O = val actions = new EventBus[A] val zero @ (_, effect) = init diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala new file mode 100644 index 0000000..6596de1 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HasEffectHook.scala @@ -0,0 +1,21 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +/** A way to add effect processor to component. + * + * To ensure that the lifecycle of the effect processor is bound to the + * lifecycle of the component, the bind is done in the component itself. + * + * There is a default implementation for components that return HtmlElement. If + * the component uses different type, it needs to provide its own + * implementation. + */ +trait HasEffectHook[O]: + extension (o: O) def amend(mod: HtmlMod): O + +object HasEffectHook: + given HasEffectHook[HtmlElement] with + extension (e: HtmlElement) + def amend(mod: HtmlMod): HtmlElement = + e.amend(mod) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala index 46e826d..652555b 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarComponent.scala @@ -3,12 +3,14 @@ import com.raquo.laminar.api.L.{*, given} -abstract class LaminarComponent[M, A, E]( +/** Hook the action -> effect cycle into a component. + */ +abstract class LaminarComponent[M, A, +E, +O: HasEffectHook]( effectHandler: EffectHandler[E, A] ) extends Module[M, A, E]: - def render(m: Signal[M], actions: Observer[A]): HtmlElement + def render(m: Signal[M], actions: Observer[A]): O - val element: HtmlElement = + val view: O = val actions = new EventBus[A] val zero @ (_, effect) = init diff --git a/ui/shared/src/main/scala/works/iterative/ui/Module.scala b/ui/shared/src/main/scala/works/iterative/ui/Module.scala index 50bfa56..574b355 100644 --- a/ui/shared/src/main/scala/works/iterative/ui/Module.scala +++ b/ui/shared/src/main/scala/works/iterative/ui/Module.scala @@ -1,6 +1,6 @@ package works.iterative.ui -trait Module[Model, Action, Effect]: +trait Module[Model, Action, +Effect]: // Define initial model and effect def init: (Model, Option[Effect]) // Define how to handle actions to build new model and run effects