There are two ways of creating virtual-DOM.
- Prefixed (recommended) - Importing DOM tags and attributes under prefixes is recommended.
Tags and tag attributes are namespaced;
tags under
<
(because<.div
looks similar to<div>
), and attributes under^
(because something concise was needed and you usually have many attributes which written on new lines all looks to point up back to the target tag).
Depending on whether you want HTML or SVG import one of:
import japgolly.scalajs.react.vdom.html_<^._
import japgolly.scalajs.react.vdom.svg_<^._
Example:
import japgolly.scalajs.react.vdom.html_<^._
<.ol(
^.id := "my-list",
^.lang := "en",
^.margin := 8.px,
<.li("Item 1"),
<.li("Item 2"))
- Global - You can import all DOM tags and attributes into the global namespace. Beware that doing so means that you will run into confusing error messages and IDE refactoring issues when you use names like
id
,a
,key
for your variables and parameters.
import japgolly.scalajs.react.vdom.all._
ol(
id := "my-list",
lang := "en",
margin := 8.px,
li("Item 1"),
li("Item 2"))
There are two ways of attaching event handlers to your virtual DOM.
A helpful way to remember which operator to use is to visualise the arrow stem:
With==>
the========
has a gap in the middle - it's a pipe for data to come through meaning it expectsEvent => Callback
.
With-->
the--------
has no gap - it's just a wire to aCallback
, no input required.
<attribute> --> <callback>
<attribute>
is a DOM attribute like onClick
, onChange
, etc.
<callback>
is a Callback
(see below) which doesn't need any input.
def onButtonPressed: Callback =
Callback.alert("The button was pressed!")
<.button(
^.onClick --> onButtonPressed,
"Press me!")
<attribute> ==> <handler>
<attribute>
is a DOM attribute like onClick
, onChange
, etc.
<handler>
is a Event => Callback
.
See event types for the actual types that events can be.
def onTextChange(e: ReactEventFromInput): Callback =
Callback.alert("Value received = " + e.target.value)
<.input(
^.`type` := "text",
^.value := currentValue,
^.onChange ==> onTextChange)
If your handler needs additional arguments, use currying so that the args you want to specify are on the left and the event is alone on the right.
def onTextChange(desc: String)(e: ReactEventFromInput): Callback =
Callback.alert(s"Value received for ${desc} = ${e.target.value}")
<.input(
^.`type` := "text",
^.value := currentValue,
^.onChange ==> onTextChange("name"))
-
when / unless - All VDOM has
.when(condition)
and.unless(condition)
that can be used to conditionally include/omit VDOM.def hasFocus: Boolean = ??? <.div( (^.color := "green").when(hasFocus), "I'm green when focused.")
-
Option / js.UndefOr - Append
.whenDefined
.val loggedInUser: Option[User] = ??? <.div( <.h3("Welcome"), loggedInUser.map(user => <.a(^.href := user.profileUrl, "My Profile") ).whenDefined )
This doesn't just work for
Option[vdom]
, you can also use it in place of.map
for improved readability and efficiency. The above example then becomes:val loggedInUser: Option[User] = ??? <.div( <.h3("Welcome"), loggedInUser.whenDefined(user => <.a(^.href := user.profileUrl, "My Profile") ) )
-
Event handler callbacks - Append
?
to-->
/==>
operators, and wrap the callback inOption
orjs.UndefOr
.val currentValue: Option[String] = ??? def onTextChange(e: ReactEventFromInput): Option[Callback] = currentValue.map { before => val after = e.target.value Callback.alert(s"Value changed from [$before] to [$after]") } <.input.text( ^.value := currentValue.getOrElse(""), ^.onChange ==>? onTextChange)
-
Attribute values - Append
?
to the:=
operator, and wrap the value inOption
orjs.UndefOr
.val altText: Option[String] = ??? <.img( ^.src := ???, ^.href := ???, ^.alt :=? altText)
-
Manual - You can also manully write conditional code by using
EmptyVdom
to represent nothing.<.div(if (allowEdit) editButton else EmptyVdom)
You have two options of using collections of VDOM:
- Use a
VdomArray
. React expects a key on each element. Helps Reacts diff'ing mechanism. There are various ways to do this:
- Call
.toVdomArray
on your collection. - If you find yourself with
.map(...).toVdomArray
, replace it with just.toVdomArray(...)
for improved readability and efficiency. - Call
VdomArray.empty()
to get an empty array, add to it via+=
and++=
, then use the array directly in your VDOM.
- Flatten the collection into a
TagMod
. There are various ways to do this:
- Call
.toTagMod
on your collection. - If you find yourself with
.map(...).toTagMod
, replace it with just.toTagMod(...)
for improved readability and efficiency. - Create the collection using
TagMod(a, b, c, d)
. You'll need to do this if elements have different types, egVdomTags
and rendered components.
Examples:
def allColumns: List[Column] = ???
def renderColumn(c: Column): VdomElement = ???
// Flat, no keys
<.div( allColumns.map(renderColumn).toTagMod )
// Flat, no keys, more efficient
<.div( allColumns.toTagMod(renderColumn) )
// Array, expects keys
<.div( allColumns.map(renderColumn).toVdomArray )
// Array, expects keys, more efficient
<.div( allColumns.toVdomArray(renderColumn) )
Manual array usage:
val array = VdomArray.empty()
for (d <- someData) {
val fullLabel = ...
val vdom = <.div(^.key := fullLabel, ...)
array += vdom
}
if (someCondition)
array += footer(...)
<.div(
<.h1("HELLO!"),
array)
val customAttr = VdomAttr("customAttr")
val customStyle = VdomStyle("customStyle")
val customHtmlTag = HtmlTag("customTag")
customTag(customAttr := "hello", customStyle := "123", "bye")
↳ produces ↴
<customTag customAttr="hello" style="customStyle:123;">bye</customTag>
In addition to HtmlTag(…)
, there is also SvgTag(…)
, HtmlTagTo[N](…)
, SvgTagTo[N](…)
.
The most important types are probably TagMod
and VdomElement
.
Type | Explaination |
---|---|
VdomElement |
A single VDOM tag. This can be a tag like <div> , or a rendered component. This is also the result of components' .render methods. |
VdomNode |
A single piece of VDOM. Can be a VdomElement , or a piece of text, a number, etc. |
VdomArray |
An array of VDOM nodes. This is passed to React as an array which helps Reacts diff'ing mechanism. React also requires that each array element have a key. |
VdomAttr |
A tag attribute (including styles). Examples: href , value , onClick , margin . |
VdomTagOf[Node] |
A HTML or SVG tag of type Node . |
VdomTag |
A HTML or SVG tag. |
HtmlTagOf[Node] |
A HTML tag of type Node . |
HtmlTag |
A HTML tag. |
SvgTagOf[Node] |
An SVG tag of type Node . |
SvgTag |
An SVG tag. |
TagMod |
Tag-Modifier. A modification to a VdomTag . All of the types here can be a TagMod because they can all be used to modify a VdomTag . This is very useful for reuse and abstraction in practice, very useful for separating DOM functionality, asthetics and content. For example, it allows a function to return a child tag, a style and some event handlers which the function caller can then apply to some external tag. |
Examples:
import japgolly.scalajs.react.vdom.all._
val tag1 : VdomTag = input(className := "name")
val mod1 : TagMod = value := "Bob"
val mod2 : TagMod = TagMod(mod1, `type` := "text", title := "hello!")
val tag2 : VdomTag = tag1(mod2, readOnly := true)
val element: VdomElement = tag2
// equivalent to
// <input class="name" value="Bob" type="text", title := "hello!" readonly=true />
Category | Expressions | Result Type |
---|---|---|
Values | Unmounted component VdomTag raw.ReactElement |
VdomElement |
Values | Primatives & String PropsChildren VdomArray VdomElement raw.ReactNode |
VdomNode |
Values | VdomNode EmptyVdom |
TagMod |
Attributes | vdomAttr := value eventHandler --> callback eventHandler ==> (event => callback) |
TagMod |
Conditional Values |
tagMod.when(condition) tagMod.unless(condition) Option[tagMod].whenDefined |
TagMod |
Conditional Attributes |
vdomAttr :=? Option[value] eventHandler -->? Option[callback] eventHandler ==>? Option[event => callback] |
TagMod |
Composition | vdomTag(tagMod*) |
VdomTag |
Composition | TagMod(tagMod*) tagMod(tagMod*) |
TagMod |
Collections (keyed array) |
Seq[A].toVdomArray(A => vdomNode) Seq[vdomNode].toVdomArray VdomArray(vdomNode*) VdomArray.empty() += … ++= … |
VdomArray |
Collections (flatten) |
Seq[A].toTagMod(A => tagMod) Seq[tagMod].toTagMod TagMod(tagMod*) |
TagMod |