# An amendment to elm-portal

**URL:** <https://discourse.elm-lang.org/t/an-amendment-to-elm-portal/9774>\
**Category:** Show and Tell\
**Created:** [May 2, 2024, 6:40pm UTC](https://discourse.elm-lang.org/t/an-amendment-to-elm-portal/9774 "2024-05-02T18:40:00Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)\
**Post date:** [May 2, 2024, 6:40pm UTC](https://discourse.elm-lang.org/t/an-amendment-to-elm-portal/9774/1 "2024-05-02T18:40:00Z")

</div>

A few months ago I ported our modals at work to use `<dialog>`. This was a huge win for a11y 🎉 and it broke our usage of elm-portal 😭. You see, the browser native modals render on a separate layer from the rest of the DOM and elm-portal was only target the normal DOM layer, causing all of our dropdowns and tooltips to render behind our new modals!

Quick aside, for those wondering what portals I’m talking about I have [a blog post](https://wolfgangschuster.wordpress.com/2023/06/21/bring-your-own-dom-part-1-portals/) about building a web component called elm-portal for building things like tooltips, dropdowns, at the time modals, and more.

Back to the portalling problem, resolving this involved 2 changes. The first is very simple. We already wrap all of our modals in a single module, so I duplicated our various target elements into our modal module. Roughly that means our code went from

```elm
module Main exposing (..)

view model =
    { title = ...
    , body =
        [ -- our regular content
        , Html.div [Html.Attributes.id "elm-portal-tooltip"] []
        ]

```

to

```elm
module Main exposing (..)

view model =
    { title = ...
    , body =
        [ -- our regular content
        , Html.div [Html.Attributes.id "elm-portal-tooltip"] []
        ]

module Modal exposing (..)

view ... =
    Html.node "dialog"
        []
        [ -- modal content
        , Html.div [Html.Attributes.id "elm-portal-tooltip"] []
        ]

```

You might notice that this breaks using `document.getElementById()` because now there are 2 elements on the page with the same id! We could change to another selector, and maybe there is a better way to do this\*, but the route we went with is writing out own “nearest node” function

```js
function nearestNodeWithId(el: HTMLElement, id: string): HTMLElement | null {
  for (const sibling of getAllSiblings(el)) {
    if (sibling.id === id) {
      return sibling
    }
  }

  if (el.parentElement) {
    return nearestNodeWithId(el.parentElement, id)
  }

  return null
}

function getAllSiblings(el: HTMLElement): HTMLElement[] {
  const siblings = []
  let sibling = el?.parentNode?.firstChild
  if (sibling) {
    do {
      // text node
      if (sibling.nodeType !== 3) {
        siblings.push(sibling as HTMLElement)
      }
    } while ((sibling = sibling.nextSibling))
  }

  return siblings
}

```

This allows our elm-portal component to look up the DOM tree for the nearest node with the specified ID.

* * *

\* a better way might be to use a `data-` attribute and a combination of query selectors, though I’m not sure what that’d look like. If anyone has suggestions for how to improve upon `nearestNodeWithId` I’d love to hear about it!

---

<div class="post-metadata">

**Author:** ![lydell](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/lydell/32/178_2.png) [@lydell](https://discourse.elm-lang.org/u/lydell)\
**Post date:** [May 2, 2024, 7:19pm UTC](https://discourse.elm-lang.org/t/an-amendment-to-elm-portal/9774/2 "2024-05-02T19:19:22Z")

</div>

Here you go!

> **[Element.closest() method - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)**
>
> The closest() method of the Element interface traverses the element and its parents (heading toward the document root) until it finds a node that matches the specified CSS selector.

---

<div class="post-metadata">

**Author:** ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)\
**Post date:** [May 3, 2024, 5:43pm UTC](https://discourse.elm-lang.org/t/an-amendment-to-elm-portal/9774/3 "2024-05-03T17:43:33Z")

</div>

I did try this, however it only looks at parents and not siblings. Our setup is kind of like

```html
Elm content
<div id="portal-dropdown"></div>
<div id="portal-tooltip"></div>

```

so `closest` never finds the targets.

I do wonder though, since we append children to the portal target (to allow for multiple portals to target the same node), I suppose you could have something like

```html
<div id="portal-tooltip">
  <div id="portal-dropdown">
    Elm content
  </div>
</div>

```

and it _should_ correctly place tooltips, dropdowns and such. If you did that and it does work, then the modal setup would be roughly

```html
<dialog>
  <div id="portal-tooltip">
    <div id="portal-dropdown">
      Elm content
    </div>
  </div>
</dialog>

```

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [May 13, 2024, 5:44pm UTC](https://discourse.elm-lang.org/t/an-amendment-to-elm-portal/9774/4 "2024-05-13T17:44:04Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
