Feature: React server side rendering

This commit is contained in:
fecaille
2016-03-24 15:25:43 +01:00
parent 1c571ef647
commit 7a2d9de5e1
9 changed files with 140 additions and 17 deletions

View File

@@ -1,8 +1,5 @@
package com.opengroupe.cloud.saas.domain;
import groovy.transform.ToString;
@ToString
public class Comment {
private final Long id;

View File

@@ -1,8 +1,8 @@
package com.opengroupe.cloud.saas.rest;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -10,12 +10,14 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.opengroupe.cloud.saas.domain.Comment;
import com.opengroupe.cloud.saas.service.CommentService;
@RestController
public class CommentController {
private List<Comment> comments = new ArrayList<Comment>();
@Autowired
private CommentService service;
@RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
@@ -23,7 +25,7 @@ public class CommentController {
@RequestMapping(value="/api/comments", method=RequestMethod.GET)
public @ResponseBody List<Comment> comments() {
return comments;
return service.getAll();
}
@RequestMapping(value="/api/comments", method=RequestMethod.POST)
@@ -31,7 +33,8 @@ public class CommentController {
@RequestParam(value="id", required=true) Long id,
@RequestParam(value="author", required=true) String author,
@RequestParam(value="text", required=true) String text) {
comments.add(new Comment(id, author, text));
return comments;
service.add(new Comment(id, author, text));
return service.getAll();
}
}

View File

@@ -0,0 +1,31 @@
package com.opengroupe.cloud.saas.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import com.opengroupe.cloud.saas.domain.Comment;
@Component
public class CommentService {
private List<Comment> comments = new ArrayList<Comment>();
@PostConstruct
void init() {
comments.addAll(Arrays.asList(new Comment(1L, "Pete Hunt", "This is one comment"),
new Comment(2L, "Jordan Walke", "This is *another* comment")));
}
public void add(Comment comment) {
comments.add(comment);
}
public List<Comment> getAll() {
return comments;
}
}

View File

@@ -0,0 +1,50 @@
package com.opengroupe.cloud.saas.util;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.List;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import com.opengroupe.cloud.saas.domain.Comment;
import jdk.nashorn.api.scripting.NashornScriptEngine;
public class React {
private ThreadLocal<NashornScriptEngine> engineHolder = new ThreadLocal<NashornScriptEngine>() {
@Override
protected NashornScriptEngine initialValue() {
NashornScriptEngine nashornScriptEngine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");
try {
nashornScriptEngine.eval(read("static/js/nashorn-polyfill.js"));
nashornScriptEngine.eval(read("META-INF/resources/webjars/react/0.14.7/react.min.js"));
nashornScriptEngine.eval(read("META-INF/resources/webjars/marked/0.3.2/marked.js"));
// nashornScriptEngine.eval(read("classpath:static/js/react-bootstrap.js"));
// nashornScriptEngine.eval(read("classpath:static/js/comments.js"));
nashornScriptEngine.eval(read("static/js/app.js"));
nashornScriptEngine.eval(read("static/js/app.render.js"));
} catch (ScriptException e) {
throw new RuntimeException(e);
}
return nashornScriptEngine;
}
};
public String renderCommentBox(List<Comment> comments) {
try {
Object html = engineHolder.get().invokeFunction("renderServer", comments);
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);
}
}

View File

@@ -1,13 +1,31 @@
package com.opengroupe.cloud.saas.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opengroupe.cloud.saas.domain.Comment;
import com.opengroupe.cloud.saas.service.CommentService;
import com.opengroupe.cloud.saas.util.React;
@Controller
public class ViewController {
@Autowired
private CommentService service;
@Autowired
private ObjectMapper objectMapper;
private React react = new React();
@RequestMapping("/greeting")
public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
@@ -15,7 +33,12 @@ public class ViewController {
}
@RequestMapping("/index")
public String index(Model model) {
public String index(Model model) throws JsonProcessingException {
List<Comment> comments = service.getAll();
String commentBox = react.renderCommentBox(comments);
String data = objectMapper.writeValueAsString(comments);
model.addAttribute("markup", commentBox);
model.addAttribute("data", data);
return "index";
}
}

View File

@@ -59,7 +59,7 @@ var CommentForm = React.createClass({
});
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
return {data: this.props.data || []};
},
loadCommentsFromServer: function() {
$.ajax({

View File

@@ -1,4 +1,14 @@
ReactDOM.render(
<CommentBox url="/api/comments" pollInterval={2000}/>,
document.getElementById('content')
);
var renderClient = function (comments) {
var data = comments || [];
ReactDOM.render(
<CommentBox data={data} url="/api/comments" pollInterval={2000}/>,
document.getElementById('content')
);
};
var renderServer = function (comments) {
var data = Java.from(comments);
return React.renderToString(
<CommentBox data={data} url="/api/comments" pollInterval={2000} />
);
};

View File

@@ -0,0 +1,6 @@
var global = this;
var console = {};
console.debug = print;
console.warn = print;
console.log = print;

View File

@@ -11,7 +11,7 @@
<div th:replace="fragments/header"></div>
<div class="container">
<div id="content"></div>
<div id="content" th:utext="${markup}"></div>
</div>
<div th:replace="fragments/footer"></div>
@@ -20,6 +20,9 @@
<script src="/js/comments.js"></script>
<script src="/js/app.js"></script>
<script src="/js/app.render.js"></script>
<script src="/js/test.js"></script>
<script th:inline="javascript">
var initialData = JSON.parse(/*[[${data}]]*/ '[]');
renderClient(initialData);
</script>
</body>
</html>