В настоящее время очень важно иметь интерактивность на наших веб-сайтах.
Я лично не люблю пользоваться формой поиска без обратной связи в режиме реального времени. Введите что-нибудь, нажмите кнопку поиска, подождите ... Ничего не найдено. Хорошо. Попробуйте снова. Измените текст внутри поля ввода, снова нажмите кнопку поиска. И ждать. После нескольких итераций я замечаю, что стараюсь сократить количество поисковых запросов, потому что не хочу ждать и снова видеть страницу загрузки. Как разработчик программного обеспечения, я использую js-фреймворки, такие как React, когда требуется интерактивность. Но давайте будем честными: после их добавления ваше приложение становится намного сложнее разрабатывать и поддерживать. Кроме того, увеличивается время разработки и сложность проекта. Вот почему я дважды думал, прежде чем принять решение о добавлении одного из них, когда для небольшого проекта требуется функция реального времени. Но благодаря Phoenix LiveView вам не придется дважды думать, прежде чем добавить интерактивность! С помощью этой библиотеки вы можете иметь динамическое поведение с серверным HTML, который обновляется с помощью веб-сокетов под капотом.
Невероятно, насколько легко создать интерактивное приложение реального времени: это почти то же самое, что создать обычную статическую страницу! Я использовал LiveView для интерактивного поиска своего проекта под названием Heroku Lighthouse. Этот проект собирает все приложения Heroku и их пользовательские домены в одном месте и может быть очень полезным, когда вы должны взаимодействовать с большим количеством из них одновременно и не можете точно вспомнить, какой домен принадлежит какому приложению.
Поле поиска помогает найти нужные приложения по имени или собственному домену.
Позвольте мне показать вам, как легко добавить эту функцию в ваше приложение Phoenix!
Во-первых, нам нужно добавить LiveView в наш mix.exs
:
def deps [ ... {:phoenix_live_view, "~> 0.3.0"} ] end
Затем сгенерируйте секрет с помощью mix phx.gen.secret
и обновите config.exs
:
use Mix.Config ... config :heroku_lighthouse, HerokuLighthouseWeb.Endpoint, pubsub: [name: HerokuLighthouse.PubSub, adapter: Phoenix.PubSub.PG2], live_view: [ signing_salt: "<secret goes here>" ]
В моем случае я также добавил конфигурацию для pubsub, потому что она будет использоваться позже.
Добавьте код в lib/heroku_lighthouse_web.ex
:
defmodule HerokuLighthouseWeb do def controller do quote do ... import Phoenix.LiveView.Controller end end def view do quote do ... import Phoenix.LiveView, only: [ live_render: 2, live_render: 3, live_link: 1, live_link: 2 ] end def router do quote do ... import Phoenix.LiveView.Router end end end
Хорошо, мы почти закончили с приготовлениями. Просто нужно добавить код в assets/js/app.js
:
import {Socket} from "phoenix" import LiveSocket from "phoenix_live_view" let liveSocket = new LiveSocket("/live", Socket) liveSocket.connect()
и assets/package.json
:
"dependencies": { ... "phoenix_live_view": "file:../deps/phoenix_live_view" }, ...
При желании для перезагрузки страницы в реальном времени добавьте это в config/dev.exs
:
config :heroku_lighthouse, HerokuLighthouseWeb.Endpoint, live_reload: [ patterns: [ ... ~r{lib/heroku_lighthouse_web/live/.*(ex)$} ] ]
Теперь мы готовы создать нашу страницу с LiveView.
Во-первых, давайте добавим новый маршрут к router.ex
:
scope "/", HerokuLighthouseWeb do pipe_through [:browser, :authenticate_user] get "/dashboard", DashboardController, :index end
Пришло время создать представление и контроллер.
lib/heroku_lighthouse_web/views/dashboard_view.ex
выглядит так:
defmodule HerokuLighthouseWeb.DashboardView do use HerokuLighthouseWeb, :view end
А это dashboard_controller.ex
:
defmodule HerokuLighthouseWeb.DashboardController do use HerokuLighthouseWeb, :controller alias HerokuLighthouse.Dashboard alias Phoenix.LiveView alias HerokuLighthouseWeb.DashboardLive.Index def action(conn, _) do apply(__MODULE__, action_name(conn), [conn, conn.params, conn.assigns.current_user]) end def index(conn, _params, user) do LiveView.Controller.live_render(conn, Index, session: %{user: user}) end end
Здесь ничего особенного. live_render
отображает наш вид в реальном времени HerokuLighthouseWeb.DashboardLive.Index
. Пора создать lib/heroku_lighthouse_web/live/dashboard_live/index.ex
:
defmodule HerokuLighthouseWeb.DashboardLive.Index do use Phoenix.LiveView alias HerokuLighthouse.Dashboard alias HerokuLighthouseWeb.DashboardView def mount(session, socket) do apps_by_team = Dashboard.cached_grouped_apps(user) {:ok, assign(socket, apps_by_team: apps_by_team, current_user: session[:user])} end def render(assigns) do DashboardView.render("index.html", assigns) end end
Когда визуализируется просмотр в реальном времени, вызывается обратный вызов mount
. В моем случае я получаю данные из Heroku API, кэширую их, сохраняю результат в переменную apps_by_team
и назначаю данные сокету. После этого вызывается render
. Он возвращает браузеру простой HTML. Клиент подключается к просмотру в реальном времени через сокет и поддерживает постоянное соединение.
Далее нам нужно создать шаблон с полем поиска. lib/heroku_lighthouse_web/templates/dashboard/index.html.leex
:
<form phx-change="search" class="search-form"> <%= text_input :search_field, :query, placeholder: "Search for application name, web url or custom domain", autofocus: true, "phx-debounce": "300" %> </form> <%= for {team, apps} <- @apps_by_team do %> <h2><%= team.name %></h4> <table> <thead> <tr> <th>Name</th> <th>Web URL</th> <th>Domains</th> </tr> </thead> <tbody> <%= for app <- apps do %> <tr> <td><%= app.name %></td> <td><%= app.web_url %></td> <td> <%= for domain <- app.domains do %> <div><%= domain %></div> <% end %> </td> </tr> <% end %> </tbody> </table> <% end %>
Атрибут формы phx-change="search"
обрабатывает входные изменения и отправляет на сервер событие со значениями из всех входных данных внутри формы. Таким образом, каждый раз, когда ввод обновляется, сервер будет получать эти изменения. Чтобы уменьшить количество запросов к серверу, я также добавил "phx-debounce": "300"
в поле поиска.
В настоящее время сервер не может обрабатывать эти события. Давай исправим.
lib/heroku_lighthouse_web/live/dashboard_live/index.ex
:
defmodule HerokuLighthouseWeb.DashboardLive.Index do ... def handle_event("search", %{"search_field" => %{"query" => query}}, socket) do filtered_apps = Dashboard.filter_grouped_apps(socket.assigns.current_user, query) {:noreply, assign(socket, :apps_by_team, filtered_apps)} end end
Когда на сервер приходит событие, я просто фильтрую приложения по значению из поиска и присваиваю их apps_by_team
. Phoenix live view замечает, что значение изменилось, и повторно отображает часть модели DOM с новыми отфильтрованными приложениями.
Как видите, с LiveView мы можем легко добавить интерактивности. Когда новый пользователь приходит в Heroku Lighthouse и у него есть несколько приложений на Heroku, рендеринг первой страницы может занять некоторое время. Давайте переместим эту операцию в фон, быстро отобразим страницу и обновим ее после получения всех данных. Мы можем сделать это, отправив сообщения на HerokuLighthouseWeb.DashboardLive.Index
.
lib/heroku_lighthouse/dashboard/dashboard.ex
:
defmodule HerokuLighthouse.Dashboard do ... def cached_grouped_apps(user) do Cachex.get!(:cache_warehouse, "user_#{user.id}_apps") || fetch_apps_async(user) end defp fetch_apps_async(user) do Phoenix.PubSub.broadcast(HerokuLighthouse.PubSub, "dashboard:#{user.id}", :fetching_apps) Task.start_link(fn -> fetch_and_cache_apps(user) Phoenix.PubSub.broadcast(HerokuLighthouse.PubSub, "dashboard:#{user.id}", :apps_fetched) end) [] end
Если в кеше нет пользовательских приложений, мы публикуем событие с темой "dashboard:#{user.id}"
и сообщением fetching_apps
и начинаем получать приложения асинхронно. После загрузки приложений будет опубликовано новое сообщение :apps_fetched
. Теперь нам нужно обработать эти сообщения в режиме реального времени.
lib/heroku_lighthouse_web/live/dashboard_live/index.ex
:
defmodule HerokuLighthouseWeb.DashboardLive.Index do ... def mount(session, socket) do user = session[:user] is_fetching = Map.get(socket.assigns, :fetching_apps, false) Phoenix.PubSub.subscribe(HerokuLighthouse.PubSub, "dashboard:#{user.id}") apps_by_team = Dashboard.cached_grouped_apps(user) { :ok, assign( socket, apps_by_team: apps_by_team, current_user: session[:user], fetching_apps: is_fetching ) } end def handle_info(:fetching_apps, socket) do {:noreply, assign(socket, fetching_apps: true)} end def handle_info(:apps_fetched, socket) do apps = Dashboard.cached_grouped_apps(socket.assigns.current_user) {:noreply, assign(socket, apps_by_team: apps, fetching_apps: false)} end end
Последний шаг - обновить шаблон.
lib/heroku_lighthouse_web/templates/dashboard/index.html.leex
:
<%= if @fetching_apps do %> <h2> Fetching apps...</h2> <% else %> <form phx-change="search" class="search-form"> <%= text_input :search_field, :query, "phx-debounce": "300", placeholder: "Search for application name, web url or custom domain", autofocus: true %> </form> <% end %>
Страница будет обновлена автоматически, как только все данные будут получены из Heroku!
Это все на сегодня!
Спасибо, что прочитали мой пост, надеюсь, он был вам полезен!