├── .gitignore ├── vertxui-examples ├── src │ └── main │ │ └── java │ │ ├── .gitignore │ │ └── live │ │ └── connector │ │ └── vertxui │ │ └── samples │ │ ├── client │ │ ├── mvcBootstrap │ │ │ ├── dto │ │ │ │ ├── Grocery.java │ │ │ │ ├── Totals.java │ │ │ │ └── Bills.java │ │ │ ├── ComponentDatePicker.java │ │ │ ├── Store.java │ │ │ ├── Controller.java │ │ │ └── View.java │ │ ├── AllExamplesClient.java │ │ ├── Dto.java │ │ ├── todomvc │ │ │ ├── State.java │ │ │ ├── Model.java │ │ │ ├── Store.java │ │ │ ├── Controller.java │ │ │ └── View.java │ │ ├── energyCalculator │ │ │ ├── components │ │ │ │ ├── Utils.java │ │ │ │ ├── InputNumber.java │ │ │ │ ├── MonthTable.java │ │ │ │ └── ChartJs.java │ │ │ ├── Client.java │ │ │ ├── Shower.java │ │ │ ├── SolarTubes.java │ │ │ ├── Cooking.java │ │ │ ├── SolarPanels.java │ │ │ └── Stove.java │ │ ├── figwheely │ │ │ └── Client.java │ │ ├── testjUnitWithDom │ │ │ ├── StoreNone.java │ │ │ ├── AnotherTest.java │ │ │ └── TestjUnitWithDom.java │ │ ├── helloWorld │ │ │ └── Client.java │ │ ├── helloWorldFluentHtml │ │ │ └── Client.java │ │ ├── chatSockjs │ │ │ └── Client.java │ │ ├── chatWebsocket │ │ │ └── Client.java │ │ ├── chatEventBus │ │ │ └── Client.java │ │ └── testjUnitWithoutDom │ │ │ └── TestjUnitWithoutDom.java │ │ └── server │ │ ├── energyCalculator │ │ └── ExampleEnergyCalculator.java │ │ ├── helloWorld │ │ └── ExampleHelloWorld.java │ │ ├── figwheely │ │ └── ExampleFigWheely.java │ │ ├── helloWorldFluent │ │ └── ExampleHelloWorldFluent.java │ │ ├── chatSockjs │ │ └── ExampleChatSockjs.java │ │ ├── AllExamplesServer.java │ │ ├── chatWebsocket │ │ └── ExampleChatWebsocket.java │ │ ├── todomvc │ │ └── ServerTodoMVC.java │ │ ├── chatEventBus │ │ └── ExampleChatEventbus.java │ │ └── mvcBootstrap │ │ └── ServerBootstrap.java ├── assets │ ├── figwheely │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ └── sample.css │ └── todos │ │ ├── base.css │ │ ├── index.html │ │ └── index.css ├── .gitignore └── pom.xml ├── vertxui-core ├── doc │ ├── old-streamy.zip │ ├── todo.txt │ └── old-teavm.txt ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ └── live │ │ │ └── connector │ │ │ └── vertxui │ │ │ ├── Vertxui.gwt.xml │ │ │ ├── client │ │ │ ├── fluent │ │ │ │ ├── Viewable.java │ │ │ │ ├── ViewOn.java │ │ │ │ ├── ViewOnBoth.java │ │ │ │ ├── ViewOnBase.java │ │ │ │ ├── Att.java │ │ │ │ └── Css.java │ │ │ ├── transport │ │ │ │ ├── SockJS.java │ │ │ │ ├── EventBus.java │ │ │ │ └── Pojofy.java │ │ │ ├── FigWheelyClient.java │ │ │ └── test │ │ │ │ ├── ConsoleTester.java │ │ │ │ ├── VirtualDomSearch.java │ │ │ │ └── TestDOM.java │ │ │ └── server │ │ │ ├── transport │ │ │ └── Pojofy.java │ │ │ └── FigWheelyServer.java │ └── test │ │ └── java │ │ └── live │ │ └── connector │ │ └── vertxui │ │ ├── SuiteCore.java │ │ ├── server │ │ └── NamedStyleTest.java │ │ └── client │ │ ├── FluentInnerRendering.java │ │ └── FluentRenderer.java └── pom.xml ├── .settings └── .gitignore └── empty-project ├── .gitignore ├── build.gradle ├── src ├── main │ └── java │ │ ├── client │ │ ├── app │ │ │ ├── Controller.java │ │ │ └── View.java │ │ └── test │ │ │ └── ViewTestWithDom.java │ │ └── server │ │ └── Server.java └── test │ └── java │ └── client │ └── ViewTestWithoutDom.java ├── settings.gradle └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.git/ 4 | /bin/ 5 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/.gitignore: -------------------------------------------------------------------------------- 1 | /gwtTemp.gwt.xml 2 | -------------------------------------------------------------------------------- /vertxui-core/doc/old-streamy.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nielsbaloe/vertxui/HEAD/vertxui-core/doc/old-streamy.zip -------------------------------------------------------------------------------- /.settings/.gitignore: -------------------------------------------------------------------------------- 1 | /.jsdtscope 2 | /org.eclipse.wst.jsdt.ui.superType.container 3 | /org.eclipse.wst.jsdt.ui.superType.name 4 | -------------------------------------------------------------------------------- /vertxui-examples/assets/figwheely/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nielsbaloe/vertxui/HEAD/vertxui-examples/assets/figwheely/1.jpg -------------------------------------------------------------------------------- /vertxui-examples/assets/figwheely/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nielsbaloe/vertxui/HEAD/vertxui-examples/assets/figwheely/2.jpg -------------------------------------------------------------------------------- /vertxui-examples/assets/todos/base.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nielsbaloe/vertxui/HEAD/vertxui-examples/assets/todos/base.css -------------------------------------------------------------------------------- /vertxui-examples/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | /.vertx/ 4 | /build/ 5 | /gwt-unitCache/ 6 | .project 7 | .classpath 8 | /bin/ 9 | -------------------------------------------------------------------------------- /vertxui-core/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | /.vertx/ 4 | /build/ 5 | /gwt-unitCache/ 6 | .project 7 | .classpath 8 | /bin/ 9 | /settings.xml 10 | -------------------------------------------------------------------------------- /empty-project/.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle/ 2 | /gradle/ 3 | /.settings/ 4 | /.vertx/ 5 | /.idea/ 6 | /bin/ 7 | /build/ 8 | /target/ 9 | .classpath 10 | .project 11 | -------------------------------------------------------------------------------- /empty-project/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | repositories { 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | compile 'live.connector:vertxui-core:1.0' 9 | 10 | testCompile 'junit:junit:4.12' 11 | } 12 | 13 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/Vertxui.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vertxui-core/doc/todo.txt: -------------------------------------------------------------------------------- 1 | ADD AFTER 1.0: 2 | - optimalisation: middle-child-removal 3 | - paginated mvcBoostrap-bills 4 | - integration with Spring Boot (and servlet in general), as requested at beyondjava.de 5 | - more examples: poll, webrtc as app 6 | - more javascript integration examples: jquery mobile, purecss.io, github.com/Dogfalo/materialize , vaadin-grid 7 | 8 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/dto/Grocery.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap.dto; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Grocery { 7 | 8 | public List all; 9 | 10 | public Grocery() { 11 | all = new ArrayList<>(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/dto/Totals.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap.dto; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Totals { 7 | 8 | public Map all; 9 | 10 | public Totals() { 11 | all = new HashMap<>(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /empty-project/src/main/java/client/app/Controller.java: -------------------------------------------------------------------------------- 1 | package client.app; 2 | 3 | import live.connector.vertxui.client.transport.Pojofy; 4 | 5 | public class Controller { 6 | 7 | public static String url = "/ajax"; 8 | 9 | private View view; 10 | 11 | public Controller() { 12 | } 13 | 14 | public void setView(View view) { 15 | this.view = view; 16 | } 17 | 18 | public void doAjax() { 19 | Pojofy.ajax("PUT", url, "from Browser", null, null, view::setResponse); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /vertxui-core/src/test/java/live/connector/vertxui/SuiteCore.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.junit.runners.Suite; 5 | 6 | import live.connector.vertxui.client.FluentInnerRendering; 7 | import live.connector.vertxui.client.FluentRenderer; 8 | import live.connector.vertxui.server.NamedStyleTest; 9 | 10 | @RunWith(Suite.class) 11 | @Suite.SuiteClasses({ FluentRenderer.class, NamedStyleTest.class, FluentInnerRendering.class }) 12 | public class SuiteCore { 13 | } 14 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/fluent/Viewable.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.fluent; 2 | 3 | /** 4 | * A base for all view elements, currently FluentBase for Fluent and ViewOnBase 5 | * for ViewOn and ViewOnBoth 6 | * 7 | * @author Niels Gorisse 8 | * 9 | */ 10 | public interface Viewable { 11 | 12 | public int getCrc(); 13 | 14 | public String getCrcString(); 15 | 16 | public Viewable hide(boolean doit); 17 | 18 | public void isRendered(boolean state); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/AllExamplesClient.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client; 2 | 3 | import com.github.nmorel.gwtjackson.client.ObjectMapper; 4 | import com.google.gwt.core.client.GWT; 5 | 6 | public class AllExamplesClient { 7 | 8 | // Mapper for json-object 9 | public interface DtoMap extends ObjectMapper { 10 | } 11 | 12 | // Mapper for json-object 13 | public final static DtoMap dto = GWT.isClient() ? GWT.create(DtoMap.class) : null; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/Dto.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Dto { 7 | 8 | public String color; 9 | 10 | public Car car = Car.Volvo; 11 | 12 | public List options = new ArrayList<>(); 13 | 14 | public Dto() { // empty constructor needed for serialization 15 | } 16 | 17 | public Dto(String color) { 18 | this.color = color; 19 | } 20 | 21 | public enum Car { 22 | Toyota, Volvo, Honda 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /empty-project/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This settings file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * In a single project build this file can be empty or even removed. 6 | * 7 | * Detailed information about configuring a multi-project build in Gradle can be found 8 | * in the user guide at https://docs.gradle.org/3.3/userguide/multi_project_builds.html 9 | */ 10 | 11 | /* 12 | // To declare projects as part of a multi-project build use the 'include' method 13 | include 'shared' 14 | include 'api' 15 | include 'services:webservice' 16 | */ 17 | 18 | rootProject.name = 'empty-project' 19 | -------------------------------------------------------------------------------- /vertxui-examples/assets/figwheely/sample.css: -------------------------------------------------------------------------------- 1 | /* Change these while looking at the browser and running the sockjs example containing a figwheely. */ 2 | body { 3 | background-image: 4 | url("http://cdn.backgroundhost.com/backgrounds/subtlepatterns/tileable_wood_texture.png") 5 | /* background-image: url("http://cdn.backgroundhost.com/backgrounds/subtlepatterns/rip_jobs.png") 6 | */ 7 | } 8 | 9 | html { 10 | margin-left: 400px; /* change 400 to 40 and save */ 11 | } 12 | 13 | #picture { 14 | width: 20px; 15 | height: 48px; 16 | min-width: 48px; 17 | min-height: 48px; 18 | background-size: 100% 100%; 19 | background-image: url("1.jpg") /* change 1 to 2 and save while figwheely is running */ 20 | } -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/energyCalculator/ExampleEnergyCalculator.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.energyCalculator; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | 5 | import io.vertx.core.AbstractVerticle; 6 | import io.vertx.core.Vertx; 7 | import io.vertx.ext.web.Router; 8 | import live.connector.vertxui.samples.client.energyCalculator.Client; 9 | import live.connector.vertxui.samples.server.AllExamplesServer; 10 | 11 | public class ExampleEnergyCalculator extends AbstractVerticle { 12 | 13 | public static void main(String[] args) { 14 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 15 | } 16 | 17 | @Override 18 | public void start() { 19 | Router router = Router.router(vertx); 20 | AllExamplesServer.start(Client.class, router); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/todomvc/State.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.todomvc; 2 | 3 | /** 4 | * The buttons-state of the application. If extra states arrise, we can put them 5 | * in this class, or create a new state class, or just use a primite and call 6 | * .state() on our ViewOn... objects. 7 | * 8 | */ 9 | public class State { 10 | 11 | private Buttons buttons; 12 | private Model editing; 13 | 14 | public static enum Buttons { 15 | All, Active, Completed; 16 | } 17 | 18 | public State() { 19 | } 20 | 21 | public Buttons getButtons() { 22 | return buttons; 23 | } 24 | 25 | public void setButtons(Buttons buttons) { 26 | this.buttons = buttons; 27 | } 28 | 29 | public void setEditing(Model editing) { 30 | this.editing = editing; 31 | } 32 | 33 | public Model getEditing() { 34 | return editing; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/todomvc/Model.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.todomvc; 2 | 3 | public class Model { 4 | 5 | private String title; 6 | private boolean completed; 7 | private long id; 8 | 9 | public Model() { 10 | } 11 | 12 | public Model(String title, boolean completed) { 13 | this.title = title; 14 | this.completed = false; 15 | this.id = (long) (Math.random() * (double) Long.MAX_VALUE); 16 | } 17 | 18 | public String getTitle() { 19 | return title; 20 | } 21 | 22 | public void setTitle(String title) { 23 | this.title = title; 24 | } 25 | 26 | public boolean isCompleted() { 27 | return completed; 28 | } 29 | 30 | public void setCompleted(boolean completed) { 31 | this.completed = completed; 32 | } 33 | 34 | public long getId() { 35 | return id; 36 | } 37 | 38 | public void setId(long id) { 39 | this.id = id; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /empty-project/src/main/java/client/app/View.java: -------------------------------------------------------------------------------- 1 | package client.app; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | import static live.connector.vertxui.client.fluent.FluentBase.console; 5 | import static live.connector.vertxui.client.fluent.FluentBase.head; 6 | 7 | import com.google.gwt.core.client.EntryPoint; 8 | 9 | import live.connector.vertxui.client.fluent.Css; 10 | 11 | public class View implements EntryPoint { 12 | 13 | @Override 14 | public void onModuleLoad() { 15 | View view = new View(); 16 | Controller controller = new Controller(); 17 | controller.setView(view); 18 | view.start(controller); 19 | } 20 | 21 | public void start(Controller controller) { 22 | head.script("/figwheely.js"); 23 | 24 | console.log("Hi there console"); 25 | body.div(null, "Hi there body").css(Css.fontSize, "330%"); 26 | controller.doAjax(); 27 | } 28 | 29 | public void setResponse(int responseCode, String text) { 30 | body.div(null, "Server said: " + text); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/components/Utils.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator.components; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.google.gwt.i18n.client.NumberFormat; 7 | 8 | public class Utils { 9 | 10 | private static NumberFormat numberFormat = NumberFormat.getFormat("##,###.##"); 11 | 12 | /** 13 | * Helper method to show a number with maximum of two digits and a dot, with 14 | * a comma for each thousand. 15 | */ 16 | public static String format(double value) { 17 | return numberFormat.format(value); 18 | } 19 | 20 | public static String[] getSelectNumbers(double start, double step, double stop) { 21 | List result = new ArrayList(); 22 | for (; start <= stop; start += step) { 23 | result.add(format(start)); 24 | result.add(format(start)); 25 | } 26 | return result.toArray(new String[result.size()]); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/dto/Bills.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap.dto; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | public class Bills { 8 | 9 | public List all; 10 | 11 | public Bills() { 12 | all = new ArrayList<>(); 13 | } 14 | 15 | public static class Bill implements Comparable { 16 | 17 | public int id; 18 | public Name who; 19 | public double amount; 20 | public String what; 21 | public Date date; 22 | 23 | public Bill() { // empty constructor for serialization 24 | } 25 | 26 | public Bill(Name who, double amount, String what, Date date) { 27 | this.who = who; 28 | this.amount = amount; 29 | this.what = what; 30 | this.date = date; 31 | } 32 | 33 | @Override 34 | public int compareTo(Bill o) { 35 | return o.date.compareTo(date); 36 | } 37 | 38 | } 39 | 40 | public enum Name { 41 | Linda, Niels 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/transport/SockJS.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.transport; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import elemental.js.html.JsWebSocket; 6 | import live.connector.vertxui.client.fluent.Fluent; 7 | 8 | /** 9 | * A wrapper around websocket with lots of shockwave-less fallback methods - 10 | * called SockJS. 11 | * 12 | * 13 | * @author ng 14 | * 15 | */ 16 | public class SockJS extends JsWebSocket { 17 | 18 | protected SockJS() { 19 | } 20 | 21 | public final native static SockJS create(String url) /*-{ return new window.top.SockJS(url); }-*/; 22 | 23 | /** 24 | * You have to load "https://cdn.jsdelivr.net/sockjs/1.1.1/sockjs.min.js" first. 25 | * You can do this here with scriptSync() or with EntryPoint::getScripts(). 26 | * 27 | * @param then what to do when the script is loaded 28 | */ 29 | public static void importJs(Consumer then) { 30 | Fluent.head.scriptSync(then, "https://cdn.jsdelivr.net/sockjs/1.1.1/sockjs.min.js"); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /vertxui-core/src/test/java/live/connector/vertxui/server/NamedStyleTest.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import live.connector.vertxui.client.fluent.Att; 8 | import live.connector.vertxui.client.fluent.Css; 9 | 10 | public class NamedStyleTest { 11 | 12 | @Test 13 | public void style() { 14 | Css a = Css.transitionTimingFunction; 15 | String expect = "transition-timing-function"; 16 | assertEquals(Css.valueOfValid(expect), a); 17 | assertEquals(a.nameValid(), expect); 18 | 19 | Css a2 = Css.alignSelf; 20 | String expect2 = "align-self"; 21 | assertEquals(Css.valueOfValid(expect2), a2); 22 | assertEquals(a2.nameValid(), expect2); 23 | 24 | Css a3 = Css.opacity; 25 | String expect3 = "opacity"; 26 | assertEquals(Css.valueOfValid(expect3), a3); 27 | assertEquals(a3.nameValid(), expect3); 28 | } 29 | 30 | @Test 31 | public void attr() { 32 | Att a1 = Att.accept_charset; 33 | String expect1 = "accept-charset"; 34 | assertEquals(a1.nameValid(), expect1); 35 | assertEquals(a1, Att.valueOfValid(expect1)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/helloWorld/ExampleHelloWorld.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.helloWorld; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | 5 | import io.vertx.core.AbstractVerticle; 6 | import io.vertx.core.Vertx; 7 | import io.vertx.ext.web.Router; 8 | import live.connector.vertxui.samples.client.helloWorld.Client; 9 | import live.connector.vertxui.samples.server.AllExamplesServer; 10 | import live.connector.vertxui.server.VertxUI; 11 | 12 | public class ExampleHelloWorld extends AbstractVerticle { 13 | 14 | public static void main(String[] args) { 15 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 16 | } 17 | 18 | @Override 19 | public void start() { 20 | // Wait and do some server stuff for AJAX 21 | Router router = Router.router(vertx); 22 | router.post(Client.url).handler(handle -> { 23 | vertx.setTimer(1000, l -> { 24 | handle.response().putHeader("Content-Type", "text/plain; charset=" + VertxUI.charset); 25 | handle.response().end("Hello, " + handle.request().getHeader("User-Agent")); 26 | }); 27 | }); 28 | 29 | AllExamplesServer.start(Client.class, router); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/figwheely/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.figwheely; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | import static live.connector.vertxui.client.fluent.FluentBase.head; 5 | 6 | import com.google.gwt.core.client.EntryPoint; 7 | 8 | import live.connector.vertxui.client.FigWheelyClient; 9 | import live.connector.vertxui.client.fluent.Fluent; 10 | 11 | public class Client implements EntryPoint { 12 | 13 | /** 14 | * Please, run the server and change some text in the constructor below, 15 | * save, and then look at your browser, press the button or see what 16 | * happens. Don't forget to edit the /sources/sample.css file, save, and 17 | * look at your browser at the same time. Do NOT reload your browser. 18 | */ 19 | 20 | public Client() { 21 | head.script(FigWheelyClient.urlJavascript).stylesheet("/sourcez/sample.css?" + System.currentTimeMillis()); 22 | 23 | body.div().id("picture"); 24 | Fluent button = body.button(null, "button", "Look at the css, and change something WITHOUT reloading."); 25 | button.click((fluent, event) -> { 26 | button.txt("Something else"); 27 | body.button(null, "button", "sdfsdf!"); 28 | }); 29 | } 30 | 31 | @Override 32 | public void onModuleLoad() { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /empty-project/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.yourURL 6 | empty-project 7 | 0.1-SNAPSHOT 8 | 9 | jar 10 | 11 | ${project.groupId}:${project.artifactId} 12 | 13 | 14 | 8 15 | 8 16 | UTF-8 17 | 18 | 19 | 20 | 21 | live.connector 22 | vertxui-core 23 | 1.0 24 | 25 | 26 | junit 27 | junit 28 | 4.12 29 | 30 | 31 | 32 | org.mockito 33 | mockito-all 34 | 1.10.19 35 | test 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/testjUnitWithDom/StoreNone.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.testjUnitWithDom; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | 6 | import live.connector.vertxui.samples.client.mvcBootstrap.Store; 7 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills; 8 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Grocery; 9 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Totals; 10 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Bill; 11 | 12 | public class StoreNone extends Store { 13 | 14 | @Override 15 | public void getTotals(BiConsumer callback) { 16 | callback.accept(200, new Totals()); 17 | } 18 | 19 | @Override 20 | public void getBills(BiConsumer callback) { 21 | callback.accept(200, new Bills()); 22 | } 23 | 24 | @Override 25 | public void getGrocery(BiConsumer callback) { 26 | callback.accept(200, new Grocery()); 27 | } 28 | 29 | @Override 30 | public void deleteGrocery(String value, Consumer revertCallback) { 31 | } 32 | 33 | @Override 34 | public void addGrocery(String text, Consumer revertCallback) { 35 | } 36 | 37 | @Override 38 | public void addBill(Bill bill, Consumer revertCallback) { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/components/InputNumber.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator.components; 2 | 3 | import elemental.events.KeyboardEvent; 4 | import live.connector.vertxui.client.fluent.Att; 5 | import live.connector.vertxui.client.fluent.Css; 6 | import live.connector.vertxui.client.fluent.Fluent; 7 | 8 | /** 9 | * An GUI input html piece which only allows numbers. 10 | * 11 | */ 12 | public class InputNumber extends Fluent { 13 | 14 | public InputNumber() { 15 | super("input", null); 16 | att(Att.type, "text").css(Css.width, "39px").keypress((Fluent ___, KeyboardEvent event) -> { 17 | int code = event.getCharCode(); 18 | if ((code >= 48 && code <= 57) || code == 0 || code == 46) { 19 | return; 20 | } 21 | event.preventDefault(); 22 | }); 23 | } 24 | 25 | /** 26 | * Get the double value out of this input field. 27 | * 28 | * @param fluent 29 | * the input field 30 | * @return a floating point number that has been entered. 31 | */ 32 | public double domValueDouble() { 33 | String value = super.domValue(); 34 | // correct if there is no input at some point, or a dot is the last 35 | // input now 36 | if (value.length() == 0 || value.endsWith(".")) { 37 | value += "0"; 38 | } 39 | return Double.parseDouble(value); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/figwheely/ExampleFigWheely.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.figwheely; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | 5 | import io.vertx.core.AbstractVerticle; 6 | import io.vertx.core.Vertx; 7 | import io.vertx.ext.web.Router; 8 | import live.connector.vertxui.client.FigWheelyClient; 9 | import live.connector.vertxui.samples.client.figwheely.Client; 10 | import live.connector.vertxui.samples.server.AllExamplesServer; 11 | import live.connector.vertxui.server.FigWheelyServer; 12 | 13 | public class ExampleFigWheely extends AbstractVerticle { 14 | 15 | public static void main(String[] args) { 16 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 17 | } 18 | 19 | @Override 20 | public void start() { 21 | // Start figwheely 22 | // Serve the javascript for figwheely (and turn it on too) 23 | Router router = Router.router(vertx); 24 | router.get(FigWheelyClient.urlJavascript).handler(FigWheelyServer.create()); 25 | 26 | // Serve folder assets-figwheely, and notify 27 | // clients of changes if figwheely is started (otherwise it is just a 28 | // normal StaticHandler). 29 | String url = "/sourcez/"; 30 | router.get(url + "*").handler(FigWheelyServer.staticHandler("assets/figwheely", url)); 31 | 32 | AllExamplesServer.start(Client.class, router); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/FigWheelyClient.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client; 2 | 3 | public class FigWheelyClient { 4 | 5 | /** 6 | * Convenient url for figwheely, change before using if you want to change 7 | * it. 8 | */ 9 | public static String urlJavascript = "/figwheely.js"; 10 | 11 | /** 12 | * Send a string to the logging of the server. Note that you need to loead 13 | * the figwheely script with head.scriptSync() in the client/browser 14 | * sourcecode so that FigWheely.js is loaded synchronously. For example: 15 | * 16 | * head.scriptSync(figwheelyLocation);
17 | * .........
18 | * FigWheely.toServer("blabla");
19 | * 20 | * @param message 21 | * the message which will be distributed to all connected 22 | * browsers. 23 | */ 24 | public static final native void toServer(String message)/*-{ 25 | if (window.top._fig) { 26 | if (window.top._fig.readyState === window.top._fig.OPEN){ 27 | window.top._fig.send("text: " + message); 28 | } else { 29 | window.top.console.log("FigWheely socket not ready yet: "+ window.top._fig.readyState); 30 | } 31 | } else { 32 | window.top.console.log("Could not be sended: " + message); 33 | } 34 | }-*/; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /vertxui-examples/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | live.connector 6 | vertxui-samples 7 | 1.01 8 | 9 | jar 10 | 11 | ${project.groupId}:${project.artifactId} 12 | 13 | 14 | 8 15 | 8 16 | UTF-8 17 | 18 | 19 | 20 | 21 | GNU General Public License, Version 3 22 | http://www.gnu.org/licenses/gpl-3.0.html 23 | manual 24 | A free, copyleft license for software and other kinds of works 25 | 26 | 27 | 28 | 29 | 30 | live.connector 31 | vertxui-core 32 | 1.01 33 | 34 | 35 | io.vertx 36 | vertx-unit 37 | 3.7.0 38 | test 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/fluent/ViewOn.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.fluent; 2 | 3 | import java.util.function.Function; 4 | 5 | /** 6 | * A view of a state. 7 | * 8 | */ 9 | public class ViewOn extends ViewOnBase { 10 | 11 | private A state; 12 | private Function translate; 13 | 14 | public ViewOn(A state, Function translate) { 15 | this.state = state; 16 | this.translate = translate; 17 | } 18 | 19 | protected Fluent generate(Fluent parent) { 20 | this.parent = parent; 21 | Fluent result = Fluent.getRootOf(translate.apply(state)); 22 | this.view = result; 23 | return result; 24 | } 25 | 26 | /** 27 | * Get the current state, in case it was easier not to keep a reference to 28 | * it yourself. 29 | * 30 | * @return the state 31 | */ 32 | public A state() { 33 | return state; 34 | } 35 | 36 | /** 37 | * Set the current state and sync(). You can also keep the state yourself, 38 | * change it and call sync(); 39 | * 40 | * @param state 41 | * the new state 42 | * @return this 43 | */ 44 | public ViewOnBase state(A state) { 45 | this.state = state; 46 | return sync(); 47 | } 48 | 49 | public ViewOn clone() { 50 | if (view != null) { 51 | throw new IllegalArgumentException("Can not clone if it is DOM-attached"); 52 | } 53 | return new ViewOn(state, translate); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /empty-project/src/test/java/client/ViewTestWithoutDom.java: -------------------------------------------------------------------------------- 1 | package client; 2 | import static org.junit.Assert.assertEquals; 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import java.util.List; 6 | 7 | import org.junit.Test; 8 | import org.mockito.Mockito; 9 | 10 | import com.google.gwt.core.shared.GwtIncompatible; 11 | 12 | import client.app.Controller; 13 | import client.app.View; 14 | import live.connector.vertxui.client.fluent.Fluent; 15 | import live.connector.vertxui.client.test.VirtualDomSearch; 16 | 17 | @GwtIncompatible 18 | public class ViewTestWithoutDom { 19 | 20 | @Test 21 | public void theDivs() { 22 | Fluent.clearVirtualDOM(); 23 | 24 | // Create a view and controller without ajax action 25 | View view = new View(); 26 | Controller controller = Mockito.spy(Controller.class); 27 | Mockito.doNothing().when(controller).doAjax(); 28 | view.start(controller); 29 | 30 | // after viewing 31 | List divs = VirtualDomSearch.getElementsByTagName("DIV", Fluent.body); 32 | // there should be one DIV 33 | assertEquals(divs.size(), 1); 34 | // which contains the inner text 'Hi' 35 | assertTrue(divs.get(0).txt().contains("Hi")); 36 | 37 | // after some AJAX 38 | String random = Math.random() + ""; 39 | view.setResponse(200, random); 40 | // there should be two DIVs 41 | divs = VirtualDomSearch.getElementsByTagName("DIV", Fluent.body); 42 | assertEquals(divs.size(), 2); 43 | // and the second should equal our ajax call. 44 | assertEquals(divs.get(1).txt(), "Server said: " + random); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/components/MonthTable.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator.components; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.Table; 4 | 5 | import live.connector.vertxui.client.fluent.Css; 6 | import live.connector.vertxui.client.fluent.Fluent; 7 | import live.connector.vertxui.client.fluent.ViewOnBoth; 8 | 9 | public class MonthTable extends ViewOnBoth { 10 | 11 | public static final String[] months = new String[] { "Jan.", "Febr.", "March", "April", "May", "June", "July", 12 | "Aug.", "Sept.", "Oct.", "Nov.", "Dec." }; 13 | 14 | public MonthTable(String[] infoInitial) { 15 | super(infoInitial, null, (infos, datas) -> { 16 | Fluent result = Table().css(Css.border, "1px solid black", Css.width, "100%"); 17 | 18 | Fluent header = result.tr(); 19 | for (String month : months) { 20 | header.th(null, month).css(Css.border, "1px solid black"); 21 | } 22 | 23 | Fluent infoRow = result.tr(); 24 | if (infos != null) { 25 | for (String info : infos) { 26 | infoRow.td(null, info).css(Css.border, "1px solid black"); 27 | } 28 | } 29 | 30 | Fluent dataRow = result.tr(); 31 | if (datas != null) { 32 | for (double data : datas) { 33 | String string = null; 34 | if (infoInitial == null) { 35 | string = Utils.format(data); 36 | } else { 37 | string = Utils.format(Math.round(data * 0.001)) + " kW"; 38 | } 39 | dataRow.td(null, string).css(Css.border, "1px solid black"); 40 | } 41 | } 42 | 43 | return result; 44 | }); 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /vertxui-examples/assets/todos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | VertxUI - TodoMVC 7 | 8 | 9 | 10 | 11 | 12 | 39 |
40 |
41 |

Double-click to edit a todo

42 |

43 | Created by NielsBaloe 44 |

45 |

46 | Part of TodoMVC 47 |

48 |
49 | 50 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/helloWorldFluent/ExampleHelloWorldFluent.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.helloWorldFluent; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.logging.Logger; 5 | 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.Vertx; 8 | import io.vertx.ext.web.Router; 9 | import live.connector.vertxui.samples.client.Dto; 10 | import live.connector.vertxui.samples.client.helloWorldFluentHtml.Client; 11 | import live.connector.vertxui.samples.server.AllExamplesServer; 12 | import live.connector.vertxui.server.VertxUI; 13 | import live.connector.vertxui.server.transport.Pojofy; 14 | 15 | public class ExampleHelloWorldFluent extends AbstractVerticle { 16 | 17 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 18 | 19 | public static void main(String[] args) { 20 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 21 | } 22 | 23 | @Override 24 | public void start() { 25 | 26 | // Wait and do some server stuff for AJAX 27 | Router router = Router.router(vertx); 28 | router.post(Client.url).handler(Pojofy.ajax(String.class, (dto, context) -> { 29 | // Without timer, write 'return "Hello,".....' because strings are 30 | // returned as is. 31 | vertx.setTimer(1000, l -> { 32 | context.response().putHeader("Content-Type", "text/plain; charset=" + VertxUI.charset); 33 | context.response().end("Hello, " + context.request().getHeader("User-Agent")); 34 | }); 35 | return null; // null means: we take care of the request() ourselves 36 | })); 37 | 38 | // extra: pojo example. Here the Pojofy.ajax() makes more sense! 39 | router.post(Client.urlPojo).handler(Pojofy.ajax(Dto.class, (dto, context) -> { 40 | log.info("Received a pojo from the client: color=" + dto.color); 41 | return new Dto("purple"); 42 | })); 43 | 44 | AllExamplesServer.start(Client.class, router); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /empty-project/src/main/java/client/test/ViewTestWithDom.java: -------------------------------------------------------------------------------- 1 | package client.test; 2 | 3 | import static live.connector.vertxui.client.test.Asserty.assertEquals; 4 | import static live.connector.vertxui.client.test.Asserty.assertTrue; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import org.junit.Test; 10 | 11 | import com.google.gwt.core.shared.GwtIncompatible; 12 | 13 | import client.app.Controller; 14 | import client.app.View; 15 | import elemental.dom.NodeList; 16 | import live.connector.vertxui.client.fluent.Fluent; 17 | import live.connector.vertxui.client.test.TestDOM; 18 | 19 | public class ViewTestWithDom extends TestDOM { 20 | 21 | private static int testNumber = 1; 22 | 23 | @GwtIncompatible 24 | @Test 25 | public void test() throws Exception { 26 | runJS(testNumber); 27 | } 28 | 29 | @Override 30 | public Map registerJS() { 31 | Map result = new HashMap<>(); 32 | result.put(testNumber, () -> divsWithDOM()); 33 | return result; 34 | } 35 | 36 | private static class ControllerMock extends Controller { 37 | @Override 38 | public void doAjax() { 39 | } 40 | } 41 | 42 | public void divsWithDOM() { 43 | 44 | // Create a view and controller without ajax action 45 | View view = new View(); 46 | 47 | Controller controller = new ControllerMock(); 48 | view.start(controller); 49 | 50 | // after viewing 51 | NodeList divs = Fluent.document.getElementsByTagName("DIV"); 52 | assertEquals("there should be one DIV", divs.length(), 1); 53 | assertTrue("which contains the inner text 'Hi'", divs.item(0).getTextContent().contains("Hi")); 54 | 55 | // after some AJAX 56 | String random = Math.random() + ""; 57 | view.setResponse(200, random); 58 | divs = Fluent.document.getElementsByTagName("DIV"); 59 | assertEquals("there should be two DIVs", divs.length(), 2); 60 | assertEquals("and the 2nd should equal ajax", divs.item(1).getTextContent(), "Server said: " + random); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /vertxui-core/doc/old-teavm.txt: -------------------------------------------------------------------------------- 1 | 2 | public String translateTeaVM() throws IOException { 3 | File temp = null; 4 | try { 5 | temp = File.createTempFile("vertxui", "js"); 6 | TeaVMTool teaVmTool = new TeaVMTool(); 7 | teaVmTool.setMainClass(classs.getCanonicalName()); 8 | teaVmTool.setTargetDirectory(temp.getParentFile()); 9 | teaVmTool.setTargetFileName(temp.getName()); 10 | teaVmTool.setCacheDirectory(temp.getParentFile()); 11 | teaVmTool.setRuntime(RuntimeCopyOperation.MERGED); 12 | teaVmTool.setMainPageIncluded(false); 13 | teaVmTool.setBytecodeLogging(debug); 14 | teaVmTool.setDebugInformationGenerated(debug); 15 | teaVmTool.setMinifying(!debug); 16 | teaVmTool.generate(); 17 | 18 | // Warnings 19 | ProblemProvider problemProvider = teaVmTool.getProblemProvider(); 20 | StringBuilder allWarnings = new StringBuilder(); 21 | List severes = problemProvider.getSevereProblems(); 22 | problemProvider.getProblems().stream().filter(s -> !severes.contains(s)).forEach(problem -> { 23 | if (allWarnings.length() == 0) { 24 | allWarnings.append("TeaVM warnings:"); 25 | } 26 | getProblemString(allWarnings, problem); 27 | }); 28 | if (allWarnings.length() != 0) { 29 | log.warning(allWarnings.toString()); 30 | } 31 | 32 | // Errors 33 | if (!severes.isEmpty()) { 34 | StringBuilder allSeveres = new StringBuilder("Severe build error(s): "); 35 | severes.forEach(problem -> { 36 | getProblemString(allSeveres, problem); 37 | }); 38 | throw new IOException(allSeveres.toString()); 39 | } 40 | String result = FileUtils.readFileToString(temp, "UTF-8"); 41 | if (withHtml) { 42 | // main in script so we can dynamicly load scripts 43 | result = ""; 45 | } 46 | return result; 47 | } finally { 48 | if (temp.exists()) { // just in case 49 | temp.delete(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /empty-project/src/main/java/server/Server.java: -------------------------------------------------------------------------------- 1 | package server; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | import client.app.Controller; 8 | import client.app.View; 9 | import io.vertx.core.AbstractVerticle; 10 | import io.vertx.core.Context; 11 | import io.vertx.core.Vertx; 12 | import io.vertx.core.http.HttpServerOptions; 13 | import io.vertx.ext.web.Router; 14 | import live.connector.vertxui.server.FigWheely; 15 | import live.connector.vertxui.server.VertxUI; 16 | import live.connector.vertxui.server.transport.Pojofy; 17 | 18 | public class Server extends AbstractVerticle { 19 | 20 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 21 | 22 | public static void main(String[] args) { 23 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 24 | } 25 | 26 | @Override 27 | public void start() { 28 | boolean debug = true; 29 | 30 | Runtime.getRuntime().addShutdownHook(new Thread() { 31 | public void run() { 32 | Context context = Vertx.currentContext(); 33 | if (context == null) { 34 | return; 35 | } 36 | Vertx vertx = context.owner(); 37 | vertx.deploymentIDs().forEach(vertx::undeploy); 38 | vertx.close(); 39 | } 40 | }); 41 | 42 | Router router = Router.router(vertx); 43 | router.put(Controller.url).handler(Pojofy.ajax(null, (input, handle) -> { 44 | log.info("Browser said: " + input); 45 | return "Hi from server"; 46 | })); 47 | if (debug) { 48 | router.get("/figwheely.js").handler(FigWheely.create()); 49 | } 50 | router.get("/*").handler(VertxUI.with(View.class, "/", debug, true)); 51 | 52 | vertx.createHttpServer(new HttpServerOptions().setCompressionSupported(true)).requestHandler(router::accept) 53 | .listen(80, listenHandler -> { 54 | if (listenHandler.failed()) { 55 | log.log(Level.SEVERE, "Startup error", listenHandler.cause()); 56 | System.exit(0); // stop on startup error 57 | } 58 | }); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/fluent/ViewOnBoth.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.fluent; 2 | 3 | import java.util.function.BiFunction; 4 | 5 | /** 6 | * A view of two states. 7 | * 8 | */ 9 | public class ViewOnBoth extends ViewOnBase { 10 | 11 | private A state1; 12 | private B state2; 13 | private BiFunction translate; 14 | 15 | public ViewOnBoth(A state1, B state2, BiFunction translate) { 16 | this.state1 = state1; 17 | this.state2 = state2; 18 | this.translate = translate; 19 | } 20 | 21 | protected Fluent generate(Fluent parent) { 22 | this.parent = parent; 23 | Fluent result = Fluent.getRootOf(translate.apply(state1, state2)); 24 | this.view = result; 25 | return result; 26 | } 27 | 28 | /** 29 | * Get the current state, in case it was easier not to keep a reference to 30 | * it yourself. 31 | * 32 | * @return the first state 33 | */ 34 | public A state1() { 35 | return state1; 36 | } 37 | 38 | /** 39 | * Get the current second state, in case it was easier not to keep a 40 | * reference to it yourself. 41 | * 42 | * @return the second state 43 | */ 44 | public B state2() { 45 | return state2; 46 | } 47 | 48 | public ViewOnBase state1(A state1) { 49 | this.state1 = state1; 50 | return sync(); 51 | } 52 | 53 | public ViewOnBase state2(B state2) { 54 | this.state2 = state2; 55 | return sync(); 56 | } 57 | 58 | /** 59 | * Set the current state and sync(). You can also keep the state yourself, 60 | * change it and call sync(); 61 | * 62 | * @param state1 63 | * the first new state 64 | * @param state2 65 | * the second new state 66 | * @return this 67 | */ 68 | public ViewOnBase state(A state1, B state2) { 69 | this.state1 = state1; 70 | this.state2 = state2; 71 | return sync(); 72 | } 73 | 74 | public ViewOnBoth clone() { 75 | if (view != null) { 76 | throw new IllegalArgumentException("Can not clone if it is DOM-attached"); 77 | } 78 | return new ViewOnBoth(state1, state2, translate); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/ComponentDatePicker.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | 6 | import com.google.gwt.i18n.shared.DateTimeFormat; 7 | import com.google.gwt.i18n.shared.DefaultDateTimeFormatInfo; 8 | 9 | import live.connector.vertxui.client.fluent.Att; 10 | import live.connector.vertxui.client.fluent.Fluent; 11 | 12 | /** 13 | * An example to create a component. Note that the class View contains css and 14 | * scripts for library pikaday.js and moment.js. 15 | * 16 | * @author ng 17 | * 18 | */ 19 | public class ComponentDatePicker extends Fluent { 20 | 21 | public static DateTimeFormat dateTimeFormat = new InnerDateTimeFormat("dd/MM/yyyy"); 22 | 23 | private final String id = Math.random() + ""; 24 | 25 | public static ArrayList getCss() { 26 | ArrayList result = new ArrayList<>(); 27 | result.add("https://cdnjs.cloudflare.com/ajax/libs/pikaday/1.5.1/css/pikaday.min.css"); 28 | return result; 29 | } 30 | 31 | public static ArrayList getScripts() { 32 | ArrayList result = new ArrayList<>(); 33 | result.add("https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"); 34 | result.add("https://cdnjs.cloudflare.com/ajax/libs/pikaday/1.5.1/pikaday.min.js"); 35 | return result; 36 | } 37 | 38 | private static class InnerDateTimeFormat extends DateTimeFormat { 39 | protected InnerDateTimeFormat(String pattern) { 40 | super(pattern, new DefaultDateTimeFormatInfo()); 41 | } 42 | } 43 | 44 | public ComponentDatePicker() { 45 | super("INPUT", null); 46 | classs("form-control").att(Att.type, "text").id(id); 47 | 48 | // The non-component version was only: 49 | // Input("form-control", "text").id("datepicker"); 50 | } 51 | 52 | @Override 53 | public void isRendered(boolean shown) { 54 | if (shown) { 55 | eval("new Pikaday({ field: document.getElementById('" + id + "'), format: 'DD/MM/YYYY' });"); 56 | } 57 | } 58 | 59 | public Date getDate() { 60 | return dateTimeFormat.parse(domValue()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/helloWorld/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.helloWorld; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.console; 4 | import static live.connector.vertxui.client.fluent.Fluent.document; 5 | 6 | import com.google.gwt.core.client.EntryPoint; 7 | import com.google.gwt.xhr.client.XMLHttpRequest; 8 | 9 | import elemental.dom.Element; 10 | import elemental.events.Event; 11 | 12 | public class Client implements EntryPoint { 13 | 14 | public final static String url = "/ajax"; 15 | 16 | private Element button; 17 | private Element response; 18 | private Element thinking; 19 | 20 | public Client() { 21 | Element body = document.getBody(); 22 | 23 | button = document.createElement("button"); 24 | button.setAttribute("id", "hello-button"); 25 | button.setTextContent("Click me"); 26 | button.setOnclick(this::clicked); 27 | body.appendChild(button); 28 | 29 | response = document.createElement("div"); 30 | body.appendChild(response); 31 | 32 | thinking = document.createElement("div"); 33 | thinking.setTextContent("The server waits as demonstration"); 34 | thinking.getStyle().setProperty("display", "none"); 35 | body.appendChild(thinking); 36 | } 37 | 38 | // It is advisable to write callbacks into methods, so you can easily write 39 | // jUnit tests. 40 | private void clicked(Event e) { 41 | button.setAttribute("disabled", ""); 42 | thinking.getStyle().setProperty("display", ""); 43 | 44 | XMLHttpRequest xhr = XMLHttpRequest.create(); 45 | xhr.setOnReadyStateChange(a -> { 46 | if (xhr.getReadyState() == 4 && xhr.getStatus() == 200) { 47 | responsed(xhr.getResponseText()); 48 | } 49 | }); 50 | xhr.open("POST", url); 51 | xhr.send(); 52 | } 53 | 54 | private void responsed(String text) { 55 | console.log("received: " + text); 56 | button.removeAttribute("disabled"); 57 | 58 | Element responseElem = document.createElement("div"); 59 | responseElem.appendChild(document.createTextNode(text)); 60 | response.appendChild(responseElem); 61 | 62 | thinking.getStyle().setProperty("display", "none"); 63 | } 64 | 65 | @Override 66 | public void onModuleLoad() { 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/chatSockjs/ExampleChatSockjs.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.chatSockjs; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.logging.Logger; 7 | 8 | import io.vertx.core.AbstractVerticle; 9 | import io.vertx.core.Vertx; 10 | import io.vertx.core.json.JsonObject; 11 | import io.vertx.ext.web.Router; 12 | import io.vertx.ext.web.handler.sockjs.SockJSHandler; 13 | import live.connector.vertxui.samples.client.Dto; 14 | import live.connector.vertxui.samples.client.chatSockjs.Client; 15 | import live.connector.vertxui.samples.server.AllExamplesServer; 16 | import live.connector.vertxui.server.transport.Pojofy; 17 | 18 | public class ExampleChatSockjs extends AbstractVerticle { 19 | 20 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 21 | 22 | public static void main(String[] args) { 23 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 24 | } 25 | 26 | @Override 27 | public void start() { 28 | // Chat with SockJS 29 | Router router = Router.router(vertx); 30 | List ids = new ArrayList<>(); 31 | router.route(Client.url + "/*").handler(SockJSHandler.create(vertx).socketHandler(socket -> { 32 | final String id = socket.writeHandlerID(); 33 | ids.add(id); // entering 34 | socket.endHandler(data -> { 35 | ids.remove(id); // leaving 36 | }); 37 | socket.handler(buffer -> { // receiving 38 | 39 | // extra: pojo example 40 | if (Pojofy.socket(socket, Client.urlPojo, buffer, Dto.class, this::serviceDoSomething)) { 41 | return; 42 | } 43 | 44 | ids.forEach(i -> vertx.eventBus().send(i, buffer)); // broadcasting 45 | // to reply to one: socket.write() 46 | 47 | }); 48 | })); 49 | 50 | AllExamplesServer.start(Client.class, router); 51 | } 52 | 53 | public Dto serviceDoSomething(Dto received, JsonObject headers) { 54 | log.info("Extra example: received a dto with action=" + headers.getString("action") + " and color=" 55 | + received.color); 56 | return new Dto("brown"); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/helloWorldFluentHtml/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.helloWorldFluentHtml; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | import static live.connector.vertxui.client.fluent.FluentBase.console; 5 | import static live.connector.vertxui.client.fluent.FluentBase.head; 6 | 7 | import com.google.gwt.core.client.EntryPoint; 8 | 9 | import elemental.events.MouseEvent; 10 | import live.connector.vertxui.client.FigWheelyClient; 11 | import live.connector.vertxui.client.fluent.Css; 12 | import live.connector.vertxui.client.fluent.Fluent; 13 | import live.connector.vertxui.client.transport.Pojofy; 14 | import live.connector.vertxui.samples.client.AllExamplesClient; 15 | import live.connector.vertxui.samples.client.Dto; 16 | 17 | public class Client implements EntryPoint { 18 | 19 | public final static String url = "/ajax"; 20 | 21 | private Fluent button; 22 | private Fluent response; 23 | private Fluent thinking; 24 | 25 | public Client() { 26 | head.script(FigWheelyClient.urlJavascript); 27 | 28 | button = body.div().button(null, "button", "Click me!").id("hello-button").click(this::clicked); 29 | response = body.div(); 30 | thinking = body.div().txt("The server waits as demonstration!").id("thinking-panel").css(Css.display, "none"); 31 | } 32 | 33 | // It is advisable to write callbacks into methods, so you can easily write 34 | // jUnit tests. 35 | private void clicked(Fluent __, MouseEvent ___) { 36 | button.disabled(true); 37 | thinking.css(Css.display, ""); 38 | Pojofy.ajax("POST", url, null, null, null, this::responsed); 39 | } 40 | 41 | // It is advisable to write callbacks into methods, so you can easily write 42 | // jUnit tests. 43 | private void responsed(int responseCode, String text) { 44 | button.disabled(false); 45 | 46 | response.div().txt(text); 47 | thinking.css(Css.display, "none"); 48 | 49 | // extra: POJO example 50 | Pojofy.ajax("POST", urlPojo, new Dto("white"), AllExamplesClient.dto, AllExamplesClient.dto, 51 | (i, a) -> console.log(a.color)); 52 | } 53 | 54 | public final static String urlPojo = "/pojo"; 55 | 56 | @Override 57 | public void onModuleLoad() { 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/fluent/ViewOnBase.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.fluent; 2 | 3 | import com.google.gwt.core.shared.GWT; 4 | 5 | /** 6 | * A base class for descriptive lambda-views. 7 | * 8 | * @author Niels Gorisse 9 | * 10 | */ 11 | public abstract class ViewOnBase implements Viewable { 12 | 13 | protected Fluent view; 14 | protected Fluent parent; 15 | 16 | /** 17 | * Set the display parameter of the view (if any) to none. 18 | */ 19 | @Override 20 | public ViewOnBase hide(boolean doit) { 21 | if (view != null) { 22 | view.hide(doit); 23 | } 24 | return this; 25 | } 26 | 27 | protected void setParent(Fluent parent) { 28 | this.parent = parent; 29 | } 30 | 31 | /** 32 | * Get the view (if there is any), useful for junit tests. 33 | * 34 | * @return this 35 | */ 36 | public Fluent getView() { 37 | return view; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | String result = "ViewOn{"; 43 | if (view != null) { 44 | result += view.tag; 45 | } 46 | result += "}"; 47 | return result; 48 | } 49 | 50 | @Override 51 | public int getCrc() { 52 | if (view != null) { 53 | return view.getCrc(); 54 | } else { 55 | return 0; 56 | } 57 | } 58 | 59 | @Override 60 | public String getCrcString() { 61 | if (view != null) { 62 | return view.getCrcString(); 63 | } else { 64 | return ""; 65 | } 66 | } 67 | 68 | public ViewOnBase sync() { 69 | // the 'if' below prevents throwing away inner viewOn's with a outer 70 | // state. Because, if sync is called fromout fluent when adding inside a 71 | // viewon, the attached dom is thrown away, which is not supposed to 72 | // happen then. (for parent!=null&&parent.elemnt!=null) 73 | // 74 | // However, it prevents testing against the virtual dom, so we leave it 75 | // on in pure java (!gwt.isClient()). 76 | if (!GWT.isClient() || (parent != null && parent.element != null)) { 77 | Renderer.syncChild(parent, this, view); 78 | isRendered(true); 79 | } 80 | return this; 81 | } 82 | 83 | @Override 84 | public void isRendered(boolean state) { 85 | if (view != null) { 86 | view.isRendered(state); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/testjUnitWithDom/AnotherTest.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.testjUnitWithDom; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.body; 4 | import static live.connector.vertxui.client.fluent.Fluent.console; 5 | import static live.connector.vertxui.client.fluent.Fluent.document; 6 | import static live.connector.vertxui.client.test.Asserty.assertEquals; 7 | import static live.connector.vertxui.client.test.Asserty.assertTrue; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import org.junit.Test; 13 | 14 | import com.google.gwt.core.shared.GwtIncompatible; 15 | 16 | import elemental.dom.Element; 17 | import elemental.dom.NamedNodeMap; 18 | import elemental.dom.Node; 19 | import elemental.dom.NodeList; 20 | import live.connector.vertxui.client.test.TestDOM; 21 | 22 | public class AnotherTest extends TestDOM { 23 | 24 | @GwtIncompatible 25 | @Test 26 | public void test() throws Exception { 27 | runJS(3); 28 | } 29 | 30 | @Override 31 | public Map registerJS() { 32 | Map result = new HashMap<>(); 33 | result.put(3, () -> { 34 | String id = "id" + Math.random(); 35 | String inner = "bladiebla" + Math.random(); 36 | body.div().txt(inner).id(id).classs("bladiebla"); 37 | printStructure((Element) body.dom()); 38 | 39 | Element found = document.getElementById(id); 40 | assertTrue("should exist", found != null); 41 | assertEquals("inner text", found.getTextContent(), inner); 42 | }); 43 | return result; 44 | } 45 | 46 | private void printStructure(Element element) { 47 | console.log("<" + element.getNodeName()); 48 | NamedNodeMap attributes = element.getAttributes(); 49 | if (attributes != null) { 50 | for (int x = 0; x < attributes.length(); x++) { 51 | Node attr = attributes.item(x); 52 | console.log(" " + attr.getNodeName() + "=" + attr.getNodeValue()); 53 | } 54 | } 55 | console.log(">"); 56 | if (element.getTextContent() != null) { 57 | console.log(element.getTextContent()); 58 | } 59 | NodeList children = element.getChildNodes(); 60 | for (int x = 0; x < children.getLength(); x++) { 61 | printStructure((Element) children.at(x)); 62 | } 63 | console.log(""); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/test/ConsoleTester.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.test; 2 | 3 | import elemental.html.Console; 4 | import elemental.html.MemoryInfo; 5 | import elemental.util.Indexable; 6 | 7 | /** 8 | * A class which simulates console.log for plain-java outside-gwt junit testing. 9 | * 10 | * @author Niels Gorisse 11 | * 12 | */ 13 | public class ConsoleTester implements Console { 14 | 15 | public ConsoleTester() { 16 | } 17 | 18 | @Override 19 | public MemoryInfo getMemory() { 20 | return null; 21 | } 22 | 23 | @Override 24 | public Indexable getProfiles() { 25 | return null; 26 | } 27 | 28 | @Override 29 | public void assertCondition(boolean condition, Object arg) { 30 | if (condition == false) { 31 | System.err.println(arg); 32 | } 33 | } 34 | 35 | @Override 36 | public void count() { 37 | } 38 | 39 | @Override 40 | public void debug(Object arg) { 41 | System.out.println(arg); 42 | } 43 | 44 | @Override 45 | public void dir() { 46 | } 47 | 48 | @Override 49 | public void dirxml() { 50 | } 51 | 52 | @Override 53 | public void error(Object arg) { 54 | System.err.println(arg); 55 | } 56 | 57 | @Override 58 | public void group(Object arg) { 59 | } 60 | 61 | @Override 62 | public void groupCollapsed(Object arg) { 63 | } 64 | 65 | @Override 66 | public void groupEnd() { 67 | } 68 | 69 | @Override 70 | public void info(Object arg) { 71 | System.out.println(arg); 72 | } 73 | 74 | @Override 75 | public void log(Object arg) { 76 | System.out.println(arg); 77 | } 78 | 79 | @Override 80 | public void markTimeline() { 81 | } 82 | 83 | @Override 84 | public void profile(String title) { 85 | } 86 | 87 | @Override 88 | public void profileEnd(String title) { 89 | } 90 | 91 | @Override 92 | public void time(String title) { 93 | } 94 | 95 | @Override 96 | public void timeEnd(String title, Object arg) { 97 | } 98 | 99 | @Override 100 | public void timeStamp(Object arg) { 101 | } 102 | 103 | @Override 104 | public void trace(Object arg) { 105 | } 106 | 107 | @Override 108 | public void warn(Object arg) { 109 | System.err.println(arg); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/chatSockjs/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.chatSockjs; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | import static live.connector.vertxui.client.fluent.FluentBase.console; 5 | import static live.connector.vertxui.client.fluent.FluentBase.head; 6 | import static live.connector.vertxui.client.fluent.FluentBase.window; 7 | 8 | import com.google.gwt.core.client.EntryPoint; 9 | 10 | import elemental.events.KeyboardEvent; 11 | import elemental.events.MessageEvent; 12 | import elemental.json.Json; 13 | import live.connector.vertxui.client.FigWheelyClient; 14 | import live.connector.vertxui.client.fluent.Att; 15 | import live.connector.vertxui.client.fluent.Fluent; 16 | import live.connector.vertxui.client.transport.Pojofy; 17 | import live.connector.vertxui.client.transport.SockJS; 18 | import live.connector.vertxui.samples.client.AllExamplesClient; 19 | import live.connector.vertxui.samples.client.Dto; 20 | 21 | /** 22 | * @author Niels Gorisse 23 | */ 24 | 25 | public class Client implements EntryPoint { 26 | 27 | public static final String url = "/chatSockjs"; 28 | 29 | public Client() { 30 | head.script(FigWheelyClient.urlJavascript); 31 | SockJS.importJs((Void) -> { 32 | 33 | String name = window.prompt("What is your name?", ""); 34 | Fluent input = body.input(null, "text"); 35 | Fluent messages = body.div(); 36 | 37 | SockJS socket = SockJS.create(url); 38 | socket.setOnopen(e -> { 39 | socket.send(name + ": Ola, I'm " + name + "."); 40 | }); 41 | socket.setOnmessage(e -> { 42 | // extra: pojo example 43 | if (Pojofy.socketReceive(urlPojo, e, AllExamplesClient.dto, 44 | d -> console.log("Received pojo color=" + d.color))) { 45 | return; 46 | } 47 | 48 | messages.li(null, ((MessageEvent) e).getData().toString()); 49 | }); 50 | input.keydown((fluent, event) -> { 51 | if (event.getKeyCode() == KeyboardEvent.KeyCode.ENTER) { 52 | socket.send(name + ": " + input.domValue()); 53 | input.att(Att.value, null); 54 | 55 | // extra: pojo example 56 | Pojofy.socketSend(socket, urlPojo, new Dto("violet"), AllExamplesClient.dto, 57 | Json.parse("{\"action\":\"save\"}")); 58 | } 59 | }); 60 | input.focus(); 61 | 62 | }); 63 | } 64 | 65 | public static String urlPojo = "/pojo"; 66 | 67 | @Override 68 | public void onModuleLoad() { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/chatWebsocket/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.chatWebsocket; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | import static live.connector.vertxui.client.fluent.FluentBase.console; 5 | import static live.connector.vertxui.client.fluent.FluentBase.head; 6 | import static live.connector.vertxui.client.fluent.FluentBase.window; 7 | 8 | import com.google.gwt.core.client.EntryPoint; 9 | 10 | import elemental.events.MessageEvent; 11 | import elemental.html.WebSocket; 12 | import elemental.json.Json; 13 | import live.connector.vertxui.client.FigWheelyClient; 14 | import live.connector.vertxui.client.fluent.Att; 15 | import live.connector.vertxui.client.fluent.Fluent; 16 | import live.connector.vertxui.client.transport.Pojofy; 17 | import live.connector.vertxui.samples.client.AllExamplesClient; 18 | import live.connector.vertxui.samples.client.Dto; 19 | import live.connector.vertxui.samples.server.AllExamplesServer; 20 | 21 | /** 22 | * @author Niels Gorisse 23 | */ 24 | 25 | public class Client implements EntryPoint { 26 | 27 | public static final String url = "/chatWebsocket"; 28 | 29 | public Client() { 30 | head.script(FigWheelyClient.urlJavascript); 31 | 32 | String name = window.prompt("What is your name?", ""); 33 | 34 | Fluent input = body.input(null, "text"); 35 | Fluent messages = body.div(); 36 | 37 | WebSocket socket = window.newWebSocket("ws://localhost:" + AllExamplesServer.port + url); 38 | socket.setBinaryType("arraybuffer"); // blobs need decoding 39 | socket.setOnopen(e -> { 40 | socket.send(name + ": Ola, I'm " + name + "."); 41 | }); 42 | socket.setOnmessage(e -> { 43 | 44 | // extra: pojo example 45 | if (Pojofy.socketReceive(urlPojo, e, AllExamplesClient.dto, 46 | d -> console.log("Received pojo color=" + d.color))) { 47 | return; 48 | } 49 | 50 | messages.li(null, ((MessageEvent) e).getData().toString()); 51 | }); 52 | 53 | input.keydown((fluent, event) -> { 54 | if (event.getKeyCode() == 13) { 55 | socket.send(name + ": " + input.domValue()); 56 | input.att(Att.value, null); 57 | 58 | // extra: pojo example 59 | Pojofy.socketSend(socket, urlPojo, new Dto("darkviolet"), AllExamplesClient.dto, 60 | Json.parse("{\"action\":\"save\"}")); 61 | } 62 | }); 63 | 64 | input.focus(); 65 | } 66 | 67 | public static String urlPojo = "/pojo"; 68 | 69 | @Override 70 | public void onModuleLoad() { 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/AllExamplesServer.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | import java.util.stream.Collectors; 7 | 8 | import io.vertx.core.Context; 9 | import io.vertx.core.Vertx; 10 | import io.vertx.core.http.HttpServer; 11 | import io.vertx.core.http.HttpServerOptions; 12 | import io.vertx.ext.web.Router; 13 | import live.connector.vertxui.client.FigWheelyClient; 14 | import live.connector.vertxui.server.FigWheelyServer; 15 | import live.connector.vertxui.server.VertxUI; 16 | 17 | /** 18 | * @author Niels Gorisse 19 | * 20 | */ 21 | public class AllExamplesServer { 22 | 23 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 24 | 25 | public static final int port = 8088; 26 | 27 | public static void start(Class classs, Router router) { 28 | Vertx vertx = Vertx.currentContext().owner(); 29 | HttpServer httpServer = vertx.createHttpServer(new HttpServerOptions().setCompressionSupported(true)); 30 | start(classs, router, httpServer); 31 | } 32 | 33 | public static void start(Class classs, Router router, HttpServer httpServer) { 34 | 35 | boolean debug = true; 36 | 37 | // Serve the javascript for figwheely (and turn it on too) 38 | if (debug) { 39 | router.get(FigWheelyClient.urlJavascript).handler(FigWheelyServer.create()); 40 | } 41 | 42 | // The main compiled js 43 | router.get("/*").handler(VertxUI.with(classs, "/", debug, true)); 44 | 45 | // Make sure that when we exit, we close vertxes too. 46 | Runtime.getRuntime().addShutdownHook(new Thread() { 47 | public void run() { 48 | Context context = Vertx.currentContext(); 49 | if (context == null) { 50 | return; 51 | } 52 | Vertx vertx = context.owner(); 53 | vertx.deploymentIDs().forEach(vertx::undeploy); 54 | vertx.close(); 55 | } 56 | }); 57 | 58 | // Start the server 59 | httpServer.requestHandler(router).listen(port, listenHandler -> { 60 | if (listenHandler.failed()) { 61 | log.log(Level.SEVERE, "Startup error", listenHandler.cause()); 62 | System.exit(0);// stop on startup error 63 | } 64 | log.info("Initialised:" + router.getRoutes().stream().map(a -> { 65 | return "\n\thttp://localhost:" + httpServer.actualPort() + a.getPath(); 66 | }).distinct().collect(Collectors.joining())); 67 | }); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/fluent/Att.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.fluent; 2 | 3 | /** 4 | * A list of standarised attributes. 5 | * 6 | * @author ng 7 | * 8 | */ 9 | public enum Att { 10 | 11 | hidden, high, href, hreflang, icon, id, ismap, itemprop, // 12 | keytype, kind, label, lang, language, list, loop, low, manifest, // 13 | max, maxlength, media, method, min, multiple, novalidate, open, // 14 | optimum, pattern, ping, paceholder, poster, preload, pubdate, radiogroup, // 15 | readonly, rel, required, reversed, role, rows, rowspan, sandbox, spellcheck, scope, // 16 | scoped, seamless, selected, shape, size, sizes, span, src, srcdoc, srclang, srcset, // 17 | start, step, style, summary, tabindex, target, title, type, usemap, value, width, wrap, // 18 | border, buffered, challenge, charset, checked, cite, color, cols, colspan, content, // 19 | contenteditable, contextmenu, controls, coords, data, datetime, defer, dir, // 20 | dirnme, disable, download, draggable, dropzone, enctype, form, formaction, // 21 | headers, height, accept, accesskey, action, align, alt, async, // 22 | autocomplete, autofocus, autoplay, autosave, // 23 | name_, for_, default_, class_, http_equiv, accept_charset, text, dataProvide, dataRole, placeholder, selectedIndex; 24 | 25 | public String nameValid() { 26 | switch (this) { 27 | case http_equiv: // java: '-' illegal 28 | return "http-equiv"; 29 | case accept_charset: // java: '-' illegal 30 | return "accept-charset"; 31 | case for_: // java: reserved keyword 32 | return "for"; 33 | case default_:// java: reserved keyword 34 | return "default"; 35 | case class_:// java: reserved keyword 36 | return "class"; 37 | case name_:// javascript: reserved keyword 38 | return "name"; 39 | case dataProvide: 40 | return "data-provide"; 41 | case dataRole: 42 | return "data-role"; 43 | default: 44 | return name(); 45 | } 46 | } 47 | 48 | public static Att valueOfValid(String name) { 49 | switch (name) { 50 | case "http-equiv": 51 | return http_equiv; 52 | case "accept-charset": 53 | return accept_charset; 54 | case "for": 55 | return for_; 56 | case "default": 57 | return default_; 58 | case "class": 59 | return class_; 60 | case "name":// javascript: reserved keyword 61 | return name_; 62 | case "data-provide": 63 | return dataProvide; 64 | case "data-role": 65 | return dataRole; 66 | default: 67 | return valueOf(name); 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/chatWebsocket/ExampleChatWebsocket.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.chatWebsocket; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.logging.Logger; 7 | 8 | import io.vertx.core.AbstractVerticle; 9 | import io.vertx.core.Vertx; 10 | import io.vertx.core.http.HttpServer; 11 | import io.vertx.core.http.HttpServerOptions; 12 | import io.vertx.core.json.JsonObject; 13 | import io.vertx.ext.web.Router; 14 | import live.connector.vertxui.samples.client.Dto; 15 | import live.connector.vertxui.samples.client.chatWebsocket.Client; 16 | import live.connector.vertxui.samples.server.AllExamplesServer; 17 | import live.connector.vertxui.server.transport.Pojofy; 18 | 19 | public class ExampleChatWebsocket extends AbstractVerticle { 20 | 21 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 22 | 23 | public static void main(String[] args) { 24 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 25 | } 26 | 27 | @Override 28 | public void start() { 29 | // Initialize the router and a webserver with HTTP-compression 30 | Router router = Router.router(vertx); 31 | HttpServer server = vertx.createHttpServer(new HttpServerOptions().setCompressionSupported(true)); 32 | 33 | // Chat with websocket 34 | List ids = new ArrayList<>(); 35 | server.websocketHandler(socket -> { 36 | if (!socket.path().equals(Client.url)) { 37 | socket.reject(); 38 | return; 39 | } 40 | final String id = socket.textHandlerID(); 41 | ids.add(id); // entering 42 | socket.closeHandler(data -> { 43 | ids.remove(id); // leaving 44 | }); 45 | socket.handler(buffer -> { // receiving 46 | 47 | // extra: pojo example 48 | if (Pojofy.socket(socket, Client.urlPojo, buffer, Dto.class, this::serviceDoSomething)) { 49 | return; 50 | } 51 | String message = buffer.toString(); 52 | ids.forEach(i -> vertx.eventBus().send(i, message)); // broadcasting 53 | // to reply to one: socket.writeFinalTextFrame(...); 54 | }); 55 | }); 56 | 57 | AllExamplesServer.start(Client.class, router, server); 58 | } 59 | 60 | public Dto serviceDoSomething(Dto received, JsonObject headers) { 61 | log.info("Extra example: received a dto with action=" + headers.getString("action") + " and color=" 62 | + received.color); 63 | return new Dto("brown"); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/todomvc/Store.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.todomvc; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.github.nmorel.gwtjackson.client.ObjectMapper; 7 | import com.github.nmorel.gwtjackson.client.exception.JsonDeserializationException; 8 | import com.google.gwt.core.client.GWT; 9 | 10 | import elemental.html.Storage; 11 | import live.connector.vertxui.client.fluent.Fluent; 12 | 13 | public class Store { 14 | 15 | private List models; 16 | 17 | private Storage storage = Fluent.window.getLocalStorage(); 18 | 19 | public Store() { 20 | } 21 | 22 | public List getAll() { 23 | if (models == null) { 24 | models = new ArrayList<>(); 25 | 26 | Storage storage = Fluent.window.getLocalStorage(); 27 | for (int x = 0; x < storage.getLength(); x++) { 28 | String key = storage.key(x); 29 | String text = storage.getItem(key); 30 | try { 31 | models.add(Store.todoMap.read(text)); 32 | } catch (JsonDeserializationException jde) { 33 | Fluent.console.log("Warn: could not parse " + text + ": " + jde.getMessage()); 34 | storage.removeItem(key); 35 | } 36 | } 37 | } 38 | return models; 39 | } 40 | 41 | public void add(Model model) { 42 | models.add(model); 43 | storage.setItem("" + model.getId(), Store.todoMap.write(model)); 44 | } 45 | 46 | public void setCompletedAll(boolean checked) { 47 | models.stream().forEach(model -> { 48 | if (model.isCompleted() != checked) { 49 | model.setCompleted(checked); 50 | storage.setItem("" + model.getId(), Store.todoMap.write(model)); 51 | } 52 | }); 53 | } 54 | 55 | public void setCompleted(Model model, boolean checked) { 56 | model.setCompleted(checked); 57 | storage.setItem(model.getId() + "", Store.todoMap.write(model)); 58 | } 59 | 60 | public void remove(Model model) { 61 | models.remove(model); 62 | storage.removeItem(model.getId() + ""); 63 | } 64 | 65 | public void removeCompletedAll() { 66 | for (int x = models.size() - 1; x != -1; x--) { 67 | Model model = models.get(x); 68 | if (model.isCompleted()) { 69 | models.remove(x); 70 | storage.removeItem(model.getId() + ""); 71 | } 72 | } 73 | } 74 | 75 | public void setText(Model model, String text) { 76 | model.setTitle(text); 77 | storage.setItem(model.getId() + "", Store.todoMap.write(model)); 78 | } 79 | 80 | // Boilerplate for pojo traffic: 81 | public interface TodoMap extends ObjectMapper { 82 | } 83 | 84 | public static TodoMap todoMap = null; 85 | static { 86 | if (GWT.isClient()) { 87 | todoMap = GWT.create(TodoMap.class); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/chatEventBus/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.chatEventBus; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | import static live.connector.vertxui.client.fluent.FluentBase.console; 5 | import static live.connector.vertxui.client.fluent.FluentBase.head; 6 | import static live.connector.vertxui.client.fluent.FluentBase.window; 7 | 8 | import com.google.gwt.core.client.EntryPoint; 9 | 10 | import elemental.events.KeyboardEvent; 11 | import elemental.json.Json; 12 | import live.connector.vertxui.client.FigWheelyClient; 13 | import live.connector.vertxui.client.fluent.Att; 14 | import live.connector.vertxui.client.fluent.Fluent; 15 | import live.connector.vertxui.client.transport.EventBus; 16 | import live.connector.vertxui.client.transport.Pojofy; 17 | import live.connector.vertxui.samples.client.AllExamplesClient; 18 | import live.connector.vertxui.samples.client.Dto; 19 | 20 | /** 21 | * @author Niels Gorisse 22 | * 23 | */ 24 | 25 | public class Client implements EntryPoint { 26 | 27 | // EventBus-address of text messages to everyone 28 | public static final String freeway = "freeWayAddress"; 29 | 30 | // EventBus-address of myDto objects 31 | public static final String addressPojo = "serviceForDto"; 32 | 33 | public static final String url = "/chatEventbus"; 34 | 35 | public Client() { 36 | head.script(FigWheelyClient.urlJavascript); 37 | EventBus.importJs((Void) -> { 38 | 39 | String name = window.prompt("What is your name?", ""); 40 | Fluent input = body.input(null, "text"); 41 | Fluent messages = body.div(); 42 | 43 | EventBus eventBus = EventBus.create(url, null); 44 | eventBus.onopen(event -> { 45 | eventBus.publish(freeway, name + ": Ola, I'm " + name + ".", null); 46 | eventBus.registerHandler(freeway, null, (error, in) -> { // onmessage 47 | messages.li(null, in.get("body").asString()); 48 | }); 49 | 50 | // extra example: pojo consume 51 | Pojofy.eventbusReceive(eventBus, addressPojo, null, AllExamplesClient.dto, 52 | a -> console.log("Received pojo: " + a.color)); 53 | }); 54 | 55 | input.keydown((fluent, event) -> { 56 | if (event.getKeyCode() == KeyboardEvent.KeyCode.ENTER) { 57 | eventBus.publish(freeway, name + ": " + input.domValue(), null); 58 | input.att(Att.value, null); 59 | 60 | // extra example: object publish 61 | Pojofy.eventbusPublish(eventBus, addressPojo, new Dto("blue by " + name), 62 | Json.parse("{\"action\":\"save\"}"), AllExamplesClient.dto); 63 | } 64 | }); 65 | input.focus(); 66 | }); 67 | } 68 | 69 | @Override 70 | public void onModuleLoad() { 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/components/ChartJs.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator.components; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import live.connector.vertxui.client.fluent.Att; 10 | import live.connector.vertxui.client.fluent.Fluent; 11 | 12 | /** 13 | * http://www.chartjs.org/docs/latest/ 14 | * 15 | * @author ng 16 | * 17 | */ 18 | public class ChartJs extends Fluent { 19 | 20 | private String id = "chartJs" + Math.round(Math.random() * 1000000000); 21 | 22 | private List names = new ArrayList<>(); 23 | 24 | public static ArrayList getScripts() { 25 | ArrayList result = new ArrayList<>(); 26 | result.add("https://cdn.jsdelivr.net/npm/chart.js@2.7.1/dist/Chart.min.js"); 27 | return result; 28 | } 29 | 30 | public ChartJs(Fluent root, int width, int height, String title) { 31 | super("canvas", root); 32 | att(Att.id, id, Att.width, width + "", Att.height, 200 + ""); 33 | 34 | String months = Stream.of(MonthTable.months).map(month -> "'" + month + "'").collect(Collectors.joining(",")); 35 | String eval = "var canvas = document.getElementById('" + id + "'); " 36 | + "var data = { labels: [" + months + "], datasets: [] }; " 37 | + "var options={responsive:false,maintainAspectRatio:false,title:{display:true,text:'" + title + "'}};" 38 | + "var c" + id + "=new Chart(canvas,{ type:'line', data:data, options:options}); "; 39 | // Fluent.console.log(eval); 40 | eval(eval); 41 | } 42 | 43 | public void showData(String title, String color, double[] data) { 44 | // /1000 and rounded 45 | double show[] = new double[data.length]; 46 | for (int x = 0; x < data.length; x++) { 47 | show[x] = Math.round(data[x] * 0.001); 48 | } 49 | 50 | // get position in dataset array 51 | if (!names.contains(title)) { 52 | names.add(title); 53 | } 54 | int position = names.indexOf(title); 55 | 56 | // show 57 | String eval = "var cdata= c" + id + ".data; " 58 | + "if (cdata.datasets['" + position + "'] === undefined) {cdata.datasets.push({});}" 59 | + "cdata.datasets['" + position + "'].label='" + title + "'; " 60 | + "cdata.datasets['" + position + "'].data=" + Arrays.toString(show) + "; " 61 | + "cdata.datasets['" + position + "'].fill=false; " 62 | + "cdata.datasets['" + position + "'].backgroundColor= '" + color + "'; " 63 | + "cdata.datasets['" + position + "'].borderColor= '" + color + "';"; 64 | eval += "c" + id + ".update();"; 65 | eval(eval); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/test/VirtualDomSearch.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.test; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.console; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.function.Predicate; 8 | 9 | import com.google.gwt.core.client.GWT; 10 | 11 | import live.connector.vertxui.client.fluent.Fluent; 12 | import live.connector.vertxui.client.fluent.ViewOnBase; 13 | import live.connector.vertxui.client.fluent.Viewable; 14 | 15 | /** 16 | * For DOM searching in junit tests. 17 | * 18 | * @author ng 19 | * 20 | */ 21 | public class VirtualDomSearch { 22 | 23 | private static void filter(Fluent target, Predicate filter, List result) { 24 | if (target == null) { 25 | return; 26 | } 27 | if (filter.test(target)) { 28 | result.add(target); 29 | } 30 | if (target.getChildren() != null) { 31 | for (Viewable child : target.getChildren()) { 32 | if (child instanceof Fluent) { 33 | filter((Fluent) child, filter, result); 34 | } else { 35 | filter(((ViewOnBase) child).getView(), filter, result); 36 | } 37 | } 38 | } 39 | } 40 | 41 | public static List getElementsBy(Predicate filter, Fluent target) { 42 | List result = new ArrayList<>(); 43 | if (GWT.isClient()) { 44 | console.warn( 45 | "Warning: avoid using VirtualDomSearch in a browser (including TestDom). If you want to use this, run the tests without TestDOM, which is 1000 times faster!"); 46 | } 47 | filter(target, filter, result); 48 | return result; 49 | } 50 | 51 | public static Fluent getElementById(String input, Fluent target) { 52 | List result = getElementsBy(f -> { 53 | String found = f.id(); 54 | if (found == null || !found.equals(input)) { 55 | return false; 56 | } else { 57 | return true; 58 | } 59 | }, target); 60 | if (result.isEmpty()) { 61 | return null; 62 | } else if (result.size() > 1) { 63 | throw new Error("Found " + result.size() + " nodes with id=" + input + " for " + target); 64 | } else { 65 | return result.get(0); 66 | } 67 | } 68 | 69 | public static List getElementsByTagName(String input, Fluent target) { 70 | return getElementsBy(f -> { 71 | String found = f.tag(); 72 | if (found == null || !found.equals(input)) { 73 | return false; 74 | } else { 75 | return true; 76 | } 77 | }, target); 78 | } 79 | 80 | public static List getElementsByClassName(String input, Fluent target) { 81 | return getElementsBy(f -> { 82 | String found = f.classs(); 83 | if (found == null || (!found.equals(input) && !found.contains(" " + input))) { 84 | return false; 85 | } else { 86 | return true; 87 | } 88 | }, target); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/Store.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | 6 | import com.github.nmorel.gwtjackson.client.ObjectMapper; 7 | import com.google.gwt.core.client.GWT; 8 | 9 | import live.connector.vertxui.client.transport.Pojofy; 10 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills; 11 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Bill; 12 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Grocery; 13 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Totals; 14 | 15 | /** 16 | * A three-way store, which handles all communication with the server. 17 | * 18 | * @author Niels Gorisse 19 | * 20 | */ 21 | public class Store { 22 | 23 | // URL's 24 | public static String totalsUrl = "/rest/totals"; 25 | public static String billsUrl = "/rest/bills"; 26 | public static String groceryUrl = "/rest/grocery"; 27 | 28 | public void getTotals(BiConsumer callback) { 29 | Pojofy.ajax("GET", totalsUrl, null, null, totalsMap, callback); 30 | } 31 | 32 | public void getBills(BiConsumer callback) { 33 | Pojofy.ajax("GET", billsUrl, null, null, billsMap, callback); 34 | } 35 | 36 | public void getGrocery(BiConsumer callback) { 37 | Pojofy.ajax("GET", groceryUrl, null, null, groceryMap, callback); 38 | } 39 | 40 | public void deleteGrocery(String value, Consumer revertCallback) { 41 | Pojofy.ajax("DELETE", groceryUrl, value, null, null, (status, __) -> { 42 | if (status != 200) { 43 | revertCallback.accept(value); 44 | } 45 | }); 46 | } 47 | 48 | public void addGrocery(String text, Consumer revertCallback) { 49 | Pojofy.ajax("POST", groceryUrl, text, null, null, (status, __) -> { 50 | if (status != 200) { 51 | revertCallback.accept(text); 52 | } 53 | }); 54 | } 55 | 56 | public void addBill(Bill bill, Consumer revertCallback) { 57 | Pojofy.ajax("POST", billsUrl, bill, billMap, null, (status, __) -> { 58 | if (status != 200) { 59 | revertCallback.accept(bill); 60 | } 61 | }); 62 | } 63 | 64 | // POJO MAPPERS 65 | public interface TotalsMap extends ObjectMapper { 66 | } 67 | 68 | public interface GroceryMap extends ObjectMapper { 69 | } 70 | 71 | public interface BillsMap extends ObjectMapper { 72 | } 73 | 74 | public interface BillMap extends ObjectMapper { 75 | } 76 | 77 | public static TotalsMap totalsMap = GWT.isClient() ? GWT.create(TotalsMap.class) : null; 78 | public static GroceryMap groceryMap = GWT.isClient() ? GWT.create(GroceryMap.class) : null; 79 | public static BillsMap billsMap = GWT.isClient() ? GWT.create(BillsMap.class) : null; 80 | public static BillMap billMap = GWT.isClient() ? GWT.create(BillMap.class) : null; 81 | 82 | } 83 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/todomvc/ServerTodoMVC.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.todomvc; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | import io.vertx.core.AbstractVerticle; 8 | import io.vertx.core.Context; 9 | import io.vertx.core.Vertx; 10 | import io.vertx.core.http.HttpServerOptions; 11 | import io.vertx.ext.web.Router; 12 | import io.vertx.ext.web.handler.StaticHandler; 13 | import live.connector.vertxui.client.FigWheelyClient; 14 | import live.connector.vertxui.samples.client.todomvc.View; 15 | import live.connector.vertxui.samples.server.AllExamplesServer; 16 | import live.connector.vertxui.server.FigWheelyServer; 17 | import live.connector.vertxui.server.VertxUI; 18 | 19 | public class ServerTodoMVC extends AbstractVerticle { 20 | 21 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 22 | 23 | public static void main(String[] args) { 24 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 25 | } 26 | 27 | @Override 28 | public void start() { 29 | boolean debug = true; 30 | 31 | // This line usually compiles java to javascript, generates a index.html 32 | // and serves the index.html and the javascript. However, this 33 | // application is also an example for using an existing index.html. 34 | // The only difference is at startup time: call VertxUI.with() 35 | // with url=null (2nd parameter) so it only compiles, give false 36 | // as last parameter so that there is no index.html generated (not 37 | // necessary), and then server folder /a/ with the javascript yourself. 38 | VertxUI.with(View.class, null, debug, false); 39 | Router router = Router.router(vertx); 40 | router.get("/a/*").handler(StaticHandler.create(VertxUI.getTargetFolder(debug) + "/a")); 41 | 42 | // serve the already existing index.html and .css files. 43 | router.get("/*").handler(FigWheelyServer.staticHandler("assets/todos", "/")); 44 | 45 | // if debugging, figwheely can notify the browser of code changes. 46 | if (debug) { 47 | router.get(FigWheelyClient.urlJavascript).handler(FigWheelyServer.create()); 48 | } 49 | 50 | // Make sure that when we exit, we close vertxes too. 51 | Runtime.getRuntime().addShutdownHook(new Thread() { 52 | public void run() { 53 | Context context = Vertx.currentContext(); 54 | if (context == null) { 55 | return; 56 | } 57 | Vertx vertx = context.owner(); 58 | vertx.deploymentIDs().forEach(vertx::undeploy); 59 | vertx.close(); 60 | } 61 | }); 62 | 63 | // Create and start the server 64 | vertx.createHttpServer(new HttpServerOptions().setCompressionSupported(true)).requestHandler(router) 65 | .listen(AllExamplesServer.port, listenHandler -> { 66 | if (listenHandler.failed()) { 67 | log.log(Level.SEVERE, "Startup error", listenHandler.cause()); 68 | System.exit(0);// stop on startup error 69 | } 70 | }); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/testjUnitWithDom/TestjUnitWithDom.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.testjUnitWithDom; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.document; 4 | import static live.connector.vertxui.client.test.Asserty.assertEquals; 5 | import static live.connector.vertxui.client.test.Asserty.assertTrue; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.junit.Test; 11 | 12 | import com.google.gwt.core.shared.GwtIncompatible; 13 | 14 | import elemental.dom.Element; 15 | import elemental.dom.NodeList; 16 | import live.connector.vertxui.client.fluent.Fluent; 17 | import live.connector.vertxui.client.test.TestDOM; 18 | import live.connector.vertxui.samples.client.mvcBootstrap.Controller; 19 | import live.connector.vertxui.samples.client.mvcBootstrap.Store; 20 | import live.connector.vertxui.samples.client.mvcBootstrap.View; 21 | 22 | /** 23 | * Run this or the suite in your IDE with junit. 24 | * 25 | * This type of testing involves compiling to javascript, and then running the 26 | * tests inside a headless browser. 27 | * 28 | * First of all you must use Asserty instead of Assert. The first method 29 | * everywhere is a description. This might feel uncomfortable but is necessary 30 | * because GWT does not point out where in a method the exception was thrown. 31 | * 32 | * Secondly, you can use registerJS() to register a lambda under a number. Use 33 | * runJS() to run the javascript test itsself. 34 | * 35 | * Note that in most cases you do not need to have the DOM itsself, so the 36 | * withoutDom method is absolutely preferred above this slower one. Think twice, 37 | * do you really need the DOM, or can you just talk to the virtual DOM inside 38 | * Fluent instead? In most cases you can. 39 | * 40 | * Do not create a constructor. It will be used in junit and in the browser. 41 | * 42 | * @author Niels Gorisse 43 | * 44 | */ 45 | 46 | public class TestjUnitWithDom extends TestDOM { 47 | 48 | private static int testNumber = 234; 49 | 50 | @GwtIncompatible 51 | @Test 52 | public void test() throws Exception { 53 | runJS(testNumber); 54 | System.out.println("This runs in Java"); 55 | } 56 | 57 | @Override 58 | public Map registerJS() { 59 | Map result = new HashMap<>(); 60 | result.put(testNumber, () -> mvcBootstrapStateChange()); 61 | return result; 62 | } 63 | 64 | public void mvcBootstrapStateChange() { 65 | Fluent.console.log("This runs in Javascript"); 66 | 67 | // This below is like 'new View().onModuleLoad()' 68 | Store transport = new StoreNone(); // BUT with a different store! 69 | View view = new View(); 70 | Controller controller = new Controller(transport, view); 71 | view.start(controller); 72 | 73 | // After pressing the menuBills button, check in the LI with class 74 | // active, the name of the A-link. 75 | controller.onMenuBills(null, null); 76 | 77 | NodeList b = document.getElementsByClassName("active"); 78 | assertEquals("length of actives", b.length(), 1); 79 | assertTrue("selected item title", ((Element) b.item(0).getChildNodes().at(0)).getTextContent().equals("Bills")); 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/chatEventBus/ExampleChatEventbus.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.chatEventBus; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.logging.Logger; 5 | 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.MultiMap; 8 | import io.vertx.core.Vertx; 9 | import io.vertx.ext.bridge.PermittedOptions; 10 | import io.vertx.ext.web.Router; 11 | import io.vertx.ext.web.handler.sockjs.BridgeOptions; 12 | import io.vertx.ext.web.handler.sockjs.SockJSHandler; 13 | import live.connector.vertxui.samples.client.Dto; 14 | import live.connector.vertxui.samples.client.chatEventBus.Client; 15 | import live.connector.vertxui.samples.server.AllExamplesServer; 16 | import live.connector.vertxui.server.transport.Pojofy; 17 | 18 | /** 19 | * Note that the chatbus has much more overhead than pure websockets or sockjs: 20 | * everyone is connected to everyone. Also, a clientside .send() does not 21 | * necessarily go to the server but after more browsers connect, it will go to 22 | * one other browser too. 23 | * 24 | * @author Niels Gorisse 25 | * 26 | */ 27 | public class ExampleChatEventbus extends AbstractVerticle { 28 | 29 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 30 | 31 | public static void main(String[] args) { 32 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 33 | } 34 | 35 | @Override 36 | public void start() { 37 | // Initialize the router and a webserver with HTTP-compression 38 | Router router = Router.router(vertx); 39 | 40 | PermittedOptions freewayOK = new PermittedOptions().setAddress(Client.freeway); 41 | PermittedOptions myDtoOK = new PermittedOptions().setAddress(Client.addressPojo); 42 | BridgeOptions firewall = new BridgeOptions().addInboundPermitted(freewayOK).addOutboundPermitted(freewayOK) 43 | .addInboundPermitted(myDtoOK).addOutboundPermitted(myDtoOK); 44 | router.route(Client.url + "/*").handler(SockJSHandler.create(vertx).bridge(firewall 45 | // If you want to know the sender, add it as header: 46 | // , be -> { 47 | // if (be.type() == BridgeEventType.RECEIVE || be.type() == 48 | // BridgeEventType.PUBLISH) { 49 | // JsonObject headers = be.getRawMessage().getJsonObject("headers"); 50 | // if (headers == null) { 51 | // headers = new JsonObject(); 52 | // be.getRawMessage().put("headers", headers); } 53 | // headers.put("sender", be.socket().writeHandlerID()); } 54 | // be.complete(true); } 55 | )); 56 | 57 | // to broadcast: vertx.eventBus().publish(Client.freeway,"Bla"); 58 | // to receive: vertx.eventBus().consumer(Client.freeway, m -> ... ); 59 | // broadcasting to everyone is done automaticly by .publish() 60 | 61 | // extra: pojo example 62 | Pojofy.eventbus(Client.addressPojo, Dto.class, this::serviceDoSomething); 63 | 64 | AllExamplesServer.start(Client.class, router); 65 | } 66 | 67 | public Dto serviceDoSomething(Dto received, MultiMap headers) { 68 | log.info("Extra example: received a dto with action=" + headers.get("action") + " and color=" + received.color); 69 | return new Dto("red"); // gives an error when publish() is used and not 70 | // send() 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/testjUnitWithoutDom/TestjUnitWithoutDom.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.testjUnitWithoutDom; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.List; 7 | 8 | import org.junit.Test; 9 | 10 | import com.google.gwt.core.shared.GwtIncompatible; 11 | 12 | import live.connector.vertxui.client.fluent.Fluent; 13 | import live.connector.vertxui.client.test.VirtualDomSearch; 14 | import live.connector.vertxui.samples.client.mvcBootstrap.Controller; 15 | import live.connector.vertxui.samples.client.mvcBootstrap.Store; 16 | import live.connector.vertxui.samples.client.mvcBootstrap.View; 17 | import live.connector.vertxui.samples.client.testjUnitWithDom.StoreNone; 18 | 19 | /** 20 | * Run this class in junit in your IDE. Note that this is the preferred way of 21 | * testing your view, you don't need the DOM is you are writing Fluent HTML. 22 | * 23 | * If you prefer, you can place this class in your .server package too, doesn't 24 | * matter. 25 | * 26 | * @author Niels Gorisse 27 | * 28 | */ 29 | 30 | @GwtIncompatible 31 | public class TestjUnitWithoutDom { 32 | 33 | @Test 34 | public void mvcBootStrapTitle() { 35 | 36 | // There is zero extra code for junit inside Fluent, so you have to 37 | // clean the virtualDOM manually between testcases. For the first method 38 | // in the class you can leave it out. 39 | Fluent.clearVirtualDOM(); 40 | 41 | // This is like 'new View().onModuleLoad()' 42 | Store transport = new StoreNone(); // BUT with a different store! 43 | View view = new View(); 44 | Controller controller = new Controller(transport, view); 45 | view.start(controller); 46 | 47 | // Check the title (using 'id') 48 | Fluent a = VirtualDomSearch.getElementById("titlerForJunitTest", Fluent.body); 49 | assertTrue(a != null); 50 | assertTrue(a.tag().equals("H1")); 51 | } 52 | 53 | @Test 54 | public void mvcBootstrapStateChange() { 55 | 56 | // There is zero extra code for junit inside Fluent, so you have to 57 | // clean the virtualDOM manually between testcases. For the first method 58 | // in the class you can leave it out. 59 | Fluent.clearVirtualDOM(); 60 | 61 | // This is like 'new View().onModuleLoad()' 62 | Store transport = new StoreNone(); // BUT with a different store! 63 | View view = new View(); 64 | Controller controller = new Controller(transport, view); 65 | view.start(controller); 66 | 67 | // After pressing the menuBills button, check in the LI with class 68 | // active, the name of the A-link. 69 | controller.onMenuBills(null, null); 70 | List b = VirtualDomSearch.getElementsByClassName("active", Fluent.body); 71 | assertEquals(b.size(), 1); 72 | assertTrue(((Fluent) b.get(0).getChildren().get(0)).txt().equals("Bills")); 73 | } 74 | 75 | @Test 76 | public void nonBodyTest() { 77 | Fluent root = Fluent.Div(); 78 | root.h1(null, "blabla"); 79 | root.ul("aClass", Fluent.Li().txt("bladiebla"), Fluent.Li().txt("pooo")); 80 | 81 | assertEquals(1, VirtualDomSearch.getElementsByClassName("aClass", root).size()); 82 | assertEquals(2, VirtualDomSearch.getElementsByTagName("LI", root).size()); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/server/mvcBootstrap/ServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.server.mvcBootstrap; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Date; 7 | import java.util.HashMap; 8 | 9 | import io.vertx.core.AbstractVerticle; 10 | import io.vertx.core.Vertx; 11 | import io.vertx.ext.web.Router; 12 | import io.vertx.ext.web.RoutingContext; 13 | import live.connector.vertxui.samples.client.mvcBootstrap.Store; 14 | import live.connector.vertxui.samples.client.mvcBootstrap.View; 15 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills; 16 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Bill; 17 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Name; 18 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Grocery; 19 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Totals; 20 | import live.connector.vertxui.samples.server.AllExamplesServer; 21 | import live.connector.vertxui.server.transport.Pojofy; 22 | 23 | public class ServerBootstrap extends AbstractVerticle { 24 | 25 | // private final static Logger log = 26 | // Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 27 | 28 | public static void main(String[] args) { 29 | Vertx.vertx().deployVerticle(MethodHandles.lookup().lookupClass().getName()); 30 | } 31 | 32 | // Fake data 33 | private Bills bills = new Bills(); 34 | private Grocery grocery = new Grocery(); 35 | 36 | @Override 37 | public void start() { 38 | // Fake initial data 39 | bills.all = new ArrayList<>(); 40 | Bill bill = new Bill(Name.Niels, 55, "Weekend shopping", new Date()); 41 | bills.all.add(bill); 42 | grocery.all = new ArrayList<>(); 43 | grocery.all.add("Chocolate milk"); 44 | 45 | // Route all URLs 46 | Router router = Router.router(vertx); 47 | router.get(Store.totalsUrl).handler(Pojofy.ajax(null, this::getTotals)); 48 | router.get(Store.billsUrl).handler(Pojofy.ajax(null, this::getBills)); 49 | router.post(Store.billsUrl).handler(Pojofy.ajax(Bill.class, this::addBill)); 50 | 51 | router.get(Store.groceryUrl).handler(Pojofy.ajax(null, this::getGrocery)); 52 | router.post(Store.groceryUrl).handler(Pojofy.ajax(null, this::addGrocery)); 53 | router.delete(Store.groceryUrl).handler(Pojofy.ajax(null, this::delGrocery)); 54 | 55 | AllExamplesServer.start(View.class, router); 56 | } 57 | 58 | public Totals getTotals(String __, RoutingContext context) { 59 | Totals result = new Totals(); 60 | result.all = new HashMap<>(); 61 | result.all.put(Name.Niels, bills.all.stream().mapToDouble(t -> t.who == Name.Niels ? t.amount : 0.0).sum()); 62 | result.all.put(Name.Linda, bills.all.stream().mapToDouble(t -> t.who == Name.Linda ? t.amount : 0.0).sum()); 63 | return result; 64 | } 65 | 66 | public Grocery getGrocery(String __, RoutingContext context) { 67 | return grocery; 68 | } 69 | 70 | public void addGrocery(String text, RoutingContext context) { 71 | grocery.all.add(text); 72 | } 73 | 74 | public void delGrocery(String text, RoutingContext context) { 75 | grocery.all.remove(text); 76 | } 77 | 78 | public Bills getBills(String empty, RoutingContext context) { 79 | Collections.sort(bills.all); 80 | return bills; 81 | } 82 | 83 | public long addBill(Bill bill, RoutingContext context) { 84 | bill.id = bills.all.size(); 85 | bills.all.add(bill); 86 | return bill.id; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/fluent/Css.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.fluent; 2 | 3 | /** 4 | * A list of standarised CSS elements 5 | * 6 | * @author ng 7 | * 8 | */ 9 | public enum Css { 10 | 11 | alignContent, alignItems, alignSelf, animation, animationDelay, animationDirection, // 12 | animationDuration, animationFillMode, animationIterationCount, animationName, // 13 | animationTimingFunction, animationPlayState, background, backgroundAttachment, // 14 | backgroundColor, backgroundImage, backgroundPosition, backgroundRepeat, backgroundClip, // 15 | backgroundOrigin, backgroundSize, backfaceVisibility, border, borderBottom, borderBottomColor, // 16 | borderBottomLeftRadius, borderBottomRightRadius, borderBottomStyle, borderBottomWidth, borderCollapse, // 17 | borderColor, borderImage, borderImageOutset, borderImageRepeat, borderImageSlice, borderImageSource, // 18 | borderImageWidth, borderLeft, borderLeftColor, borderLeftStyle, borderLeftWidth, borderRadius, // 19 | borderRight, borderRightColor, borderRightStyle, borderRightWidth, borderSpacing, borderStyle, // 20 | borderTop, borderTopColor, borderTopLeftRadius, borderTopRightRadius, borderTopStyle, // 21 | borderTopWidth, borderWidth, bottom, boxDecorationBreak, boxShadow, boxSizing, captionSide, // 22 | clear, clip, color, columnCount, columnFill, columnGap, columnRule, columnRuleColor, // 23 | columnRuleStyle, columnRuleWidth, columns, columnSpan, columnWidth, content, counterIncrement, // 24 | counterReset, cursor, direction, display, emptyCells, filter, flex, flexBasis, flexDirection, // 25 | flexFlow, flexGrow, flexShrink, flexWrap, Float, font, fontFamily, fontSize, fontStyle, // 26 | fontVariant, fontWeight, fontSizeAdjust, fontStretch, hangingPunctuation, height, hyphens, // 27 | icon, imageOrientation, justifyContent, left, letterSpacing, lineHeight, listStyle, // 28 | listStyleImage, listStylePosition, listStyleType, margin, marginBottom, marginLeft, // 29 | marginRight, marginTop, maxHeight, maxWidth, minHeight, minWidth, navDown, navIndex, // 30 | navLeft, navRight, navUp, opacity, order, orphans, outline, outlineColor, // 31 | outlineOffset, outlineStyle, outlineWidth, overflow, overflowX, overflowY, // 32 | padding, paddingBottom, paddingLeft, paddingRight, paddingTop, pageBreakAfter, // 33 | pageBreakBefore, pageBreakInside, perspective, perspectiveOrigin, position, // 34 | quotes, resize, right, tableLayout, tabSize, textAlign, textAlignLast, textDecoration, // 35 | textDecorationColor, textDecorationLine, textDecorationStyle, textIndent, textJustify, // 36 | textOverflow, textShadow, textTransform, top, transform, transformOrigin, transformStyle, // 37 | transition, transitionProperty, transitionDuration, transitionTimingFunction, // 38 | transitionDelay, unicodeBidi, verticalAlign, visibility, whiteSpace, width, wordBreak, // 39 | wordSpacing, wordWrap, widows, zIndex; 40 | 41 | public String nameValid() { 42 | if (name().equals("Float")) { 43 | return "float"; 44 | } 45 | StringBuffer result = new StringBuffer(); 46 | for (char a : name().toCharArray()) { 47 | if (Character.isUpperCase(a)) { 48 | result.append("-"); 49 | } 50 | result.append(a); 51 | } 52 | return result.toString().toLowerCase(); 53 | } 54 | 55 | public static Css valueOfValid(String lineStyle) { 56 | if (lineStyle.equals("float")) { 57 | return Css.Float; 58 | } 59 | StringBuffer result = new StringBuffer(); 60 | for (String y : lineStyle.split("-")) { 61 | if (result.length() == 0) { 62 | result.append(y); 63 | } else { 64 | result.append(y.substring(0, 1).toUpperCase() + y.substring(1)); 65 | } 66 | } 67 | return valueOf(result.toString()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/todomvc/Controller.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.todomvc; 2 | 3 | import java.util.List; 4 | 5 | import com.google.gwt.core.client.GWT; 6 | 7 | import elemental.events.KeyboardEvent; 8 | import elemental.events.MouseEvent; 9 | import elemental.events.UIEvent; 10 | import live.connector.vertxui.client.fluent.Att; 11 | import live.connector.vertxui.client.fluent.Fluent; 12 | import live.connector.vertxui.samples.client.todomvc.State.Buttons; 13 | 14 | public class Controller { 15 | 16 | // State, owned by this controller 17 | private State state = new State(); 18 | 19 | // View and store. The store owns the models. 20 | private View view; 21 | private Store store; 22 | 23 | public Controller(Store store, View view) { 24 | this.store = store; 25 | this.view = view; 26 | 27 | // Get the initial state for the buttons 28 | if (GWT.isClient()) { 29 | String url = Fluent.window.getLocation().getHref(); 30 | int start = url.indexOf("#"); 31 | if (start == -1) { 32 | state.setButtons(Buttons.All); 33 | } else { 34 | url = url.substring(start + 1, url.length()); 35 | if (url.equals("/active")) { 36 | state.setButtons(Buttons.Active); 37 | } else if (url.equals("/completed")) { 38 | state.setButtons(Buttons.Completed); 39 | } else { 40 | state.setButtons(Buttons.All); 41 | } 42 | } 43 | } 44 | 45 | } 46 | 47 | public List getModels() { 48 | return store.getAll(); 49 | } 50 | 51 | public State getState() { 52 | return state; 53 | } 54 | 55 | public void onInput(Fluent fluent, KeyboardEvent event) { 56 | if (event.getKeyCode() == KeyboardEvent.KeyCode.ENTER) { 57 | String value = fluent.domValue().trim(); 58 | if (!value.isEmpty()) { 59 | addModel(value); 60 | fluent.att(Att.value, ""); 61 | } 62 | } 63 | } 64 | 65 | public void addModel(String value) { 66 | store.add(new Model(value, false)); 67 | view.syncModel(); 68 | } 69 | 70 | public void onSelectAll(Fluent fluent, MouseEvent __) { 71 | store.setCompletedAll(fluent.domChecked()); 72 | view.syncModel(); 73 | } 74 | 75 | public void onSelect(Fluent fluent, Model model) { 76 | // It is -unfortunately- very important that we call fluent.domChecked() 77 | // here, because this also synchronizes the latest visual state into the 78 | // virtual DOM. If we would not call it (or use it), we would have 79 | // checkboxes that were still checked when switching from view. This is 80 | // because Fluent changes as less as possible, and it simply doesn't 81 | // know that something is changed in the DOM. 82 | store.setCompleted(model, fluent.domChecked()); 83 | view.syncModel(); 84 | } 85 | 86 | public void onAll(Fluent __, MouseEvent ___) { 87 | state.setButtons(Buttons.All); 88 | view.syncState(); 89 | } 90 | 91 | public void onActive(Fluent __, MouseEvent ___) { 92 | state.setButtons(Buttons.Active); 93 | view.syncState(); 94 | } 95 | 96 | public void onCompleted(Fluent __, MouseEvent ___) { 97 | state.setButtons(Buttons.Completed); 98 | view.syncState(); 99 | } 100 | 101 | public void onDestroy(Model model) { 102 | store.remove(model); 103 | view.syncModel(); 104 | } 105 | 106 | public void onClearCompleted(Fluent __, MouseEvent ___) { 107 | store.removeCompletedAll(); 108 | view.syncModel(); 109 | } 110 | 111 | public void onEditStart(Model item) { 112 | state.setEditing(item); 113 | view.syncState(); 114 | } 115 | 116 | public void onEditEnd(Fluent __, UIEvent ___) { 117 | state.setEditing(null); 118 | view.syncState(); 119 | } 120 | 121 | public void onEditKey(Fluent fluent, KeyboardEvent event) { 122 | if (event.getKeyCode() != KeyboardEvent.KeyCode.ENTER) { 123 | return; 124 | } 125 | String value = fluent.domValue().trim(); 126 | Model model = state.getEditing(); 127 | if (!value.isEmpty()) { 128 | store.setText(model, value); 129 | } else { 130 | store.remove(model); 131 | } 132 | state.setEditing(null); 133 | view.syncModel(); 134 | view.syncState(); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/test/TestDOM.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.test; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.stream.Stream; 10 | 11 | import org.apache.commons.io.IOUtils; 12 | import org.junit.After; 13 | import org.junit.Before; 14 | 15 | import com.google.gwt.core.client.EntryPoint; 16 | import com.google.gwt.core.shared.GwtIncompatible; 17 | import com.machinepublishers.jbrowserdriver.JBrowserDriver; 18 | import com.machinepublishers.jbrowserdriver.Settings; 19 | 20 | import live.connector.vertxui.server.VertxUI; 21 | 22 | /** 23 | * A junit testrunner which starts as a normal java junit test, but underneath 24 | * converts the class to a javascript page, fires up a headless browser. 25 | * Register with registerJS() and run your tests with runJS(). 26 | * 27 | * @author Niels Gorisse 28 | * 29 | */ 30 | public abstract class TestDOM implements EntryPoint { 31 | 32 | @GwtIncompatible 33 | private JBrowserDriver jBrowser; 34 | 35 | @GwtIncompatible 36 | @Before 37 | public void before() { 38 | // for correct stacktrace decoding, we need the debug info. 39 | boolean debug = true; 40 | 41 | // Convert to javascript 42 | VertxUI.with(this.getClass(), null, debug, true); 43 | 44 | // Start the headless browser 45 | jBrowser = new JBrowserDriver(Settings.builder().logJavascript(true).build()); 46 | jBrowser.get("file:///" + new File(VertxUI.getTargetFolder(debug) + "/index.html").getAbsolutePath()); 47 | } 48 | 49 | @GwtIncompatible 50 | @After 51 | public void after() { 52 | if (jBrowser != null) { 53 | jBrowser.quit(); 54 | } 55 | } 56 | 57 | public abstract Map registerJS(); 58 | 59 | @Override 60 | public void onModuleLoad() { 61 | Asserty.asserty(registerJS()); 62 | } 63 | 64 | @GwtIncompatible 65 | public void runJS(int which) throws Exception { 66 | try { 67 | String error = (String) jBrowser.executeScript("return window.asserty(" + which + ");"); 68 | if (error == null) { 69 | return; // OK 70 | } 71 | 72 | // find symbolmap-file 73 | Optional symbolMap = Stream 74 | .of(new File(VertxUI.getTargetFolder(true) + "/WEB-INF/deploy/a/symbolMaps").listFiles()) 75 | .filter(f -> f.getName().endsWith(".symbolMap")).findFirst(); 76 | if (symbolMap.isPresent() == false) { 77 | // no symbolmap file, is OK but less readable 78 | throw new Exception(error); 79 | } 80 | 81 | // Read it 82 | List maps = IOUtils.readLines(new FileReader(symbolMap.get())); 83 | 84 | // Apply 85 | String[] errors = error.split("\n"); 86 | Exception exception = new Exception(errors[0]); 87 | List stacks = new ArrayList<>(); 88 | boolean skippingStart = true; 89 | for (int x = 1; x < errors.length; x++) { 90 | int find = errors[x].indexOf("@"); 91 | if (find == -1) { // garbage 92 | continue; 93 | } 94 | String fileAndMethod = errors[x].substring(0, find); 95 | Optional line = maps.stream().filter(l -> l.startsWith(fileAndMethod)).findFirst(); 96 | if (line.isPresent() == false) { // garbage 97 | continue; 98 | } 99 | String[] lne = line.get().split(","); 100 | 101 | // skipping the first few 'getting outside javascript' 102 | if (lne[2].equals("java.lang.AssertionError")) { 103 | skippingStart = false; 104 | continue; 105 | } else if (skippingStart) { 106 | continue; 107 | } 108 | StackTraceElement stack = new StackTraceElement(lne[2], lne[3], lne[4], Integer.parseInt(lne[5])); 109 | stacks.add(stack); 110 | } 111 | exception.setStackTrace(stacks.toArray(new StackTraceElement[0])); 112 | throw exception; 113 | } finally { 114 | // unfortunately jBrowser doesn't react on a runtime shutdown hook, 115 | // otherwise we could just recycle the jBrowser.... 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/Client.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator; 2 | 3 | import static live.connector.vertxui.client.fluent.FluentBase.body; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | 8 | import com.google.gwt.core.client.EntryPoint; 9 | 10 | import live.connector.vertxui.client.fluent.Css; 11 | import live.connector.vertxui.client.fluent.Fluent; 12 | import live.connector.vertxui.client.fluent.ViewOn; 13 | import live.connector.vertxui.samples.client.energyCalculator.components.ChartJs; 14 | 15 | public class Client implements EntryPoint { 16 | 17 | private Shower shower; 18 | private Heating heating; 19 | private SolarTubes solarTubes; 20 | private SolarPanels solarPanels; 21 | private ChartJs electricChart; 22 | private ChartJs waterChart; 23 | private Cooking cooking; 24 | private Stove stove; 25 | private ViewOn> infoAndwarnings; 26 | 27 | public static ArrayList getScripts() { 28 | return ChartJs.getScripts(); 29 | } 30 | 31 | public Client() { 32 | // try to put everthing in
33 | Fluent root = Fluent.getElementById("here"); 34 | if (root == null) { 35 | root = body; 36 | } 37 | root.p().txt( 38 | "By using this energy calculator, you understand that it is extremely beta, and probably contains serious errors. " 39 | + "Please help! For example, if you are an engineer and you see an improvement in the" 40 | + " heat calculation, or if you know a lot about wood and you see an error: e-mail us!") 41 | .css(Css.color, "red"); 42 | 43 | Fluent conclusions = root.div(); 44 | conclusions.css(Css.position, "sticky", Css.top, "0px"); // sticky 45 | conclusions.css(Css.backgroundColor, "rgba(255, 255, 255, 0.8)"); // background 46 | electricChart = new ChartJs(conclusions, 500, 300, "Electricity (kW)"); 47 | electricChart.css(Css.Float, "right"); // position 48 | waterChart = new ChartJs(conclusions, 500, 300, "Warm water (kW)"); 49 | infoAndwarnings = conclusions.add(new HashMap<>(), infoAndWarnings -> { 50 | Fluent result = Fluent.Div(); 51 | infoAndWarnings.forEach((key, message) -> { 52 | Fluent span = result.span(null, message); 53 | if (key.startsWith("info")) { 54 | span.css(Css.color, "purple"); 55 | result.br(); 56 | } else { 57 | span.css(Css.color, "red"); 58 | result.br(); 59 | } 60 | }); 61 | return result; 62 | }); 63 | 64 | new Heating(root, waterChart, this); 65 | new Shower(root, waterChart, this); 66 | new SolarTubes(root, waterChart, this); 67 | 68 | new Cooking(root, this); 69 | new SolarPanels(root, this); 70 | 71 | new Stove(root, this); 72 | } 73 | 74 | protected void setShower(Shower shower) { 75 | this.shower = shower; 76 | } 77 | 78 | protected Shower getShower() { 79 | return shower; 80 | } 81 | 82 | protected ViewOn> getInfoAndWarnings() { 83 | return infoAndwarnings; 84 | } 85 | 86 | protected void setHeating(Heating heating) { 87 | this.heating = heating; 88 | } 89 | 90 | protected Heating getHeating() { 91 | return heating; 92 | } 93 | 94 | protected SolarTubes getSolarTubes() { 95 | return solarTubes; 96 | } 97 | 98 | protected void setSolarTubes(SolarTubes solarTubes) { 99 | this.solarTubes = solarTubes; 100 | } 101 | 102 | protected ChartJs getElectricChart() { 103 | return electricChart; 104 | } 105 | 106 | protected ChartJs getWaterChart() { 107 | return waterChart; 108 | } 109 | 110 | protected Cooking getCooking() { 111 | return cooking; 112 | } 113 | 114 | protected void setCooking(Cooking cooking) { 115 | this.cooking = cooking; 116 | } 117 | 118 | public void setSolarPanels(SolarPanels solarPanels) { 119 | this.solarPanels = solarPanels; 120 | } 121 | 122 | public SolarPanels getSolarPanels() { 123 | return solarPanels; 124 | } 125 | 126 | public Stove getStove() { 127 | return stove; 128 | } 129 | 130 | public void setStove(Stove stove) { 131 | this.stove = stove; 132 | } 133 | 134 | @Override 135 | public void onModuleLoad() { 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/transport/EventBus.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.transport; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | 6 | import com.google.gwt.core.client.JavaScriptObject; 7 | 8 | import elemental.events.EventListener; 9 | import elemental.json.JsonObject; 10 | import live.connector.vertxui.client.fluent.Fluent; 11 | 12 | /** 13 | * A Vert.X sockJs EventBus wrapper for 14 | * https://github.com/vert-x3/vertx-bus-bower . WARNING: send() does not go to 15 | * the server but to one randomly chosen connected machine, which can be either 16 | * the server or any browser that has connected so far - be aware of that!! 17 | * 18 | * @author Niels Gorisse 19 | * 20 | */ 21 | public class EventBus extends JavaScriptObject { 22 | 23 | protected EventBus() { 24 | } 25 | 26 | /** 27 | * You have to load 28 | * "https://raw.githubusercontent.com/vert-x3/vertx-bus-bower/master/vertx-eventbus.js" 29 | * first. You can do this here with scriptSync() or with 30 | * EntryPoint::getScripts(). 31 | * 32 | * @param then what to do when the script is loaded 33 | */ 34 | public static void importJs(Consumer then) { 35 | SockJS.importJs((Void) -> { 36 | Fluent.head.scriptSync(then, 37 | "https://raw.githubusercontent.com/vert-x3/vertx-bus-bower/master/vertx-eventbus.js"); 38 | }); 39 | } 40 | 41 | public final native static EventBus create(String address, String[] options) /*-{ 42 | return new window.top.EventBus(address, options); 43 | }-*/; 44 | 45 | public final native void onopen(EventListener listener)/*-{ 46 | this.onopen = @elemental.js.dom.JsElementalMixinBase::getHandlerFor(Lelemental/events/EventListener;)(listener); 47 | }-*/; 48 | 49 | /** 50 | * Warning: the thing you send can go to anyone connected to the eventbus, 51 | * including other browsers that are connected. So please handle with care! 52 | */ 53 | /** 54 | * @param address the address 55 | * @param message the message 56 | * @param headers the headers 57 | * @param receiver the callback 58 | */ 59 | public final native void send(String address, String message, JsonObject headers, 60 | BiConsumer receiver)/*-{ 61 | this.send(address,message,headers, 62 | function(a,b) 63 | { @live.connector.vertxui.client.transport.EventBus::doit(Ljava/util/function/BiConsumer;Lelemental/json/JsonObject;Lelemental/json/JsonObject;) 64 | (receiver,a,b); } 65 | ); 66 | }-*/; 67 | 68 | public final native void registerHandler(String address, JsonObject headers, 69 | BiConsumer receiver)/*-{ 70 | this.registerHandler(address,headers, 71 | function(a,b) 72 | { @live.connector.vertxui.client.transport.EventBus::doit(Ljava/util/function/BiConsumer;Lelemental/json/JsonObject;Lelemental/json/JsonObject;) 73 | (receiver,a,b); } 74 | ); 75 | }-*/; 76 | 77 | private static void doit(BiConsumer handler, JsonObject error, JsonObject message) { 78 | handler.accept(error, message); 79 | } 80 | 81 | public final native void unregisterHandler(String address, JsonObject headers, 82 | BiConsumer receiver)/*-{ 83 | this.unregisterHandler(address,headers, function(a,b) 84 | { @live.connector.vertxui.client.transport.EventBus::doit(Ljava/util/function/BiConsumer;Lelemental/json/JsonObject;Lelemental/json/JsonObject;) 85 | (receiver,a,b); } 86 | ); 87 | }-*/; 88 | 89 | public final native void publish(String address, String message, JsonObject headers)/*-{ 90 | this.publish(address,message,headers); 91 | }-*/; 92 | 93 | public final native void onclose(EventListener listener) /*-{ 94 | this.onclose = @elemental.js.dom.JsElementalMixinBase::getHandlerFor(Lelemental/events/EventListener;)(listener); 95 | }-*/; 96 | 97 | public final native void onerror(EventListener listener) /*-{ 98 | this.onerror = @elemental.js.dom.JsElementalMixinBase::getHandlerFor(Lelemental/events/EventListener;)(listener); 99 | }-*/; 100 | 101 | } 102 | -------------------------------------------------------------------------------- /vertxui-core/src/test/java/live/connector/vertxui/client/FluentInnerRendering.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.Li; 4 | import static live.connector.vertxui.client.fluent.Fluent.Ul; 5 | import static live.connector.vertxui.client.test.Asserty.assertEquals; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.junit.Test; 11 | 12 | import com.google.gwt.core.shared.GwtIncompatible; 13 | 14 | import elemental.dom.NodeList; 15 | import live.connector.vertxui.client.fluent.Fluent; 16 | import live.connector.vertxui.client.fluent.ViewOn; 17 | import live.connector.vertxui.client.test.TestDOM; 18 | 19 | public class FluentInnerRendering extends TestDOM { 20 | 21 | @GwtIncompatible 22 | @Test 23 | public void test() throws Exception { 24 | runJS(3); 25 | } 26 | 27 | @Override 28 | public Map registerJS() { 29 | Map result = new HashMap<>(); 30 | result.put(3, () -> { 31 | inner(); 32 | middleChildRemoval(); 33 | }); 34 | return result; 35 | } 36 | 37 | private void middleChildRemoval() { 38 | ViewOn children = Fluent.body.add(0, i -> { 39 | switch (i) { 40 | case 0: 41 | return Ul(null, Li(null, "a"), Li(null, "b"), Li(null, "c")); 42 | case 1: 43 | return Ul(null, Li(null, "a"), Li(null, "b"), Li(null, "c")); 44 | case 2: 45 | return Ul(null, Li(null, "a"), Li(null, "c")); 46 | case 3: 47 | return Ul(null, Li(null, "a"), Li(null, "b")); 48 | case 4: 49 | return Ul(null, Li(null, "c"), Li(null, "b"), Li(null, "a")); 50 | default: 51 | return null; 52 | } 53 | }); 54 | NodeList nodes = Fluent.document.getElementsByTagName("LI"); 55 | assertEquals("1. length", 3, nodes.length()); 56 | assertEquals("1. first value", "a", nodes.item(0).getTextContent()); 57 | assertEquals("1. second value", "b", nodes.item(1).getTextContent()); 58 | assertEquals("1. third value", "c", nodes.item(2).getTextContent()); 59 | 60 | children.state(1); 61 | nodes = Fluent.document.getElementsByTagName("LI"); 62 | assertEquals("2. length", 3, nodes.length()); 63 | assertEquals("2. first value", "a", nodes.item(0).getTextContent()); 64 | assertEquals("2. second value", "b", nodes.item(1).getTextContent()); 65 | assertEquals("2. third alue", "c", nodes.item(2).getTextContent()); 66 | 67 | children.state(2); 68 | nodes = Fluent.document.getElementsByTagName("LI"); 69 | assertEquals("3. length", 2, nodes.length()); 70 | assertEquals("3. first value", "a", nodes.item(0).getTextContent()); 71 | assertEquals("3. second value", "c", nodes.item(1).getTextContent()); 72 | 73 | children.state(3); 74 | nodes = Fluent.document.getElementsByTagName("LI"); 75 | assertEquals("4. length", 2, nodes.length()); 76 | assertEquals("4. first value", "a", nodes.item(0).getTextContent()); 77 | assertEquals("4. second value", "b", nodes.item(1).getTextContent()); 78 | 79 | children.state(1); 80 | nodes = Fluent.document.getElementsByTagName("LI"); 81 | assertEquals("5. length", 3, nodes.length()); 82 | assertEquals("5. first value", "a", nodes.item(0).getTextContent()); 83 | assertEquals("5. second value", "b", nodes.item(1).getTextContent()); 84 | assertEquals("5. third alue", "c", nodes.item(2).getTextContent()); 85 | 86 | children.state(4); 87 | nodes = Fluent.document.getElementsByTagName("LI"); 88 | assertEquals("6. length", 3, nodes.length()); 89 | assertEquals("6. first value", "c", nodes.item(0).getTextContent()); 90 | assertEquals("6. second value", "b", nodes.item(1).getTextContent()); 91 | assertEquals("6. third alue", "a", nodes.item(2).getTextContent()); 92 | 93 | } 94 | 95 | private void inner() { 96 | String starttext = Math.random() + "aSeed"; 97 | Fluent div = Fluent.body.div(); 98 | 99 | assertEquals("0. real DOM is empty string before start", "", div.dom().getTextContent()); 100 | 101 | div.txt(starttext); 102 | 103 | assertEquals("1. given value match virtual DOM", starttext, div.txt()); 104 | assertEquals("1. given value match real DOM", starttext, div.dom().getTextContent()); 105 | 106 | div.txt(starttext); // manualtest: should skip 107 | 108 | div.txt(null); 109 | assertEquals("2. given value match virtual DOM", null, div.txt()); 110 | assertEquals("2. given value match real DOM not null but empty string", "", div.dom().getTextContent()); 111 | 112 | div.txt(null); // manual test: should skip 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/Shower.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator; 2 | 3 | import live.connector.vertxui.client.fluent.Att; 4 | import live.connector.vertxui.client.fluent.Fluent; 5 | import live.connector.vertxui.client.fluent.ViewOn; 6 | import live.connector.vertxui.samples.client.energyCalculator.components.ChartJs; 7 | import live.connector.vertxui.samples.client.energyCalculator.components.Utils; 8 | 9 | public class Shower { 10 | 11 | private double duration = 9, lPerMinute = 7, degrees = 38, timesPerWeek = 7, waterDegrees = 11, people = 1; 12 | private ViewOn conclusion; 13 | private double[] result = new double[12]; 14 | private boolean enabled = true; 15 | 16 | public Shower(Fluent body, ChartJs chart, Client client) { 17 | client.setShower(this); 18 | 19 | body.h2(null, "Shower"); 20 | body.input(null, "checkbox").att(Att.checked, "1").click((fluent, b) -> { 21 | this.enabled = fluent.domChecked(); 22 | conclusion.sync(); 23 | }); 24 | body.span(null, " People: "); 25 | body.select(null, people + "", Utils.getSelectNumbers(1, 1, 15.0)).changed((fluent, ___) -> { 26 | people = Double.parseDouble(fluent.domSelectedOptions()[0]); 27 | conclusion.sync(); 28 | }); 29 | body.span(null, ", duration: "); 30 | body.select(null, duration + "", Utils.getSelectNumbers(1.0, 1, 15.0)).changed((fluent, ___) -> { 31 | duration = Double.parseDouble(fluent.domSelectedOptions()[0]); 32 | conclusion.sync(); 33 | }); 34 | body.span(null, " minutes with a shower head that uses "); 35 | body.select(null, lPerMinute + "", Utils.getSelectNumbers(5, 1, 12)).changed((fluent, ___) -> { 36 | lPerMinute = Double.parseDouble(fluent.domSelectedOptions()[0]); 37 | conclusion.sync(); 38 | }); 39 | body.span(null, " liters per minute, at "); 40 | body.select(null, degrees + "", Utils.getSelectNumbers(30, 1, 45)).changed((fluent, ___) -> { 41 | degrees = Double.parseDouble(fluent.domSelectedOptions()[0]); 42 | conclusion.sync(); 43 | }); 44 | body.span(null, " degrees, about "); 45 | body.select(null, timesPerWeek + "", Utils.getSelectNumbers(1, 1, 7)).changed((fluent, ___) -> { 46 | timesPerWeek = Double.parseDouble(fluent.domSelectedOptions()[0]); 47 | conclusion.sync(); 48 | }); 49 | body.span(null, " times per week."); 50 | conclusion = body.add(null, ___ -> { 51 | double totalLiters = duration * lPerMinute; 52 | double delta = degrees - waterDegrees; 53 | 54 | StringBuilder text1 = new StringBuilder("So in total "); 55 | text1.append(Utils.format(totalLiters)); 56 | text1.append(" liters, which is heated "); 57 | text1.append(Utils.format(degrees) + "-" + Utils.format(waterDegrees) + "=" + Utils.format(delta)); 58 | text1.append(" degrees. This takes liters*degrees*1.16W = "); 59 | text1.append(Utils.format(totalLiters)); 60 | text1.append("*"); 61 | text1.append(Utils.format(delta)); 62 | text1.append("*1.16="); 63 | double perShower = Math.round(totalLiters * delta * 1.16); 64 | text1.append(Utils.format(perShower)); 65 | text1.append(" watt per showering."); 66 | 67 | StringBuilder text2 = new StringBuilder("So this is more or less (30.5/7)*"); 68 | text2.append(Utils.format(timesPerWeek)); 69 | text2.append(")*"); 70 | text2.append(Utils.format(perShower)); 71 | text2.append(" = "); 72 | double resultPerMonth = Math.round(totalLiters * delta * 1.16 * timesPerWeek * (30.5 / 7.0)); 73 | text2.append(Utils.format(Math.round(resultPerMonth * 0.001))); 74 | text2.append(" kW per month per person. For all people in total: "); 75 | resultPerMonth *= people; 76 | text2.append(Utils.format(Math.round(resultPerMonth * 0.001))); 77 | text2.append(" kW per month."); 78 | 79 | if (!enabled) { 80 | perShower = 0.0; 81 | } 82 | double perDay = perShower * people * (timesPerWeek / 7.0); 83 | result = new double[] { perDay * 31, perDay * 28, perDay * 31, perDay * 30, perDay * 31, perDay * 30, 84 | perDay * 31, perDay * 31, perDay * 30, perDay * 31, perDay * 30, perDay * 31 }; 85 | chart.showData("Shower", "darkblue", result); 86 | if (client.getHeating() != null) { 87 | client.getHeating().updateHeatingPlusShower(); 88 | } 89 | 90 | Fluent result = Fluent.P(); 91 | result.span(null, text1.toString()); 92 | result.br(); 93 | result.span(null, text2.toString()); 94 | return result; 95 | }); 96 | 97 | } 98 | 99 | public double[] getResult() { 100 | return result; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/SolarTubes.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator; 2 | 3 | import live.connector.vertxui.client.fluent.Att; 4 | import live.connector.vertxui.client.fluent.Fluent; 5 | import live.connector.vertxui.client.fluent.ViewOn; 6 | import live.connector.vertxui.samples.client.energyCalculator.components.ChartJs; 7 | import live.connector.vertxui.samples.client.energyCalculator.components.MonthTable; 8 | import live.connector.vertxui.samples.client.energyCalculator.components.Utils; 9 | 10 | public class SolarTubes { 11 | 12 | private double quantity = 2, tubes = 20; 13 | private ViewOn conclusion; 14 | private MonthTable monthTable; 15 | private double[] result = new double[12]; 16 | private double totalLength; 17 | private boolean enabled = true; 18 | 19 | public SolarTubes(Fluent body, ChartJs chart, Client client) { 20 | client.setSolarTubes(this); 21 | 22 | body.h2(null, "Solar tubes"); 23 | body.input(null, "checkbox").att(Att.checked, "1").click((fluent, b) -> { 24 | this.enabled = fluent.domChecked(); 25 | conclusion.sync(); 26 | }); 27 | body.span(null, " I want "); 28 | body.select(null, quantity + "", Utils.getSelectNumbers(0, 1, 100)).changed((fluent, ___) -> { 29 | quantity = Double.parseDouble(fluent.domSelectedOptions()[0]); 30 | conclusion.sync(); 31 | }); 32 | body.span(null, " collectors with each "); 33 | body.select(null, tubes + "", "8", "8", "18", "18", "20", "20", "24", "24", "30", "30", "36", "36", "48", "48", 34 | "54", "54").changed((fluent, ___) -> { 35 | tubes = Double.parseDouble(fluent.domSelectedOptions()[0]); 36 | conclusion.sync(); 37 | }); 38 | body.span(null, " heat tubes. ").br(); 39 | 40 | // already create monthTable, so that we do not need to check for its 41 | // existance in the conclusion 42 | monthTable = new MonthTable(new String[] { "1,3% ", "3,8% ", "7,3% ", "11,5% ", "14,7% ", "13,2% ", "14,6% ", 43 | "13,2% ", "10,1% ", "5,4% ", "2,5% ", "1,5% " }); 44 | 45 | conclusion = body.add(null, ___ -> { 46 | 47 | // Source 110kW: 48 | // https://econo.nl/berekening-zonneboiler-subsidie-2017 with 45 49 | // degrees. 50 | // percentages: 51 | // https://econo.nl/berekening-zonneboiler-subsidie-2017 52 | 53 | StringBuilder text1 = new StringBuilder("Total: "); 54 | text1.append(Utils.format(quantity * tubes)); 55 | text1.append(" heat pipes = 110kW*"); 56 | text1.append(Utils.format(quantity * tubes)); 57 | text1.append(" = "); 58 | double yearly = quantity * tubes * 110000; 59 | text1.append(Utils.format(yearly * 0.001)); 60 | text1.append(" kW per year."); 61 | 62 | StringBuilder text2 = new StringBuilder("Roof size: 1.98m x (0.115*"); 63 | text2.append(Utils.format(tubes)); 64 | text2.append("*"); 65 | text2.append(Utils.format(quantity)); 66 | text2.append(") = 1.98m x "); 67 | double tubeWidth = 0.115; 68 | double length = tubeWidth * tubes * quantity; 69 | text2.append(Utils.format(length)); 70 | text2.append("m = "); 71 | double area = 1.98 * length; 72 | text2.append(Utils.format(area)); 73 | text2.append(" m2 roof."); 74 | 75 | totalLength = tubeWidth * tubes * quantity; 76 | client.getHeating().warnPanelsLength(); 77 | 78 | StringBuilder text3 = new StringBuilder("The efficiency of the panels is (yearly/1040)/area = "); 79 | text3.append(Utils.format(Math.round((yearly / 1040) / area))); 80 | text3.append(" watt per m2."); 81 | 82 | Fluent returner = Fluent.P(); 83 | returner.span(null, text1.toString()); 84 | returner.br(); 85 | returner.span(null, text2.toString()); 86 | returner.br(); 87 | returner.span(null, text3.toString()); 88 | returner.add(monthTable); 89 | 90 | // update chart and table 91 | result = new double[] { 0.013 * yearly, 0.038 * yearly, 0.087 * yearly, 0.138 * yearly, 0.13 * yearly, 92 | 0.13 * yearly, 0.13 * yearly, 0.13 * yearly, 0.10 * yearly, 0.067 * yearly, 0.024 * yearly, 93 | 0.013 * yearly }; 94 | monthTable.state2(result); 95 | 96 | if (!enabled) { 97 | for (int x = 0; x != result.length; x++) { 98 | result[x] = 0.0; 99 | } 100 | } 101 | 102 | chart.showData("Solar tubes", "green", result); 103 | client.getHeating().updateHeatgap(); 104 | 105 | return returner; 106 | }); 107 | 108 | } 109 | 110 | public double[] getResult() { 111 | return result; 112 | } 113 | 114 | public double getTotalLength() { 115 | return totalLength; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/Cooking.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator; 2 | 3 | import live.connector.vertxui.client.fluent.Att; 4 | import live.connector.vertxui.client.fluent.Fluent; 5 | import live.connector.vertxui.client.fluent.ViewOn; 6 | import live.connector.vertxui.samples.client.energyCalculator.components.InputNumber; 7 | import live.connector.vertxui.samples.client.energyCalculator.components.Utils; 8 | 9 | public class Cooking { 10 | 11 | private double minutes = 45.0, plates = 2.0, energy = 1300.0, timesPerWeek = 6.0, other = 3_000; 12 | private ViewOn conclusion; 13 | private double[] result = new double[12]; 14 | private boolean enabled = true; 15 | 16 | public Cooking(Fluent body, Client client) { 17 | client.setCooking(this); 18 | 19 | body.h2(null, "Cooking"); 20 | body.input(null, "checkbox").att(Att.checked, "1").click((fluent, b) -> { 21 | this.enabled = fluent.domChecked(); 22 | conclusion.sync(); 23 | }); 24 | body.span(null, 25 | " WARNING: electronic cooking off-grid is absolutely non-sense, the power peaks that you need are huge - this is just for calculating how much external energy you would need."); 26 | body.br(); 27 | body.span(null, "I am usually cooking about "); 28 | body.add(new InputNumber().att(Att.value, minutes + "").keyup((fluent, ___) -> { 29 | minutes = ((InputNumber) fluent).domValueDouble(); 30 | conclusion.sync(); 31 | })); 32 | body.span(null, " minutes with "); 33 | body.add(new InputNumber().att(Att.value, plates + "").keyup((fluent, ___) -> { 34 | plates = ((InputNumber) fluent).domValueDouble(); 35 | conclusion.sync(); 36 | })); 37 | body.span(null, " plates on an electric cooking plate which consumes "); 38 | body.add(new InputNumber().att(Att.value, energy + "").keyup((fluent, ___) -> { 39 | energy = ((InputNumber) fluent).domValueDouble(); 40 | conclusion.sync(); 41 | })); 42 | body.span(null, " watt (electric 1500 watt, ceramic 1400 watt, induction 1300 watt)."); 43 | body.span(null, " I cook about "); 44 | body.add(new InputNumber().att(Att.value, timesPerWeek + "").keyup((fluent, ___) -> { 45 | timesPerWeek = ((InputNumber) fluent).domValueDouble(); 46 | conclusion.sync(); 47 | })); 48 | body.span(null, " times per week. For all other things I use "); 49 | body.add(new InputNumber().att(Att.value, other + "").keyup((fluent, ___) -> { 50 | other = ((InputNumber) fluent).domValueDouble(); 51 | conclusion.sync(); 52 | })); 53 | body.span(null, " kW per year."); 54 | body.br(); 55 | body.br(); 56 | 57 | conclusion = body.add(null, ___ -> { 58 | 59 | StringBuilder text1 = new StringBuilder("Assuming that my cook plate only heats half of the time, "); 60 | text1.append(" this means that for every time cooking I consume about "); 61 | text1.append(" hours*plates*energy*0.5 = "); 62 | double perDinner = (minutes / 60.0) * plates * energy * 0.5; 63 | text1.append(Utils.format(Math.round(perDinner))); 64 | text1.append(" watt per dinner."); 65 | 66 | StringBuilder text2 = new StringBuilder("This is more or less (30.5/7)*"); 67 | text2.append(Utils.format(timesPerWeek)); 68 | text2.append("*"); 69 | text2.append(Utils.format(perDinner)); 70 | text2.append("="); 71 | double resultPerMonth = Math.round(perDinner * timesPerWeek * 30.5 / 7.0); 72 | text2.append(Utils.format(Math.round(resultPerMonth * 0.001))); 73 | text2.append(" kW per month."); 74 | 75 | double perDay = perDinner * (timesPerWeek / 7.0); 76 | if (enabled == false) { 77 | perDay = 0.0; 78 | resultPerMonth = 0.0; 79 | } 80 | 81 | double[] cooking = new double[] { perDay * 31, perDay * 28, perDay * 31, perDay * 30, perDay * 31, 82 | perDay * 30, perDay * 31, perDay * 31, perDay * 30, perDay * 31, perDay * 30, perDay * 31 }; 83 | client.getElectricChart().showData("Cooking", "darkblue", cooking); 84 | 85 | // Cooking+other 86 | double withOtherPerMonth = resultPerMonth + other * 1000 / 12.0; 87 | result = new double[] { withOtherPerMonth, withOtherPerMonth, withOtherPerMonth, withOtherPerMonth, 88 | withOtherPerMonth, withOtherPerMonth, withOtherPerMonth, withOtherPerMonth, withOtherPerMonth, 89 | withOtherPerMonth, withOtherPerMonth, withOtherPerMonth }; 90 | client.getElectricChart().showData("Cooking+other", "blue", result); 91 | client.getHeating().updateHeatgap(); 92 | 93 | Fluent result = Fluent.P(); 94 | result.span(null, text1.toString()); 95 | result.br(); 96 | result.span(null, text2.toString()); 97 | return result; 98 | }); 99 | } 100 | 101 | public double[] getResult() { 102 | return result; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/SolarPanels.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator; 2 | 3 | import live.connector.vertxui.client.fluent.Att; 4 | import live.connector.vertxui.client.fluent.Fluent; 5 | import live.connector.vertxui.client.fluent.ViewOn; 6 | import live.connector.vertxui.samples.client.energyCalculator.components.MonthTable; 7 | import live.connector.vertxui.samples.client.energyCalculator.components.Utils; 8 | 9 | public class SolarPanels { 10 | 11 | private double quantity = 3, strength = 280, width = 1.650, length = 0.992; 12 | private ViewOn conclusion; 13 | private MonthTable monthTable; 14 | private double totalLength; 15 | private boolean enabled = true; 16 | 17 | public SolarPanels(Fluent body, Client client) { 18 | client.setSolarPanels(this); 19 | 20 | body.h2(null, "Solar Panels"); 21 | body.input(null, "checkbox").att(Att.checked, "1").click((fluent, b) -> { 22 | this.enabled = fluent.domChecked(); 23 | conclusion.sync(); 24 | }); 25 | body.span(null, " I want "); 26 | 27 | body.select(null, quantity + "", Utils.getSelectNumbers(0, 1, 100)).changed((fluent, ___) -> { 28 | quantity = Double.parseDouble(fluent.domSelectedOptions()[0]); 29 | conclusion.sync(); 30 | }); 31 | body.span(null, " solar panels each "); 32 | body.select(null, "280W 1.65x0.992", 33 | new String[] { "280W 1.65x0.992", "280W 1.65x0.992", "290W 1.65x0.992", "290W 1.65x0.992", 34 | "300W 1.65x0.992", "300W 1.65x0.992", "330W 1.559x1.046", "330W 1.559x1.046", 35 | "370W 1.710x1.016", "370W 1.710x1.016" }) 36 | .changed((fluent, ___) -> { 37 | String selected = fluent.domSelectedOptions()[0]; 38 | if (selected.equals("280W 1.65x0.992")) { 39 | strength = 280; 40 | width = 1.650; 41 | length = 0.992; 42 | } else if (selected.equals("290W 1.65x0.992")) { 43 | strength = 290; 44 | width = 1.650; 45 | length = 0.992; 46 | } else if (selected.equals("300W 1.65x0.992")) { 47 | strength = 300; 48 | width = 1.650; 49 | length = 0.992; 50 | } else if (selected.equals("330W 1.559x1.046")) { 51 | strength = 330; 52 | width = 1.559; 53 | length = 1.046; 54 | } else if (selected.equals("370W 1.710x1.016")) { 55 | strength = 370; 56 | width = 1.710; 57 | length = 1.016; 58 | } 59 | conclusion.sync(); 60 | }); 61 | body.br(); 62 | 63 | // already create monthTable, so that we do not need to check for its 64 | // existance in the conclusion 65 | // Source solar hours: 66 | // http://www.zonurencalculator.nl/sun_hours_calculation 67 | monthTable = new MonthTable(new String[] { "1,3% ", "3,8% ", "7,3% ", "11,5% ", "14,7% ", "13,2% ", "14,6% ", 68 | "13,2% ", "10,1% ", "5,4% ", "2,5% ", "1,5% " }); 69 | 70 | conclusion = body.add(null, ___ -> { 71 | 72 | StringBuilder text1 = new StringBuilder("The peak production is peak*quantity = "); 73 | double peak = quantity * strength; 74 | text1.append(Utils.format(peak)); 75 | text1.append(" watt. In the Netherlands there are 1040 effective sun hours (2016) so that is "); 76 | text1.append(Utils.format(Math.round(peak * 1040 * 0.001))); 77 | text1.append(" kW per year."); 78 | 79 | StringBuilder text2 = new StringBuilder("Roof size: "); 80 | double area = width * length * quantity; 81 | text2.append(Utils.format(area)); 82 | text2.append(" m2 of roof."); 83 | 84 | totalLength = length * quantity; 85 | client.getHeating().warnPanelsLength(); 86 | 87 | StringBuilder text3 = new StringBuilder("The efficiency of the panels is peak/area ="); 88 | text3.append(Utils.format(peak / area)); 89 | text3.append(" watt per m2."); 90 | 91 | Fluent result = Fluent.P(); 92 | result.span(null, text1.toString()); 93 | result.br(); 94 | result.span(null, text2.toString()); 95 | result.br(); 96 | result.span(null, text3.toString()); 97 | 98 | // update table 99 | double yearly = peak * 1040; 100 | double data[] = new double[] { 0.013 * yearly, 0.038 * yearly, 0.073 * yearly, 0.115 * yearly, 101 | 0.147 * yearly, 0.132 * yearly, 0.146 * yearly, 0.132 * yearly, 0.101 * yearly, 0.054 * yearly, 102 | 0.025 * yearly, 0.015 * yearly }; 103 | monthTable.state2(data); 104 | 105 | if (!enabled) { 106 | for (int x = 0; x != data.length; x++) { 107 | data[x] = 0.0; 108 | } 109 | } 110 | client.getElectricChart().showData("Solar panels", "green", data); 111 | 112 | return result; 113 | }); 114 | 115 | body.add(monthTable); 116 | } 117 | 118 | public double getTotalLength() { 119 | return totalLength; 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/energyCalculator/Stove.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.energyCalculator; 2 | 3 | import live.connector.vertxui.client.fluent.Fluent; 4 | import live.connector.vertxui.client.fluent.ViewOn; 5 | import live.connector.vertxui.samples.client.energyCalculator.components.MonthTable; 6 | import live.connector.vertxui.samples.client.energyCalculator.components.Utils; 7 | 8 | public class Stove { 9 | 10 | private double power = 5000, efficiency = 75, kgPerCubic = 350, wood = 4220; 11 | private ViewOn conclusion; 12 | private ViewOn total; 13 | private MonthTable monthTable; 14 | private Client client; 15 | 16 | public Stove(Fluent body, Client client) { 17 | this.client = client; 18 | client.setStove(this); 19 | 20 | body.h2(null, "Stove"); 21 | body.span(null, "I have a wood stove that can give "); 22 | body.select(null, power + "", new String[] { "4000", "4000", "5000", "5000", "6000", "6000", "7000", "7000", 23 | "8000", "8000", "9000", "9000", "10000", "10000" }).changed((fluent, ___) -> { 24 | power = Double.parseDouble(fluent.domSelectedOptions()[0]); 25 | conclusion.sync(); 26 | }); 27 | body.span(null, " watt per hour, and has an efficiency of "); 28 | body.select(null, efficiency + "", 29 | new String[] { "60", "60", "70", "70", "75", "75", "80", "80", "85", "85", "90", "90", "95", "95" }) 30 | .changed((fluent, ___) -> { 31 | efficiency = Double.parseDouble(fluent.domSelectedOptions()[0]); 32 | conclusion.sync(); 33 | }); 34 | body.span(null, " percent. I use wood that is "); 35 | body.select(null, kgPerCubic + "", new String[] { "350 (zachthout)", "350", "380 (spar)", "380", 36 | "544 (hardhout)", "544", "600 (berk)", "600", "750 (eik, beuk)", "750" }).changed((fluent, ___) -> { 37 | kgPerCubic = Double.parseDouble(fluent.domSelectedOptions()[0]); 38 | conclusion.sync(); 39 | }); 40 | body.span(null, " kg/m3 and that is "); 41 | body.select(null, wood + "", new String[] { "normal (4220 w/kg)", "4220", "1 summer (3400 w/kg)", "3400", 42 | "not (2000 w/kg)", "2000" }).changed((fluent, ___) -> { 43 | wood = Double.parseDouble(fluent.domSelectedOptions()[0]); 44 | conclusion.sync(); 45 | }); 46 | body.span(null, " dried wood."); 47 | 48 | // initialise now to avoid null-pointer error later 49 | monthTable = new MonthTable(null); 50 | total = new ViewOn<>(null, total -> { 51 | return Fluent.Span(null, " in total " + Utils.format(total) + "m3 wood."); 52 | }); 53 | 54 | conclusion = body.add(null, ___ -> { 55 | 56 | // Source for statistics: 57 | // http://www.warmteprijzen.nl/brandhout_prijzen.html 58 | // http://www.warmteprijzen.nl/rekenmachine_kwh.html 59 | 60 | StringBuilder text1 = new StringBuilder("So for one hour the stove can give at maximum "); 61 | text1.append(Utils.format(power)); 62 | text1.append("*("); 63 | text1.append(Utils.format(efficiency)); 64 | text1.append("/100) = "); 65 | double maxStove = power * 0.01 * efficiency; 66 | text1.append(Utils.format(maxStove)); 67 | text1.append(" watt per hour."); 68 | 69 | StringBuilder text2 = new StringBuilder("For this I need "); 70 | text2.append(Utils.format(power)); 71 | text2.append("/"); 72 | text2.append(Utils.format(wood)); 73 | text2.append(" = "); 74 | double kgNeeded = power / wood; 75 | text2.append(Utils.format(kgNeeded)); 76 | text2.append(" kg of wood. This is "); 77 | text2.append(Utils.format(kgNeeded)); 78 | text2.append("/"); 79 | text2.append(Utils.format(kgPerCubic)); 80 | text2.append(" m3 of wood. The table below show how much wood you may need for the heatgap, "); 81 | 82 | Fluent result = Fluent.P(); 83 | result.span(null, text1.toString()); 84 | result.br(); 85 | result.span(null, text2.toString()); 86 | result.add(total); 87 | updateTable(); 88 | 89 | return result; 90 | }); 91 | body.add(monthTable); 92 | } 93 | 94 | public void updateTable() { 95 | String[] heatgapString = new String[12]; 96 | double[] heatgap = client.getHeating().getHeatgap(); 97 | for (int x = 0; x < 12; x++) { 98 | heatgapString[x] = Math.round(heatgap[x] * -0.001) + "kW"; 99 | } 100 | 101 | double[] m3 = new double[12]; 102 | double maxStove = power * 0.01 * efficiency; 103 | double kgNeeded = power / wood; 104 | double all = 0; 105 | for (int x = 0; x < 12; x++) { 106 | if (heatgap[x] == 0) { 107 | m3[x] = 0; 108 | } else { 109 | double hours = (-1.0 * heatgap[x]) / maxStove; 110 | double kgNeededNow = hours * kgNeeded; 111 | m3[x] = kgNeededNow / kgPerCubic; 112 | all += m3[x]; 113 | } 114 | } 115 | 116 | monthTable.state(heatgapString, m3); 117 | total.state(all); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/client/transport/Pojofy.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client.transport; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | 6 | import com.github.nmorel.gwtjackson.client.ObjectMapper; 7 | import com.google.gwt.xhr.client.XMLHttpRequest; 8 | 9 | import elemental.events.Event; 10 | import elemental.events.MessageEvent; 11 | import elemental.html.ArrayBuffer; 12 | import elemental.html.WebSocket; 13 | import elemental.json.Json; 14 | import elemental.json.JsonObject; 15 | 16 | /** 17 | * A helper class for communicating in pure-java POJO objects. 18 | * 19 | * @author ng 20 | * 21 | */ 22 | public class Pojofy { 23 | 24 | public static void ajax(String protocol, String url, I model, ObjectMapper inMapper, 25 | ObjectMapper outMapper, BiConsumer handler) { 26 | XMLHttpRequest xhr = XMLHttpRequest.create(); 27 | xhr.setOnReadyStateChange(a -> { 28 | if (handler == null || xhr.getReadyState() != 4) { 29 | return; 30 | } 31 | O result = null; 32 | if (xhr.getStatus() == 200) { 33 | result = out(xhr.getResponseText(), outMapper); 34 | } 35 | handler.accept(xhr.getStatus(), result); 36 | }); 37 | xhr.open(protocol, url); 38 | xhr.send(in(model, inMapper)); 39 | } 40 | 41 | private final native static String socketToString(ArrayBuffer buf)/*-{ 42 | return String.fromCharCode.apply(null, new Uint8Array(buf)); 43 | }-*/; 44 | 45 | public static boolean socketReceive(String url, Event e, ObjectMapper outMapper, Consumer handler) { 46 | Object me = ((MessageEvent) e).getData(); 47 | String meString = me.toString(); 48 | if (meString.equals("[object ArrayBuffer]")) { // websockets 49 | meString = socketToString((ArrayBuffer) me); 50 | } 51 | if (!meString.startsWith("{\"url\":\"" + url + "\"")) { 52 | return false; 53 | } 54 | JsonObject json = Json.parse(meString); 55 | handler.accept(out(json.getString("body"), outMapper)); 56 | return true; 57 | } 58 | 59 | protected static String in(I model, ObjectMapper inMapper) { 60 | if (model == null) { 61 | return null; 62 | } else if (model instanceof String || inMapper == null) { 63 | return (String) model; 64 | } else { 65 | return inMapper.write(model); 66 | } 67 | } 68 | 69 | @SuppressWarnings("unchecked") 70 | protected static O out(String message, ObjectMapper outMapper) { 71 | if (message == null) { 72 | return null; 73 | } else if (outMapper == null) { // outMapper null: string 74 | return (O) message; 75 | } else { 76 | return outMapper.read(message); 77 | } 78 | } 79 | 80 | public static void socketSend(WebSocket socket, String url, I model, ObjectMapper inMapper, 81 | JsonObject headers) { 82 | JsonObject object = Json.createObject(); 83 | object.put("url", url); 84 | object.put("body", in(model, inMapper)); 85 | object.put("headers", headers); 86 | socket.send(object.toJson()); 87 | } 88 | 89 | /** 90 | * Warning: the thing you send can go to anyone connected to the eventbus, 91 | * including other browsers that are connected. So please handle with care! 92 | * 93 | * @param 94 | * input type 95 | * @param 96 | * output type 97 | * @param eventBus 98 | * the eventbus 99 | * @param address 100 | * the address 101 | * @param model 102 | * the model 103 | * @param headers 104 | * the headers 105 | * @param inMapper 106 | * the inputmapper 107 | * @param outMapper 108 | * the outputmapper 109 | * @param handler 110 | * the callback handler 111 | */ 112 | public static void eventbusSend(EventBus eventBus, String address, I model, JsonObject headers, 113 | ObjectMapper inMapper, ObjectMapper outMapper, Consumer handler) { 114 | eventBus.send(address, in(model, inMapper), headers, (error, m) -> { 115 | if (error != null) { 116 | throw new IllegalArgumentException(error.asString()); 117 | } 118 | handler.accept(out(m.getString("body"), outMapper)); 119 | }); 120 | } 121 | 122 | public static void eventbusPublish(EventBus eventBus, String address, I model, JsonObject headers, 123 | ObjectMapper inMapper) { 124 | eventBus.publish(address, in(model, inMapper), headers); 125 | } 126 | 127 | public static void eventbusReceive(EventBus eventBus, String address, JsonObject headers, 128 | ObjectMapper outMapper, Consumer handler) { 129 | eventBus.registerHandler(address, headers, (error, m) -> { 130 | if (error != null) { 131 | throw new IllegalArgumentException(error.asString()); 132 | } 133 | O output = out(m.getString("body"), outMapper); 134 | handler.accept(output); 135 | }); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /vertxui-core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | live.connector 6 | vertxui-core 7 | 1.01 8 | jar 9 | 10 | ${project.groupId}:${project.artifactId} 11 | 100% Java 100% async client and server with POJO codecs, Fluent HTML (with virtualDOM and declarative view-on-model), fast GUI jUnit testing, an Eventbus server and clientside, automatic browser reloading and more. 12 | https://github.com/nielsbaloe/vertxui 13 | 14 | 15 | 8 16 | 8 17 | UTF-8 18 | 19 | 20 | 21 | 22 | GNU General Public License, Version 3 23 | http://www.gnu.org/licenses/gpl-3.0.html 24 | manual 25 | A free, copyleft license for software and other kinds of works 26 | 27 | 28 | 29 | 30 | 31 | Niels Baloe 32 | nielsbaloe@usegithubplease.com 33 | Bonneville 34 | http://www.bonneville.nl 35 | 36 | 37 | 38 | 39 | scm:git:git://github.com/nielsbaloe/vertxui.git 40 | scm:git:ssh://github.com:nielsbaloe/vertxui.git 41 | https://github.com/nielsbaloe/vertxui/tree/master/vertxui-core 42 | 43 | 44 | 45 | 46 | 47 | io.vertx 48 | vertx-web 49 | 3.7.0 50 | 51 | 52 | 53 | com.google.gwt 54 | gwt-dev 55 | 2.8.2 56 | 57 | 58 | com.google.gwt 59 | gwt-elemental 60 | 2.8.2 61 | 62 | 63 | 64 | com.github.nmorel.gwtjackson 65 | gwt-jackson 66 | 0.15.4 67 | 68 | 69 | 70 | junit 71 | junit 72 | 4.12 73 | 74 | 75 | 76 | com.machinepublishers 77 | jbrowserdriver 78 | 1.0.1 79 | 80 | 81 | 82 | org.slf4j 83 | slf4j-simple 84 | 1.6.2 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | ${basedir}/src/main/java 93 | 94 | **/*.* 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.sonatype.plugins 103 | nexus-staging-maven-plugin 104 | 1.6.7 105 | true 106 | 107 | ossrh 108 | https://oss.sonatype.org/ 109 | true 110 | 111 | 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-source-plugin 116 | 2.2.1 117 | 118 | 119 | attach-sources 120 | 121 | jar-no-fork 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-javadoc-plugin 129 | 2.9.1 130 | 131 | 132 | attach-javadocs 133 | 134 | jar 135 | 136 | 137 | 138 | 139 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/server/transport/Pojofy.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.server.transport; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.function.BiConsumer; 5 | import java.util.function.BiFunction; 6 | import java.util.logging.Logger; 7 | 8 | import io.vertx.core.Handler; 9 | import io.vertx.core.MultiMap; 10 | import io.vertx.core.Vertx; 11 | import io.vertx.core.buffer.Buffer; 12 | import io.vertx.core.http.WebSocket; 13 | import io.vertx.core.json.Json; 14 | import io.vertx.core.json.JsonObject; 15 | import io.vertx.core.streams.WriteStream; 16 | import io.vertx.ext.web.RoutingContext; 17 | import live.connector.vertxui.server.VertxUI; 18 | 19 | /** 20 | * A Vert.x helper class to communicate in POJO's for ajax, the eventbus and 21 | * websocket/sockjs. 22 | * 23 | * Note: try to use vertx-jersey for a lot of ajax calls if you do not use 24 | * Vert.x as microservice, it is probably more efficient and does generate 25 | * cleaner code. 26 | * 27 | * @author ng 28 | * 29 | */ 30 | public class Pojofy { 31 | 32 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 33 | 34 | public static Handler ajax(Class inputType, BiFunction handler) { 35 | return context -> { 36 | context.request().bodyHandler(body -> { 37 | A input = in(inputType, body.toString()); 38 | String output = out(handler.apply(input, context)); 39 | if (output == null) { 40 | // do nothing 41 | } else { 42 | context.response().putHeader("Content-Type", "application/json; charset=" + VertxUI.charset); 43 | context.response().end(output); 44 | } 45 | }); 46 | }; 47 | } 48 | 49 | /** 50 | * An ajax call handler for a void method. 51 | */ 52 | /** 53 | * @param 54 | * the input type class 55 | * @param inputType 56 | * the input type 57 | * @param handler 58 | * the callback 59 | * @return the webserver handler for this ajax call. 60 | */ 61 | public static Handler ajax(Class inputType, BiConsumer handler) { 62 | return context -> { 63 | 64 | // Internet Explorer 11 caches ajax calls 65 | context.response().putHeader("Cache-control", "none"); 66 | context.response().putHeader("Pragma", "none"); 67 | context.response().putHeader("Expires", "0"); 68 | context.response().putHeader("Content-Type", "application/json; charset=" + VertxUI.charset); 69 | 70 | context.request().bodyHandler(body -> { 71 | context.response().end(); 72 | handler.accept(in(inputType, body.toString()), context); 73 | }); 74 | }; 75 | } 76 | 77 | public static void eventbus(String urlOrAddress, Class inputType, BiFunction handler) { 78 | Vertx.currentContext().owner().eventBus().consumer(urlOrAddress, message -> { 79 | A input = in(inputType, (String) message.body()); 80 | String output = out(handler.apply(input, message.headers())); 81 | if (output != null) { 82 | if (message.replyAddress() != null) { 83 | message.reply(output); 84 | } else { 85 | log.warning("reply not send, the client is not using send() but publish(), lost output=" + output); 86 | } 87 | } 88 | }); 89 | } 90 | 91 | /** 92 | * Note: replies at the same address! 93 | * 94 | * @param 95 | * the input type class 96 | * @param 97 | * the socket 98 | * @param socket 99 | * the socket 100 | * @param url 101 | * the url 102 | * @param in 103 | * the input buffer 104 | * @param inputType 105 | * the input class 106 | * @param handler 107 | * the callback 108 | * @return whether the input wqs successfully shallowed. 109 | */ 110 | public static > boolean socket(S socket, String url, Buffer in, Class inputType, 111 | BiFunction handler) { 112 | String start = ("{\"url\":\"" + url); 113 | if (in.length() < start.getBytes().length || !in.getString(0, start.length()).equals(start)) { 114 | return false; 115 | } 116 | 117 | JsonObject json = in.toJsonObject(); 118 | A input = in(inputType, json.getString("body")); 119 | 120 | String output = out(handler.apply(input, json.getJsonObject("headers"))); 121 | if (output == null) { 122 | return false; 123 | } 124 | json.put("body", output); 125 | json.remove("headers"); 126 | 127 | if (socket instanceof WebSocket) { 128 | // prevents blobs at the javascript end, so is faster 129 | ((WebSocket) socket).writeFinalTextFrame(json.toString()); 130 | } else { 131 | socket.write(Buffer.buffer(json.toString())); 132 | } 133 | return true; 134 | } 135 | 136 | private static String out(Object output) { 137 | if (output == null) { 138 | return null; 139 | } else if (output instanceof String) { 140 | return (String) output; 141 | } else { 142 | return Json.encode(output); 143 | } 144 | } 145 | 146 | @SuppressWarnings("unchecked") 147 | private static I in(Class inputType, String in) { 148 | // no input type or null? use string 149 | if (in == null || in.isEmpty() || inputType == null || inputType.getClass().equals(String.class)) { 150 | return (I) in; 151 | } else { 152 | return (I) Json.decodeValue(in, inputType); 153 | } 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/Controller.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap; 2 | 3 | import java.util.Collections; 4 | import java.util.Date; 5 | 6 | import elemental.events.Event; 7 | import elemental.events.KeyboardEvent; 8 | import elemental.events.MouseEvent; 9 | import live.connector.vertxui.client.fluent.Att; 10 | import live.connector.vertxui.client.fluent.Fluent; 11 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills; 12 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Bill; 13 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Name; 14 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Grocery; 15 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Totals; 16 | 17 | public class Controller { 18 | 19 | // Models - here the controller 'owns' them, but you can also let the store 20 | // own them. Actually, the model-to-view objects also have the models, 21 | // because these are placeholders that do not change in the application 22 | // (only the .all content is changed). 23 | private Totals totals = new Totals(); 24 | private Bills bills = new Bills(); 25 | private Grocery grocery = new Grocery(); 26 | // model 'menu' is a primitive not saved here, but directly pushed to the 27 | // client. 28 | 29 | private View view; 30 | 31 | private Store store; 32 | 33 | public Controller(Store store, View view) { 34 | this.store = store; 35 | this.view = view; 36 | } 37 | 38 | public Bills getBills() { 39 | return bills; 40 | } 41 | 42 | public Totals getTotals() { 43 | return totals; 44 | } 45 | 46 | public Grocery getGrocery() { 47 | return grocery; 48 | } 49 | 50 | public void onBillOnlyNumeric(Fluent __, KeyboardEvent event) { 51 | int code = event.getCharCode(); 52 | if ((code >= 48 && code <= 57) || code == 0) { 53 | return; // numeric or a not-a-character is OK 54 | } 55 | event.preventDefault(); 56 | } 57 | 58 | public void onGroceryAdd(Fluent fluent, KeyboardEvent event) { 59 | if (event.getKeyCode() != KeyboardEvent.KeyCode.ENTER) { 60 | return; 61 | } 62 | 63 | // Internet explorer 11 64 | // PREVENT SENDING THE FORM BECAUSE IT HAS ONLY ONE ELEMENT 65 | event.preventDefault(); 66 | 67 | String text = fluent.domValue(); 68 | if (!text.isEmpty()) { 69 | fluent.att(Att.value, null); 70 | 71 | // Apply change in model 72 | grocery.all.add(text); 73 | 74 | // Push model to view 75 | view.syncGrocery(); 76 | 77 | // Push change to server 78 | store.addGrocery(text, textOnError -> { 79 | view.errorGroceryAdd(text); 80 | grocery.all.remove(text); 81 | view.syncGrocery(); 82 | }); 83 | } 84 | 85 | } 86 | 87 | public void onGroceryDelete(Fluent fluent, MouseEvent __) { 88 | String text = fluent.domValue(); 89 | 90 | // Apply change in model 91 | grocery.all.remove(text); 92 | 93 | // Push model to view 94 | view.syncGrocery(); 95 | 96 | // Push change to server 97 | store.deleteGrocery(text, textOnError -> { 98 | view.errorGroceryDelete(text); 99 | grocery.all.add(text); 100 | view.syncGrocery(); 101 | }); 102 | } 103 | 104 | public void onAddBill(String name, int amount, String what, Date date) { 105 | // Apply change in model 106 | Bill bill = new Bills.Bill(Name.valueOf(name), amount, what, date); 107 | bills.all.add(bill); 108 | Collections.sort(bills.all); 109 | 110 | // Push model to view 111 | view.syncBills(); 112 | 113 | // Push change to server 114 | store.addBill(bill, billOnError -> { 115 | view.errorBillAdd(); 116 | bills.all.remove(billOnError); 117 | view.syncBills(); 118 | }); 119 | } 120 | 121 | public void onMenuHome(Fluent __, Event ___) { 122 | // Push model to view 123 | view.stateAndSyncMenu("home"); // the view handles this model 124 | 125 | // Note that old available data is already shown before any answer 126 | store.getTotals(this::setTotals); 127 | } 128 | 129 | public void onMenuBills(Fluent __, Event ___) { 130 | // Push model to view 131 | view.stateAndSyncMenu("bills");// the view handles this model 132 | 133 | // Note that old available data is already shown before any answer 134 | store.getBills(this::setBills); 135 | } 136 | 137 | public void onMenuGrocery(Fluent __, Event ___) { 138 | // Push model to view 139 | view.stateAndSyncMenu("grocery");// the view handles this model 140 | 141 | // Note that old available data is already shown before any answer 142 | store.getGrocery(this::setGrocery); 143 | } 144 | 145 | /** 146 | * Callback from the store - in a seperate method so we can junit test it 147 | * 148 | * @param responseCode 149 | * @param totals 150 | */ 151 | public void setTotals(int responseCode, Totals totals) { 152 | this.totals.all = totals.all; 153 | view.syncTotals(); 154 | } 155 | 156 | /** 157 | * Callback from the store - in a seperate method so we can junit test it 158 | * 159 | * @param responseCode 160 | * @param totals 161 | */ 162 | public void setBills(int responseCode, Bills bills) { 163 | this.bills.all = bills.all; 164 | view.syncBills(); 165 | } 166 | 167 | /** 168 | * Callback from the store - in a seperate method so we can junit test it 169 | * 170 | * @param responseCode 171 | * @param totals 172 | */ 173 | public void setGrocery(int responseCode, Grocery grocery) { 174 | this.grocery.all = grocery.all; 175 | view.syncGrocery(); 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/todomvc/View.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.todomvc; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.Footer; 4 | import static live.connector.vertxui.client.fluent.Fluent.H1; 5 | import static live.connector.vertxui.client.fluent.Fluent.Input; 6 | import static live.connector.vertxui.client.fluent.Fluent.Li; 7 | import static live.connector.vertxui.client.fluent.Fluent.Ul; 8 | import static live.connector.vertxui.client.fluent.FluentBase.body; 9 | 10 | import java.util.List; 11 | import java.util.function.Predicate; 12 | 13 | import com.google.gwt.core.client.EntryPoint; 14 | 15 | import live.connector.vertxui.client.fluent.Att; 16 | import live.connector.vertxui.client.fluent.Css; 17 | import live.connector.vertxui.client.fluent.Fluent; 18 | import live.connector.vertxui.client.fluent.ViewOn; 19 | import live.connector.vertxui.client.fluent.ViewOnBoth; 20 | import live.connector.vertxui.samples.client.todomvc.State.Buttons; 21 | 22 | public class View implements EntryPoint { 23 | 24 | // Views 25 | private ViewOnBoth> list; 26 | private ViewOnBoth> footer; 27 | private ViewOn> toggle; 28 | 29 | public static String[] css = { "css/base.css", "css/index.css" }; 30 | 31 | @Override 32 | public void onModuleLoad() { 33 | Store store = new Store(); 34 | Controller controller = new Controller(store, this); 35 | start(controller); 36 | } 37 | 38 | public void start(Controller controller) { 39 | 40 | // Initialise models 41 | List modelsInit = controller.getModels(); 42 | State stateInit = controller.getState(); 43 | 44 | // Initialise views: 45 | 46 | // Static upper part 47 | // search for id=startpoint 48 | Fluent container = Fluent.getElementById("startpoint"); 49 | if (container == null) { // if the existing index.html is not found 50 | // Note: todomvc is also the extend-an-existing-index.html example 51 | // for vertxui. 52 | body.classs("learn-bar").aside(); 53 | container = body.section("todoapp"); 54 | } 55 | 56 | // Header with H1 and Input 57 | container.header("header", H1(null, "todos"), Input("new-todo").att(Att.placeholder, "What needs to be done?") 58 | .att(Att.autofocus, "1").keydown(controller::onInput)); 59 | 60 | // Main view 61 | Fluent main = container.section("main").css(Css.display, "block"); 62 | 63 | // The toggle 64 | toggle = main.add(modelsInit, models -> { 65 | boolean allChecked = models.size() != 0 && models.stream().filter(x -> !x.isCompleted()).count() == 0; 66 | 67 | // Actually main should have display:none when no models 68 | return Input("toggle-all", "checkbox").att(Att.checked, allChecked ? "1" : null) 69 | .css(Css.display, models.size() == 0 ? "none" : null).click(controller::onSelectAll); 70 | }); 71 | 72 | // The list of items 73 | list = main.add(stateInit, modelsInit, (state, models) -> { 74 | 75 | // Which models should be displayed? 76 | Predicate filter = p -> (state.getButtons() == Buttons.All) 77 | || (p.isCompleted() == (state.getButtons() == Buttons.Completed)); 78 | 79 | // Return an Unordered List with LIsted items. 80 | return Ul("todo-list", models.stream().filter(filter).map(item -> { 81 | 82 | Fluent li = Li(item.isCompleted() ? "completed" : null); 83 | 84 | Fluent div = li.div("view"); 85 | 86 | // Someone has doubleclicked 87 | if (item == state.getEditing()) { 88 | // add 'editing' to the existing class 89 | li.classs(li.classs() != null ? li.classs() + " editing" : "editing"); 90 | 91 | // add an extra input field 92 | li.input("edit").att(Att.value, item.getTitle()).keydown(controller::onEditKey) 93 | .blur(controller::onEditEnd); 94 | } 95 | 96 | div.input("toggle", "checkbox").att(Att.checked, item.isCompleted() ? "1" : null) 97 | .click((fluent, event) -> controller.onSelect(fluent, item)); 98 | 99 | div.label(null, item.getTitle()).dblclick((fluent, event) -> controller.onEditStart(item)); 100 | 101 | div.button("destroy", "button", null).click((f, e) -> controller.onDestroy(item)); 102 | 103 | return div; 104 | })); 105 | }); 106 | 107 | // Footer 108 | footer = main.add(stateInit, modelsInit, (state, models) -> { 109 | 110 | // Footer 111 | Fluent footer = Footer("footer").css(Css.display, models.size() == 0 ? "none" : "block"); 112 | 113 | // Counter 114 | Fluent counter = footer.span("todo-count"); 115 | long count = models.stream().filter(t -> !t.isCompleted()).count(); 116 | counter.strong(null, count + ""); 117 | if (count == 1) { 118 | // this is exceptional and bad style, combining plain text and 119 | // html. however this is how the todomvc example should work 120 | counter.textNode(" item left"); 121 | } else { 122 | // this is exceptional and bad style, combining plain text and 123 | // html. however this is how the todomvc example should work 124 | counter.textNode(" items left"); 125 | } 126 | 127 | // Buttons 128 | Fluent buttons = footer.ul("filters"); 129 | buttons.li().a((state.getButtons() == Buttons.All ? "selected" : null), "All", "#/", controller::onAll); 130 | buttons.li().a((state.getButtons() == Buttons.Active ? "selected" : null), "Active", "#/active", 131 | controller::onActive); 132 | buttons.li().a((state.getButtons() == Buttons.Completed ? "selected" : null), "Completed", "#/completed", 133 | controller::onCompleted); 134 | 135 | // "Clear Completed" button 136 | if (count != modelsInit.size()) { 137 | footer.button("clear-completed", "button", "Clear completed").css(Css.display, "block") 138 | .click(controller::onClearCompleted); 139 | } 140 | return footer; 141 | }); 142 | } 143 | 144 | public void syncModel() { 145 | list.sync(); 146 | footer.sync(); 147 | toggle.sync(); 148 | } 149 | 150 | public void syncState() { 151 | list.sync(); 152 | footer.sync(); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /vertxui-examples/assets/todos/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | background: none; 11 | font-size: 100%; 12 | vertical-align: baseline; 13 | font-family: inherit; 14 | font-weight: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | appearance: none; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | min-width: 230px; 29 | max-width: 550px; 30 | margin: 0 auto; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | font-weight: 300; 35 | } 36 | 37 | button, input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | .hidden { 42 | display: none; 43 | } 44 | 45 | .todoapp { 46 | background: #fff; 47 | margin: 130px 0 40px 0; 48 | position: relative; 49 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 50 | rgba(0, 0, 0, 0.1); 51 | } 52 | 53 | .todoapp input::-webkit-input-placeholder { 54 | font-style: italic; 55 | font-weight: 300; 56 | color: #e6e6e6; 57 | } 58 | 59 | .todoapp input::-moz-placeholder { 60 | font-style: italic; 61 | font-weight: 300; 62 | color: #e6e6e6; 63 | } 64 | 65 | .todoapp input::input-placeholder { 66 | font-style: italic; 67 | font-weight: 300; 68 | color: #e6e6e6; 69 | } 70 | 71 | .todoapp h1 { 72 | position: absolute; 73 | top: -155px; 74 | width: 100%; 75 | font-size: 100px; 76 | font-weight: 100; 77 | text-align: center; 78 | color: rgba(175, 47, 47, 0.15); 79 | -webkit-text-rendering: optimizeLegibility; 80 | -moz-text-rendering: optimizeLegibility; 81 | text-rendering: optimizeLegibility; 82 | } 83 | 84 | .new-todo, .edit { 85 | position: relative; 86 | margin: 0; 87 | width: 100%; 88 | font-size: 24px; 89 | font-family: inherit; 90 | font-weight: inherit; 91 | line-height: 1.4em; 92 | border: 0; 93 | outline: none; 94 | color: inherit; 95 | padding: 6px; 96 | border: 1px solid #999; 97 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 98 | box-sizing: border-box; 99 | -webkit-font-smoothing: antialiased; 100 | -moz-font-smoothing: antialiased; 101 | font-smoothing: antialiased; 102 | } 103 | 104 | .new-todo { 105 | padding: 16px 16px 16px 60px; 106 | border: none; 107 | background: rgba(0, 0, 0, 0.003); 108 | box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); 109 | } 110 | 111 | .main { 112 | position: relative; 113 | z-index: 2; 114 | border-top: 1px solid #e6e6e6; 115 | } 116 | 117 | label[for='toggle-all'] { 118 | display: none; 119 | } 120 | 121 | .toggle-all { 122 | position: absolute; 123 | top: -55px; 124 | left: -12px; 125 | width: 60px; 126 | height: 34px; 127 | text-align: center; 128 | border: none; /* Mobile Safari */ 129 | } 130 | 131 | .toggle-all:before { 132 | content: "\276f"; 133 | font-size: 22px; 134 | color: #e6e6e6; 135 | padding: 10px 27px 10px 27px; 136 | } 137 | 138 | .toggle-all:checked:before { 139 | color: #737373; 140 | } 141 | 142 | .todo-list { 143 | margin: 0; 144 | padding: 0; 145 | list-style: none; 146 | } 147 | 148 | .todo-list li { 149 | position: relative; 150 | font-size: 24px; 151 | border-bottom: 1px solid #ededed; 152 | } 153 | 154 | .todo-list li:last-child { 155 | border-bottom: none; 156 | } 157 | 158 | .todo-list li.editing { 159 | border-bottom: none; 160 | padding: 0; 161 | } 162 | 163 | .todo-list li.editing .edit { 164 | display: block; 165 | width: 506px; 166 | padding: 13px 17px 12px 17px; 167 | margin: 0 0 0 43px; 168 | } 169 | 170 | .todo-list li.editing .view { 171 | display: none; 172 | } 173 | 174 | .todo-list li .toggle { 175 | text-align: center; 176 | width: 40px; 177 | /* auto, since non-WebKit browsers doesn't support input styling */ 178 | height: auto; 179 | position: absolute; 180 | top: 0; 181 | bottom: 0; 182 | margin: auto 0; 183 | border: none; /* Mobile Safari */ 184 | -webkit-appearance: none; 185 | appearance: none; 186 | } 187 | 188 | .todo-list li .toggle:after { 189 | content: 190 | url('data:image/svg+xml;utf8,'); 191 | } 192 | 193 | .todo-list li .toggle:checked:after { 194 | content: 195 | url('data:image/svg+xml;utf8,'); 196 | } 197 | 198 | .todo-list li label { 199 | white-space: pre-line; 200 | word-break: break-all; 201 | padding: 15px 60px 15px 15px; 202 | margin-left: 45px; 203 | display: block; 204 | line-height: 1.2; 205 | transition: color 0.4s; 206 | } 207 | 208 | .todo-list li.completed label { 209 | color: #d9d9d9; 210 | text-decoration: line-through; 211 | } 212 | 213 | .todo-list li .destroy { 214 | display: none; 215 | position: absolute; 216 | top: 0; 217 | right: 10px; 218 | bottom: 0; 219 | width: 40px; 220 | height: 40px; 221 | margin: auto 0; 222 | font-size: 30px; 223 | color: #cc9a9a; 224 | margin-bottom: 11px; 225 | transition: color 0.2s ease-out; 226 | } 227 | 228 | .todo-list li .destroy:hover { 229 | color: #af5b5e; 230 | } 231 | 232 | .todo-list li .destroy:after { 233 | content: "\00d7" 234 | } 235 | 236 | .todo-list li:hover .destroy { 237 | display: block; 238 | } 239 | 240 | .todo-list li .edit { 241 | display: none; 242 | } 243 | 244 | .todo-list li.editing:last-child { 245 | margin-bottom: -1px; 246 | } 247 | 248 | .footer { 249 | color: #777; 250 | padding: 10px 15px; 251 | height: 20px; 252 | text-align: center; 253 | border-top: 1px solid #e6e6e6; 254 | } 255 | 256 | .footer:before { 257 | content: ''; 258 | position: absolute; 259 | right: 0; 260 | bottom: 0; 261 | left: 0; 262 | height: 50px; 263 | overflow: hidden; 264 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 265 | 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px 266 | rgba(0, 0, 0, 0.2); 267 | } 268 | 269 | .todo-count { 270 | float: left; 271 | text-align: left; 272 | } 273 | 274 | .todo-count strong { 275 | font-weight: 300; 276 | } 277 | 278 | .filters { 279 | margin: 0; 280 | padding: 0; 281 | list-style: none; 282 | position: absolute; 283 | right: 0; 284 | left: 0; 285 | } 286 | 287 | .filters li { 288 | display: inline; 289 | } 290 | 291 | .filters li a { 292 | color: inherit; 293 | margin: 3px; 294 | padding: 3px 7px; 295 | text-decoration: none; 296 | border: 1px solid transparent; 297 | border-radius: 3px; 298 | } 299 | 300 | .filters li a.selected, .filters li a:hover { 301 | border-color: rgba(175, 47, 47, 0.1); 302 | } 303 | 304 | .filters li a.selected { 305 | border-color: rgba(175, 47, 47, 0.2); 306 | } 307 | 308 | .clear-completed, html .clear-completed:active { 309 | float: right; 310 | position: relative; 311 | line-height: 20px; 312 | text-decoration: none; 313 | cursor: pointer; 314 | position: relative; 315 | } 316 | 317 | .clear-completed:hover { 318 | text-decoration: underline; 319 | } 320 | 321 | .info { 322 | margin: 65px auto 0; 323 | color: #bfbfbf; 324 | font-size: 10px; 325 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 326 | text-align: center; 327 | } 328 | 329 | .info p { 330 | line-height: 1; 331 | } 332 | 333 | .info a { 334 | color: inherit; 335 | text-decoration: none; 336 | font-weight: 400; 337 | } 338 | 339 | .info a:hover { 340 | text-decoration: underline; 341 | } 342 | 343 | /* 344 | Hack to remove background from Mobile Safari. 345 | Can't use it globally since it destroys checkboxes in Firefox 346 | */ 347 | @media screen and (-webkit-min-device-pixel-ratio:0) { 348 | .toggle-all, .todo-list li .toggle { 349 | background: none; 350 | } 351 | .todo-list li .toggle { 352 | height: 40px; 353 | } 354 | .toggle-all { 355 | -webkit-transform: rotate(90deg); 356 | transform: rotate(90deg); 357 | -webkit-appearance: none; 358 | appearance: none; 359 | } 360 | } 361 | 362 | @media ( max-width : 430px) { 363 | .footer { 364 | height: 50px; 365 | } 366 | .filters { 367 | bottom: 10px; 368 | } 369 | } -------------------------------------------------------------------------------- /vertxui-core/src/main/java/live/connector/vertxui/server/FigWheelyServer.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.server; 2 | 3 | import java.io.File; 4 | import java.lang.invoke.MethodHandles; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | 10 | import io.vertx.core.AbstractVerticle; 11 | import io.vertx.core.Handler; 12 | import io.vertx.core.Vertx; 13 | import io.vertx.core.file.FileSystem; 14 | import io.vertx.core.http.HttpServer; 15 | import io.vertx.ext.web.RoutingContext; 16 | import io.vertx.ext.web.handler.StaticHandler; 17 | 18 | /** 19 | * Figwheely is for detecting javascript or file changes, and then triggering a 20 | * recompilation (if it is java for javascript compilation) and notify the 21 | * browser of these changes. 22 | * 23 | * You can also send a message to the browser, for example: 24 | * vertx.eventBus().publish(FigWheely.figNotify, "bla die bla"); 25 | * 26 | * @author ng 27 | * 28 | */ 29 | public class FigWheelyServer extends AbstractVerticle { 30 | 31 | private final static Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); 32 | 33 | public static String urlSocket = "figwheelySocket"; 34 | public static int portSocket = 8090; 35 | 36 | protected static boolean started = false; 37 | 38 | private final static String browserIds = "figwheelyEventBus"; 39 | public final static String figNotify = "figNotify"; 40 | private static List watchables = new ArrayList<>(); 41 | 42 | private static class Watchable { 43 | 44 | public Watchable(String url, String root, VertxUI handler) { 45 | this.url = url; 46 | this.root = root; 47 | this.handler = handler; 48 | this.lastState = getCurrentState(); 49 | } 50 | 51 | public Watchable(String url, String root) { 52 | this.url = url; 53 | this.root = root; 54 | this.lastState = getCurrentState(); 55 | } 56 | 57 | String url; 58 | String root; 59 | VertxUI handler; 60 | String lastState; 61 | 62 | public String getCurrentState() { 63 | 64 | // file 65 | File file = new File(root); 66 | if (file.isFile()) { 67 | return file.getAbsolutePath() + file.lastModified(); 68 | } 69 | 70 | // folder 71 | StringBuilder result = new StringBuilder(); 72 | getContent(Vertx.currentContext().owner().fileSystem(), root, result); 73 | return result.toString(); 74 | } 75 | 76 | private void getContent(FileSystem fileSystem, String path, StringBuilder result) { 77 | fileSystem.readDirBlocking(path).forEach(file -> { 78 | File jfile = new File(file); 79 | if (jfile.isDirectory()) { 80 | getContent(fileSystem, jfile.getAbsolutePath(), result); 81 | } else { 82 | result.append(jfile.getAbsolutePath()); 83 | result.append(jfile.lastModified()); 84 | } 85 | }); 86 | } 87 | 88 | } 89 | 90 | /** 91 | * Serve the root folder as static handler, but with notifications to the 92 | * browser if folder change. Does not work when figwheely wasn't started before, 93 | * so there is no performance loss if you leave this on. 94 | * 95 | * @param root the root folder 96 | * @param urlWithoutAsterix the url but without the asterix at the end 97 | * @return a static file handler with figwheely support 98 | */ 99 | public static Handler staticHandler(String root, String urlWithoutAsterix) { 100 | // log.info("creating figwheely static handler, started=" + 101 | // FigWheely.started); 102 | if (FigWheelyServer.started) { 103 | FigWheelyServer.addFromStaticHandler(Vertx.factory.context().owner().fileSystem(), root, urlWithoutAsterix, 104 | root); 105 | } 106 | 107 | return StaticHandler.create(root).setCachingEnabled(false).setDefaultContentEncoding(VertxUI.charset); 108 | } 109 | 110 | private static void addFromStaticHandler(FileSystem fileSystem, String sourcePath, String url, String rootroot) { 111 | fileSystem.readDir(sourcePath, files -> { 112 | if (files.result() == null) { 113 | return; 114 | } 115 | for (String item : files.result()) { 116 | File file = new File(item); 117 | if (file.isFile()) { 118 | watchables.add(new Watchable(url + file.getName(), item)); 119 | } else { 120 | addFromStaticHandler(fileSystem, item, url + file.getName() + "/", rootroot); 121 | } 122 | } 123 | }); 124 | } 125 | 126 | @Override 127 | public void start() { 128 | HttpServer server = vertx.createHttpServer(); 129 | server.websocketHandler(webSocket -> { 130 | if (!webSocket.path().equals("/" + urlSocket)) { 131 | webSocket.reject(); 132 | return; 133 | } 134 | final String id = webSocket.textHandlerID(); 135 | // log.info("welcoming " + id); 136 | vertx.sharedData().getLocalMap(browserIds).put(id, "whatever"); 137 | webSocket.closeHandler(data -> { 138 | vertx.sharedData().getLocalMap(browserIds).remove(id); 139 | }); 140 | webSocket.handler(buffer -> { 141 | log.info(buffer.toString()); 142 | }); 143 | }); 144 | server.listen(portSocket, listenHandler -> { 145 | if (listenHandler.failed()) { 146 | log.log(Level.SEVERE, "Startup error", listenHandler.cause()); 147 | System.exit(0); // stop on startup error 148 | } 149 | }); 150 | 151 | // Ensure that even outside this class people can push messages to the 152 | // browser 153 | vertx.eventBus().consumer(figNotify, message -> { 154 | for (Object obj : vertx.sharedData().getLocalMap(browserIds).keySet()) { 155 | vertx.eventBus().send((String) obj, message.body()); 156 | } 157 | }); 158 | 159 | vertx.setPeriodic(250, id -> { 160 | if (VertxUI.isCompiling()) { 161 | return; 162 | } 163 | for (Watchable watchable : watchables) { 164 | String currentState = watchable.getCurrentState(); 165 | if (watchable.lastState.equals(currentState)) { 166 | continue; 167 | } 168 | log.info("Changed: " + watchable.root); 169 | watchable.lastState = currentState; 170 | if (watchable.handler == null) { 171 | log.info("Skipping recompile, no handler found."); 172 | } else { 173 | // Recompile 174 | boolean succeeded = watchable.handler.translate(); 175 | if (succeeded == false) { 176 | vertx.eventBus().publish(figNotify, 177 | "unsuccessfull rebuild for url=" + watchable.url + " root=" + watchable.root); 178 | } 179 | } 180 | vertx.eventBus().publish(figNotify, "reload: " + watchable.url); 181 | } 182 | }); 183 | } 184 | 185 | // Internet Explorer 11 does not have .endsWith() 186 | private static final String script = "function endsWith(str, suffix) {return str.indexOf(suffix, str.length - suffix.length) !== -1;}" 187 | + "var _fig = new WebSocket('ws://localhost:" + portSocket + "/" + urlSocket + "'); " 188 | + "_fig.onmessage = function(m) {console.log(m.data);removejscssfile(m.data.substr(8));}; \n " 189 | + "console.log('FigWheely loaded'); \n " 190 | + "function removejscssfile(filename){ \n" 191 | + "if (endsWith(filename,'js')) filetype='js'; else filetype='css'; " 192 | + "var el = (filetype=='js')? 'script':'link'; " 193 | + "var attr = (filetype=='js')? 'src':'href'; \n" 194 | + "var all =document.getElementsByTagName(el); \n" 195 | + "for (var i=all.length; i>=0; i--) { \n" 196 | + " if (all[i] && all[i].getAttribute(attr)!=null && all[i].getAttribute(attr).indexOf(filename)!=-1) {" 197 | + " var parent=all[i].parentNode; \n" 198 | + " parent.removeChild(all[i]); \n" 199 | + " var script = document.createElement(el); \n" 200 | + " if (filetype=='js') { \n" 201 | + " script.type='text/javascript'; script.src=filename;" 202 | + " } else { " 203 | + " script.rel='stylesheet'; script.href=filename+'?'+(new Date().getTime());" 204 | + " } " 205 | + " parent.appendChild(script); \n" + " } } }; "; 206 | 207 | /** 208 | * Create handler which serves the figwheely javascript. Also turns on the wheel 209 | * of figwheely. 210 | * 211 | * @return the static handler which servers the necessary javascript. 212 | */ 213 | public static Handler create() { 214 | if (!started) { 215 | started = true; 216 | Vertx.currentContext().owner().deployVerticle(FigWheelyServer.class.getName()); 217 | } 218 | return context -> { 219 | context.response().putHeader("Content-Type", "text/javascript; charset=utf-8").end(FigWheelyServer.script); 220 | }; 221 | } 222 | 223 | public static void addWatchable(String url, String root, VertxUI handler) { 224 | watchables.add(new Watchable(url, root, handler)); 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /vertxui-examples/src/main/java/live/connector/vertxui/samples/client/mvcBootstrap/View.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.samples.client.mvcBootstrap; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.Button; 4 | import static live.connector.vertxui.client.fluent.Fluent.Div; 5 | import static live.connector.vertxui.client.fluent.Fluent.Form; 6 | import static live.connector.vertxui.client.fluent.Fluent.Input; 7 | import static live.connector.vertxui.client.fluent.Fluent.Label; 8 | import static live.connector.vertxui.client.fluent.Fluent.Select; 9 | import static live.connector.vertxui.client.fluent.Fluent.Span; 10 | import static live.connector.vertxui.client.fluent.Fluent.Td; 11 | import static live.connector.vertxui.client.fluent.Fluent.Ul; 12 | import static live.connector.vertxui.client.fluent.FluentBase.body; 13 | import static live.connector.vertxui.client.fluent.FluentBase.head; 14 | import static live.connector.vertxui.client.fluent.FluentBase.window; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Date; 18 | 19 | import com.google.gwt.core.client.EntryPoint; 20 | import com.google.gwt.core.client.GWT; 21 | 22 | import live.connector.vertxui.client.FigWheelyClient; 23 | import live.connector.vertxui.client.fluent.Att; 24 | import live.connector.vertxui.client.fluent.Css; 25 | import live.connector.vertxui.client.fluent.Fluent; 26 | import live.connector.vertxui.client.fluent.ViewOn; 27 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills; 28 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Bill; 29 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Bills.Name; 30 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Grocery; 31 | import live.connector.vertxui.samples.client.mvcBootstrap.dto.Totals; 32 | 33 | public class View implements EntryPoint { 34 | 35 | // Views 36 | private ViewOn menu; 37 | private ViewOn totals; 38 | private ViewOn bills; 39 | private ViewOn billsForm; 40 | private ViewOn grocery; 41 | 42 | public static ArrayList getCss() { 43 | ArrayList result = new ArrayList<>(); 44 | result.add("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"); 45 | result.addAll(ComponentDatePicker.getCss()); 46 | return result; 47 | } 48 | 49 | public static ArrayList getScripts() { 50 | return ComponentDatePicker.getScripts(); 51 | } 52 | 53 | @Override 54 | public void onModuleLoad() { 55 | Store store = new Store(); 56 | Controller controller = new Controller(store, this); 57 | start(controller); 58 | } 59 | 60 | public void start(Controller controller) { 61 | head.script(FigWheelyClient.urlJavascript); 62 | 63 | // Initialise models: 64 | String menuStart = "home"; 65 | if (GWT.isClient()) { // in junit tests, there is no Fluent.window 66 | String url = Fluent.window.getLocation().getHref(); 67 | int start = url.indexOf("#"); 68 | if (start != -1) { 69 | menuStart = url.substring(start + 1, url.length()); 70 | } 71 | if (!menuStart.equals("home") && !menuStart.equals("bills") && !menuStart.equals("grocery")) { 72 | menuStart = "home"; 73 | } 74 | } 75 | 76 | // Initialise view: 77 | 78 | // Page head 79 | head.meta().att(Att.name_, "viewport", Att.content, "width=device-width, initial-scale=1"); 80 | 81 | // Header 82 | Fluent container = body.div("container"); 83 | container.h1("jumbotron text-center", "Bills").id("titlerForJunitTest"); 84 | 85 | // Menu 86 | menu = container.nav("navbar navbar-inverse").add(menuStart, selected -> { 87 | Fluent result = Ul("nav navbar-nav"); 88 | result.li(selected.equals("home") ? "active" : null).a(null, "Home", "#home", controller::onMenuHome); 89 | result.li(selected.equals("bills") ? "active" : null).a(null, "Bills", "#bills", controller::onMenuBills); 90 | result.li(selected.equals("grocery") ? "active" : null).a(null, "Grocery", "#grocery", 91 | controller::onMenuGrocery); 92 | return result; 93 | }); 94 | 95 | // Totals 96 | totals = container.add(controller.getTotals(), totals -> { 97 | if (totals.all.isEmpty()) { 98 | return null; 99 | } 100 | Fluent result = Div("row"); 101 | result.div("col-sm-3"); 102 | 103 | double niels = totals.all.get(Name.Niels); 104 | double linda = totals.all.get(Name.Linda); 105 | 106 | result.div("col-sm-3").h4().span("label label-" + (niels > linda ? "success" : "warning"), 107 | Name.Niels.name() + ": " + niels + ""); 108 | result.div("col-sm-3").h4().span("label label-" + (linda > niels ? "success" : "warning"), 109 | Name.Linda.name() + ": " + linda + ""); 110 | result.div("col-sm-3"); 111 | return result; 112 | }); 113 | 114 | // a detached view on a bills form 115 | billsForm = new ViewOn(false, opened -> { 116 | if (opened == false) { 117 | return Button("btn btn-primary", "button", "Add").click((fluent, event) -> { 118 | 119 | // change the state of myself, which causes redrawing of 120 | // myself! 121 | billsForm.state(true); 122 | }); 123 | } 124 | 125 | Fluent result = Form(); 126 | 127 | // input fields in bootstrap: a div/input-group with a 128 | // span/input-group-addon and a form element/form-control, pfff 129 | Fluent name = Select("form-control", null, Name.Niels.name(), null, Name.Linda.name(), null); 130 | Fluent amount = Input("form-control", "number").att(Att.min, "0", Att.max, "2000", Att.value, "") 131 | .keypress(controller::onBillOnlyNumeric); 132 | ComponentDatePicker when = new ComponentDatePicker(); 133 | Fluent what = Input("form-control", "text"); 134 | 135 | Fluent text = Span("input-group-addon").css(Css.width, "100px"); 136 | 137 | result.div("input-group", text.clone().txt("Name"), name).css(Css.width, "80%"); 138 | result.div("input-group", text.clone().txt("Amount"), amount).css(Css.width, "80%"); 139 | result.div("input-group", text.clone().txt("When"), when).css(Css.width, "80%"); 140 | result.div("input-group", text.clone().txt("What"), what).css(Css.width, "80%"); 141 | 142 | result.button("btn btn-success", "button", "OK").click((fluent, event) -> { 143 | int much = 0; 144 | try { 145 | String muchRead = amount.domValue(); 146 | if (muchRead.isEmpty()) { 147 | throw new Exception("Illegal amount"); 148 | } 149 | much = Integer.parseInt(amount.domValue()); 150 | if (much == 0) { 151 | throw new Exception("Much is 0"); 152 | } 153 | } catch (Exception __) { 154 | Fluent.window.alert("Please fill in an amount."); 155 | return; 156 | } 157 | Date date = null; 158 | try { 159 | date = when.getDate(); 160 | } catch (IllegalArgumentException e) { 161 | Fluent.window.alert("Please fill in the date."); 162 | return; 163 | } 164 | String description = what.domValue(); 165 | if (description.length() >= 32) { 166 | Fluent.window.alert("Please do not use more than 32 characters for the description."); 167 | return; 168 | } 169 | controller.onAddBill(name.domValue(), much, description, date); 170 | billsForm.state(false); 171 | }); 172 | return result; 173 | }); 174 | 175 | bills = container.add(controller.getBills(), bills -> { 176 | if (bills.all == null) { 177 | return null; 178 | } 179 | Fluent result = Div(); 180 | result.add(billsForm); 181 | Fluent table = result.table("table table-condensed table-striped").tbody(); 182 | for (Bill bill : bills.all) { 183 | String when = ComponentDatePicker.dateTimeFormat.format(bill.date); 184 | table.tr(Td(null, bill.who.name()), Td(null, bill.amount + ""), Td(null, bill.what), Td(null, when)); 185 | } 186 | return result; 187 | }); 188 | 189 | grocery = container.add(controller.getGrocery(), grocery -> { 190 | if (grocery.all == null) { 191 | return null; 192 | } 193 | Fluent form = Div().form("form"); 194 | 195 | form.div("form-group", Label(null, "Name ").att(Att.for_, "n"), Input("form-control", "text").id("n") 196 | .css(Css.maxWidth, "200px").keypress(controller::onGroceryAdd)); 197 | form.div(grocery.all.stream().map(s -> { 198 | Fluent result = Div().css(Css.marginTop, "20px"); 199 | result.input(null, "checkbox").att(Att.value, s).click((fluent, event) -> { 200 | 201 | // Reset checkbox: 202 | fluent.domChecked(); // synchronize the virtual DOM 203 | fluent.att(Att.checked, null); // before adjusting it 204 | 205 | if (window.confirm("Delete '" + s + "'?")) { 206 | controller.onGroceryDelete(fluent, event); 207 | } 208 | }); 209 | result.span(null, s).css(Css.fontSize, "140%", Css.marginLeft, "20px"); 210 | return result; 211 | })); 212 | return form; 213 | }); 214 | 215 | // Init data 216 | switch (menuStart) { 217 | case "home": 218 | controller.onMenuHome(null, null); 219 | break; 220 | case "bills": 221 | controller.onMenuBills(null, null); 222 | break; 223 | case "grocery": 224 | controller.onMenuGrocery(null, null); 225 | break; 226 | } 227 | } 228 | 229 | public void syncGrocery() { 230 | grocery.sync(); 231 | } 232 | 233 | public void syncBills() { 234 | bills.sync(); 235 | } 236 | 237 | public void syncTotals() { 238 | totals.sync(); 239 | } 240 | 241 | public void stateAndSyncMenu(String state) { 242 | menu.state(state); 243 | 244 | switch (state) { 245 | case "home": 246 | totals.hide(false); 247 | bills.hide(true); 248 | grocery.hide(true); 249 | break; 250 | case "bills": 251 | totals.hide(true); 252 | bills.hide(false); 253 | grocery.hide(true); 254 | break; 255 | case "grocery": 256 | totals.hide(true); 257 | bills.hide(true); 258 | grocery.hide(false); 259 | break; 260 | } 261 | } 262 | 263 | public void errorBillAdd() { 264 | window.alert("Could not add the new bill"); 265 | } 266 | 267 | public void errorGroceryDelete(String text) { 268 | window.alert("Could not delete grocery item '" + text + "'."); 269 | } 270 | 271 | public void errorGroceryAdd(String text) { 272 | window.alert("Could not add grocery item '" + text + "'."); 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /vertxui-core/src/test/java/live/connector/vertxui/client/FluentRenderer.java: -------------------------------------------------------------------------------- 1 | package live.connector.vertxui.client; 2 | 3 | import static live.connector.vertxui.client.fluent.Fluent.*; 4 | import static live.connector.vertxui.client.test.Asserty.assertEquals; 5 | import static live.connector.vertxui.client.test.Asserty.assertTrue; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.junit.Test; 14 | 15 | import com.google.gwt.core.shared.GwtIncompatible; 16 | 17 | import elemental.css.CSSStyleDeclaration; 18 | import elemental.dom.Element; 19 | import elemental.dom.NamedNodeMap; 20 | import elemental.events.Event; 21 | import live.connector.vertxui.client.fluent.Att; 22 | import live.connector.vertxui.client.fluent.Css; 23 | import live.connector.vertxui.client.fluent.Fluent; 24 | import live.connector.vertxui.client.fluent.ViewOn; 25 | import live.connector.vertxui.client.test.TestDOM; 26 | 27 | public class FluentRenderer extends TestDOM { 28 | 29 | @GwtIncompatible 30 | @Test 31 | public void aTest() throws Exception { 32 | runJS(1); 33 | runJS(2); 34 | } 35 | 36 | public Map registerJS() { 37 | Map result = new HashMap<>(); 38 | result.put(1, () -> attributes()); 39 | result.put(2, () -> { 40 | styles(); 41 | listeners(); 42 | }); 43 | return result; 44 | } 45 | 46 | private void attributes() { 47 | ViewOn view = body.add(0, s -> { 48 | Fluent result = Fluent.Div(); 49 | switch (s) { 50 | case 0: 51 | result.att(Att.id, "id"); 52 | break; 53 | case 1: 54 | result.att(Att.id, "id"); 55 | result.att(Att.accept, "accept"); 56 | break; 57 | case 2: 58 | result.att(Att.alt, "alt"); 59 | break; 60 | case 3: 61 | break; 62 | case 4: 63 | result.att(Att.id, "id"); 64 | result.att(Att.content, "content"); 65 | result.att(Att.accept, "accept"); 66 | break; 67 | } 68 | return result; 69 | }); 70 | 71 | // Should add: id 72 | List attributeNames = getAllNamesFromAttributes(view); 73 | assertEquals("length should be 1", attributeNames.size(), 1); 74 | assertTrue("first should be id but is ", attributeNames.get(0).equals("id")); 75 | 76 | // Should add accept 77 | view.state(1); 78 | attributeNames = getAllNamesFromAttributes(view); 79 | assertEquals("2", attributeNames.size(), 2); 80 | assertEquals("2.1", attributeNames.get(0), "accept"); 81 | assertEquals("2.2", attributeNames.get(1), "id"); 82 | 83 | // Should remove accept 84 | view.state(0); 85 | attributeNames = getAllNamesFromAttributes(view); 86 | assertEquals("3", attributeNames.size(), 1); 87 | assertEquals("3.1", attributeNames.get(0), "id"); 88 | 89 | // Should add accept 90 | view.state(1); 91 | attributeNames = getAllNamesFromAttributes(view); 92 | assertEquals("4", attributeNames.size(), 2); 93 | assertEquals("4.1", attributeNames.get(0), "accept"); 94 | assertEquals("4.2", attributeNames.get(1), "id"); 95 | 96 | // Should remove id accept, and add alt 97 | view.state(2); 98 | attributeNames = getAllNamesFromAttributes(view); 99 | assertEquals("5", attributeNames.size(), 1); 100 | assertEquals("5.1", attributeNames.get(0), "alt"); 101 | 102 | // Should add id and accept, remove alt 103 | view.state(1); 104 | attributeNames = getAllNamesFromAttributes(view); 105 | assertEquals("6", attributeNames.size(), 2); 106 | assertEquals("6.1", attributeNames.get(0), "accept"); 107 | assertEquals("6.2", attributeNames.get(1), "id"); 108 | 109 | // Should remove id and accept 110 | view.state(3); 111 | attributeNames = getAllNamesFromAttributes(view); 112 | assertEquals("6.3", attributeNames.size(), 0); 113 | 114 | // Should add id accept 115 | view.state(1); 116 | attributeNames = getAllNamesFromAttributes(view); 117 | assertEquals("7", attributeNames.size(), 2); 118 | assertEquals("7.1", attributeNames.get(0), "accept"); 119 | assertEquals("7.2", attributeNames.get(1), "id"); 120 | 121 | // Should add checked 122 | view.state(4); 123 | attributeNames = getAllNamesFromAttributes(view); 124 | assertEquals("8", attributeNames.size(), 3); 125 | assertEquals("8.1", attributeNames.get(0), "accept"); 126 | assertEquals("8.2", attributeNames.get(1), "content"); 127 | assertEquals("8.3", attributeNames.get(2), "id"); 128 | 129 | // Should remove checked 130 | view.state(1); 131 | attributeNames = getAllNamesFromAttributes(view); 132 | assertEquals("9", attributeNames.size(), 2); 133 | assertEquals("9.1", attributeNames.get(0), "accept"); 134 | assertEquals("9.2", attributeNames.get(1), "id"); 135 | } 136 | 137 | private List getAllNamesFromAttributes(ViewOn view) { 138 | NamedNodeMap attributes = view.getView().dom().getAttributes(); 139 | List attributeNames = new ArrayList<>(); 140 | for (int x = 0; x < attributes.length(); x++) { 141 | attributeNames.add(attributes.item(x).getNodeName()); 142 | } 143 | Collections.sort(attributeNames); 144 | return attributeNames; 145 | } 146 | 147 | private void styles() { 148 | // tricky to test because the browser does whatever it wants. First of 149 | // all, only valid values get through, the rest is ignored. And 150 | // sometimes things are added extra (like when setting the background). 151 | 152 | ViewOn view = body.add(0, s -> { 153 | Fluent result = Fluent.Div(); 154 | switch (s) { 155 | case 0: 156 | result.css(Css.color, "blue"); 157 | break; 158 | case 1: 159 | result.css(Css.color, "blue"); 160 | result.css(Css.fontSize, "50px"); 161 | break; 162 | case 2: 163 | result.css(Css.marginLeft, "0"); 164 | break; 165 | case 3: 166 | break; 167 | case 4: 168 | result.css(Css.color, "blue"); 169 | result.css(Css.fontSize, "50px"); 170 | result.css(Css.textAlign, "left"); 171 | break; 172 | } 173 | return result; 174 | }); 175 | 176 | assertTrue("is this the real dom or just fantasy?", body.dom() != null); 177 | 178 | List styleNames = getAllNamesFromStyles(view); 179 | assertEquals("length should be 1", styleNames.size(), 1); 180 | assertTrue("1", styleNames.get(0).equals("color")); 181 | 182 | view.state(1); 183 | styleNames = getAllNamesFromStyles(view); 184 | assertEquals("2", styleNames.size(), 2); 185 | assertEquals("2.1", styleNames.get(0), "color"); 186 | assertEquals("2.2", styleNames.get(1), Css.fontSize.nameValid()); 187 | 188 | view.state(0); 189 | styleNames = getAllNamesFromStyles(view); 190 | assertEquals("length should be 1", styleNames.size(), 1); 191 | assertTrue("and the first item color", styleNames.get(0).equals("color")); 192 | 193 | view.state(1); 194 | styleNames = getAllNamesFromStyles(view); 195 | assertEquals("3", styleNames.size(), 2); 196 | assertEquals("3.1", styleNames.get(0), "color"); 197 | assertEquals("3.2", styleNames.get(1), Css.fontSize.nameValid()); 198 | 199 | view.state(2); 200 | styleNames = getAllNamesFromStyles(view); 201 | assertEquals("4", styleNames.size(), 1); 202 | assertEquals("4.1", styleNames.get(0), "margin-left"); 203 | 204 | view.state(1); 205 | styleNames = getAllNamesFromStyles(view); 206 | assertEquals("5", styleNames.size(), 2); 207 | assertEquals("5.1", styleNames.get(0), "color"); 208 | assertEquals("5.2", styleNames.get(1), Css.fontSize.nameValid()); 209 | 210 | view.state(3); 211 | styleNames = getAllNamesFromStyles(view); 212 | assertEquals("6", styleNames.size(), 0); 213 | 214 | view.state(1); 215 | styleNames = getAllNamesFromStyles(view); 216 | assertEquals("7", styleNames.size(), 2); 217 | assertEquals("7.1", styleNames.get(0), "color"); 218 | assertEquals("7.2", styleNames.get(1), Css.fontSize.nameValid()); 219 | 220 | view.state(4); 221 | styleNames = getAllNamesFromStyles(view); 222 | assertEquals("8", styleNames.size(), 3); 223 | assertEquals("8.1", styleNames.get(0), "color"); 224 | assertEquals("8.2", styleNames.get(1), "font-size"); 225 | assertEquals("8.3", styleNames.get(2), "text-align"); 226 | 227 | view.state(1); 228 | styleNames = getAllNamesFromStyles(view); 229 | assertEquals("9", styleNames.size(), 2); 230 | assertEquals("9.1", styleNames.get(0), "color"); 231 | assertEquals("9.2", styleNames.get(1), "font-size"); 232 | } 233 | 234 | private List getAllNamesFromStyles(ViewOn view) { 235 | CSSStyleDeclaration styles = ((Element) view.getView().dom()).getStyle(); 236 | List result = new ArrayList<>(); 237 | for (int x = 0; x < styles.getLength(); x++) { 238 | result.add(styles.item(x)); 239 | } 240 | Collections.sort(result); 241 | return result; 242 | } 243 | 244 | private void listeners() { 245 | // unable to test, this is a manual test 246 | ViewOn view = body.add(0, s -> { 247 | Fluent result = Fluent.Div(); 248 | switch (s) { 249 | case 0: 250 | result.click(this::a); 251 | break; 252 | case 1: 253 | result.click(this::b); 254 | break; 255 | case 2: 256 | result.click(this::b); 257 | result.keypress(this::c); 258 | break; 259 | case 3: 260 | break; 261 | case 4: 262 | result.click(this::a); 263 | result.keypress(this::c); 264 | result.dblclick(this::b); 265 | break; 266 | } 267 | return result; 268 | }); 269 | 270 | // console.log("should replace click"); 271 | view.state(1); 272 | // console.log("2. should add focus and keep click"); 273 | view.state(2); 274 | // console.log("3. should remove click and focus"); 275 | view.state(3); 276 | // console.log("should add click focus and dblclick"); 277 | view.state(4); 278 | // console.log("should remove focus and dblclick"); 279 | view.state(0); 280 | // console.log("should add focus and dblclick"); 281 | view.state(4); 282 | } 283 | 284 | public void a(Fluent __, Event e) { 285 | } 286 | 287 | public void b(Fluent __, Event e) { 288 | } 289 | 290 | public void c(Fluent __, Event e) { 291 | } 292 | 293 | } --------------------------------------------------------------------------------