Please start by reading our What does it look like? section. It contains most of the information you need to write usual ST-JS code. On top of that, our tutorial will guide you through all the steps necessary to create and run your first ST-JS project with Maven.
If you need to use more advanced features of ST-JS, or haven't found out how a particular JavaScript construct can be expressed in Java, then keep on reading.
Most of the Java code you write will be translated basically as-is to its JavaScript counterpart. However, there are some JavaScript constructs that cannot be written as-is in Java. For these cases, ST-JS provides workarounds as special fields and methods.
All of those special fields and methods are easy to identify: there name always starts with the dollar sign ($). When you use these methods you should expect the generated JavaScript code to look slighty different (it is usually prettier, actually) than the corresponding Java construct.
Here is the list of these constructs and methods:
// JavaScript // Java equivalent // Create a map with the given keys and values {k1:v1, k2:v2} $map(k1,v1,k2,v2) // Create an array with the given items [a, b, c] $array(a, b, c) // Convert a java array to the org.stjs.javascript.Array type var a=b Array<T> a = $castArray(T[]b)
// JavaScript // Java equivalent // Put a key and a value in a map x[a] = b x.$put(a, b) // Retrieve a value from a map x[a] x.$get(a) // Delete an entry from a map delete x[a] x.$delete(a)
// JavaScript // Java equivalent // Retrieve an item from an array x[a] x.$get(a) // Modify an item in an array x[index] = val x.$set(index, val)
// JavaScript // Java equivalent // expose all the properties of an object as a map propsMap = obj propsMap = $properties(obj) // get the value of a property of an object by name obj["prop"] $properties(obj).$get("prop") // convert a map as an object of a given type with the same properties var obj = map T obj = $object(map) // Return the prototype of a JavaScript objects obj.prototype $prototype(obj) // Return the constructor of a JavaScript object obj.constructor $constructor(obj) // Replaces with the given javascriptCode (only String literals accepted). // Use it only in extreme cases as it makes it impossible to minimize the code. javascriptCode $js(javascriptCode)
// JavaScript // Java equivalent // Return first value the is equivalent to true in JavaScript a || b || c $or(a,b,c)
In most cases, there is no need to change the way the JavaScript is generated. There are however come cases where this is useful
ST-JS lets you customize the way the JavaScript is generated (if you want to). You can give hints to the generator by placing annotations in your Java code.
The annotated Class, or all the classes in the annotated package will be placed in the specified namespace. This is useful to avoid name clashes with classes coming from other libraries.
// Java with @Namespace @Namespace("genie.in.a") public class Bottle { public Bottle() { doSomething(); } } // Java without @Namespace public class Plain { public Plain() { doNothing(); } }
// JavaScript with @Namespace stjs.ns("genie.in.a"); genie.in.a.Bottle = function() { doSomething(); } // JavaScript without @Namespace Plain = function() { doNothing(); }
All the anonymous implementations of the annotated single-method Java interface will be generated as a JavaScript anonymous function. All invocations of the method contained in the annotated interface will be generated as a direct call to that function. The interface itself will not be generated in Javascript
// Java @JavascriptFunction public interface EventHandler { public boolean onEvent(Event evt); } public class Something { public Something(Event event){ // defining a new anonymous implementation EventHandler handler = (evt) -> { console.print(evt.name); }; // calling the single-method handler.onEvent(event); } }
// JavaScript // the interface is not generated Something = function(event){ // translated as an anonymous function var handler = function(evt) { console.print(evt.name); }; // invoke the function directly handler(event); }
The static fields and methods in the annotated type are considered already being part of the global scope at runtime, typically provided by an existing imported JavaScript library. When a type is annotated with @GlobalScope, no corresponding JavaScript is generated.
This annotation is useful when writing bridges for an existing JavaScript library which declares some of its members in the global scope (typical example: the $ identifier for JQuery). org.stjs.javascript.Global is an example of class with this annotation, which contains the declaration of several global objects such as "window" or "console".
// Java (declaration) @GlobalScope public class JQuerySubset { public static JQuery $; public static JQuery $(String selector) { // this doesn't need to be implemented // the real implementation comes // directly from jquery.js throw new UnsupportedOperationException(); } } // Java (usage) import static JQuerySubset.$; public class Something { public Something(){ $(".hello").addClass(".world"); } }
// JavaScript (declaration) // nothing is generated, implementation comes from // including jquery.js // JavaScript (usage) Something = new function(){ $(".hello").addClass(".world"); }
The annotated class is marked as a data-only object. Objects marked with this annotation can only contain non-private fields and cannot contain any methods. Instance of a class marked with this annotation can be initialized using the double-brace syntax in your java source code, and will be translated to a Javascript Object Literal.
This annotation is typically used by bridges to provide a correctly typed equivalent of a parameter that the javscript library expects to be an Object Literal. An example is AjaxParams used by JQuery.ajax
// Java (declaration) @SyntheticType public class AjaxParams { // some fields are omitted for brevity public boolean async; public String url; } // Java (usage) import static JQuerySubset.$; public class Something { public Something(){ $.ajax(new AjaxParams(){ { async = false; url = "http://example.com"; } }); } }
// JavaScript (declaration) var AjaxParams = function(){}; stjs.extend(AjaxParams, null, [], function(constructor, prototype){ prototype.async = null; prototype.url = null; }, {}); // JavaScript (usage) Something = function(){ $.ajax({ async : false, url : "http://example.com" }); }
The annotated package or type is marked as a simple bridge between ST-JS Java source code and an existing JavaScript library. The purpose of types annotated (or contained withing packages that are annotated) with @STJSBridge is only to provide information about types, fields and methods contained defined by an existing external JavaScript library to the Java tooling.
The JavaScript source file that contains the actual implementation that the bridge corresponds to should be specified using the sources field of @STJSBridge. By default the sources location is specified as relative to the classpath. Since ST-JS 3.2.0, the sources location can be specified using a webjar: url.
// Classpath URI @STJSBridge( sources = {"/path/to/js/jquery.1.11.js"} ) public class WithClassPath { ... } // Webjar URI @STJSBridge( sources = {"webjar:/jquery.js"} ) public class WithWebjar { ... }
The static methods in the annotated type are marked as being members of another existing JavaScript type. The scope of this annotation is rather narrow, and is used by ST-JS to make it possible to use all the methods that the JavaScript standard library defines for types analog to those in the java.lang package, such as Number(and its subclasses), String, Object, etc...
ST-JS tries to keep the Java code as close as possible to the generated JavaScript code, and therefore uses java.lang.Number (and subclasses) and java.lang.String to represent the equivalent types of JavaScript. However, the member methods of each of these types are different in Java and in JavaScript. ST-JS hides this difference by providing JavaScript implementations of Java methods in stjs.js (when the Java method doesn't exist in JavaScript), and by using @Adapter Java methods corresponding to the JavaScript methods (when the JavaScript method doesn't exist in Java).
For all the methods of an adapter class, the first parameter must be object to which the method is applied. The other parameters are the parameters normally supplied to the JavaScript method.
// Java (declaration) @Adapter public class JSNumberAdapter { public static String toFixed(Number n, int p) { throw new UnsupportedOperationException(); } public static String toPrecision(Number n, int p) { throw new UnsupportedOperationException(); } } // Java (usage) public class Something { public Something(int val){ String fixed = JSNumberAdapter.toFixed(val, 2); } }
// JavaScript (declaration) // nothing is generated. The implementation is // already provided by the bridged library // JavaScript (usage) Something = function(val){ var fixed = val.toFixed(2); }
this annotation can be used on a method to control how the call to this method will be generated. The parameter taken by this annotation is a name of a defined template. object.$get(i) for example will generate object.$get(i) instead of object[i]. The current list of templates:
adapter = org.stjs.generator.writer.template.AdapterTemplate assert = org.stjs.generator.writer.template.AssertTemplate array = org.stjs.generator.writer.template.ArrayTemplate delete = org.stjs.generator.writer.template.DeleteTemplate get = org.stjs.generator.writer.template.GetTemplate invoke = org.stjs.generator.writer.template.InvokeTemplate js = org.stjs.generator.writer.template.JsTemplate map = org.stjs.generator.writer.template.MapTemplate toProperty = org.stjs.generator.writer.template.MethodToPropertyTemplate none = org.stjs.generator.writer.template.NoneTemplate or = org.stjs.generator.writer.template.OrTemplate properties = org.stjs.generator.writer.template.PropertiesTemplate put = org.stjs.generator.writer.template.PutTemplate set = org.stjs.generator.writer.template.SetTemplate typeOf = org.stjs.generator.writer.template.TypeOfTemplate
// Java bridge public interface A { public int field; //< Illegal, oops... public String method(); }
// JavaScript source (bridged lib) var A = { field: null, method: function(){} }
As you probably know, it is impossible to place a field in an interface in Java. ST-JS provides a workaround by using the template property.
// Java bridge (declaration) public interface A { /** Legal */ @Template("toProperty") public int $field(); /** Legal. Don't add, if the field is read-only*/ @Template("toProperty") public void $field(int s); public String method(); } // Java application code (usage) public class App { public App(A a){ a.$field(42); String b = a.$field(); } }
// JavaScript source (bridged lib) var A = { field : null, method : function(){} } // Generated JavaScript application code (usage) App = function(a){ a.field = 42; var b = a.field; }
This annotation is used to mark the constructors that are less generic when you need to overload the constructors of a class. The effect of using this annotation on a constructor or even a method is that the code of the given constructor/method is not generated at all. When you need to overload constructors, ST-JS checks that only ONE constructor has a body and all the others are marked with @Native. Also, the constructor that has the most generic signature should be left without @Native.
// Java (declaration) public class MyClass { @Native public MyClass(){ } @Native public MyClass(int p1){ } public MyClass(Integer p1, Integer p2){ //do something here } }
// JavaScript (declaration) var MyClass = function(p1, p2){}; //only this constructor is generated! stjs.extend(MyClass, null, [], function(constructor, prototype){ }, {});
The org.stjs.javascript.stjs.STJS interface is the bridge to the JavaScript code coming with the generator. It offers some helpers for different type inexistent in JavaScript:
The JavaScript library (js-lib) is a Java library that you need as a compile-time dependency for the Java source code. This library conveniently exposes a properly typed version of the standard JavaScript library and DOM objects as defined by the browsers. The maven dependency for this library is the following:
For JavaScript and DOM the global functions and objects (like setInterval or window) are found in the org.stjs.javascript.Global class. For jQuery, the $ object and function are found in the org.stjs.javascript.jquery.GlobalJQuery class.<dependency> <groupId>org.st-js.bridge</groupId> <artifactId>html</artifactId> <version>5.0.bv0</version> </dependency>
We currently do not have a place to store the javadoc of those libraries, so for the moment, please use the documentation provided by w3schools.
ST-JS also comes with another Java library that allows you to share objects easily between your client (JavaScript) app and you server (Java) by providing concrete java implementations of some of the classes in the org.stjs.javascript package (such as Array and Map). The maven dependency for this library is the following
<dependency> <groupId>org.st-js</groupId> <artifactId>server</artifactId> <version>${stjs.version}</version> </dependency>
This dependency allows you to create maps and arrays and to serialize them (as JSON for example).
As we have mentionned before, STJS is NOT a web framework, so no visual components or other components are bundled with STJS. You should therefore rely on existing components defined by other JavaScript frameworks like jQuery, DOJO, etc. There are two different ways integrate STJS-generated code with existing JavaScript libraries:
By writing parts of your application in JavaScript, and using the existing library and directly calling the generated STJS code. While you can probably make do with this for a while, this is not the recommended technique.
By writing all your application in ST-JS enabled Java, by writing a Java bridge for the JavaScript library. This is the recommended approach, and will allow you to take advantage of all the benefits and tooling of Java, while still using your favorite, cross-browser library.
STJS already provides bridges for jQuery and jQuery UI plugins. The bridge is composed mostly of Java interfaces that have the same name as their JavaScript counterpart. Sometimes you may also need to provide global objects or functions (that will be implemented as static fields or methods of the object). In the case of a static method, the method should throw UnsupportedOperationException - to indicate it's a method to be used in the JavaScript code (not in some server-side Java code).
One interesting feature of the bridges is that they are not submitted to the same list of constrains as the regular STJS Java code. This means that you can overload methods in order to provide more clarity to the user. A good example is the way plugins are built in jQuery where the same function (usually the plugin's name) has many different usages: activate the plugin, change an option, get a state value, call a method, etc.
The JQuery bridge must be added as a compile time dependency to your project. Here is the maven dependency section for this artifact.
We currently do not have a place to store the javadoc for the jQuery and jQuery UI bridges, so for the moment please use the documentation provided by jQuery and jQuery UI
<dependency> <groupId>org.st-js.bridge</groupId> <artifactId>jqueryui</artifactId> <version>1.10.3.bv0</version> </dependency>
This is the STJS module that will actually take your both Java source code and the corresponding compiled classes, and generate the corresponding JavaScript code that your browser can execute. This module can be used either on the command line, or as a maven plugin.
To use the command line version of the module, please send us an email to ask us to write the documentation for it...
To use the generator as a maven plugin, include the following snippet in your pom.xml
<plugin> <groupId>org.st-js</groupId> <artifactId>stjs-maven-plugin</artifactId> <version>${stjs.version}</version> <executions> <execution> <id>main</id> <goals> <goal>generate</goal> </goals> </execution> <!-- if you use the JUnit runner --> <execution> <id>test</id> <goals> <goal>generate-test</goal> </goals> </execution> </executions> <configuration> <includes> <include>**/*.java</include> </includes> </configuration> </plugin>
You can activate separately the JavaScript generation for main sources and tests. Here are the goals of the maven plugin:
There are some parameters that can be used to configure the plugin:
If you use Eclipse you can take advantage of the Maven / Eclipse integration. The Javascript code is generated each time the corresponding Java class is modified and saved. This way any modification you bring to your code will be instantly observed on the browser! Note: you need the at least the 1.0 version of the Eclipse plugin for Maven.
<plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.st-js</groupId> <artifactId> stjs-maven-plugin </artifactId> <versionRange> [${stjs.version},) </versionRange> <goals> <goal>generate</goal> <!-- if you use the test helper --> <goal>generate-test</goal> </goals> </pluginExecutionFilter> <action> <execute> <runOnIncremental>true</runOnIncremental> </execute> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin>
The ST-JS JUnit runner module allows you to write your unit tests in ST-JS enabled Java, have JUnit run them on a browser of your choice (or multiple browsers in parallel) and report the test results.
In order to use the STJS JUnit runner, you must first make sure it is included in your test classpath. If you are using maven, you can simply add the following dependency to your pom.xml
<dependency> <groupId>org.st-js</groupId> <artifactId>test-helper</artifactId> <version>${stjs.version}</version> <scope>test</scope> </dependency>
To activate the STJS JUnit runner, annotate your JUnit test classes this way
import org.stjs.testing.driver.STJSTestDriverRunner; import org.junit.annotations.Test; @RunWith(STJSTestDriverRunner.class) public class TestMyStuff { @Test public void testMyThing(){ // Your test code goes here } }
This is all you need to get your unit tests to execute in your system's default browser. While writing your unit tests, you can of course use the main JUnit annotations such as @Test, @Before and @After
In order to run your unit tests in a browser, the STJS Junit runner builds an HTML page that includes all the necessary HTML and javascript to execute your tests and return the result to JUnit. By default, the HTML page that is sent to the browser includes the following things:
You can have a better control on what is sent in that HTML page by using some annotations defined by STJS.
This optional annotation can be applied to your test class to force the STJSTestDriverRunner to include a specific HTML fragment on the page it sends to the browser. This is useful if the piece of code you are unit testing does some DOM manipulation and needs some DOM elements to be present to function as expected.
If the HTML fragment is short enough, it can be specified verbatim in the annotation.
@RunWith(STJSTestDriverRunner.class) @HtmlFixture("<div id='importantContainer'></div>") public class TestSimpleFixture { ... }
If the fragment is longer, the annotation allows you to specify a path to a classpath resource that contains the HTML code. The HTML fragment file will be looked up using ClassLoader.getResourceAsStream(). This means that STJSTestDriverRunner (trough the ClassLoader) will first attempt to find the file in your classpath. If the file cannot be found in your classpath, then STJSTestDriverRunner will look for the file starting at the "document root" or your webapp, if any. For a typical maven project, this means it will first look in /target/WEB-INF/classes, and then in /target.
@RunWith(STJSTestDriverRunner.class) @HtmlFixture(url = "/SomeComplexHtmlFragment.html") public class TestSimpleFixture { ... }
These optional annotations can be applied to your test class to force the STJSTestDriverRunner to include extra javascript files in the <head> section of the HTML page that is sent to the browser. This is useful when you are using a bridged JavaScript library that depends on a third party (unbridged) library, and that the unit tests require this library to be in the JavaScript execution environment.
Script files specified in @ScriptsBefore will be included in the HTML page before the class under test and all its dependencies. Script files specifed in @ScriptsAfter will be included in the HTML page after the class under test and all its dependencies.
Each of the strings passed as a value to these annotations will be used verbatim in the generated HTML. This means that you can pass either a path to a javascript file that exists within your project, or one that resides on another domain.
@RunWith(STJSTestDriverRunner.class) @ScriptsBefore({"/someLibBefore.js"}) @ScriptsAfter({"http://example.com/someLibAfter.js"}) public class TestHelloWorld { ... }
Will generate the following fragment of HTML
<head> <!-- contents of @ScriptsBefore --> <script src="/someLibBefore.js" type="text/javascript"></script> <!-- Class under test plus other stuff required by stjs --> <script src="/stjs.js" type="text/javascript"></script> <script src="/HelloWorld.js" type="text/javascript"></script> <!-- contents of @ScriptsAfter --> <script src="http://example.com/someLibAfter.js" type="text/javascript"></script> </head>
If the script passed to this annotation is a reference to a file defined in your project (ie: it doesn't start with a protocol handler such as http:, file:, ftp:, https:, etc...), then the script file will be looked up using ClassLoader.getResourceAsStream(). This means that STJSTestDriverRunner (trough the ClassLoader) will first attempt to find the file in your class path. If the file cannot be found in your classpath, then STJSTestDriverRunner will look for the file starting at the "document root" or your webapp, if any. For a typical maven project, this means it will first look in /target/WEB-INF/classes, and then in /target.
You can configure some of the test driver's parameters in a file called stjs-test.properties that must be placed the root of your class path. The properties supported by this file are the described in the table below
All these properties can be overridden by specifying them as system properties when launching the java vm. For example to force debug mode when launching the tests via Maven you can run `mvn test -Dstjs.test.debug=true`.
Property | Description |
---|---|
stjs.test.config | The classpath location of properties file that contains the stjs test driver configuration. Default value is "/stjs-test.properties" |
stjs.test.browsers | A comma separated list of browsers on which to run the tests. A test is successful only if it has been run successfully on all the browsers in this list. See the table below for a description of the supported browsers. The default value is "desktopDefault". |
stjs.test.port | the port opened by the test driver waiting for connection from the browsers. Default value is 8055 |
stjs.test.wait | the time (in seconds) the test driver waits for the number of configured browsers to connect. Default value is 10 |
stjs.test.skipIfNoBrowser | if this is true, if no browser was connected that it considers the tests as ignored (can be used in some batch processing, without failing completely the tests). Default value is false |
stjs.test.startBrowser | if true, if after 2 seconds (normally check of the client's code check is 1 second) no browser connected to the test driver, it tries to start the system's default browser. This can only work is the Desktop.isDesktopSupported() check return true (so usually a developer's machine). Default value is true |
stjs.test.testTimeout | the time (in seconds) the test driver waits for a test to return a result from the browser. Passed this time the test is considered failed. Default value is 2 seconds |
stjs.test.debug | if this is true, debug information is displayed. Default value is false |
stjs.test.debugJavaScript | if true, JUnit tests are run in JavaScript debug mode, where ST-JS gives the opportunity to the developper to manually set JS breakpoints in his browser before the unit test are launched. If this property is not set, ST-JS automatically attempts to detect if the JUnit tests are run from an IDE in debug mode and will activate the JavaScript debug mode if that is the case. Default value is unset (ie: autodetect) |
firefox.bin | the path to the binary of firefox (see "firefox" in the table below) |
chrome.bin | the path to the binary of Google Chrome or chromium (see "chrome" in the table below) |
phantomjs.bin | the path to the binary of phantomjs (see "phantomjs" in the table below) |
The browsers described in the table below can only run on operating systems that provide some kind of graphics sub-system. Fox example, these browsers won't work if your operating system is a Linux distribution without a running X server.
Value | Description |
---|---|
desktopDefault |
Launches whichever browser is the default for the desktop on which the tests are running. On windows the browser is launched using Desktop.getDesktop().browse(). On other systems it is launched using the "xdg-open" utility. This browser might fail to launch on headless machines in some operating systems, for example a linux server without an X11 server. |
firefox |
Launches firefox. STJS will look in the common firefox installation directories to find the binary. If firefox is not installed in the standard location on your target system, you can specify the path of the binary in the firefox.bin property in "stjs-test.properties", in whichever file you specified in the stjs.test.config system property or on the command line. |
chrome |
Launches Goole Chrome (or chromium). STJS will look in the common chrome installation directories to find the binary. If chrome is not installed in the standard location on your target system, you can specify the path of the binary in the chrome.bin property in "stjs-test.properties", in whichever file you specified in the stjs.test.config system property or on the command line. |
Use one of these browsers if your test execution environment doesn't provide a working graphics subsystem (such as a running X server for Linux).
Value | Description |
---|---|
rhino |
Runs the tests in an instance of Rhino that is embedded in stjs' test runner. No additional software needs to be installed on the target system for this browser to run. |
remote |
Doesn't launch any browsers. It assumes that a browser is already running somewhere (potentially on a remote machine), and that this browsers periodically polls the stjs test runners HTTP port to fetch unit tests to run. |
phantomjs |
Launches all the tests in phantomjs. Phantomjs is a lightweight, headless browser based on webkit and V8. Phantomjs needs to be installed on the target system for this browser to run. STJS will look in the common phantomjs installation directories to find the binary. If phantomjs is not installed in the standard location on your target system, you can specify the path of the binary in the phantomjs.bin property in "stjs-test.properties", in whichever file you specified in the stjs.test.config system property or on the command line. |
headlessFirefox |
Only supported on systems that use an X11 server. This browser needs Xvfb and firefox to be installed on the target system. Xvfb (X Virutal Frame Buffer) is an X11 server that doesn't need any real graphics capability to run, and will happily run on servers that do not have a graphics card. This browser will first launch an instance of Xvfb, and then launch a firefox instance instructing it to use Xvfb as its display. Xvfb is expcted to be in your PATH. The path to the firefox binary can be configured in the same way as for the "firefox" browser. |
headlessChrome | Same as headlessFirefox but starts Google Chrome instead. |
Thanks to Daniel you can now use ST-JS also with Gradle. You can find the plugin's sources here.