Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Phoenix LiveView Demystified

Phoenix LiveView Demystified

Phoenix LiveView is here to empower developers to build rich, real-time user experiences in Elixir without needing to write JavaScript. At the surface, this may seem like magic, but a closer look reveals how Phoenix leverages the primitives of the platform, with some front-end help from Morphdom.js, to make this programming model a compelling choice for beginners or experts alike.

Starting from the server to the browser, we’ll explore what makes Phoenix LiveView possible. Along the way, we’ll learn tips and tricks to make the most of your LiveView applications while seeing how Phoenix Channels and LiveEEx templates power the internals. We’ll also learn how LiveView is an abstraction over GenServer, giving us resiliency in the browser. Finally, we’ll cover how Phoenix LiveView interacts with JavaScript to provide bidirectional communication of events between the server and the browser.

Whether you’re a new or seasoned Elixir developer, you’ll leave with a new mental model of how Phoenix LiveView works to drive real-time user experiences all from server-rendered HTML.

Alex Garibay

August 29, 2019
Tweet

More Decks by Alex Garibay

Other Decks in Programming

Transcript

  1. Phoenix LiveView [is a library that] enables rich, real-time user

    experiences with server- rendered HTML. ” “
  2. [ "Hello, ", {:@, [line: 11], [{:name, [line: 11], nil}]},

    "." ] ~L""" Hello, <%= @name %>. """
  3. # Statics and Vars [ "Hello, ", {:arg0, [], Phoenix.LiveView.Engine},

    "." ] # Dynamics [ {:=, [], [ {:arg0, [], Phoenix.LiveView.Engine}, {{:., [], [Phoenix.LiveView.Engine, :to_safe]}, [], [{:@, [line: 11], [{:name, [line: 11], nil}]}]} ] } ]
  4. arg0 = case(Phoenix.LiveView.Engine.changed_assign?(__changed__, :name)) do true -> case(__prints__) do %{0

    => {_, _} = print} -> Process.put({Phoenix.LiveView.Engine, :fingerprint}, print) %{} -> :ok end case(Phoenix.LiveView.Engine.fetch_assign!(assigns, :name)) do %{__struct__: Phoenix.LiveView.Rendered} = other -> other {:safe, data} -> data bin when is_binary(bin) -> Plug.HTML.html_escape_to_iodata(bin) other -> Phoenix.HTML.Safe.to_iodata(other) end false -> nil end
  5. %Phoenix.LiveView.Rendered{ static: [ "Hello, ", "." ], dynamic: [ ]

    fingerprint: 216187664090110924469697367378888494464 } arg0
  6. %Phoenix.LiveView.Rendered{ static: [ "Hello, ", "." ], dynamic: [ ]

    fingerprint: 216187664090110924469697367378888494464 } "ElixirConf"
  7. <%= for point <- @points do %> x: <%= point.x

    %> y: <%= point.y %> <% end %>
  8. <%= for point <- @points do %> x: <%= point.x

    %> y: <%= point.y %> <% end %> points = [ %{x: 1, y: 2}, %{x: 3, y: 4} ] !20
  9. # In a router live "/", ClockLive # In a

    controller live_render(conn, ClockLive, session: %{}) # In a template live_render(conn, ClockLive, session: %{})
  10. "lv:phx-xeR6nJ8t" %{ # Params from JavaScript LiveSocket object "params" =>

    %{ "timezone" => “America/Denver" }, # data-phx-session "session" => "SQyYk.g3QACZAN8XasAQ.RnkD" }
  11. "lv:phx-xeR6nJ8t" %{ # Params from JavaScript LiveSocket object "params" =>

    %{ "timezone" => “America/Denver" }, # data-phx-session "session" => "SQyYk.g3QACZAN8XasAQ.RnkD" }
  12. "lv:phx-xeR6nJ8t" %{ # Params from JavaScript LiveSocket object "params" =>

    %{ "timezone" => “America/Denver" }, # data-phx-session "session" => "SQyYk.g3QACZAN8XasAQ.RnkD" }
  13. "lv:phx-xeR6nJ8t" %{ # Params from JavaScript LiveSocket object "params" =>

    %{ "timezone" => "America/Denver" }, # data-phx-session "session" => %{ id: "phx-xeR6nJ8t", parent_pid: nil, router: MyAppWeb.Router, session: %{}, view: MyAppWeb.ClockLive } }
  14. <button phx-click="my_click_event" phx-value="button's value" > Click </button> %Phoenix.Socket.Message{ event: "event",

    join_ref: "1", payload: %{ "event" => "my_click_event", "type" => "click", "value" => "button's value" }, ref: "2", topic: "lv:phx-xeR6nJ8t" }
  15. <button phx-click="my_click_event" phx-value="button's value" > Click </button> %Phoenix.Socket.Message{ event: "event",

    join_ref: "1", payload: %{ "event" => "my_click_event", "type" => "click", "value" => "button's value" }, ref: "2", topic: "lv:phx-xeR6nJ8t" }
  16. <button phx-click="my_click_event" phx-value="button's value" > Click </button> %Phoenix.Socket.Message{ event: "event",

    join_ref: "1", payload: %{ "event" => "my_click_event", "type" => "click", "value" => "button's value" }, ref: "2", topic: "lv:phx-xeR6nJ8t" }
  17. <button phx-click="my_click_event" phx-value="button's value" > Click </button> %Phoenix.Socket.Message{ event: "event",

    join_ref: "1", payload: %{ "event" => "my_click_event", "type" => "click", "value" => "button's value" }, ref: "2", topic: "lv:phx-xeR6nJ8t" } view_module.handle_event("my_click_event", "button's value", socket)
  18. let parent = null let href = window.location.href let view

    = new View(lvContainer, liveSocket, parent, href) view.join()
  19. let parent = null let href = window.location.href let view

    = new View(lvContainer, liveSocket, parent, href) view.join()
  20. let parent = null let href = window.location.href let view

    = new View(lvContainer, liveSocket, parent, href) view.join()
  21. let {rendered} = { "rendered": { "0": "10:10", "static": [

    "<p>The time is ", ".</p>\n" ] } } let static = rendered.static
  22. let {rendered} = { "rendered": { "0": "10:10", "static": [

    "<p>The time is ", ".</p>\n" ] } } let static = rendered.static static[0] + rendered[0] + static[1]
  23. { "rendered": { "0": "10:10", "static": [ "<p>The time is

    ", ".</p>\n" ] } } { "diff": { "0": "10:11" } }
  24. var morphdom = require('morphdom') var source = document.createElement('p') source.innerHTML =

    'The time is 10:10.' // '<p>The time is 10:10.</p>' var desired = '<p>The time is 10:11.</p>' morphdom(source, desired)