Skip to content

Commit 1651dee

Browse files
committed
Update progress, finishing with Channel chapter
There was enough thrashing in this chapter that I'll have to run through it again from scratch to make sure everything works as advertised. Thankfully I'm nearing the end with all the rewrites, and I'll replace the original sample app with a new version as I verify all the updated content works well.
1 parent 9632fa8 commit 1651dee

File tree

3 files changed

+99
-97
lines changed

3 files changed

+99
-97
lines changed

manuscript/Rewrites.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
- [x] adding_interaction.md
1717
- [x] displaying_game_data.md
1818
- [x] handling_game_states.md
19-
- [ ] phoenix_channel_setup.md
19+
- [x] phoenix_channel_setup.md
2020
- [ ] syncing_score_data.md
2121
- [ ] socket_authentication.md
2222
- [x] whats_next.md
2323
- [x] outline.md
2424
- [x] appendix.md
2525
- [x] contact.md
2626
- [x] run proselint
27+
- [ ] update Elm API chapter for new Http module API
28+
- [ ] test all content from beginning for new sample
29+
- [ ] replace old sample app on GitHub with new one
30+
- [ ] deploy new version of sample app to Heroku
Binary file not shown.

manuscript/phoenix_channel_setup.md

Lines changed: 94 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ configured earlier in the book.
4444
## Socket Connections
4545

4646
A helpful way to think about Phoenix channels is to think of data being sent
47-
back and forth over the socket. We saw something very similar in this book
48-
while working with the Phoenix framework. Clients make a _request_ and the
49-
server returns a _response_, and Phoenix uses a `conn` to store all the data
50-
that gets communicated back and forth.
47+
back and forth over the socket. We saw something similar in this book while
48+
working with the Phoenix framework. Clients make a _request_ and the server
49+
returns a _response_, and Phoenix uses a `conn` to store all the data that gets
50+
communicated back and forth.
5151

5252
Channels use the same idea, but instead of a `conn` we use a `socket`
5353
connection. We can assign data to the socket, and then it's available on both
@@ -469,86 +469,63 @@ SaveScore ->
469469

470470
Now that we have everything in place to push our data over the socket, we'll
471471
just need to join the `"score:platformer"` channel and broadcast the player's
472-
score. Below our `initialSocket` function, let's create a new function called
473-
`initialChannel`.
472+
score. We'll accomplish this in two steps. First, we'll create a button that
473+
allows players to join the channel. Then, we'll add another button that enables
474+
us to save scores.
474475

475-
```elm
476-
initialChannel : Phoenix.Channel.Channel msg
477-
initialChannel =
478-
Phoenix.Channel.init "score:platformer"
479-
```
480-
481-
We're going to adjust the `initialSocket` function so that it returns a tuple.
482-
The first item in that tuple is what we'll use for the `phxSocket` field in our
483-
`initialModel`, and then second item in the tuple will be used as the initial
484-
command in our `init` function.
485-
486-
Here is the full code for our `initialModel`, `initPhxSocket`, and `init`
487-
functions:
476+
Below our `initialSocket` function, let's start by creating a new function
477+
called `initialChannel`.
488478

489479
```elm
490-
initialSocket : ( Phoenix.Socket.Socket Msg, Cmd (Phoenix.Message.Msg Msg) )
491-
initialSocket =
492-
let
493-
devSocketServer =
494-
"ws://localhost:4000/socket/websocket"
495-
in
496-
devSocketServer
497-
|> Phoenix.Socket.init
498-
-- |> Phoenix.Socket.on "save_score" "score:platformer" SaveScore
499-
|> Phoenix.Socket.join initialChannel
480+
initialChannel : Phoenix.Channel.Channel Msg
481+
initialChannel =
482+
"score:platformer"
483+
|> Phoenix.Channel.init
484+
|> Phoenix.Channel.on "save_score" SaveScoreSuccess
500485
```
501486

502-
We can create two new functions to identify which parts of the tuple we need:
487+
Next, we'll update our `Msg` type with a new `JoinChannel` message:
503488

504489
```elm
505-
initialSocketJoin : Phoenix.Socket.Socket Msg
506-
initialSocketJoin =
507-
Tuple.first initialSocket
508-
509-
510-
initialSocketCommand : Cmd (Phoenix.Message.Msg Msg)
511-
initialSocketCommand =
512-
Tuple.second initialSocket
490+
type Msg
491+
= CountdownTimer Time.Posix
492+
| GameLoop Float
493+
| JoinChannel
494+
| KeyDown String
495+
| NoOp
496+
| SaveScoreSuccess Encode.Value
497+
| SaveScoreError Encode.Value
498+
| SaveScore
499+
| PhoenixMsg (Phoenix.Message.Msg Msg)
500+
| SetNewItemPositionX Int
513501
```
514502

515-
Now, we can set the `phxSocket` field in our `initialModel` to
516-
`initialSocketJoin`:
503+
With our `initialChannel` and `JoinChannel` code in place, we can set up our
504+
`update` function. For the `JoinChannel` case, we're taking the existing socket
505+
connection with `model.phxSocket` and using `Phoenix.join` with our
506+
`initialChannel` function to join the channel and update it in the model.
517507

518508
```elm
519-
initialModel : Model
520-
initialModel =
521-
{ characterDirection = Right
522-
, characterPositionX = 50
523-
, characterPositionY = 300
524-
, gameState = StartScreen
525-
, itemPositionX = 500
526-
, itemPositionY = 300
527-
, itemsCollected = 0
528-
, phxSocket = initialSocketJoin
529-
, playerScore = 0
530-
, timeRemaining = 10
531-
}
532-
```
509+
update : Msg -> Model -> ( Model, Cmd Msg )
510+
update msg model =
511+
case msg of
512+
-- ...
533513

534-
And we can update our `init` function with the `initialSocketCommand` to get
535-
things up and running:
514+
JoinChannel ->
515+
let
516+
( updatedSocket, updatedCommand ) =
517+
Phoenix.join PhoenixMsg initialChannel model.phxSocket
518+
in
519+
( { model | phxSocket = updatedSocket }, updatedCommand )
536520

537-
```elm
538-
init : () -> ( Model, Cmd Msg )
539-
init _ =
540-
( initialModel, Cmd.map PhoenixMsg initialSocketCommand )
521+
-- ...
541522
```
542523

543-
This is a lot to process, but let's keep moving for now so we can get things
544-
working, and we'll work towards a deeper understanding as we gain more
545-
familiarity with the code we're working with.
546-
547-
## Triggering SaveScore
524+
## Triggering JoinChannel and SaveScore
548525

549-
Finally, we just need to trigger the `SaveScoreRequest` message whenever we
550-
want to send our score over the socket. We'll set it up so that we can click a
551-
button and then check the DevTools console to view the `payload` that's being
526+
Finally, we just need to trigger the `JoinChannel` and `SaveScore` messages
527+
to send our score over the socket. We'll set it up so that we can click a
528+
button and then check the server console to view the `payload` that's being
552529
sent over the socket.
553530

554531
At the top of our file, let's import the `onClick` functionality from
@@ -562,63 +539,84 @@ import Html.Events exposing (onClick)
562539

563540
## Adding a Button to the View
564541

565-
Now, we can create a new `viewSaveScoreButton` function and add it to our main
566-
`view` to trigger the `SaveScoreRequest` message.
542+
Now, we can create new `viewJoinChannelButton` and `viewSaveScoreButton`
543+
functions and add them to our main `view` to trigger the `JoinChannel` and
544+
`SaveScore` messages.
567545

568546
```elm
569547
view : Model -> Html Msg
570548
view model =
571549
div []
572550
[ viewGame model
551+
, viewJoinChannelButton
573552
, viewSaveScoreButton
574553
]
575554

576555

556+
viewJoinChannelButton : Html Msg
557+
viewJoinChannelButton =
558+
div []
559+
[ button [ onClick JoinChannel, class "button" ]
560+
[ text "Join Channel" ]
561+
]
562+
563+
577564
viewSaveScoreButton : Html Msg
578565
viewSaveScoreButton =
579566
div []
580-
[ button [ onClick SaveScoreRequest, class "button" ]
567+
[ button [ onClick SaveScore, class "button" ]
581568
[ text "Save Score" ]
582569
]
583570
```
584571

585-
Add `Debug.log` to `SaveScoreRequest`?
586-
587572
## Testing Out the Socket
588573

589574
We've got everything configured, and we should be able to test out our working
590-
socket payload. Start the Phoenix server with `mix phx.server` and load the
591-
game in the browser. Collect a few coins to increment the player's score, and
592-
then click the new "Save Score" button.
575+
socket payload. Here are the steps to try it:
576+
577+
- Start the server with `mix phx.server` and load the game in the browser.
578+
- Play the game and collect a few coins to increment the player's score.
579+
- Click the "Join Channel" button.
580+
- Click the "Save Score" button.
593581

594-
The DevTools console will show an initial message when the page loads, and this
595-
let's us know that our `"score:platformer"` topic was initialized with an `"ok"`
596-
status.
582+
Although we won't see any changes in the UI, we should be able to check the
583+
server console and see things working.
584+
585+
When the game loads on the page we see that we connect to the socket:
597586

598587
```shell
599-
SaveScoreRequest: ({ event = "save_score", topic = "score:platformer", playerScore = 500, timeRemaining = 5 })
588+
[info] GET /games/platformer
589+
[info] CONNECT PlatformWeb.UserSocket
590+
[info] Replied PlatformWeb.UserSocket :ok
600591
```
601592

602-
We also see a message triggered in the DevTools console when we click the score
603-
text. It shows that we triggered a `"save_score"` event, which means we can
604-
broadcast the data over the socket. This is the behavior we want, because we'll
605-
want all the player scores to be visible and update in real-time on the Phoenix
606-
page where we list out all the players. In this example, you can see that we
607-
collected three coins for a score of `300`, and that is reflected in the
608-
payload with `{ score = 300 }`.
593+
Then, when we click the "Join Channel" button we see the successful join
594+
message:
609595

610596
```shell
611-
Phoenix message: { event = "save_score", topic = "score:platformer", payload = { player_score = 300 }, ref = Nothing }
597+
[info] JOIN "score:platformer" to PlatformWeb.ScoreChannel
598+
[info] Replied score:platformer :ok
612599
```
613600

614-
![Working Socket Payload](images/phoenix_channel_setup/working_socket_payload.png)
601+
Lastly, when we click the "Save Score" button we see the data being sent over
602+
the socket:
615603

616-
## Summary
604+
```shell
605+
[debug] INCOMING "save_score" on "score:platformer" to PlatformWeb.ScoreChannel
606+
Parameters: %{"player_score" => 300}
607+
```
617608

618-
We made it a _long_ way in this chapter, but we still haven't accomplished our
619-
goal of syncing data between the Elm front-end and Phoenix back-end.
609+
When we trigger the `"save_score"` event, we're broadcasting the data over the
610+
socket. The broadcast allows us to send the data in real-time to any other
611+
players connected to the socket. In the example above, you can see that we
612+
collected three coins for a score of `300`, and that is reflected in the
613+
payload with `%{"player_score" => 300}`.
614+
615+
## Summary
620616

621-
We created a new Phoenix channel, we installed elm-phoenix-socket, and we
622-
configured our game to send a payload. But we still need to handle the data
623-
that's being sent from the front-end. Let's take a look at these topics in the
624-
next chapter.
617+
We made it a _long_ way in this chapter. We managed to take care of most of the
618+
hard work, and we're successfully sending data from the Elm front-end to the
619+
Phoenix back-end. We created a new Phoenix channel, installed a package, and
620+
configured our game to send a payload. But we're not displaying the player
621+
scores in our application or persisting the data anywhere yet. We'll take a
622+
look at these topics in the next chapter.

0 commit comments

Comments
 (0)