Tango API in JavaScript

This tutorial teaches, step by step, the use and capabilities of Tango interface for JavaScript. Each of the following chapters presents one or a few aspects of programming for Tango in JavaScript. The consecutive chapters show gradually more and more complex problems and their solutions. Usually, to understand the ideas presented in any given chapter the knowledge of previous chapters is required.

Since the JavaScript API closely reflects the Java API, those who are already familiar with Java interface will find obvious the chapters 0 through 4. They might find it useful to take a look at the Quick introduction to Tango API in JavaScript for the users of Tango API in Java appended at the end of this text, but shall discover new possibilities in the Chapter 5 and 6.

The material in the tutorial is illustrated with a simple chat application. All code listings highlight taught concepts according to the following conventions:

Chapter 0: Becoming a Tango application

Tango runtime, as a generic platform, will know nothing about our application until we advertise it to the Tango control application. This is a configuration issue that will not be covered in this tutorial. Interested are referred to the Tango Session Manager configuration manual.

What we have to realize here though is that, in case of JavaScript applications, the control application shall know the location of a web page with our application. This location will be used to start the application by opening a new browser window and loading its content from the given URL. Our task is, then, to create a Tango enabled HTML page.

In the following chapters we assume Tango control application working and configured to know the location of our Web page. The scope of this teaching is confined to creation of HTML content for this very page.

Chapter 1: Connecting to the Tango system

Before any collaboration can take place one must connect to the Tango runtime. This step, which will be referred to as registering, has to be performed even though the Tango itself starts our application. It not only notifies the Tango system that we are joining, but also provides us with a system proxy - the handle to the Tango agent. Here is the implementation:

ex11.html
<html>
  <body>
    <script language="javascript">
      var Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(window);
    </script>
  </body>
</html>

In the script above new variable Tango_agent is created. Tango_agent is initialized to the value returned by the static method createTAgentJS() of TAgentScript class from webwisdom.tango package. The package is available to the script if the Tango client has been installed. If, for any reason, registration fails, createTAgentJS() will return null. From now on we will use Tango_agent to access the Tango runtime.

It is a good practice to let the system release the resources. We shall use exitJS() function of Tango_agent when the application is unloaded:

ex12.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
        if(Tango_agent!=null)
          Tango_agent.setDbgModeJS(true);
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      Tango_register(window);
    </script>
  </body>
</html>
Note also that we added setDbgModeJS() call, which will help us trace how Tango_agent interprets arguments we pass. With this option engaged, all calls to Tango_agent will be echoed on the browser's Java console.

Chapter 2: Exchanging data within a session

Registered application's fundamental capability is its ability to communicate with other applications registered in the same session. The information exchange is achieved by the mean of sending and receiving messages. In case of JavaScript applications, a message takes form of a string. Sending is as simple as calling sendJS() method of Tango_agent. In order to receive, the application has to register a script page as a listener of messages. (Note the difference between registering application and registering a listener.) This is done by calling addTDataListenerJS() method of Tango_agent. Once a script is registered as a listener its method Tango_receive() will be called upon receiving messages.

We create a simple chat to illustrate sending and receiving mechanism:

chat.html
<html>
  <body>
    <form name="chat">
      <input type="text" name="tty" size=16>
      <input type="button" value="send">
    </form>    
  </body>
</html>
Our chat is a form named "chat" made of two elements:

Now we add Tango code to it:

ex21.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      function Tango_send(m)
      {
        if(Tango_agent!=null)
          Tango_agent.sendJS(m);
      }

      function Tango_receive(m)
      {
        document.forms.chat.tty.value=m;
      }

      Tango_register(window);
      if(Tango_agent!=null)
        Tango_agent.addTDataListenerJS(window);
    </script>
    <form name="chat">
      <input type="text" name="tty" size=16 onclick="Tango_send(tty.value)">
      <input type="button" value="send">
    </form>    
  </body>
</html>
In the script above two functions are defined: The actual sending is triggered when the "send" button is pressed. The onclick event of the button is handled by Tango_send() function, invoked with the value of the text field "tty". The code registering a listener is placed after Tango_receive() function definition. This is because browsers interpret html page sequentially from top to bottom; therefore it may happen that certain code contained on the page is executed while objects from the rest of the page are still undefined. Because registering a listener may result in immediate attempt to call Tango_receive() function we have to make sure this function is defined beforehand.

Chapter 3: Acquiring system level information

This chapter shows how the system level information can be acquired in synchronous or asynchronous manner.

At any point in time after registration, certain system information can be obtained from Tango_agent:

Example below creates a form with one button, which, when clicked, calls appropriate methods of Tango_agent in order to get Tango system info and print it out to the Java console of the browser:
ex31.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      function print(s)
      {
        Packages.java.lang.System.out.println(s);
      }

      function Tango_debug()
      {
        if(Tango_agent==null)
          return;
        print("agent="+Tango_agent);
        print("  userName="+Tango_agent.getUserNameJS());
        print("  isMaster="+Tango_agent.isMasterJS());
        print("  masterName="+Tango_agent.getMasterNameJS());
        print("  participantNames="+Tango_agent.getParticipantsNamesJS());
        print("  isAudioAvailable="+Tango_agent.isAudioAvailableJS());
      }

      Tango_register(window);
    </script>
    <form>
      <input type="button" value="debug" onclick="Tango_debug()">
    </form>    
  </body>
</html>
Function print() is only a utility that prints its argument to the Java console. Function Tango_debug() calls Tango_agent's methods and prints results using print() function. Button "debug" calls Tango_debug() when clicked.

Some of the information provided by the Tango system is, by its very nature, constant, e.g. user name, which is determined on login, remains unchanged during the session's life span. Another type of system information may change during the session, e.g. who is the master of the session. In such a case it is useful to be have a mechanism for being informed about changes when they occur. In Tango, an application may register to receive asynchronous updates about system state changes. The way to do this is similar to registering data listeners (Tango_agent.addTDataListenerJS(window) in the Chapter 2):

ex32.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      function print(s)
      {
        Packages.java.lang.System.out.println(s);
      }

      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      Tango_register(window);

      function Tango_masterChanged(isM,mNa)
      {
        print("Tango_masterChanged("+isM+","+mNa+")");
      }

      function Tango_participantJoined(pNa)
      {
        print("Tango_participantJoined("+pNa+")");
      }

      function Tango_participantLeft(pNa)
      {
        print("Tango_participantLeft("+pNa+")");
      }

      if(Tango_agent!=null)
      {
        Tango_agent.addTMasterListenerJS(window);
        Tango_agent.addTParticipantsListenerJS(window);
      }
    </script>
  </body>
</html>

Function addTMaster/ParticipantsListenerJS() registers the script identified by the window passed as the function's argument. Note that this is different registration than the initial registration in the system (Tango_register()) or the registration of this session private data listeners (addTDataListenerJS()). This one notifies Tango_agent that the given script should receive notifications about system events. Notifications will be made by calling script's functions: Tango_masterChanged(), Tango_participantJoined(), Tango_participantLeft(), and Tango_audioRequested(). All these functions must be defined in the script, and their signatures (number of arguments) must be as in the example above. The implementation, however, will vary with the applications needs; some functions' bodies may be left empty, while another may make a full use of all the arguments. Our code just prints function names and their arguments.

Advanced applications will most probably require asynchronous notifications and still use synchronous calls for the sake of convenience.

Chapter 4: Broadcasting versus sending messages selectively

In addition to broadcasting mechanism, which was described in Chapter 1, Tango allows for sending data to selected members of the session.

Given the ability to obtain the names of session participants as described in the Chapter 3, we can use these names for explicit addressing of messages. Function selectiveSendJS() takes two arguments. First is an array of user names to which the message, given as the second argument, is addressed. In the example that follows, selectiveSendJS() is called when the "send" button is pressed. Note also the use of system notifications Tango_participantJoined/Left() and getParticipantsNamesJS() call for updating html forms displaying current participants of the session:

ex41.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      function print(s)
      {
        Packages.java.lang.System.out.println(s);
      }

      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
        if(Tango_agent!=null)
        {
          Tango_agent.setDbgModeJS(true);
          Tango_agent.addTDataListenerJS(win);
          Tango_agent.addTParticipantsListenerJS(win);
        }
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      function Tango_selectiveSend(recp,data)
      {
        if(Tango_agent!=null)
          Tango_agent.selectiveSendJS(recp,data);
      }

      function Tango_receive(data)
      {
        document.forms.chat.tty.value=data;
      }

      function Tango_participantJoined(pNa)
      {
        optionsAddOption(document.forms.chat.userNames.options,pNa);
      }

      function Tango_participantLeft(pNa)
      {
        optionsRemoveOption(document.forms.chat.userNames.options,pNa);
      }

      function optionsToArray(opts)
      {
        var a=new Array();
        for(var i=0,ai=0;i<opts.length;i++)
        {
          var o=opts[i];
          if(o.selected)
            a[ai++]=o.value;
        }
        return a;
      }

      function optionsAddOption(opts,nam)
      {
        var o=new Option(nam,nam);
        opts[opts.length]=o;
      }

      function optionsRemoveOption(opts,nam)
      {
        for(var i=0;i<opts.length;i++)
        {
          if(opts[i].value==nam)
          {
            opts[i]=null;
            return;
          }
        }
      }
    </script>
    <form name="chat">
      <input type="text" name="tty" size=16><br>
      <select name="userNames" size=4 multiple></select>
      <input type="button" value="send" onclick="Tango_selectiveSend(optionsToArray(userNames.options),tty.value)">
    </form>    
    <script language="javascript">
      Tango_register(window);

      if(Tango_agent!=null)
      {
        var part=Tango_agent.getParticipantsNamesJS();
        for(var i=0;i<part.length;i++)
          optionsAddOption(document.forms.chat.userNames.options,part[i]);
      }
    </script>
  </body>
</html>

This time we split the Java script code into two parts. The second part appears only after forms are created and takes care of their initialization. Initial content of the form "userNames" is obtained from Tango_agent.getParticipantsNamesJS() call. The form is updated in Tango_participantJoined/Left() functions whenever change is reported by the Tango system (see Chapter 3 on asynchronous system notifications).

Messages are sent by Tango_agent.selectiveSendJS(), which takes array of recipients as the first parameter and the message itself as the second one. Although different method must be used for selective and nonselective sends, reception in both cases is identical: Tango_receive() is called if data listener is registered by Tango_agent.addTDataListenerJS().

Other functions used in the example are JavaScript utilities:

Chapter 5: Communicating applets with Tango through JavaScript

Applets can be directly connected to Tango using Java API. However, if multiple applets want to participate in the same session they all have to use a single Tango proxy: instance of TAgent class in Java or Tango_agent in JavaScript. Because Tango_agent is in fact an instance of TAgent it can also be used inside Java applications as is TAgent in Java API fo Tango. To make it happen the reference on Tango_agent must be passed to the applet. Moreover, message streams of different applications must be channeled so they can be delivered to their appropriate counterparts.

We create a simple chat - Java applet - that uses Tango capabilities via JavaScript. For those knowing Java API: this applet looks as written for Tango Java API, except that TAgent is imported from JavaScript rather than created internally. Here is the code in the file Chat.java:

Chat.java
import java.awt.*;
import java.applet.Applet;
import netscape.javascript.JSObject;
import webwisdom.tango.*;

public class Chat extends Applet implements TDataListener
{
    TextField text;

    private TAgent agent=null;
    private int channelId=0;

    public void init()
    {
      super.init();
      setLayout(new BorderLayout());
      text=new TextField("Uninitialized");
      add("North",text);

      JSObject jsWin=JSObject.getWindow(this);
      Object[] args=new Object[1];
      args[0]=this;
      jsWin.call("appletReadyJ",args);
    }

    public void initTangoJS(TAgent a,Number ch)
    {
      agent=a;
      channelId=((Number)ch).intValue();
      if(agent!=null)
	agent.addTDataListener(channelId,this);
    }

    public boolean handleEvent(Event evt)
    {
      if(evt.id==Event.ACTION_EVENT)
	if(evt.target==text)
	  if(agent!=null)
	    agent.send(channelId,stringToBytes(text.getText()));
      return super.handleEvent(evt);
    }

    public void receive(byte[] b)
    {
      text.setText(bytesToString(b));
    }

    private String bytesToString(byte t[])
    {
      return new String(t,0);
    }

    private byte[] stringToBytes(String s)
    {
      int l=s.length();
      byte[] t=new byte[l];
      s.getBytes(0,l,t,0);
      return t;
    }
}

Important in this Applet is that it uses send() and addTDataListener() functions with the channel number as the first of their arguments. Channel number, together with TAgent, is received from JavaScript application in initTangoJS() call. This is JavaScript applications who decides which application should use which channel so the data of possibly multiple applications using the same TAgent does not interfere. Neither initTangoJS() function nor mechanism of passing information from JavaScript application to Java applet is a part of Tango API - this is an interface between a JavaScript application and its applet sub-components. Other details of Java programming for Tango are covered by Tango API in Java manual.

Let's now take a look how this chat application makes part of the JavaScript application:

ex51.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
        if(Tango_agent!=null)
          Tango_agent.setDbgModeJS(true);
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      Tango_register(window);

      function appletReadyJ(a)
      {
        a.initTangoJS(Tango_agent,57);
      }
    </script>
    <applet name="app" codebase="." code="Chat.class" width=200 height=33 mayscript>
    </applet>
  </body>
</html>

Applet Chat is placed on the page and identified by the name "app". Function appletReadyJ() is exposed by the script and called by chat when the applet finishes its initialization (see Chat.init() function). Call to this method signifies that the applet is ready to be used and its interface functions can now be called. In particular, initTangoJS() method of the applet is invoked to pass Tango specific arguments. For the applet it is equivalent of creating its own TAgent, so the applet can start effectively using the Tango communication framework. Number 57 has no special meaning - it just identifies a channel.

Next example will show how multiple applets, in addition to script's own messages are all connected to Tango_agent. Effectively, JavaScript here plays a role of an integration platform for applets:

ex52.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      var numAppReady=0;
      var masterReady=false;
      
      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentJS(win);
        if(Tango_agent!=null)
        {
          Tango_agent.setDbgModeJS(true);
          Tango_agent.addTDataListenerJS(window);
        }
        Tango_send('q');
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      function Tango_send(data)
      {
        if(Tango_agent!=null)
          Tango_agent.sendJS(data);
      }

      function Tango_receive(data)
      {
        var tag=data.substring(0,1);
        if(tag=='t')
        {
          document.forms.chat.tty.value=data.substring(1,data.length);
        }
        else if(tag=='r')
        {
          masterReady=true;
          if(numAppReady==2)
            initApplets();
        }
        else if(tag=='q')
        {
          if((Tango_agent.isMaster())&&(masterReady==true))
            Tango_send('r');
        }
      }

      Tango_register(window);

      function appletReadyJ(a)
      {
        numAppReady++;
        if(numAppReady==2)
        {
          if(Tango_agent.isMaster())
          {
            initApplets();
            masterReady=true;
            Tango_send('r');
          }
          else
          {
            if(masterReady)
              initApplets();
          }
        }
      }

      function initApplets()
      {
        document.applets.a1.initTangoJS(Tango_agent,57);
        document.applets.a2.initTangoJS(Tango_agent,58);
      }
    </script>
    <form name="chat">
      <input type="text" name="tty" size=16 onclick="Tango_send('t'+tty.value)">
      <input type="button" value="send">
    </form>    
    <applet name="a1" codebase="." code="Chat.class" width=200 height=33 mayscript>
    </applet>
    <applet name="a2" codebase="." code="Chat.class" width=200 height=33 mayscript>
    </applet>
  </body>
</html>

This application is made of three independent sub-applications:

Each of these applications uses the same Tango_agent created by the JavaScript application. JavaScript chat was described in the Chapter 2, Java chats were introduced earlier in this chapter. Messages of these three applications are sent by different channels: JavaScript chat goes by implicit and unnamed default channel, Java chats use channels 57 and 58. The choice of channels is arbitrary and has no other meaning than to distinguish several applications.

Special care is taken to assure proper initialization. Because applets may be not activated instantly when the JavaScript application registers with Tango, their creation should be synchronized. These precautions can be omitted if applets are stateless. Our example, however, implements special protocol to ensure connection of slave applets to Tango after the applets on the master side are connected. The protocol consists of three message types:

Application's initialization state is remembered by two variables: Slave can be connected to Tango when numAppReady equals 2 - the number of applets - and masterReady flag is true.

The protocol 'r' notification could be implemeted per applet rather than once per all applets.

Appendix A: Using "Fake Tango"

JavaScript applications written for Tango can be tested without the Tango control application. Instead, a "fake server" must be running and the script must use a different registration method.

The "fake server" can be started by issuing the following command from the operating system shell (should it be UNIX or PC):

$ java webwisdom.tango.fake.TServerFake 32831

The above command starts Java virtual machine and executes TServerFake class. Java virtual machine must be installed and the CLASSPATH environment variable has to include tango.jar archive. The command starts the "fake server" that listens on the port given in the parameter (32831).

Once the "fake server" is running, JavaScript applications can connect to it as if they connected to the control application. The only difference is that another registration procedure must be called. The code below highlights the dissimilarity, otherwise is identical to the example 2.1.

exA1.html
<html>
  <body onUnload="Tango_exit()">
    <script language="javascript">
      var Tango_agent=null;

      function Tango_register(win)
      {
        Tango_agent=Packages.webwisdom.tango.TAgentJS.createTAgentFakeJS("kopernik.npac.syr.edu",32831);
      }

      function Tango_exit()
      {
        if(Tango_agent!=null)
          Tango_agent.exitJS();
      }

      function Tango_send(m)
      {
        if(Tango_agent!=null)
          Tango_agent.sendJS(m);
      }

      function Tango_receive(m)
      {
        document.forms.chat.tty.value=m;
      }

      Tango_register(window);
      if(Tango_agent!=null)
        Tango_agent.addTDataListenerJS(window);
    </script>
    <form name="chat">
      <input type="text" name="tty" size=16 onclick="Tango_send(tty.value)">
      <input type="button" value="send">
    </form>    
  </body>
</html>

In the script above Tango_agent is created by the method createTAgentFakeJS() instead of createTAgentJS(). The method takes two parameters: the host name, and the port number of the "fake server". If, for whatever reason, registration fails, null is returned.

For the obvious reason of missing control application, the "fake" setup cannot implement the full functionality of the real Tango. Only a pair Tango_agent.send() and Tango_receive() can be reliably used, while all control interfaces has no guaranteed modus operandi.

Appendix B: Quick intro for users of Tango Java API

JavaScript code on any given HTML page is represented by JavaScript object Window. webwisdom.tango.TAgentJS.createTAgentJS() registers in Tango and returns the handle to the TAgent. The main window must be passed as an argument for registration. Windows are also used to add listeners.

All methods of TAgent all basically the same except they ends with JS, and JSObject replaces all types unavailable in JavaScript. These JSObjects wrap JavaScript objects according to the following key:


Last modified: Fri Jun 18 15:38:27 EDT 1999
Address questions related to Tango API to support@webwisdom.com.