Firing Events

Events can be fired from the server-side Java API of a component, replicating events that occurred on the client. To do this, the first requirement is that the client-side implementation of your component be able to fire an event, as discussed in the client side Firing Events section. The component will then need to have listener registration and event firing methods added to it, and the peer will need to be updated to forward incoming events to the client.

In the example used in this discussion, we'll add a capability to the server-side SpinButton to fire ActionEvents when the ENTER key is pressed on the client.

The Component

The Component base class provides a generic event registration facility in the form of an EventListenerList object. The getListenerList() method is used to retrieve (and lazy-create, if necessary) this object.

The modification to a component for event firing support will be to add event listener management methods:

    public static final String ACTION_LISTENERS_CHANGED_PROPERTY = "actionListeners";
    public void addActionListener(ActionListener l) {
        getEventListenerList().addListener(ActionListener.class, l);
        firePropertyChange(ACTION_LISTENERS_CHANGED_PROPERTY, null, l);
    }

    public void removeActionListener(ActionListener l) {
        getEventListenerList().removeListener(ActionListener.class, l);
        firePropertyChange(ACTION_LISTENERS_CHANGED_PROPERTY, l, null);
    }

The listener add and remove methods will also fire a property change event to indicate that the component has been updated. This is necessary to inform the synchronization system that the component has changed state.

Additionally we'll add a hasActionListeners() method to determine if any ActionListeners are present (this will be needed by the synchronization peer):

    public boolean hasActionListeners() {
        return hasEventListenerList() 
                && getEventListenerList().getListenerCount(ActionListener.class) > 0;
    }

We'll also add the capability to set an actionCommand property on the SpinButton, given that ActionEvent supports it, and it can be useful in development. To do this, we'll need to add a property name constant for change listeners, an instance variable, and a getter/setter pair:

    public static final String ACTION_COMMAND_CHANGED_PROPERTY = "actionCommand";
    private String actionCommand;
    public String getActionCommand() {
        return actionCommand;
    }

    public void setActionCommand(String newValue) {
        String oldValue = actionCommand;
        actionCommand = newValue;
        firePropertyChange(ACTION_COMMAND_CHANGED_PROPERTY, oldValue, newValue);
    }

And to provide notification when the ENTER key is pressed, we'll add a fireAction() method that will create an ActionEvent and send it to any registered listeners:

    private void fireAction() {
        EventListener[] actionListeners 
                = getEventListenerList().getListeners(ActionListener.class);
        ActionEvent e = new ActionEvent(this, getActionCommand());
        for (int i = 0; i < actionListeners.length; ++i) {
            ((ActionListener) actionListeners[i]).actionPerformed(e);
        }
    }

The final step is to update the processInput() method of the component, such that when the peer notifies the component of an action, the component can fire an ActionEvent. To do this, we'll create a constant name for an incoming action event, INPUT_ACTION, and fire ActionEvents when it is received by processInput():

    public static final String INPUT_ACTION = "action";
    public void processInput(String inputName, Object inputValue) {
        super.processInput(inputName, inputValue);
        if (VALUE_CHANGED_PROPERTY.equals(inputName)) {
            Integer value = (Integer) inputValue;
            setValue(value == null ? 0 : value.intValue());
        } else if (INPUT_ACTION.equals(inputName)) {
            fireAction();
        }
    }

(Changes to the processInput() method are shown in bold).

The Synchronization Peer

The necessary changes to the SpinButtonPeer class are less significant. We only need to add a new event type in the constructor:

    public SpinButtonPeer() {
        super();
        addOutputProperty(SpinButton.VALUE_CHANGED_PROPERTY);
        addEvent(new EventPeer(SpinButton.INPUT_ACTION,
                SpinButton.ACTION_LISTENERS_CHANGED_PROPERTY) {
            public boolean hasListeners(Context context, Component c) {
                return ((SpinButton) c).hasActionListeners();
            }
        });
    }

(Changes to the constructor are shown in bold.)

The addEvent() method provides an EventPeer that describes how the event should be handled. The first parameter to the EventPeer constructor is the property name that should be sent to Component.processInput() when the event is fired. The second parameter is the property name that will be fired from the Component in a PropertyChangeEvent in the case where listeners are added or removed. Additionally, note that the hasListeners() implementation of EventPeer is overridden to query SpinButton's hasActionListeners() method.