LiveView聊天教程
我們真的想要一個免費和開源的現實世界示例,其中包含完整的代碼,測試和auth。
我們寫了這篇文章,以便我們可以將我們的團隊/社區學習Phoenix LiveView的人指向它。
這個LiveView示例/教程將您從零到20分鐘的應用程序。
這是您可以期望在此示例/教程中涵蓋的目錄:
LiveView聊天教程Phoenix應用live目錄, LiveView控制器和模板router.exmount/3功能mount/3handle_event/3handle_info/2AUTH_API_KEYauth_plugrouter.ex中創建可選的驗證管道AuthControlleron_mount/4功能任何學習Phoenix LiveView想要一個獨立教程的人,包括: Setup , Testing , Authentication , Presence ,
建議您遵循LiveView Counter教程,因為該教程更為先進。至少,請查看先決條件列表,以便在開始這次冒險之前就知道需要在計算機上安裝的內容!
只要您安裝了Elixir , Phoenix和Postgres ,那就可以了!
Phoenix應用首先創建新的liveview_chat Phoenix應用程序:
mix phx.new liveview_chat --no-mailer --no-dashboard我們不需要email或dashboard功能,因此我們將它們排除在應用程序之外。您可以通過運行: mix help phx.new了解有關創建新的Phoenix應用程序的更多信息
運行mix deps.get以檢索依賴項。然後通過運行命令來創建liveview_chat_dev Postgres數據庫:
mix ecto.setup您應該看到類似於以下的輸出:
The database for LiveviewChat.Repo has been created
14:20:19.71 [info] Migrations already up該命令成功後,您現在應該能夠通過運行命令來啟動應用程序:
mix phx.server您將看到類似於以下內容的終端輸出:
[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...打開URL: http://localhost:4000在您的網絡瀏覽器中,您應該看到類似的內容:

live目錄, LiveView控制器和模板創建lib/liveview_chat_web/live文件夾和控制器, 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
end注意:文件名和代碼在任何地方都沒有“控制器”一詞。希望這不會混淆。從某種意義上說,它可以控制應用程序中發生的事情,這是一個“控制器”。
LiveView控制器需要定義函數mount/3和render/1 。
為了保持控制器簡單, mount/3只是返回{:ok, socket}元組,沒有任何更改。渲染render/1調用LiveviewChatWeb.MessageView.render/2 (隨附Phoenix ),該詞顯示messages.html.heex template ,我們將在下面定義。
創建lib/liveview_chat_web/views/message_view.ex文件:
defmodule LiveviewChatWeb.MessageView do
use LiveviewChatWeb , :view
end這類似於常規的Phoenix view 。這裡沒什麼特別的/有趣的。
接下來,創建lib/liveview_chat_web/templates/message Directory,然後創建lib/liveview_chat_web/templates/message/messages.html.heex文件,添加以下HTML的行:
< h1 > LiveView Message Page </ h1 >最後,為了使根佈局更簡單,請打開lib/liveview_chat_web/templates/layout/root.html.heex文件,然後將<body>的內容更新為:
< body >
< header >
< section class =" container " >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > router.ex現在,您已經創建了必要的文件,請打開路由器lib/liveview_chat_web/router.ex替換默認路由PageController控制器:
get "/" , PageController , :index使用MessageLive Controller:
scope "/" , LiveviewChatWeb do
pipe_through :browser
live "/" , MessageLive
end現在,如果您刷新頁面,則應查看以下內容:

在這一點上,我們進行了一些更改,這意味著我們的自動測試套件將不再通過...使用以下命令在命令行中運行測試:
mix test您將看到與以下相似的輸出:
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這是因為page_controller_test.exs仍然期望主頁包含"Welcome to Phoenix!"文本。
讓我們更新測試!在其中創建test test/liveview_chat_web/live/message_live_test.exs test/liveview_chat_web/live /live/live/ message_live_test.exs
向其添加以下測試代碼:
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我們正在測試/端點可以訪問,並且頁面上具有文本"LiveView Message Page" 。
有關測試和LiveView的更多信息,另請參見LiveViewTest模塊。
最後,您可以刪除鏈接到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現在,您可以使用mix test命令再次運行測試。您應該看到以下(測試通過):
Generated liveview_chat app
...
Finished in 0.1 seconds (0.06s async, 0.1s sync)
3 tests, 0 failures
Randomized with seed 841084通過定義了LiveView結構,我們可以專注於創建消息。數據庫將保存消息和發件人名稱。讓我們創建一個新的模式和遷移:
mix phx.gen.schema Message messages name:string message:string注意:不要忘記運行
mix ecto.migrate以在數據庫中創建新的messages表。
現在,我們可以更新Message模式以添加用於創建新消息和列出現有消息的功能。我們還將更新更改集,以添加消息文本的需求和驗證。打開lib/liveview_chat/message.ex文件並使用以下內容更新代碼:
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我們已經在消息輸入上添加了validate_length函數,以確保消息至少具有2個字符。這只是一個示例,以說明changeset驗證如何與LiveView頁面上的表單一起使用。
然後,我們創建了create_message/1和list_messages/0函數。類似於鳳凰城示例,我們將返回的消息數limit為最新20個。
mount/3功能打開lib/liveview_chat_web/live/message_live.ex文件,並在第3行中添加以下行:
alias LiveviewChat.Message下一個更新lib/liveview_chat_web/live/message_live.ex文件中的mount/3函數以使用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現在將獲取messages列表,並創建將用於消息表單的changeset 。然後,我們將changeset和messages分配給套接字,將其顯示在LiveView頁面上。
將messages.html.heex模板更新到以下代碼:
< 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 >它首先顯示新消息,然後為人們create新消息提供了一種表格。
如果刷新頁面,則應查看以下內容:

<.form></.form>語法是如何使用表單函數組件的方法。
函數組件是接收
assigns映射作為參數並返回使用~Hsigil構建的渲染struct的任何函數。
最後,讓我們確保測試仍在通過更新test/liveview_chat_web/live/message_live_test.exs文件中的assert來通過:
assert html_response ( conn , 200 ) =~ "LiveView Chat"當我們刪除了LiveView Message Page H1標題時,我們可以在根佈局中測試標題,並確保該頁面仍然正確顯示。
目前,如果我們運行Phoenix App mix phx.server並在瀏覽器中提交表單,則不會發生任何事情。如果我們查看服務器日誌,我們會看到以下內容:
** (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
提交表格正在創建一個新事件,該事件定義了phx-submit :
< . form let = { f } for = { @ changeset } id = "form" phx - submit = "new_message" >
但是,該事件尚未在服務器上管理,我們可以通過在lib/liveview_chat_web/live/message_live.ex中添加handle_event/3函數來解決此問題:
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 create_message函數與表單中的值一起調用。如果在嘗試將信息保存在數據庫中時發生error ,例如,如果名稱或message為空,則changeset可能會返回錯誤,或者message太短,則將changeset再次分配給套接字。這將允許表格顯示error信息:

如果消息沒有任何錯誤保存,我們正在創建一個新的更改集,該更改包含表單中的名稱,以避免人們必須以表單再次輸入其名稱,並且我們將新更改集分配給套接字。

現在顯示了表單,我們可以將以下測試添加到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我們正在使用form/3函數選擇表單並觸發名稱和消息的值不同的提交事件。我們正在測試正確顯示錯誤。
我們可以使用PubSub( Pub lish Sub Scribe)通知所有已連接的客戶,而不必重新加載頁面才能查看新創建的消息,而是創建了新消息並更新UI以顯示新消息。
打開lib/liveview_chat/message.ex文件,然後在頂部附近添加以下行:
alias Phoenix.PubSub接下來添加以下3個功能:
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 }當客戶端正確顯示LiveView頁面並收聽新消息時,將調用subscribe/0它只是Phoenix.pubsub.subscribe的包裝功能。
每次創建新消息將消息傳播給連接的客戶端時,都會調用notify/2 。 Repo.insert可以返回{:ok, message}或{:error, reason} ,因此我們需要定義notify/2處理兩種情況。
在message.ex中更新create_message/1函數以調用我們新創建的notify/2函數:
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
|> notify ( :message_created )
endmount/3現在,當LiveView頁面渲染時,我們可以連接客戶端。在lib/liveview_chat_web/live/message_live.ex文件的頂部,添加以下行:
alias LiveviewChat.PubSub然後使用以下方式更新mount/3功能:
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現在檢查套接字已連接,然後調用新消息Message.subscribe/0功能。
handle_event/3由於create_message/1的返回值已更改,因此我們需要將handle_event/3更新為以下:
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最後一步是通過在lib/liveview_chat_web/live/message_live.ex中定義handle_info/2函數來處理:message_created事件:
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ]
{ :noreply , assign ( socket , messages: messages ) }
end收到事件後,將新消息添加到現有消息列表中。然後將新列表分配給套接字,該列將更新UI以顯示新消息。
將以下測試添加到test/liveview_chat_web/live/message_live_test.exs以確保正確顯示消息在頁面上:
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現在,您應該使用LiveView擁有功能性聊天應用程序!使用以下方式運行Phoenix應用程序
mix phx.server訪問應用程序localhost:4000或更多的瀏覽器,並向自己發送一些消息!

我們可以注意到的一個問題是,在使用輸入字段上的Enter鍵發送消息後,消息輸入並不總是重置為空值。這迫使我們在編寫並發送新消息之前手動刪除了先前的消息。
原因是:
JavaScript客戶端始終是當前輸入值的真相來源。對於任何具有焦點的給定輸入,
LiveView也永遠不會覆蓋輸入的當前值,即使它與服務器的渲染更新偏離。請參閱:https://hexdocs.pm/phoenix_live_view/form-bindings.html#javascript-client-xtregifics
我們的解決方案是在一個LiveView Life-Cycle回調(安裝,更新,更新,破壞,斷開,重新連接)之後,使用phx-hook在客戶端上運行一些JavaScript。
讓我們添加一個掛鉤以在updated消息表格時監視。 phx-hook message.html.heex <.form>中
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >然後在assets/js/app.js文件中,添加以下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重置消息值的主要邏輯包含在updated()回調函數中:
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
}在將值設置為空字符串之前,我們首先檢查表格上沒有顯示invalid-feedback CSS類,在表單上顯示錯誤。 (閱讀有關反饋的更多信息:https://hexdocs.pm/phoenix_live_view/form-bindings.html#phx-feedback-for)
最後一步是用hooks在liveSocket上設置hooks: Hooks 。添加新消息時,現在應重置消息輸入!
目前, mount/3功能首先通過加載數據庫中的最新20個消息來初始化消息列表:
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然後,每次創建新消息時, handle_info函數將消息附加到消息列表:
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 ) }
end如果消息列表變得太長,那麼所有消息都將所有消息保存在服務器上的內存中,這可能會導致問題。
為了最大程度地減少內存的使用,我們可以將消息定義為臨時assign :
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: [ ] ] }
end消息列表被檢索一次,然後將其重置為空列表。
現在, handle_info/2僅需要將新消息分配給套接字:
def handle_info ( { :message_created , message } , socket ) do
{ :noreply , assign ( socket , messages: [ message ] ) }
end最後, heex消息模板會在帶有phx-update消息列表中的任何更改中聽,並將新消息附加到現有顯示的列表中。
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = {message.id} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >另請參見phoenix temporary-assigns文檔頁面:https://hexdocs.pm/phoenix_live_view/dom-patching.html#tempormorary-assigns
當前, name字段留給了該人在發送消息之前手動定義。這在基本的演示應用程序中很好,但是我們知道我們可以做得更好。在本節中,我們將使用auth_plug添加身份驗證。這將允許使用該應用程序的人們使用其GitHub或Google帳戶進行身份驗證,然後在消息表格中預填充name 。
AUTH_API_KEY根據說明,首先在https://authdemo.fly.dev/ eg中創建一個新的API密鑰:

然後創建一個.env文件並添加您的新創建的API密鑰:
export AUTH_API_KEY = 88SwQGzaZoJYXs6ihvwMy2dRVtm6KVeg4tSCjRKtwDvMUYUbi/88SwQDatWtSTMd2rKPnaZsAWFNpbf4vv2ZK7JW2nwuSypMeg/authdemo.fly.dev注意:出於安全原因,這不是有效的API密鑰。請創建自己的,它是免費的,需要不到一分鐘的時間。
auth_plug將auth_plug軟件包添加到您的依賴項中。在mix.exs文件中更新您的deps函數並添加:
{ :auth_plug , "~> 1.4.10" }此依賴性將為您創建新的會議,並與DWYL auth應用程序進行通信。
不要忘記:
source .envmix deps.get在編譯新依賴性之前,請確保可以訪問AUTH_API_KEY 。
您可以將依賴項重新編譯為mix deps.compile --force 。
現在,我們可以開始添加身份驗證功能。
router.ex中創建可選的驗證管道為了允許[未經身份驗證]“訪客”用戶訪問聊天,我們使用AuthPlugOptional插件。在可選的auth上閱讀更多信息。
在router.ex文件中,我們創建一個新的Plug管道:
# define the new pipeline using auth_plug
pipeline :authOptional , do: plug ( AuthPlugOptional )接下來更新scope "/", LiveviewChatWeb do阻止以下內容:
scope "/" , LiveviewChatWeb do
pipe_through [ :browser , :authOptional ]
live "/" , MessageLive
get "/login" , AuthController , :login
get "/logout" , AuthController , :logout
end現在,我們允許對路由器中所有路線的身份驗證是可選的。容易,嘿?
AuthController創建具有login/2和logout/2功能的AuthController 。
創建一個新文件: lib/liveview_chat_web/controllers/auth_controller.ex ,添加以下代碼:
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 login/2函數將重定向到DWYL驗證應用程序。閱讀有關如何使用AuthPlug.get_auth_url/2函數的更多信息。經過身份驗證後,用戶將被重定向到/端點,並在客戶端上創建jwt會話。
logout/2函數調用AuthPlug.logout/1該函數將刪除(JWT)會話並重定向回主頁。
on_mount/4功能LiveView提供了on_mount回調,該回調使我們可以在mount之前運行代碼。我們將使用此回調來驗證jwt會話,並將person ( Map )和loggedin ( boolean )值分配給socket 。
在lib/liveview_chat_web/controllers/auth_controller.ex文件中,添加以下代碼來定義兩個版本的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 }
end如果不存在,則分配_new/3將值分配給套接字。
定義了on_mount/2回調後,我們可以在我們的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
現在,我們擁有所有邏輯來讓人們進行身份驗證,我們只需要更新我們的root佈局文件lib/liveview_chat_web/templates/layout/root.html.heex即可顯示login (或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 >如果該人尚未loggedin我們將顯示一個login鏈接,否則顯示了logout鏈接。
最後一步是在消息表單的名稱字段中顯示已登錄人員的名稱。為此,我們可以更新mount函數中的表單更改集以設置名稱參數:
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: [ ] ] }
end您現在可以運行該應用程序並能夠登錄/註銷!

在本節中,我們將使用Phoenix在線顯示當前使用該應用程序的人的列表。
第一步是創建lib/liveview_chat/presence.ex文件:
defmodule LiveviewChat.Presence do
use Phoenix.Presence ,
otp_app: :liveview_chat ,
pubsub_server: LiveviewChat.PubSub
end然後在lib/liveview_chat/application.ex中,我們將新創建的Presence模塊添加到主管啟動的應用程序列表中:
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}
]
...
現在,我們準備使用LiveView端點中的存在功能。
在lib/liveview_chat_web/live/message_live.ex文件中,使用以下內容更新mount功能:
@ 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讓我們回顧一下mount/3函數的主要更改:
首先,我們創建模塊屬性@presence_topic來定義我們將與存在函數一起使用的topic 。
代碼的以下部分定義了包含該人id及其名稱的元組。如果該人不是loggedin,則該名稱將默認為“來賓”。
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
end其次,我們使用曲目/4功能讓在場知道新客戶正在查看該應用程序:
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )第三,我們使用pubsub聆聽存在變化(加入或離開申請的人):
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic )最後,我們在插座中創建一個新的presence :
presence : get_presence_names ( ) get_presence_names函數將返回loggedin用戶列表,以及任何“訪客”用戶的數量。
在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"上面代碼中的重要函數調用是Presence.list(@presence_topic) 。列表/1功能返回使用該應用程序的用戶列表。函數group_names和guest_names在這裡只是為了操縱list返回的存在數據,請參見https://hexdocs.pm/phoenix/phoenix/phoenix.presence.html#c:list/1-presence/1-presence-data-scrupture
到目前為止,我們已經使用mount功能中的聊天頁面跟踪了新朋友,並且我們一直在使用PubSub來收聽在場更改。最後一步是通過添加handle_info函數來處理這些更改:
def handle_info ( % { event: "presence_diff" , payload: _diff } , socket ) do
{ :noreply , assign ( socket , presence: get_presence_names ( ) ) }
end最後,在“存在”事件實時發生時,將會將一定的存在加入和休假事件發送給客戶。
handle_info函數捕獲了presence_diff事件,並以get_presence_names函數調用的結果重新分配到presence 。
要顯示名稱,我們在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 >現在,您應該能夠運行該應用程序,並查看Loggedin用戶和來賓用戶的數量。
我們可以通過在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 如果您是Tailwind的新手,請參閱:https://github.com/dwyl/learn-tailwind
lib/liveview_chat_web/templates/layout/root.html.heex :
<!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 >然後用以下方式替換lib/liveview_chat_web/templates/message/messages.html.heex的內容。
< 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 >現在,您應該擁有一個看起來像這樣的UI/佈局:

如果您對使用的任何Tailwind班有疑問,請花2分鐘的時間谷歌搜索,然後如果您仍然卡住,請打開問題。
如果您發現此示例有用,請github存儲庫,所以我們(和其他人)知道您喜歡它!
這是您可能想閱讀的其他一些存儲庫:
有任何問題或建議嗎?請隨時開放新問題!
謝謝你!