Making the archetype much simpler - one model class, one HTML page is easier to change into something real
1.1 --- a/ko-archetype/src/main/resources/archetype-resources/pom.xml Sat Sep 07 20:31:23 2013 +0200
1.2 +++ b/ko-archetype/src/main/resources/archetype-resources/pom.xml Sun Sep 08 15:14:36 2013 +0200
1.3 @@ -129,4 +129,28 @@
1.4 <scope>test</scope>
1.5 </dependency>
1.6 </dependencies>
1.7 + <profiles>
1.8 + <profile>
1.9 + <id>jdk18</id>
1.10 + <activation>
1.11 + <jdk>1.8</jdk>
1.12 + </activation>
1.13 + <build>
1.14 + <plugins>
1.15 + <plugin>
1.16 + <groupId>org.apache.maven.plugins</groupId>
1.17 + <artifactId>maven-compiler-plugin</artifactId>
1.18 + <version>2.3.2</version>
1.19 + <configuration>
1.20 + <source>1.7</source>
1.21 + <target>1.8</target>
1.22 + <compilerArguments>
1.23 + <profile>compact1</profile>
1.24 + </compilerArguments>
1.25 + </configuration>
1.26 + </plugin>
1.27 + </plugins>
1.28 + </build>
1.29 + </profile>
1.30 + </profiles>
1.31 </project>
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/java/DataModel.java Sun Sep 08 15:14:36 2013 +0200
2.3 @@ -0,0 +1,31 @@
2.4 +package ${package};
2.5 +
2.6 +import net.java.html.json.ComputedProperty;
2.7 +import net.java.html.json.Function;
2.8 +import net.java.html.json.Model;
2.9 +import net.java.html.json.Property;
2.10 +
2.11 +/** Model annotation generates class Data with
2.12 + * one message property, boolean property and read only words property
2.13 + */
2.14 +@Model(className = "Data", properties = {
2.15 + @Property(name = "message", type = String.class),
2.16 + @Property(name = "on", type = boolean.class)
2.17 +})
2.18 +final class DataModel {
2.19 + @ComputedProperty static java.util.List<String> words(String message) {
2.20 + String[] arr = new String[6];
2.21 + String[] words = message == null ? new String[0] : message.split(" ", 6);
2.22 + for (int i = 0; i < 6; i++) {
2.23 + arr[i] = words.length > i ? words[i] : "!";
2.24 + }
2.25 + return java.util.Arrays.asList(arr);
2.26 + }
2.27 +
2.28 + @Function static void turnOn(Data model) {
2.29 + model.setOn(true);
2.30 + }
2.31 + @Function static void turnOff(Data model) {
2.32 + model.setOn(false);
2.33 + }
2.34 +}
3.1 --- a/ko-archetype/src/main/resources/archetype-resources/src/main/java/Main.java Sat Sep 07 20:31:23 2013 +0200
3.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/java/Main.java Sun Sep 08 15:14:36 2013 +0200
3.3 @@ -9,9 +9,19 @@
3.4 public static void main(String... args) throws Exception {
3.5 BrowserBuilder.newBrowser().
3.6 loadPage("pages/index.html").
3.7 - loadClass(TwitterClient.class).
3.8 - invoke("initialize", args).
3.9 + loadClass(Main.class).
3.10 + invoke("onPageLoad", args).
3.11 showAndWait();
3.12 System.exit(0);
3.13 }
3.14 +
3.15 + /**
3.16 + * Called when the page is ready.
3.17 + */
3.18 + public static void onPageLoad(String... args) throws Exception {
3.19 + Data d = new Data();
3.20 + d.setMessage("Hello World from HTML and Java!");
3.21 + d.applyBindings();
3.22 + }
3.23 +
3.24 }
4.1 --- a/ko-archetype/src/main/resources/archetype-resources/src/main/java/TwitterClient.java Sat Sep 07 20:31:23 2013 +0200
4.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
4.3 @@ -1,178 +0,0 @@
4.4 -package ${package};
4.5 -
4.6 -import java.util.Arrays;
4.7 -import java.util.List;
4.8 -import net.java.html.json.ComputedProperty;
4.9 -import net.java.html.json.Function;
4.10 -import net.java.html.json.Model;
4.11 -import net.java.html.json.OnPropertyChange;
4.12 -import net.java.html.json.OnReceive;
4.13 -import net.java.html.json.Property;
4.14 -
4.15 -@Model(className="TwitterModel", properties={
4.16 - @Property(name="savedLists", type=Tweeters.class, array = true),
4.17 - @Property(name="activeTweetersName", type=String.class),
4.18 - @Property(name="activeTweeters", type=String.class, array = true),
4.19 - @Property(name="userNameToAdd", type=String.class),
4.20 - @Property(name="loading", type=boolean.class),
4.21 - @Property(name="currentTweets", type=Tweet.class, array = true)
4.22 -})
4.23 -public class TwitterClient {
4.24 - @Model(className = "Tweeters", properties = {
4.25 - @Property(name="name", type = String.class),
4.26 - @Property(name="userNames", type = String.class, array = true)
4.27 - })
4.28 - static class Twttrs {
4.29 - }
4.30 - @Model(className = "Tweet", properties = {
4.31 - @Property(name = "from_user", type = String.class),
4.32 - @Property(name = "from_user_id", type = int.class),
4.33 - @Property(name = "profile_image_url", type = String.class),
4.34 - @Property(name = "text", type = String.class),
4.35 - @Property(name = "created_at", type = String.class),
4.36 - })
4.37 - static final class Twt {
4.38 - @ComputedProperty static String html(String text) {
4.39 - StringBuilder sb = new StringBuilder(320);
4.40 - for (int pos = 0;;) {
4.41 - int http = text.indexOf("http", pos);
4.42 - if (http == -1) {
4.43 - sb.append(text.substring(pos));
4.44 - return sb.toString();
4.45 - }
4.46 - int spc = text.indexOf(' ', http);
4.47 - if (spc == -1) {
4.48 - spc = text.length();
4.49 - }
4.50 - sb.append(text.substring(pos, http));
4.51 - String url = text.substring(http, spc);
4.52 - sb.append("<a href='").append(url).append("'>").append(url).append("</a>");
4.53 - pos = spc;
4.54 - }
4.55 - }
4.56 -
4.57 - @ComputedProperty static String userUrl(String from_user) {
4.58 - return "http://twitter.com/" + from_user;
4.59 - }
4.60 - }
4.61 - @Model(className = "TwitterQuery", properties = {
4.62 - @Property(array = true, name = "results", type = Twt.class)
4.63 - })
4.64 - public static final class TwttrQr {
4.65 - }
4.66 -
4.67 - @OnReceive(url="{root}/search.json?{query}&callback={me}", jsonp="me")
4.68 - static void queryTweets(TwitterModel page, TwitterQuery q) {
4.69 - page.getCurrentTweets().clear();
4.70 - page.getCurrentTweets().addAll(q.getResults());
4.71 - page.setLoading(false);
4.72 - }
4.73 -
4.74 - @OnPropertyChange("activeTweetersName")
4.75 - static void changeTweetersList(TwitterModel model) {
4.76 - Tweeters people = findByName(model.getSavedLists(), model.getActiveTweetersName());
4.77 - model.getActiveTweeters().clear();
4.78 - model.getActiveTweeters().addAll(people.getUserNames());
4.79 - }
4.80 -
4.81 - @OnPropertyChange({ "activeTweeters", "activeTweetersCount" })
4.82 - static void refreshTweets(TwitterModel model) {
4.83 - StringBuilder sb = new StringBuilder();
4.84 - sb.append("rpp=25&q=");
4.85 - String sep = "";
4.86 - for (String p : model.getActiveTweeters()) {
4.87 - sb.append(sep);
4.88 - sb.append("from:");
4.89 - sb.append(p);
4.90 - sep = " OR ";
4.91 - }
4.92 - model.setLoading(true);
4.93 - model.queryTweets("http://search.twitter.com", sb.toString());
4.94 - }
4.95 -
4.96 - public static void initialize(String... args) {
4.97 - final TwitterModel model = new TwitterModel();
4.98 - final List<Tweeters> svdLst = model.getSavedLists();
4.99 - svdLst.add(newTweeters("API Design", "JaroslavTulach"));
4.100 - svdLst.add(newTweeters("Celebrities", "JohnCleese", "MCHammer", "StephenFry", "algore", "StevenSanderson"));
4.101 - svdLst.add(newTweeters("Microsoft people", "BillGates", "shanselman", "ScottGu"));
4.102 - svdLst.add(newTweeters("NetBeans", "GeertjanW","monacotoni", "NetBeans", "petrjiricka"));
4.103 - svdLst.add(newTweeters("Tech pundits", "Scobleizer", "LeoLaporte", "techcrunch", "BoingBoing", "timoreilly", "codinghorror"));
4.104 -
4.105 - model.setActiveTweetersName("NetBeans");
4.106 -
4.107 - model.applyBindings();
4.108 - }
4.109 -
4.110 - @ComputedProperty
4.111 - static boolean hasUnsavedChanges(List<String> activeTweeters, List<Tweeters> savedLists, String activeTweetersName) {
4.112 - Tweeters tw = findByName(savedLists, activeTweetersName);
4.113 - if (activeTweeters == null) {
4.114 - return false;
4.115 - }
4.116 - return !tw.getUserNames().equals(activeTweeters);
4.117 - }
4.118 -
4.119 - @ComputedProperty
4.120 - static int activeTweetersCount(List<String> activeTweeters) {
4.121 - return activeTweeters.size();
4.122 - }
4.123 -
4.124 - @ComputedProperty
4.125 - static boolean userNameToAddIsValid(
4.126 - String userNameToAdd, String activeTweetersName, List<Tweeters> savedLists, List<String> activeTweeters
4.127 - ) {
4.128 - return userNameToAdd != null &&
4.129 - userNameToAdd.matches("[a-zA-Z0-9_]{1,15}") &&
4.130 - !activeTweeters.contains(userNameToAdd);
4.131 - }
4.132 -
4.133 - @Function
4.134 - static void deleteList(TwitterModel model) {
4.135 - final List<Tweeters> sl = model.getSavedLists();
4.136 - sl.remove(findByName(sl, model.getActiveTweetersName()));
4.137 - if (sl.isEmpty()) {
4.138 - final Tweeters t = new Tweeters();
4.139 - t.setName("New");
4.140 - sl.add(t);
4.141 - }
4.142 - model.setActiveTweetersName(sl.get(0).getName());
4.143 - }
4.144 -
4.145 - @Function
4.146 - static void saveChanges(TwitterModel model) {
4.147 - Tweeters t = findByName(model.getSavedLists(), model.getActiveTweetersName());
4.148 - int indx = model.getSavedLists().indexOf(t);
4.149 - if (indx != -1) {
4.150 - t.setName(model.getActiveTweetersName());
4.151 - t.getUserNames().clear();
4.152 - t.getUserNames().addAll(model.getActiveTweeters());
4.153 - }
4.154 - }
4.155 -
4.156 - @Function
4.157 - static void addUser(TwitterModel model) {
4.158 - String n = model.getUserNameToAdd();
4.159 - model.getActiveTweeters().add(n);
4.160 - }
4.161 - @Function
4.162 - static void removeUser(String data, TwitterModel model) {
4.163 - model.getActiveTweeters().remove(data);
4.164 - }
4.165 -
4.166 - private static Tweeters findByName(List<Tweeters> list, String name) {
4.167 - for (Tweeters l : list) {
4.168 - if (l.getName() != null && l.getName().equals(name)) {
4.169 - return l;
4.170 - }
4.171 - }
4.172 - return list.isEmpty() ? new Tweeters() : list.get(0);
4.173 - }
4.174 -
4.175 - private static Tweeters newTweeters(String listName, String... userNames) {
4.176 - Tweeters t = new Tweeters();
4.177 - t.setName(listName);
4.178 - t.getUserNames().addAll(Arrays.asList(userNames));
4.179 - return t;
4.180 - }
4.181 -}
5.1 --- a/ko-archetype/src/main/resources/archetype-resources/src/main/webapp/pages/index.html Sat Sep 07 20:31:23 2013 +0200
5.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/webapp/pages/index.html Sun Sep 08 15:14:36 2013 +0200
5.3 @@ -1,79 +1,58 @@
5.4 -<?xml version="1.0" encoding="UTF-8"?>
5.5 +<!DOCTYPE html>
5.6 +<html>
5.7 + <head>
5.8 + <title></title>
5.9 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5.10
5.11 -<!--
5.12 - Copied from knockout.js Twitter example:
5.13 - http://knockoutjs.com/examples/twitter.html
5.14 --->
5.15 + <style type="text/css">
5.16 + @-webkit-keyframes spin {
5.17 + 0% { -webkit-transform: rotate(0deg); }
5.18 + 100% { -webkit-transform: rotate(360deg); }
5.19 + }
5.20
5.21 -<!DOCTYPE html>
5.22 -<html xmlns="http://www.w3.org/1999/xhtml">
5.23 - <head>
5.24 - <title>Knockout in Java Twitter</title>
5.25 + .rotate {
5.26 + -webkit-animation-name: spin;
5.27 + -webkit-animation-duration: 3s;
5.28 + -webkit-animation-iteration-count: infinite;
5.29 + -webkit-animation-direction: alternate;
5.30 + }
5.31 +
5.32 + #scene {
5.33 + position: relative;
5.34 + top: 60px;
5.35 + text-align: center;
5.36 + }
5.37 +
5.38 + #words span {
5.39 + border: 1px solid #ccc;
5.40 + background: rgba(255,255,155,0.8);
5.41 + text-align: center;
5.42 + font-size: 30px;
5.43 + -webkit-box-shadow: inset 0 0 40px rgba(0,0,0,0.4);
5.44 + position: absolute;
5.45 + }
5.46 +
5.47 + #words span:nth-child(1) { left: 45%; top: 0px; }
5.48 + #words span:nth-child(2) { left: 25%; top: 100px; }
5.49 + #words span:nth-child(3) { left: 65%; top: 100px; }
5.50 + #words span:nth-child(4) { left: 10%; top: 200px; }
5.51 + #words span:nth-child(5) { left: 45%; top: 200px; }
5.52 + #words span:nth-child(6) { left: 80%; top: 200px; }
5.53 +
5.54 + </style>
5.55 +
5.56 </head>
5.57 <body>
5.58 - <link href='twitterExample.css' rel='Stylesheet' ></link>
5.59 -
5.60 - <style type='text/css'>
5.61 - .liveExample select { height: 1.7em; }
5.62 - .liveExample button { height: 2em; }
5.63 - </style>
5.64 -
5.65 -
5.66 - <h2>Knockout in Java Twitter</h2>
5.67 -
5.68 - <p>
5.69 - This code is based on original
5.70 - <a href="http://knockoutjs.com/examples/twitter.html">knockout.js
5.71 - Twitter example</a> and
5.72 - uses almost unmodified HTML page. It just changes the model. The model
5.73 - is written in Java language with the help of
5.74 - <a href="http://bck2brwsr.apidesign.org/javadoc/net.java.html.json/">
5.75 - Knockout/Java binding library
5.76 - </a>. The Java source code has about 180 lines and seems more
5.77 - dense and shorter than the original JavaScript model.
5.78 - </p>
5.79 - <p>
5.80 - The project executes in real Java virtual
5.81 - machine and renders using JavaFX's WebView.
5.82 - </p>
5.83 -
5.84 - <div class='liveExample'>
5.85 - <div class='configuration'>
5.86 - <div class='listChooser'>
5.87 - <button data-bind='click: deleteList, enable: activeTweetersName'>Delete</button>
5.88 - <button data-bind='click: saveChanges, enable: hasUnsavedChanges'>Save</button>
5.89 - <select data-bind='options: savedLists, optionsValue: "name", value: activeTweetersName'> </select>
5.90 - </div>
5.91 + <h1>Words Demo</h1>
5.92 + <input data-bind="value: message, valueUpdate: 'afterkeydown'" size="80">
5.93 + <br>
5.94 + <button data-bind="enable: !on(), click: $root.turnOn">Start</button>
5.95 + <button data-bind="enable: on, click: $root.turnOff">Stop</button>
5.96
5.97 - <p>Currently viewing <span data-bind='text: activeTweetersCount'> </span> user(s):</p>
5.98 - <div class='currentUsers' >
5.99 - <ul data-bind='foreach: activeTweeters'>
5.100 - <li>
5.101 - <button data-bind='click: $root.removeUser'>Remove</button>
5.102 - <div data-bind='text: $data'> </div>
5.103 - </li>
5.104 - </ul>
5.105 - </div>
5.106 -
5.107 - <form data-bind='submit: addUser'>
5.108 - <label>Add user:</label>
5.109 - <input data-bind='value: userNameToAdd, valueUpdate: "keyup", css: { invalid: !userNameToAddIsValid() }' />
5.110 - <button data-bind='enable: userNameToAddIsValid' type='submit'>Add</button>
5.111 - </form>
5.112 - </div>
5.113 - <div class='tweets'>
5.114 - <div class='loadingIndicator' data-bind="visible: loading">Loading...</div>
5.115 - <table data-bind='foreach: currentTweets' width='100%'>
5.116 - <tr>
5.117 - <td><img data-bind='attr: { src: profile_image_url }' /></td>
5.118 - <td>
5.119 - <a class='twitterUser' data-bind='attr: { href: userUrl }, text: from_user'> </a>
5.120 - <span data-bind='html: html'> </span>
5.121 - <div class='tweetInfo' data-bind='text: created_at'> </div>
5.122 - </td>
5.123 - </tr>
5.124 - </table>
5.125 - </div>
5.126 + <div id="scene">
5.127 + <span id="words" data-bind="foreach: words">
5.128 + <span data-bind="text: $data, css: { 'rotate' : $root.on } "></span>
5.129 + </span>
5.130 </div>
5.131 </body>
5.132 </html>
6.1 --- a/ko-archetype/src/main/resources/archetype-resources/src/main/webapp/pages/twitterExample.css Sat Sep 07 20:31:23 2013 +0200
6.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
6.3 @@ -1,32 +0,0 @@
6.4 -/*
6.5 - Copied from knockout.js Twitter example:
6.6 - http://knockoutjs.com/examples/twitter.html
6.7 -*/
6.8 -
6.9 -.configuration, .tweets, .tweets td { font-family: Verdana; font-size: 13px; }
6.10 -.configuration { background-color: #DEDEDE; border: 2px solid gray; float:left; height: 40em; width: 40%; padding: 0.5em; border-right-width:0; }
6.11 -.tweets { width: 55%; border: 2px solid gray; height: 40em; overflow: scroll; overflow-x: hidden; background-color: Black; color: White; padding: 0.5em; position: relative; }
6.12 -.tweets table { border-width: 0;}
6.13 -.tweets tr { vertical-align: top; }
6.14 -.tweets td { padding: 0.4em 0.3em 1em 0.4em; border-width: 0; }
6.15 -.tweets img { width: 4em; }
6.16 -.tweetInfo { color: Gray; font-size: 0.9em; }
6.17 -.twitterUser { color: #77AAFF; text-decoration: none; font-size: 1.1em; font-weight: bold; }
6.18 -input.invalid { border: 1px solid red !important; background-color: #FFAAAA !important; }
6.19 -
6.20 -.listChooser select, .listChooser button { vertical-align:top; }
6.21 -.listChooser select { width: 60%; font-size:1.2em; height:1.4em; }
6.22 -.listChooser button { width: 19%; height:1.68em; float:right; }
6.23 -
6.24 -.currentUsers { height: 28em; overflow-y: auto; overflow-x: hidden; }
6.25 -.currentUsers button { float: right; height: 2.5em; margin: 0.1em; padding-left: 1em; padding-right: 1em; }
6.26 -.currentUsers ul, .configuration li { list-style: none; margin: 0; padding: 0 }
6.27 -.currentUsers li { height: 2.4em; font-size: 1.2em; background-color: #A7D0E3; border: 1px solid gray; margin-bottom: 0.3em; -webkit-border-radius: 5px; -moz-border-radius: 5px; -webkit-box-shadow: 0 0.2em 0.5em gray; -moz-box-shadow: 0 0.2em 0.5em gray; }
6.28 -.currentUsers li div { padding: 0.6em; }
6.29 -.currentUsers li:hover { background-color: #EEC; }
6.30 -
6.31 -.configuration form label { width: 25%; display: inline-block; text-align:right; overflow: hidden; }
6.32 -.configuration form input { width:40%; font-size: 1.3em; border:1px solid silver; background-color: White; padding: 0.1em; }
6.33 -.configuration form button { width: 20%; margin-left: 0.3em; height: 2em; }
6.34 -
6.35 -.loadingIndicator { position: absolute; top: 0.1em; left: 0.1em; font: 0.8em Arial; background-color: #229; color: White; padding: 0.2em 0.5em 0.2em 0.5em; }
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/test/java/DataModelTest.java Sun Sep 08 15:14:36 2013 +0200
7.3 @@ -0,0 +1,16 @@
7.4 +package ${package};
7.5 +
7.6 +import static org.testng.Assert.*;
7.7 +import org.testng.annotations.Test;
7.8 +
7.9 +public class DataModelTest {
7.10 + @Test public void areHelloWorldTwoWords() {
7.11 + Data model = new Data();
7.12 + model.setMessage("Hello World!");
7.13 +
7.14 + java.util.List<String> arr = model.getWords();
7.15 + assertEquals(arr.size(), 6, "Six words always");
7.16 + assertEquals("Hello", arr.get(0), "Hello is the first word");
7.17 + assertEquals("World!", arr.get(1), "World is the second word");
7.18 + }
7.19 +}
8.1 --- a/ko-archetype/src/main/resources/archetype-resources/src/test/java/TwitterClientTest.java Sat Sep 07 20:31:23 2013 +0200
8.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
8.3 @@ -1,50 +0,0 @@
8.4 -package ${package};
8.5 -
8.6 -import java.util.List;
8.7 -import net.java.html.BrwsrCtx;
8.8 -import net.java.html.json.Models;
8.9 -import static org.testng.Assert.*;
8.10 -import org.testng.annotations.BeforeMethod;
8.11 -import org.testng.annotations.Test;
8.12 -
8.13 -/** We can unit test the TwitterModel smoothly.
8.14 - */
8.15 -public class TwitterClientTest {
8.16 - private TwitterModel model;
8.17 -
8.18 -
8.19 - @BeforeMethod
8.20 - public void initModel() {
8.21 - model = Models.bind(new TwitterModel(), BrwsrCtx.EMPTY);
8.22 - }
8.23 -
8.24 - @Test public void testIsValidToAdd() {
8.25 - model.setUserNameToAdd("Joe");
8.26 - Tweeters t = Models.bind(new Tweeters(), BrwsrCtx.EMPTY);
8.27 - t.setName("test");
8.28 - model.getSavedLists().add(t);
8.29 - model.setActiveTweetersName("test");
8.30 -
8.31 - assertTrue(model.isUserNameToAddIsValid(), "Joe is OK");
8.32 - TwitterClient.addUser(model);
8.33 - assertFalse(model.isUserNameToAddIsValid(), "Can't add Joe for the 2nd time");
8.34 - assertEquals(t.getUserNames().size(), 0, "Original tweeters list remains empty");
8.35 -
8.36 - List<String> mod = model.getActiveTweeters();
8.37 - assertTrue(model.isHasUnsavedChanges(), "We have modifications");
8.38 - assertEquals(mod.size(), 1, "One element in the list");
8.39 - assertEquals(mod.get(0), "Joe", "Its name is Joe");
8.40 -
8.41 - assertSame(model.getActiveTweeters(), mod, "Editing list is the modified one");
8.42 -
8.43 - TwitterClient.saveChanges(model);
8.44 - assertFalse(model.isHasUnsavedChanges(), "Does not have anything to save");
8.45 -
8.46 - assertSame(model.getActiveTweeters(), mod, "Still editing the old modified one");
8.47 - }
8.48 -
8.49 - @Test public void httpAtTheEnd() {
8.50 - String res = TwitterClient.Twt.html("Ahoj http://kuk");
8.51 - assertEquals(res, "Ahoj <a href='http://kuk'>http://kuk</a>");
8.52 - }
8.53 -}