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.
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.
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" ); } }
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.
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 Spinner widget changes.
|
SWT.Verify
|
Fired then the value of the "text" property of a Text
or Combo widget 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.
|
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:
|
|
Changing the value on the client does not change it on the server.
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" ) );
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() ); }
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:
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.
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.
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.
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.
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.
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.
The Java Math
class and the JavaScript Math
object have almost
identical API.
The JavaScript constructor
Date
creates objects almost identical in API to instances of Javas Date
class.
JavaScript also supports regular expressions.
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
".