---
title: Extending Elm Native UI with JavaScript
teaser: How to add extra UI functionality to your Elm Native UI project.
tags: code,elm,javascript,mobile
author: Jon Yurek
published_on: 2017-03-27
---

You may have heard about our [foray] into writing a native app using
[Elm Native UI]. [Purple Train] accomplishes its goals quite well for us. It's
not only a useful product, it's also a learning experience for us. We decided
that it's only fair for us to try to push the boundaries of the technology.

[foray]: https://thoughtbot.com/blog/elm-native-ui-in-production
[Purple Train]: https://apps.apple.com/us/app/purple-train/id1153352657
[Elm Native UI]: https://github.com/ohanhi/elm-native-ui

After we submitted the first Elm version to the Apple App Store, I entered what
I thought would be a simple issue on the project: when you open the Stop Picker,
it should be scrolled to the stop that's selected instead of being at the top of
the list. It became a prime example of why you should not tell a developer that
something is "just a small change".

## Imperativeness in a land of Declarations

The biggest problem is that when the Stop Picker opens, we want code to execute
that affects the state of the UI. The [Elm Architecture] builds its UIs in a
declarative manner. You say "There's a scroll view here and it contains a list
of buttons" and the display layer takes care of laying that out on the screen
for you. There's no place to say "When the box is displayed, execute this code".

[Elm Architecture]: https://guide.elm-lang.org/architecture/

In order to get this to work, we decided to play nice with declarations instead
of trying to fight them. We wanted a way to declare that a given element should
be marked as the scroll target when the box is rendered. You've undoubtedly seen
this pattern when using a loop to generate some HTML `<option>` tags and one is
`<option selected="true">`. We had to make the function that generates the
button be able to tell if it's the selected one. We also had to be able to pass
the selected button down the chain so the function can figure that out.

The code to generate a button in the Stop Picker originally looked like this:

```elm
stopButton : Stop -> Node Msg
stopButton stop =
    pickerButton (PickStop stop) stop
```

Where `pickerButton` called `Element.touchableHighlight` to create the button
and `PickStop stop` is the message we send our event loop.

We changed the `stopButton` function to this:

```elm
stopButton : Maybe Stop -> Stop -> Node Msg
stopButton highlightStop stop =
    case highlightStop of
        Nothing ->
            pickerButton (PickStop stop) stop

        Just pickedStop ->
            if stop == pickedStop then
                highlightPickerButton (PickStop stop) stop
            else
                pickerButton (PickStop stop) stop
```

And we pass in the already-selected stop or `Nothing` if there isn't one.

The main difference between `pickerButton` and `highlightPickerButton` is that
they pass a `scrollTarget` property to the `touchableHighlight` call. This lets
the underlying component know where it should scroll to on load.

This whole setup lets us define what's special about the situation using Elm
instead of using JS. We tried a few ways of letting Elm handle React Native's
`componentDidMount` callback, but ultimately had to fall back to JavaScript.

## The State of the UI

Since Elm Native UI is built on top of [React Native], it's possible to drop
down into that more imperative land of JavaScript to do things we can't do (or,
at least, we haven't yet specified a way to do) in Elm. Thankfully, since Elm
compiles to JavaScript, it's rather straightforward to craft some JavaScript
code that Elm is able to use.

[React Native]: https://facebook.github.io/react-native/

What we need here is a React component that knows that it needs to find and
scroll to a sub-element once it's mounted into the UI. We'll need to hook into
the `componentDidMount` callback because we need values (height, specifically)
from the rendered state of the component.

In the file `app/Native/ScrollWrapper.js`, we created a component subclass that
looks like this:

```javascript
const _user$project$Native_ScrollWrapper = function () {
  var ScrollView = require('ScrollView');
  class ScrollWrapper extends ScrollView {
    componentDidMount() {
      // Find the child node that has the `scrollTarget` property
      // Scroll to that node with `this.scrollTo();`
    }
  }

  return {
    view: ScrollWrapper
  };
}();
```

Because we named the `const` "`_user$project$Native_ScrollWrapper`", we'll be
accessing it in Elm as `Native.ScrollWrapper`. We also need this Elm code in
`app/ScrollWrapper.elm`:

```elm
module ScrollWrapper exposing (view)

import NativeUi exposing (Node, Property)
import Native.ScrollWrapper


view : List (Property msg) -> List (Node msg) -> Node msg
view =
    NativeUi.customNode "ScrollWrapper" Native.ScrollWrapper.view
```

Once we have this in place, we can `import ScrollWrapper` in our Elm view and we
can replace the call to `Element.scrollView` with `ScrollWrapper.view`.

Now that all this is in place, we can refresh the app in the iOS Simulator and
see the results:

![Autoscroll Demo](https://images.thoughtbot.com/blog-vellum-image-uploads/a8xynTpnQr2SkCaRD0VP_purple-train-scroll.gif)

## Getting Things Done

I admit, this isn't the best thing to have to do. I'd much prefer that we can
specify this kind of extension in pure Elm. That's the point of writing Elm,
after all. But it's very useful that we can interact with JavaScript in this
way. Even if we could write new functionality to extend the framework in Elm,
there is a *lot* of existing code written in JavaScript that could be very
useful to include in a project. It's comforting to know that it's fairly easy to
access should we need it.
