feat: reactivity???
This commit is contained in:
parent
131f90fcd8
commit
07e4e4006a
26 changed files with 252 additions and 228 deletions
|
@ -1,4 +1,4 @@
|
|||
ThisBuild / scalaVersion := "3.5.1"
|
||||
ThisBuild / scalaVersion := "3.5.2"
|
||||
ThisBuild / versionScheme := Some("semver-spec")
|
||||
|
||||
ThisBuild / publishMavenStyle := true
|
||||
|
|
|
@ -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]]
|
||||
) {}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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](
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package top.davidon.sfs.dom.codecs
|
||||
|
||||
trait Coder[F, T] {
|
||||
def apply(value: F): T
|
||||
}
|
|
@ -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](
|
||||
* => (),
|
||||
* => (),
|
||||
* => ""
|
||||
)
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package top.davidon.sfs.dom.codecs
|
||||
|
||||
//trait StringCodec[T] extends Codec[T, String] {}
|
||||
type StringCodec[T] = Codec[T, String]
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package top.davidon.sfs.dom.reactive
|
||||
|
||||
trait TriggerableValue[T] {
|
||||
def trigger(value: T): Unit
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue