VirtualDom

API to the core diffing algorithm. Can serve as a foundation for libraries that expose more helper functions for HTML or SVG.

Create

type Node msg

An immutable chunk of data representing a DOM node. This can be HTML or SVG.

text : String -> Node msg

Just put plain text in the DOM. It will escape the string so that it appears exactly as you specify.

text "Hello World!"
node :
String
-> Array (Attribute msg)
-> Array (Node msg)
-> Node msg

Create a DOM node with a tag name, a list of HTML properties that can include styles and event listeners, a list of CSS properties like color, and a list of child nodes.

import Json.Encode as Json

hello : Node msg
hello =
  node "div" [] [ text "Hello!" ]

greeting : Node msg
greeting =
  node "div"
    [ property "id" (Json.string "greeting") ]
    [ text "Hello!" ]
nodeNS :
String
-> String
-> Array (Attribute msg)
-> Array (Node msg)
-> Node msg

Create a namespaced DOM node. For example, an SVG <path> node could be defined like this:

path : Array (Attribute msg) -> Array (Node msg) -> Node msg
path attrubutes children =
  nodeNS "http://www.w3.org/2000/svg" "path" attributes children

Attributes

type Attribute msg

When using HTML and JS, there are two ways to specify parts of a DOM node.

  1. Attributes — You can set things in HTML itself. So the class in <div class="greeting"></div> is called an attribute.

  2. Properties — You can also set things in JS. So the className in div.className = 'greeting' is called a property.

So the class attribute corresponds to the className property. At first glance, perhaps this distinction is defensible, but it gets much crazier. There is not always a one-to-one mapping between attributes and properties! Yes, that is a true fact. Sometimes an attribute exists, but there is no corresponding property. Sometimes changing an attribute does not change the underlying property. For example, as of this writing, the webkit-playsinline attribute can be used in HTML, but there is no corresponding property!

style : String -> String -> Attribute msg

Specify a style.

greeting : Node msg
greeting =
  node "div"
    [ style "backgroundColor" "red"
    , style "height" "90px"
    , style "width" "100%"
    ]
    [ text "Hello!"
    ]
property : String -> Value -> Attribute msg

Create a property.

import Json.Encode as Encode

buttonLabel : Node msg
buttonLabel =
  node "label" [ property "htmlFor" (Encode.string "button") ] [ text "Label" ]

Notice that you must give the property name, so we use htmlFor as it would be in JavaScript, not for as it would appear in HTML.

attribute : String -> String -> Attribute msg

Create an attribute. This uses JavaScript’s setAttribute function behind the scenes.

buttonLabel : Node msg
buttonLabel =
  node "label" [ attribute "for" "button" ] [ text "Label" ]

Notice that you must give the attribute name, so we use for as it would be in HTML, not htmlFor as it would appear in JS.

attributeNS : String -> String -> String -> Attribute msg

Would you believe that there is another way to do this?! This uses JavaScript's setAttributeNS function behind the scenes. It is doing pretty much the same thing as attribute but you are able to have namespaced attributes. As an example, the elm/svg package defines an attribute like this:

xlinkHref : String -> Attribute msg
xlinkHref value =
  attributeNS "http://www.w3.org/1999/xlink" "xlink:href" value

Events

on : String -> Handler msg -> Attribute msg

Create custom event handlers.

You can define onClick like this:

import Json.Decode as Decode

onClick : msg -> Attribute msg
onClick msg =
  on "click" (Normal (Decode.succeed msg))

Note: These event handlers trigger in the bubble phase. You can learn more about what that means here. There is not support within Gren for doing tricks with the capture phase. We recommend doing that in JS through ports.

type Handler msg
= Normal (Decoder msg)
| MayStopPropagation (Decoder { message : msg, stopPropagation : Bool })
| MayPreventDefault (Decoder { message : msg, preventDefault : Bool })
| Custom (Decoder { message : msg, stopPropagation : Bool, preventDefault : Bool })

When using on you can customize the event behavior a bit. There are two ways to do this:

  • stopPropagation means the event stops traveling through the DOM. So if propagation of a click is stopped, it will not trigger any other event listeners.

  • preventDefault means any built-in browser behavior related to the event is prevented. This can be handy with key presses or touch gestures.

Note 1: A passive event listener will be created if you use Normal or MayStopPropagation. In both cases preventDefault cannot be used, so we can enable optimizations for touch, scroll, and wheel events in some browsers.

Note 2: Some actions, like uploading and downloading files, are only allowed when the JavaScript event loop is running because of user input. This is for security! So when an event occurs, we call update and send any port messages immediately, all within the same tick of the event loop. This makes it possible to handle user-instigated events in ports.

Note 3: Normally the view is shown in the next requestAnimationFrame call. This allows us to save some work if messages are coming in very quickly. But if stopPropagation is used, we update the DOM immediately, within the same tick of the event loop. This is useful for DOM nodes that hold their own state, like <input type="text">. If someone types very fast, the state in the DOM can diverge from the state in your Model while waiting on the next requestAnimationFrame call. So updating the DOM synchronously makes this divergence impossible.

Routing Messages

map : (a -> msg) -> Node a -> Node msg

This function is useful when nesting components with the Elm Architecture. It lets you transform the messages produced by a subtree.

Say you have a node named button that produces () values when it is clicked. To get your model updating properly, you will probably want to tag this () value like this:

type Msg = Click | ...

update msg model =
  case msg of
    Click ->
      ...

view model =
  map (\_ -> Click) button

So now all the events produced by button will be transformed to be of type Msg so they can be handled by your update function!

mapAttribute : (a -> b) -> Attribute a -> Attribute b

Transform the messages produced by a Attribute.

Keyed Nodes

keyedNode :
String
-> Array (Attribute msg)
-> Array { key : String, node : Node msg }
-> Node msg

Works just like node, but you add a unique identifier to each child node. You want this when you have a list of nodes that is changing: adding nodes, removing nodes, etc. In these cases, the unique identifiers help make the DOM modifications more efficient.

keyedNodeNS :
String
-> String
-> Array (Attribute msg)
-> Array { key : String, node : Node msg }
-> Node msg

Create a keyed and namespaced DOM node. For example, an SVG <g> node could be defined like this:

g : Array (Attribute msg) -> Array ( String, Node msg ) -> Node msg
g =
  keyedNodeNS "http://www.w3.org/2000/svg" "g"

Lazy Nodes

lazy : (a -> Node msg) -> a -> Node msg

A performance optimization that delays the building of virtual DOM nodes.

Calling (view model) will definitely build some virtual DOM, perhaps a lot of it. Calling (lazy view model) delays the call until later. During diffing, we can check to see if model is referentially equal to the previous value used, and if so, we just stop. No need to build up the tree structure and diff it, we know if the input to view is the same, the output must be the same!

lazy2 : (a -> b -> Node msg) -> a -> b -> Node msg

Same as lazy but checks on two arguments.

lazy3 : (a -> b -> c -> Node msg) -> a -> b -> c -> Node msg

Same as lazy but checks on three arguments.

lazy4 :
(a -> b -> c -> d -> Node msg)
-> a
-> b
-> c
-> d
-> Node msg

Same as lazy but checks on four arguments.

lazy5 :
(a -> b -> c -> d -> e -> Node msg)
-> a
-> b
-> c
-> d
-> e
-> Node msg

Same as lazy but checks on five arguments.

lazy6 :
(a -> b -> c -> d -> e -> f -> Node msg)
-> a
-> b
-> c
-> d
-> e
-> f
-> Node msg

Same as lazy but checks on six arguments.

lazy7 :
(a -> b -> c -> d -> e -> f -> g -> Node msg)
-> a
-> b
-> c
-> d
-> e
-> f
-> g
-> Node msg

Same as lazy but checks on seven arguments.

lazy8 :
(a -> b -> c -> d -> e -> f -> g -> h -> Node msg)
-> a
-> b
-> c
-> d
-> e
-> f
-> g
-> h
-> Node msg

Same as lazy but checks on eight arguments.