Server-Side Javascript Developer’s Guide

 

Documentation home

 

Introduction. 1

Language Basics 2

Ebase API 2

API variables added to Javascript 3

Access to fields, tables, pages etc. 3

Examples of accessing Ebase form data. 4

Ebase-provided global functions 4

Exception Handling. 4

Shared Functions 4

Using Javadoc to document code. 5

Accessing Java objects 5

Automatic conversion of Java objects 6

Bean access 7

Adding Java libraries 7

Further Reading. 8

Exception Handling. 8

Debugging. 9

Working with Components 9

Component Context 9

Shared Functions for Components 10

Advanced Considerations 10

Saving objects in form fields 10

Saving objects in session state or application state. 10

Suspensions 11

 

See also: Javascript Scripting Menu, Javascript Editor, Javascript Quick Start Guide, API Javadoc

 

 

This document contains detailed information on the use of Javascript for server-side programming. It includes information on the Ebase API and how to access Java objects from Javascript.

Introduction

Ebase provides full support for server-side programming using Javascript which is implemented with the Mozilla Rhino runtime engine. The subject of standards and version levels within Javascript is a little confused: core Javascript is covered by the ECMAScript standard, however this term is hardly ever used; version levels vary according to implementation. The Mozilla Rhino version implemented by Ebase is 1.7R6 which provides support for nearly all of ECMAScript Edition 5 plus a few features from Mozilla Javascript 1.8.

 

Access to the Ebase form e.g. to fields, tables, controls, pages etc is provided via a comprehensive API. This API is written in Java, but is accessed from a Javascript script via the Rhino implementation of the JSR223 standard, which specifies how Java objects are made available to Java-based scripting languages. This support for JSR223 also makes it possible to access customer written Java classes directly from a Javascript script.

Language Basics

The idea of server-side Javascript is often confusing: it includes all the features of core Javascript (as described by the ECMAScript standard). On the other hand, client-side Javascript (as found in a browser) is familiar to most web developers and consists of all of this core Javascript, but also adds a large number of client-side extensions e.g. Document, Form, Element, Input objects etc.

 

The main objects available with core Javascript are: Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.

Rhino provided additions include E4X objects that provide an XML API.

 

All other aspects of syntax are the same for both server-side and client-side Javascript. See any good Javascript reference for details.

 

It’s useful to remember that when calling a javascript function it is not necessary to always pass the exact number of arguments, and you can usually get away with omitting some of the arguments. The script editor on the other hand will always show all of the arguments. For example the editor code assist shows the substring method of String as:

 

substring(Number start, Number end)

 

but this can be coded as:

 

var test = "abcdefg".substring(4);     // returns "efg"

Note that Rhino supports both the for in and for each in syntaxes, which are particularly useful for iterating through arrays returned from the Ebase API or for iterating through native Javascript arrays and objects e.g.:

 

var controls = pages.PAGE_1.getAllControls();

for (var n in controls)

{

  controls[n].hide();

}

 

can also be programmed, and with less effort, as..

 

var controls = pages.PAGE_1.getAllControls();

for each (var ctrl in controls)

{

  ctrl.hide();

}

 

Ebase API

This API is written in Java and provides access from a script to all elements within an executing Ebase form, integration service or workflow job: this includes fields, tables, pages, controls, external resources etc. In addition, it also provides access to browser client and session data plus many additional programming facilities e.g. transactions, security, files, Velocity templates etc. The API is also accessible from a JSP or an FPL function using the UFSFormInterface class.

 

Click here for the API javadoc.

 

API variables added to Javascript

The API is accessible to Javascript scripts via a number of variables which are added automatically to each script:

 

Variable Name

Description

form

This acts as the core object for the form API and provides methods to access all other form elements. It also provides a number of methods to perform user actions such as: go to page, call form, call URL, generate PDF, upload file etc.

event

Access to the controlling event e.g. Button Control on-click event, before page event

fields

Access to all fields in the form or integration service, or process attributes in a workflow process

tables

Access to all tables in the form, integration service or workflow process

pages

Access to all pages (only applies to forms)

controls

Access to all controls on all pages (only applies to forms)

resources

Access to all resources within the business view of the form, integration service or workflow process

texts

Access to all local texts plus and linked Texts entities

components

Access to all elements for a deployed component – this supports changing the context so that elements are accessed as if from a specific deployed component e.g. to load data from a table defined within a component. See Working with Components.

system

Access to many additional system services: includes system variables, transactions, sequences, security etc

client

Access to information and methods pertaining to the browser client and HTTP session

 

Access to fields, tables, pages etc

All the variables with an “s” on the end of the name (i.e. fields, tables, pages, controls, resources, components, texts) act as parent objects and provide direct access to all named sub-elements via the sub-element name as shown in the following examples:

 

// change value of RequestorType field

var requestorType = fields.RequestorType;

requestorType.value = "xxx";

// load the orders table

var ordersTable = tables.orders;

ordersTable.fetchTable();

// go to the Summary page

form.gotoPage(pages.Summary);

 

This same approach is also used for elements that appear at lower levels e.g. system variables or resources within a deployed component:

 

// get the value of the $FILE_NAME system variable

var fn = system.variables.$FILE_NAME.value;

// insert a row into the Request database resource specified in the COMP component

components.A_COMP1.resources.Request.insert();

 

When using the Javascript editor, the available names within each parent object will be shown automatically by the code-assist feature as soon as a “.” is entered after one of these variables e.g. “fields.”. Note that to display this list of available names, the script editor must be linked to a form.

 

Alternatively, named sub-elements can also be accessed using getter methods on their parent object e.g.

 

var requestorType = fields.getField("RequestorType");

var p1 = pages.getPage("Summary");

var fn = system.variables.getVariable("$FILE_NAME");

 

Using a getter method is the only way to access an element that has a name not supported by Java e.g. name starts with a numeric or contains a period.

 

It is possible to iterate through all child objects within a parent, e.g. iterate through all field objects within fields, like this:

 

for ( var field in Iterator(fields.iterator()) )

{

   log(field.elementName);

}  

 

Examples of accessing Ebase form data

See Quick Start Guide.

 

Ebase-provided global functions

 

log(message)

The message is written to the designer execution log (viewed with View > Execution log) and to the server log with level info (this will be written to the server log when parameter Ufs.logInfo in UFSSetup.properties is set to true).

 

print(message)

Writes the message to System.out.

 

importPackage(javaPackageName)

Imports a Java package (see Accessing Java Objects). This can only be used for packages that begin with com, org or java – see above.

 

importClass(javaClassName)

Imports a Java class (see Accessing Java Objects).

 

Exception Handling

Exceptions thrown by methods in the API can be caught by JavaScript code using try, catch and finally statements. See exception handling.

 

Shared Functions

Shared Function scripts provide a way of accessing shared Javascript functions. They are configured at form (or component, integration service or workflow process) level and are then available to all events within the owning object.

 

To configure Shared Function scripts for a form, component or integration service, click on the properties icon on the main toolbar, then select the Events tab. To configure Shared Function scripts for a workflow process, click on the properties icon on the main toolbar in the workflow process editor, then select the Scripting tab.

 

 

Although Shared Function scripts are intended primarily to contain shared functions, they can in practice contain any executable Javascript code.

Using Javadoc to document code

When functions are documented using Javadoc notation, the documentation will be shown in the editor by the code-assist feature in a panel with a yellow background. A Javadoc block is a comment block that begins with /** and ends with */. Any standard Javadoc tags can be used.

 

For example, function multiplyTwoNumbers has source:

 

/**

 * Multiplies two numbers together and returns the result

 * @param n1 the first number

 * @param n2 the second number

 * @return the result

 */

 function multiplyTwoNumbers( n1, n2 )

 {

       return n1 * n2;

 }

 

Using the code-assist feature, the javadoc is displayed as:

 

Accessing Java objects

As mentioned in the introduction, Mozilla Rhino provides support for accessing Java objects directly from Javascript code. This is a very powerful feature as it makes it possible to use any available Java package or to write specific functionality in Java and invoke this directly from a Javascript script.

 

Any Java class on the server’s classpath can be accessed in this way. The following are all supported:

 

  • Creation of new objects
  • Invoking public methods on objects including static methods
  • Referring to public fields including static fields
  • Catching exceptions

 

For example, we can create a new File object as follows:

 

var path = "C:/temp";

var f1 = new java.io.File(path);

 

This works for all package names beginning with com, org, or java. If however your package name begins with something else, you can address the class by prefixing the class name with “Packages.” Here’s an example showing use of class ebase.org.EbaseTest:

 

var tester = new Packages.ebase.org.EBaseTest();
tester.test();

 

or alternatively (for package names beginning with com, org, or java), we could use the importPackage() function – this is equivalent to the java import statement. Note that Java imports java.lang.* implicitly, while Rhino does not. The reason is that JavaScript has its own top-level objects Boolean, Math, Number, Object, and String that are different from the classes with the same name defined in the java.lang package. Because of this conflict, it's a good idea not to use importPackage on the java.lang package.

 

importPackage(java.io);

var path = "C:/temp";

var f1 = new File(path);

 

We can then invoke methods directly on the object e.g.

 

f1.getName();

if (f1.exists())..

 

The importPackage function can also be used with “Packages.” e.g.

 

importPackage(Packages.ebase.org);

 

When Javascript objects are passed as parameters into Java methods, Rhino tries to find the method that matches the parameters, and this can result in manipulation of the parameter objects. In general, this process works very well and does not often cause problems.

 

Automatic conversion of Java objects

To make it easier to work with Java objects, some Java types are converted automatically into Javascript equivalents. This conversion takes place each time an object is returned from Java to Javascript. Note that Java objects created with new are not converted in this way.

 

  • Java String, Number and Boolean objects are converted to the equivalent Javascript primitive
  • Java arrays are converted to Javascript arrays (see comments below)
  • Java dates (java.util.Date) are converted to Javascript Date objects
  • Java lists (implement java.util.List) can be treated as Javascript arrays and Java maps (implement java.util.Map) can be treated as Javascript objects

 

The conversion of Java arrays, List and Map objects makes it possible to iterate through these objects using the Javascript for syntax. This is true both for objects returned from the Ebase API and from any other Java classes.

 

for each (var ctrl in pages.PAGE_1.getAllControls())

{

   ctrl..  

}

 

Java Map objects (e.g. HashMap) are converted to Javascript Object’s where the map keys correspond to property names and the map values correspond to property values. Therefore Javascript mymap.PROP1 is equivalent to invoking get("PROP1") on the Java map.

 

Bean access

When a Java object contains JavaBean type getter and setter methods i.e. a no argument getXxx() method and a matching single argument setXxx() method, then xxx can be referenced from Javascript as property xxx e.g.

 

Java code:

String getValue();

Void setValue(String value);

 

Can be read in Javascript using either of:

var v1 = obj.getValue();

var v2 = obj.value;

 

And can be set from Javascript using either of:

obj.setValue("new value");

obj.value = "new value";

 

This choice of syntax is available throughout the Ebase API.

 

Adding Java libraries

Additional Java libraries are added separately to the server and the designer.

 

It is mandatory to add Java classes to the server before they can be used. Add the files to either the WEB-INF/lib folder (zip or Java ARchive Files (jar) files) or WEB-INF classes folder (for individual .class files) on the server.

 

Adding the Java classes to the code assistant in the designer is optional. Similar to the Java classpath, the code assistant will read directory, zip or Java ARchive Files (jar) files. The class files are packaged the same as for a Java class loader, so must be archived or copied using the classes fully qualified path e.g com.ebasetech.test.TestClass > com/ebasetech/test/TestClass directory structure. If Javadoc is required for the class and methods, then the source code must be packaged together with the class files. The source code is optional.

 

Additional classes can be added as follows:

 

Note: If the Ebase designer is running. Stop the designer.

 

  1. Move the jar, zip or directory that contains the java classes (and optional source files) to a convenient location on the file system. e.g <Ebase-Installation-Dir>/UfsClient/additional/myClasses.jar
  2. Open designer.vmoptions, located in <Ebase-Installation-Dir>/UfsClient/ directory using a text editor.
  3. Add the following line to the bottom of the file:

 

-Drsta.completion.loc=<additional-java-classes-location>

 

e.g -Drsta.completion.loc=<Ebase-Installation-Dir>/UfsClient/additional/myClasses.jar

 

  1. Save the file.
  2. Start the designer.

 

See Accessing Java objects for information on using the additional classes in the editor.

 

It is possible to add more than one java archive file or zip file or directory. These can be added by using the system path separator character (semicolon in Windows system or a colon on Linux system).

 

e.g -Drsta.completion.loc=<Ebase-Installation-Dir>/UfsClient/additional/myClasses.jar;c:/temp/j2see.jar;c:/temp/mail.jar;c:/temp/classes12.zip

 

 

Further Reading

Click here for more details on accessing Java objects and implementing Java interfaces.

 

Exception Handling

Exceptions thrown by Java methods, including the Ebase API, can be caught and processed by JavaScript code using try, catch and finally statements. Rhino wraps Java exceptions into error objects with the following properties:

·         javaException: the original exception thrown by the Java method

·         rhinoException: the exception wrapped by the Rhino runtime

 

The instanceof operator can be used to query the type of an exception:

 
try
{
  system.securityManager.logon([ ["Script", fields.USER.value], ["Script", fields.PASSWORD.value] ]);
}
catch (e)
{
  if (e.javaException instanceof com.ebasetech.api.exceptions.LogonException)
  {
    event.owner.addErrorMessage(e.javaException.message);
  }
  log (e.javaException);
}
 

The following syntax can also be used:

 

catch (e if e.javaException instanceof com.ebasetech.api.exceptions.LogonException)
{
  event.owner.addErrorMessage(e.javaException.message);
  log (e.javaException);
}
 

Debugging

The Execution log (View > Execution log) contains the following information:

  • The start of each script is logged
  • Each failure is logged, including a Javascript stacktrace that shows the stack of invoked functions. Script line numbers are shown.
  • All output from the log() function
 

An interactive debugger may be added in a future Ebase version. Until this is introduced, the log() function is the most useful debugging tool.

Working with Components

Component Context

When a form contains one or more deployed components, each event that is part of a deployed component operates within its own context. This context provides a component-specific view of the fields, tables, pages, resources and controls which effectively limits the component so that it can only operate on elements defined within the component itself and cannot operate on elements in the wider form.

 

However, this context can be changed at any time. It is possible to:

  • Switch from one component context to another
  • Switch from a component context to the form context
  • Switch from the form context to a component context

 

This component context switching is achieved using the components API variable. To switch to the context of a deployed component, the component prefix must be known: this is the prefix assigned when the component was inserted plus the component name e.g. the COMP1 component is inserted with prefix A_, so the component prefix is A_COMP1. These prefixes can also be determined by looking at the names of component elements such as fields or controls in the deployed form.

 

Note that the form context can be obtained from a component event by using the form variable:

 

Examples:

 

// load a form level table from a component script

form.tables.TABLE1.fetchTable();

// insert a record into a database table using a resource in a deployed component

components.A_MYCOMP.resources.REQUESTS.insert();

 

Shared Functions for Components 

When an event is executed from a deployed component, the shared function scripts used are those specified for the component, not the form where the component is deployed.

 

Advanced Considerations

Saving objects in form fields

Sometimes there is a need to create an object in one script and then use it later in another script and possibly in another event. This is achieved by creating a field with type Object and then assigning the value of this field to the object e.g.

 

// create and save the Javascript object

var obj = {};

obj.A = ..

obj.B = ..

etc.

fields.OBJFIELD1.value = obj;

 

// Reload later from another script

var obj = fields.OBJFIELD1.value;

obj.A..

 

If a Javascript object is changed after it has been loaded from a form field, it must be re-saved back to the form field. This restriction applies only to Javascript objects.

var obj = fields.OBJFIELD1.value;

obj.C = [ 1, 2, 3 ];

fields.OBJFIELD1.value = obj;

 

The reason for this restriction is that Javascript objects are saved in form fields in a serialized state and are de-serialized each time the object is requested from a script. Storing Javascript objects in this way allows them to be independent of the script where they were created.

 

The examples above all show saving/loading Javascript objects, but the same can be also achieved with Java objects so long as all objects implement the java.io.Serializable interface.

 

 

Saving objects in session state or application state

When there is a need to share objects across multiple forms, this can be achieved by saving the object in the session state where they are available to all tabs in a browser e.g.

 

// create and save the Javascript object

var obj = {};

obj.A = ..

obj.B = ..

client.httpSession.setAttribute("SessionAttr1", obj);       // this object is available to all tabs within the browser window

 

// reload the object later

var obj = client.httpSession.getAttribute("SessionAttr1");

obj1.C = ..      

 

Objects can also be saved in form session state – these objects are only available to a single tab within a browser e.g.

 

// create and save the Javascript object

var obj = {};

obj.A = ..

obj.B = ..

client.formSession.setFormSessionAttribute ("FormSessionAttr1", obj);       // this object is available to just one tab within a browser window

 

// reload the object later

var obj = client.formSession.getFormSessionAttribute("FormSessionAttr1");

obj1.C = ..      

 

Objects can also be saved into the application context where they are available to all users:

 

// create and save the Javascript object

var obj = {};

obj.A = ..

obj.B = ..

client.httpSession.servletContext.setAttribute ("GlobalAttr1", obj);       // this object is available globally

 

// reload the object later

var obj = client. httpSession.servletContext.getAttribute("GlobalAttr1");

obj1.C = ..      

 

The examples above all show saving/loading Javascript objects, but the same can be also achieved with Java objects so long as all objects implement the java.io.Serializable interface.

 

Note that saving Javascript objects into session or application contexts in this way can lead to memory leaks in some circumstances i.e. excessive use of memory, and should therefore be used with caution. The reason for this is that Javascript objects all contain references to the scope where they are created, so when objects are modified by several different scripts these scope references can accumulate to a significant amount of memory. This memory leak risk applies only when new Javascript objects are added to an existing object e.g. any of the following:

 

var obj = client.httpSession.getAttribute("SessionAttr1");

obj1.C = [ 1, 2 ];      // a Javascript array

obj1.C = { A: "a" };    // a Javascript object

obj1.C = new Date();    // a Javascript date

obj1.C = new MyClass(); // an instance of a Javscript class

 

It does not apply when numbers or strings are added e.g.

var obj = client.httpSession.getAttribute("SessionAttr1");

obj1.C = 12;

obj1.C = "abc";          

 

Because of this memory leak risk, it is safer to save a Javascript object in a form field rather than one of the contexts described above. Of course, this means that the object is only available to a single form, so this might not be possible.       

 

Suspensions

A few methods within the Ebase API will suspend execution of a script; execution is normally resumed at some later point with the next statement within the script. The methods that suspend execution in this way are all found on the WebForm object:

 

callForm()

callUrl()

uploadFileFromBrowser()

 

When script execution is suspended in this way, all Javascript variables within the currently executing script are saved in the form’s state. When execution resumes, the variables are restored and execution continues. All objects within the form’s state must implement the Serializable Java interface to allow the serialization/deserialization of state by application servers.