Mikael Lundin
Mikael Lundin

Reputation: 289

Transform Html DOM

I am new to Elm and I really love it so far, but I've run into a problem that I cannot seem to wrap my head around.

I have an Html DOM, for example

div []
  [ h1 [] [text "Headline 1"]
  , p [] [text "Some text"]
  , h2 [] [text "Headline 2"]
  ]

I would like to add a-links inside each h[1-6] element and so transform it to something like (keeping it simple)

div []
  [ h1 [] [ text "Headline 1"
          , [a [name "headline"] [text "#"]
          ]
  , p [] [text "Some text"]
  , h2 [] [text "Headline 2"
          , [a [name "headline"] [text "#"]
          ]
  ]

This is conceptually not very hard. Look through the DOM, if element is h[1-6] add an a-link as child element. However my understanding of Elm is not well enough to get it to work.

Here is what I've been trying so far.

transform : Html a -> Html a
transform node =
    -- check if the tag is h1-h6
    case node.tag of
        -- add a-link to h1 children
        "h1" -> { node | children = (a [name "headline"] [text "#") :: node.children }
        "h2" -> { node | children = (a [name "headline"] [text "#") :: node.children }
        -- do this for all nodes in the tree
        _ -> { node | children = List.map transform node.children }

This doesn't work.

The type annotation for `transform` does not match its definition.

40| transform : Html a -> Html a
                ^^^^^^^^^^^^^^^^
The type annotation is saying:

    VirtualDom.Node a -> VirtualDom.Node a

But I am inferring that the definition has this type:

    { b | tag : String, children : List (Html a) }
    -> { b | children : List (Html a), tag : String }

I understand that I can't do node.tag because the generic type a might not have that field. It wouldn't be type safe. For example the text node doesn't have a tag field, but is still an instance of Html.Html a.

> text "Hello World"
{ type = "text", text = "Hello World" } : Html.Html a

My question is, how can I do this? Can I do this? or shouldn't I be doing this?

Upvotes: 2

Views: 491

Answers (2)

halfzebra
halfzebra

Reputation: 6797

It is not possible to modify existing values of Html msg type.

They are final internal structures, which are rendered by Virtual DOM in to actual HTML Nodes as an output of your program.

Html msg is an alias for VirtualDom.Node a

You are attempting to use them as Records, but that's just a JavaScript object.

Elm REPL outputs String presentation of an abstract data structure here:

> text "Hello World"
{ type = "text", text = "Hello World" } : Html.Html a -- not a record

Instead of attempting to transform Html msg -> Html msg, you should try something like:

-- Input example: [ "#", "http://google.com/", "http://package.elm-lang.org/" ]

linksView : List String -> Html msg
linksView links =
    links
        |> List.map (\link -> a [ href link ] [ text link ])
        |> div [] -- Expected output: <div> with thre links

Upvotes: 2

Chad Gilbert
Chad Gilbert

Reputation: 36375

In Elm, Html a is really only useful as output. You're never going to use it as input in the way that your transform function is attempting.

You will be better served by creating a model to describe your domain, then passing that to a view function to render html.

type alias Article =
  { priority : Priority
  , headline : String
  , body : String
  }

type alias Model =
  List Article

type Priority = First | Second

Your view could then look something like this:

view : Model -> Html msg
view =
  div [] << List.map viewArticle

viewArticle : Article -> Html msg
viewArticle article =
  let
    priorityTag =
      case article.priority of
        First -> h1
        Second -> h2
  in
    div []
      [ priorityTag []
        [ text article.headline
        , a [ name "headline" ] [ text "#" ]
        ]
      , p [] [ text article.body ]
      ]

Upvotes: 1

Related Questions