Skip to content

Commit 3fdfc07

Browse files
committed
model layer example
1 parent ed45353 commit 3fdfc07

File tree

5 files changed

+2161
-43
lines changed

5 files changed

+2161
-43
lines changed

lesson3.html

Lines changed: 233 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
<html>
22
<head>
3-
<title>Messages, Models &amp; Routes</title>
3+
<title>Messages &amp; Models</title>
44
<link href="lib/bootstrap.css" rel="stylesheet" />
55
<link href="lib/prism.css" rel="stylesheet" />
66
<link href="lib/style.css" rel="stylesheet" type="text/css" media="all" />
77
<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>
912
<script src="lib/react.js"></script>
1013
<script src="lib/prism.js"></script>
1114
<script src="lib/JSXTransformer.js"></script>
1215
<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>
1324
<script type="text/jsx" id="useMessageSource">
1425
/** @jsx React.DOM */
1526
var ChatSession = React.createClass({
@@ -19,10 +30,13 @@
1930
}
2031
},
2132
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});
2337
},
2438
componentWillUnmount: function() {
25-
radio(this.props.channel + ".message.received").unsubscribe(this.receiveMessage);
39+
this.sub.unsubscribe()
2640
},
2741
receiveMessage: function(message) {
2842
var messages = this.state.messages;
@@ -34,7 +48,10 @@
3448
},
3549
sendMessage: function(event) {
3650
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});
3855
input.setState({value: ""});
3956
},
4057
render: function() {
@@ -53,17 +70,29 @@ <h3>Chat Connection ({this.props.channel})</h3>
5370

5471
var FuseSessionChats = function(channel1, channel2) {
5572
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+
}
5980
});
6081
}
6182

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+
}
6489
});
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+
}
6796
});
6897

6998
send(channel1, "[Client] Connection established");
@@ -80,28 +109,199 @@ <h3>Chat Connection ({this.props.channel})</h3>
80109
);
81110
FuseSessionChats("chat1", "chat2")
82111
</script>
83-
<script type="text/jsx" id="routesSource">
112+
<script type="text/jsx" id="playlistSource">
84113
/** @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],
86267
getInitialState: function() {
87268
return {
88-
clicks: this.props.clicks || 0
269+
songs: []
89270
};
90271
},
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}/>
93288
},
94289
render: function() {
95290
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}/>
98296
</div>;
99297
}
100298
});
101299

300+
var songChannel = postal.channel("mySongs")
301+
SongController(songChannel);
102302
React.renderComponent(
103-
<UberTag clicks={-1}>Hello World</UberTag>,
104-
document.getElementById('routes')
303+
<Playlist songChannel={songChannel}/>,
304+
document.getElementById('playlist')
105305
);
106306
</script>
107307
</head>
@@ -112,8 +312,7 @@ <h1>Messages</h1>
112312
<div class="col-sm-4">
113313
<p>
114314
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.
117316
Messages can even be sent to web workers or to an iframe.
118317
</p>
119318
<ul>
@@ -131,35 +330,26 @@ <h1>Messages</h1>
131330
</div>
132331
</section>
133332
<section class="container">
134-
<h1>Ajax &amp; Model Layer</h1>
333+
<h1>Models</h1>
135334
<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">
138336
<p>
139337
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.
141338
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.
142341
</p>
143342
<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>
158348
</ul>
159-
<div class="result" id="routes"></div>
349+
<div class="result" id="playlist"></div>
160350
</div>
161351
<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>
163353
</div>
164354
</div>
165355
</section>

lib/conduit.min.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)