Bindings must by dynamic mods

That way they each get their own event went inside a comprehension
This commit is contained in:
Paul-Henri Froidmont 2025-09-25 03:39:05 +02:00
parent eab5eb3316
commit 21309629bc
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
4 changed files with 30 additions and 16 deletions

View file

@ -23,6 +23,9 @@ object DiffBuilder:
private def buildDynamic(dynamicMods: Seq[DynamicMod], trackUpdates: Boolean): Seq[Option[Diff]] =
dynamicMods.flatMap {
case Attr.Binding(_, id, _) => List(id.render(trackUpdates).map(Diff.Value(_)))
case Attr.JsBinding(_, jsonValue, _) =>
List(jsonValue.render(trackUpdates).map(Diff.Value(_)))
case Attr.Dyn(name, value, _) =>
List(value.render(trackUpdates).map(v => Diff.Value(v.toString)))
case Attr.DynValueAsPresence(name, value) =>

View file

@ -20,6 +20,8 @@ object HtmlBuilder:
for i <- dynamic.indices do
strw.write(static(i))
dynamic(i) match
case Attr.Binding(_, id, _) => strw.write(id.render(false).getOrElse(""))
case Attr.JsBinding(_, jsonValue, _) => strw.write(jsonValue.render(false).getOrElse(""))
case Attr.Dyn(name, value, isJson) =>
strw.write(value.render(false).getOrElse(""))
case Attr.DynValueAsPresence(name, value) =>

View file

@ -70,7 +70,7 @@ class HtmlAttr[V](val name: String, val codec: Encoder[V, String]):
class HtmlAttrBinding(val name: String):
def apply(cmd: JSCommand): Mod.Attr =
Mod.Attr.JsBinding(name, cmd.toJson, cmd.bindings)
Mod.Attr.JsBinding(name, Var(cmd.toJson), cmd.bindings)
def apply[Msg](msg: Msg): Mod.Attr =
apply(_ => msg)
@ -78,12 +78,19 @@ class HtmlAttrBinding(val name: String):
def apply[Msg](f: Map[String, String] => Msg): Mod.Attr =
Mod.Attr.Binding(
name,
Base64.getUrlEncoder().withoutPadding().encodeToString(Random().nextBytes(12)),
Var(Base64.getUrlEncoder().withoutPadding().encodeToString(Random().nextBytes(12))),
f
)
def withValue[Msg](f: String => Msg): Mod.Attr =
apply(m => f(m("value")))
def withBoolValue[Msg](f: Boolean => Msg): Mod.Attr =
apply(m =>
f(m("value") match
case "on" | "yes" | "true" => true
case "off" | "no" | "false" => false)
)
trait BindingAdapter[F, Msg]:
def createMessage(f: F): Map[String, String] => Msg
object BindingAdapter:
@ -101,10 +108,12 @@ object Mod:
enum Attr extends Mod:
case Static(name: String, value: String) extends Attr with StaticMod
case StaticValueAsPresence(name: String, value: Boolean) extends Attr with StaticMod
case Binding(name: String, id: String, f: Map[String, String] => ?) extends Attr with StaticMod
case JsBinding(name: String, jsonValue: String, bindings: Map[String, ?])
case Binding(name: String, id: scalive.Dyn[String], f: Map[String, String] => ?)
extends Attr
with StaticMod
with DynamicMod
case JsBinding(name: String, jsonValue: scalive.Dyn[String], bindings: Map[String, ?])
extends Attr
with DynamicMod
case Dyn(name: String, value: scalive.Dyn[String], isJson: Boolean = false)
extends Attr
with DynamicMod
@ -124,8 +133,8 @@ extension (mod: Mod)
private[scalive] def setAllUnchanged(): Unit =
mod match
case Attr.Static(_, _) => ()
case Attr.Binding(_, _, _) => ()
case Attr.JsBinding(_, _, _) => ()
case Attr.Binding(_, id, _) => id.setUnchanged()
case Attr.JsBinding(_, json, _) => json.setUnchanged()
case Attr.StaticValueAsPresence(_, _) => ()
case Attr.Dyn(_, value, _) => value.setUnchanged()
case Attr.DynValueAsPresence(_, value) => value.setUnchanged()
@ -149,8 +158,8 @@ extension (mod: Mod)
mod match
case Attr.Static(_, _) => ()
case Attr.StaticValueAsPresence(_, _) => ()
case Attr.Binding(_, _, _) => ()
case Attr.JsBinding(_, _, _) => ()
case Attr.Binding(_, id, _) => id.sync()
case Attr.JsBinding(_, json, _) => json.sync()
case Attr.Dyn(_, value, _) => value.sync()
case Attr.DynValueAsPresence(_, value) => value.sync()
case Content.Text(text) => ()
@ -174,7 +183,7 @@ extension (mod: Mod)
case Attr.Static(_, _) => None
case Attr.StaticValueAsPresence(_, _) => None
case Attr.Binding(_, eventId, f) =>
if id == eventId then Some(f.asInstanceOf[Map[String, String] => Msg])
if id == eventId.currentValue then Some(f.asInstanceOf[Map[String, String] => Msg])
else None
case Attr.JsBinding(_, _, bindings) =>
bindings.get(id).map(msg => _ => msg.asInstanceOf[Msg])

View file

@ -14,8 +14,8 @@ object StaticBuilder:
val attrs = el.attrMods.flatMap {
case Attr.Static(name, value) => List(Some(s" $name='$value'"))
case Attr.StaticValueAsPresence(name, value) => List(Some(s" $name"))
case Attr.Binding(name, id, _) => List(Some(s""" $name="$id""""))
case Attr.JsBinding(name, json, _) => List(Some(s" $name='$json'"))
case Attr.Binding(name, _, _) => List(Some(s""" $name=""""), None, Some('"'.toString))
case Attr.JsBinding(name, _, _) => List(Some(s" $name='"), None, Some("'"))
case Attr.Dyn(name, value, isJson) =>
if isJson then List(Some(s" $name='"), None, Some("'"))
else List(Some(s""" $name=""""), None, Some('"'.toString))