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.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.text(
^.value := currentValue,
^.onChange ==> onTextChange("name"))
-
Optional nodes/elements - Since React 16 (and scalajs-react 1.2.0), optional
VdomNode
andVdomElement
s can be used directly.val loginButton : js.UndefOr[VdomNode] = ??? val logoutButton: Option[VdomNode] = ??? <.div( <.a(^.href := "/contact", "Contact Us"), loginButton, logoutButton)
-
Optional tag-modifiers - Unlike optional nodes above, optional tag-modifiers (
TagMod
) cannot be used directly; React itself doesn't allow you to have a component that renders justclass="btn-large"
, nor would it make sense. To work with optional tag-modifiers, append.whenDefined
to yourOption
/js.UndefOr
.val loggedInUser: Option[User] = ??? <.div( <.h3("Welcome"), loggedInUser.map(user => TagMod( ^.cls := "user-logged-in", <.a(^.href := user.profileUrl, "My Profile"))) .whenDefined)
You can also use
.whenDefined
in place of.map
for improved readability and efficiency. The above example then becomes:val loggedInUser: Option[User] = ??? <.div( <.h3("Welcome"), loggedInUser.whenDefined(user => TagMod( ^.cls := "user-logged-in", <.a(^.href := user.profileUrl, "My Profile"))))
-
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.")
It's noteworthy that using this approach will result in VDOM being created before it determines whether it will be used or thrown away. Performance should be maintained via the approaches described in PERFORMANCE.md so you shouldn't need to worry about optimisation of VDOM construction at this level on impact but you've been informed and it's up to you to evaluate your code and environment.
-
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 := "blah", ^.href := "blah", ^.alt :=? altText)
-
Manually - You can also write conditional VDOM using manual
if-then-else
expressions.For VDOM, use
EmptyVdom
:<.div(if (allowEdit) <.button("...") else EmptyVdom)
For tag-modifiers, use
TagMod.empty
<.div(if (hide) ^.display.none else TagMod.empty)
You have a few ways of using collections of VDOM:
-
VdomArray - An array of VDOM nodes.
Properties:
- Mutable; don't let it escape a local pure function
- React expects a key on each element (which helps React's diff'ing mechanism)
- Itself, cannot be assigned a key.
There are various ways to construct one:
- 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.
-
ReactFragment - A sequence of VDOM nodes. (new as of React 16)
Properties:
- Immutable
- Elements may, but needn't have keys
- Itself, can be assigned a key
There are two ways to construct one, (both after importing vdom):
- Call
ReactFragment(...)
- Call
ReactFragment.withKey(...)
-
Flatten a Scala collection into a
TagMod
.There are various ways to do this:
- Call
.toTagMod
on your collection. - Call
.mkTagMod(sep)
on your collection which is like.mkString(sep)
in Scala stdlib. - Call
.mkTagMod(start,sep,end)
on your collection which is like.mkString(start,sep,end)
in Scala stdlib. - 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.
- Call
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)
-
Flatten a Scala collection into a
ReactFragment
.There are various ways to do this:
- Call
.toReactFragment
on your collection. - Call
.mkReactFragment(sep)
on your collection which is like.mkString(sep)
in Scala stdlib. - Call
.mkReactFragment(start,sep,end)
on your collection which is like.mkString(start,sep,end)
in Scala stdlib.
- Call
val customAttr = VdomAttr("customAttr")
val customStyle = VdomStyle("customStyle")
val customHtmlTag = HtmlTag("customTag")
customHtmlTag(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 (like <div> ), or a rendered component. |
VdomNode |
A single piece of VDOM. Can be a VdomElement , or a piece of text, a number, etc.This is also the result of components' .render methods. |
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 .It cannot be returned from a component's render function. 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 | Some component VdomTag raw.ReactElement VdomElement.static(VdomElement) |
VdomElement |
Values | Numbers String PropsChildren VdomArray VdomElement EmptyVdom raw.ReactNode VdomNode.static(VdomNode) |
VdomNode |
Values | VdomNode TagMod.empty |
TagMod |
Attributes | vdomAttr := value eventHandler --> callback eventHandler ==> (event => callback) |
TagMod |
Conditional Values |
Option[vdomNode] js.UndefOr[vdomNode] EmptyVdom |
VdomNode |
Conditional Values |
tagMod.when(condition) tagMod.unless(condition) Option[tagMod].whenDefined TagMod.empty |
TagMod |
Conditional Attributes |
vdomAttr :=? Option[value] eventHandler -->? Option[callback] eventHandler ==>? Option[event => callback] |
TagMod |
Composition | vdomTag(tagMod*) |
VdomTag |
Composition | TagMod(tagMod*) |
TagMod |
Collections (keyed array) |
Seq[A].toVdomArray(A => vdomNode) Seq[vdomNode].toVdomArray VdomArray(vdomNode*) VdomArray.empty() += … ++= … |
VdomArray |
Collections (fragment) |
ReactFragment(VdomNode*) |
VdomElement |
Collections (flatten) |
Seq[A].mkReactFragment(...) Seq[A].toReactFragment(A => vdomNode) Seq[vdomNode].toReactFragment |
ReactFragment |
Collections (flatten) |
Seq[A].mkTagMod(...) Seq[A].toTagMod(A => tagMod) Seq[tagMod].toTagMod TagMod(tagMod*) |
TagMod |