LiveView 
Nós realmente queríamos um exemplo de mundo real gratuito e de código aberto com código completo , testes e autenticação.
Escrevemos isso para que pudéssemos apontar as pessoas em nossa equipe/comunidade aprendendo Phoenix LiveView .
Este exemplo/tutorial LiveView leva você de zero para um aplicativo totalmente funcional em 20 minutos .
Aqui está o índice do que você pode esperar cobrir neste exemplo/tutorial:
LiveViewPhoenixlive , controlador e modelo LiveViewrouter.exmount/3mount/3handle_event/3handle_info/2AUTH_API_KEYauth_plugrouter.exAuthControlleron_mount/4 Qualquer pessoa que aprenda Phoenix LiveView querendo um tutorial independente, incluindo: Setup , Testing , Authentication , Presence ,
É recomendável , embora não seja necessário , que você siga o tutorial do LiveView Counter, pois este é mais avançado. Pelo menos, consulte a lista de pré -requisitos para que você saiba o que precisa instalar no seu computador antes de iniciar esta aventura!
Desde que você tenha Elixir , Phoenix e Postgres instalados, você está pronto para ir!
Phoenix Comece criando o novo aplicativo liveview_chat Phoenix :
mix phx.new liveview_chat --no-mailer --no-dashboard Não precisamos de recursos email ou dashboard , por isso estamos excluindo -os do nosso aplicativo. Você pode aprender mais sobre como criar novos aplicativos Phoenix executando: mix help phx.new
Run mix deps.get para recuperar as dependências. Em seguida, crie o banco de dados liveview_chat_dev PostGres executando o comando:
mix ecto.setupVocê deve ver a saída semelhante ao seguinte:
The database for LiveviewChat.Repo has been created
14:20:19.71 [info] Migrations already upUma vez que esse comando for bem -sucedido, você poderá iniciar o aplicativo executando o comando:
mix phx.serverVocê verá a saída do terminal semelhante ao seguinte:
[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... Quando você abre o URL: http://localhost:4000 no navegador da web, você deve ver algo semelhante a:

live , controlador e modelo LiveView Crie a pasta lib/liveview_chat_web/live e o controlador em 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
endNota : Nem o nome do arquivo nem o código têm a palavra " controlador " em qualquer lugar. Espero que não seja confuso. É um "controlador" no sentido de controlar o que acontece no aplicativo.
Um controlador LiveView requer que as funções mount/3 e render/1 sejam definidas.
Para manter o controlador simples, o mount/3 está apenas retornando o {:ok, socket} tupla sem nenhuma alteração. O render/1 invoca LiveviewChatWeb.MessageView.render/2 (incluído no Phoenix ), o que renderiza o template messages.html.heex que definiremos abaixo.
Crie o arquivo lib/liveview_chat_web/views/message_view.ex :
defmodule LiveviewChatWeb.MessageView do
use LiveviewChatWeb , :view
end Isso é semelhante à view regular Phoenix ; Nada especial/interessante aqui.
Em seguida, crie o diretório lib/liveview_chat_web/templates/message
lib/liveview_chat_web/templates/message/messages.html.heex File e adicione a seguinte linha de HTML :
< h1 > LiveView Message Page </ h1 > Finalmente, para simplificar o layout da raiz , abra o arquivo lib/liveview_chat_web/templates/layout/root.html.heex e atualize o conteúdo do <body> para:
< body >
< header >
< section class =" container " >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > router.ex Agora que você criou os arquivos necessários, abra o roteador lib/liveview_chat_web/router.ex Substitua o controlador PageController de rota padrão:
get "/" , PageController , :index Com o MessageLive Controller:
scope "/" , LiveviewChatWeb do
pipe_through :browser
live "/" , MessageLive
endAgora, se você atualizar a página, você deve ver o seguinte:

Nesse ponto, fizemos algumas alterações que significam que nossa suíte de teste automatizada não passará mais ... Execute os testes em sua linha de comando com o seguinte comando:
mix testVocê verá a produção semelhante ao seguinte:
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 Isso ocorre porque o page_controller_test.exs ainda espera que a página inicial contenha o "Welcome to Phoenix!" texto.
Vamos atualizar os testes! Crie a pasta test/liveview_chat_web/live e o arquivo message_live_test.exs dentro dele: test/liveview_chat_web/live/message_live_test.exs
Adicione o seguinte código de teste:
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 Estamos testando que o endpoint / é acessível e tem o texto "LiveView Message Page" na página.
Veja também o módulo LiveViewTest para obter mais informações sobre testes e LiveView.
Finalmente, você pode excluir todo o código gerado padrão vinculado ao 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 Agora você pode executar o teste novamente com o comando mix test . Você deve ver o seguinte (testes passando):
Generated liveview_chat app
...
Finished in 0.1 seconds (0.06s async, 0.1s sync)
3 tests, 0 failures
Randomized with seed 841084 Com a estrutura LiveView definida, podemos nos concentrar na criação de mensagens. O banco de dados salvará a mensagem e o nome do remetente. Vamos criar um novo esquema e migração:
mix phx.gen.schema Message messages name:string message:stringNOTA : Não se esqueça de executar
mix ecto.migratepara criar a tabela de novasmessagesno banco de dados.
Agora podemos atualizar o esquema Message para adicionar funções para criar novas mensagens e listar as mensagens existentes. Também atualizaremos o alteração para adicionar requisitos e validações no texto da mensagem. Abra o arquivo lib/liveview_chat/message.ex e atualize o código com o seguinte:
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 Adicionamos a função validate_length na entrada da mensagem para garantir que as mensagens tenham pelo menos 2 caracteres . Este é apenas um exemplo para mostrar como a validação changeset funciona com o formulário na página LiveView .
Em seguida, criamos as funções create_message/1 e list_messages/0 . Semelhante ao Exemplo de Phoenix-Chat, limit o número de mensagens retornadas aos 20 mais recentes .
mount/3 Abra o arquivo lib/liveview_chat_web/live/message_live.ex e adicione a seguinte linha na linha 3:
alias LiveviewChat.Message Em seguida, atualize a função mount/3 no arquivo lib/liveview_chat_web/live/message_live.ex para usar a função 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 agora receberá a lista de messages e criará um changeset que será usado para o formulário de mensagem. Em seguida, atribuímos o changeset e as messages ao soquete que as exibirá na página LiveView.
Atualize o modelo messages.html.heex para o seguinte código:
< 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 > Primeiro, ele exibe as novas mensagens e depois fornece um formulário para as pessoas create uma nova mensagem.
Se você atualizar a página, verá o seguinte:

A sintaxe <.form></.form>
Um componente de função é qualquer função que receba um mapa
assignscomo argumento e retorna umastructrenderizada construída com o~H.
Finalmente, vamos garantir que o teste ainda esteja passando, atualizando a assert no arquivo test/liveview_chat_web/live/message_live_test.exs para:
assert html_response ( conn , 200 ) =~ "LiveView Chat" Como excluímos o título LiveView Message Page H1, podemos testar o título no layout da raiz e garantir que a página ainda seja exibida corretamente.
No momento, se executarmos o aplicativo Phoenix mix phx.server e enviar o formulário no navegador, nada acontecerá. Se olharmos para o log do servidor, vemos o seguinte:
** (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
No envio, o formulário está criando um novo evento definido com phx-submit :
< . form let = { f } for = { @ changeset } id = "form" phx - submit = "new_message" >
No entanto, este evento ainda não foi gerenciado no servidor, podemos corrigir isso adicionando a função handle_event/3 em 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 A função create_message é chamada com os valores do formulário. Se ocorrer um error ao tentar salvar as informações no banco de dados, por exemplo, o changeset pode retornar um erro se o nome ou a message estiver vazia ou se a message for muito curta, o changeset será atribuído novamente ao soquete. Isso permitirá que o formulário exiba as informações error :

Se a mensagem for salva sem erros, estamos criando uma nova troca que contém o nome do formulário para evitar que as pessoas tenham que inserir seu nome novamente no formulário e atribuímos o novo alterações ao soquete.

Agora, o formulário é exibido, podemos adicionar os seguintes testes para 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 Estamos usando a função form/3 para selecionar o formulário e acionar o evento de envio com valores diferentes para o nome e a mensagem. Estamos testando que os erros são exibidos corretamente.
Em vez de precisar recarregar a página para ver as mensagens recém -criadas, podemos usar o PubSub ( Pub Lish Sub Scribe) para informar todos os clientes conectados que uma nova mensagem foi criada e atualizar a interface do usuário para exibir a nova mensagem.
Abra o arquivo lib/liveview_chat/message.ex e adicione a seguinte linha perto da parte superior:
alias Phoenix.PubSubEm seguida, adicione as 3 funções a seguir:
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 será chamado quando um cliente tiver exibido corretamente a página do LiveView e ouça novas mensagens. É apenas uma função de wrapper para phoenix.pubsub.subscribe.
notify/2 é chamado sempre que uma nova mensagem é criada para transmitir a mensagem para os clientes conectados. Repo.insert pode retornar {:ok, message} ou {:error, reason} ; portanto, precisamos definir notify/2 lide com os dois casos.
Atualize a função create_message/1 no message.ex para invocar nossa função notify/2 recém -criada:
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
|> notify ( :message_created )
endmount/3 Agora podemos conectar o cliente quando a página LiveView for renderizada. Na parte superior do arquivo lib/liveview_chat_web/live/message_live.ex , adicione a seguinte linha:
alias LiveviewChat.PubSub Em seguida, atualize a função de mount/3 com:
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 agora verifica o soquete conectado e chama a nova Message.subscribe/0 função.
handle_event/3 Como o valor de retorno de create_message/1 mudou, precisamos atualizar handle_event/3 para o seguinte:
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 A última etapa é lidar com o evento :message_created definindo a função handle_info/2 em 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 ) }
endQuando o evento é recebido, a nova mensagem é adicionada à lista de mensagens existentes. A nova lista é atribuída ao soquete que atualizará a interface do usuário para exibir a nova mensagem.
Adicione os seguintes testes para test/liveview_chat_web/live/message_live_test.exs para garantir que as mensagens sejam exibidas corretamente na página:
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 Agora você deve ter um aplicativo de bate -papo funcional usando o LiveView! Execute o aplicativo Phoenix com:
mix phx.server Visite o App localhost:4000 em 2 ou mais navegadores e envie algumas mensagens!

Uma questão que podemos notar é que a entrada da mensagem nem sempre é redefinida para um valor vazio após o envio de uma mensagem usando a tecla Enter no campo de entrada. Isso nos obriga a remover a mensagem anterior manualmente antes de escrever e enviar uma nova.
A razão é:
O cliente JavaScript é sempre a fonte da verdade para os valores atuais de entrada. Para qualquer entrada com foco ,
LiveViewnunca substituirá o valor atual da entrada, mesmo que se desvie das atualizações renderizadas do servidor. Veja: https://hexdocs.pm/phoenix_live_view/form-bindings.html#javascript-lient specifics
Nossa solução é usar phx-hook para executar algum JavaScript no cliente após um dos retornos de chamada do ciclo de vida LiveView (montados, antes atualizados, atualizados, destruídos, desconectados, reconectados).
Vamos adicionar um gancho para monitorar quando o formulário de mensagem for updated . No arquivo message.html.heex adicione o atributo phx-hook ao elemento <.form> :
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" > Então, no arquivo assets/js/app.js , adicione a seguinte lógica JavaScript :
// 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 A lógica principal para redefinir o valor da mensagem está contida dentro da função de retorno de chamada updated() :
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
} Antes de definir o valor em uma string vazia, verificamos primeiro que nenhum erro é exibido no formulário, verificando a classe CSS invalid-feedback . (Leia mais sobre feedback: https://hexdocs.pm/phoenix_live_view/form-bindings.html#phx-feedback-for)
A etapa final é definir os hooks no liveSocket com hooks: Hooks . A entrada da mensagem agora deve ser redefinida quando uma nova mensagem for adicionada!
No momento, a função mount/3 inicializa primeiro a lista de mensagens carregando as 20 mais recentes mensagens do banco de dados:
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 Cada vez que uma nova mensagem é criada, a função handle_info anexa a mensagem à lista de mensagens:
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 ) }
endIsso pode causar problemas se a lista de mensagens se tornar muito longa quando todas as mensagens forem mantidas na memória no servidor.
Para minimizar o uso da memória, podemos definir mensagens como um assign temporário:
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: [ ] ] }
endA lista de mensagens é recuperada uma vez e é redefinida para uma lista vazia.
Agora o handle_info/2 só precisa atribuir a nova mensagem ao soquete:
def handle_info ( { :message_created , message } , socket ) do
{ :noreply , assign ( socket , messages: [ message ] ) }
end Finalmente, o modelo de mensagens heex escuta quaisquer alterações na lista de mensagens com phx-update e anexa a nova mensagem à lista exibida existente.
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = {message.id} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul > Veja também a página de documentação de Phoenix temporary-assigns : https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-assignns
Atualmente, o campo name é deixado para a pessoa para definir manualmente antes de enviar uma mensagem. Isso é bom em um aplicativo de demonstração básico, mas sabemos que podemos fazer melhor. Nesta seção, adicionaremos autenticação usando auth_plug . Isso permitirá que as pessoas que usem o aplicativo se autentiquem com o GitHub ou a conta Google e, em seguida, preencha o name no formulário de mensagem.
AUTH_API_KEYDe acordo com as instruções, crie uma nova chave da API em https://authdemo.fly.dev/, por exemplo:

Em seguida, crie um arquivo .env e adicione sua nova chave de API criada:
export AUTH_API_KEY = 88SwQGzaZoJYXs6ihvwMy2dRVtm6KVeg4tSCjRKtwDvMUYUbi/88SwQDatWtSTMd2rKPnaZsAWFNpbf4vv2ZK7JW2nwuSypMeg/authdemo.fly.devNota : Por motivos de segurança, essa não é uma chave de API válida. Crie o seu, é gratuito e leva menos de um minuto.
auth_plug Adicione o pacote auth_plug às suas dependências. No arquivo mix.exs Atualize sua função deps e adicione:
{ :auth_plug , "~> 1.4.10" } Essa dependência criará novas sessões para você e se comunicará com o aplicativo Dwyl auth .
Não se esqueça de:
source .envmix deps.get Verifique se o AUTH_API_KEY está acessível antes que a nova dependência seja compilada.
Você pode recompilar as dependências com mix deps.compile --force .
Agora podemos começar a adicionar o recurso de autenticação.
router.ex Para permitir que os usuários "não autenticados]" convidados "acessem o bate -papo, usamos o plugue AuthPlugOptional . Leia mais com autenticação opcional.
No arquivo router.ex , criamos um novo pipeline Plug :
# define the new pipeline using auth_plug
pipeline :authOptional , do: plug ( AuthPlugOptional ) Em seguida, atualize o scope "/", LiveviewChatWeb do bloqueio para o seguinte:
scope "/" , LiveviewChatWeb do
pipe_through [ :browser , :authOptional ]
live "/" , MessageLive
get "/login" , AuthController , :login
get "/logout" , AuthController , :logout
endAgora estamos permitindo que a autenticação seja opcional para todas as rotas no roteador. Fácil, ei?
AuthController Crie o AuthController com as funções login/2 e logout/2 .
Crie um novo arquivo: lib/liveview_chat_web/controllers/auth_controller.ex e adicione o seguinte código:
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 A função login/2 redireciona para o aplicativo Dwyl Auth. Leia mais sobre como usar a função AuthPlug.get_auth_url/2 . Uma vez autenticado, o usuário será redirecionado para o / terminal e uma sessão jwt for criada no cliente.
A função logout/2 invoca AuthPlug.logout/1 , que remove a sessão (JWT) e redireciona de volta para a página inicial.
on_mount/4 LiveView fornece o retorno de chamada on_mount que nos permite executar o código antes da mount . Usaremos esse retorno de chamada para verificar a sessão jwt e atribuir os valores person ( Map ) e loggedin ( boolean ) ao socket .
No arquivo lib/liveview_chat_web/controllers/auth_controller.ex adicione o seguinte código para definir duas versões 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 }
endatribui_new/3 atribui um valor ao soquete, se não existir.
Depois que o retorno de chamada on_mount/2 for definido, podemos chamá -lo em nosso arquivo 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
Agora temos toda a lógica para permitir que as pessoas se autentiquem, apenas precisamos atualizar nosso arquivo de layout root lib/liveview_chat_web/templates/layout/root.html.heex para exibir um link 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 > Se a pessoa ainda não loggedin exibimos um link login , caso contrário, o link logout será exibido.
A última etapa é exibir o nome da pessoa conectada no campo Nome do formulário de mensagem. Para isso, podemos atualizar o formulário Mudanças na função mount para definir os parâmetros de nome:
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: [ ] ] }
endAgora você pode executar o aplicativo e poder fazer login/logout!

Nesta seção, usaremos a presença de Phoenix para exibir uma lista de pessoas que estão usando o aplicativo atualmente.
A primeira etapa é criar o arquivo lib/liveview_chat/presence.ex :
defmodule LiveviewChat.Presence do
use Phoenix.Presence ,
otp_app: :liveview_chat ,
pubsub_server: LiveviewChat.PubSub
end Então, no lib/liveview_chat/application.ex adicionamos o módulo Presence recém -criado à lista de aplicativos para o supervisor iniciar:
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}
]
...
Agora estamos prontos para usar os recursos de presença em nosso terminal LiveView.
No arquivo lib/liveview_chat_web/live/message_live.ex , atualize a função mount com o seguinte:
@ 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 Vamos recapitular as principais mudanças na função mount/3 :
Primeiro, criamos o atributo do módulo @presence_topic para definir o topic que usaremos com as funções de presença.
A parte a seguir do código define uma tupla contendo um id da pessoa e seu nome. O nome será padrão para "Guest" se a pessoa não estiver logada.
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
endEm segundo lugar, usamos a função Track/4 para informar a presença que um novo cliente está analisando o aplicativo:
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )Terceiro, usamos o pubsub para ouvir mudanças de presença (pessoa que se junta ou deixa o aplicativo):
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic ) Finalmente, criamos uma nova presence atribuída no soquete:
presence : get_presence_names ( ) A função get_presence_names retornará uma lista de usuários do loggedIn e se houver algum número de usuários "convidados".
Adicione o código a seguir no final do módulo 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" A chamada de função importante no código acima é Presence.list(@presence_topic) . A função List/1 retorna a lista de usuários usando o aplicativo. Os group_names de Funções e os Guests e guest_names estão aqui para manipular os dados de presença retornados pela list , consulte https://hexdocs.pm/phoenix/phoenix.presence.html#c:list/1-presence-data-structure
Até agora, rastreamos novas pessoas usando a página de bate -papo na função mount e usamos o PubSub para ouvir mudanças de presença. A etapa final é lidar com essas alterações adicionando uma função handle_info :
def handle_info ( % { event: "presence_diff" , payload: _diff } , socket ) do
{ :noreply , assign ( socket , presence: get_presence_names ( ) ) }
endFinalmente, uma diferença de presença se unir e os eventos de licença será enviada aos clientes à medida que ocorrem em tempo real com o evento "presença_diff".
A função handle_info captura o evento presence_diff e reatribui ao soquete o valor presence com o resultado da chamada de função get_presence_names .
Para exibir os nomes, adicionamos o seguinte no lib/liveview_chat_web/templates/message/messages.html.heex Arquivo de modelo:
< b > People currently using the app: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >Agora você deve poder executar o aplicativo e ver os usuários do LoggedIn e o número de usuários convidados.
Podemos testar que o modelo foi atualizado corretamente adicionando esses dois testes em 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 Se você é novo no Tailwind , consulte: https://github.com/dwyl/learn-tailwind
Substitua o conteúdo de lib/liveview_chat_web/templates/layout/root.html.heex com:
<!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 > E, em seguida, substitua o conteúdo de lib/liveview_chat_web/templates/message/messages.html.heex com:
< 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 >Agora você deve ter uma interface do usuário/layout que se parece com o seguinte:

Se você tiver dúvidas sobre qualquer uma das aulas de Tailwind usadas, gaste 2 minutos no Google e, se você ainda estiver preso, abra um problema.
Se você achou este exemplo útil, por favor ️ o repositório do GitHub para que nós ( e outros ) saibamos que você gostamos!
Aqui estão alguns outros repositórios que você pode querer ler:
Alguma dúvida ou sugestão? Não hesite em abrir novos problemas!
Obrigado!