Skip to content

Commit 8ae29d3

Browse files
committed
Add TagMod.toJs and ToJs.childrenAsVdomNodes
1 parent b9fa89c commit 8ae29d3

File tree

5 files changed

+76
-8
lines changed

5 files changed

+76
-8
lines changed

core/src/main/scala/japgolly/scalajs/react/vdom/Builder.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ object Builder {
3939

4040
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4141

42+
/**
43+
* Raw JS values of:
44+
* - className
45+
* - props
46+
* - styles
47+
* - children
48+
*
49+
* Everything here is mutable.
50+
*
51+
* There are convenience methods to (mutably) add className and styles to props.
52+
*/
4253
trait ToJs extends Builder {
4354
// Exposing vars here is acceptable because:
4455
// 1. The contents are all mutable anyway and a defensive-copy cost isn't worth it
@@ -74,6 +85,20 @@ object Builder {
7485

7586
def addStyleToProps(): Unit =
7687
nonEmptyStyles.foreach(setObjectKeyValue(props, "style", _))
88+
89+
def childrenAsVdomNodes: List[VdomNode] = {
90+
import Implicits._
91+
var i = children.length
92+
var nodes = List.empty[VdomNode]
93+
while (i > 0) {
94+
i -= 1
95+
nodes ::= children(i)
96+
}
97+
nodes
98+
}
99+
100+
def nonEmptyChildrenAsVdomNodes: js.UndefOr[List[VdomNode]] =
101+
if (children.length == 0) js.undefined else childrenAsVdomNodes
77102
}
78103

79104
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

core/src/main/scala/japgolly/scalajs/react/vdom/Implicits.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,16 @@ object ImplicitsForVdomNode {
7777
trait ImplicitsForVdomNode {
7878
import ImplicitsForVdomNode._
7979

80-
implicit def vdomNodeFromLong (v: Long) : VdomNode = VdomNode.cast(v.toString)
81-
implicit def vdomNodeFromInt (v: Int) : VdomNode = VdomNode.cast(v)
82-
implicit def vdomNodeFromShort (v: Short) : VdomNode = VdomNode.cast(v)
83-
implicit def vdomNodeFromByte (v: Byte) : VdomNode = VdomNode.cast(v)
84-
implicit def vdomNodeFromDouble (v: Double) : VdomNode = VdomNode.cast(v)
85-
implicit def vdomNodeFromFloat (v: Float) : VdomNode = VdomNode.cast(v)
86-
implicit def vdomNodeFromString (v: String) : VdomNode = VdomNode.cast(v)
87-
implicit def vdomNodeFromPropsChildren(v: PropsChildren): VdomNode = VdomNode.cast(v.raw)
80+
implicit def vdomNodeFromLong (v: Long) : VdomNode = VdomNode.cast(v.toString)
81+
implicit def vdomNodeFromInt (v: Int) : VdomNode = VdomNode.cast(v)
82+
implicit def vdomNodeFromShort (v: Short) : VdomNode = VdomNode.cast(v)
83+
implicit def vdomNodeFromByte (v: Byte) : VdomNode = VdomNode.cast(v)
84+
implicit def vdomNodeFromDouble (v: Double) : VdomNode = VdomNode.cast(v)
85+
implicit def vdomNodeFromFloat (v: Float) : VdomNode = VdomNode.cast(v)
86+
implicit def vdomNodeFromString (v: String) : VdomNode = VdomNode.cast(v)
87+
implicit def vdomNodeFromReactNode (v: raw.ReactNode) : VdomNode = VdomNode(v)
88+
implicit def vdomNodeFromReactNodeList(v: raw.ReactNodeList): VdomNode = VdomNode.cast(v)
89+
implicit def vdomNodeFromPropsChildren(v: PropsChildren) : VdomNode = VdomNode.cast(v.raw)
8890

8991
implicit def vdomSeqExtForTO[A](as: TraversableOnce[A]) = new TraversableOnceExt[A](as)
9092
implicit def vdomSeqExtForSA[A](as: Array [A]) = new TraversableOnceExt[A](as)

core/src/main/scala/japgolly/scalajs/react/vdom/TagMod.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ trait TagMod {
2424

2525
def apply(ms: TagMod*): TagMod =
2626
TagMod.Composite((Vector.newBuilder[TagMod] += this ++= ms).result())
27+
28+
/**
29+
* Converts this VDOM and all its potential children into raw JS values.
30+
*
31+
* Meant for very advanced usage.
32+
*
33+
* Do not use this unless you know what you're doing (and you're doing something very funky)!
34+
*/
35+
final def toJs: Builder.ToJs = {
36+
val t = new Builder.ToJs {}
37+
applyTo(t)
38+
t
39+
}
2740
}
2841

2942
object TagMod {

doc/changelog/1.1.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
* `vdom.Builder.ToVdomElement` - builds a `VdomElement` which was the previous default
1414
* `vdom.Builder.ToJs` - provides raw JS values like `props: js.Object` and more
1515

16+
* `vdom.TagMod` now has a `.toJs` method which turns the VDOM tree into a bunch of raw JS values.
17+
This is for advanced hackers only.
18+
1619
* Opening links in new tabs using `target="_blank"` without `rel="noopener` is a
1720
[security risk and performance impediment](https://blog.dareboost.com/en/2017/03/target-blank-links-rel-noopener-performance-security/).
1821
Google's Lighthouse auditor [flags it](https://developers.google.com/web/tools/lighthouse/audits/noopener).
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package japgolly.scalajs.react.core.vdom
2+
3+
import japgolly.scalajs.react._
4+
import japgolly.scalajs.react.test.TestUtil._
5+
import japgolly.scalajs.react.vdom.html_<^._
6+
import utest._
7+
8+
object VdomTest extends TestSuite {
9+
10+
val C = ScalaComponent.static("")(<.br)
11+
val Span = ScalaComponent.builder[Unit]("").render_C(<.span(_)).build
12+
13+
override def tests = TestSuite {
14+
15+
'tagModToJs {
16+
'childrenAsVdomNodes {
17+
val vdom = TagMod("hehe", 123, <.em(456L), C())
18+
val expect = "<span>hehe123<em>456</em><br/></span>"
19+
assertRender(<.span(vdom), expect)
20+
assertRender(Span(vdom.toJs.childrenAsVdomNodes: _*), expect)
21+
}
22+
}
23+
24+
}
25+
}

0 commit comments

Comments
 (0)