1
1
< html >
2
2
< head >
3
- < title > Messages, Models & Routes </ title >
3
+ < title > Messages & Models </ title >
4
4
< link href ="lib/bootstrap.css " rel ="stylesheet " />
5
5
< link href ="lib/prism.css " rel ="stylesheet " />
6
6
< link href ="lib/style.css " rel ="stylesheet " type ="text/css " media ="all " />
7
7
< script src ="lib/lodash.min.js "> </ script >
8
- < script src ="lib/radio.min.js "> </ script >
8
+ < script src ="lib/conduit.min.js "> </ script >
9
+ < script src ="lib/postal.min.js "> </ script >
10
+ < script src ="lib/postal.request-response.min.js "> </ script >
11
+ < script src ="lib/q.js "> </ script >
9
12
< script src ="lib/react.js "> </ script >
10
13
< script src ="lib/prism.js "> </ script >
11
14
< script src ="lib/JSXTransformer.js "> </ script >
12
15
< script src ="lib/sourceview.js "> </ script >
16
+ < script >
17
+ postal . configuration . promise . createDeferred = function ( ) {
18
+ return Q . defer ( ) ;
19
+ } ;
20
+ postal . configuration . promise . getPromise = function ( dfd ) {
21
+ return dfd . promise ;
22
+ } ;
23
+ </ script >
13
24
< script type ="text/jsx " id ="useMessageSource ">
14
25
/** @jsx React.DOM */
15
26
var ChatSession = React . createClass ( {
19
30
}
20
31
} ,
21
32
componentWillMount : function ( ) {
22
- radio ( this . props . channel + ".message.received" ) . subscribe ( this . receiveMessage ) ;
33
+ this . sub = postal . subscribe ( {
34
+ channel : this . props . channel ,
35
+ topic : "message.received" ,
36
+ callback : this . receiveMessage } ) ;
23
37
} ,
24
38
componentWillUnmount : function ( ) {
25
- radio ( this . props . channel + ".message.received" ) . unsubscribe ( this . receiveMessage ) ;
39
+ this . sub . unsubscribe ( )
26
40
} ,
27
41
receiveMessage : function ( message ) {
28
42
var messages = this . state . messages ;
34
48
} ,
35
49
sendMessage : function ( event ) {
36
50
var input = this . refs . input ;
37
- radio ( this . props . channel + ".message.send" ) . broadcast ( { message : input . state . value } ) ;
51
+ postal . publish ( {
52
+ channel : this . props . channel ,
53
+ topic : "message.send" ,
54
+ data : input . state . value } ) ;
38
55
input . setState ( { value : "" } ) ;
39
56
} ,
40
57
render : function ( ) {
@@ -53,17 +70,29 @@ <h3>Chat Connection ({this.props.channel})</h3>
53
70
54
71
var FuseSessionChats = function ( channel1 , channel2 ) {
55
72
function send ( channel , msg ) {
56
- radio ( channel + ".message.received" ) . broadcast ( {
57
- message : msg ,
58
- id : _ . uniqueId ( )
73
+ postal . publish ( {
74
+ channel : channel ,
75
+ topic : "message.received" ,
76
+ data : {
77
+ message : msg ,
78
+ id : _ . uniqueId ( )
79
+ }
59
80
} ) ;
60
81
}
61
82
62
- radio ( channel1 + ".message.send" ) . subscribe ( function ( data ) {
63
- send ( channel2 , "[" + channel1 + "]:" + data . message ) ;
83
+ postal . subscribe ( {
84
+ channel : channel1 ,
85
+ topic : "message.send" ,
86
+ callback : function ( data ) {
87
+ send ( channel2 , "[" + channel1 + "]:" + data ) ;
88
+ }
64
89
} ) ;
65
- radio ( channel2 + ".message.send" ) . subscribe ( function ( data ) {
66
- send ( channel1 , "[" + channel2 + "]:" + data . message ) ;
90
+ postal . subscribe ( {
91
+ channel : channel2 ,
92
+ topic : "message.send" ,
93
+ callback : function ( data ) {
94
+ send ( channel1 , "[" + channel2 + "]:" + data ) ;
95
+ }
67
96
} ) ;
68
97
69
98
send ( channel1 , "[Client] Connection established" ) ;
@@ -80,28 +109,199 @@ <h3>Chat Connection ({this.props.channel})</h3>
80
109
) ;
81
110
FuseSessionChats ( "chat1" , "chat2" )
82
111
</ script >
83
- < script type ="text/jsx " id ="routesSource ">
112
+ < script type ="text/jsx " id ="playlistSource ">
84
113
/** @jsx React.DOM */
85
- var UberTag = React . createClass ( {
114
+ var SongController = function ( channel ) {
115
+ var songs = { "pf" : { title : 'Another Brick in the Wall' , album : 'The Wall' , artist : 'Pink Floyd' , _id :"pf" } } ;
116
+
117
+ function notifyDetail ( song ) {
118
+ channel . publish ( "instance." + song . _id , song ) ;
119
+ }
120
+
121
+ function notifyList ( ) {
122
+ channel . publish ( "list" , _ . values ( songs ) ) ;
123
+ }
124
+
125
+ function create ( data , envelope ) {
126
+ data = data ? data : { } ;
127
+ data . _id = _ . uniqueId ( ) ;
128
+ songs [ data . _id ] = data ;
129
+ notifyDetail ( data )
130
+ notifyList ( )
131
+ }
132
+
133
+ function update ( data , envelope ) {
134
+ var songId = envelope . topic . substr ( 7 ) ;
135
+ data . _id = songId ;
136
+ songs [ songId ] = data ;
137
+ notifyDetail ( data ) ;
138
+ notifyList ( )
139
+ }
140
+
141
+ function patch ( data , envelope ) {
142
+ var songId = envelope . topic . substr ( 6 ) ;
143
+ var song = songs [ songId ] ;
144
+ song = _ . merge ( song , data , { _id : songId } ) ;
145
+ notifyDetail ( song )
146
+ notifyList ( )
147
+ }
148
+
149
+ function remove ( data , envelope ) {
150
+
151
+ }
152
+
153
+ //respond to CRUD operations
154
+ channel . subscribe ( "create" , create ) ;
155
+ channel . subscribe ( "update.#" , update ) ;
156
+ channel . subscribe ( "patch.#" , patch ) ;
157
+ channel . subscribe ( "delete.#" , remove ) ;
158
+
159
+ //response to data requests
160
+ channel . subscribe ( "list.get" , function ( data , envelope ) {
161
+ envelope . reply ( _ . values ( songs ) ) ;
162
+ } ) ;
163
+ } ;
164
+
165
+ var SubscriptionMixin = {
166
+ /*
167
+ * Tracks subscriptions and closes them on unmount
168
+ */
169
+ subscribe : function ( channel , topic , f ) {
170
+ //opens a guarded subscription. Return false to stop getting messages.
171
+ var sub ;
172
+ function callback ( data , envelope ) {
173
+ try {
174
+ if ( f ( data , envelope ) === false ) sub . unsubscribe ( ) ;
175
+ } catch ( e ) {
176
+ console . log ( "Error in subscription handler" , channel , topic , f , e )
177
+ console . log ( e . stack )
178
+ }
179
+ }
180
+ if ( _ . isString ( channel ) ) {
181
+ sub = postal . subscribe ( {
182
+ channel : channel ,
183
+ topic : topic ,
184
+ callback : callback
185
+ } ) ;
186
+ } else {
187
+ sub = channel . subscribe ( topic , callback ) ;
188
+ }
189
+ this . registerSubscription ( sub )
190
+ } ,
191
+ registerSubscription : function ( sub ) {
192
+ if ( ! this . subscriptions ) this . subscriptions = [ ] ;
193
+ this . subscriptions . push ( sub )
194
+ } ,
195
+ componentWillUnmount : function ( ) {
196
+ _ . each ( this . subscriptions , function ( sub ) {
197
+ sub . unsubscribe ( )
198
+ } ) ;
199
+ }
200
+ } ;
201
+
202
+ var SongEntry = React . createClass ( {
203
+ mixins : [ SubscriptionMixin ] ,
204
+ getInitialState : function ( ) {
205
+ return { song : this . props . song }
206
+ } ,
207
+ componentWillMount : function ( ) {
208
+ this . subscribe ( this . props . songChannel , "instance." + this . state . song . _id , this . updateSong )
209
+ } ,
210
+ updateSong : function ( data , envelope ) {
211
+ this . setState ( { song : data } )
212
+ } ,
213
+ update : function ( refName , event ) {
214
+ var patch = { } ;
215
+ patch [ refName ] = event . target . value ;
216
+ this . props . songChannel . publish ( "patch." + this . state . song . _id , patch ) ;
217
+ } ,
218
+ render : function ( ) {
219
+ return ( < div >
220
+ < fieldset >
221
+ < label > Title</ label >
222
+ < input value = { this . state . song . title } ref = "title" onChange = { this . update . bind ( this , "title" ) } type = "text" />
223
+ </ fieldset >
224
+ < fieldset >
225
+ < label > Album</ label >
226
+ < input value = { this . state . song . album } ref = "album" onChange = { this . update . bind ( this , "album" ) } type = "text" />
227
+ </ fieldset >
228
+ < fieldset >
229
+ < label > Artist</ label >
230
+ < input value = { this . state . song . artist } ref = "artist" onChange = { this . update . bind ( this , "artist" ) } type = "text" />
231
+ </ fieldset >
232
+ </ div > )
233
+ }
234
+ } ) ;
235
+
236
+ var SongPlayer = React . createClass ( {
237
+ getInitialState : function ( ) {
238
+ return {
239
+ current_song : null
240
+ }
241
+ } ,
242
+ componentWillMount : function ( ) {
243
+ this . playNext ( ) ;
244
+ } ,
245
+ playNext : function ( ) {
246
+ var index = _ . findIndex ( this . props . songs , { _id : this . state . current_song } ) ;
247
+ index += 1 ;
248
+ if ( index >= this . props . songs . length ) index = 0 ;
249
+ var song = this . props . songs [ index ]
250
+ if ( song ) {
251
+ this . setState ( { current_song : song . _id } ) ;
252
+ }
253
+ _ . delay ( this . playNext . bind ( this , null ) , 3000 ) ;
254
+ } ,
255
+ render : function ( ) {
256
+ var song = _ . find ( this . props . songs , { _id : this . state . current_song } ) ;
257
+ return < div >
258
+ < span key = "nowplaying" > Now Playing: </ span >
259
+ { song ? < span key = "display" > { song . title } - { song . album } </ span > : null }
260
+ </ div >
261
+ }
262
+
263
+ } ) ;
264
+
265
+ var Playlist = React . createClass ( {
266
+ mixins : [ SubscriptionMixin ] ,
86
267
getInitialState : function ( ) {
87
268
return {
88
- clicks : this . props . clicks || 0
269
+ songs : [ ]
89
270
} ;
90
271
} ,
91
- increment : function ( ) {
92
- this . setState ( { clicks : this . state . clicks + 1 } ) ;
272
+ componentWillMount : function ( ) {
273
+ this . props . songChannel . request ( {
274
+ topic : "list.get" ,
275
+ data : { } ,
276
+ timeout : 3000
277
+ } ) . then ( this . updateSongList ) ;
278
+ this . subscribe ( this . props . songChannel , "list" , this . updateSongList ) ;
279
+ } ,
280
+ updateSongList : function ( data , envelope ) {
281
+ this . setState ( { songs : data } ) ;
282
+ } ,
283
+ addEntry : function ( ) {
284
+ this . props . songChannel . publish ( "create" , { } ) ;
285
+ } ,
286
+ renderSongEntry : function ( song ) {
287
+ return < SongEntry song = { song } key = { song . _id } songChannel = { this . props . songChannel } />
93
288
} ,
94
289
render : function ( ) {
95
290
return < div >
96
- < button onClick = { this . increment } > { this . props . children } </ button >
97
- { this . state . clicks ? < span > Number of clicks: { this . state . clicks } </ span > : null }
291
+ < ul >
292
+ { this . state . songs . map ( this . renderSongEntry ) }
293
+ </ ul >
294
+ < button className = "btn btn-success" onClick = { this . addEntry } > Add</ button >
295
+ < SongPlayer songs = { this . state . songs } />
98
296
</ div > ;
99
297
}
100
298
} ) ;
101
299
300
+ var songChannel = postal . channel ( "mySongs" )
301
+ SongController ( songChannel ) ;
102
302
React . renderComponent (
103
- < UberTag clicks = { - 1 } > Hello World </ UberTag > ,
104
- document . getElementById ( 'routes ' )
303
+ < Playlist songChannel = { songChannel } / >,
304
+ document . getElementById ( 'playlist ' )
105
305
) ;
106
306
</ script >
107
307
</ head >
@@ -112,8 +312,7 @@ <h1>Messages</h1>
112
312
< div class ="col-sm-4 ">
113
313
< p >
114
314
Channels allow for asynchronous message passing between code routines.
115
- In this example we are using Radio which is a minimalist pub/sub library.
116
- Other libraries like Postal.js offer more features like topics, promise integration, and responses.
315
+ In this example we are using Postal.js which is a pub/sub library.
117
316
Messages can even be sent to web workers or to an iframe.
118
317
</ p >
119
318
< ul >
@@ -131,35 +330,26 @@ <h1>Messages</h1>
131
330
</ div >
132
331
</ section >
133
332
< section class ="container ">
134
- < h1 > Ajax & Model Layer </ h1 >
333
+ < h1 > Models </ h1 >
135
334
< div class ="row ">
136
- < div class ="col-sm-12 ">
137
- < p > Due to time constraints and lack of good public cross origin APIs the example for this has been skipped </ p >
335
+ < div class ="col-sm-4 ">
138
336
< p >
139
337
If components are editing data on a server then it is recommended that you use a model layer.
140
- It is recommended that you use messages to integrate React.js with the model layer.
141
338
React.js doesn't care what is being used for the model layer or if AJAX is directly called as long as updates are pushed through "setState".
339
+ Messages allow for all your components to be notified of model changes.
340
+ Postal.js offers promise integration and responses handling which we use for immediate data requests.
142
341
</ p >
143
342
< ul >
144
- < li > Choose your data backend</ li >
145
- < li > Bind to your model layer using messages</ li >
146
- < li > Ajax requests are messages made to the internet</ li >
147
- </ ul >
148
- </ div >
149
- </ div >
150
- </ section >
151
- < section class ="container ">
152
- < h1 > Routes</ h1 >
153
- < div class ="row ">
154
- < div class ="col-sm-4 ">
155
- < ul >
156
- < li > A component can act as a view controller</ li >
157
- < li > Tree structure allows the current state to be presented as a url</ li >
343
+ < li > Choose your own data backend</ li >
344
+ < li > Bind to your model layer using messages or callbacks</ li >
345
+ < li > Ajax requests are messages made to the internet with a callback</ li >
346
+ < li > Use < a href ="https://www.npmjs.org/package/backbone-react-component "> backbone-react-component</ a > for backbone integration</ li >
347
+ < li > Facebook has their own model layer for react called < a href ="https://facebook.github.io/react/docs/flux-todo-list.html "> flux</ a > </ li >
158
348
</ ul >
159
- < div class ="result " id ="routes "> </ div >
349
+ < div class ="result " id ="playlist "> </ div >
160
350
</ div >
161
351
< div class ="col-sm-8 ">
162
- < div class ="sourceCode " data-source ="routesSource " data-edit ="true "> </ div >
352
+ < div class ="sourceCode " data-source ="playlistSource " data-edit ="true "> </ div >
163
353
</ div >
164
354
</ div >
165
355
</ section >
0 commit comments