lue-bird / elm-state-interface / Web.Dom

Helpers for DOM nodes as part of an Interface.

These are primitives used for svg and html (filling the same role as elm/virtual-dom)


type Node future
    = Text String
    | Element (Element future)

Plain text or an Element. Create using text and element

text : String -> Node future_

Plain text DOM Node


type alias Element future =
RecordWithoutConstructorFunction { header : Web.DomElementHeader future
, subs : List (Node future) 
}

A tagged DOM node that can itself contain child nodes

element : String -> List (Modifier future) -> List (Node future) -> Node future

Create a DOM element with a given tag, Modifiers and sub-nodes. For example to get <p>flying</p>

Web.Dom.element "p"
    []
    [ Web.Dom.text "flying" ]

To create SVG elements, use Web.Svg.element

elementNamespaced : String -> String -> List (Modifier future) -> List (Node future) -> Node future

Create a DOM element with a given namespace, tag, Modifiers and sub-nodes. For example, Web.Svg defines its elements using

element : String -> List (Modifier future) -> List (DomNode future) -> DomNode future
element tag modifiers subs =
    Web.Dom.elementNamespaced "http://www.w3.org/2000/svg" tag modifiers subs

futureMap : (future -> mappedFuture) -> Node future -> Node mappedFuture

Wire events from this Web.Dom.Node to a specific event, for example

buttonUi "start"
    |> Web.Dom.futureMap (\Clicked -> StartButtonClicked)

with

buttonUi : String -> Web.Dom.Node ButtonEvent
buttonUi label =
    Web.Dom.element "button"
        [ Web.Dom.listenTo "click"
            |> Web.Dom.modifierFutureMap (\_ -> Clicked)
        ]
        [ Web.Dom.text label ]

type ButtonEvent
    = Clicked

render : Node future -> Web.Interface future

An Interface for displaying a given Web.Dom.Node


type alias Modifier future =
Rope (ModifierSingle future)

Setting of a Web.Dom.Element. To create one, use attribute, style, listenTo etc. To combine multiple, use Web.Dom.modifierBatch and Web.Dom.modifierNone

For example to get <a href="https://elm-lang.org">elm</a>

Web.Dom.element "a"
    [ Web.Dom.attribute "href" "https://elm-lang.org" ]
    [ Web.Dom.text "elm" ]

Btw: If you can think of a nicer name for this like "customization", "characteristic" or "aspect", please open an issue.

attribute vs property

But don't be surprised: There are cases where

For example, trying to reset the text inside a a text input with

Web.Dom.attribute "value" "user input"

-- then later replace it with
Web.Dom.attribute "value" ""

will only provide a default value and has no effect on the currently written text, so you'll have to use

Web.Dom.stringProperty "value" ""

Similarly for checkboxes:

Web.Dom.boolProperty "checked" False

Maybe a rule of thumb is: Use properties to set anything related to interactivity and attributes for everything else.

If you have some opinions or better explanations, please open an issue.

modifierFutureMap : (future -> mappedFuture) -> Modifier future -> Modifier mappedFuture

Wire events from this Modifier to a specific event.

Web.Dom.listen "click" |> Web.Dom.modifierFutureMap (\_ -> ButtonClicked)

modifierBatch : List (Modifier future) -> Modifier future

Combine multiple Modifiers into one.

modifierNone : Modifier future_

Doing nothing as a Modifier. These two examples are equivalent:

Web.Dom.modifierBatch
    [ a, Web.Dom.modifierNone, b ]

and

Web.Dom.modifierBatch
    (List.filterMap identity
        [ a |> Just, Nothing, b |> Just ]
    )

attribute : String -> String -> Modifier future_

A key-value attribute Modifier

attributeNamespaced : String -> String -> String -> Modifier future_

A namespaced key-value attribute Modifier. For example, you could define an SVG xlink href attribute as

attributeXlinkHref : String -> Modifier msg
attributeXlinkHref value =
    Web.Dom.attributeNamespaced "http://www.w3.org/1999/xlink" "xlink:href" value

style : String -> String -> Modifier future_

A key-value style Modifier

boolProperty : String -> Basics.Bool -> Modifier future_

A key-bool value DOM property Modifier

stringProperty : String -> String -> Modifier future_

A key-string value DOM property Modifier

listenTo : String -> Modifier Json.Decode.Value

Listen for a specific DOM event on the Web.Dom.Element. Use modifierFutureMap to wire this to a specific event.

If you want to override the browser's default behavior for that event, use listenToPreventingDefaultAction

listenToPreventingDefaultAction : String -> Modifier Json.Decode.Value

Like listenTo but preventing the browser's default action.

That's for example how elm's Browser.Events.onSubmit prevents the form from changing the page’s location:

submitListen : Web.Dom.Modifier ()
submitListen =
    Web.Dom.listenToPreventingDefaultAction "submit"
        |> Web.Dom.modifierFutureMap (\_ -> ())

scrollToShow : { x : Web.DomElementVisibilityAlignment, y : Web.DomElementVisibilityAlignment } -> Modifier future_

Ensure a given initial DomElementVisibilityAlignment in both directions.

Unlike styles, this is just an initial configuration which can be changed by user actions. So adding e.g. scrollToShow { y = Web.DomElementStart, x = Web.DomElementStart } will scroll to the top left once the next render happens but will not prevent users from scrolling away.

Note: Uses Element.scrollIntoView

scrollPositionRequest : Modifier { fromLeft : Basics.Float, fromTop : Basics.Float }

Getting the current scroll position from the left and top.

Use in combination with scrollToPosition to implement saving and restoring scroll position even when users had navigated off a URL.

scrollToPosition : { fromLeft : Basics.Float, fromTop : Basics.Float } -> Modifier future_

Ensure a given initial scroll position in both directions. To move to the edge in a direction, use scrollToShow instead.

Unlike styles, this is just an initial configuration which can be changed by user actions. So adding e.g. scrollToPosition ... will scroll once the next render happens but will not prevent users from scrolling away.

internals, safe to ignore for users

Exposed so can for example simulate it more easily in tests, add a debugger etc.


type ModifierSingle future
    = Attribute ({ namespace : Maybe String, key : String, value : String })
    | StringProperty ({ key : String, value : String })
    | BoolProperty ({ key : String, value : Basics.Bool })
    | Style ({ key : String, value : String })
    | ScrollToPosition ({ fromLeft : Basics.Float, fromTop : Basics.Float })
    | ScrollToShow ({ x : Web.DomElementVisibilityAlignment, y : Web.DomElementVisibilityAlignment })
    | ScrollPositionRequest ({ fromLeft : Basics.Float, fromTop : Basics.Float } -> future)
    | Listen ({ eventName : String, on : Json.Decode.Value -> future, defaultActionHandling : Web.DefaultActionHandling })

An individual Modifier. Create using attribute, style, listenTo etc.