Skip to content

Commit

Permalink
Merge pull request #28 from buntec/make-vnodedata-immutable
Browse files Browse the repository at this point in the history
Make `VNodeData` immutable
  • Loading branch information
buntec authored May 14, 2022
2 parents 91ac5ea + 0c05538 commit bf3ce4e
Show file tree
Hide file tree
Showing 23 changed files with 314 additions and 449 deletions.
20 changes: 8 additions & 12 deletions examples/src/main/scala/snabbdom/examples/Example.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,17 @@ object Example {

val vnode = h(
"div",
VNodeData.builder
.withOn("click" -> ((_: dom.Event) => println("foo")))
.build,
VNodeData(on = Map("click" -> ((_: dom.Event) => println("foo")))),
Array[VNode](
h(
"span",
VNodeData.builder.withStyle("fontWeight" -> "bold").build,
VNodeData(style = Map("fontWeight" -> "bold")),
"This is bold"
),
" and this is just normal text",
h(
"a",
VNodeData.builder.withProps("href" -> "/foo").build,
VNodeData(props = Map("href" -> "/foo")),
"I'll take you places!"
)
)
Expand All @@ -62,21 +60,19 @@ object Example {

val newVnode = h(
"div#container.two.classes",
VNodeData.builder
.withOn("click" -> ((_: dom.Event) => println("bar")))
.build,
VNodeData(on = Map("click" -> ((_: dom.Event) => println("bar")))),
Array[VNode](
h(
"span",
VNodeData.builder
.withStyle("fontWeight" -> "normal", "fontStyle" -> "italic")
.build,
VNodeData(style =
Map("fontWeight" -> "normal", "fontStyle" -> "italic")
),
"This is now italic type"
),
" and this is still just normal text",
h(
"a",
VNodeData.builder.withProps("href" -> "/foo").build,
VNodeData(props = Map("href" -> "/foo")),
"I'll take you places!"
)
)
Expand Down
4 changes: 2 additions & 2 deletions snabbdom/src/main/scala/snabbdom/Listener.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Listener(var vnode: VNode) {

def handleEvent(event: dom.Event): Unit = {
val name = event.`type`
vnode.data.flatMap(_.on).flatMap(_.get(name)).foreach { handler =>
vnode.data.on.get(name).foreach { handler =>
handler.cbs.foreach(cb => cb(event, vnode))
}
}
Expand All @@ -33,6 +33,6 @@ class Listener(var vnode: VNode) {
* listener and using `handleEvent` directly would result
* in a new implicit conversion to `js.Function1` every time.
*/
val jsFun: js.Function1[dom.Event, Unit] = handleEvent _
private[snabbdom] val jsFun: js.Function1[dom.Event, Unit] = handleEvent _

}
22 changes: 15 additions & 7 deletions snabbdom/src/main/scala/snabbdom/VNode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,36 @@ import org.scalajs.dom

class VNode private (
var sel: Option[String],
var data: Option[VNodeData],
var data: VNodeData,
var children: Option[Array[VNode]],
var elm: Option[
dom.Node
], // can't be `dom.Element` unfortunately b/c of fragments
var text: Option[String],
var key: Option[KeyValue],
var listener: Option[Listener]
)
val key: Option[KeyValue],
private[snabbdom] var listener: Option[Listener]
) {

override def toString: String =
s"sel=$sel, data=$data, text=$text, key=$key, children=$children, elm=$elm"

private[snabbdom] def isTextNode: Boolean =
sel.isEmpty && children.isEmpty && text.isDefined

}

object VNode {

def create(
sel: Option[String],
data: Option[VNodeData],
data: VNodeData,
children: Option[Array[VNode]],
text: Option[String],
elm: Option[dom.Node]
) = new VNode(sel, data, children, elm, text, data.flatMap(_.key), None)
) = new VNode(sel, data, children, elm, text, data.key, None)

def text(text: String) =
new VNode(None, None, None, None, Some(text), None, None)
new VNode(None, VNodeData.empty, None, None, Some(text), None, None)

implicit def fromString(s: String): VNode = text(s)

Expand Down
95 changes: 14 additions & 81 deletions snabbdom/src/main/scala/snabbdom/VNodeData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,90 +38,23 @@

package snabbdom

class VNodeData(
var props: Option[Map[String, PropValue]],
var attrs: Option[Map[String, AttrValue]],
var classes: Option[Map[String, ClassValue]],
var style: Option[Map[String, StyleValue]],
var dataset: Option[Map[String, String]],
var on: Option[Map[String, EventHandler]],
var hook: Option[Hooks],
var key: Option[String],
var ns: Option[String], // for SVG
var fn: Option[Seq[Any] => VNode], // for thunks
var args: Option[Seq[Any]], // for thunks
var is: Option[String]
case class VNodeData(
props: Map[String, PropValue] = Map.empty,
attrs: Map[String, AttrValue] = Map.empty,
classes: Map[String, ClassValue] = Map.empty,
style: Map[String, StyleValue] = Map.empty,
dataset: Map[String, String] = Map.empty,
on: Map[String, EventHandler] = Map.empty,
hook: Option[Hooks] = None,
key: Option[String] = None,
ns: Option[String] = None, // for SVG
fn: Option[Seq[Any] => VNode] = None, // for thunks
args: Option[Seq[Any]] = None, // for thunks
is: Option[String] = None
)

object VNodeData {

def empty =
new VNodeData(
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None
)

def builder = new Builder()

class Builder(private val data: VNodeData = empty) extends AnyVal {

@inline def build: VNodeData = data

@inline def withKey(key: String): Builder = {
data.key = Some(key)
this
}

@inline def withProps(props: (String, PropValue)*): Builder = {
data.props = Some(props.toMap)
this
}

@inline def withAttrs(attrs: (String, AttrValue)*): Builder = {
data.attrs = Some(attrs.toMap)
this
}

@inline def withClasses(classes: (String, ClassValue)*): Builder = {
data.classes = Some(classes.toMap)
this
}

@inline def withStyle(style: (String, StyleValue)*): Builder = {
data.style = Some(style.toMap)
this
}

@inline def withDataset(dataset: (String, String)*): Builder = {
data.dataset = Some(dataset.toMap)
this
}

@inline def withOn(on: (String, EventHandler)*): Builder = {
data.on = Some(on.toMap)
this
}

@inline def withHook(hook: Hooks): Builder = {
data.hook = Some(hook)
this
}

@inline def withNs(ns: String): Builder = {
data.ns = Some(ns)
this
}

}
val empty = VNodeData()

}
2 changes: 1 addition & 1 deletion snabbdom/src/main/scala/snabbdom/fragment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ package snabbdom
object fragment {

def apply(children: Array[VNode]): VNode = {
VNode.create(None, None, Some(children), None, None)
VNode.create(None, VNodeData.empty, Some(children), None, None)
}

}
32 changes: 16 additions & 16 deletions snabbdom/src/main/scala/snabbdom/h.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,28 @@ object h {
children: Option[Array[VNode]],
text: Option[String]
): VNode = {
val data0 = data.getOrElse(VNodeData.empty)
val vnode =
VNode.create(
Some(sel),
data.getOrElse(VNodeData.empty),
children,
text,
None
)
if (
sel(0) == 's' && sel(1) == 'v' && sel(2) == 'g' &&
sel.startsWith("svg") &&
(sel.length == 3 || sel(3) == '.' || sel(3) == '#')
) {
addNS(data0, children, Some(sel))
addNS(vnode)
}
VNode.create(Some(sel), Some(data0), children, text, None)
vnode
}

private[snabbdom] def addNS(
data: VNodeData,
children: Option[Array[VNode]],
sel: Option[String]
): Unit = {
data.ns = Some("http://www.w3.org/2000/svg")
if (sel.forall(_ != "foreignObject")) {
children.foreach {
_.map { child =>
child.data.foreach(data => addNS(data, child.children, child.sel))
}
}
private[snabbdom] def addNS(vnode: VNode): Unit = {
val ns = "http://www.w3.org/2000/svg"
vnode.data = vnode.data.copy(ns = Some(ns))
if (vnode.sel.forall(_ != "foreignObject")) {
vnode.children.foreach(_.map(addNS))
}
}

Expand Down
Loading

0 comments on commit bf3ce4e

Please sign in to comment.