feat: reactivity 1.

This commit is contained in:
DavidOnTop 2024-09-24 06:28:42 +02:00
parent 8c9893846c
commit 131f90fcd8
No known key found for this signature in database
GPG key ID: 5D05538A45D5149F
19 changed files with 153 additions and 78 deletions

View file

@ -6,6 +6,6 @@ import top.davidon.sfs.dom.tags.Tag
/** tag + modifiers + value */ /** tag + modifiers + value */
class Element[+Ref <: org.scalajs.dom.Element]( class Element[+Ref <: org.scalajs.dom.Element](
val tag: Tag[Ref], val tag: Tag[Ref],
val mods: Iterable[Modifier[?, ?]], val mods: Iterable[Modifier[?]],
val children: Seq[Element[?] | Value[?, String]] val children: Seq[Element[?] | Value[String]]
) {} ) {}

View file

@ -5,5 +5,5 @@ import top.davidon.sfs.dom.mods.Modifier
trait ReactiveRenderer { trait ReactiveRenderer {
def valueFunc[F](element: dom.Element, value: F): Unit def valueFunc[F](element: dom.Element, value: F): Unit
def modifierFunc[F, T](modifier: Modifier[F, T], value: F): Unit def modifierFunc[F, T](modifier: Modifier[T], value: F): Unit
} }

View file

@ -6,6 +6,7 @@ import top.davidon.sfs.dom.defs.complex.{ComplexHtmlKeys, ComplexSvgKeys}
import top.davidon.sfs.dom.defs.eventProps.GlobalEventProps import top.davidon.sfs.dom.defs.eventProps.GlobalEventProps
import top.davidon.sfs.dom.defs.props.HtmlProps import top.davidon.sfs.dom.defs.props.HtmlProps
import top.davidon.sfs.dom.defs.tags.{HtmlTags, SvgTags} import top.davidon.sfs.dom.defs.tags.{HtmlTags, SvgTags}
import top.davidon.sfs.dom.plain.PlainValue
trait ScalaFullStackDOM trait ScalaFullStackDOM
extends HtmlTags extends HtmlTags
@ -20,23 +21,24 @@ trait ScalaFullStackDOM
object svg extends SvgTags with SvgAttrs with ComplexSvgKeys object svg extends SvgTags with SvgAttrs with ComplexSvgKeys
given strToVal: Conversion[String, Value[String, String]] with { given strToVal: Conversion[String, Value[String]] with {
def apply(from: String): Value[String, String] = def apply(from: String): PlainValue[String, String] =
Value(from, StringAsIsCodec) PlainValue(from, StringAsIsCodec)
} }
given intToVal: Conversion[Int, Value[Int, String]] with { given intToVal: Conversion[Int, Value[String]] with {
def apply(from: Int): Value[Int, String] = Value(from, IntAsStringCodec) def apply(from: Int): PlainValue[Int, String] =
PlainValue(from, IntAsStringCodec)
} }
given doubleToVal: Conversion[Double, Value[Double, String]] with { given doubleToVal: Conversion[Double, Value[String]] with {
def apply(from: Double): Value[Double, String] = def apply(from: Double): PlainValue[Double, String] =
Value(from, DoubleAsStringCodec) PlainValue(from, DoubleAsStringCodec)
} }
given longToVal: Conversion[Long, Value[Long, String]] with { given longToVal: Conversion[Long, Value[String]] with {
def apply(from: Long): Value[Long, String] = Value(from, LongAsStringCodec) def apply(from: Long): PlainValue[Long, String] =
PlainValue(from, LongAsStringCodec)
} }
given iterToVal: Conversion[Iterable[String], Value[Iterable[String], String]] given iterToVal: Conversion[Iterable[String], Value[String]] with {
with { def apply(from: Iterable[String]): PlainValue[Iterable[String], String] =
def apply(from: Iterable[String]): Value[Iterable[String], String] = PlainValue(from, IterableAsSpaceSeparatedStringCodec)
Value(from, IterableAsSpaceSeparatedStringCodec)
} }
} }

View file

@ -1,5 +0,0 @@
package top.davidon.sfs.dom
trait TriggerableValue[T] {
def trigger[T](value: T): Unit
}

View file

@ -1,36 +1,41 @@
package top.davidon.sfs.dom package top.davidon.sfs.dom
import top.davidon.sfs.dom.codecs.{Codec, StringAsIsCodec} import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.reactive.Observable
class Value[F, T]( trait Value[T] {
val value: F, def apply(): T
val codec: Codec[F, 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")
}
} }
object Value { //class Value[F, T](
def join( // val value: F,
iterator: Iterable[Value[?, String]] // val codec: Codec[F, T]
): Value[String, String] = { //) {
Value( // def apply(): T = {
iterator.map(v => v.toString).mkString(""), // codec.encode(value)
StringAsIsCodec // }
) //
} // 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")
// }
//}
//
//object Value {
// def join(
// iterator: Iterable[Value[?, String]]
// ): Value[String, String] = {
// Value(
// iterator.map(v => v.toString).mkString(""),
// StringAsIsCodec
// )
// }
//
//}

View file

@ -2,6 +2,7 @@ package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.{Codec, StringCodec} import top.davidon.sfs.dom.codecs.{Codec, StringCodec}
import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.Value
import top.davidon.sfs.dom.mods.Modifier import top.davidon.sfs.dom.mods.Modifier
import top.davidon.sfs.dom.plain.PlainValue
class AriaAttr[V]( class AriaAttr[V](
suffix: String, suffix: String,
@ -9,11 +10,11 @@ class AriaAttr[V](
) extends Key { ) extends Key {
override val name: String = "aria-" + suffix override val name: String = "aria-" + suffix
@inline def apply(value: V): Modifier[V, String] = { @inline def apply(value: V): Modifier[String] = {
this := value this := value
} }
def :=(value: V): Modifier[V, String] = { def :=(value: V): Modifier[String] = {
Modifier(this, Value(value, codec)) Modifier(this, PlainValue(value, codec))
} }
} }

View file

@ -1,9 +1,11 @@
package top.davidon.sfs.dom.keys package top.davidon.sfs.dom.keys
import org.scalajs.dom import org.scalajs.dom
import top.davidon.sfs.dom.Value
import top.davidon.sfs.dom.codecs.{EmptyCodec, StringAsIsCodec} import top.davidon.sfs.dom.codecs.{EmptyCodec, StringAsIsCodec}
import top.davidon.sfs.dom.mods.{EventMod, Modifier} import top.davidon.sfs.dom.mods.{EventMod, Modifier}
import top.davidon.sfs.dom.{TriggerableValue, Value} import top.davidon.sfs.dom.plain.PlainValue
import top.davidon.sfs.dom.reactive.TriggerableValue
class EventProp[E <: dom.Event](override val name: String) extends Key { class EventProp[E <: dom.Event](override val name: String) extends Key {
@ -11,23 +13,23 @@ class EventProp[E <: dom.Event](override val name: String) extends Key {
* @param value * @param value
* @return * @return
*/ */
def :=(value: E => Unit): Modifier[EventMod[E], Unit] = { def :=(value: E => Unit): Modifier[Unit] = {
Modifier(this, Value(EventMod(this, value), EmptyCodec())) Modifier(this, PlainValue(EventMod(this, value), EmptyCodec()))
} }
/** Don't use with StringRenderer and ssr off/false /** Don't use with StringRenderer and ssr off/false
* @param value * @param value
* @return * @return
*/ */
def :=(value: TriggerableValue[E]): Modifier[EventMod[E], Unit] = { def :=(value: TriggerableValue[E]): Modifier[Unit] = {
Modifier(this, Value(EventMod(this, value.trigger), EmptyCodec())) Modifier(this, PlainValue(EventMod(this, value.trigger), EmptyCodec()))
} }
/** Only use with StringRenderer and ssr off/false /** Only use with StringRenderer and ssr off/false
* @param value * @param value
* @return * @return
*/ */
def :=(value: String): Modifier[String, String] = { def :=(value: String): Modifier[String] = {
Modifier(this, Value(value, StringAsIsCodec)) Modifier(this, PlainValue(value, StringAsIsCodec))
} }
} }

View file

@ -3,16 +3,17 @@ package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.{Codec, StringCodec} import top.davidon.sfs.dom.codecs.{Codec, StringCodec}
import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.Value
import top.davidon.sfs.dom.mods.Modifier import top.davidon.sfs.dom.mods.Modifier
import top.davidon.sfs.dom.plain.PlainValue
class HtmlAttr[V]( class HtmlAttr[V](
override val name: String, override val name: String,
val codec: StringCodec[V] val codec: StringCodec[V]
) extends Key { ) extends Key {
@inline def apply(value: V): Modifier[V, String] = { @inline def apply(value: V): Modifier[String] = {
this := value this := value
} }
def :=(value: V): Modifier[V, String] = { def :=(value: V): Modifier[String] = {
Modifier(this, Value(value, codec)) Modifier(this, PlainValue(value, codec))
} }
} }

View file

@ -2,16 +2,17 @@ package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.Value
import top.davidon.sfs.dom.mods.Modifier import top.davidon.sfs.dom.mods.Modifier
import top.davidon.sfs.dom.plain.PlainValue
class HtmlProp[V, DomV]( class HtmlProp[V, DomV](
override val name: String, override val name: String,
val codec: Codec[V, DomV] val codec: Codec[V, DomV]
) extends Key { ) extends Key {
@inline def apply(value: V): Modifier[V, DomV] = { @inline def apply(value: V): Modifier[DomV] = {
this := value this := value
} }
def :=(value: V): Modifier[V, DomV] = { def :=(value: V): Modifier[DomV] = {
Modifier(this, Value(value, codec)) Modifier(this, PlainValue(value, codec))
} }
} }

View file

@ -2,6 +2,7 @@ package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.{Codec, StringCodec} import top.davidon.sfs.dom.codecs.{Codec, StringCodec}
import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.Value
import top.davidon.sfs.dom.mods.Modifier import top.davidon.sfs.dom.mods.Modifier
import top.davidon.sfs.dom.plain.PlainValue
class SvgAttr[V]( class SvgAttr[V](
val localName: String, val localName: String,
@ -13,12 +14,12 @@ class SvgAttr[V](
val namespaceUri: Option[String] = namespacePrefix.map(SvgAttr.namespaceUri) val namespaceUri: Option[String] = namespacePrefix.map(SvgAttr.namespaceUri)
@inline def apply(value: V): Modifier[V, String] = { @inline def apply(value: V): Modifier[String] = {
this := value this := value
} }
def :=(value: V): Modifier[V, String] = { def :=(value: V): Modifier[String] = {
Modifier(this, Value(value, codec)) Modifier(this, PlainValue(value, codec))
} }
} }

View file

@ -4,7 +4,7 @@ import top.davidon.sfs.dom.Value
import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.keys.Key import top.davidon.sfs.dom.keys.Key
class Modifier[F, T]( class Modifier[T](
val key: Key, val key: Key,
val value: Value[F, T] val value: Value[T]
) {} ) {}

View file

@ -0,0 +1,5 @@
package top.davidon.sfs.dom.plain
class PlainModifier {
}

View file

@ -0,0 +1,25 @@
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")
}
}

View file

@ -0,0 +1,9 @@
package top.davidon.sfs.dom.reactive
trait Observable[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
}

View file

@ -0,0 +1,5 @@
package top.davidon.sfs.dom.reactive
class ReactiveModifier {
}

View file

@ -0,0 +1,18 @@
package top.davidon.sfs.dom.reactive
import top.davidon.sfs.dom.Value
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())
}
def toValueNow: PlainValue[F, T] = {
PlainValue(value.now(), codec)
}
}

View file

@ -0,0 +1,5 @@
package top.davidon.sfs.dom.reactive
trait TriggerableValue[T] {
def trigger(value: T): Unit
}

View file

@ -9,9 +9,9 @@ trait Tag[+Ref <: dom.Element] {
val void: Boolean val void: Boolean
def apply( def apply(
modifiers: Modifier[?, ?]* modifiers: Modifier[?]*
)( )(
values: Element[?] | Value[?, String]* values: Element[?] | Value[String]*
): Element[Ref] = { ): Element[Ref] = {
Element[Ref](this, modifiers, values.toSeq) Element[Ref](this, modifiers, values.toSeq)
} }

View file

@ -15,10 +15,10 @@ class StringRenderer(val ssr: Boolean) extends Renderer[String] {
e.mods.map(m => s" ${m.key.name}=\"${m.value.toString}\"").mkString("") e.mods.map(m => s" ${m.key.name}=\"${m.value.toString}\"").mkString("")
val bodyStr = e.children val bodyStr = e.children
.map { .map {
case c: Value[String] =>
c()
case e: Element[?] => case e: Element[?] =>
renderElement(e) renderElement(e)
case c: Value[?, String] =>
c()
} }
.mkString(" ") .mkString(" ")
s"<${e.tag.name}$modsStr>$bodyStr${ s"<${e.tag.name}$modsStr>$bodyStr${