LiveView -Chat -Tutorial 
Wir wollten wirklich ein kostenloses und open-Source -Beispiel mit vollem Code, Tests und Auth.
Wir haben dies geschrieben, damit wir Menschen in unserem Team/Community -Lernen Phoenix LiveView darauf hinweisen können.
Dieses LiveView -Beispiel/Tutorial führt in 20 Minuten von Null bis voll funktionsfähig .
Hier ist der Inhaltsverzeichnis, was Sie in diesem Beispiel/Tutorial erwarten können:
LiveView -Chat -TutorialPhoenix -Applive -Verzeichnis, LiveView -Controller und Vorlagerouter.exmount/3 -Funktionmount/3handle_event/3handle_info/2AUTH_API_KEYauth_plug installierenrouter.exAuthController erstellenon_mount/4 Funktionen Jeder lernt Phoenix LiveView der ein in sich geschlossenes Tutorial wünscht, einschließlich: Setup , Testing , Authentication , Presence ,
Es wird empfohlen , wenn auch nicht erforderlich , dass Sie das LiveView -Zähler -Tutorial folgen, da dieser fortgeschritten ist. Schauen Sie sich zumindest die Liste der Voraussetzungen an, damit Sie wissen, was Sie auf Ihrem Computer installiert haben müssen, bevor Sie dieses Abenteuer beginnen!
Vorausgesetzt, Sie haben Elixir , Phoenix und Postgres installiert, können Sie loslegen!
Phoenix -App Erstellen Sie zunächst die neue Anwendung liveview_chat Phoenix :
mix phx.new liveview_chat --no-mailer --no-dashboard Wir brauchen keine email oder dashboard -Funktionen, daher schließen wir sie von unserer App aus. Weitere Informationen zum Erstellen neuer Phoenix -Apps finden Sie unter Ausführen: mix help phx.new
Führen Sie mix deps.get aus, um die Abhängigkeiten abzurufen. Erstellen Sie dann die Datenbank liveview_chat_dev postgres , indem Sie den Befehl ausführen:
mix ecto.setupSie sollten die Ausgabe ähnlich wie folgt sehen:
The database for LiveviewChat.Repo has been created
14:20:19.71 [info] Migrations already upSobald dieser Befehl erfolgreich ist, sollten Sie jetzt in der Lage sein, die Anwendung zu starten, indem Sie den Befehl ausführen:
mix phx.serverSie sehen eine terminale Ausgabe ähnlich wie folgt:
[info] Running LiveviewChatWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz
[info] Access LiveviewChatWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes... Wenn Sie die URL öffnen: http://localhost:4000 in Ihrem Webbrowser, sollten Sie etwas Ähnliches sehen wie:

live -Verzeichnis, LiveView -Controller und Vorlage Erstellen Sie den Ordner lib/liveview_chat_web/live und den Controller unter lib/liveview_chat_web/live/message_live.ex :
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
def mount ( _params , _session , socket ) do
{ :ok , socket }
end
def render ( assigns ) do
LiveviewChatWeb.MessageView . render ( "messages.html" , assigns )
end
endHinweis : Weder der Dateiname noch der Code hat das Wort " Controller " überall. Hoffentlich ist es nicht verwirrend. Es ist ein "Controller" in dem Sinne, dass er steuert, was in der App passiert.
Für einen LiveView -Controller müssen die Funktionen mount/3 und render/1 definiert werden.
Um den Controller einfach zu halten, gibt das mount/3 nur das {:ok, socket} Tupel ohne Änderungen zurück. Der render/1 template LiveviewChatWeb.MessageView.render/2 (enthalten in Phoenix enthalten) auf, die die messages.html.heex rendert.
Erstellen Sie die lib/liveview_chat_web/views/message_view.ex -Datei:
defmodule LiveviewChatWeb.MessageView do
use LiveviewChatWeb , :view
end Dies ähnelt der regulären Phoenix view ; Nichts Besonderes/Interessantes hier.
Erstellen Sie lib/liveview_chat_web/templates/message Nächst
lib/liveview_chat_web/templates/message/messages.html.heex Datei und fügen Sie die folgende HTML -Zeile hinzu:
< h1 > LiveView Message Page </ h1 > Um das Stammlayout einfacher zu machen, öffnen Sie die lib/liveview_chat_web/templates/layout/root.html.heex -Datei und aktualisieren Sie den Inhalt des <body> bis:
< body >
< header >
< section class =" container " >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > router.ex Nachdem Sie die erforderlichen Dateien erstellt haben, öffnen Sie den Router lib/liveview_chat_web/router.ex Ersetzen Sie den Standard -Route PageController -Controller:
get "/" , PageController , :index mit MessageLive Controller:
scope "/" , LiveviewChatWeb do
pipe_through :browser
live "/" , MessageLive
endWenn Sie nun die Seite aktualisieren, sollten Sie Folgendes sehen:

Zu diesem Zeitpunkt haben wir einige Änderungen vorgenommen, die bedeuten, dass unsere automatisierte Testsuite nicht mehr passt. Führen Sie die Tests in Ihrer Befehlszeile mit dem folgenden Befehl aus:
mix testSie sehen die Ausgabe ähnlich wie folgt:
Generated liveview_chat app
..
1) test GET / (LiveviewChatWeb.PageControllerTest)
test/liveview_chat_web/controllers/page_controller_test.exs:4
Assertion with = ~ failed
code: assert html_response(conn, 200) = ~ " Welcome to Phoenix! "
left: " <!DOCTYPE html><html lang= " en " > <head> <meta charset= " utf-8 " > <meta http-equiv= " X-UA-Compatible " content= " IE=edge " >
<title data-suffix= " · Phoenix Framework " >LiveviewChat · Phoenix Framework</title> <link phx-track-static rel= " stylesheet " href= " /assets/app.css " > <script defer phx-track-static type= " text/javascript " src= " /assets/app.js " ></script> </head>
<body> <header> <section class= " container " >
<h1>LiveView Chat Example</h1></section> </header>
<h1>LiveView Message Page</h1></main></div> </body></html> "
right: " Welcome to Phoenix! "
stacktrace:
test/liveview_chat_web/controllers/page_controller_test.exs:6: (test)
Finished in 0.03 seconds (0.02s async, 0.01s sync)
3 tests, 1 failure Dies liegt daran, dass die page_controller_test.exs immer noch erwartet, dass die Homepage das "Welcome to Phoenix!" Text.
Lassen Sie uns die Tests aktualisieren! Erstellen Sie den Ordner test/liveview_chat_web/live und die Inneren von message_live_test.exs : test/liveview_chat_web/live/message_live_test.exs
Fügen Sie dem folgenden Testcode hinzu:
defmodule LiveviewChatWeb.MessageLiveTest do
use LiveviewChatWeb.ConnCase
import Phoenix.LiveViewTest
test "disconnected and connected mount" , % { conn: conn } do
conn = get ( conn , "/" )
assert html_response ( conn , 200 ) =~ "LiveView Message Page"
{ :ok , _view , _html } = live ( conn )
end
end Wir testen, dass der / Endpunkt zugänglich ist und auf der Seite die Text "LiveView Message Page" auf der Seite hat.
Weitere Informationen zu Tests und LiveView finden Sie im Modul LiveViewTest -Modul.
Schließlich können Sie alle mit dem PageController verknüpften Standard -Code löschen:
rm test/liveview_chat_web/controllers/page_controller_test.exsrm lib/liveview_chat_web/controllers/page_controller.exrm test/liveview_chat_web/views/page_view_test.exsrm lib/liveview_chat_web/views/page_view.exrm -r lib/liveview_chat_web/templates/page Sie können den Test jetzt erneut mit dem mix test ausführen. Sie sollten Folgendes sehen (Tests bestehen):
Generated liveview_chat app
...
Finished in 0.1 seconds (0.06s async, 0.1s sync)
3 tests, 0 failures
Randomized with seed 841084 Mit der definierten LiveView -Struktur können wir uns auf das Erstellen von Nachrichten konzentrieren. Die Datenbank speichert die Nachricht und den Namen des Absenders. Lassen Sie uns ein neues Schema und eine neue Migration erstellen:
mix phx.gen.schema Message messages name:string message:stringHINWEIS : Vergessen Sie nicht,
mix ecto.migrate, um die neuemessagesin der Datenbank zu erstellen.
Wir können jetzt das Message aktualisieren, um Funktionen zum Erstellen neuer Nachrichten und zum Auflisten der vorhandenen Nachrichten hinzuzufügen. Wir werden auch den Änderungssatz aktualisieren, um Anforderungen und Validierungen im Nachrichtentext hinzuzufügen. Öffnen Sie die Datei lib/liveview_chat/message.ex und aktualisieren Sie den Code mit den folgenden:
defmodule LiveviewChat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias LiveviewChat.Repo
alias __MODULE__
schema "messages" do
field :message , :string
field :name , :string
timestamps ( )
end
@ doc false
def changeset ( message , attrs ) do
message
|> cast ( attrs , [ :name , :message ] )
|> validate_required ( [ :name , :message ] )
|> validate_length ( :message , min: 2 )
end
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
end
def list_messages do
Message
|> limit ( 20 )
|> order_by ( desc: :inserted_at )
|> Repo . all ( )
end
end Wir haben die Funktion validate_length in der Nachrichteneingabe hinzugefügt, um sicherzustellen, dass Nachrichten über mindestens 2 Zeichen enthalten. Dies ist nur ein Beispiel, um anzuzeigen, wie die changeset -Validierung mit dem Formular auf der LiveView -Seite funktioniert.
Wir haben dann die Funktionen create_message/1 und list_messages/0 erstellt. Ähnlich wie bei Phoenix-Chat-Exampe limit wir die Anzahl der zurückgegebenen Nachrichten an die neuesten 20 .
mount/3 -Funktion Öffnen Sie die lib/liveview_chat_web/live/message_live.ex -Datei und fügen Sie die folgende Zeile in Zeile 3 hinzu:
alias LiveviewChat.Message Aktualisieren Sie als nächstes die Funktion mount/3 in der Datei lib/liveview_chat_web/live/message_live.ex um die Funktion list_messages zu verwenden:
def mount ( _params , _session , socket ) do
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , changeset: changeset , messages: messages ) }
end mount/3 erstellt nun die Liste der messages und erstellt einen changeset , der für das Nachrichtenformular verwendet wird. Wir weisen dann den changeset und die messages dem Socket zu, mit denen sie auf der LiveView -Seite angezeigt werden.
Aktualisieren Sie die messages.html.heex -Vorlage in den folgenden Code:
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = { "msg-#{message.id}"} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >
< %= text_input f, :name, id: "name", placeholder: "Your name", autofocus: "true" % >
< %= error_tag f, :name % >
< %= text_input f, :message, id: "msg", placeholder: "Your message" % >
< %= error_tag f, :message % >
< %= submit "Send"% >
</ .form > Es wird zuerst die neuen Nachrichten angezeigt und dann ein Formular für Personen bereitgestellt, um eine neue Nachricht zu create .
Wenn Sie die Seite aktualisieren, sollten Sie Folgendes sehen:

Die <.form></.form> Syntax ist, wie die Formularfunktionskomponente verwendet wird.
Eine Funktionskomponente ist jede Funktion, die eine MAP als Argument
assignsund eine mit dem~Hsigil erstellte gestaltetestructzurückgibt.
Schließlich stellen wir sicher, dass der Test immer noch bestehen, indem wir die assert im test/liveview_chat_web/live/message_live_test.exs -Datei zu aktualisieren::
assert html_response ( conn , 200 ) =~ "LiveView Chat" Da wir den Titel H1 LiveView Message Page gelöscht haben, können wir stattdessen den Titel im Stammlayout testen und sicherstellen, dass die Seite noch korrekt angezeigt wird.
Im Moment, wenn wir die Phoenix App ausführen, mix phx.server und senden das Formular im Browser nichts. Wenn wir uns das Serverprotokoll ansehen, sehen wir Folgendes:
** (UndefinedFunctionError) function LiveviewChatWeb.MessageLive.handle_event/3
is undefined or private
(liveview_chat 0.1.0) LiveviewChatWeb.MessageLive.handle_event("new_message",
%{"_csrf_token" => "fyVPIls_XRBuGwlkMhxsFAciRRkpAVUOLW5k4UoR7JF1uZ5z2Dundigv",
"message" => %{"message" => "", "name" => ""}}, #Phoenix.LiveView.Socket
Bei Senden erstellt das Formular ein neues Ereignis, das mit phx-submit definiert ist:
< . form let = { f } for = { @ changeset } id = "form" phx - submit = "new_message" >
Dieses Ereignis wird jedoch noch nicht auf dem Server verwaltet. Wir können dies beheben, indem wir die Funktion handle_event/3 in lib/liveview_chat_web/live/message_live.ex hinzufügen:
def handle_event ( "new_message" , % { "message" => params } , socket ) do
case Message . create_message ( params ) do
{ :error , changeset } ->
{ :noreply , assign ( socket , changeset: changeset ) }
{ :ok , _message } ->
changeset = Message . changeset ( % Message { } , % { "name" => params [ "name" ] } )
{ :noreply , assign ( socket , changeset: changeset ) }
end
end Die Funktion create_message wird mit den Werten aus dem Formular aufgerufen. Wenn beim Versuch, die Informationen in der Datenbank zu speichern, ein error auftritt, kann der changeset beispielsweise einen Fehler zurückgeben, wenn der Name oder die message leer ist oder wenn die message zu kurz ist, wird der changeset dem Socket erneut zugeordnet. Dadurch kann das Formular die error anzeigen:

Wenn die Nachricht ohne Fehler gespeichert wird, erstellen wir einen neuen Änderungssatz, der den Namen aus dem Formular enthält, um zu vermeiden, dass Personen den Namen erneut in das Formular eingeben müssen, und wir weisen dem Socket den neuen Änderungssatz zu.

Jetzt wird das Formular angezeigt. Wir können die folgenden Tests zum test/liveview_chat_web/live/message_live_test.exs hinzufügen:
import Plug.HTML , only: [ html_escape: 1 ]
test "name can't be blank" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "" , message: "hello" } )
|> render_submit ( ) =~ html_escape ( "can't be blank" )
end
test "message" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "" } )
|> render_submit ( ) =~ html_escape ( "can't be blank" )
end
test "minimum message length" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "h" } )
|> render_submit ( ) =~ "should be at least 2 character(s)"
end Wir verwenden das form/3 -Funktion, um das Formular auszuwählen und das Ereignis ein Sendenereignis mit unterschiedlichen Werten für den Namen und die Nachricht auszulösen. Wir testen, dass Fehler ordnungsgemäß angezeigt werden.
Anstatt die Seite neu zu laden, um die neu erstellten Nachrichten anzuzeigen, können wir PubSub ( Pub Lish Sub Scribe) verwenden, um alle verbundenen Clients darüber zu informieren, dass eine neue Nachricht erstellt wurde, und um die Benutzeroberfläche zu aktualisieren, um die neue Nachricht anzuzeigen.
Öffnen Sie die Datei lib/liveview_chat/message.ex und fügen Sie die folgende Zeile oben hinzu:
alias Phoenix.PubSubFügen Sie als nächstes die folgenden 3 Funktionen hinzu:
def subscribe ( ) do
PubSub . subscribe ( LiveviewChat.PubSub , "liveview_chat" )
end
def notify ( { :ok , message } , event ) do
PubSub . broadcast ( LiveviewChat.PubSub , "liveview_chat" , { event , message } )
end
def notify ( { :error , reason } , _event ) , do: { :error , reason } subscribe/0 wird aufgerufen, wenn ein Client die LiveView -Seite ordnungsgemäß angezeigt hat und neue Nachrichten anhört. Es ist nur eine Wrapper -Funktion für phoenix.pubsub.Subscribe.
notify/2 wird jedes Mal aufgerufen, wenn eine neue Nachricht erstellt wird, um die Nachricht an die angeschlossenen Clients zu senden. Repo.insert kann entweder {:ok, message} oder {:error, reason} zurückgeben, daher müssen wir notify/2 -Behandlungen definieren.
Aktualisieren Sie die Funktion create_message/1 in message.ex um unsere neu erstellte notify/2 -Funktion aufzurufen:
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
|> notify ( :message_created )
endmount/3 Wir können den Client jetzt anschließen, wenn die LiveView -Seite wiedergegeben wird. Fügen Sie lib/liveview_chat_web/live/message_live.ex folgende Zeile hinzu:
alias LiveviewChat.PubSub Aktualisieren Sie dann die mount/3 -Funktion mit:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) }
end mount/3 prüft nun die Socket ist angeschlossen und ruft die neue Message.subscribe/0 -Funktion.
handle_event/3 Da sich der Rückgabewert von create_message/1 geändert hat, müssen wir handle_event/3 auf Folgendes aktualisieren:
def handle_event ( "new_message" , % { "message" => params } , socket ) do
case Message . create_message ( params ) do
{ :error , changeset } ->
{ :noreply , assign ( socket , changeset: changeset ) }
:ok -> # broadcast returns :ok (just the atom!) if there are no errors
changeset = Message . changeset ( % Message { } , % { "name" => params [ "name" ] } )
{ :noreply , assign ( socket , changeset: changeset ) }
end
endhandle_info/2 Der letzte Schritt besteht darin, das Ereignis von :message_created zu verarbeiten, indem die Funktion handle_info/2 in lib/liveview_chat_web/live/message_live.ex definiert wird:
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ]
{ :noreply , assign ( socket , messages: messages ) }
endWenn das Ereignis empfangen wird, wird die neue Nachricht in die Liste der vorhandenen Nachrichten hinzugefügt. Die neue Liste wird dann dem Socket zugewiesen, mit dem die Benutzeroberfläche aktualisiert wird, um die neue Nachricht anzuzeigen.
Fügen Sie die folgenden Tests hinzu, um test/liveview_chat_web/live/message_live_test.exs um sicherzustellen, dass die Nachrichten auf der Seite korrekt angezeigt werden:
test "message form submitted correctly" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "hi" } )
|> render_submit ( )
assert render ( view ) =~ "<b>Simon:</b>"
assert render ( view ) =~ "hi"
end
test "handle_info/2" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert render ( view )
# send :created_message event when the message is created
Message . create_message ( % { "name" => "Simon" , "message" => "hello" } )
# test that the name and the message is displayed
assert render ( view ) =~ "<b>Simon:</b>"
assert render ( view ) =~ "hello"
end Sie sollten jetzt eine funktionale Chat -Anwendung mit LiveView haben! Führen Sie die Phoenix -App mit: aus:
mix phx.server Besuchen Sie die App localhost:4000 in 2 oder mehr Browsern und senden Sie sich einige Nachrichten!

Ein Problem, das wir bemerken können, ist, dass die Nachrichteneingabe nach dem Senden einer Nachricht mit der Enter im Eingabefeld nicht immer auf einen leeren Wert zurückgesetzt wird. Dies zwingt uns, die vorherige Nachricht manuell vor dem Schreiben und Senden eines neuen zu entfernen.
Der Grund ist:
Der JavaScript -Client ist immer die Quelle der Wahrheit für aktuelle Eingabewerte. Für eine bestimmte Eingabe mit Focus wird
LiveViewden aktuellen Wert der Eingabe niemals überschreiben, auch wenn er von den verwalteten Aktualisierungen des Servers abweicht. Siehe: https://hexdocs.pm/phoenix_live_view/form-bindings.html#javaScript-client-Specifics
Unsere Lösung besteht darin, phx-hook zu verwenden, um JavaScript auf dem Client nach einem der LiveView -Lebenszyklus-Rückrufe (montiert, vor Outdated, aktualisiert, zerstört, getrennt und wiederhergestellt) auszuführen.
Fügen wir einen Haken hinzu, um zu überwachen, wenn das Nachrichtenformular updated wird. In der message.html.heex -Datei fügen Sie das Attribut von phx-hook zum <.form> Element hinzu:
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" > Fügen Sie dann in der Datei assets/js/app.js die folgende JavaScript -Logik hinzu:
// get message input element
let msg = document . getElementById ( 'msg' ) ;
// define "Form" hook, the name must match the one
// defined with phx-hoo="Form"
let Hooks = { }
Hooks . Form = {
// Each time the form is updated run the code in the callback
updated ( ) {
// If no error displayed reset the message value
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
}
}
}
let csrfToken = document . querySelector ( "meta[name='csrf-token']" ) . getAttribute ( "content" )
let liveSocket = new LiveSocket ( "/live" , Socket , { params : { _csrf_token : csrfToken } , hooks : Hooks } ) // Add hooks: Hooks Die Hauptlogik zum Zurücksetzen des Nachrichtenwerts befindet sich in der updated() :
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
} Bevor wir den Wert auf eine leere Zeichenfolge festlegen, überprüfen wir zunächst, dass im Formular keine Fehler angezeigt werden, indem Sie nach der CSS-Klasse für invalid-feedback überprüfen. (Weitere Informationen zu Feedback:
Der letzte Schritt besteht darin, die hooks mit hooks: Hooks auf das liveSocket zu setzen. Die Nachrichteneingabe sollte jetzt zurückgesetzt werden, wenn eine neue Nachricht hinzugefügt wird!
Derzeit initialisiert die Funktion der mount/3 die Liste der Nachrichten zuerst, indem die neuesten 20 Nachrichten aus der Datenbank geladen werden:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( ) # get the list of messages
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) } ## assigns messages to socket
end Dann, wenn eine neue Nachricht erstellt wird, fügen Sie die Nachricht handle_info " an die Nachricht an die Liste der Nachrichten hinzu:
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ] # append new message to the existing list
{ :noreply , assign ( socket , messages: messages ) }
endDies kann zu Problemen führen, wenn die Liste der Nachrichten zu lang wird, wenn alle Nachrichten auf dem Server im Speicher gehalten werden.
Um die Verwendung des Speichers zu minimieren, können wir Nachrichten als vorübergehende assign definieren:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) ,
temporary_assigns: [ messages: [ ] ] }
endDie Liste der Nachrichten wird einmal abgerufen, dann wird sie auf eine leere Liste zurückgesetzt.
Jetzt muss der handle_info/2 nur die neue Nachricht dem Socket zuweisen:
def handle_info ( { :message_created , message } , socket ) do
{ :noreply , assign ( socket , messages: [ message ] ) }
end Schließlich hört die heex Nachrichtenvorlage auf Änderungen in der Liste der Nachrichten mit phx-update an und findet die neue Nachricht an die vorhandene angezeigte Liste an.
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = {message.id} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul > Siehe auch die Seite "Phoenix temporary-assigns Dokumentation": https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-signs
Derzeit bleibt das Feld der name der Person überlassen, manuell zu definieren, bevor sie eine Nachricht senden. Dies ist in einer grundlegenden Demo -App in Ordnung, aber wir wissen, dass wir es besser machen können. In diesem Abschnitt werden die Authentifizierung mit auth_plug hinzugefügt. Dadurch werden Personen, die die App verwenden, mit ihrem GitHub oder Google Konto authentifiziert und dann den name im Nachrichtenformular vorab füllen.
AUTH_API_KEYNach den Anweisungen erstellen Sie zunächst einen neuen API -Schlüssel unter https://authdemo.fly.dev/ EG:

Erstellen Sie dann eine .env -Datei und fügen Sie Ihren neu erstellten API -Schlüssel hinzu:
export AUTH_API_KEY = 88SwQGzaZoJYXs6ihvwMy2dRVtm6KVeg4tSCjRKtwDvMUYUbi/88SwQDatWtSTMd2rKPnaZsAWFNpbf4vv2ZK7JW2nwuSypMeg/authdemo.fly.devHinweis : Aus Sicherheitsgründen ist dies kein gültiger API -Schlüssel. Bitte erstellen Sie Ihre eigenen, es ist kostenlos und dauert weniger als eine Minute.
auth_plug installieren Fügen Sie das Paket auth_plug zu Ihren Abhängigkeiten hinzu. In mix.exs Datei aktualisieren Sie Ihre deps -Funktion und fügen Sie hinzu:
{ :auth_plug , "~> 1.4.10" } Diese Abhängigkeit erstellt neue Sitzungen für Sie und kommunizieren mit der Dwyl auth -Anwendung.
Vergessen Sie nicht:
source .envmix deps.get Stellen Sie sicher, dass der AUTH_API_KEY zugänglich ist, bevor die neue Abhängigkeit zusammengestellt wird.
Sie können die Abhängigkeiten mit mix deps.compile --force neu kompilieren.
Jetzt können wir die Authentifizierungsfunktion hinzufügen.
router.ex Um [nicht authentifizierte] "Gast" -Nutzer auf den Chat zugreifen zu können, verwenden wir den AuthPlugOptional -Stecker. Lesen Sie mehr unter optionalem Auth.
In der Datei router.ex erstellen wir eine neue Plug -Pipeline:
# define the new pipeline using auth_plug
pipeline :authOptional , do: plug ( AuthPlugOptional ) Aktualisieren Sie als nächstes den scope "/", LiveviewChatWeb do Folgendes:
scope "/" , LiveviewChatWeb do
pipe_through [ :browser , :authOptional ]
live "/" , MessageLive
get "/login" , AuthController , :login
get "/logout" , AuthController , :logout
endWir ermöglichen jetzt, dass die Authentifizierung für alle Routen im Router optional ist. Einfach, hey?
AuthController erstellen Erstellen Sie den AuthController mit login/2 und logout/2 -Funktionen.
Erstellen Sie eine neue Datei: lib/liveview_chat_web/controllers/auth_controller.ex und fügen Sie den folgenden Code hinzu:
defmodule LiveviewChatWeb.AuthController do
use LiveviewChatWeb , :controller
def login ( conn , _params ) do
redirect ( conn , external: AuthPlug . get_auth_url ( conn , "/" ) )
end
def logout ( conn , _params ) do
conn
|> AuthPlug . logout ( )
|> put_status ( 302 )
|> redirect ( to: "/" )
end
end Die login/2 -Funktion leitet in die Dwyl -Auth -App weiter. Lesen Sie mehr über die Verwendung der Funktion AuthPlug.get_auth_url/2 . Sobald der Benutzer authentifiziert wurde, wird der Benutzer in den / Endpunkt umgeleitet und eine jwt -Sitzung wird auf dem Client erstellt.
Die Funktion logout/2 ruft AuthPlug.logout/1 auf, wodurch die (JWT) Sitzung entfernt und auf die Homepage zurückgeleitet wird.
on_mount/4 Funktionen LiveView bietet den on_mount -Rückruf, mit dem wir Code vor dem mount ausführen können. Wir werden diesen Rückruf verwenden, um die jwt -Sitzung zu überprüfen und die Werte der person ( Map ) und loggedin ( boolean ) dem socket zuzuweisen.
In der Datei lib/liveview_chat_web/controllers/auth_controller.ex fügen Sie den folgenden Code hinzu, um zwei Versionen von mount/4 zu definieren:
# import the assign_new function from LiveView
import Phoenix.LiveView , only: [ assign_new: 3 ]
# pattern match on :default auth and check session has jwt
def on_mount ( :default , _params , % { "jwt" => jwt } = _session , socket ) do
# verify and retrieve jwt stored data
claims = AuthPlug.Token . verify_jwt! ( jwt )
# assigns the person and the loggedin values
socket =
socket
|> assign_new ( :person , fn ->
AuthPlug.Helpers . strip_struct_metadata ( claims )
end )
|> assign_new ( :loggedin , fn -> true end )
{ :cont , socket }
end
# when jwt is not defined just returns the current socket
def on_mount ( :default , _params , _session , socket ) do
socket = assign_new ( socket , :loggedin , fn -> false end )
{ :cont , socket }
endHEIBLEINE_NEW/3 ordnet der Socket einen Wert zu, wenn dies nicht vorhanden ist.
Sobald der on_mount/2 -Rückruf definiert ist, können wir ihn in unserer lib/liveview_chat_web/live/message_live.ex -Datei aufrufen:
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
alias LiveviewChat.Message
# run authentication on mount
on_mount LiveviewChatWeb.AuthController
Wir haben jetzt die gesamte Logik, um die Leute authentifizieren zu lassen. Wir müssen nur unsere Root -Layout -Datei lib/liveview_chat_web/templates/layout/root.html.heex aktualisieren, um einen login (oder logout ) anzuzeigen:
< body >
< header >
< section class =" container " >
< nav >
< ul >
< %= if @loggedin do % >
< li >
< img width =" 40px " src = {@person.picture}/ >
</ li >
< li > < %= link "logout", to: "/logout" % > </ li >
< % else % >
< li > < %= link "Login", to: "/login" % > </ li >
< % end % >
</ ul >
</ nav >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > Wenn die Person noch nicht loggedin ist, anzeigen wir einen login -Link anzeigen, da sonst der logout angezeigt wird.
Der letzte Schritt besteht darin, den Namen der angemeldeten Person im Feld des Namens des Nachrichtenformulars anzuzeigen. Dafür können wir das Formular ändern, um die mount festzulegen:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
# add name parameter if loggedin
changeset =
if socket . assigns . loggedin do
Message . changeset ( % Message { } , % { "name" => socket . assigns . person [ "givenName" ] } )
else
Message . changeset ( % Message { } , % { } )
end
messages = Message . list_messages ( ) |> Enum . reverse ( )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) ,
temporary_assigns: [ messages: [ ] ] }
endSie können jetzt die Anwendung ausführen und sich anmelden/abmelden können!

In diesem Abschnitt werden wir Phoenix -Präsenz verwenden, um eine Liste von Personen anzuzeigen, die derzeit die Anwendung verwenden.
Der erste Schritt besteht darin, die Datei lib/liveview_chat/presence.ex zu erstellen:
defmodule LiveviewChat.Presence do
use Phoenix.Presence ,
otp_app: :liveview_chat ,
pubsub_server: LiveviewChat.PubSub
end Dann fügen wir in lib/liveview_chat/application.ex das neu erstellte Presence in die Liste der Anwendungen hinzu, damit der Vorgesetzte startet:
def start ( _type , _args ) do
children = [
# Start the Ecto repository
LiveviewChat.Repo ,
# Start the Telemetry supervisor
LiveviewChatWeb.Telemetry ,
# Start the PubSub system
{ Phoenix.PubSub , name: LiveviewChat.PubSub } ,
# Presence
LiveviewChat.Presence ,
# Start the Endpoint (http/https)
LiveviewChatWeb.Endpoint
# Start a worker by calling: LiveviewChat.Worker.start_link(arg)
# {LiveviewChat.Worker, arg}
]
...
Wir sind jetzt bereit, die Präsenzfunktionen in unserem LiveView -Endpunkt zu verwenden.
Aktualisieren Sie in der Datei lib/liveview_chat_web/live/message_live.ex die mount -Funktion mit Folgendes:
@ presence_topic "liveview_chat_presence"
def mount ( _params , _session , socket ) do
if connected? ( socket ) do
Message . subscribe ( )
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
end
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic )
end
changeset =
if socket . assigns . loggedin do
Message . changeset ( % Message { } , % { "name" => socket . assigns . person [ "givenName" ] } )
else
Message . changeset ( % Message { } , % { } )
end
messages = Message . list_messages ( ) |> Enum . reverse ( )
{ :ok ,
assign ( socket ,
messages: messages ,
changeset: changeset ,
presence: get_presence_names ( )
) , temporary_assigns: [ messages: [ ] ] }
end Lassen Sie uns die Hauptänderungen an der mount/3 -Funktion zusammenfassen:
Zuerst erstellen wir das Modulattribut @presence_topic , um das topic zu definieren, das wir mit den Präsenzfunktionen verwenden werden.
Der folgende Teil des Codes definiert ein Tupel, das eine id der Person und ihres Namens enthält. Der Name nennt standardmäßig "Gast", wenn die Person nicht protokolliert ist.
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
endZweitens verwenden wir die Funktion der Track/4, um die Präsenz zu lassen, dass ein neuer Client die Anwendung untersucht:
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )Drittens verwenden wir PubSub, um die Veränderungen der Präsenz anzuhören (Personen, die sich anschließt oder die Anwendung verlassen):
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic ) Schließlich erstellen wir eine neue presence in der Socket:
presence : get_presence_names ( ) get_presence_names -Funktion gibt eine Liste von Protokolledin -Benutzern und falls die Anzahl der "Gast" -Nutzer zurück.
Fügen Sie den folgenden Code am Ende des MessageLive -Moduls hinzu:
defp get_presence_names ( ) do
Presence . list ( @ presence_topic )
|> Enum . map ( fn { _k , v } -> List . first ( v . metas ) . name end )
|> group_names ( )
end
# return list of names and number of guests
defp group_names ( names ) do
loggedin_names = Enum . filter ( names , fn name -> name != "guest" end )
guest_names =
Enum . count ( names , fn name -> name == "guest" end )
|> guest_names ( )
if guest_names do
[ guest_names | loggedin_names ]
else
loggedin_names
end
end
defp guest_names ( 0 ) , do: nil
defp guest_names ( 1 ) , do: "1 guest"
defp guest_names ( n ) , do: " #{ n } guests" Der wichtige Funktionsaufruf im obigen Code ist Presence.list(@presence_topic) . Die Liste/1 -Funktion gibt die Liste der Benutzer mithilfe der Anwendung zurück. Die group_names und guest_names sind nur hier, um die von list zurückgegebenen Präsenzdaten zu manipulieren. Siehe https://hexdocs.pm/phoenix/phoenix.presence.html#c:List/1-presence-data-strukturure
Bisher haben wir neue Personen mithilfe der Chat -Seite in der mount -Funktion verfolgt und PubSub verwendet, um Präsenzänderungen anzuhören. Der letzte Schritt besteht darin, diese Änderungen durch Hinzufügen einer handle_info -Funktion zu behandeln:
def handle_info ( % { event: "presence_diff" , payload: _diff } , socket ) do
{ :noreply , assign ( socket , presence: get_presence_names ( ) ) }
endSchließlich wird ein Unterschied zwischen Präsenz und Leave-Ereignissen an die Kunden gesendet, da sie in Echtzeit mit dem Ereignis "präsca_diff" stattfinden.
Die Funktion handle_info fängt das Ereignis für das presence_diff auf und gibt den presence mit dem Ergebnis des Funktion get_presence_names zu dem Socket zu.
Um die Namen anzuzeigen, fügen wir Folgendes in lib/liveview_chat_web/templates/message/messages.html.heex Vorlagendatei hinzu:
< b > People currently using the app: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >Sie sollten jetzt in der Lage sein, die Anwendung auszuführen und die Protokolledin -Benutzer und die Anzahl der Gastbenutzer anzusehen.
Wir können testen, dass die Vorlage ordnungsgemäß aktualisiert wurde, indem diese beiden Tests in test/liveview_chat_web/live/message_live_test.exs hinzugefügt wurden:
test "1 guest online" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert render ( view ) =~ "1 guest"
end
test "2 guests online" , % { conn: conn } do
{ :ok , _view , _html } = live ( conn , "/" )
{ :ok , view2 , _html } = live ( conn , "/" )
assert render ( view2 ) =~ "2 guests"
end Wenn Sie neu bei Tailwind sind
Ersetzen Sie den Inhalt von lib/liveview_chat_web/templates/layout/root.html.heex mit:
<!DOCTYPE html >
< html lang =" en " >
< head >
< meta charset =" utf-8 " />
< meta http-equiv =" X-UA-Compatible " content =" IE=edge " />
< meta name =" viewport " content =" width=device-width, initial-scale=1.0 " />
< meta name =" csrf-token " content = {csrf_token_value()} >
< %= live_title_tag assigns[:page_title] || "LiveviewChat", suffix: " · Phoenix Framework" % >
< script defer phx-track-static type =" text/javascript " src = {Routes.static_path(@conn, "/assets/app.js")} > </ script >
< script src =" https://cdn.tailwindcss.com " > </ script >
</ head >
< body >
< header class =" bg-slate-800 w-full min-h-[15%] pt-5 pb-1 mb-2 " >
< section >
< nav >
< div class =" text-white width-[10%] float-left ml-3 -mt-5 align-middle " >
< b > People in Chat: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >
</ div >
< ul class =" float-right mr-3 " >
< %= if @loggedin do % >
< li >
< img width =" 42px " src = {@person.picture} class =" -mt-3 " />
</ li >
< li class =" text-white " >
< %= link "logout", to: "/logout" % >
</ li >
< % else % >
< li class =" bg-green-600 text-white rounded-xl px-4 py-2 w-full mb-2 font-bold " >
< %= link "Login", to: "/login" % >
</ li >
< % end % >
</ ul >
</ nav >
< h1 class =" text-3xl mb-4 text-center font-mono text-white " > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body >
</ html > Ersetzen Sie dann den Inhalt von lib/liveview_chat_web/templates/message/messages.html.heex mit:
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = { "msg-#{message.id}"} class="px-5" >
< small class =" float-right text-xs align-middle " >
< %= message.inserted_at % >
</ small >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >
< footer class =" fixed bottom-0 w-full bg-slate-300 pb-2 px-5 pt-2 " >
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >
< %= if @loggedin do % >
< %= text_input f, :name, id: "name", value: @person.givenName,
class: "hidden" % >
< % else % >
< %= text_input f, :name, id: "name", placeholder: "Name", autofocus: "true",
class: "border p-2 w-9/12 mb-2 mt-2 mr2" % >
< span class =" italic text-2xl ml-4 " > or </ span >
< span class =" bg-green-600 text-white rounded-xl px-4 py-2 mb-2 mt-3 float-right " >
< %= link "Login", to: "/login" % >
</ span >
< %= error_tag f, :name % >
< % end % >
< %= text_input f, :message, id: "msg", placeholder: "Message",
class: "border p-2 w-10/12 mb-2 mt-2 float-left" % >
< p class =" text-amber-600 " >
< %= error_tag f, :message % >
</ p >
< %= submit "Send", class: "bg-sky-600 text-white rounded-xl px-4 py-2 mt-2 float-right" % >
</ .form >
</ footer >Sie sollten jetzt eine Benutzeroberfläche/ein Layout haben, das so aussieht:

Wenn Sie Fragen zu einem der verwendeten Tailwind haben, verbringen Sie bitte 2 Minuten mit Googeln und öffnen Sie dann, wenn Sie noch festsitzen, ein Problem.
Wenn Sie dieses Beispiel nützlich fanden, wissen Sie bitte das Github -Repository, damit wir ( und andere ) es Ihnen gefallen haben!
Hier sind einige andere Repositorys, die Sie vielleicht lesen möchten:
Irgendwelche Fragen oder Vorschläge? Zögern Sie nicht, neue Probleme zu eröffnen!
Danke schön!