# Initializing MapboxGL.js within a single page app

**URL:** https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396
**Category:** Learn
**Created:** [June 18, 2018, 2:16pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396 "2018-06-18T14:16:03Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![enetsee](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enetsee/32/869_2.png) [@enetsee](https://discourse.elm-lang.org/u/enetsee)
#### Post date: [June 18, 2018, 2:16pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/1 "2018-06-18T14:16:03Z")

</div>

Hi,

I have been trying to figure out a non-hacky way of initializing a MapboxGL map within the a single page application.

The MapboxGL library exposes a method `Map([options])` which allows you specify the id of the node which should act as the parent and I am able to call this via a port. Clearly the node must exist at the point that call is made.

My application is structured along the lines of @rtfeldman [single page app example](https://github.com/rtfeldman/elm-spa-example).

The problem arises since I don’t know _when_ to send my port messages after loading a specific page since I don’t know when the node onto which Mapbox will mount has been rendered.

My current approach involves queueing port messages when the target node does not exist and using a `MutationObserver` to replay then when the node is added but this seems both a long winded way to doing it and slightly fragile.

Does anyone know of other approaches for this kind of thing?

Thanks

Michael

---

<div class="post-metadata">

### Author: ![luke](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/luke/32/709_2.png) [@luke](https://discourse.elm-lang.org/u/luke)
#### Post date: [June 18, 2018, 3:38pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/2 "2018-06-18T15:38:10Z")

</div>

Use a custom element

```javascript
customElements.define('mapbox-gl', class extends HTMLElement {
  constructor() {
    super()
    this._zoom = 0
  }
  get zoom() {
    return this._zoom
  }
  set zoom(value) {
    if (this._zoom === value) return
    this._zoom = value
    if (!this._map) return
    this._map.setZoom(this._zoom)
  }
  connectedCallback() {
    this._map = new Map({
      container: this,
      zoom: this._zoom
    })
  }
  disconnectedCallback() {
    this._map.remove()
  }
})

```

```elm

zoom : Int -> Html.Attribute msg
zoom howMuch =
    Html.Attributes.property "zoom" (Json.Encode.int howMuch)

mapbox : List (Html.Attribute msg) -> Html msg
mapbox attrs =
    Html.node "mapbox-gl" attrs []

```

> **[Using custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)**
>
> One of the key features of the Web Components standard is the ability to create custom elements that encapsulate your functionality on an HTML page, rather than having to make do with a long, nested batch of elements that together provide a custom...

---

<div class="post-metadata">

### Author: ![luke](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/luke/32/709_2.png) [@luke](https://discourse.elm-lang.org/u/luke)
#### Post date: [June 18, 2018, 3:59pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/3 "2018-06-18T15:59:18Z")

</div>

You can try it here if you fill in your mapbox token in the HTML editor

[https://ellie-app.com/xJCBDRbbcqa1](https://ellie-app.com/xJCBDRbbcqa1)

* * *

Browser support for this ellie: [https://caniuse.com/#feat=custom-elementsv1](https://caniuse.com/#feat=custom-elementsv1)

---

<div class="post-metadata">

### Author: ![enetsee](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enetsee/32/869_2.png) [@enetsee](https://discourse.elm-lang.org/u/enetsee)
#### Post date: [June 18, 2018, 5:14pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/4 "2018-06-18T17:14:40Z")

</div>

That’s… awesome 😍

Is it possible to use the same, declarative, approach for `sources` and `layers` i.e. using nested custom elements? If so, do you have any references you could point me toward?

---

<div class="post-metadata">

### Author: ![luke](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/luke/32/709_2.png) [@luke](https://discourse.elm-lang.org/u/luke)
#### Post date: [June 18, 2018, 5:21pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/5 "2018-06-18T17:21:21Z")

</div>

that one is a little trickier because it requires shadow dom to do correctly and browser support and polyfills for shadow dom are much shakier than custom element definitions. the way i would go first is to make types that describe layers and sources and then encode them to json and pass them as properties in the same way that the example passes `zoom` as an int. still declarative! but it just doesn’t look as satisfying in the dom explorer.

---

<div class="post-metadata">

### Author: ![enetsee](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enetsee/32/869_2.png) [@enetsee](https://discourse.elm-lang.org/u/enetsee)
#### Post date: [June 18, 2018, 5:30pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/6 "2018-06-18T17:30:03Z")

</div>

Cool - I’ll give it a try that way.

One thing that isn’t immediately clear is how to set up properties when there are multiple values (as is the case for both `layers` and `sources`).

Would you simply encode as a JSON object (or array) and iterate through using the API add them on the JS side?

---

<div class="post-metadata">

### Author: ![luke](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/luke/32/709_2.png) [@luke](https://discourse.elm-lang.org/u/luke)
#### Post date: [June 18, 2018, 5:41pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/7 "2018-06-18T17:41:30Z")

</div>

yep! the values you can pass as properties are in the set of anything you can represent as a `Json.Encode.Value`, which includes arrays and objects.

---

<div class="post-metadata">

### Author: ![enetsee](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enetsee/32/869_2.png) [@enetsee](https://discourse.elm-lang.org/u/enetsee)
#### Post date: [June 18, 2018, 6:03pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/8 "2018-06-18T18:03:06Z")

</div>

Perfect. Thank you for your help Luke.

---

<div class="post-metadata">

### Author: ![gampleman](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gampleman/32/43_2.png) [@gampleman](https://discourse.elm-lang.org/u/gampleman)
#### Post date: [June 20, 2018, 1:08pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/9 "2018-06-20T13:08:30Z")

</div>

I’ve been working for a while on a project that wraps all this in a library. It is getting relatively close to being published:

> **[gampleman/elm-mapbox](https://github.com/gampleman/elm-mapbox)**
>
> MapboxGL bindings for Elm. Contribute to gampleman/elm-mapbox development by creating an account on GitHub.

I’ll post here when I have a version actually published.

---

<div class="post-metadata">

### Author: ![enetsee](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enetsee/32/869_2.png) [@enetsee](https://discourse.elm-lang.org/u/enetsee)
#### Post date: [June 22, 2018, 10:35am UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/10 "2018-06-22T10:35:28Z")

</div>

That looks cool!

I threw together something that just covered my initial use case; the [parts I can share are here](https://github.com/enetsee/elm-mapbox).

It’s not as complete as what you have shared but the part I spent most time on was the `Expression`s.

I took the approach of having an underlying untyped AST ([here](https://github.com/enetsee/elm-mapbox/blob/master/src/Mapbox/GL/Expression/Internal.elm)) and a second type which wraps it and uses a phantom type to give a degree of type safety when constructing expressions ([here](https://github.com/enetsee/elm-mapbox/blob/master/src/Mapbox/GL/Expression.elm)).

For example, the exposed `case_` function ensures all branches and the default are of the same type and each case test is of type `Bool`:

```
case_ : List ( Expression Bool, Expression a ) -> Expression a -> Expression a
case_ cases (E default) =
    let
        cs =
            List.map (\( E b, E a ) -> ( b, a )) cases
    in
        E <| Case cs default

```

Feel free to grab it if you think it’s useful 🙂

The code below is the [example for a heatmap layer from the Mapbox docs](https://www.mapbox.com/mapbox-gl-js/example/heatmap-layer/).

Note that each of the heatmaps properties is also typed, e.g.

```
heatmapColor : Expression Color -> Heatmap -> Heatmap

heatmapOpacity : Expression Float -> Heatmap -> Heatmap

...

```

The expression is then:

```
import Mapbox.GL.Expression as Expr
import Mapbox.GL.Layer.Heatmap as Heatmap
import Mapbox.GL.Layer as Layer
import Mapbox.GL.Source as Source

heatmapLayer =
    Heatmap.heatmap "earthquakes-heat" (Source.reference "earthquakes")
        |> Heatmap.heatmapWeight
            (Expr.interpolate
                Expr.linear
                (Expr.get_ "mag")
                [ ( Expr.number 0, Expr.number 0 )
                , ( Expr.number 6, Expr.number 1 )
                ]
            )
        |> Heatmap.heatmapIntensity
            (Expr.interpolate
                Expr.linear
                Expr.zoom
                [ ( Expr.number 0, Expr.number 0 )
                , ( Expr.number 9, Expr.number 3 )
                ]
            )
        |> Heatmap.heatmapColor
            (Expr.interpolate
                Expr.linear
                Expr.heatmapDensity
                [ ( Expr.number 0, Expr.color (Color.rgba 33 102 172 0) )
                , ( Expr.number 0.2, Expr.color (Color.rgb 103 168 207) )
                , ( Expr.number 0.4, Expr.color (Color.rgb 209 229 240) )
                , ( Expr.number 0.6, Expr.color (Color.rgb 253 219 199) )
                , ( Expr.number 0.8, Expr.color (Color.rgb 239 138 98) )
                , ( Expr.number 1.0, Expr.color (Color.rgb 178 24 43) )
                ]
            )
        |> Heatmap.heatmapRadius
            (Expr.interpolate
                Expr.linear
                Expr.zoom
                [ ( Expr.number 0, Expr.number 2 )
                , ( Expr.number 9, Expr.number 20 )
                ]
            )
        |> Heatmap.heatmapOpacity
            (Expr.interpolate
                Expr.linear
                Expr.zoom
                [ ( Expr.number 7, Expr.number 1 )
                , ( Expr.number 9, Expr.number 0 )
                ]
            )
        |> Layer.heatmap
```

---

<div class="post-metadata">

### Author: ![gampleman](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gampleman/32/43_2.png) [@gampleman](https://discourse.elm-lang.org/u/gampleman)
#### Post date: [June 22, 2018, 1:18pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/11 "2018-06-22T13:18:24Z")

</div>

That’s very similar to what I have. Although I don’t bother with an internal type but directly encode into JSON. Under 0.19 this should be as fast as manually constructing JSON. I also use phantom types for type safety, though I also track whether the expression is a DataExpression or CameraExpression to keep stuff a bit safer that way as well:

> <https://github.com/gampleman/elm-mapbox/blob/master/src/Mapbox/Expression.elm>

---

<div class="post-metadata">

### Author: ![gampleman](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/gampleman/32/43_2.png) [@gampleman](https://discourse.elm-lang.org/u/gampleman)
#### Post date: [June 22, 2018, 1:19pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/12 "2018-06-22T13:19:24Z")

</div>

Also this library is now ready to be published, so would appreciate any feedback on it.

---

<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: [July 2, 2018, 1:23pm UTC](https://discourse.elm-lang.org/t/initializing-mapboxgl-js-within-a-single-page-app/1396/13 "2018-07-02T13:23:54Z")

</div>

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