Skip to content

Latest commit

 

History

History
206 lines (156 loc) · 6.28 KB

File metadata and controls

206 lines (156 loc) · 6.28 KB

Tree updates

Operations

A Node is equipped with a variety of functions to easily manipulate trees such as prepend, append, remove, clearAll, filter, flatMap, map and others. See the source code for an overview.

Referencing nodes

While the operations from the previous section allow you to modify the tree, they operate on either the root node or are applied recursively to all children.

If you would like to update a specific child further down in the hierarchy, Pine introduces the concept of tag references (TagRefs). These have the advantage that changes can be batched and applied efficiently.

In order to do so, you need to make the nodes you would like to reference identifiable, for example by setting the id attributes:

val node = html"""
  <div id="child">
    <span id="age"></span>
    <span id="name"></span>
  </div>
"""

Now you can reference these nodes using TagRefs. A TagRef takes the referenced tag's ID and HTML type:

val spanAge  = TagRef[tag.Span]("age")
val spanName = TagRef[tag.Span]("name")

There are more ways to reference nodes such as by class name or tag type. See section "Tag references".

Updating nodes

You can use the update() method to change the node:

val result = node.update { implicit ctx =>
  spanAge  := 42
  spanName := "Joe"
}

The changes (diffs) take an implicit rendering context. When you call update(), the changes will be queued up in the rendering context and processed in a batch.

result will be equivalent to:

<div id="child">
  <span id="age">42</span>
  <span id="name">Joe</span>
</div>

Replacing nodes

If you would like to replace the node itself, you can use replace():

val result = node.update { implicit ctx =>
  spanAge .replace(42)
  spanName.replace("Joe")
}

result will be equivalent to:

<div id="child">
  42
  Joe
</div>

Updating children

val node = html"""<div id="page"></div>"""
val root = TagRef[tag.Div]("page")

In order to render a list, you can use the := function (alias for set):

root.update { implicit ctx =>
  root := List(
    html"<div>Hello, </div>",
    html"<div>world!</div>"
  )
}

But if you would like to later access those child nodes they need unique IDs. This is particularly useful when you render your HTML on the server and want to access it in JavaScript, e.g. in order to attach event handlers.

First, we define a data type we would like to render:

case class Item(id: Int, name: String)

Next, we define a function that returns a child node given an item.

val itemView = html"""<div id="child"><span id="name"></span></div>"""
def idOf(item: Item): String = item.id.toString
def renderItem(item: Item): Tag[_] = {
  val id   = idOf(item)
  val node = itemView.suffixIds(id)
  val spanName = TagRef[tag.Span]("name", id)
  node.update(implicit ctx => spanName := item.name)
}

Finally, we render a list of items using the set method.

val items  = List(Item(0, "Joe"), Item(1, "Jeff"))
val result = node.update(implicit ctx => root.set(items.map(renderItem)))

result will be equivalent to:

<div id="page">
  <div id="child0">
    <span id="name0">Joe</span>
  </div>
  <div id="child1">
    <span id="name1">Jeff</span>
  </div>
</div>

Now, we can reference child nodes using a TagRef:

TagRef[tag.Div]("child", idOf(items.head))  // TagRef[tag.Child]("child0")

Updating attributes

As our TagRef objects are typed, we can provide implicits for supported attributes.

val node = html"""<a id="lnk">GitHub</a>"""
node.update(implicit ctx =>
  NodeRef[tag.A]("lnk").href := "https://github.com/"
)

Tag references

Tags can be referenced using:

  • ID attribute: TagRef[tag.A]("id")
  • Tag type: TagRef[tag.A]
  • Class name: TagRef.byClass[tag.A]("class-name")

A TagRef exposes methods for manipulating nodes and their attributes. See its source code for a full list of operations.

Diffs

A Diff is an immutable object which describes tree changes. It is instantiated for example by the TagRef operations you have seen before such as := (set), replace etc.

So far, these changes were performed directly on the tree. However, for the JavaScript back end, we have an additional rendering context that can apply those changes to the DOM. This will be explained in the next chapter.

The full list of supported diffs can be found here.

Multiple occurrences

If you would like to perform a change on all occurrences of a TagRef, use the each function:

val div = html"""<div><span></span><span></span></div>"""
div.update(implicit ctx =>
  TagRef["span"].each += html"<b>Hello</b>").toHtml
// <div><span><b>Hello</b></span><span><b>Hello</b></span></div>

each can also be used in conjunction with any other diff type, such as attribute updates:

val div  = html"""<div><a href="/a">A</a><a href="/b">B</a></div>"""
val html = div.update(implicit ctx =>
  TagRef["a"].each.href.update(_.map(url => s"$url/test"))).toHtml
// <div><a href="/a/test">A</a><a href="/b/test">B</a></div>

DSL

To facilitate interaction with nodes, Pine provides a small DSL with extensions.

For example, to toggle one or multiple CSS tags, use css():

val div = TagRef[tag.Div]("div")
div.css(false, "a", "b")  // Remove the CSS tags "a" and "b" from div

For toggling the visibility of a node, use hide():

div.hide(true)  // Sets `style` attribute to hide the element in the browser

Custom attributes

If you would like to support custom attributes, you can extend the functionality of any tag by defining an implicit class. This is the same approach which Pine uses internally to define attributes for HTML elements.

For example, to define attributes on anchor nodes, you would write:

implicit class TagRefAttributesA(tagRef: TagRef[tag.A]) {
  val dataTooltip = new Attribute[tag.A, Option[String], String](tagRef, "data-tooltip")
  val dataShow    = new Attribute[tag.A, Boolean, Boolean](tagRef, "data-show")
}