Client objects

Overview

You implement client-side business logic and process client component events (that do not need access to the server) using Managed Code Objects (MCOs). MCOs are Java classes or JavaScript objects that the Nexaweb Client loads dynamically from the server (and then runs locally). An MCO can be declared and configured in XML or can be programmatically added to an application at runtime.

To use MCOs, you:

  • Create Java Classes or JavaScript objects that implement some functionality
  • Then either:
    • Add declaratively
    • Add programmatically

The primary use of MCOs is as event handlers which you add declaratively. However, you can use MCOs to encapsulate any kind of client side processing. For example, for validation logic, screen manipulation, or data binding or formatting.

Creating MCO Classes

An MCO is a Java or Javascript object that runs in the client and is exposed in the UI document through a declarative wrapper, the <mco> tag.

You can use the following in MCOs as event handlers:

  • Public methods
  • System service methods

The Nexaweb Platform package, com.nexaweb.client.mco, provides the following classes for creating and managing MCOs:

 

ClassDescription
AbstractMcoThe base class you extend to create MCO classes. It provides the getSession() method for ease of use and effeciency, rather than relying on McoContainer.getClientSessionFromMco().
Container

Houses objects associated with an application and provides some lifecycle management and notification for objects that implement the ContainerLifecycleObject interface.

You can access objects in containers through XML using the CONTAINER_NAME://OBJECT_NAME or CONTAINER_NAME://OBJECT_NAME.METHOD_NAME() notation. See following sections for more detail about method calling and parameter passing in XML.

McoContainerMcoContainer holds MCOs that are declared via XML, as well as MCOs that are added to the application programatically.

MCOs can use McoContainer to get a ClientSession reference.

In addition, the Platform package, com.nexaweb.client, provides classes and interfaces used by MCOs including ClientSession,EventHandler, and UiUpdateQueue.

Ajax

For an MCO class in Ajax, create an object using a no-argument constructor. For example:

//Default no-parameter constructor
JsMCO = function() {
}; 

Then use prototype to add methods to the object. For example:

//Scope: Local, Selected Component: button, Selected Event: onCommand
JsMCO.prototype.onCommand = function(event){
};

For an event object to have access to the event, you must pass the event object into the MCO. For example:

<button onCommand="mco:javaMco.onCommand( event )"/>

Java

For an MCO class in Java, create an object by extending the AbstractMco class. For example:

public class JavaMCO extends AbstractMco {...}

Add methods to the class. For example:

public void onCommand() {...}

This method accesses the event object through the ClientSession ojbect. For example:

ClientSession clientSession = this.getSession();
ClientEvent clientEvent = clientSession.getEventHandler().getClientEvent();

You do not need to pass the event object as an agrument to the MCO. For example:

<button onCommand="mco:javaMco.onCommand()"/>

Alternatively, you can add the onCommand to the class as follows:

public void onCommand ( ClientEvent eventOjb ) {...]

and then pass the event object as an argument to the MCO as follows:

<button onCommand="mco:javaMco.onCommand(event)"/>

ClientSession object

Your (Java) MCOs typically subclass com.nexaweb.client.mco.AbstractMco, a convenience class which contains the getSessionmethod. The getSession method provides the ClientSession object. Through the ClientSession object, your MCO can access any client-side services. MCOs that do not extend AbstractMCO can use the static method McoContainer.getClientSessionFromMco() available on the ClientSession class to obtain the ClientSession object.

ClientEvent object

Use the ClientEvent object, available from the ClientSession object, to obtain information about a fired event. The ClientEvent object provides information such as parameters and attributes.

public void onTextChanged() {
   ClientEvent event = 
          getSession().getEventHandler().getClientEvent();
   Element textField = (Element) event.getSource();
   String text = textField.getAttribute("text");
   // validate the text entered in the TextField
}

MCO Lifecycle Management

Whenever an MCO is declared in XML, it replaces any MCO that has the same ID. The replaced MCO is removed from the MCO container and unload() and/or removedFromContainer() are called on it.

MCOs can be declared in MCOs as a group or as singular MCOs. A singular MCO is considered a group
of size one. When a group is declared, the following operations are performed in order:

  1. Any existing MCO with the same ID is removed from the MCO container.
  2. The no-argument constructor for each MCO is called.
  3. The MCO is added to that ClientSession's McoContainer. From this point forward the MCO can
    get the ClientSession by calling McoContainer.getClientSessionFromMco(this) or by calling
    getSession(), if it extends AbstractMco. 
  4. Each MCO is configured with the initial properties declared in the XML.
  5. The onLoad method, if any, of each MCO is called.

MCO Threading and Execution

Threading in a Nexaweb application is similar to threading in a Java Swing based application. When an MCO event handler is invoked, it is put on an event queue and its listeners are invoked on the Java event thread. Since the event thread invokes all listeners, this means that MCO event handlers will block the update of the UI until the event handler is complete. It is important that all listeners return quickly, since performing time-consuming operations in a listener can prevent subsequent events from processing immediately, thus freezing up the UI. If a listener requires an expensive, long-running operation, perform the operation in a new thread or use asynchronous versions of available Nexaweb methods such as com.nexaweb.client.netservice. NetService object's retrieve asynchronously and retrieve and process asynchronous methods when retrieving resources in a listener.

MCOs invoked directly through XML (using <execute>) can also be invoked in the event thread.
(In Java VMs 1.2+)

Thread Safety Considerations

Whenever an MCO accesses or changes a Document such as the UI Document or a DataSet, you must wrap all modifications in a synchronized block on Document.getDomSynchronizationObject(), because
the DOM that represents the UI is not threadsafe. This includes calls to XPath.

In addition, changes to the UI Document should always occur on the thread the MCO was originally called on. If a background thread needs to modify the UI Document it should do so using the methods in com.nexaweb.client.UiEventQueue, which operates similarly to the SwingUtilities invocation methods like invokeAndWait() and invokeLater().

The following example shows a common pattern to use in a MCO event handler when modifying the UI; this example resets the text in 2 text fields in the UI.

public void onReset() {
  ClientSession clientSession = getSession();
  Document document = 
        clientSession.getDocumentProvider().getUiDocument();
  
  synchronized (document.getDomSynchronizationObject()) {        
    Element userNameTextBox = 
            document.getElementById("userNameTextBox");
    userNameTextBox.setAttribute("text", "");
    Element passwordTextBox = 
            document.getElementById("passwordTextBox");
    passwordTextBox.setAttribute("text", "");
  }
}

As an alternative to the above code, MCO event handlers can use the com.nexaweb.client.UiUpdateQueue object to add a UI update task. The following example illustrates the previous example using the UiUpdateQueue instead of a synchronized block:

public void onReset () { 
    ClientSession clientSession = getSession();

    final Document document = 
               clientSession.getDocumentProvider().getUiDocument();
    UiUpdateQueue uiUpdateQueue = clientSession.getUiUpdateQueue();
    uiUpdateQueue.invokeLater(new Runnable () {
      public void run () {
        Element userNameTextBox = 
                document.getElementById("userNameTextBox"); 
        userNameTextBox.setAttribute("text", "");
        Element passwordTextBox = 
                document.getElementById("passwordTextBox");
        passwordTextBox.setAttribute("text", ""); 
        }
    });
} 

The following example shows a pattern for performing a time-consuming non-UI updating operation followed by updates to the UI. This example represents a handler for an MCO's Load event.

public void onLoad () {
    final ClientSession clientSession = getSession();
    new Thread(new Runnable() {
       public void run () {
           // long running task to get data from a database
          clientSession
         .getUiUpdateQueue()
         .invokeLater(new Runnable () {
             public void run () {
                 // update the UI with the 
                 // data retrieved from the database 
             } 
        }); 
      }
    }).start();
}

 

Compiling and Deploying MCOs

Compile MCOs with the -1.1 target. If you intend to use MCOs in the Microsoft or Netscape 4 JVM, compile them with a 1.3 compiler, rather than 1.4 or 1.5, to ensure maximum compatibility and as Java 1.1 compliant.

Compiled MCOs can be deployed as individual classes or as JAR collections. See the configuration documentation on nexaweb-client.xml for details.

Programmatically Adding MCOs

MCOs can be added to a ClientSession programatically by constructing an Object and calling McoContainer.addMco(). From this point forward the MCO can get the ClientSession by calling McoContainer.getClientSessionFromMco(this) or if it extends AbstractMco, by calling getSession().

When an application exits, all the MCOs in the MCO container are removed from the MCO container and will have unload() and/or removedFromContainer() called on them.

Declaratively Adding MCOs

To add an MCO declaratively, you:

  • Declare the MCO in the UI file where it will be used
  • Then:
    • To use as an event handler, specify in the UI file a call to the MCO from an event
    • To implement some non-event related functionality, specify in the UI file a statement to execute the MCO

To use an MCO as event handler, for example:
<button onCommand="mco://myMco.doSomething()"/>

To call and MCO directly from XML, for example:
<mco:execute>myMco.doSomething()</execute>"

Using Parameters

You can use any of the parameters described here in declarative MCO calls.

The following example shows an MCO using parameters:

<button text="Press Me" onCommand="mco://myMco.doSomething
("hello", true, 5, 5.5, this.text,this)"/>
public void doSomething(String s, boolean b, int i, double d, String attribute, Element e){...}

Setting Properties

You can declare an MCO in XML and configure initial properties on it inside of that declaration, including references to other MCOs. In addition, once declared, you can use MCO methods as event-handlers or call them when parsed in XML.

To set MCO properties in XAL, the MCO class must define a setter method for the property.

For example:

<mco:declarations xmlns:mco="http://openxal.org/core/mco"> 
  <mco id="MyMco" src="com.mycompany.SimpleJavaClass"> 
      <property name="defaultValue" value="15" />
  </mco>
</mco:declarations>	

For this MCO to work, the MCO must declare a corresponding method set<propertyName>, as follows:

public void setDefaultValue(int aValue) {
	 // set the default value
}

The following example of a minimal MCO declaration declares an MCO called mco1 instantiated from the com.nexaweb.client.mco.TestMco1 class:  

<mco:mco xmlns:mco="http://openxal.org/core/mco" 
         id="mco1" src="com.nexaweb.client.mco.TestMco1"/>

The following example shows an MCO declaration using src and class attributes:

 

<mco:mco xmlns:mco="http://openxal.org/core/mco" id="mco1" src="src-js/com/nexaweb/TestMco.js" class="TestMco"/>

Registering Listeners in MCOs

MCOs can register listeners for any available server-side events such as DOM changes, document registration, or published messages. When the event fires, the registered listener gets called.

The following example registers a com.nexaweb.messaging.MessageListener object to be notified of any messages published on the "News" topic:

import com.nexaweb.messaging.MessageListener;
  public class MyMessageListener extends MessageListener {
    public void onMessage(String topic, Serializable msg) {
        String message = (String) msg;
        System.out.println("Got message: " 
         + message + ". On topic: " + topic);
    }

    // in an MCO's load event handler
    public void onLoad() {
       NetService netService = 
           McoContainer.getClientSessionFromMco(this).getNetService(); 
       netService.subscribe("News", new MyMessageListener());
  }
}

Putting It All Together

This section provides examples of the complete Java or JavaScript objects and XAL syntax required to use MCOs in a UI document.

The following XAL declares an MCO:

<mco:declarations xmlns:mco="http://openxal.org/core/mco">
    <mco id="MyMco" src="com.mycompany.SimpleJavaClass"/>
</mco:declarations>

The following example shows a button using this MCO to handle its onCommand event:

<button text="Submit" width="100" height="35" onCommand="mco:MyMco.onSubmit()" />

The following example shows the Java code that defines this MCO: 

package com.mycompany;

public class SimpleJavaClass {
    public void onSubmit() {
      // event handling logic
    }
}

-----

Example Executing MCO

-----

Java Example: Simple Element Access and Attribute Change (Java )

The following example gets the clientSession and the label element. It then calls the ClientSession's getEventHandler().getClientEvent() to get the ClientEvent object. This object provides the event's parameters. Available parameters vary by event type.

In the following section, the UI document declares the MCO and specifies the handler:

<mco:declarations xmlns:mco="http://openxal.org/core/mco">
  <mco:mco id="eventMco" src="pkg.EventHandlers"/>
</mco:declarations>
<xal>
  <rootPane>
    <flowPane/>
      <button onCommand="mco://eventMco.changeLabelText()" 
              text="Change mylabel text" />
      <label id="mylabel" text="This is some text" />
  </rootPane>
</xal>

The handler is method changeLabelText in user-created EventHandlers.java:

private static final XPath LABEL_XPATH =
      XPathFactory.createXPath("id('mylabel')");
	
public void changeLabelText(){
    ClientSession s = getSession();
    Element label = LABEL_XPATH.evaluateAsElement(
    s.getDocumentRegistry().getUiDocument());

    Object domLock =
      label.getOwnerDocument().getDomSynchronizationObject();

    ClientEvent event = s.getEventHandler().getClientEvent();
    synchronized(domLock){
      label.setAttribute("text", "some new text from "
      + event.getParameter("id") + ":" 
      + event.getParameter("event"));
    }
}

Ajax Example: Simple Element Access and Attribute Change

This sample shows the UI code and supporting JavaScript used to change the text attribute of a <button>. In this case, the MCO declaration ( line 2 ) is to a JavaScript file named Login.js.

<xal xmlns="http://openxal.org/ui">
  <mco xmlns="http://openxal.org/core/mco" id="loginMCO" 
       class="Login" src="Login.js"/>
  <verticalBoxPane align="center" padding="3px" 
    width="300px" x="100px">
    <horizontalBoxPane xmlns="http://openxal.org/ui/html" 
      height="40px" width="250px">
      <label width="190px" id="loginAlert" 
      text="This is a mockup login screen (password is 'password')"/>
    </horizontalBoxPane>
    <horizontalBoxPane xmlns="http://openxal.org/ui/html" 
      height="50px" width="250px">
      <label text="Login:" width="100px"/>
      <textField text="" width="110px" id="login"/>
    </horizontalBoxPane>
    <horizontalBoxPane xmlns="http://openxal.org/ui/html" 
       height="50px" width="250px">
      <label text="Password:" width="100px"/>
      <passwordField text="" width="110px" id="pwd"/>
    </horizontalBoxPane>
    <horizontalBoxPane xmlns="http://openxal.org/ui/html" 
      height="50px" width="250px">
      <label text="(Client-side Validation)" width="140px"/>
      <button text="Submit" width="75px"
      onCommand="mco:loginMCO.login(login.text,
                 pwd.text,loginAlert,'client')"/>
    </horizontalBoxPane>
    <horizontalBoxPane xmlns="http://openxal.org/ui/html" 
      height="50px" width="250px">
      <label text="(Server-side Validation)" width="140px"/>
      <button text="Submit" width="75px"
      onCommand="mco:loginMCO.login(login.text,
                 pwd.text,loginAlert,'server')"/>
    </horizontalBoxPane>
    <horizontalBoxPane xmlns="http://openxal.org/ui/html" 
      height="50px" width="250px">
      <button text="Change Button Text" width="75px" id="btn_Change" 
        onCommand="mco:loginMCO.chgButtonText(btn_Change)"/>
    </horizontalBoxPane>
  </verticalBoxPane>
</xal>

Login.js 

Login = function () {
};

var empty='';
/* Client side input validation*/
Login.prototype.login = function(login, pwd, loginAlert,type) {
  var alert_string='';
    if(Login.prototype.isEmpty(login.toString())){
      alert_string+='Login';
      if(Login.prototype.isEmpty(pwd.toString())){ 
      alert_string+=' and Password';
      }
      alert_string='Please input your '+alert_string;
     }else if(Login.prototype.isEmpty(pwd.toString())){
      alert_string ='Please input your Password';
    }

    if(alert_string!=empty){
      loginAlert.setAttribute('text',alert_string);
      loginAlert.setAttribute('color','red');
    }else{
      var s_pwd=pwd.toString();
      var s_login=login.toString();
	
      if(type=='client'){
        Login.prototype.clientValidate(s_login,s_pwd,loginAlert);
      }else{
        window.location.replace('login.jsp?login='
              +s_login+'&pwd='+s_pwd);
      }
    }
};
/* Check if the value is empty*/
Login.prototype.isEmpty = function(value){
    return (!value value ==empty);
};
/* Client side password validation*/
Login.prototype.clientValidate = function(login, pwd, loginAlert){
  if(pwd!='password'){
    loginAlert.setAttribute('text',
         'Your password is invalid, please try again');
    loginAlert.setAttribute('color','red'); 
  }else{
  window.location.replace('welcome.jsp?type=client&login='+login);
  }
};
/* Client side text change*/
Login.prototype.chgButtonText = function(btn_Change){
  btn_Change.setAttribute('text','This is the new text');
};