Module

Bonsai.VirtualDom

Package
purescript-bonsai
Repository
grmble/purescript-bonsai

Purescript interface to Elm Virtual DOM

#VNode Source

newtype VNode msg

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

It's a functor that maps the Cmds that are emitted by event handlers.

Instances

#node Source

node :: forall msg. String -> Array (Property msg) -> Array (VNode msg) -> VNode 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!" ]

#text Source

text :: forall msg. String -> VNode msg

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

text "Hello World!"

#Property Source

newtype Property 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!

Instances

#property Source

property :: forall msg a. String -> a -> Property msg

Create arbitrary properties.

import JavaScript.Encode as Json

greeting : Html greeting = node "div" [ property "className" (Json.string "greeting") ] [ text "Hello!" ]

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

#attribute Source

attribute :: forall msg. String -> String -> Property msg

Create arbitrary HTML attributes. Maps onto JavaScript’s setAttribute function under the hood.

greeting : Html
greeting =
    node "div" [ attribute "class" "greeting" ] [
      text "Hello!"
    ]

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

#attributeNS Source

attributeNS :: forall msg. String -> String -> String -> Property msg

Would you believe that there is another way to do this?! This corresponds to JavaScript's setAttributeNS function under the hood. It is doing pretty much the same thing as attribute but you are able to have "namespaced" attributes. This is used in some SVG stuff at least.

#style Source

style :: forall msg. Array (Tuple String String) -> Property msg

Specify a list of styles.

myStyle : Property msg
myStyle =
  style
    [ ("backgroundColor", "red")
    , ("height", "90px")
    , ("width", "100%")
    ]

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

#EventDecoder Source

type EventDecoder msg = Foreign -> F (Cmd msg)

A EventDecoder turns DOM events into messages.

#on Source

on :: forall msg. String -> (EventDecoder msg) -> Property msg

Create a custom event listener.

import Json.Decode as Json

onClick : msg -> Property msg
onClick msg =
  on "click" (Json.succeed msg)

You first specify the name of the event in the same format as with JavaScript’s addEventListener. Next you give a JSON decoder, which lets you pull information out of the event object. If the decoder succeeds, it will produce a message and route it to your update function.

#onWithOptions Source

onWithOptions :: forall msg. String -> Options -> EventDecoder msg -> Property msg

Same as on but you can set a few options.

#Options Source

type Options = { preventDefault :: Boolean, stopPropagation :: Boolean }

Options for an event listener. If stopPropagation is true, it means the event stops traveling through the DOM so it will not trigger any other event listeners. If preventDefault is true, any built-in browser behavior related to the event is prevented. For example, this is used with touch events when you want to treat them as gestures of your own, not as scrolls.

#lazy Source

lazy :: forall msg a. (a -> VNode msg) -> a -> VNode 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 Source

lazy2 :: forall msg b a. (a -> b -> VNode msg) -> a -> b -> VNode msg

Same as lazy but checks on two arguments.

#lazy3 Source

lazy3 :: forall msg c b a. (a -> b -> c -> VNode msg) -> a -> b -> c -> VNode msg

Same as lazy but checks on three arguments.

#keyedNode Source

keyedNode :: forall msg. String -> Array (Property msg) -> Array (Tuple String (VNode msg)) -> VNode 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.

#render Source

render :: forall msg eff. Emitter eff msg -> VNode msg -> Element

Render a virtual dom node to a DOM Element.

Initial step - the whole point in a VDom is the diffing and patching. So after rendering once, diff and applyPatches should be used.

#Patch Source

newtype Patch msg

A Patch for efficient updates.

#diff Source

diff :: forall msg. VNode msg -> VNode msg -> Patch msg

Compute a patch between the old vnode representation and the new one.

#applyPatches Source

applyPatches :: forall msg eff. Emitter eff msg -> Element -> VNode msg -> Patch msg -> Eff (dom :: DOM | eff) Element

Apply a diff between VDoms to the DOM element.

The DOM element should be the one from the last diff/applyPatches pass, or the initially rendered one.