未加星标

Creating a Chatroom Using Phoenix, Elm, and Websockets

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏
This post will cover Creating a channel in Phoenix to handle websocket traffic Connecting to a web channel and handling messages using a websocket in Elm This post assumes the following Elixir , Elm , Phoenix , and Node have been installed A Phoenix Project is available to manipulate
Need Help? Try this post first! Elm is setup in Phoenix
This post by Cultivate HQ is excellent for starting elm in phoenix A working Elm site
Don’t have a working Elm site? Refer to the Elm starter guide or my template for a starter Elm app Creating a Channel in Phoenix

The easiest way to get started with channels is to use a phoenix generator .

Execute the following command in the root folder of the project.

mix phoenix.gen.channel Room

This will generate a default channel room_channel.ex and a couple of tests room_channel_test.exs to accompany it.

The output for the command looks like

[email protected] ~/temp/testelm $ mix phoenix.gen.channel Room * creating web/channels/room_channel.ex * creating test/channels/room_channel_test.exs Add the channel to your `web/channels/user_socket.ex` handler, for example: channel "room:lobby", Testelm.RoomChannel Adding the channel to the Socket Handler

Phoenix multiplexes channels over a single connection. The channel that was generated needs to be added to a socket handler. There is a default handler that is defined by phoenix located at ./web/channels/user_socket.ex .

Edit the file ./web/channels/user_socket.ex

Find the code block that looks like the following

defmodule Testelm.UserSocket do use Phoenix.Socket ## Channels # channel "room:*", Testelm.RoomChannel

Add

channel "room:lobby", Testelm.RoomChannel

The code block should look like

defmodule Testelm.UserSocket do use Phoenix.Socket ## Channels # channel "room:*", Testelm.RoomChannel channel "room:lobby", Testelm.RoomChannel Creating the Elm Chatroom Interface The interface

A simple chatroom consists of 2 main components; the message log and the chat input.

The chat input engages users by allowing interaction with the system. The interfacet must consist of elements that serve to optimize the user experience so that user engagement is encouraged.

The target minimal interface consists of a textbox to input a message, a button to send the message, and a dynamically generated list of messages.

A possible minimal interface looks like

import Html exposing (Html, div, li, ul, text, form, input, button) view : Model -> Html Msg view model = div [] [ ul [] [ li [] [ text model ] ], form [] [ input [] [ ], button [] [ text "Submit" ] ] ] Creating the model

The message that is crafted by the user and the messages that are received needs to be stored within a model in the elm application.

The model needs to store a string for the message being constructed and a list of strings for the messages received.

type alias Model = { messageInProgress : String, messages List : String } Initial model

The new model needs to be defined when the application starts.

Change the init function to handle the new model.

init : ( Model, Cmd Msg ) init = let model = { messageInProgress = "", messages = [ "Test message" ] } in ( model, Cmd.none ) Rendering the messages

Since the messages are stored in a list, the view needs to dynamically render the messages.

Create the function drawMessage to handle message rendering.

drawMessage : String -> Html Msg drawMessage message = li [] [ text message ] view : Model -> Html Msg view model = let drawMessages messages = messages |> List.map drawMessage in div [] [ ul [] (model.messages |> drawMessages), form [] [ input [] [ ], button [] [ text "Submit" ] ] ]

At this point the application should render the following screen.


Creating a Chatroom Using Phoenix, Elm, and Websockets
Using a Web Socket in Elm

There are multiple libraries for Elm that assist with connecting to phoenix web sockets. elm-phoenix-socket is a pure Elm interpretation of the Phoenix Socket library and integrates well into the Elm ecosystem.

Add elm-phoenix-socket as a dependency by running the following command in ./web/elm

elm package install fbonetti/elm-phoenix-socket

The output of the console should look like

[email protected] ~/temp/testelm/web/elm $ elm package install fbonetti/elm-phoenix-socket To install fbonetti/elm-phoenix-socket I would like to add the following dependency to elm-package.json: "fbonetti/elm-phoenix-socket": "2.0.0 <= v < 3.0.0" May I add that to elm-package.json for you? [Y/n] Y Some new packages are needed. Here is the upgrade plan. Install: elm-lang/websocket 1.0.1 fbonetti/elm-phoenix-socket 2.0.0 Do you approve of this plan? [Y/n] Y Starting downloads... ● elm-lang/websocket 1.0.1 ● fbonetti/elm-phoenix-socket 2.0.0 Packages configured successfully! Boiler plate code for phoenix sockets

There is some mandatory boiler plate code for the elm-phoenix-socket library that was installed.

In App.elm import the following

import Phoenix.Socket import Phoenix.Channel import Phoenix.Push

Add a phoenix socket to the model

type alias Model = { phxSocket : Phoenix.Socket.Socket Msg, messageInProgress : String, messages List : String }

Init the socket with the model

init : ( Model, Cmd Msg ) init = let model = { phxSocket = Phoenix.Socket.init "ws://localhost:4000/socket/websocket", messageInProgress = "", messages = [ "Test message" ] } in ( model, Cmd.none )

Add a PhoenixMsg to Msg

type Msg = HelloWorld | PhoenixMsg (Phoenix.Socket.Msg Msg)

Add the logic to the update case statement for Msg

update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of HelloWorld -> ( model, Cmd.none ) PhoenixMsg msg -> let ( phxSocket, phxCmd ) = Phoenix.Socket.update msg model.phxSocket in ( { model | phxSocket = phxSocket } , Cmd.map PhoenixMsg phxCmd )

Add a subscription for listening on the phoenix socket

subscriptions : Model -> Sub Msg subscriptions model = Phoenix.Socket.listen model.phxSocket PhoenixMsg Sending a message Storing the user input

The message input box is being rendered but the data is not being stored. The state needs to track user input by storing the value generated from the OnInput event.

In App.elm add SetMessage to Msg , add logic to handle the updated value, and hook the Msg into the html event.

type Msg = HelloWorld | PhoenixMsg (Phoenix.Socket.Msg Msg) | SetMessage String import Html.Events exposing (onInput) view : Model -> Html Msg view model = let drawMessages messages = messages |> List.map drawMessage in div [] [ li [] (model.messages |> drawMessages), form [] [ input [ onInput SetMessage ] [ ], button [] [ text "Submit" ] ] ] update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of HelloWorld -> ( model, Cmd.none ) PhoenixMsg msg -> let ( phxSocket, phxCmd ) = Phoenix.Socket.update msg model.phxSocket in ( { model | phxSocket = phxSocket } , Cmd.map PhoenixMsg phxCmd ) SetMessage message -> ( { model | messageInProgress = message }, Cmd.none ) Sending the message over the channel

In App.elm add a case for SendMessage to update.

type Msg = HelloWorld | PhoenixMsg (Phoenix.Socket.Msg Msg) | SetMessage String | SendMessage update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of HelloWorld -> ( model, Cmd.none ) PhoenixMsg msg -> let ( phxSocket, phxCmd ) = Phoenix.Socket.update msg model.phxSocket in ( { model | phxSocket = phxSocket } , Cmd.map PhoenixMsg phxCmd ) SetMessage message -> ( { model | messageInProgress = message }, Cmd.none ) SendMessage -> ( model, Cmd.none ) Constructing the payload

SendMessage will contain the logic for constructing the payload and pushing the payload over the socket.

update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of HelloWorld -> ( model, Cmd.none ) PhoenixMsg msg -> let ( phxSocket, phxCmd ) = Phoenix.Socket.update msg model.phxSocket in ( { model | phxSocket = phxSocket } , Cmd.map PhoenixMsg phxCmd ) SetMessage message -> ( { model | messageInProgress = message }, Cmd.none ) SendMessage -> let payload = JsEncode.object [ ("message", JsEncode.string model.messageInProgress) ] in ({ model }, Cmd.none) Creating the push command

The push command tells the elm-phoenix-socket library what to send, how to handle errors, and handle successful responses.

The elm application must handle successful and unsuccessful scenarios of web socket communication. Add ReceiveMessage and HandleSendError to the pattern match in update .

I also deleted the HelloWorld case as it wasn’t needed anymore

import Json.Encode as JsEncode type Msg = PhoenixMsg (Phoenix.Socket.Msg Msg) | SetMessage String | SendMessage | Receivemessage JsEncode.Value | HandleSendError JsEncode.Value update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of PhoenixMsg msg -> let ( phxSocket, phxCmd ) = Phoenix.Socket.update msg model.phxSocket in ( { model | phxSocket = phxSocket } , Cmd.map PhoenixMsg phxCmd ) SetMessage message -> ( { model | messageInProgress = message }, Cmd.none ) SendMessage -> let payload = JsEncode.object [ ("message", JsEncode.string model.messageInProgress) ] phxPush = Phoenix.Push.init "shout" "room:lobby" |> Phoenix.Push.withPayload payload |> Phoenix.Push.onOk ReceiveMessage |> Phoenix.Push.onError HandleSendError in ({ model }, Cmd.none) ReceiveMessage _ -> ( model, Cmd.none) HandleSendError _ -> let message = "Failed to Send Message" in ({ model | messages = message :: model.messages }, Cmd.none) Executing the push command

The push command has been created but needs to be pushed over the socket.

Call the function Phoenix.Socket.push passing the push command and the phoenix socket as parameters.

import Json.Encode as JsEncode update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of HelloWorld -> ( model, Cmd.none ) PhoenixMsg msg -> let ( phxSocket, phxCmd ) = Phoenix.Socket.update msg model.phxSocket in ( { model | phxSocket = phxSocket } , Cmd.map PhoenixMsg phxCmd ) SetMessage message -> ( { model | messageInProgress = message }, Cmd.none ) SendMessage -> let payload = JsEncode.object [ ("message", JsEncode.string model.messageInProgress) ] phxPush = Phoenix.Push.init "shout" "room:lobby" |> Phoenix.Push.withPayload payload |> Phoenix.Push.onOk ReceiveMessage |> Phoenix.Push.onError HandleSendError (phxSocket, phxCmd) = Phoenix.Socket.push phxPush model.phxSocket in ( { model | phxSocket = phxSocket }, Cmd.map PhoenixMsg phxCmd ) ReceiveMessage _ -> ( model, Cmd.none) HandleSendError _ -> let message = "Failed to Send Message" in ({ model | messages = message :: model.messages }, Cmd.none) Joining the channel

The Phoenix socket is being initalized in the init function. In order for messages to be sent over the channel, a room must be joined.

Change the init function to pipe the socket after initialization into the join function

init : ( Model, Cmd Msg ) init = let initSocket = Phoenix.Socket.init "ws://localhost:4000/socket/websocket" |> Phoenix.Socket.withDebug |> Phoenix.Socket.on "shout" "room:lobby" ReceiveMessage model = { phxSocket = initSocket, messageInProgress = "", messages = [ "Test message" ] } in ( model, Cmd.none ) Hooking into the OnSubmit event for the chat input form

Hook the SendMessage Msg into the OnSubmit event for the chat input form so that user input is tracked in the state.

import Html.Events exposing (onInput, onSubmit) view : Model -> Html Msg view model = let drawMessages messages = messages |> List.map drawMessage in div [] [ li [] (model.messages |> drawMessages), form [ onSubmit SendMessage] [ input [ onInput SetMessage ] [ ], button [] [ text "Submit" ] ] ] Receiving a Message in Elm

ReceiveMessage contains a Json.Encode.Value type that needs to be decoded then added to the message list.

import Json.Decode as JsDecode update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = .... ReceiveMessage raw -> let someMessage = JsDecode.decodeString "message" raw in case someMessage of Ok message -> ( { model | messages = message :: model.messages }, Cmd.none ) Err error -> ( model, Cmd.none ) Try it out!

At this point, the code should work. The next step is to write tests. There should be tests for the phoenix channel that was generated already (located in the file room_channel_test.exs

Summary

This post covered how to create a simple chatroom. The steps this post covered are:

Generating a channel in Phoenix Sending and Receiving messages over a websocket in elm

Questions?

Comment below or reach out over twitter!

@JeremyBellows

@elixirlang

@elmlang

@elixirphoenix Sample code from this post is located on Github

https://github.com/JeremyBellows/ElmPhoenixChatroom

There’s also a prototype chatroom

https://github.com/JeremyBellows/chilixelm

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: GitUA
分页:12
转载请注明
本文标题:Creating a Chatroom Using Phoenix, Elm, and Websockets
本站链接:http://www.codesec.net/view/480984.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(34)