ClientObject Tutorial

Introduction

In BBj 2.0 and higher, programmers can embed Java to access the full functionality of the Java language from within a BBj program. So, for example, a program might use the following code to print the current time using embedded (server-side) objects.

Print Time and TimeZone of Server Machine

rem Print time and TimeZone of server machine

use java.util.Date
use java.text.DateFormat

serverDate! = new Date()
serverFormat! = DateFormat.getDateTimeInstance()

print serverFormat!.format(serverDate!)," ",
print serverFormat!.getTimeZone().getDisplayName()

In many configurations, the user display is on a different machine (the client machine) than that on which the BBjInterpreterServer is running (the server machine). Because these machines may be in different time zones, this small program may display results that are different from what the programmer intended. This code is executed within the Java Virtual Machine (JVM) of the server and so it will display the time and time zone of the server machine while the programmer may have wanted to display the local time on the client machine.

Prior to BBj 8.0, there has not been a mechanism by which a program could execute code within the JVM of the client. BBj 8.0 introduces syntax that provides an RMI (Remote Method Invocation) interface that allows the embedded Java code within a BBj program to access objects that reside in the client JVM rather than within the server JVM.

In order to access this RMI capability, append an @ symbol to a class name to indicate that the class will be accessed in the client JVM rather than in the server JVM as shown in the sample below using ClientObjects:

rem Print time and TimeZone of client machine

use java.util.Date
use java.text.DateFormat

clientDate! = new Date@()
clientFormat! = DateFormat@.getDateTimeInstance()

print clientFormat!.format(clientDate!)," ",
print clientFormat!.getTimeZone().getDisplayName()

When this code is executed it will use the time of the client machine to create a new Date Object in the client JVM and will use the locale of the client to create a DateFormat object in the client JVM. The result of running this program will be to display the time and TimeZone of the client machine rather than the time and TimeZone of the server.

When objects are created in the client JVM those objects are referred to as client-side objects. The server-side variables that reference those client-side objects are referred to as ClientObjects. So, for example, when line 40 is executed a client-side instance of Date is created and the variable remoteDate! is a ClientObject that represents an RMI handle to that client-side object. The ClientObject can be manipulated in the same way as a server-side object.

This tutorial provides a short example of how to use ClientObjects. If unfamiliar with the use of Java objects within a BBj program, read the following references about using server-side embedded Java before attempting to use ClientObjects:

BBj Custom Objects Tutorial

Exponentially Better Applications: Embedded Java and BBj (PDF)

Syntax of ClientObjects

There are three ways in which the @ symbol is used in BBj to indicate that embedded Java is to use ClientObjects; when

  1. Creating a new ClientObject

  2. Invoking a static method on a client-side class

  3. Declaring a value to be a ClientObject

In each case, the @ is appended to the class name. The class name can be a fully qualified class name or it can be an abbreviated class name if the fully qualified class name has appeared in a USE statement.

The following sample shows the various uses of the @ character within a BBj program.

0010 use java.util.HashMap
0020 use java.util.Calendar

0030 REM declaring variables to have ClientObject types
0040 declare HashMap@ map!
0050 declare Calendar@ calendar!
0060 declare java.util.ArrayList@ list!
0070 declare java.text.DateFormat@ dateFormat!

0080 REM creating new instances of ClientObjects
0090 map! = new HashMap@()
0100 list! = new java.util.ArrayList@()

0110 REM invoking static methods on client-side classes
0120 calendar! = Calendar@.getInstance()

0130 dateFormat! = java.text.DateFormat@.getDateTimeInstance()

At lines 040-070 a number of variables are declared to be ClientObjects. At lines 090-110 two client-side objects are created and assigned to ClientObject variables. At lines 120-130, two static methods are called on client-side classes and the return values of those static methods are assigned to ClientObject variables.

Using a ClientObject

The methods of a ClientObject are called in the same way that the methods of other objects are called; by using the standard dot notation. In general, the parameters that are passed when calling a method on a ClientObject must themselves be ClientObjects. If a method accepts a string or a number then it will accept a server-side value (see the ClientObject vs. Server-side Objects section for details).

The return value of a method called on a ClientObject is again generally a ClientObject unless that return value is a number or a string. If the return value is a number or a string then the return value will be a server-side value.

This code sample calls methods on ClientObjects:

use java.util.HashMap
use java.util.Calendar

declare HashMap@ map!
declare Calendar@ calendar!

map! = new HashMap@()
calendar! = Calendar@.getInstance()
print calendar!
print
print calendar!.getTime()
print

map!.put("calendar",calendar!)
map!.put(123,456)

print map!.get("calendar")
print
print map!.get(123)

Running the code sample results in output similar to the following.

[CLIENTOBJECT] @139f593
java.util.GregorianCalendar[time=1178801482531,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo
[id="America/Denver",offset=-25200000,dstSavings=3600000,useDaylight=true,transitions=157,lastRule=java.util.SimpleTimeZone
[id=America/Denver,offset=25200000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1
, startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1, 
minimalDaysInFirstWeek=1,ERA=1,YEAR=2007,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=130,DAY_OF_WEEK=5, 
DAY_OF_WEEK_IN_MONTH=2,AM_PM=0, HOUR=6,HOUR_OF_DAY=6,MINUTE=51,SECOND=22,MILLISECOND=531,ZONE_OFFSET=-5200000,DST_OFFSET=3600000]

[CLIENTOBJECT] @1976073
Thu May 10 06:51:22 MDT 2007

[CLIENTOBJECT] @139f593
java.util.GregorianCalendar[time=1178801482531,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo 
[id="America/Denver",offset=-25200000,dstSavings=3600000,useDaylight=true,transitions=157,lastRule=java.util.SimpleTimeZone 
[id=America/Denver,offset=25200000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1
, startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1, 
minimalDaysInFirstWeek=1,ERA=1,YEAR=2007,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=130,DAY_OF_WEEK=5, 
DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=6,HOUR_OF_DAY=6,MINUTE=51,SECOND=22,MILLISECOND=531,ZONE_OFFSET=-5200000,DST_OFFSET=3600000]

456

The toString() value of a ClientObject consists of the string "[CLIENTOBJECT]" followed by an ID followed by the toString() value of the client-side object.

Notice that looking at the output above, calendar! is a ClientObject and that the return value of calendar!.getTime() is also a ClientObject. If we place calendar! into a client-side HashMap and then retrieve it, we will receive the same ClientObject. And if we place the server-side number 456 into the client-side HashMap and then retrieve it, we will receive the server-side number.

The field values of a client-side object can also be accessed through the ClientObject and the static fields of a client-side class can be accessed by addressing that class using the @ symbol as shown below.

use java.awt.Color
declare Color@ blue!
blue! = Color@.BLUE
print blue!

ClientObject vs. Server-side Objects

It is important to understand the difference between a ClientObject and a server-side Object. Any Object that was created using the '@' notation is a ClientObject. Any Object that was created without the '@' notation is a server-side Object. BBjAPI is a server-side Object. Any Object that is obtained through calls on BBjAPI is a server-side Object. In particular, all BBjControls are server-side Objects even though they 'represent' GUI Objects that exist on the client.

In general, the parameters passed to a ClientObject must be ClientObjects while the parameters passed to a server-side object must be server-side objects.

The exceptions to this general rule are:

  1. Strings and Numbers are always server-side values. Strings and Numbers may be passed as parameters to methods of ClientObjects.

  2. The method BBjWindow::addWrappedJComponent (which is a method call on a server-side object) accepts as a parameter a ClientObject.

  3. A BBjControl (which is a server-side object) can be passed as a parameter to a ClientObject if that ClientObject represents a client-side swing component.

  4. A BBj CustomObject (which is server-side object) can be registered as an event listener on a ClientObject that represents a swing component

These exceptions allow a program to place objects that extend javax.awt.JComponent@ (ie any client-side JComponent) onto a BBjWindow as well as to place BBjControls onto JComponents and to respond to events that occur on JComponents. All these use cases are demonstrated in the following sections.

Similarly, the return value of a method invocation on a ClientObject is itself a ClientObject and the return value of a method invocation on a server-side Object is a server-side Object except that:

  1. Strings and Numbers are always server-side values.

  2. BBjBarChart, BBjLineChart, and BBjPieChart (which are server-side Objects) each have a method getClientChart() which returns a ClientObject.

Placing a JComponent into a BBjWindow

BBj 8.0 provides a new BBjWindow::addWrappedJComponent method that allows the program to create a BBjControl that contains a client-side Java Component. In order to place a client-side JComponent onto a BBjWindow, the program first creates a ClientObject that represents the JComponent and then 'wraps' the Object in a BBjWrappedJComponent by calling addWrappedJComponent.

Note: BBjWrappedJComponent depends on the assumption that the client is Java. In the BUI and DWC browser-based clients, use BBjWebComponent.

The following code creates a JButton and places it onto a BBjWindow.

use javax.swing.JButton
REM declare server side variables
declare BBjSysGui sysGui!
declare BBjWindow window!
declare BBjControl BBjControl!
REM declare ClientObject variables
declare JButton@ JButton!
REM create a window
sysGui! = BBjapi().openSysGui("X0")
window! = sysGui!.addWindow(100,100,140,100,"JButton",$00090083$)
window!.setCallback(window!.ON_CLOSE,"eoj")
JButton! = new JButton@()
REM add ClientObject as a wrapped component on the window
BBjControl! = window!.addWrappedJComponent(101,50,50,100,60,JButton!)
REM manipulate BBjControl! as a BBjControl
BBjControl!.setText("JButton")
BBjControl!.setLocation(20,20)
process_events
eoj:
release

Registering a BBj Callback as a Listener on a JComponent

A BBj CustomObject may be registered as an event listener on a ClientObject by calling an add<some>Listener method of the ClientObject.

Note: The BBj API that manages ClientObject event listeners expects the Listener to be a java.util.EventListener and the event object to be a java.util.EventObject.

The CustomObject must have methods that 'correspond' to the methods of <some>Listener. Each of the corresponding methods of the CustomObject must have the same name as the method of the <some>Listener and must accept ClientObjects where the <some>Listener accepts events.

So, for example, the following code sample registers a CustomObject named Listener as a PropertyChangeListener on a JSplitPane. A PropertyChangeListener has a single method.

void PropertyChange(PropertyChangeEvent event)

So the CustomObject, Listener, must have a corresponding method.

void PropertyChange(PropertyChangeEvent@event)

with the same name but accepting a PropertyChangeEvent@ rather than a PropertyChangeEvent.

This sample adds several JComponents to a BBjWindow and registers some listeners:

rem ' BBjWrappedJComponent

use java.awt.Color
use java.awt.Dimension
use java.awt.event.ActionEvent
use java.awt.event.ActionListener
use java.beans.PropertyChangeEvent
use java.beans.PropertyChangeListener
use javax.swing.JButton
use javax.swing.JFormattedTextField
use javax.swing.JPanel
use javax.swing.JScrollPane
use javax.swing.JSplitPane
use javax.swing.JTable
use javax.swing.JTextArea
use java.util.Vector

sysgui = unt
open (sysgui)"X0"
sysgui! = bbjapi().getSysGui()
window! = sysgui!.addWindow(40,40,400,310,"Working with JComponents",$00090083$)
window!.setCallback(window!.ON_CLOSE,"eoj")

aButton! = window!.addButton(1,10,10,120,40,"BBjButton 1")
aButton!.setCallback(aButton!.ON_BUTTON_PUSH,"buttonPush")

bButton! = window!.addButton(2,10,60,120,40,"BBjButton 2")
bButton!.setCallback(aButton!.ON_BUTTON_PUSH,"buttonPush")

jButton! = new JButton@("JButton")
jButton!.addActionListener(new ButtonListener())
cButton! = window!.addWrappedJComponent(3,10,110,120,40,jButton!)
cButton!.setToolTipText("Hi!  I'm a JButton!")
cButton!.setCallback(cButton!.ON_MOUSE_ENTER,"mouseover",err=*next)
cButton!.setCallback(cButton!.ON_MOUSE_EXIT,"mouseout",err=*next)
cButton!.focus()
cButton!.setTabTraversable(1)

aEdit! = window!.addInputE(101,10,160,120,40)
aEdit!.setText("BBjInputE 101")

bEdit! = window!.addInputE(102,10,210,120,40)
bEdit!.setText("BBjInputE 102")

jEdit! = new JFormattedTextField@("JTextField 103")
cEdit! = window!.addWrappedJComponent(103,10,260,120,40,jEdit!)
cEdit!.setCallback(cEdit!.ON_MOUSE_ENTER,"mouseover",err=*next)
cEdit!.setCallback(cEdit!.ON_MOUSE_EXIT,"mouseout",err=*next)
cEdit!.setTabTraversable(1)
cEdit!.setToolTipText("Hi!  I'm a JFormattedTextField!")

columnNames! = new Vector@()
columnNames!.add("A")
columnNames!.add("B")
columnNames!.add("C")
rowData! = new Vector@()
row! = new Vector@()
row!.add(1);row!.add(2);row!.add(3)
rowData!.add(row!)
row! = new Vector@()
row!.add(4);row!.add(5);row!.add(6)
rowData!.add(row!)
row! = new Vector@()
row!.add(7);row!.add(8);row!.add(9)
rowData!.add(row!)
jtable! = new JTable@(rowData!,columnNames!)
jtable!.setRowHeight(25)
scroll! = new JScrollPane@(jtable!)
tableComponent! = window!.addWrappedJComponent(104,140,10,250,105,scroll!)
tableComponent!.setCallback(tableComponent!.ON_MOUSE_ENTER,"mouseover",err=*next)
tableComponent!.setCallback(tableComponent!.ON_MOUSE_EXIT,"mouseout",err=*next)
tableComponent!.setTabTraversable(1)
tableComponent!.setToolTipText("Hi!  I'm a JTable!")

jtable!.setRowSelectionAllowed(0)
jtable!.getModel().setValueAt("Hello world",1,1)
jtable!.setToolTipText("Hi!  I'm a JTable!")
jtable!.setAutoCreateRowSorter(1)

grid! = window!.addGrid(105,140,130,250,120,$804a$,3,3)
grid!.setEditable(1)
grid!.setAllColumnsUserSortable(1)
grid!.setRowHeight(25)
grid!.setFitToGrid(grid!.AUTO_RESIZE_SUBSEQUENT_COLUMNS)
columnNames! = bbjapi().makeVector()
columnNames!.add("A")
columnNames!.add("B")
columnNames!.add("C")
grid!.setColumnHeaderText(columnNames!)
cellText! = bbjapi().makeVector()
for i=1 to 9; cellText!.add(str(i)); next i
grid!.setCellText(cellText!)
grid!.setCellText(1,1,"Hello world")
grid!.setShortCue("Hi!  I'm a BBjGrid!")

text$ = "The quick brown fox jumps over the lazy dog."
left! = new JFormattedTextField@(text$)
left!.setToolTipText("Hi!  I'm the left side of a JSplitPane!")
right! = new JFormattedTextField@(text$)
right!.setToolTipText("Hi!  I'm the right side of a JSplitPane!")
splitPane! = new JSplitPane@(JSplitPane@.HORIZONTAL_SPLIT,1,left!,right!)
splitPane!.setPreferredSize(new Dimension@(250,50))
splitPane!.setDividerLocation(125)
splitPane!.addPropertyChangeListener(new PropertyListener())
panel! = new JPanel@()
panel!.add(splitPane!)
panel!.validate()
split! = window!.addWrappedJComponent(106,140,240,250,60,panel!)
split!.setToolTipText("Hi!  I'm a JSplitPane!")
split!.setCallback(split!.ON_MOUSE_ENTER,"mouseover",err=*next)
split!.setCallback(split!.ON_MOUSE_EXIT,"mouseout",err=*next)

process_events

eoj:
release

buttonPush:
  event! = sysgui!.getLastEvent()
  info$ = "Clicked: " + str(event!.getControl().getText())
  print info$
  System.out.println(info$)
return

mouseover:
  event! = sysgui!.getLastEvent()
  print event!.getEventName()
  control! = event!.getControl()
  control!.setBackColor(BBjColor.YELLOW)
return

mouseout:
  event! = sysgui!.getLastEvent()
  print event!.getEventName()
  control! = event!.getControl()
  control!.setBackColor(null())
return

class public ButtonListener
  method public void actionPerformed(ActionEvent@ actionEvent!)
    info$ = "Clicked: " + str(actionEvent!.getActionCommand())
    print info$
    System.out.println(info$)
  methodend
classend

class public PropertyListener
  method public void propertyChange(PropertyChangeEvent@ propertyChangeEvent!)
    info$ = "PropertyChange: " + str(propertyChangeEvent!)
    print info$
    System.out.println(info$)
 methodend
classend

The screen that this code sample generates appears as:

Mixing ClientObjects and BBjControls to Provide a BBjWindow with a LayoutManager

We are now in a position to build a program that provides an interesting mix of JComponents and BBjContols. The following sample provides a BBjWindow that contains a JSplitPane. The left side of the JSplitPane contains both BBjControls and JComponents. In addition, the left panel has a layout manager so that all the controls (both the JComponents and the BBjControls) change sizes as the separator of the JSplitPane is moved.

Note: In BBj 22.10 and higher, the BBjSplitter control replicates the functionality of the JSplitPane, and can be used in the BUI and DWC browser-based clients.

This code sample shows a mix of JComponents and BBjControls with a LayoutManager:

use java.beans.PropertyChangeEvent
use javax.swing.JPanel
use javax.swing.JSplitPane
use javax.swing.JFormattedTextField
use java.awt.Dimension
use java.awt.GridLayout
use java.awt.event.FocusEvent

declare BBjSysGui sysgui!
declare BBjWindow window!
declare BBjButton button!
declare JSplitPane@ splitPane!
declare JPanel@ panel!
declare Listener listener!

REM open sysgui and add a window
sysgui! = BBjapi().openSysGui("X0")
window! = sysgui!.addWindow(100,100,500,400,"BBjWindow",$00010003$)
panel! = new JPanel@()

REM create some BBjControls
inputn! = window!.addInputN(102,10,10,90,30)
inputd! = window!.addInputD(103,10,10,90,30)
navigator! = window!.addNavigator(104,10,10,90,30,"BBjNavigator")
button! = window!.addButton(1,10,10,90,30,"BBjButton")

gosub gettysburg
cedit! = window!.addCEdit(106,10,10,200,300,gettysburg$,$0002$)

REM add a wrapped JPanel to the BBjWindow and
REM place a JSplitPane into that JPanel
window!.addWrappedJComponent(2222,0,0,500,400,panel!)
splitPane! = new JSplitPane@()
dimension! = new Dimension@(window!.getWidth(),window!.getHeight())
splitPane!.setPreferredSize(dimension!)
splitPane!.setContinuousLayout(1)
splitPane!.setOrientation(JSplitPane@.HORIZONTAL_SPLIT)

REM create a JPanel that will become the left side panel
REM give it a GridLayout
layout! = new GridLayout@(6,1)
leftPanel! = new JPanel@(layout!)

REM add a JComponent to the left panel
jEdit! = new JFormattedTextField@()
jEdit!.setText("JTextField 220")
jEdit!.setBounds(10,10,90,30)
jEdit!.setVisible(1)
leftPanel!.add(jEdit!)

REM place most of the BBjComponents into the left panel
leftPanel!.add(inputn!)
leftPanel!.add(inputd!)
leftPanel!.add(navigator!)
leftPanel!.add(button!)

REM place the remaining BBJComponent into the panels
splitPane!.add(leftPanel!,JSplitPane@.LEFT)
splitPane!.add(cedit!,JSplitPane@.RIGHT)

REM place the JSplitPane into the wrapped JPanel
REM and validate the wrapped JPanel
panel!.add(splitPane!)
panel!.validate()

REM create a listener. Register it to listen for
REM PropertyChangeEvents on the splitPane and for
REM FocusEvents on the jEdit
listener! = new Listener()
splitPane!.addPropertyChangeListener(listener!)
jEdit!.addFocusListener(listener!)

REM set some BBj callbacks
window!.setCallback(window!.ON_CLOSE,"eoj")
window!.setCallback(window!.ON_RESIZE,"resize")
button!.setCallback(button!.ON_BUTTON_PUSH,listener!,"buttonPush")

process_events

eoj:
release

resize:
  event! = sysgui!.getLastEvent()
  dimension! = new Dimension@(event!.getWidth(), event!.getHeight())
  splitPane!.setPreferredSize(dimension!)
return

class public Listener

  method public void propertyChange( PropertyChangeEvent@ p_event!)
    target! = p_event!.getSource()
    print p_event!.getPropertyName()," = ",
    print p_event!.getNewValue()
  methodend

  method public void buttonPush(BBjButtonPushEvent event!)
    print "button pushed"
  methodend

  method public void focusGained(FocusEvent@ p_event!)
    print "GainedFocus: ",
    print p_event!.getSource().getClass()
  methodend

  method public void focusLost(FocusEvent@ p_event!)
    print "LostFocus: ",
    print p_event!.getSource().getClass()
  methodend

classend

gettysburg:
  gettysburg$ = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal."+$0a$+$0a$+ "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this."+$0a$+$0a$+ "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."+$0a$
return

This sample generates this screen:

If the user moved the separator, the screen might look like this:

In addition to managing the Layout of the left panel, this sample also reports FocusGain and FocusLoss whenever the JTextField gains or loses focus and reports PropertyChangeEvents whenever the user moves the separator of the JSplitPane.

Requirements for Class files Used to Create ClientObjects

In order to create a ClientObject the Java Class file that defines the client-side Object must be visible to the server as well as the client. For core Java classes like JButton, this happens automatically. Custom Java classes that don't come with BBj must be explicitly added to both the server and client classpaths.