Elm and Polymer

How to integrate external components in your Elm application?

Tech Lead @ Meetic

k.lebrun@meetic-corp.com

kevinlebrun

Elm

A counter component

  • show the current value
  • decrease the value
  • increase the value
type alias Model =
    Int


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , span [] [ text (toString model) ]
        , button [ onClick Increment ] [ text "+" ]
        ]
type Msg
    = Increment
    | Decrement


update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            model + 1

        Decrement ->
            model - 1
main : Program Never
main =
    beginnerProgram
        { model = 0
        , view = view
        , update = update
        }

Polymer

Polymer({
    is: "my-counter",
    properties: {
        value: {
            type: Number,
            value: 0,
        },
    },
    increment() {
        this.value++;
    },
    decrement() {
        this.value--;
    },
});
<my-counter id="counter" value=1></my-counter>
<template>
    <style>
        :host {
            display: block;
        }
    </style>

    <button on-click="decrement">-</button>
    <span>[[value]]</span>
    <button on-click="increment">+</button>
</template>

Interop

How to communicate with the outside world?

Elm Interop - Ports

var app = Elm.Counter.embed(node, { value : this.value });

app.ports.change.subscribe(value => {
    this.value = value;
});

app.ports.set.send(this.value);

Elm Interop - Virtual DOM

div []
    [ button [ onClick Decrement ] [ text "-" ]
    , span [] [ text <| toString model ]
    , button [ onClick Increment ] [ text "+" ]
    ]

Elm Interop - Native

Polymer Interop - Attributes I

Polymer({
    is: "my-counter",
    properties: {
        value: {
            type: Number,
            value: -1,
            notify: true,
        },
    },
    increment() {
        this.value++;
    },
    decrement() {
        this.value--;
    },
});

Polymer Interop - Attributes II

<my-counter id="counter" value=1></my-counter>

<script>
    var counter = document.getElementById('counter');
    counter.addEventListener('value-changed', function (e) {
        console.log(e.detail.value);
    });
</script>

Polymer Interop - Events I

Polymer({
    is: "my-counter",
    properties: {
        value: Number,
    },
    increment() {
        this.value++;
        this.fire('change', this.value);
    },
    decrement() {
        this.value--;
        this.fire('change', this.value);
    },
});

Polymer Interop - Events II

<my-counter id="counter" value=1></my-counter>

<script>
    var counter = document.getElementById('counter');
    counter.addEventListener('change', function (e) {
        console.log(e.detail);
    });
</script>

Polymer Interop - Methods I

Polymer({
    is: "my-counter",
    properties: {
        value: Number,
    },
    increment() {
        this.value++;
    },
    decrement() {
        this.value--;
    }
});

Polymer Interop - Methods II

<my-counter id="counter" value=1></my-counter>

<script>
    var counter = document.getElementById('counter');
    counter.increment();
</script>

Counter value

Counter

Counter reset

<div id="counter"></div>

<my-counter-controls id="controls"></my-counter-controls>
var node = document.getElementById('counter');

var counter = Elm.Counter.embed(node, { value : 0 });

var controls = document.getElementById('controls');
controls.addEventListener('reset', function (e) {
    counter.ports.set.send(0);
});

<dom-module id="my-counter-controls">
    <template>
        <p>Controls: <button on-click="reset">Reset</button></p>
    </template>

    <script>
        (function () {

            Polymer({
                is: "my-counter-controls",
                reset() {
                    this.fire('reset');
                },
            })
        })();
    </script>
</dom-module>

Counter value

Counter

Counter reset

<p>
    In the document,<br>
    the value is <span class="value">1</span>
</p>

<my-elm-counter id="counter" value=1></my-elm-counter>
var counter = document.getElementById('counter');
counter.addEventListener('value-changed', function (e) {
    document.querySelector('.value').textContent = e.detail.value;
});

var app;

Polymer({
    is: "my-elm-counter",
    properties: {
        value: {
            type: Number,
            value: 0,
            notify: true,
        },
    },
    reset() {
        this.value = 0;
        app.ports.set.send(this.value);
    },
    ready() {
        app = Elm.Counter.embed(this.$.embed, { value : this.value });
        app.ports.change.subscribe(value => {
            this.value = value;
        });
    },
});

Counter value

Counter

Counter reset

onValueChanged tagger =
    on "value-changed" <| Json.map tagger detailValue

value =
    attribute "value"

myCounter =
    node "my-counter"

view : Model -> Html Msg
view model =
    div []
        [ myCounter [ value <| toString model , onValueChanged Change ] []
        , p []
            [ text "Controls:"
            , button [ onClick Reset ] [ text "Reset" ]
            ]
        ]

Conclusion

paperCard =
    node "paper-card"

paperButton =
    node "paper-button"


view : Model -> Html Msg
view model =
    div []
        [ paperCard
            [ attribute "heading" ("Counter: " ++ (toString model)) ]
            [ div [ class "card-content" ] [ text (toString model) ]
            , div [ class "card-actions" ]
                [ paperButton [ onClick Decrement ] [ text "Decrement" ]
                , paperButton [ onClick Increment ] [ text "Increment" ]
                ]
            ]
        ]

The content is not updated

<script>
    window.Polymer = {
        dom: 'shadow',
        lazyRegister: true
    };
</script>

Solution I - Disable Shady DOM

But it only works with native Shadow DOM...

view : Model -> Html Msg
view model =
    Html.Keyed.node "div"
        []
        [ ( toString model
          , paperCard [ attribute "heading" ("Counter: " ++ (toString model)) ]
                [ div [ class "card-content" ] [ text (toString model) ]
                , div [ class "card-actions" ]
                    [ paperButton [ onClick Decrement ] [ text "Decrement" ]
                    , paperButton [ onClick Increment ] [ text "Increment" ]
                    ]
                ]
          )
        ]

Solution II - Use Elm Html.Keyed.node

Q/A

github.com/kevinlebrun/elm-polymer

Elm and Polymer: A Case Study

By Kevin Le Brun

Elm and Polymer: A Case Study

How to integrate external components in your Elm application?

  • 1,397