diff --git a/build.sbt b/build.sbt index 3b83e80..bf5ddaf 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -ThisBuild / scalaVersion := "3.5.1" +ThisBuild / scalaVersion := "3.5.2" ThisBuild / versionScheme := Some("semver-spec") ThisBuild / publishMavenStyle := true diff --git a/dom/src/main/scala/top/davidon/sfs/dom/Element.scala b/dom/src/main/scala/top/davidon/sfs/dom/Element.scala index 3574008..a801eb0 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/Element.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/Element.scala @@ -6,6 +6,6 @@ import top.davidon.sfs.dom.tags.Tag /** tag + modifiers + value */ class Element[+Ref <: org.scalajs.dom.Element]( val tag: Tag[Ref], - val mods: Iterable[Modifier[?]], - val children: Seq[Element[?] | Value[String]] + val mods: Iterable[Modifier[?, ?]], + val children: Seq[Element[?] | Value[?, String]] ) {} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala b/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala deleted file mode 100644 index e922455..0000000 --- a/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala +++ /dev/null @@ -1,9 +0,0 @@ -package top.davidon.sfs.dom - -import org.scalajs.dom -import top.davidon.sfs.dom.mods.Modifier - -trait ReactiveRenderer { - def valueFunc[F](element: dom.Element, value: F): Unit - def modifierFunc[F, T](modifier: Modifier[T], value: F): Unit -} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala b/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala index fee109f..0bbf933 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala @@ -21,23 +21,24 @@ trait ScalaFullStackDOM object svg extends SvgTags with SvgAttrs with ComplexSvgKeys - given strToVal: Conversion[String, Value[String]] with { + given strToVal: Conversion[String, Value[String, String]] with { def apply(from: String): PlainValue[String, String] = PlainValue(from, StringAsIsCodec) } - given intToVal: Conversion[Int, Value[String]] with { + given intToVal: Conversion[Int, Value[Int, String]] with { def apply(from: Int): PlainValue[Int, String] = PlainValue(from, IntAsStringCodec) } - given doubleToVal: Conversion[Double, Value[String]] with { + given doubleToVal: Conversion[Double, Value[Double, String]] with { def apply(from: Double): PlainValue[Double, String] = PlainValue(from, DoubleAsStringCodec) } - given longToVal: Conversion[Long, Value[String]] with { + given longToVal: Conversion[Long, Value[Long, String]] with { def apply(from: Long): PlainValue[Long, String] = PlainValue(from, LongAsStringCodec) } - given iterToVal: Conversion[Iterable[String], Value[String]] with { + given iterToVal: Conversion[Iterable[String], Value[Iterable[String], String]] + with { def apply(from: Iterable[String]): PlainValue[Iterable[String], String] = PlainValue(from, IterableAsSpaceSeparatedStringCodec) } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/Value.scala b/dom/src/main/scala/top/davidon/sfs/dom/Value.scala index 4a9b3cd..9a263ee 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/Value.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/Value.scala @@ -1,10 +1,12 @@ package top.davidon.sfs.dom +import top.davidon.sfs.dom.codecs.{Codec, Coder} -import top.davidon.sfs.dom.codecs.Codec -import top.davidon.sfs.dom.reactive.Observable +trait Value[F, T](val value: F, val codec: Codec[F, T]) { + def apply(): T = { + codec.encoder(value) + } -trait Value[T] { - def apply(): T + override def toString: String = { codec.stringCode(value) } } //class Value[F, T]( diff --git a/dom/src/main/scala/top/davidon/sfs/dom/codecs/AsIsCodec.scala b/dom/src/main/scala/top/davidon/sfs/dom/codecs/AsIsCodec.scala index 0403e80..c5d645c 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/codecs/AsIsCodec.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/codecs/AsIsCodec.scala @@ -1,7 +1,8 @@ package top.davidon.sfs.dom.codecs -class AsIsCodec[T](val strCodec: StringCodec[T]) extends Codec[T, T] { - override def encode(scalaValue: T): T = scalaValue - - override def decode(domValue: T): T = domValue +class AsIsCoder[T] extends Coder[T, T] { + def apply(value: T): T = value } + +class AsIsCodec[T](sc: Coder[T, String]) + extends Codec[T, T](new AsIsCoder[T], new AsIsCoder[T], sc) diff --git a/dom/src/main/scala/top/davidon/sfs/dom/codecs/Codec.scala b/dom/src/main/scala/top/davidon/sfs/dom/codecs/Codec.scala index 937aba2..ca67064 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/codecs/Codec.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/codecs/Codec.scala @@ -1,25 +1,19 @@ package top.davidon.sfs.dom.codecs -trait Codec[ScalaType, DomType] { +class Codec[F, T]( + val encoder: Coder[F, T], + val decoder: Coder[T, F], + val stringCoder: Coder[F, String] +) { + def decode(value: T): F = { + decoder(value) + } - /** Convert the result of a `dom.Node.getAttribute` call to appropriate Scala - * type. - * - * Note: HTML Attributes are generally optional, and `dom.Node.getAttribute` - * will return `null` if an attribute is not defined on a given DOM node. - * However, this decoder is only intended for cases when the attribute is - * defined. - */ - def decode(domValue: DomType): ScalaType + def encode(scalaValue: F): T = { + encoder(scalaValue) + } - /** Convert desired attribute value to appropriate DOM type. The resulting - * value should be passed to `dom.Node.setAttribute` call, EXCEPT when - * resulting value is a `null`. In that case you should call - * `dom.Node.removeAttribute` instead. - * - * We use `null` instead of [[Option]] here to reduce overhead in JS land. - * This method should not be called by end users anyway, it's the consuming - * library's job to call this method under the hood. - */ - def encode(scalaValue: ScalaType): DomType + def stringCode(value: F): String = { + stringCoder(value) + } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/codecs/Coder.scala b/dom/src/main/scala/top/davidon/sfs/dom/codecs/Coder.scala new file mode 100644 index 0000000..fbf28a4 --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/codecs/Coder.scala @@ -0,0 +1,5 @@ +package top.davidon.sfs.dom.codecs + +trait Coder[F, T] { + def apply(value: F): T +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/codecs/EmptyCodec.scala b/dom/src/main/scala/top/davidon/sfs/dom/codecs/EmptyCodec.scala index 4b31abf..6cbccd7 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/codecs/EmptyCodec.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/codecs/EmptyCodec.scala @@ -1,6 +1,7 @@ package top.davidon.sfs.dom.codecs -class EmptyCodec[T] extends Codec[T, Unit] { - override def decode(domValue: Unit): T = ??? - override def encode(scalaValue: T): Unit = {} -} +val EmptyCodec = new Codec[Any, Unit]( + * => (), + * => (), + * => "" +) diff --git a/dom/src/main/scala/top/davidon/sfs/dom/codecs/StringCodec.scala b/dom/src/main/scala/top/davidon/sfs/dom/codecs/StringCodec.scala deleted file mode 100644 index 9ce2454..0000000 --- a/dom/src/main/scala/top/davidon/sfs/dom/codecs/StringCodec.scala +++ /dev/null @@ -1,4 +0,0 @@ -package top.davidon.sfs.dom.codecs - -//trait StringCodec[T] extends Codec[T, String] {} -type StringCodec[T] = Codec[T, String] diff --git a/dom/src/main/scala/top/davidon/sfs/dom/codecs/package.scala b/dom/src/main/scala/top/davidon/sfs/dom/codecs/package.scala index fc14f03..9f5c4fb 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/codecs/package.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/codecs/package.scala @@ -1,93 +1,88 @@ package top.davidon.sfs.dom -package object codecs { +import top.davidon.sfs.dom.codecs.Codec - lazy val IntAsStringCodec: StringCodec[Int] = - new StringCodec[Int] { +class StringCodecs { - override def decode(domValue: String): Int = - domValue.toInt // @TODO this can throw exception. How do we handle this? + lazy val IntAsStringCodec: Codec[Int, String] = + new Codec[Int, String]( + (value: Int) => value.toString, + (value: String) => value.toInt, + (value: Int) => value.toString + ) - override def encode(scalaValue: Int): String = scalaValue.toString - } + lazy val DoubleAsStringCodec: Codec[Double, String] = + new Codec[Double, String]( + (value: Double) => value.toString, + (value: String) => value.toDouble, + (value: Double) => value.toString + ) - lazy val DoubleAsStringCodec: StringCodec[Double] = - new StringCodec[Double] { + lazy val LongAsStringCodec: Codec[Long, String] = + new Codec[Long, String]( + (value: Long) => value.toString, + (value: String) => value.toLong, + (value: Long) => value.toString + ) - override def decode(domValue: String): Double = - domValue.toDouble // @TODO this can throw exception. How do we handle this? + lazy val BooleanAsTrueFalseStringCodec: Codec[Boolean, String] = + new Codec[Boolean, String]( + (value: Boolean) => value.toString, + (value: String) => value == "true", + (value: Boolean) => value.toString + ) - override def encode(scalaValue: Double): String = scalaValue.toString - } + lazy val BooleanAsYesNoStringCodec: Codec[Boolean, String] = + new Codec[Boolean, String]( + (value: Boolean) => if value then "yes" else "no", + (value: String) => value == "yes", + (value: Boolean) => if value then "yes" else "no" + ) - lazy val LongAsStringCodec: StringCodec[Long] = - new StringCodec[Long] { + lazy val BooleanAsOnOffStringCodec: Codec[Boolean, String] = + new Codec[Boolean, String]( + (value: Boolean) => if value then "on" else "off", + (value: String) => value == "on", + (value: Boolean) => if value then "on" else "off" + ) - override def decode(domValue: String): Long = - domValue.toLong // @TODO this can throw exception. How do we handle this? + lazy val IterableAsSpaceSeparatedStringCodec + : Codec[Iterable[String], String] = + new Codec[Iterable[String], String]( + (value: Iterable[String]) => value.mkString(" "), + (value: String) => if value == "" then Nil else value.split(" "), + (value: Iterable[String]) => value.mkString(" ") + ) - override def encode(scalaValue: Long): String = scalaValue.toString - } + lazy val IterableAsCommaSeparatedStringCodec + : Codec[Iterable[String], String] = + new Codec[Iterable[String], String]( + (value: Iterable[String]) => value.mkString(","), + (value: String) => if value == "" then Nil else value.split(","), + (value: Iterable[String]) => value.mkString(",") + ) - lazy val BooleanAsTrueFalseStringCodec: StringCodec[Boolean] = - new StringCodec[Boolean] { + lazy val BooleanAsAttrPresenceCodec: Codec[Boolean, String] = + new Codec[Boolean, String]( + (value: Boolean) => if value then "" else null, + (value: String) => value != null, + (value: Boolean) => if value then "" else null + ) +} - override def decode(domValue: String): Boolean = domValue == "true" - - override def encode(scalaValue: Boolean): String = - if scalaValue then "true" else "false" - } - - lazy val BooleanAsYesNoStringCodec: StringCodec[Boolean] = - new StringCodec[Boolean] { - - override def decode(domValue: String): Boolean = domValue == "yes" - - override def encode(scalaValue: Boolean): String = - if scalaValue then "yes" else "no" - } - lazy val BooleanAsOnOffStringCodec: StringCodec[Boolean] = - new StringCodec[Boolean] { - - override def decode(domValue: String): Boolean = domValue == "on" - - override def encode(scalaValue: Boolean): String = - if scalaValue then "on" else "off" - } - - lazy val IterableAsSpaceSeparatedStringCodec: StringCodec[Iterable[String]] = - new StringCodec[Iterable[String]] { // could use for e.g. className - - override def decode(domValue: String): Iterable[String] = - if domValue == "" then Nil else domValue.split(' ') - - override def encode(scalaValue: Iterable[String]): String = - scalaValue.mkString(" ") - } - lazy val IterableAsCommaSeparatedStringCodec: StringCodec[Iterable[String]] = - new StringCodec[Iterable[String]] { // could use for lists of IDs - - override def decode(domValue: String): Iterable[String] = - if domValue == "" then Nil else domValue.split(',') - - override def encode(scalaValue: Iterable[String]): String = - scalaValue.mkString(",") - } - lazy val BooleanAsAttrPresenceCodec: StringCodec[Boolean] = - new StringCodec[Boolean] { - - override def decode(domValue: String): Boolean = domValue != null - - override def encode(scalaValue: Boolean): String = - if scalaValue then "" else null - } - - lazy val LongAsIsCodec: AsIsCodec[Long] = AsIsCodec(LongAsStringCodec) - lazy val DoubleAsIsCodec: AsIsCodec[Double] = AsIsCodec(DoubleAsStringCodec) - lazy val StringAsIsCodec: AsIsCodec[String] & StringCodec[String] = - new AsIsCodec[String](StringAsIsCodec) with StringCodec[String] {} - lazy val IntAsIsCodec: AsIsCodec[Int] = AsIsCodec(IntAsStringCodec) +package object codecs extends StringCodecs { + lazy val LongAsIsCodec: AsIsCodec[Long] = AsIsCodec( + LongAsStringCodec.stringCoder + ) + lazy val DoubleAsIsCodec: AsIsCodec[Double] = AsIsCodec( + DoubleAsStringCodec.stringCoder + ) + lazy val StringAsIsCodec: AsIsCodec[String] = + AsIsCodec[String](AsIsCoder[String]()) + lazy val IntAsIsCodec: AsIsCodec[Int] = AsIsCodec( + IntAsStringCodec.stringCoder + ) lazy val BooleanAsIsCodec: AsIsCodec[Boolean] = AsIsCodec( - BooleanAsTrueFalseStringCodec + BooleanAsTrueFalseStringCodec.stringCoder ) } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala index 09dba45..3e8f538 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala @@ -1,20 +1,24 @@ package top.davidon.sfs.dom.keys -import top.davidon.sfs.dom.codecs.{Codec, StringCodec} -import top.davidon.sfs.dom.Value +import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.mods.Modifier -import top.davidon.sfs.dom.plain.PlainValue +import top.davidon.sfs.dom.plain.{PlainModifier, PlainValue} +import top.davidon.sfs.dom.reactive.{ + Observable, + ReactiveModifier, + ReactiveValue +} class AriaAttr[V]( suffix: String, - val codec: StringCodec[V] + val codec: Codec[V, String] ) extends Key { override val name: String = "aria-" + suffix - @inline def apply(value: V): Modifier[String] = { + @inline def apply(value: V | Observable[V]): Modifier[V, String] = { this := value } - def :=(value: V): Modifier[String] = { - Modifier(this, PlainValue(value, codec)) + def :=(value: V | Observable[V]): Modifier[V, String] = { + Modifier.fromVorObservableV(this, value, codec) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala index c656426..1161e78 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala @@ -4,8 +4,8 @@ import org.scalajs.dom import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.codecs.{EmptyCodec, StringAsIsCodec} import top.davidon.sfs.dom.mods.{EventMod, Modifier} -import top.davidon.sfs.dom.plain.PlainValue -import top.davidon.sfs.dom.reactive.TriggerableValue +import top.davidon.sfs.dom.plain.{PlainModifier, PlainValue} +import top.davidon.sfs.dom.reactive.Observable class EventProp[E <: dom.Event](override val name: String) extends Key { @@ -13,23 +13,15 @@ class EventProp[E <: dom.Event](override val name: String) extends Key { * @param value * @return */ - def :=(value: E => Unit): Modifier[Unit] = { - Modifier(this, PlainValue(EventMod(this, value), EmptyCodec())) + def :=(value: E => Unit): Modifier[Any, Unit] = { + PlainModifier(this, PlainValue(EventMod(this, value), EmptyCodec)) } - /** Don't use with StringRenderer and ssr off/false + /** Only use with StringRenderer and ssr on/true * @param value * @return */ - def :=(value: TriggerableValue[E]): Modifier[Unit] = { - Modifier(this, PlainValue(EventMod(this, value.trigger), EmptyCodec())) - } - - /** Only use with StringRenderer and ssr off/false - * @param value - * @return - */ - def :=(value: String): Modifier[String] = { - Modifier(this, PlainValue(value, StringAsIsCodec)) + def :=(value: String): Modifier[String, String] = { + PlainModifier(this, PlainValue(value, StringAsIsCodec)) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala index d881aa0..811074e 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala @@ -1,19 +1,24 @@ package top.davidon.sfs.dom.keys -import top.davidon.sfs.dom.codecs.{Codec, StringCodec} +import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.mods.Modifier -import top.davidon.sfs.dom.plain.PlainValue +import top.davidon.sfs.dom.plain.{PlainModifier, PlainValue} +import top.davidon.sfs.dom.reactive.{ + Observable, + ReactiveModifier, + ReactiveValue +} class HtmlAttr[V]( override val name: String, - val codec: StringCodec[V] + val codec: Codec[V, String] ) extends Key { - @inline def apply(value: V): Modifier[String] = { + @inline def apply(value: V | Observable[V]): Modifier[V, String] = { this := value } - def :=(value: V): Modifier[String] = { - Modifier(this, PlainValue(value, codec)) + def :=(value: V | Observable[V]): Modifier[V, String] = { + Modifier.fromVorObservableV(this, value, codec) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala index deb3384..4ea7752 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala @@ -3,16 +3,17 @@ import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.mods.Modifier import top.davidon.sfs.dom.plain.PlainValue +import top.davidon.sfs.dom.reactive.Observable class HtmlProp[V, DomV]( override val name: String, val codec: Codec[V, DomV] ) extends Key { - @inline def apply(value: V): Modifier[DomV] = { + @inline def apply(value: V | Observable[V]): Modifier[V, DomV] = { this := value } - def :=(value: V): Modifier[DomV] = { - Modifier(this, PlainValue(value, codec)) + def :=(value: V | Observable[V]): Modifier[V, DomV] = { + Modifier.fromVorObservableV(this, value, codec) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala index 69e8073..646945e 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala @@ -1,12 +1,11 @@ package top.davidon.sfs.dom.keys -import top.davidon.sfs.dom.codecs.{Codec, StringCodec} -import top.davidon.sfs.dom.Value +import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.mods.Modifier -import top.davidon.sfs.dom.plain.PlainValue +import top.davidon.sfs.dom.reactive.Observable class SvgAttr[V]( val localName: String, - val codec: StringCodec[V], + val codec: Codec[V, String], val namespacePrefix: Option[String] ) extends Key { override val name: String = @@ -14,12 +13,12 @@ class SvgAttr[V]( val namespaceUri: Option[String] = namespacePrefix.map(SvgAttr.namespaceUri) - @inline def apply(value: V): Modifier[String] = { + @inline def apply(value: V | Observable[V]): Modifier[V, String] = { this := value } - def :=(value: V): Modifier[String] = { - Modifier(this, PlainValue(value, codec)) + def :=(value: V | Observable[V]): Modifier[V, String] = { + Modifier.fromVorObservableV(this, value, codec) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala b/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala index a074766..c27a451 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala @@ -3,8 +3,32 @@ package top.davidon.sfs.dom.mods import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.keys.Key +import top.davidon.sfs.dom.plain.{PlainModifier, PlainValue} +import top.davidon.sfs.dom.reactive.{ + Observable, + ReactiveModifier, + ReactiveValue +} -class Modifier[T]( +class Modifier[F, T] protected ( val key: Key, - val value: Value[T] + val value: Value[F, T] ) {} + +object Modifier { + def fromVorObservableV[F, T]( + key: Key, + value: F | Observable[F], + codec: Codec[F, T] + ): Modifier[F, T] = { + value match { + case _: F => + PlainModifier(key, PlainValue(value.asInstanceOf[F], codec)) + case _: Observable[F] => + ReactiveModifier( + key, + ReactiveValue(value.asInstanceOf[Observable[F]], codec) + ) + } + } +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala index b9b26f6..ceec4e7 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala @@ -1,5 +1,7 @@ package top.davidon.sfs.dom.plain +import top.davidon.sfs.dom.codecs.Codec +import top.davidon.sfs.dom.keys.Key +import top.davidon.sfs.dom.mods.Modifier -class PlainModifier { - -} +class PlainModifier[F, T](key: Key, value: PlainValue[F, T]) + extends Modifier[F, T](key, value) {} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala index 35ca9d7..06f9043 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala @@ -3,23 +3,5 @@ package top.davidon.sfs.dom.plain import top.davidon.sfs.dom.{Value, codecs} import top.davidon.sfs.dom.codecs.Codec -class PlainValue[F, T]( - val value: F, - val codec: Codec[F, T] -) extends Value[T] { - def apply(): T = { - codec.encode(value) - } - - override def toString: String = { - value match - case v: Int => codecs.IntAsStringCodec.encode(v) - case v: Long => codecs.LongAsStringCodec.encode(v) - case v: Double => codecs.DoubleAsStringCodec.encode(v) - case v: Boolean => codecs.BooleanAsTrueFalseStringCodec.encode(v) - case v: Iterable[String] => - codecs.IterableAsSpaceSeparatedStringCodec.encode(v) - case _ => - throw Exception("Couldn't find codec to convert a value to a string") - } -} +class PlainValue[F, T](value: F, codec: Codec[F, T]) + extends Value[F, T](value, codec) {} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala index b376c13..56f828b 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala @@ -1,9 +1,8 @@ package top.davidon.sfs.dom.reactive -trait Observable[T] { +abstract class Observable[T](var value: T) { def subscribe(observer: T => Unit): Unit def unsubscribe(observer: T => Unit): Unit - def notify(value: T): Unit def update(value: T): Unit def now(): T } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala index 70a94f5..04aa888 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala @@ -1,5 +1,6 @@ package top.davidon.sfs.dom.reactive +import top.davidon.sfs.dom.keys.Key +import top.davidon.sfs.dom.mods.Modifier -class ReactiveModifier { - -} +class ReactiveModifier[F, T](key: Key, reactiveValue: ReactiveValue[F, T]) + extends Modifier[F, T](key, reactiveValue) {} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala index 3a328f3..5412c1c 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala @@ -5,14 +5,18 @@ import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.plain.PlainValue class ReactiveValue[F, T]( - val value: Observable[F], - val codec: Codec[F, T] -) extends Value[T] { - def apply(): T = { - codec.encode(value.now()) + val reactiveValue: Observable[F], + codec: Codec[F, T] +) extends Value[F, T](reactiveValue.now(), codec) { + override def apply(): T = { + codec.encode(reactiveValue.now()) + } + + override def toString: String = { + codec.stringCode(reactiveValue.now()) } def toValueNow: PlainValue[F, T] = { - PlainValue(value.now(), codec) + PlainValue(reactiveValue.now(), codec) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala deleted file mode 100644 index fae9eaa..0000000 --- a/dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala +++ /dev/null @@ -1,5 +0,0 @@ -package top.davidon.sfs.dom.reactive - -trait TriggerableValue[T] { - def trigger(value: T): Unit -} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala b/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala index 93d8012..3be7a0a 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala @@ -9,9 +9,9 @@ trait Tag[+Ref <: dom.Element] { val void: Boolean def apply( - modifiers: Modifier[?]* + modifiers: Modifier[?, ?]* )( - values: Element[?] | Value[String]* + values: Element[?] | Value[?, String]* ): Element[Ref] = { Element[Ref](this, modifiers, values.toSeq) } diff --git a/sfs/.js/src/main/scala/top/davidon/sfs/renderers/ClientSideRenderer.scala b/sfs/.js/src/main/scala/top/davidon/sfs/renderers/ClientSideRenderer.scala index 2f9b9b2..b534c21 100644 --- a/sfs/.js/src/main/scala/top/davidon/sfs/renderers/ClientSideRenderer.scala +++ b/sfs/.js/src/main/scala/top/davidon/sfs/renderers/ClientSideRenderer.scala @@ -3,13 +3,13 @@ package top.davidon.sfs.renderers import org.scalajs.dom import top.davidon.sfs.dom.keys.{EventProp, HtmlProp} import top.davidon.sfs.dom.mods.{EventMod, Modifier} -import top.davidon.sfs.dom.{Element, ReactiveRenderer, Renderer, Value} +import top.davidon.sfs.dom.plain.PlainValue +import top.davidon.sfs.dom.reactive.ReactiveValue +import top.davidon.sfs.dom.{Element, Renderer, Value} import scala.scalajs.js -class ClientSideRenderer(val root: dom.Element) - extends Renderer[Unit] - with ReactiveRenderer { +class ClientSideRenderer(val root: dom.Element) extends Renderer[Unit] { override def render( elements: Element[dom.Element]* @@ -17,36 +17,66 @@ class ClientSideRenderer(val root: dom.Element) elements.foreach(renderElement(root, _)) } - override def valueFunc[F](element: dom.Element, value: F): Unit = {} + def valueFunc[F]( + element: dom.Element, + children: Seq[Element[?] | Value[?, String]] + ): Unit = { + children.foreach { + case e: Element[?] => + renderElement(element, e) + case s: Value[?, String] => element.append(s()) + } + } - override def modifierFunc[F, T]( - modifier: Modifier[F, T], - value: F - ): Unit = {} + def modifierFunc( + el: dom.Element, + mod: Modifier[?, ?] + ): Unit = { + mod.key match { + case _: HtmlProp[?, ?] => + el.asInstanceOf[js.Dynamic] + .updateDynamic(mod.key.name)(mod.value.asInstanceOf[js.Any]) + case _: EventProp[?] => + case _ => el.setAttribute(mod.key.name, mod.value.toString) + } + } private def renderElement( parent: dom.Element, element: Element[dom.Element] ): Unit = { val el = dom.document.createElement(element.tag.name) + element.mods.foreach(m => + modifierFunc(el, m) m.key match { case _: HtmlProp[?, ?] => - el.asInstanceOf[js.Dynamic] - .updateDynamic(m.key.name)(m.value().asInstanceOf[js.Any]) + if m.value.isInstanceOf[ReactiveValue[?, ?]] then { + m.value + .asInstanceOf[ReactiveValue[?, ?]] + .reactiveValue + .subscribe(value => { modifierFunc(el, m) }) + } case _: EventProp[?] => el.addEventListener( m.key.name, m.value.value.asInstanceOf[EventMod[?]].value ) case _ => - el.setAttribute(m.key.name, m.value().asInstanceOf[String]) + if m.value.isInstanceOf[ReactiveValue[?, ?]] then { + m.value + .asInstanceOf[ReactiveValue[?, ?]] + .reactiveValue + .subscribe(value => { modifierFunc(el, m) }) + } } ) - element.children.foreach { - case e: Element[dom.Element] => renderElement(el, e) - case s: Value[?, String] => el.append(s()) + + valueFunc(el, element.children) + element.children.foreach { case r: ReactiveValue[?, String] => + r.reactiveValue.subscribe(value => { valueFunc(el, element.children) }) } + parent.append(el) } } diff --git a/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala b/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala index 0c27425..1ebc06d 100644 --- a/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala +++ b/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala @@ -15,8 +15,8 @@ class StringRenderer(val ssr: Boolean) extends Renderer[String] { e.mods.map(m => s" ${m.key.name}=\"${m.value.toString}\"").mkString("") val bodyStr = e.children .map { - case c: Value[String] => - c() + case c: Value[?, String] => + c.toString case e: Element[?] => renderElement(e) }