LiveView 
Nous voulions vraiment un exemple réel gratuit et open source avec du code complet , des tests et de l'authentification.
Nous avons écrit ceci afin que nous puissions pointer des gens de notre équipe / communauté à apprendre Phoenix LiveView .
Cet exemple / tutoriel LiveView vous emmène de zéro à l'application entièrement fonctionnelle en 20 minutes .
Voici la table des matières de ce que vous pouvez vous attendre à couvrir dans cet exemple / tutoriel:
LiveViewPhoenixlive , un contrôleur et un modèle LiveViewrouter.exmount/3mount/3handle_event/3handle_info/2AUTH_API_KEYauth_plugrouter.exAuthControlleron_mount/4 Quiconque apprend Phoenix LiveView qui veut un tutoriel autonome, y compris: Setup , Testing , Authentication , Presence ,
Il est recommandé , mais non obligé , que vous suiviez le tutoriel sur le compteur LiveView car celui-ci est plus avancé. Au moins, consultez la liste des conditions préalables afin que vous sachiez ce que vous avez besoin d'avoir installé sur votre ordinateur avant de commencer cette aventure!
À condition que vous ayez installé Elixir , Phoenix et Postgres , vous êtes prêt à partir!
Phoenix Commencez par créer la nouvelle application liveview_chat Phoenix :
mix phx.new liveview_chat --no-mailer --no-dashboard Nous n'avons pas besoin de fonctionnalités email ou dashboard , nous les excluons donc de notre application. Vous pouvez en savoir plus sur la création de nouvelles applications Phoenix en fonctionnant: mix help phx.new
Exécutez mix deps.get pour récupérer les dépendances. Créez ensuite la base de données liveview_chat_dev Postgres en exécutant la commande:
mix ecto.setupVous devriez voir une sortie similaire à ce qui suit:
The database for LiveviewChat.Repo has been created
14:20:19.71 [info] Migrations already upUne fois que cette commande réussit, vous devriez maintenant pouvoir démarrer l'application en exécutant la commande:
mix phx.serverVous verrez la sortie du terminal similaire à ce qui suit:
[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... Lorsque vous ouvrez l'URL: http://localhost:4000 Dans votre navigateur Web, vous devriez voir quelque chose de similaire à:

live , un contrôleur et un modèle LiveView Créez le dossier lib/liveview_chat_web/live et le contrôleur de 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
endRemarque : Ni le nom de fichier ni le code n'ont le mot " contrôleur " nulle part. J'espère que ce n'est pas déroutant. C'est un "contrôleur" dans le sens où il contrôle ce qui se passe dans l'application.
Un contrôleur LiveView nécessite la définition des fonctions mount/3 et render/1 .
Pour garder le contrôleur simple, le mount/3 ne fait que renvoyer le tuple {:ok, socket} sans aucune modification. Le render/1 invoque LiveviewChatWeb.MessageView.render/2 (inclus avec Phoenix ) qui rend le template messages.html.heex que nous définirons ci-dessous.
Créez le fichier lib/liveview_chat_web/views/message_view.ex :
defmodule LiveviewChatWeb.MessageView do
use LiveviewChatWeb , :view
end Ceci est similaire à view Phoenix ordinaire; Rien de spécial / intéressant ici.
Ensuite, créez le répertoire lib/liveview_chat_web/templates/message , puis créez
lib/liveview_chat_web/templates/message/messages.html.heex Fichier et ajoutez la ligne suivante de HTML :
< h1 > LiveView Message Page </ h1 > Enfin, pour simplifier la disposition root , ouvrez le fichier lib/liveview_chat_web/templates/layout/root.html.heex et mettez à jour le contenu du <body> à:
< body >
< header >
< section class =" container " >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > router.ex Maintenant que vous avez créé les fichiers nécessaires, ouvrez le routeur lib/liveview_chat_web/router.ex Remplacez le contrôleur PageController de route par défaut:
get "/" , PageController , :index avec MessageLive Controller:
scope "/" , LiveviewChatWeb do
pipe_through :browser
live "/" , MessageLive
endMaintenant, si vous actualisez la page, vous devriez voir ce qui suit:

À ce stade, nous avons apporté quelques modifications qui signifient que notre suite de tests automatisé ne passera plus ... exécutez les tests dans votre ligne de commande avec la commande suivante:
mix testVous verrez une sortie similaire à ce qui suit:
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 En effet, la page_controller_test.exs s'attend toujours à ce que la page d'accueil contienne le "Welcome to Phoenix!" texte.
Mettons à jour les tests! Créez le dossier test/liveview_chat_web/live et le fichier message_live_test.exs à l'intérieur: test/liveview_chat_web/live/message_live_test.exs
Ajoutez le code de test suivant:
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 Nous testons que / point de terminaison est accessible et dispose du texte "LiveView Message Page" sur la page.
Voir également le module LiveViewTest pour plus d'informations sur les tests et LiveView.
Enfin, vous pouvez supprimer tout le code généré par défaut lié au PageController :
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 Vous pouvez maintenant exécuter à nouveau le test avec la commande mix test . Vous devriez voir ce qui suit (tests passant):
Generated liveview_chat app
...
Finished in 0.1 seconds (0.06s async, 0.1s sync)
3 tests, 0 failures
Randomized with seed 841084 Avec la structure LiveView définie, nous pouvons nous concentrer sur la création de messages. La base de données enregistrera le message et le nom de l'expéditeur. Créons un nouveau schéma et une nouvelle migration:
mix phx.gen.schema Message messages name:string message:stringRemarque : n'oubliez pas d'exécuter
mix ecto.migratepour créer le nouveau tableaumessagesdans la base de données.
Nous pouvons maintenant mettre à jour le schéma Message pour ajouter des fonctions pour créer de nouveaux messages et répertorier les messages existants. Nous mettrons également à jour l'ensemble de modifications pour ajouter des exigences et des validations sur le texte du message. Ouvrez le fichier lib/liveview_chat/message.ex et mettez à jour le code avec les suivants:
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 Nous avons ajouté la fonction validate_length sur l'entrée du message pour nous assurer que les messages ont au moins 2 caractères . Ceci est juste un exemple pour montrer comment la validation changeset fonctionne avec le formulaire sur la page LiveView .
Nous avons ensuite créé les fonctions create_message/1 et list_messages/0 . Semblable à l'exemple de phoenix-chat, nous limit le nombre de messages renvoyés aux 20 derniers .
mount/3 Ouvrez le fichier lib/liveview_chat_web/live/message_live.ex et ajoutez la ligne suivante à la ligne 3:
alias LiveviewChat.Message Mettez à jour la fonction mount/3 dans le fichier lib/liveview_chat_web/live/message_live.ex pour utiliser la fonction list_messages :
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 obtiendra désormais la liste des messages et créera un changeset qui sera utilisé pour le formulaire de message. Nous attribuons ensuite le changeset et les messages à la prise qui les affichera sur la page LiveView.
Mettez à jour le modèle messages.html.heex au code suivant:
< 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 > Il affiche d'abord les nouveaux messages, puis fournit un formulaire aux gens pour create un nouveau message.
Si vous actualisez la page, vous devriez voir ce qui suit:

La syntaxe <.form></.form> est comment utiliser le composant de fonction de formulaire.
Un composant de fonction est toute fonction qui reçoit une carte
assignscomme argument et renvoie unestructrendue construite avec le sceau~H.
Assurez-vous enfin que le test passe toujours en mettant à jour l' assert dans le fichier test/liveview_chat_web/live/message_live_test.exs vers:
assert html_response ( conn , 200 ) =~ "LiveView Chat" Comme nous avons supprimé le titre H1 LiveView Message Page , nous pouvons plutôt tester le titre dans la disposition racine et nous assurer que la page est toujours affichée correctement.
Pour le moment, si nous exécutons le Phoenix application mix phx.server et soumettons le formulaire dans le navigateur, rien ne se passera. Si nous regardons le journal du serveur, nous voyons ce qui suit:
** (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
Sur Soumettre, le formulaire est de créer un nouvel événement défini avec phx-submit :
< . form let = { f } for = { @ changeset } id = "form" phx - submit = "new_message" >
Cependant, cet événement n'est pas encore géré sur le serveur, nous pouvons résoudre ce problème en ajoutant la fonction handle_event/3 dans lib/liveview_chat_web/live/message_live.ex :
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 La fonction create_message est appelée avec les valeurs du formulaire. Si une error se produit en essayant d'enregistrer les informations dans la base de données, par exemple, l' changeset peut renvoyer une erreur si le nom ou le message est vide ou si le message est trop court, l' changeset est à nouveau attribué au socket. Cela permettra au formulaire d'afficher les informations error :

Si le message est enregistré sans aucune erreur, nous créons un nouvel ensemble de modifications qui contient le nom du formulaire pour éviter que les gens ne saisissent à nouveau leur nom dans le formulaire, et nous attribuons le nouveau jeu de change à la prise.

Maintenant, le formulaire s'affiche, nous pouvons ajouter les tests suivants pour test/liveview_chat_web/live/message_live_test.exs :
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 Nous utilisons la fonction form/3 pour sélectionner le formulaire et déclencher l'événement Soumettre avec différentes valeurs pour le nom et le message. Nous testons que les erreurs sont correctement affichées.
Au lieu d'avoir à recharger la page pour voir les messages nouvellement créés, nous pouvons utiliser PubSub ( Pub Lish Sub Scribe) pour informer tous les clients connectés qu'un nouveau message a été créé et pour mettre à jour l'interface utilisateur pour afficher le nouveau message.
Ouvrez le fichier lib/liveview_chat/message.ex et ajoutez la ligne suivante près du haut:
alias Phoenix.PubSubAjoutez ensuite les 3 fonctions suivantes:
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 sera appelé lorsqu'un client a correctement affiché la page LiveView et écouter de nouveaux messages. C'est juste une fonction de wrapper pour phoenix.pubsub.subscribe.
notify/2 est invoqué chaque fois qu'un nouveau message est créé pour diffuser le message aux clients connectés. Repo.insert peut soit renvoyer {:ok, message} ou {:error, reason} , nous devons donc définir notify/2 gérer les deux cas.
Mettez à jour la fonction create_message/1 dans message.ex pour invoquer notre fonction notify/2 nouvellement créée:
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
|> notify ( :message_created )
endmount/3 Nous pouvons désormais connecter le client lorsque la page LiveView est rendue. En haut du fichier lib/liveview_chat_web/live/message_live.ex , ajoutez la ligne suivante:
alias LiveviewChat.PubSub Ensuite, mettez à jour la fonction mount/3 avec:
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 vérifie désormais que le socket est connecté, puis appelle la nouvelle fonction Message.subscribe/0 .
handle_event/3 Étant donné que la valeur de retour de create_message/1 a changé, nous devons mettre à jour handle_event/3 à ce qui suit:
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 La dernière étape consiste à gérer l'événement :message_created en définissant la fonction handle_info/2 dans lib/liveview_chat_web/live/message_live.ex :
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ]
{ :noreply , assign ( socket , messages: messages ) }
endLorsque l'événement est reçu, le nouveau message est ajouté à la liste des messages existants. La nouvelle liste est ensuite attribuée à la prise qui mettra à jour l'interface utilisateur pour afficher le nouveau message.
Ajoutez les tests suivants pour test/liveview_chat_web/live/message_live_test.exs pour s'assurer que les messages sont correctement affichés sur la page:
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 Vous devriez maintenant avoir une application de chat fonctionnelle à l'aide de LiveView! Exécutez l'application Phoenix avec:
mix phx.server Visitez l'application localhost:4000 en 2 navigateurs ou plus et envoyez-vous des messages!

Un problème que nous pouvons remarquer est que l'entrée de message ne réinitialise pas toujours une valeur vide après avoir envoyé un message à l'aide de la touche Enter sur le champ de saisie. Cela nous oblige à supprimer manuellement le message précédent avant d'écrire et d'en envoyer un nouveau.
La raison en est:
Le client JavaScript est toujours la source de la vérité pour les valeurs d'entrée actuelles. Pour toute entrée donnée avec Focus ,
LiveViewne remplacera jamais la valeur actuelle de l'entrée, même si elle s'écarte des mises à jour rendues du serveur. Voir: https://hexdocs.pm/phoenix_live_view/form-bindings.html#javascript-lient-spécifiques
Notre solution consiste à utiliser phx-hook pour exécuter un javascript sur le client après l'un des rappels du cycle de vie LiveView (monté, avant updaté, mis à jour, détruit, déconnecté, reconnecté).
Ajoutons un crochet pour surveiller lorsque le formulaire de message est updated . Dans le fichier message.html.heex ajoutez l'attribut phx-hook à l'élément <.form> :
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" > Ensuite, dans le fichier assets/js/app.js , ajoutez la logique JavaScript suivante:
// 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 La logique principale pour réinitialiser la valeur du message est contenue dans la fonction de rappel updated() :
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
} Avant de définir la valeur sur une chaîne vide, nous vérifions d'abord qu'aucune erreur n'est affichée sur le formulaire en vérifiant la classe CSS invalid-feedback . (En savoir plus sur les commentaires: https://hexdocs.pm/phoenix_live_view/form-bindings.html#phx-feedback-for)
La dernière étape consiste à régler les hooks sur le liveSocket avec hooks: Hooks . L'entrée de message doit désormais être réinitialisée lorsqu'un nouveau message est ajouté!
À l'heure actuelle, la fonction mount/3 initialise d'abord la liste des messages en chargeant les 20 derniers messages de la base de données:
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 Ensuite, chaque fois qu'un nouveau message est créé, la fonction handle_info ajoute le message à la liste des messages:
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 ) }
endCela peut entraîner des problèmes si la liste des messages devient trop longue car tous les messages sont conservés en mémoire sur le serveur.
Pour minimiser l'utilisation de la mémoire, nous pouvons définir les messages comme une assign temporaire:
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: [ ] ] }
endLa liste des messages est récupérée une fois, puis elle est réinitialisée à une liste vide.
Maintenant, le handle_info/2 doit seulement attribuer le nouveau message à la prise:
def handle_info ( { :message_created , message } , socket ) do
{ :noreply , assign ( socket , messages: [ message ] ) }
end Enfin, le modèle de messages heex écoute toute modification de la liste des messages avec phx-update et ajoute le nouveau message à la liste affichée existante.
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = {message.id} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul > Voir également la page de documentation Phoenix temporary-assigns : https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-assignens
Actuellement, le champ name est laissé à la personne pour définir manuellement avant d'envoyer un message. C'est bien dans une application de démonstration de base, mais nous savons que nous pouvons faire mieux. Dans cette section, nous ajouterons l'authentification à l'aide auth_plug . Cela permettra aux personnes utilisant l'application de s'authentifier avec leur compte GitHub ou Google , puis de remplir le name dans le formulaire de message.
AUTH_API_KEYSelon les instructions, créez d'abord une nouvelle clé API à https://authdemo.fly.dev/ par exemple:

Créez ensuite un fichier .env et ajoutez votre nouvelle clé API créée:
export AUTH_API_KEY = 88SwQGzaZoJYXs6ihvwMy2dRVtm6KVeg4tSCjRKtwDvMUYUbi/88SwQDatWtSTMd2rKPnaZsAWFNpbf4vv2ZK7JW2nwuSypMeg/authdemo.fly.devRemarque : Pour des raisons de sécurité, il ne s'agit pas d'une clé API valide. Veuillez créer le vôtre, il est gratuit et prend moins d'une minute.
auth_plug Ajoutez le package AUTH_PLUG à vos dépendances. Dans le fichier mix.exs mettez à jour votre fonction deps et ajoutez:
{ :auth_plug , "~> 1.4.10" } Cette dépendance créera de nouvelles sessions pour vous et communiquera avec l'application Dwyl auth .
N'oubliez pas:
source .envmix deps.get Assurez-vous que l' AUTH_API_KEY est accessible avant la compilation de la nouvelle dépendance.
Vous pouvez recompiler les dépendances avec mix deps.compile --force .
Nous pouvons maintenant commencer à ajouter la fonction d'authentification.
router.ex Pour permettre aux utilisateurs «invités» [non authentifiés] d'accéder au chat, nous utilisons la prise AuthPlugOptional . En savoir plus sur l'authentification facultative.
Dans le fichier router.ex , nous créons un nouveau pipeline Plug :
# define the new pipeline using auth_plug
pipeline :authOptional , do: plug ( AuthPlugOptional ) Mettez à jour la scope "/", LiveviewChatWeb do les éléments suivants:
scope "/" , LiveviewChatWeb do
pipe_through [ :browser , :authOptional ]
live "/" , MessageLive
get "/login" , AuthController , :login
get "/logout" , AuthController , :logout
endNous permettons maintenant à l'authentification d'être facultative pour tous les itinéraires du routeur. Facile, hé?
AuthController Créez le AuthController avec les fonctions login/2 et logout/2 .
Créez un nouveau fichier: lib/liveview_chat_web/controllers/auth_controller.ex et ajoutez le code suivant:
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 La fonction login/2 redirige vers l'application Dwyl Auth. En savoir plus sur la façon d'utiliser la fonction AuthPlug.get_auth_url/2 . Une fois authentifié, l'utilisateur sera redirigé vers le / de terminaison et une session jwt est créée sur le client.
La fonction logout/2 invoque AuthPlug.logout/1 qui supprime la session (JWT) et redirige vers la page d'accueil.
on_mount/4 LiveView fournit le rappel on_mount qui nous permet d'exécuter du code avant le mount . Nous utiliserons ce rappel pour vérifier la session jwt et attribuer les valeurs de la person ( Map ) et loggedin ( boolean ) à la socket .
Dans le fichier lib/liveview_chat_web/controllers/auth_controller.ex ajoutez le code suivant pour définir deux versions de mount/4 :
# 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 }
endASTH_NEW / 3 attribue une valeur à la prise en cas de naissance.
Une fois le rappel on_mount/2 défini, nous pouvons l'appeler dans notre fichier lib/liveview_chat_web/live/message_live.ex :
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
alias LiveviewChat.Message
# run authentication on mount
on_mount LiveviewChatWeb.AuthController
Nous avons maintenant toute la logique pour permettre aux gens de s'authentifier, nous avons juste besoin de mettre à jour notre fichier de mise en page racine lib/liveview_chat_web/templates/layout/root.html.heex pour afficher un lien login (ou logout ):
< 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 > Si la personne n'est pas encore loggedin nous affichons un lien login sinon le lien de logout s'affiche.
La dernière étape consiste à afficher le nom de la personne connectée dans le champ Nom du formulaire de message. Pour cela, nous pouvons mettre à jour le formulaire de forme de formulaire dans la fonction mount pour définir les paramètres du nom:
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: [ ] ] }
endVous pouvez maintenant exécuter l'application et être en mesure de vous connecter / déconnecter!

Dans cette section, nous utiliserons la présence de Phoenix pour afficher une liste de personnes qui utilisent actuellement l'application.
La première étape consiste à créer le fichier lib/liveview_chat/presence.ex :
defmodule LiveviewChat.Presence do
use Phoenix.Presence ,
otp_app: :liveview_chat ,
pubsub_server: LiveviewChat.PubSub
end Ensuite, dans lib/liveview_chat/application.ex nous ajoutons le module Presence nouvellement créé à la liste des applications pour que le superviseur commence:
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}
]
...
Nous sommes maintenant prêts à utiliser les fonctionnalités de présence dans notre point de terminaison LiveView.
Dans le fichier lib/liveview_chat_web/live/message_live.ex , mettez à jour la fonction mount avec les suivantes:
@ 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 Récapitulons les principales modifications de la fonction mount/3 :
Nous créons d'abord l'attribut de module @presence_topic pour définir le topic nous utiliserons avec les fonctions de présence.
La partie suivante du code définit un tuple contenant un id de la personne et de son nom. Le nom "invité" si la personne n'est pas logique.
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
endDeuxièmement, nous utilisons la fonction Track / 4 pour permettre à la présence de savoir qu'un nouveau client examine l'application:
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )Troisièmement, nous utilisons PubSub pour écouter les changements de présence (personne rejoint ou quittant l'application):
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic ) Enfin, nous créons une nouvelle affectation presence dans la prise:
presence : get_presence_names ( ) La fonction get_presence_names renverra une liste d'utilisateurs de LoggedIn et le cas échéant des utilisateurs "invités".
Ajoutez le code suivant à la fin du module MessageLive :
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" L'appel de fonction important dans le code ci-dessus est Presence.list(@presence_topic) . La fonction List / 1 renvoie la liste des utilisateurs à l'aide de l'application. La fonction group_names et guest_names sont juste là pour manipuler les données de présence renvoyées par list , voir https://hexdocs.pm/phoenix/phoenix.presence.html#c:list/1-presence-data-structure
Jusqu'à présent, nous avons suivi de nouvelles personnes en utilisant la page de chat dans la fonction mount et nous avons utilisé PubSub pour écouter les changements de présence. La dernière étape consiste à gérer ces modifications en ajoutant une fonction handle_info :
def handle_info ( % { event: "presence_diff" , payload: _diff } , socket ) do
{ :noreply , assign ( socket , presence: get_presence_names ( ) ) }
endEnfin, une difficulté de présence et des événements qui laissent des événements seront envoyés aux clients au fur et à mesure qu'ils se produisent en temps réel avec l'événement "Présence_diff".
La fonction handle_info attrape l'événement presence_diff et réaffecte à la valeur de la prise la valeur presence avec le résultat de l'appel de fonction get_presence_names .
Pour afficher les noms, nous ajoutons ce qui suit dans le fichier de modèle lib/liveview_chat_web/templates/message/messages.html.heex :
< b > People currently using the app: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >Vous devriez maintenant pouvoir exécuter l'application et voir les utilisateurs de LoggedIn et le nombre d'utilisateurs invités.
Nous pouvons tester que le modèle a été correctement mis à jour en ajoutant ces deux tests dans test/liveview_chat_web/live/message_live_test.exs :
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 Si vous êtes nouveau dans Tailwind , veuillez consulter: https://github.com/dwyl/learn-tailwind
Remplacez le contenu de lib/liveview_chat_web/templates/layout/root.html.heex avec:
<!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 > Puis remplacez le contenu de lib/liveview_chat_web/templates/message/messages.html.heex avec:
< 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 >Vous devriez maintenant avoir une interface utilisateur / mise en page qui ressemble à ceci:

Si vous avez des questions sur l'une des cours Tailwind utilisé, veuillez passer 2 minutes sur Google, puis si vous êtes toujours coincé, ouvrez un problème.
Si vous avez trouvé cet exemple utile, veuillez ️ le référentiel GitHub afin que nous ( et autres ) sachez que vous l'avez aimé!
Voici quelques autres référentiels que vous voudrez peut-être lire:
Des questions ou des suggestions? N'hésitez pas à ouvrir de nouveaux problèmes!
Merci!