If we take a look at the Phoenix live view Docs this statement actually comes directly from it Phoenix live view is a library that enables rich real-time user experiences all from server rendered HTML and you might be thinking that this is quite an ambitious claim is that even possible what does actually mean to you what this actually means is you don't have to immediately reach for some sort of front-end library or framework to have rich user experiences what it actually means is that you can truly write some rich interactive user interfaces using the tools that you're already familiar with in the elixir ecosystem without needing to write JavaScript you might need to write JavaScript but definitely a lot less so but don't get me wrong when I make this statement you still might want client side JavaScript it still does have its use cases and you shouldn't discard it completely but one of the benefits is that we can lessen the technical burden of developers by not making them learn JavaScript immediately and you you know obviously you don't have to wrestle with a web pack for hours just to get your loader to work but you can delay implementing client-side JavaScript until it's more of a hard requirement that will actually fulfill your business's needs and if you're unfamiliar with live you might be wondering how is this all possible what kind of wizardry is that play here and is this really too good to be true don't worry
LiveView is actually comprised of three main things we have
live ee x which is responsible for the templating
Phoenix channels which are responsible for the bi-directional communication over WebSockets and
JavaScript pretty minimal but it is necessary unfortunately
live ee x
live eex is responsible for rendering our templates and rendering dynamic changes if we are in a stateful mode based on any data that changes within your process live eex is actually built on Phoenix's regular eex template which means you can use the familiar syntax so you're already used to if you are already using the templating system and if you already didn't know ee x templates and lightly e x-- templates are come optimized at compile time and will actually dive into the details of this in just a moment
With live eex you can actually write your templates in two different ways
we can use an actual file where we render the whole template in a dot leex extension file and alternatively you can use them in line using the L sigil there's also only equivalent E sigil. if you've never seen that before this lets you just write them inline in a file but both of these forms are treated the same at compile time
I actually want to walk through what the compiler sees because this is where all the cool change tracking actually happens so say we have this inline template where we are just rendering a simple string of hello and some dynamic name that we're going to be pulling away from the assigns when the compiler sees this template it's going to be breaking up this string by certain delimiters and one of those delimiters would be you know the percent equal and then you know your assigned value but when the compiler sees this is actually gonna be in the AST form the quoted form you don't really have to understand it but just know it does look weird and it's not just you
But the live view engine the live eex engine is gonna try and separate things that it can determine that our static such as the string literals like this hello in this period and the other things that are dynamic and that would require some dynamic evaluation at runtime
so it's going to actually separate these out into two separate lists we're gonna have these this list of statics and dynamics in the statics we're actually going to put a placeholder variable where we're going to inject this value after it's evaluated and then we're gonna pull out the dynamic code and assign it to a an argument or sorry a variable and if you really wanted to see what this actually prints out in is injected into your code at runtime this is actually what it looks like
don't worry about completely understanding it but the key thing here is a look at that case statement so we're saying if this value changed at runtime and we know it changed you know go evaluate it and return it otherwise if it did not change we're just gonna return nil
when we actually are done compiling we're actually gonna get some sort of structure that looks like this
we have this rendered struck that's going to contain three keys one of them is this static field which is you know those string lentils that literals that I mentioned just a second ago and we're also going to hold a list of dynamic values that we saw with those placeholder variables and when we evaluate our time it's the most important part this fingerprint this is a uniquely guaranteed value that represents a template at any given time so if we were to actually have runtime see a different fingerprint that will signal that we are in a completely different template that's being rendering or being rendered and just you know replace everything that you see but at runtime, this argument is going to be evaluated and it's gonna be filled and returned to the client and change tracking will happen on the Dom and all that good magic stuff
so one thing that we can fill in with this dynamic is some optimizations or different types of data and I want to walk through that real quick
io data which would be you know string literals
nil which means you know at runtime this value did not change so don't go update it
rendered struct meaning we have nested live view or nested templates being rendered at any given point
comprehension struct and this is for representing like for comprehensions we didn't really want to send up this dynamic data every single time so Joe's went and optimized this and want to actually walk through that real quick so let's imagine we have this list of XY coordinates in this assigns called points and we're going to iterate through it with a for comprehension and we want to just output what that x and y coordinate is with a label in front so if we imagine this is what those values are like a list of x and y's
when we evaluate it we're actually going to separate those out as well we're have the list of statics and the list of dynamic values
and when we actually render list to the Dom the statics and dynamics are just gonna be kind of like interleave together to reconstruct what the entire Dom representation should be so the reason we do this is so we send less data to a client it's a small optimization but these are some small things that Phoenix live view can do at compile time for us
let's actually look at how this actually gets mounted to the Dom there are three main ways of getting a live view onto our Dom
we could either route directly to a live view from the router using the live macro from a controller
we can actually call a live render and this will just instead of like render a template render this live view
and additionally, you can render another live view template within an actual eex template and it's gonna look exactly the same as a controller action but they're actually differently named spaced
But when we do mount to the Dom we're gonna get what we call this container div
container div contains information that LiveView needs on the JavaScript side as well as a server-side and if we look at this first attribute here we have this ID this is uniquely generated on every render and this actually doubles as the phoenix channels live view ID so whenever we connect a live view it'll open a channel and join it where it matches this ID
and then we have this weird-looking string which is actually really shortened here but it's some sign stuff session data that will be passed to your live yuan join the the channel process this information in here includes some stuff like the live view modules name the expected channel ID which you know doubles as the the ID of the element some information about the router intend points and maybe a parent paid if we are in nested live you land but the key thing here is that this data is signed by Phoenix so we have a tamper-proof way of providing data in a stateless manner to our live view processes when they are spawned on the server
In this last attribute here is data PHX view this would just be your module name of the live view that would be called JavaScript uses this as a query selector for finding some things there's also a way to get this when you're in a JavaScript hook which Chris will go into later in his keynote don't forget it but it mostly acts as a query selector for now
one cool thing that most people may not know is we can actually change the container div to being something else so if you have some particular markup that needs to be matched and styled you can actually change this by default it's going to use a div but if you provide a keyword option you can change this by providing a two element tuple where the first element is a the tag name and it can also provide some HTML attributes that are for data to the Dom to render so if I were to change it to a P tag and I wanted to add a class it would just be reflected when you render the next thing I wanted to cover is
Phoenix channels
Phoenix channels is the abstraction that gives us a mechanism for bi-directional communication between our browser and our server and if you've gone through setting up a a live view or integrating LiveVIEW into your existing Phoenix applications
You would have had to add this line to your endpoint and all this does is open up an entry point into your application for our WebSocket endpoint and it's actually going to use Phoenix live view own socket implementation
when we look at this this socket act module we can see that Phoenix ships with its own single channel with the prefix of LV so this is the topic that's used for all live view communication and we also ship with a our own implementation of a channel and let's actually walk through what happens when we boot up a channel process for live view so we're gonna use this topic as an example topic using some of the same data I showed off a few slides ago don't worry it's not really important if you if you call it all that data but when this process spawns we're gonna expect some data to be passed to it so this supply data is going to be a map with some expected keys if you're if you're used to gen servers this would be the data that's passed via like in it or start link so that that makes it to where if our process dies we can boot back up with the same exact data if we crash if we take a look here this first key that we run into is params you can actually supply client-side data to your live view processes so if you needed some contextual information about your browser such as like a time zone it can provide that when you instantiate your live socket object it's actually the dock but it's a lesser-known thing but if you need client-side information you can pass along to your live views and read it when you're in a connected socket
we'll also get a key with our session data this is that same session data we saw on the Dom from that data PHX session attribute but this data is useless to us almost we can go to decode it and it actually looks a little bit like this internally so we're gonna have that ID which matches our topic we're gonna have a little bit information about our router which actual live view we're going to be calling into and any session data that we need to supply to our live view unmount and since our live users are actually channel processes under the hood we can simulate what would actually happen when we get an event so let's actually imagine that we have a button here that has a click handle there attached to it and it also has a value attached to it and let's actually see what happens if we were to simulate this click
our channel process is going to receive a socket message we're gonna receive an event type called event it's not very descriptive but this could be also be something like live redirects or some other event that's triggered from JavaScript but for events this would be the same for all event handlers such as like blur or update or submit on your forms we actually see this payload which can provide some contextual information so if you look to the left we're matching some values here so we have our event which matches my click event and some other data that's attached to the event handler and this information will be called and invoked with our handle event callback in our view module so like under the hood like I'm mentioning it's just the channel process with some callbacks in your module
I guess I'm going really fast ok and I actually wanted to cover JavaScript because this is actually an important thing if you didn't already know Chris McCord absolutely loves JavaScript so so much so that he wants to write it for you so if there's ever a JavaScript feature that you are wanting please go talk to Chris McCord
So to connect the browser to our server over WebSockets we actually need to instantiate this live socket object when we create it we're actually going to pass in that route that we expose in our endpoint and when we actually instantiate this object some top-level event handlers are attached to the browser's window such events such as clicks and key presses and like I mentioned a second ago we can here provide some additional options to this object instantiation such as prams you want to send to your live view you can add the buggers you can even add some JavaScript hooks I will not go into that today but we have JavaScript hooks
And to actually bring our live views alive on the Dom we actually have to call connect on our live socket object this will go through the DOM and I'll go through that in just a second but when we do that we're gonna go through the whole entire Dom we have this query selector here which we'll go through each entire element on the Dom to see if it has this data PHX view attribute and if it's not some nested live view and this way we can consider like the the root container of our live view
live view has this javascript class which is used to hold some information about our live views such information such as the current dom node in scope the live views ID which doubles as a channel ID and the current state of the things that should be rendered or the current state of the world that it knows of so every live you container that we find we create a brand new object and each object then will add the appropriate event handlers to the live view and it'll also intercept any events coming from the server so you can handle them if you need to via hooks and these events also include such things as like live redirects and dips that come from the server and once we have this new view object we join a channel when we join the channel their servers actually give a reply back with some information about the new state of the world so if I haven't mentioned this your live views mount function is actually gonna be called twice so we have the normal HTTP response you know the get that's going to return back some static HTML and when you have your live view process connected via JavaScript it's going to be executed again and that's going to have a potentially different state of the world and I actually want to walk through what that would look like
so our server is actually gonna reply back with this rendered representation of what we saw a few slides ago from like live e X so we're gonna have a section on some static data which would be like the things that aren't gonna change very often and then we're gonna have some placeholder information for things that are dynamic and just like before when we inter leave these things that would give us the accurate representation of what the Dom should be
so if I were to assign some variables to this I wanted to walk through like how wide we would actually do this so like I was mentioning it's actually going to interleave each item from each static value and then very again it's gonna be reconstructed into a total string which then would be applied to the Dom
But one good feature of live view is that we have change tracking and we're going to apply diffs to the Dom if our data changes so if we were to see a diff from the server due to some event that we triggered we're gonna receive a diff and if our value change we're gonna get a placeholder value for it
That value is going to be merged so we saw that that zero index was also was the dynamic data and as gonna be replaced with that dynamic data and we're gonna have our whole new state of the world
we need to apply this to the Dom with an efficient diff mechanism and we actually use morph Dom js for this
I like popular JavaScript libraries such as react or vue morph Dom does not have a virtual Dom it's a whole benefit is that it uses the real Dom as the source of truth and it actually is pretty fast for certain size of disks and we could just replace the whole Dom node if we really wanted to and delete it and replace it but that's not really a good practice because if you have some sort of animation being applied to your your HTML element or there's some squirrel positions that you're using you're actually going to mess up your users view so what the more film does it just does the minimal diff to get that new state of the world
Morph Tom's API is actually really straightforward you just have you find an element on the page that you want to change and then you give the new representation of what you want it to be and you just say hey morph Dom here's what I want here's what I have
And this example actually comes from a blog post from Chris McCord if you look at it if you're in Google Chrome and you're inspecting the HTML things that highlight are the things that change on the Dom and if you can see where Chris is actually moving a slider around but only a few fields actually changed so instead of replacing that whole Dom element we're only replacing the things that need to be changed
Putting it all together how does this all fit together into individual pieces so let's imagine we have our browser and we have a server our browser is going to make a get request to our server then our servers want to fly back with our static HTML and some Java scripts gonna be loaded on the page our JavaScript is gonna start that WebSocket connection which actually boot up a live view process on the server our live view process is going to respond back with what the save-the-world should be knowing that we have this booted process and it's going to send it up to the JavaScript to handle and this JavaScript is going to update its known state of the world and it's going to re-render via morph tom and if we were to add some sort of event handler such as a click that click event is going to be pushed down to our live view process our library process is going to handle it update its assigns and we're gonna have a new state of the world with a diff or even a new template that's gonna be rendered all together it's gonna push it back up to the JavaScript we're good at our new state of the world and this cycle continues on and on until your Live View process dies or you just leave the page
Comments
Post a Comment