Twitter Logo Follow us on Twitter
Project Information About this project

Scripting

Contents

Overview

RAP applications are running almost entirely on a Server. All application-relevant events that occur on the client have to be forwarded to the Server before being processed. Scenarios where minor delays in the event handling are unacceptable (e.g. fast typing or mouse movements) would therefore be undesirable.

This is where RWT Scripting can help. With scripting developers can handle some of the events directly on the client, without creating any HTTP-requests. This is ideal to customize or enhance the behavior of specific widgets, most notably Text.

The scripts themselves have to be written in JavaScript on an SWT-like API. This allows application developers with SWT-experience to get started right away, and makes porting between SWT and RWT Scripting fairly easy. Even without much JavaScript-experience, this article should provide you with all the basics you need for RWT Scripting.

Java and JavaScript API

Client event processing works like untyped event handling in SWT/RWT, with the main difference that the handler itself has to be written in JavaScript. It also can not not provide out-of-the-box access to all of the resources and functionality that would be available on the server.

Creating a ClientListener

To attach a client side listener to a widget, instances of ClientListener are used. Example:

widget.addListener( SWT.Verify, new ClientListener( scriptCode ) );
  

The JavaScript source code can define any number of named function, either with
"var myFunction = function(){};" or
"function myFunction(){}". A function named "handleEvent" is obligatory and will be called in case an event is fired. It takes one argument, which is the event object.

Example:

var handleEvent = function( event ){
  event.widget.setText( "Hello World!" );
};

Other functions within the script can be called from handleEvent to be used as helper. The order in which the functions are defined is not relevant. Strict mode is supported and can be activated for the entire script by writing "use strict"; in the first line.

If your script is longer than a few lines, we recommend to read it from an external file. You may want to create a helper class to do so. It could look like this:

public class ResourceLoaderUtil {

  private static final ClassLoader CLASSLOADER = ResourceLoaderUtil.class.getClassLoader();

  public static String readTextContent( String resource ) {
    try {
      return readTextContentChecked( resource );
    } catch( IOException e ) {
      throw new IllegalArgumentException( "Failed to read: " + resource );
    }
  }

  private static String readTextContentChecked( String resource ) throws IOException {
    InputStream stream = CLASSLOADER.getResourceAsStream( resource );
    if( stream == null ) {
      throw new IllegalArgumentException( "Not found: " + resource );
    }
    try {
      BufferedReader reader = new BufferedReader( new InputStreamReader( stream, "UTF-8" ) );
      return readLines( reader );
    } finally {
      stream.close();
    }
  }

  private static String readLines( BufferedReader reader ) throws IOException {
    StringBuilder builder = new StringBuilder();
    String line = reader.readLine();
    while( line != null ) {
      builder.append( line );
      builder.append( '\n' );
      line = reader.readLine();
    }
    return builder.toString();
  }

}

Note that a ClientListener instance is permanently bound to the UISession. It is therefore undesirable to create multiple ClientListener with the same script. Instead it should be created once and shared within the session, for example by using the SessionStore or creating a SessionSingleton.

Bad:

public static void addCustomBehavior( Control control ) {
  String scriptCode
    = ResourceLoaderUtil.readTextContent( "MyScript.js" );
  ClientListener listener = new ClientListener( scriptCode );
  control.addListener( SWT.MouseDown, listener );
}

Good:

public static void addCustomBehavior( Control control ) {
  ClientListener listener = MyClientListener.getInstance();
  control.addListener( SWT.MouseDown, listener );
}
public class MyClientListener extends ClientListener {

  public static MyClientListener getInstance() {
    return SingletonUtil.getSessionInstance( MyClientListener.class );
  }

  private MyClientListener() {
    super( getText() );
  }

  private static String getText() {
    return ResourceLoaderUtil.readTextContent( "MyScript.js" );
  }

}

Client Widget Objects

The widget objects in RWT Scripting are abstract representations of SWT widget instances. They have a JavaScript-conform subset of the API of the actual SWT widgets they represent. Client widget objects can be obtained from the event objects given in handleEvent, or from rap.getObject. (See also Cross-Widget Scripting below.) The JavaScript API for all scriptable widgets is documented the WebClient API Reference. Not all widgets support scripting. Adding a ClientListener to a widget that does not support Scripting has no effect.

Supported Event Types

The following Event types are supported:

Event Type Notes
SWT.KeyDown Fired once when pressing a key, then repeatedly while holding it down. The doit flag can be used to prevent the character from beeing inserted.
SWT.KeyUp Fired when releasing a key.
SWT.MouseDown Fired when pressing a mouse button.
SWT.MouseUp Fired when releasing a mouse button.
SWT.MouseMove Fired when moving the mouse within the widget. This type is not supported by server-side Listener, only by ClientListener.
SWT.MouseWheel Fired when moving the mouse wheel. This type is not supported by server-side Listener, only by ClientListener.
SWT.MouseEnter Fired when moving the mouse over the widget. This type is not supported by server-side Listener, only by ClientListener.
SWT.MouseExit Fired when moving the mouse out of the widget. This type is not supported by server-side Listener, only by ClientListener.
SWT.MouseDoubleClick Fired when clicking twice.
SWT.FocusIn Fired when widget is focused.
SWT.FocusOut Fired when widget is blured.
SWT.Paint Fired when widget appears, is changing size, or when "redraw" is called on the widget either in java, or in RWT Scripting. Only supported on Canvas.
SWT.Selection Fired on Button and Scale widgets when selection changes.
SWT.Modify Fired then the value of the "text" property of a Text, Combo or Spinnerwidget changes.
SWT.Verify Fired then the value of the "text" property of a Text or Combowidget is changed by the user. Not supported on other widgets. The doit flag can be used to prevent the change. The "text" field of the event may be changed to replace the inserted text.

Transferring Widget Data

As in SWT the client widget objects provides setData and getData methods. These allow to attach data to a widget instance without affecting the widget itself. Unlike SWT any value can be stored with setData, not just objects.

Data attached to the Java widget instance can be transferred to the client widget object. To do so, the key for that data has to be registered with the method WidgetUtil.registerDataKeys, like this:

WidgetUtil.registerDataKeys( "foo" );
widget.setData( "foo", "myData" );

The key has only to be added once per session, but adding it multiple times has no side effects. The following types are supported:

  • null
  • String
  • Byte
  • Short
  • Integer
  • Long
  • Double
  • Float
  • Boolean
  • int[]
  • boolean[]
  • String[]
  • Object[]
  • Map
  • JsonValue

Changing the value on the client does not change it on the server.

Cross-Widget Scripting

Each ClientListener script has it's own scope and no direct access any widget other than the one given by the event object. Widget references can also not be transferred to the client using the setData method, but a widgets protocol id can:

Java:

widget.setData( "otherWidget", WidgetUtil.getId( otherWidget ) );

The id can then be used to obtain the matching widget object on the client.

JavaScript:

var otherWidget = rap.getObject( widget.getData( "otherWidget" ) );

HTML Attributes

JavaScript widget objects obtained via rap.getObject( id ) have a field $el that allows manipulating their HTML element. Currently it can only be used to set HTML attributes of the element containing the entire widget HTML, and (in case of Text) the <input> element.

This feature can be useful to assign test-id's for easier UI-testing. It can also be used to add ARIA attributes that can be evaluated by screen reader software, thereby providing an alternative to the unsupported SWT accessibility API. (Please take a look at the RAP FAQ for more information on UI-testing and accessibility in RAP.)

To access the $el field from Java code use the JavaScriptExecutor service. If, for example, you want a method to set test-id's on any given widget, your code may look like this:

static void setTestId( Widget widget, String value ) {
  if( uiTestsEnabled && !widget.isDisposed() ) {
    String $el = widget instanceof Text ? "$input" : "$el";
    String id = WidgetUtil.getId( widget );
    exec( "rap.getObject( '", id, "' ).", $el, ".attr( 'test-id', '", value + "' );" );
  }
}

private static void exec( String... strings ) {
  StringBuilder builder = new StringBuilder();
  builder.append( "try{" );
  for( String str : strings ) {
    builder.append( str );
  }
  builder.append( "}catch(e){}" );
  JavaScriptExecutor executor = RWT.getClient().getService( JavaScriptExecutor.class );
  executor.execute( builder.toString() );
}

Notable Limitations and discouraged Usage

  • It is not (and will likely never be) possible to prevent Text selection/carret changes in mouse events (using doit flag). Its also not supported to set the Text selection property in a MouseDown event. These are also not working (correctly) in SWT. Changing selection on Text in a MouseUp event works.
  • Verify event:
    • Currently only supported on Text.
    • Setting the text or selection property of the target within the Verify event is not supported, but it doesn't produce useful results in SWT either. The modiy event is better suited for this.
    • Changing the events "text" field has no effect if its empty to begin with. (I.e. deleting can not be used to insert text).
  • In SWT, some key constants can be compared with both the keyCode field and the character field (SWT.DEL, SWT.ESC, SWT.BS, SWT.BS, SWT.CR, SWT.TAB). In RWT Scripting they can only be compared with the keyCode field. The character field will either not be set for these keys, or contain a (JavaScript) string, while these constants are numbers.
  • Accessing the window/document Objects is discouraged for cross-browser/cross-client compaibility concerns. You can do so at your own risk.
  • Creating global variables from within a ClientListener is heavily discouraged and can easily happen by accident if a variable is created without the "var" keyword. Use strict mode and jshint to prevent this.
  • For security reasons you should be aware that, unlike RAP source code written in Java, all RWT Scripting functions (JavaScript source code) are currently transferred to the client completely unaltered, including comments, and can be read by any user with enough technical expertice.

JavaScript Hints for Java Developer

Developers experienced with Java programming and less familiar with (or completely new to) JavaScript might find the following hints useful in regard to RWT Scripting:

Debugging

Most modern browser have built-in developer tools, including a debugger. However, since the script of a ClientListener is created using an eval statement, the scripts do not simply appear in the "Scripts" or "Resources" tab of the developer tools, and so it is not possible to set a break point. There are two ways to solve this, though browser support may vary:

1. To make the Script appear in the developer tools, add the line
"//@ sourceURL=NameOfYourScript.js" to your script. Newer browser may also recognize
"//# sourceURL=NameOfYourScript.js".

2. To set a break point programmatically, write debugger; in your script where you wish to start debugging.

Noteable differences between Java and JavaScript

  • JavaScript variables are dynamically typed and have function scope
    All local variables in JavaScript are delcared with "var" and can contain any type (undefined, null, number, boolean, string, object). It is not relevant at all where in the function it is declared, its scope is always the entire function.
  • Strings are not objects

Strings are primitives in JavaScript, and are compared with "==" (or "==="), not ".equals". However, string primitives can be coerced into a string object, on which several useful methods are available.

  • A number is not always a number

When calculating a numeric value in JavaScript, the result might not always be a number, even if it is of the type number. The two cases are infinity (e.g. "10/0 == infinity") and NaN (not a number, e.g. "Math.sqrt( -1 )"). NaN can be detected only by using isNaN (e.g. " isNaN( Math.sqrt( -1 ) ) == true"). If a number is neither NaN nor infinity, it can do most things Javas int or double can, including bitwise operations.

  • Arrays have dynamic length

Even though their syntax is very similar, JavaScript Arrays behave more like Java Lists than Java Arrays. They can store different types in different slots, and can change their length as needed. They also have differently named, but similarly working methods.

Noteable similarities between Java and JavaScript

  • Objects and Maps

JavaScript Objects (created with a literal "{}") can be used like Java Maps. A "map.put( "key", value )" would be "map[ key ] = value", and a " map.get( "key" )" would be "map[ "key" ] ". In JavaScript, value could be of any type.

  • System.out.println and console.log

All modern desktop browser have some form of javascript console. They all have at least one function in common that can be used like System.out.println, which is console.log. Some browser also have console.trace. Browser not supporting console.log (or in case of InternetExplorer, not having it activated), will crash when calling that method, so remember removing all occurrences of console from your JavaScript code after debugging.

  • Math and Math

The Java Math class and the JavaScript Math object have almost identical API.

  • Date and Date

The JavaScript constructor Date creates objects almost identical in API to instances of Javas Date class.

  • Regular Expressions and RegExp

JavaScript also supports regular expressions.

  • char and string

JavaScript has no char type. For RWT Scripting, a string with a length of one character is used instead. This allows for comparison like "event.character == "A"", but not "event.character >= 65". To do that use charCodeAt. Example: "event.character.charCodeAt( 0 ) >= 65".