Эх сурвалжийг харах

Feature: React server side rendering

fecaille 9 жил өмнө
parent
commit
7a2d9de5e1

+ 0 - 3
src/main/java/com/opengroupe/cloud/saas/domain/Comment.java

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

+ 9 - 6
src/main/java/com/opengroupe/cloud/saas/rest/CommentController.java

@@ -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();
 	}
+	
 }

+ 31 - 0
src/main/java/com/opengroupe/cloud/saas/service/CommentService.java

@@ -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;
+	}
+}

+ 50 - 0
src/main/java/com/opengroupe/cloud/saas/util/React.java

@@ -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);
+    }
+}

+ 24 - 1
src/main/java/com/opengroupe/cloud/saas/web/ViewController.java

@@ -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";
 	}
 }

+ 1 - 1
src/main/resources/static/js/app.jsx

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

+ 14 - 4
src/main/resources/static/js/app.render.jsx

@@ -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} />
+    );
+};

+ 6 - 0
src/main/resources/static/js/nashorn-polyfill.js

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

+ 5 - 2
src/main/resources/templates/index.html

@@ -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>