This tutorial teaches, step by step, the use and capabilities of Tango interface for Java. Each of the following chapters presents one or a few aspects of programming for Tango in Java. 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.
The material in the tutorial is illustrated with a simple chat application. All code listings highlight taught concepts according to the following conventions:
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 control application configuration manual.
What we have to realize here though is that there are two ways of being a Tango enabled Java application:
In the following chapters we assume Tango control application working and configured to know the location of a Web page containing our applet. The scope of this teaching is confined to creation of a Java class extending java.applet.Applet for this very page.
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 import java.applet.Applet; import webwisdom.tango.*; public class JEx11 extends Applet { public void init() { try { TAgent agent=new TAgent(new TLAgentApplet(this)); } catch(TangoException e) { System.err.println("JEx11: Tango registration failed: "+e); } } }
ex11.html import java.applet.Applet; import webwisdom.tango.*; public class JEx12 { public static void main(String[] args) { try { TAgent agent=new TAgent(new TLAgentApplication(args)); } catch(TangoException e) { System.err.println("JEx11: Tango registration failed: "+e); } } }
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:
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.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>
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:
Our chat is a form named "chat" made of two elements:chat.html <html> <body> <form name="chat"> <input type="text" name="tty" size=16> <input type="button" value="send"> </form> </body> </html>
Now we add Tango code to it:
In the script above two functions are defined: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>
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:
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.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.getParticipantNamesJS()); print(" isAudioAvailable="+Tango_agent.isAudioAvailableJS()); } Tango_register(window); </script> <form> <input type="button" value="debug" onclick="Tango_debug()"> </form> </body> </html>
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+")"); } function Tango_audioRequested(isR) { print("Tango_audioRequested("+isR+")"); } if(Tango_agent!=null) Tango_agent.addTControlListenerJS(window); </script> </body> </html>
Function addTControlListenerJS() 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.
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 getParticipantsJS() 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.addTControlListenerJS(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_masterChanged(isM,mNa) { } function Tango_participantJoined(pNa) { optionsAddOption(document.forms.chat.userNames.options,pNa); } function Tango_participantLeft(pNa) { optionsRemoveOption(document.forms.chat.userNames.options,pNa); } function Tango_audioRequested(isR) { } 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.getParticipantNamesJS(); 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.getParticipantNamesJS() call. The form is updated in Tango_participantJoined/Left() functions whenever change is reported by the Tango system. Functions Tango_masterChanged() and Tango_audioRequested() are not used in this example but have to be defined as a consequence of registering control listener in Tango_agent.addTControlListenerJS() (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.addTControlListenerJS().
Other functions used in the example are JavaScript utilities:
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.exit(); } 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.exit(); } 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:
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:
The protocol 'r' notification could be implemeted per applet rather than once per all applets.
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: