@@ -4,8 +4,6 @@ import ghpages.GhPagesMacros
4
4
import ghpages .examples .util .SingleSide
5
5
import japgolly .scalajs .react ._ , vdom .prefix_<^ ._
6
6
7
- import org .scalajs .dom .{WebSocket , MessageEvent , Event , CloseEvent , ErrorEvent }
8
-
9
7
object WebSocketsExample {
10
8
11
9
def content = SingleSide .Content (source, main())
@@ -16,80 +14,105 @@ object WebSocketsExample {
16
14
val source = GhPagesMacros .exampleSource
17
15
18
16
// EXAMPLE:START
17
+
18
+ import org .scalajs .dom .{WebSocket , MessageEvent , Event , CloseEvent , ErrorEvent }
19
+
19
20
val url = " ws://echo.websocket.org"
20
21
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
+ }
22
28
23
29
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
+
25
37
< .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(
29
40
< .input(
30
- ^ .onChange ==> onChange,
41
+ ^ .onChange ==> onChange,
31
42
^ .value := s.message),
32
43
< .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" )),
34
47
< .h4(" Connection log" ),
35
- < .pre( // Log content
36
- ^ .width := 200 , // Basic style
48
+ < .pre(
49
+ ^ .width := 200 ,
37
50
^ .height := 200 ,
38
51
^ .border := " 1px solid" ,
39
- s.log .map(< .p(_)))
52
+ s.logLines .map(< .p(_))) // Display log
40
53
)
41
54
}
42
55
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
+ }
45
60
46
- def send ( e : ReactEventI ): Callback = {
61
+ def sendMessage ( ws : WebSocket , msg : String ): Callback = {
47
62
// 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))
53
64
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 = " " ))
60
67
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
64
69
}
65
70
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
70
75
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
+ }
75
97
76
- def start : Callback = {
77
98
// Create WebSocket and setup listeners
78
99
val ws = new WebSocket (url)
79
100
ws.onopen = onopen _
80
101
ws.onclose = onclose _
81
102
ws.onmessage = onmessage _
82
103
ws.onerror = onerror _
83
- $.setState(State (Some (ws), List (" Connecting" ), " " ))
104
+ $.setState(State (Some (ws), Vector (" Connecting... " ), " " ))
84
105
}
85
106
86
107
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
88
111
}
89
112
}
90
113
91
114
val WebSocketsApp = ReactComponentB [Unit ](" WebSocketsApp" )
92
- .initialState(State (None , Nil , " " ))
115
+ .initialState(State (None , Vector .empty , " " ))
93
116
.renderBackend[Backend ]
94
117
.componentDidMount(_.backend.start)
95
118
.componentWillUnmount(_.backend.end)
0 commit comments