Skip to content

Commit a12e0bc

Browse files
committed
Refactored WebSocketsExample
1 parent 9d2d07e commit a12e0bc

File tree

1 file changed

+64
-41
lines changed

1 file changed

+64
-41
lines changed

gh-pages/src/main/scala/ghpages/examples/WebSocketsExample.scala

+64-41
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import ghpages.GhPagesMacros
44
import ghpages.examples.util.SingleSide
55
import japgolly.scalajs.react._, vdom.prefix_<^._
66

7-
import org.scalajs.dom.{WebSocket, MessageEvent, Event, CloseEvent, ErrorEvent}
8-
97
object WebSocketsExample {
108

119
def content = SingleSide.Content(source, main())
@@ -16,80 +14,105 @@ object WebSocketsExample {
1614
val source = GhPagesMacros.exampleSource
1715

1816
// EXAMPLE:START
17+
18+
import org.scalajs.dom.{WebSocket, MessageEvent, Event, CloseEvent, ErrorEvent}
19+
1920
val url = "ws://echo.websocket.org"
2021

21-
case class State(ws: Option[WebSocket], log: List[String], message: String)
22+
case class State(ws: Option[WebSocket], logLines: Vector[String], message: String) {
23+
24+
// Create a new state with a line added to the log
25+
def log(line: String): State =
26+
copy(logLines = logLines :+ line)
27+
}
2228

2329
class Backend($: BackendScope[Unit, State]) {
24-
def render(p: Unit, s: State) = {
30+
def render(s: State) = {
31+
32+
// Can only send if WebSocket is connected and user has entered text
33+
val send: Option[Callback] =
34+
for (ws <- s.ws if s.message.nonEmpty)
35+
yield sendMessage(ws, s.message)
36+
2537
<.div(
26-
<.h3(s"Type a message and get an echo"),
27-
<.form(
28-
^.onSubmit ==> send,
38+
<.h3("Type a message and get an echo:"),
39+
<.div(
2940
<.input(
30-
^.onChange ==> onChange,
41+
^.onChange ==> onChange,
3142
^.value := s.message),
3243
<.button(
33-
^.disabled := s.message.isEmpty && s.ws.isDefined, "Send")), // Enable if the text exist and the WebSocket is connected
44+
^.disabled := send.isEmpty, // Disable button if unable to send
45+
^.onClick -->? send, // --> suffixed by ? because it's for Option[Callback]
46+
"Send")),
3447
<.h4("Connection log"),
35-
<.pre( // Log content
36-
^.width := 200, // Basic style
48+
<.pre(
49+
^.width := 200,
3750
^.height := 200,
3851
^.border := "1px solid",
39-
s.log.map(<.p(_)))
52+
s.logLines.map(<.p(_))) // Display log
4053
)
4154
}
4255

43-
def onChange(e: ReactEventI): Callback =
44-
$.modState(_.copy(message = e.target.value))
56+
def onChange(e: ReactEventI): Callback = {
57+
val newMessage = e.target.value
58+
$.modState(_.copy(message = newMessage))
59+
}
4560

46-
def send(e: ReactEventI): Callback = {
61+
def sendMessage(ws: WebSocket, msg: String): Callback = {
4762
// Send a message to the WebSocket
48-
val send = $.state.map(s => s.ws.foreach(_.send(s.message)))
49-
val preventSubmit = e.preventDefaultCB
50-
val updateLog = $.modState(s => s.copy(log = s.log :+ s"Sent: ${s.message}", message = ""))
51-
send >> preventSubmit >> updateLog
52-
}
63+
def send = Callback(ws.send(msg))
5364

54-
// These are message receiving events from the WebSocket "thread",
55-
// to change the state, you need to call `runNow()` on them
56-
def onopen(e: Event) = {
57-
// Indicate the connection is open
58-
$.modState(s => s.copy(log = s.log :+ "Connected")).runNow()
59-
}
65+
// Update the log, clear the text box
66+
def updateState = $.modState(s => s.log(s"Sent: ${s.message}").copy(message = ""))
6067

61-
def onmessage(e: MessageEvent) = {
62-
// Echo message received
63-
$.modState(s => s.copy(log = s.log :+ s"Echo: ${e.data.toString}")).runNow()
68+
send >> updateState
6469
}
6570

66-
def onerror(e: ErrorEvent) = {
67-
// Display error message
68-
$.modState(s => s.copy(log = s.log :+ s"Error: ${e.message}")).runNow()
69-
}
71+
def start: Callback = {
72+
// Get direct access so WebSockets API can modify state directly
73+
// (for access outside of a normal DOM/React callback).
74+
val direct = $.accessDirect
7075

71-
def onclose(e: CloseEvent) = {
72-
// Close the connection
73-
$.modState(s => s.copy(ws = None, log = s.log :+ s"Closed: ${e.reason}")).runNow()
74-
}
76+
// These are message-receiving events from the WebSocket "thread".
77+
78+
def onopen(e: Event): Unit = {
79+
// Indicate the connection is open
80+
direct.modState(_.log("Connected."))
81+
}
82+
83+
def onmessage(e: MessageEvent): Unit = {
84+
// Echo message received
85+
direct.modState(_.log(s"Echo: ${e.data.toString}"))
86+
}
87+
88+
def onerror(e: ErrorEvent): Unit = {
89+
// Display error message
90+
direct.modState(_.log(s"Error: ${e.message}"))
91+
}
92+
93+
def onclose(e: CloseEvent): Unit = {
94+
// Close the connection
95+
direct.modState(_.copy(ws = None).log(s"Closed: ${e.reason}"))
96+
}
7597

76-
def start: Callback = {
7798
// Create WebSocket and setup listeners
7899
val ws = new WebSocket(url)
79100
ws.onopen = onopen _
80101
ws.onclose = onclose _
81102
ws.onmessage = onmessage _
82103
ws.onerror = onerror _
83-
$.setState(State(Some(ws), List("Connecting"), ""))
104+
$.setState(State(Some(ws), Vector("Connecting..."), ""))
84105
}
85106

86107
def end: Callback = {
87-
$.state.map(s => s.ws.foreach(_.close())) >> $.modState(_.copy(ws = None))
108+
def closeWebSocket = $.state.map(_.ws.foreach(_.close()))
109+
def clearWebSocket = $.modState(_.copy(ws = None))
110+
closeWebSocket >> clearWebSocket
88111
}
89112
}
90113

91114
val WebSocketsApp = ReactComponentB[Unit]("WebSocketsApp")
92-
.initialState(State(None, Nil, ""))
115+
.initialState(State(None, Vector.empty, ""))
93116
.renderBackend[Backend]
94117
.componentDidMount(_.backend.start)
95118
.componentWillUnmount(_.backend.end)

0 commit comments

Comments
 (0)