You can find the video below as well as a cleaned-up transcript of the talk with descriptions of slides included. All code and slide content can be found at https://github.com/lukewestby/elm-europe-2018.
If you are looking for speakers, and you like this talk and enjoy my speaking style feel free to reach out at firstname.lastname@example.org!
Slide 1: "Hello!"
I love these headset mics. I feel like I should be standing here with my
fingers steepled saying stuff like, you know, "We have machine learning... but do we have machine understanding?". Anyway, hello! Everybody's really excited for custom elements.
Slide 2: "I'm Luke"
Slide 3: Photograph of the pier in Manhattan Beach, CA
I came here from Manhattan Beach, California which is right near LeBron – excuse me – Los Angeles.
Slide 4: NoRedInk logo
I work at NoRedInk on the internal team. Right now I'm converting all of our Elm code to Elm 0.19. Very fun.
Slide 5: Ellie logo, NoRedInk logo, Elm logo
I also work on Ellie and contribute to Elm in other ways.
Slide 6: The sidebar of the Elm Slack, with the #beginners channel selected
One of the things that I do among those three three things is hang out in Slack and watch the #beginners channel.
Slide 8: A screenshot of the list of all Web APIs on MDN
For instance, this is how many web APIs there are. That's not how many Elm packages there are. So can anybody in the audience who maybe has been in the Elm community for a while tell me how you might go about using one of these if it's not in the
[Audience members respond "ports"]
Slide 9: Search bar in the Elm Slack, searching for "use a port"
closed question almost.
Slide 10: A drawing of Pandora's Box being opened
So what I want to do in this in this talk is reopen it just a little bit. Probably the world won't end, but we'll see.
Slide 11: Ancient Aliens meme, with the text "Blockchain"
This has nothing to do with anything. (Audience laughter). I forgot to take this slide out. I don't remember what it was for. There's a there's more... uh... let's see if I can work it in.
Slide 12: A Google Map showing a pin for the location of the conference
Slide 13: A screenshot of the text editor from https://ellie-app.com
Or maybe you want to build an Elm compiler into the into a browser platform and you need a text editor for people to share code and tell them where they can check out you mixtape.
Slide 14: "port module Main"
You can do this with ports. You can do anything with ports. It'd be very hard to get me on record saying that you can't do something with ports. If you've interacted with me online at all you know that to be true.
So the way that we would go about building, maybe, this...
Slide 15: A screen shot of Ellie's text editor again
... with ports is we would ...
Slide 16: A call to
... first make a div and we give it an ID like
Slide 17: Two ports for communicating between Elm and JS
Value sometimes. We'd have a port coming in where we can tag some information with a string and pass it as a
Value and deal with it in Elm in a subscription.
Slide 18: An
initvalue that uses a port to send an
"Init"message to JS
And then when we initialize our application we have to set up the initial model and also send out an
Slide 19: A port subscription in JS that sets up a CodeMirror instance when it receives a message tagged as
(Speaking even faster, for dramatic effect)
requestAnimationFrame so we can wait till the virtual DOM is finish rendering and then we init and then we look out the element and hopefully it's there. Who knows what we do if it's not. I certainly don't. Then we make the editor assuming the element is there and then we attach the editor to the element so we don't have to remake it every time.
Slide 20: Focus on CodeMirror creation code, with the addition of a listener to CodeMirror's
changesevent to forward changes back over a port to Elm
And then we listen for changes on the editor. we send those back in through the
port. We call it
"ValueUpdated" as a string tag get the value from the editor.
Slide 21: A
Msgtype in Elm with a
CodeChangedconstructor to tag incoming strings from CodeMirror
CodeChanged message so that we can capture that into our update function
Slide 22: A case in an
updatefunction that assigns the string from
CodeChangedto a field on the model
Handle that message. Set the code in the model.
Slide 23: A subscription to
textEditorInport that decodes messages from CodeMirror
And then subscribe to
textEditorIn. Lookup the type. See if it's
"ValueUpdated". Decode the data. If it fails (shrugs shoulders). If it's something else (shrugs shoulders again). That works. That's fine.
Slide 24: The
Msgtype with a new constructor
ResetCodefor blanking out the editor
And then maybe also we want to do things like reset the code from within the Elm application if the user clicks a button. Ao we make a
ResetCode message and then in our update function we reset the code in the model we send a message out through
Slide 25: Handling
updateto send an empty string over the
Give it the name
"ValueUpdated". Pass it as a string.
Slide 26: Handling
setValueon the CodeMirror instance
Slide 26: "DONE"
... done. (Audience laughter and applause).
Slide 27: Several copies of all of the code from the previous slides overlayed on top of one another like scattered sheets of paper.
What if we have more than one text editor in our application?
Slide 28: An example of using
If we're not writing code this is how we get text input from the user. It's just a text area and we use an HTML attribute to say that this is what the code is and we use
onInput to handle the event from HTML. This is, like, a pretty good way to represent some visual compon– excuse me. Some special elements of our user interface that could be interacted with by the user.
Slide 29: A hypothetical code snippet where
Html.codeEditorexists and is used similarly to
So wouldn't it be cool if we also had these. Like we could just say, like, give me a
codeEditor element and then put these attributes on there let me listen for editor changes. Obviously these functions don't exist in the
html package, but ...
Slide 30: "Folks"
... folks ...
Slide 31: "Turs out"
... turns out ...
Slide 32: "You can do this"
... you can do this. (Pretending as if an audience member is saying it:) WHAAAT?
Slide 33: "You can do this with just today's Elm and the Web Platform"
You can do it with just today's Elm and the web platform as it currently exists.
Slide 34: "RIGHT NOW"
Slide 35: Classic "mind blown" GIF from Tim and Eric
So let's let's do it.
Slide 36: Hypothetical
Like I said, these functions don't exist in the
Slide 37: Replacing the hypothetical functions with their real analogues in
But these functions do. With
Html.node, which lets us pick any tag name we want, and we have
Html.Attributes.property which lets us assign any
Html.Events.on lets us listen to any event.
Live Demo. All demo code is available at https://github.com/lukewestby/elm-europe-2018
So here's that same code in a text editor and here is that element rendered. So we can see we're assigning
editorValue here. The value of that should be
"hello world", according to our Elm program ... and it is. And then if we go back to the elements tab of Chrome's dev tools and take a look at our event listeners here we see that there's an
editorChanged event. You can just do this. It's pretty cool.
So right now we have a nice grey rectangle which, as much as I'd love for that to be the way that my text editor looks some days, it's not super useful right now. So we actually have to explain to the browser how to deal with this. And the
way we do that is with the custom elements API. So we do
customElements.define. So this was
'code-editor'. And we're gonna write a
class that extends
HTMLElement. Oh my gosh, VSCode, we get it.
So the one thing that we absolutely have to do to get started is write this
connectedCallback method. This method is called by the custom elements API in the
browser whenever the element has been inserted into the DOM or it's been discovered in the DOM before this definition was defined. So it guarantees us that all the the DOM APIs are going to be ready for us by the time this code is executed. So we'll say
this._editor = CodeMirror(this, ...) and we'll do some some options.
indentUnit: 4. We'll do
mode and we'll do
lineNumbers: true and we'll do
value, hard-coded to
"module Main exposing (..)" and we'll save it.
And WebPack will surely be very fast and we compile this for us (laughing). There it is. So there's ... there's ... (audience applause). Yeah! That's pretty cool. Thank you for applauding, but unfortunately that's not the text that we said we should have in our in our Elm program so it's not super useful yet.
So what we need to do is set up a property getter and setter to intercept
the virtual DOM's behavior of getting and setting this property. So the first thing that we will do is set up a constructor, and we have to call
_editorValue backing store. This should be familiar to anybody who's done a lot of C# programming. We put that
default value up in the constructor and we will set this to
_editorValue and now
editoValue will return
this._editorValue and when we
the real magic happens. So not only can we assign here, we can also check that
our editor exists and this time it's totally cool if it doesn't because we're
guaranteed for this method to run and we always know that the latest value that
we've set is going to be used. So that's fine.
So we can just return here and if it does exist we
setValue. And then the last thing that I like to make sure to do is avoid doing any unnecessary work. So if the value is already equal to the thing that was set we're not going to do anything.
So now I'll save that and we'll stand here awkwardly with our arms folded while
that recompiles. (Brief silence). So France is cool. (Audience laughter).
There it is. "Hello world". You know what, you don't have to clap every time. I'll let you know when to clap after I'm finished writing. I'm a regular Jeb Bush. (Laughter from American audience members, I'm assuming).
Okay so we can type in here and CodeMirror is fine with that but as you can see no messages are being triggered. We have this whole elaborate listener set up but but
nothing coming in. So the way that we deal with this is to go down to where we
defined out editor, and if you'll remember from before when I was talking really
fast about ports we listen to on
"changes" to find out when CodeMirror has changed.
And this time we will first sync our backing store with the CodeMirror instance and then we'll dispatch an event – this is a built-in function – with a new
CustomEvent – this is a built-in constructor. And we said it was
'editorChanged'. I'll save it. I hope the syntax is right ... looks like it is. I think this is the last time we're gonna do this so that's fine. And now when I expose everything from
Main we can see there's some messages there and it's filling in character by character.
That's how you use a custom element with Elm! When I figured out how to do this I was pretty psyched. I was like "hell yeah, dude"
Slide 38: "'Hell yeah, dude' – Me, to myself after figuring out how to do this"
Remember that. (Audience laughter).
Slide 39: "⚠️Some warnings ⚠️"
It's not all fun and games though. It's not all jokes. There's some things you need to be aware of when you're doing this, even though I totally recommend that you do.
Slide 40: "Warning #1 The Elm Virtual DOM is always right"
The first thing, warning number one, is that the Elm virtual DOM is always right. It's always going to have the last say about what's actually in the DOM. So, to illustrate my point,
Slide 41: A
Maps.mapview function with
I when I teach this to people sometimes they ask, maybe they're coming from React, and they ask "If I have a Google Maps element can I put markers in the map as nested elements? That kind of looks the way that I think that should look." And I agree that it looks nice, like this is pretty cool.
Slide 42: What Elm needs the
Maps.maprendered output to be vs. what Leaflet or Google Maps would need the DOM to be, displayed top vs. bottom.
The problem is the top code is what Elm thinks you're asking for when you when you do this and these markers represent elements this is what Elm thinks that you want it to be and when you render your map into the custom element this is what it's actually going to be. So you have this conflict here and the virtual DOM is always going to overwrite what's in there, that you've put in there kind of outside of its
Slide 43: "Shadow DOM"
So what you should do instead when you have situations like this is
either use the shadow DOM if your browser space supports it.
Slide 44: Caniuse stats for shadow DOM v1.
Unfortunately it probably doesn't and unfortunately this is not really possible to polyfill completely, so we have some waiting to do there.
Slide 45: Elm code to writing markers into the map with
Or what I recommend that you do is represent your markers, represent your
your nested data as like some other kind of structured data and encode it to JSON
Slide 46: "Warning #2 The Elm Virtual DOM reuses nodes"
Warning number two is that virtual DOM reuses nodes. And this is kind of weird
and it's hard to explain why it happens, but you'll know it when you see it and I'll give you an example so maybe if you go down this path and you encounter
something that's really weird you'll remember this and kind of have an intuitive understanding of what's going on.
Slide 47: Two dark blue rectangles, with one light blue rectangle inside of one of the dark blue ones
So let's say we have a custom element for rendering blue
rectangles. It's like really just incredibly useful custom element. It's very important. They can be light blue or dark blue, and the custom element decides. One
is inside of the other and what we want to do is remove the outer dark blue one
and have the light blue one fill the space.
Slide 48: The light blue rectangle has replaced the dark blue rectangle that contained it
So what we expect it to look like is this.
Slide 49: The light blue rectangle has vanished and the dark blue rectangles remain.
What's actually going to happen is this. And that's because the virtual DOM saw the outer rectangle and it's like "oh this is a rectangle I'm supposed
to put a rectangle here so I'm done." And it's wrong but it's not Elm's fault, it's mine for making a bad rectangle. So there's those tricks you can do and I really encourage you to avoid getting into the situation but it is good to be
aware of just in case.
Slide 50: "Warning #3 Don't use
And then warning number three,
Slide 51: Same as slide 50, but with small text
(Whispering) and this is this a small warning
Slide 52: Back to normal sized text
It's not very big deal. Warning number three is don't use
value because the
Elm virtual DOM special cases this particular property name to deal with
Slide 53: "Recommendations: 1. Always use leaf nodes 2. Don't use
So here's what I recommend you do when you're building your custom elements. The first one is the most important and it's to always use leaf nodes whether your
custom element itself is at the very end of your tree branch or whether you've
got some stuff inside of it but that stuff is fixed. I'll give some examples of that. And the second one is to not use value for the reason I just mentioned.
Slide 54: A simplified version of the
Html.nodecall to create the editor custom element in Ellie
So here is almost verbatim the the code editor view from from Ellie. It takes a
bunch of attributes and it renders them all and most importantly that last list
there is empty. Elm, when it sees an empty list from diff to diff, it doesn't clear anything out. It just skips it and says "I get it there's nothing here," even though we're lying. You can see the actual implementation of this in the
Ellie repo and I know this is a long URL and I'm gonna click pass it but I'll
post it in slack or something. (It's here: https://github.com/ellie-app/ellie/blob/master/assets/src/Ellie/Ui/CodeEditor.elm#L112-L114)
Slide 55: code from the NoRedInk codebase for using a custom element that grows and shrinks a textarea when the user types
Elm virtual DOM is like totally okay with. So since everything within the custom element is fixed and not really changing around this will survive the
diff. So you can you can do it that way too. You can't do dynamic stuff inside of, a dynamic list of elements inside of a custom element and feel totally secure about doing that so I recommend that you don't.
Slide 57: "UI widget? Custom Element!"
First ask, "Is the thing you want to do a UI widget? Is it a map or a date picker or a fancy input?" In that case use a custom element.
Slide 58: "Growth Hacking AI Blockchain client, etc? Use a port"
Otherwise if you have a growth hacking AI blockchain client or something similar to that, you use a port. And so with that I'll just leave you with something that's kind of a tradition at Elm conferences: to put a slide with Evan's face superimposed on an actor.
Slide 59: A diagram of the Actor Model, with Evan's face on top of elements of the diagram representing actors in the system.
So here's mine. (Audience laughter).
Slide 60: "email@example.com", followed by links
Yeah you can email me about this or you can find me in slack and those are some some links that you should check out to learn about how to do this.
Thanks everybody (Audience applause).
Oh yeah, questions.
So I've actually done this with CodeMirror as well I came up with a very similar solution with custom elements but I had this problem that if you typed really fast in the CodeMirror editor like you do kind of day-to-day, often the letters kind of ended up in a different order or you dropped some characters. Kind of they got
overwritten by the Elm state management – overwrote the internal CodeMirror state management. Did you manage to work out how to how to stop that from happening?
Yes, so in the example that I gave I'm just dispatching events every time
the change event happens and in real life I debounce the code that dispatches the event on
requestIdleCallback. So as long as you're typing or have typed recently it won't bother to trigger any changes. So by the time it actually sends the event into Elm the editor is like guaranteed to be settled.
Well that's awesome, thanks.
Yeah so you can find that in the Ellie codebase.
So what's the main advantage of using custom elements over mutation observers?
I think the the the instance management – I guess is what I would call
that. Like you don't have to manually watch the DOM and wait for those
elements to be discovered. You don't have to have a mutation observer, you don't have to write the mutation observer that then queries the DOM that's changed and
find all elements and then iterate over them, it just kind of does that for you. I
think most of the polyfills that exist are based on mutation observer so nothing
you can do with a custom element can't be done with a mutation observer. It's
just kind of nicer it's a nicer API.
How do you manage Firefox> do you use a polyfill?
Actually the second link here is a repo for like the official polyfill. It works very well in Firefox. We use it in production at NoRedInk and I use it in production on Ellie and we haven't had any problems with Firefox at all. And I think actually they're like about to release support like in the next version so that's very cool.
All right I think that's it I think and you can come find me wherever I'll be happy to help.