Skip to content

Commit a971bbf

Browse files
committed
Add a better react example
1 parent 8cdd86c commit a971bbf

File tree

9 files changed

+313
-1
lines changed

9 files changed

+313
-1
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ src/test/groovy/io/vertx/groovy/codegen/**/*.groovy
2525
openshift-example/quickstart-diy/application/*.jar
2626
openshift-example/vertx-cartridge/application/*.jar
2727
web-examples/src/main/java/io/vertx/example/web/vertxbus/amd/webroot/sockjs-client
28+
/web-examples/src/main/java/io/vertx/example/web/react/webroot/dist
2829
web-examples/src/main/java/io/vertx/example/web/vertxbus/amd/webroot/vertx3-eventbus-client
2930
web-examples/src/main/java/io/vertx/example/web/vertxbus/commonjs/webroot/node_modules
3031
web-examples/src/main/java/io/vertx/example/web/vertxbus/node/node_modules
3132
web-examples/src/main/java/io/vertx/example/web/vertxbus/webpack/client/node_modules
32-
service-proxy-examples/service-provider/node-client/node_modules
33+
web-examples/src/main/java/io/vertx/example/web/react/node_modules
34+
service-proxy-examples/service-provider/node-client/node_modules

web-examples/README.adoc

+14
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,17 @@ tokens, in order to show that you cannot access the last resource.
381381

382382
Run the server either in your IDE or on the command line, then open your browser and hit
383383
link:http://localhost:8080 and click around the links
384+
385+
== React.js Realtime chat
386+
387+
This example shows how you can integrate Vert.x EventBus SockJS bridge in a simple React.JS application. Since react is
388+
writen in ES6 + JSX you will need to use webpack to bundle you client application, for this run `npm install` to install
389+
all the dependencies locally and later you can compile your client application with: `./node_modules/.bin/webpack -p`
390+
391+
Important files to note:
392+
393+
* link:src/main/java/io/vertx/example/web/react/app-client.js[React app example]
394+
* link:src/main/java/io/vertx/example/web/react/Server.java[Java Vert.x server]
395+
396+
Run the server either in your IDE or on the command line, then open your browser and hit
397+
link:http://localhost:8080 and chat with a couple of browser windows!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015", "react", "stage-2"]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"extends": "airbnb",
3+
"parser": "babel-eslint",
4+
"env": {
5+
"browser": true
6+
},
7+
"plugins": [
8+
"react"
9+
],
10+
"rules": {
11+
"react/jsx-no-bind": 0,
12+
"no-new": 0,
13+
"curly": [2, "multi-or-nest"],
14+
"comma-dangle": [2, "never"],
15+
"semi": 0,
16+
"camelcase": 0,
17+
"new-cap": 0,
18+
"strict": 0,
19+
"no-underscore-dangle": 0,
20+
"no-use-before-define": 0,
21+
"eol-last": 0,
22+
"quotes": [2, "single"],
23+
"jsx-quotes": 1,
24+
"react/jsx-no-undef": 1,
25+
"react/jsx-uses-react": 1,
26+
"react/jsx-uses-vars": 1
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.vertx.example.web.react;
2+
3+
import io.vertx.core.AbstractVerticle;
4+
import io.vertx.example.util.Runner;
5+
import io.vertx.ext.web.Router;
6+
import io.vertx.ext.web.handler.*;
7+
import io.vertx.ext.web.handler.sockjs.BridgeOptions;
8+
import io.vertx.ext.web.handler.sockjs.PermittedOptions;
9+
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
10+
11+
/*
12+
* @author <a href="mailto:[email protected]">Paulo Lopes</a>
13+
*/
14+
public class Server extends AbstractVerticle {
15+
16+
// Convenience method so you can run it in your IDE
17+
public static void main(String[] args) {
18+
Runner.runExample(Server.class);
19+
}
20+
21+
@Override
22+
public void start() throws Exception {
23+
24+
Router router = Router.router(vertx);
25+
26+
// Allow events for the designated addresses in/out of the event bus bridge
27+
BridgeOptions opts = new BridgeOptions()
28+
.addInboundPermitted(new PermittedOptions().setAddress("chat.message"))
29+
.addOutboundPermitted(new PermittedOptions().setAddress("chat.message"));
30+
31+
// Create the event bus bridge and add it to the router.
32+
SockJSHandler ebHandler = SockJSHandler.create(vertx).bridge(opts);
33+
router.route("/eventbus/*").handler(ebHandler);
34+
35+
// Create a router endpoint for the static content.
36+
router.route().handler(StaticHandler.create());
37+
38+
// Start the web server and tell it to use the router to handle requests.
39+
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// app-client.js
2+
import React, { Component } from 'react'
3+
import { render } from 'react-dom'
4+
import EventBus from 'vertx3-eventbus-client'
5+
import S from 'shorti'
6+
import _ from 'lodash'
7+
import { Input } from 'react-bootstrap'
8+
9+
class App extends Component {
10+
11+
constructor() {
12+
super();
13+
this.state = {
14+
data: {
15+
messages: []
16+
}
17+
};
18+
this.eventBus = null;
19+
}
20+
21+
componentDidMount() {
22+
let data = this.state.data;
23+
setTimeout(() => {
24+
this.refs.author.refs.input.focus();
25+
}, 100);
26+
27+
this.eventBus = new EventBus('http://localhost:8080/eventbus');
28+
const eb = this.eventBus;
29+
const self = this;
30+
// Listen for messages coming in
31+
eb.onopen = function () {
32+
eb.registerHandler('chat.message', (err, message) => {
33+
if (!err) {
34+
data = self.state.data;
35+
const messages = self.state.data.messages;
36+
if (data.author !== message.body.author) {
37+
messages.push(message.body);
38+
self.setState({
39+
data: {
40+
author: data.author,
41+
messages
42+
}
43+
})
44+
}
45+
}
46+
})
47+
}
48+
}
49+
50+
componentDidUpdate() {
51+
if (this.refs.message)
52+
this.refs.message.refs.input.focus();
53+
if (this.refs.messages_scroll_area)
54+
this.refs.messages_scroll_area.scrollTop = this.refs.messages_scroll_area.scrollHeight
55+
}
56+
57+
setAuthor() {
58+
const author = this.refs.author.refs.input.value.trim();
59+
if (!author)
60+
return;
61+
this.refs.author.refs.input.value = '';
62+
const messages = this.state.data.messages;
63+
this.setState({
64+
data: {
65+
author,
66+
messages
67+
}
68+
})
69+
}
70+
71+
createMessage() {
72+
const data = this.state.data;
73+
const messages = data.messages;
74+
const eb = this.eventBus;
75+
const message_text = this.refs.message.refs.input.value.trim();
76+
if (!message_text)
77+
return;
78+
const message = {
79+
message: message_text,
80+
author: data.author
81+
};
82+
// Send message out
83+
eb.publish('chat.message', message);
84+
// Render to browser
85+
messages.push(message);
86+
this.setState({
87+
data: {
88+
author: data.author,
89+
messages
90+
}
91+
});
92+
this.refs.message.refs.input.value = ''
93+
}
94+
95+
handleSubmit(e) {
96+
e.preventDefault();
97+
const data = this.state.data;
98+
if (data.author)
99+
this.createMessage();
100+
else
101+
this.setAuthor();
102+
}
103+
104+
render() {
105+
const data = this.state.data;
106+
let form_input;
107+
if (!data.author) {
108+
form_input = (
109+
<div>
110+
Hi, what is your name?<br />
111+
<Input type="text" ref="author" />
112+
</div>
113+
)
114+
} else {
115+
form_input = (
116+
<div>
117+
Hello { data.author }, type a message:<br />
118+
<Input type="text" ref="message" />
119+
</div>
120+
)
121+
}
122+
const messages = data.messages;
123+
let messages_list;
124+
if (messages) {
125+
// order by created
126+
const sorted_messages = _.sortBy(messages, message => {
127+
return message.created
128+
});
129+
messages_list = sorted_messages.map(message_object => {
130+
if (message_object) {
131+
return (
132+
<li style={ { listStyle: 'none', ...S('mb-5') } }>
133+
<b>{ message_object.author }</b><br/>
134+
{ message_object.message }
135+
</li>
136+
)
137+
}
138+
})
139+
}
140+
const scroll_area_style = {
141+
...S('h-' + (window.innerHeight - 140)),
142+
overflowY: 'scroll'
143+
};
144+
return (
145+
<div>
146+
<div style={ S('pl-15') }>
147+
<h2>React Chat App</h2>
148+
<div ref="messages_scroll_area" style={ scroll_area_style }>
149+
<ul style={ S('p-0') }>{ messages_list }</ul>
150+
</div>
151+
</div>
152+
<div style={ S('absolute b-0 w-100p pl-15 pr-15') }>
153+
<form onSubmit={ this.handleSubmit.bind(this) }>
154+
{ form_input }
155+
</form>
156+
</div>
157+
</div>
158+
)
159+
}
160+
}
161+
const app = document.getElementById('app');
162+
render(<App />, app);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "vertx-chat",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "app-client.js",
6+
"dependencies": {
7+
"lodash": "^4.0.0",
8+
"react": "^0.14.1",
9+
"react-bootstrap": "^0.28.2",
10+
"react-dom": "^0.14.1",
11+
"shorti": "^1.1.3",
12+
"vertx3-eventbus-client": "^3.2.1",
13+
"webpack": "^1.12.2"
14+
},
15+
"author": "Paulo Lopes",
16+
"license": "ISC",
17+
"devDependencies": {
18+
"babel": "^6.3.26",
19+
"babel-core": "^6.4.5",
20+
"babel-loader": "^6.2.1",
21+
"babel-preset-es2015": "^6.3.13",
22+
"babel-preset-react": "^6.3.13",
23+
"babel-preset-stage-2": "^6.3.13",
24+
"babel-register": "^6.4.3",
25+
"babel-eslint": "^5.0.0-beta6",
26+
"eslint": "^1.10.3",
27+
"eslint-config-airbnb": "^3.0.2",
28+
"eslint-loader": "^1.2.0",
29+
"eslint-plugin-react": "^3.14.0"
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// webpack.config.js
2+
var webpack = require('webpack');
3+
4+
module.exports = {
5+
devtool: 'source-map',
6+
entry: './app-client.js',
7+
output: {
8+
path: __dirname + '/webroot/dist',
9+
filename: 'bundle.js',
10+
publicPath: '/dist/'
11+
},
12+
module: {
13+
loaders: [{
14+
test: /\.js$/,
15+
loaders: ['babel'],
16+
exclude: /node_modules/
17+
}]
18+
}
19+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>React Chat App</title>
5+
<!-- Latest compiled and minified CSS -->
6+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script src="/dist/bundle.js"></script>
11+
</body>
12+
</html>

0 commit comments

Comments
 (0)