From 534f08093335c2467c40e3d66c4f3e5f809b7bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 15:41:40 +0200 Subject: [PATCH 01/11] compact example --- build.gradle | 20 +++--- src/main/java/dasniko/ozark/react/React.java | 41 ++++++++++++ .../dasniko/ozark/react/ReactController.java | 10 ++- .../dasniko/ozark/react/ReactViewEngine.java | 67 +++++++++++++++++++ src/main/resources/jsx/bookBox.js | 5 +- src/main/resources/nashorn-polyfill.js | 7 ++ src/main/webapp/WEB-INF/views/react.jsp | 7 +- 7 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 src/main/java/dasniko/ozark/react/React.java create mode 100644 src/main/java/dasniko/ozark/react/ReactViewEngine.java create mode 100644 src/main/resources/nashorn-polyfill.js diff --git a/build.gradle b/build.gradle index 75d9331..6e5ec75 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,6 @@ plugins { } apply plugin: 'war' -group = 'com.github.dasniko' - sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -20,18 +18,20 @@ repositories { dependencies { // servlet-api providedCompile 'javax:javaee-web-api:7.0' + // mvc api and ri + compile 'javax.mvc:javax.mvc-api:1.0-SNAPSHOT' + compile 'org.glassfish.ozark:ozark:1.0.0-m03-SNAPSHOT' // required dependencies to be able to deploy to a plain servlet container like tomcat - compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.20' - compile 'org.glassfish.jersey.ext.cdi:jersey-cdi1x:2.20' - compile 'org.glassfish.jersey.ext:jersey-bean-validation:2.20' - compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.20' + compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.2' + compile 'org.glassfish.jersey.ext.cdi:jersey-cdi1x:2.22.2' + compile 'org.glassfish.jersey.ext:jersey-bean-validation:2.22.2' + compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.22.2' compile 'javax.enterprise:cdi-api:2.0-EDR1' compile 'org.jboss.weld.servlet:weld-servlet-core:3.0.0.Alpha12' - // the actual ozark-react viewenginge - compile 'com.github.dasniko:ozark-react:0.1.1' // some needed webjars (frontend-stuff) - compile 'org.webjars:jquery:1.11.3' - compile 'org.webjars:bootstrap:3.3.5' + compile 'org.webjars:react:0.14.8' + compile 'org.webjars:jquery:2.2.3' + compile 'org.webjars:bootstrap:3.3.6' } war { diff --git a/src/main/java/dasniko/ozark/react/React.java b/src/main/java/dasniko/ozark/react/React.java new file mode 100644 index 0000000..bcf4912 --- /dev/null +++ b/src/main/java/dasniko/ozark/react/React.java @@ -0,0 +1,41 @@ +package dasniko.ozark.react; + +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +/** + * @author Niko Köbler, http://www.n-k.de, @dasniko + */ +public class React { + + private ThreadLocal engineHolder = ThreadLocal.withInitial(() -> { + ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn"); + try { + nashorn.eval(read("/nashorn-polyfill.js")); + nashorn.eval(read("/META-INF/resources/webjars/react/0.14.8/react.min.js")); + nashorn.eval(read("/js/bookBox.js")); + } catch (ScriptException e) { + throw new RuntimeException(e); + } + return nashorn; + }); + + public String render(Object object) { + try { + Object html = ((Invocable) engineHolder.get()).invokeFunction("renderServer", object); + return String.valueOf(html); + } catch (Exception e) { + throw new IllegalStateException("failed to render react component", e); + } + } + + private Reader read(String path) { + InputStream in = getClass().getClassLoader().getResourceAsStream(path); + return new InputStreamReader(in); + } +} \ No newline at end of file diff --git a/src/main/java/dasniko/ozark/react/ReactController.java b/src/main/java/dasniko/ozark/react/ReactController.java index dd94027..185cfe6 100644 --- a/src/main/java/dasniko/ozark/react/ReactController.java +++ b/src/main/java/dasniko/ozark/react/ReactController.java @@ -24,6 +24,14 @@ public class ReactController { public String index() throws Exception { List books = service.getBooks(); models.put("data", books); - return "react:react.jsp?function=renderServer"; + return "react:react.jsp"; } + +// @GET +// public String index() throws Exception { +// List books = service.getBooks(); +// ObjectMapper mapper = new ObjectMapper(); +// models.put("data", mapper.writeValueAsString(books)); +// return "react.jsp"; +// } } diff --git a/src/main/java/dasniko/ozark/react/ReactViewEngine.java b/src/main/java/dasniko/ozark/react/ReactViewEngine.java new file mode 100644 index 0000000..b6b9f84 --- /dev/null +++ b/src/main/java/dasniko/ozark/react/ReactViewEngine.java @@ -0,0 +1,67 @@ +package dasniko.ozark.react; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.glassfish.ozark.engine.ServletViewEngine; +import org.glassfish.ozark.engine.ViewEngineContextImpl; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.mvc.Models; +import javax.mvc.engine.Priorities; +import javax.mvc.engine.ViewEngineContext; +import javax.mvc.engine.ViewEngineException; +import javax.servlet.ServletException; +import java.io.IOException; + +/** + * @author Niko Köbler, http://www.n-k.de, @dasniko + */ +@Priority(Priorities.FRAMEWORK) +public class ReactViewEngine extends ServletViewEngine { + + private static final String viewPrefix = "react:"; + + @Inject + React react; + + ObjectMapper mapper = new ObjectMapper(); + + @Override + public boolean supports(String view) { + return view.startsWith(viewPrefix); + } + + @Override + public void processView(ViewEngineContext context) throws ViewEngineException { + // parse view and extract the actual template + String template = context.getView().substring(viewPrefix.length()); + + // get "data" from model + Models models = context.getModels(); + Object data = models.get("data"); + + // call js function on data to generate html + String content = react.render(data); + + // and put results as string in model + models.put("content", content); + try { + models.put("data", mapper.writeValueAsString(data)); + } catch (JsonProcessingException e) { + throw new ViewEngineException(e); + } + + // create a new context with the actual view and forward to ServletViewEngine + ViewEngineContext ctx = new ViewEngineContextImpl(template, models, + context.getRequest(), context.getResponse(), context.getUriInfo(), + context.getResourceInfo(), context.getConfiguration()); + + try { + forwardRequest(ctx, "*.jsp", "*.jspx"); + } catch (ServletException | IOException e) { + throw new ViewEngineException(e); + } + } + +} diff --git a/src/main/resources/jsx/bookBox.js b/src/main/resources/jsx/bookBox.js index ca0a420..9d347fc 100644 --- a/src/main/resources/jsx/bookBox.js +++ b/src/main/resources/jsx/bookBox.js @@ -1,5 +1,3 @@ -var converter = new Showdown.converter(); - var BookForm = React.createClass({ handleSubmit: function (e) { e.preventDefault(); @@ -26,11 +24,10 @@ var BookForm = React.createClass({ var Book = React.createClass({ render: function () { - var rawMarkup = converter.makeHtml(this.props.children.toString()); return (

{this.props.author}

-
+
); } diff --git a/src/main/resources/nashorn-polyfill.js b/src/main/resources/nashorn-polyfill.js new file mode 100644 index 0000000..11568c5 --- /dev/null +++ b/src/main/resources/nashorn-polyfill.js @@ -0,0 +1,7 @@ +var global = this; +var process = {env:{}}; + +var console = {}; +console.debug = print; +console.warn = print; +console.log = print; diff --git a/src/main/webapp/WEB-INF/views/react.jsp b/src/main/webapp/WEB-INF/views/react.jsp index 1f7f9c7..f489bef 100644 --- a/src/main/webapp/WEB-INF/views/react.jsp +++ b/src/main/webapp/WEB-INF/views/react.jsp @@ -8,11 +8,10 @@ ReactJS Bookstore with Ozark - - - + + - + From a9092e9965caf5098fe3395c5a3457f71895243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 17:48:57 +0200 Subject: [PATCH 02/11] switched to packaging all js/jsx resources with webpack, what is much more powerful --- build.gradle | 15 --- package.json | 35 ++++++ src/main/java/dasniko/ozark/react/React.java | 10 +- .../resources/META-INF/resources/js/app.js | 1 + .../META-INF/resources/js/bookBox.js | 1 - src/main/resources/jsx/book.jsx | 12 ++ src/main/resources/jsx/bookBox.js | 116 ------------------ src/main/resources/jsx/bookBox.jsx | 60 +++++++++ src/main/resources/jsx/bookForm.jsx | 31 +++++ src/main/resources/jsx/bookList.jsx | 19 +++ src/main/resources/jsx/index.jsx | 26 ++++ src/main/resources/nashorn-polyfill.js | 10 +- src/main/webapp/WEB-INF/views/react.jsp | 6 +- webpack.config.js | 26 ++++ 14 files changed, 223 insertions(+), 145 deletions(-) create mode 100644 package.json create mode 100644 src/main/resources/META-INF/resources/js/app.js delete mode 100644 src/main/resources/META-INF/resources/js/bookBox.js create mode 100644 src/main/resources/jsx/book.jsx delete mode 100644 src/main/resources/jsx/bookBox.js create mode 100644 src/main/resources/jsx/bookBox.jsx create mode 100644 src/main/resources/jsx/bookForm.jsx create mode 100644 src/main/resources/jsx/bookList.jsx create mode 100644 src/main/resources/jsx/index.jsx create mode 100644 webpack.config.js diff --git a/build.gradle b/build.gradle index 6e5ec75..8c13a44 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,3 @@ -plugins { - id 'net.eikehirsch.react' version '0.3.1' -} apply plugin: 'war' sourceCompatibility = JavaVersion.VERSION_1_8 @@ -29,18 +26,6 @@ dependencies { compile 'javax.enterprise:cdi-api:2.0-EDR1' compile 'org.jboss.weld.servlet:weld-servlet-core:3.0.0.Alpha12' // some needed webjars (frontend-stuff) - compile 'org.webjars:react:0.14.8' compile 'org.webjars:jquery:2.2.3' compile 'org.webjars:bootstrap:3.3.6' } - -war { - exclude('**/module-cache/**') -} - -jsx { - sourcesDir = 'src/main/resources/jsx' - destDir = 'src/main/resources/js' -} - -processResources.dependsOn 'jsx' diff --git a/package.json b/package.json new file mode 100644 index 0000000..5db954b --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "ozark-react-example", + "version": "1.0.0", + "description": "A ViewEngine for ReactJS templates for the Java EE MVC 1.0 reference implementation Ozark.", + "main": "index.js", + "dependencies": { + "react": "^0.14.8", + "react-dom": "^0.14.8" + }, + "devDependencies": { + "babel-core": "^5.8.38", + "babel-loader": "^5.4.0", + "webpack": "^1.13.0" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "webpack": "./node_modules/.bin/webpack" + }, + "repository": { + "type": "git", + "url": "git+/~https://github.com/dasniko/ozark-react-example.git" + }, + "keywords": [ + "react", + "ozark", + "mvc", + "java" + ], + "author": "Niko Köbler (http://www.n-k.de)", + "license": "MIT", + "bugs": { + "url": "/~https://github.com/dasniko/ozark-react-example/issues" + }, + "homepage": "/~https://github.com/dasniko/ozark-react-example#readme" +} diff --git a/src/main/java/dasniko/ozark/react/React.java b/src/main/java/dasniko/ozark/react/React.java index bcf4912..af8e8d6 100644 --- a/src/main/java/dasniko/ozark/react/React.java +++ b/src/main/java/dasniko/ozark/react/React.java @@ -1,6 +1,7 @@ package dasniko.ozark.react; -import javax.script.Invocable; +import jdk.nashorn.api.scripting.ScriptObjectMirror; + import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; @@ -17,17 +18,16 @@ public class React { ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn"); try { nashorn.eval(read("/nashorn-polyfill.js")); - nashorn.eval(read("/META-INF/resources/webjars/react/0.14.8/react.min.js")); - nashorn.eval(read("/js/bookBox.js")); + nashorn.eval(read("/js/app.js")); } catch (ScriptException e) { throw new RuntimeException(e); } return nashorn; }); - public String render(Object object) { + String render(Object object) { try { - Object html = ((Invocable) engineHolder.get()).invokeFunction("renderServer", object); + Object html = ((ScriptObjectMirror) engineHolder.get().get("BookApp")).callMember("renderServer", object); return String.valueOf(html); } catch (Exception e) { throw new IllegalStateException("failed to render react component", e); diff --git a/src/main/resources/META-INF/resources/js/app.js b/src/main/resources/META-INF/resources/js/app.js new file mode 100644 index 0000000..b9f6055 --- /dev/null +++ b/src/main/resources/META-INF/resources/js/app.js @@ -0,0 +1 @@ +// we need this empty file as a reference to /resources/js/app.js, so we can use it from the browser \ No newline at end of file diff --git a/src/main/resources/META-INF/resources/js/bookBox.js b/src/main/resources/META-INF/resources/js/bookBox.js deleted file mode 100644 index fddad82..0000000 --- a/src/main/resources/META-INF/resources/js/bookBox.js +++ /dev/null @@ -1 +0,0 @@ -// we need this empty file as a reference to /resources/js/bookBox.js, so we can use it from the browser \ No newline at end of file diff --git a/src/main/resources/jsx/book.jsx b/src/main/resources/jsx/book.jsx new file mode 100644 index 0000000..c450798 --- /dev/null +++ b/src/main/resources/jsx/book.jsx @@ -0,0 +1,12 @@ +import React from "react"; + +export default class Book extends React.Component { + render() { + return ( +
+

{this.props.author}

+
+
+ ); + } +} diff --git a/src/main/resources/jsx/bookBox.js b/src/main/resources/jsx/bookBox.js deleted file mode 100644 index 9d347fc..0000000 --- a/src/main/resources/jsx/bookBox.js +++ /dev/null @@ -1,116 +0,0 @@ -var BookForm = React.createClass({ - handleSubmit: function (e) { - e.preventDefault(); - var author = this.refs.author.getDOMNode().value.trim(); - var title = this.refs.title.getDOMNode().value.trim(); - if (!author || !title) { - return; - } - this.props.onBookSubmit({author: author, title: title}); - this.refs.author.getDOMNode().value = ''; - this.refs.title.getDOMNode().value = ''; - }, - render: function () { - return ( -
-

Add a new book:

- - - -
- ); - } -}); - -var Book = React.createClass({ - render: function () { - return ( -
-

{this.props.author}

-
-
- ); - } -}); - -var BookList = React.createClass({ - render: function () { - var bookNodes = this.props.data.map(function (book, index) { - return ( - - {book.title} - - ); - }); - return ( -
- {bookNodes} -
- ); - } -}); - -var BookBox = React.createClass({ - handleBookSubmit: function (book) { - var books = this.state.data; - books.push(book); - this.setState({data: books}, function () { - $.ajax({ - url: this.props.url, - contentType: 'application/json', - dataType: 'json', - type: 'POST', - data: JSON.stringify(book), - success: function (data) { - this.setState({data: data}); - }.bind(this), - error: function (xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }); - }, - loadBooksFromServer: function () { - $.ajax({ - url: this.props.url, - dataType: 'json', - success: function (data) { - this.setState({data: data}); - }.bind(this), - error: function (xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }, - getInitialState: function () { - return {data: this.props.data}; - }, - componentDidMount: function () { - this.loadBooksFromServer(); - setInterval(this.loadBooksFromServer, this.props.pollInterval); - }, - render: function () { - return ( -
-

Best Books ever!

- - -
- ); - } -}); - -var renderClient = function (books) { - var data = books || []; - React.render( - , - document.getElementById("content") - ); -}; - -var renderServer = function (books) { - var data = Java.from(books); - return React.renderToString( - - ); -}; \ No newline at end of file diff --git a/src/main/resources/jsx/bookBox.jsx b/src/main/resources/jsx/bookBox.jsx new file mode 100644 index 0000000..f63552f --- /dev/null +++ b/src/main/resources/jsx/bookBox.jsx @@ -0,0 +1,60 @@ +import React from "react"; +import BookForm from "./bookForm"; +import BookList from "./bookList"; + +export default class BookBox extends React.Component { + constructor(props) { + super(props); + this.handleBookSubmit = this.handleBookSubmit.bind(this); + this.loadBooksFromServer = this.loadBooksFromServer.bind(this); + this.state = {data: props.data}; + } + + handleBookSubmit(book) { + var books = this.state.data; + books.push(book); + this.setState({data: books}, function () { + $.ajax({ + url: this.props.url, + contentType: 'application/json', + dataType: 'json', + type: 'POST', + data: JSON.stringify(book), + success: function (data) { + this.setState({data: data}); + }.bind(this), + error: function (xhr, status, err) { + console.error(this.props.url, status, err.toString()); + }.bind(this) + }); + }); + } + + loadBooksFromServer() { + $.ajax({ + url: this.props.url, + dataType: 'json', + success: function (data) { + this.setState({data: data}); + }.bind(this), + error: function (xhr, status, err) { + console.error(this.props.url, status, err.toString()); + }.bind(this) + }); + } + + componentDidMount() { + this.loadBooksFromServer(); + setInterval(this.loadBooksFromServer, this.props.pollInterval); + } + + render() { + return ( +
+

Best Books ever!

+ + +
+ ); + } +} diff --git a/src/main/resources/jsx/bookForm.jsx b/src/main/resources/jsx/bookForm.jsx new file mode 100644 index 0000000..d5ecfae --- /dev/null +++ b/src/main/resources/jsx/bookForm.jsx @@ -0,0 +1,31 @@ +import React from "react"; + +export default class BookForm extends React.Component { + constructor(props) { + super(props); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleSubmit(e) { + e.preventDefault(); + var author = this.refs.author.value.trim(); + var title = this.refs.title.value.trim(); + if (!author || !title) { + return; + } + this.props.onBookSubmit({author: author, title: title}); + this.refs.author.value = ''; + this.refs.title.value = ''; + } + + render() { + return ( +
+

Add a new book:

+ + + +
+ ); + } +} diff --git a/src/main/resources/jsx/bookList.jsx b/src/main/resources/jsx/bookList.jsx new file mode 100644 index 0000000..45b17f0 --- /dev/null +++ b/src/main/resources/jsx/bookList.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import Book from "./book"; + +export default class BookList extends React.Component { + render() { + var bookNodes = this.props.data.map(function (book, index) { + return ( + + {book.title} + + ); + }); + return ( +
+ {bookNodes} +
+ ); + } +} diff --git a/src/main/resources/jsx/index.jsx b/src/main/resources/jsx/index.jsx new file mode 100644 index 0000000..0350ae0 --- /dev/null +++ b/src/main/resources/jsx/index.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import ReactDOMServer from "react-dom/server"; +import BookBox from "./bookBox"; + +let renderClient = function (books) { + var data = books || []; + if (typeof window !== 'undefined') { + ReactDOM.render( + , + document.getElementById("content") + ); + } +}; + +let renderServer = function (books) { + var data = Java.from(books); + return ReactDOMServer.renderToString( + + ); +}; + +module.exports = { + renderClient: renderClient, + renderServer: renderServer +}; diff --git a/src/main/resources/nashorn-polyfill.js b/src/main/resources/nashorn-polyfill.js index 11568c5..a85f79e 100644 --- a/src/main/resources/nashorn-polyfill.js +++ b/src/main/resources/nashorn-polyfill.js @@ -1,7 +1,9 @@ var global = this; var process = {env:{}}; -var console = {}; -console.debug = print; -console.warn = print; -console.log = print; +var console = { + debug: print, + warn: print, + error: print, + log: print +}; diff --git a/src/main/webapp/WEB-INF/views/react.jsp b/src/main/webapp/WEB-INF/views/react.jsp index f489bef..4dab337 100644 --- a/src/main/webapp/WEB-INF/views/react.jsp +++ b/src/main/webapp/WEB-INF/views/react.jsp @@ -8,19 +8,17 @@ ReactJS Bookstore with Ozark - -
${content}
- + diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..aa5d515 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,26 @@ +var path = require('path'); +var ROOT = path.resolve(__dirname, 'src/main/resources'); +var SRC = path.resolve(ROOT, 'jsx'); +var DEST = path.resolve(ROOT, 'js'); + +module.exports = { + entry: SRC, + resolve: { + extensions: ['', '.js', '.jsx' ] + }, + output: { + path: DEST, + filename: 'app.js', + publicPath: '/js/', + library: "BookApp" + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + loaders: ['babel'], + include: SRC + } + ] + } +}; \ No newline at end of file From cde450c9d2862538b830605eccec3a56729d6f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 18:14:29 +0200 Subject: [PATCH 03/11] replaced bootstrap with materializecss --- build.gradle | 2 +- src/main/resources/jsx/book.jsx | 5 +++-- src/main/resources/jsx/bookBox.jsx | 2 +- src/main/resources/jsx/bookForm.jsx | 14 ++++++++------ src/main/resources/jsx/bookList.jsx | 6 ++---- src/main/webapp/WEB-INF/views/react.jsp | 4 +++- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 8c13a44..97f949c 100644 --- a/build.gradle +++ b/build.gradle @@ -27,5 +27,5 @@ dependencies { compile 'org.jboss.weld.servlet:weld-servlet-core:3.0.0.Alpha12' // some needed webjars (frontend-stuff) compile 'org.webjars:jquery:2.2.3' - compile 'org.webjars:bootstrap:3.3.6' + compile 'org.webjars:materializecss:0.97.5' } diff --git a/src/main/resources/jsx/book.jsx b/src/main/resources/jsx/book.jsx index c450798..84967c3 100644 --- a/src/main/resources/jsx/book.jsx +++ b/src/main/resources/jsx/book.jsx @@ -4,8 +4,9 @@ export default class Book extends React.Component { render() { return (
-

{this.props.author}

-
+
+

{this.props.author}

+
{this.props.title}
); } diff --git a/src/main/resources/jsx/bookBox.jsx b/src/main/resources/jsx/bookBox.jsx index f63552f..d67739a 100644 --- a/src/main/resources/jsx/bookBox.jsx +++ b/src/main/resources/jsx/bookBox.jsx @@ -51,7 +51,7 @@ export default class BookBox extends React.Component { render() { return (
-

Best Books ever!

+

Best Books ever!

diff --git a/src/main/resources/jsx/bookForm.jsx b/src/main/resources/jsx/bookForm.jsx index d5ecfae..33f406e 100644 --- a/src/main/resources/jsx/bookForm.jsx +++ b/src/main/resources/jsx/bookForm.jsx @@ -20,12 +20,14 @@ export default class BookForm extends React.Component { render() { return ( -
-

Add a new book:

- - - -
+
+
+
Add a new book:
+ + + +
+
); } } diff --git a/src/main/resources/jsx/bookList.jsx b/src/main/resources/jsx/bookList.jsx index 45b17f0..4b4c39c 100644 --- a/src/main/resources/jsx/bookList.jsx +++ b/src/main/resources/jsx/bookList.jsx @@ -3,11 +3,9 @@ import Book from "./book"; export default class BookList extends React.Component { render() { - var bookNodes = this.props.data.map(function (book, index) { + let bookNodes = this.props.data.map(function (book, index) { return ( - - {book.title} - + ); }); return ( diff --git a/src/main/webapp/WEB-INF/views/react.jsp b/src/main/webapp/WEB-INF/views/react.jsp index 4dab337..a8fa64c 100644 --- a/src/main/webapp/WEB-INF/views/react.jsp +++ b/src/main/webapp/WEB-INF/views/react.jsp @@ -8,8 +8,10 @@ ReactJS Bookstore with Ozark + + - + From 470880f951f5a69b9a1d83aaf8002cdad9ba991d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 19:35:30 +0200 Subject: [PATCH 04/11] use the moowork node gradle plugin to do the npm stuff with gradle --- build.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build.gradle b/build.gradle index 97f949c..c03b7f9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'com.moowork.node' version '0.12' +} + apply plugin: 'war' sourceCompatibility = JavaVersion.VERSION_1_8 @@ -29,3 +33,9 @@ dependencies { compile 'org.webjars:jquery:2.2.3' compile 'org.webjars:materializecss:0.97.5' } + +npm_run { + args = ['webpack'] +} + +processResources.dependsOn('npm_install', 'npm_run') From f98f4d435870c7afe13d7f8feeed2b9bad0f9017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 19:35:30 +0200 Subject: [PATCH 05/11] use the moowork node gradle plugin to do the npm stuff with gradle --- build.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build.gradle b/build.gradle index 97f949c..b45b56c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'com.moowork.node' version '0.12' +} + apply plugin: 'war' sourceCompatibility = JavaVersion.VERSION_1_8 @@ -29,3 +33,9 @@ dependencies { compile 'org.webjars:jquery:2.2.3' compile 'org.webjars:materializecss:0.97.5' } + +npm_run { + args = ['webpack'] +} + +processResources.dependsOn('npm_run') From 79679b64177b0e5d933d303dae15e21000c234ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 20:09:00 +0200 Subject: [PATCH 06/11] more materializecss --- src/main/resources/jsx/book.jsx | 2 +- src/main/resources/jsx/bookBox.jsx | 2 +- src/main/resources/jsx/bookForm.jsx | 16 ++++++++++++---- src/main/resources/jsx/bookList.jsx | 2 +- src/main/webapp/WEB-INF/views/react.jsp | 1 + 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/resources/jsx/book.jsx b/src/main/resources/jsx/book.jsx index 84967c3..7724d0d 100644 --- a/src/main/resources/jsx/book.jsx +++ b/src/main/resources/jsx/book.jsx @@ -4,9 +4,9 @@ export default class Book extends React.Component { render() { return (
-

{this.props.author}

{this.props.title}
+
); } diff --git a/src/main/resources/jsx/bookBox.jsx b/src/main/resources/jsx/bookBox.jsx index d67739a..ce872ed 100644 --- a/src/main/resources/jsx/bookBox.jsx +++ b/src/main/resources/jsx/bookBox.jsx @@ -50,7 +50,7 @@ export default class BookBox extends React.Component { render() { return ( -
+

Best Books ever!

diff --git a/src/main/resources/jsx/bookForm.jsx b/src/main/resources/jsx/bookForm.jsx index 33f406e..bb7570d 100644 --- a/src/main/resources/jsx/bookForm.jsx +++ b/src/main/resources/jsx/bookForm.jsx @@ -20,12 +20,20 @@ export default class BookForm extends React.Component { render() { return ( -
+
Add a new book:
- - - +
+ + +
+
+ + +
+
); diff --git a/src/main/resources/jsx/bookList.jsx b/src/main/resources/jsx/bookList.jsx index 4b4c39c..5c07d8d 100644 --- a/src/main/resources/jsx/bookList.jsx +++ b/src/main/resources/jsx/bookList.jsx @@ -9,7 +9,7 @@ export default class BookList extends React.Component { ); }); return ( -
+
{bookNodes}
); diff --git a/src/main/webapp/WEB-INF/views/react.jsp b/src/main/webapp/WEB-INF/views/react.jsp index a8fa64c..e0628ab 100644 --- a/src/main/webapp/WEB-INF/views/react.jsp +++ b/src/main/webapp/WEB-INF/views/react.jsp @@ -8,6 +8,7 @@ ReactJS Bookstore with Ozark + From 91b4b008a0f99245b054b6bdc7ab87b4d9d5ad15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 20:09:13 +0200 Subject: [PATCH 07/11] upgrade to latest react version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5db954b..99ac503 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "A ViewEngine for ReactJS templates for the Java EE MVC 1.0 reference implementation Ozark.", "main": "index.js", "dependencies": { - "react": "^0.14.8", - "react-dom": "^0.14.8" + "react": "^15.0.1", + "react-dom": "^15.0.1" }, "devDependencies": { "babel-core": "^5.8.38", From b3884852d72db512e076abe9a5c374bddf09c3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sat, 16 Apr 2016 21:06:43 +0200 Subject: [PATCH 08/11] readded accidentially removed task --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b45b56c..c03b7f9 100644 --- a/build.gradle +++ b/build.gradle @@ -38,4 +38,4 @@ npm_run { args = ['webpack'] } -processResources.dependsOn('npm_run') +processResources.dependsOn('npm_install', 'npm_run') From 95d18f8d3f44b243473d9aa3dc48ab81664b4de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sun, 17 Apr 2016 13:05:18 +0200 Subject: [PATCH 09/11] let's start to include view engine and example project again into one --- src/main/java/dasniko/ozark/react/React.java | 57 ++++++++++++++++--- .../dasniko/ozark/react/ReactController.java | 2 +- .../dasniko/ozark/react/ReactViewEngine.java | 34 ++++++++--- 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/main/java/dasniko/ozark/react/React.java b/src/main/java/dasniko/ozark/react/React.java index af8e8d6..b38c97f 100644 --- a/src/main/java/dasniko/ozark/react/React.java +++ b/src/main/java/dasniko/ozark/react/React.java @@ -2,32 +2,73 @@ import jdk.nashorn.api.scripting.ScriptObjectMirror; +import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; /** * @author Niko Köbler, http://www.n-k.de, @dasniko */ public class React { + private static final String DEFAULT_JS_RESOURCE_PATH = "/js"; + private static final String RESOURCE_PATH = System.getProperty("mvc.reactjs.resourcePath", DEFAULT_JS_RESOURCE_PATH); + private ThreadLocal engineHolder = ThreadLocal.withInitial(() -> { ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn"); - try { - nashorn.eval(read("/nashorn-polyfill.js")); - nashorn.eval(read("/js/app.js")); - } catch (ScriptException e) { - throw new RuntimeException(e); - } + + // initial basically needed files for dealing with react + List jsResources = new ArrayList<>(); + jsResources.add("nashorn-polyfill.js"); + + // add all resources from custom application + jsResources.addAll(getAllFilesFromResourcePath()); + // and load/evaluate them within nashorn + jsResources.forEach(p -> { + try { + nashorn.eval(read(p)); + } catch (ScriptException e) { + throw new RuntimeException(e); + } + }); + return nashorn; }); - String render(Object object) { + private List getAllFilesFromResourcePath() { + URL url = this.getClass().getClassLoader().getResource(RESOURCE_PATH); + if (url != null && url.getProtocol().equals("file")) { + try { + return Arrays.asList(new File(url.toURI()).list()).stream().map(p -> RESOURCE_PATH + "/" + p).collect(Collectors.toList()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } else { + // TODO do something + return new ArrayList<>(); + } + } + + String render(String function, Object object) { try { - Object html = ((ScriptObjectMirror) engineHolder.get().get("BookApp")).callMember("renderServer", object); + Object html; + if (function.contains(".")) { + String[] parts = function.split("\\."); + html = ((ScriptObjectMirror) engineHolder.get().get(parts[0])).callMember(parts[1], object); + } else { + html = ((Invocable) engineHolder.get()).invokeFunction(function, object); + } return String.valueOf(html); } catch (Exception e) { throw new IllegalStateException("failed to render react component", e); diff --git a/src/main/java/dasniko/ozark/react/ReactController.java b/src/main/java/dasniko/ozark/react/ReactController.java index 185cfe6..1f52151 100644 --- a/src/main/java/dasniko/ozark/react/ReactController.java +++ b/src/main/java/dasniko/ozark/react/ReactController.java @@ -24,7 +24,7 @@ public class ReactController { public String index() throws Exception { List books = service.getBooks(); models.put("data", books); - return "react:react.jsp"; + return "react:react.jsp?function=BookApp.renderServer"; } // @GET diff --git a/src/main/java/dasniko/ozark/react/ReactViewEngine.java b/src/main/java/dasniko/ozark/react/ReactViewEngine.java index b6b9f84..b46e15c 100644 --- a/src/main/java/dasniko/ozark/react/ReactViewEngine.java +++ b/src/main/java/dasniko/ozark/react/ReactViewEngine.java @@ -13,6 +13,9 @@ import javax.mvc.engine.ViewEngineException; import javax.servlet.ServletException; import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; /** * @author Niko Köbler, http://www.n-k.de, @dasniko @@ -25,7 +28,7 @@ public class ReactViewEngine extends ServletViewEngine { @Inject React react; - ObjectMapper mapper = new ObjectMapper(); + private ObjectMapper mapper = new ObjectMapper(); @Override public boolean supports(String view) { @@ -34,20 +37,32 @@ public boolean supports(String view) { @Override public void processView(ViewEngineContext context) throws ViewEngineException { - // parse view and extract the actual template - String template = context.getView().substring(viewPrefix.length()); + // parse view and extract the actual template and the react.js function to call + String view = context.getView(); + String[] viewParts = view.substring(viewPrefix.length()).split("\\?"); + if (viewParts.length < 2) { + throw new ViewEngineException("You have to specify at least a view and a function (e.g. react:view.jsp?function=renderOnServer)!"); + } + + String template = viewParts[0]; + + Map params = parseQueryString(viewParts[1]); + String function = params.get("function"); + + String dataKey = params.getOrDefault("data", "data"); + String contentKey = params.getOrDefault("content", "content"); // get "data" from model Models models = context.getModels(); - Object data = models.get("data"); + Object data = models.get(dataKey); - // call js function on data to generate html - String content = react.render(data); + // call given js function on data + String content = react.render(function, data); // and put results as string in model - models.put("content", content); + models.put(contentKey, content); try { - models.put("data", mapper.writeValueAsString(data)); + models.put(dataKey, mapper.writeValueAsString(data)); } catch (JsonProcessingException e) { throw new ViewEngineException(e); } @@ -64,4 +79,7 @@ public void processView(ViewEngineContext context) throws ViewEngineException { } } + private Map parseQueryString(final String query) { + return Arrays.asList(query.split("&")).stream().map(p -> p.split("=")).collect(Collectors.toMap(s -> s[0], s -> s[1])); + } } From 3b62454c4806fa04d0aaa465fc2e629eb448f301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Ko=CC=88bler?= Date: Sun, 17 Apr 2016 13:05:52 +0200 Subject: [PATCH 10/11] adjust attribute names to react compatibility --- src/main/resources/jsx/bookForm.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/jsx/bookForm.jsx b/src/main/resources/jsx/bookForm.jsx index bb7570d..a639d2b 100644 --- a/src/main/resources/jsx/bookForm.jsx +++ b/src/main/resources/jsx/bookForm.jsx @@ -25,11 +25,11 @@ export default class BookForm extends React.Component {
Add a new book:
- +
- +