Posts Tagged Swing

Porting Swing’s Action pattern to GXT (Ext-GWT)

ext_logoWhile working with GWT and GXT, it occurred to me that there was no equivalent in either to Swing’s Action architecture… So I set myself to implement an equivalent functionality in GXT.

The Swing legacy

In a word, the action architecture is an implementation of the Command design pattern, with the Action class being an abstraction of a command which is not bound to a specific UI component. Action implements ActionListener, which lets users perform it, namely by calling its actionPerformed method. An Action may also contain additional meta-information such as a name, a description, an icon, etc.

The cool thing with actions is that many Swing components (such as a JButton or a JMenuItem) are action-aware, which means that they can be constructed from an action instance. The data required to build the UI component, such as the button’s label, is taken from the Action’s meta-data. When the component is activated (read: clicked), the corresponding action’s actionPerformed method is then called.

Furthermore, if the action is disabled, then the corresponding UI component becomes disabled too. If you used the same Action instance to construct several UI components, they will all reflect the action’s disabled status! Now this is starting to get interesting…

Let’s see if we can make this happen with GXT.

Step 1: the Action classes

First we need an ActionListener interface:

import com.extjs.gxt.ui.client.event.BaseEvent;
 
public interface ActionListener {
 
    /**
     * Invoked when an action occurs.
     */
    void actionPerformed(BaseEvent e);
 
}

This is quite similar to Java’s original ActionListener interface, except that we use GXT’s BaseEvent instead of Java’s ActionEvent, for obvious reasons. The original ActionListener also inherited EventListener, which is not needed here.

Next we need an Action interface that allows UI components to fetch what they need to construct themselves (name, etc.). The original Action interface contains

  • a set of String constants that define the names of the properties (NAME, SHORT_DESCRIPTION, SMALL_ICON, etc.)
  • methods to get/set those properties
  • methods to add/remove a PropertyChangeListener so that we can be notified of changes

In native Java, the property-related methods are implemented completely in AbstractAction using ArrayTable and SwingPropertyChangeSupport, which means I would have to add a lot of code to duplicate the functionality.

Fortunately, GXT has a class called BaseModel which provides exactly the same set of functionality: named properties plus support for change listeners. So I slightly modified the Action interface so that GXT’s BaseModel would directly implement the property-related methods:

  • Object getValue(String key) becomes <X> X get(String key)
  • void putValue(String key, Object value) becomes <X> X set(String key, X value)
  • void addPropertyChangeListener(PropertyChangeListener listener) becomes void addChangeListener(ChangeListener... listener)
  • void removePropertyChangeListener(PropertyChangeListener listener) becomes void removeChangeListener(ChangeListener... listener)

Here is the full listing (most comments removed for brevity; see original Action Javadoc):

import com.extjs.gxt.ui.client.data.ChangeListener;
 
public interface Action extends ActionListener {
    /**
     * Useful constants that can be used as the storage-retrieval key
     * when setting or getting one of this object's properties (text
     * or icon).
     */
    public static final String DEFAULT = "Default";
    public static final String NAME = "Name";
    public static final String SHORT_DESCRIPTION = "ShortDescription";
    public static final String LONG_DESCRIPTION = "LongDescription";
    public static final String SMALL_ICON = "SmallIcon";
    public static final String ACTION_COMMAND_KEY = "ActionCommandKey";
    public static final String ACCELERATOR_KEY = "AcceleratorKey";
    public static final String MNEMONIC_KEY = "MnemonicKey";
    /**
     * The key used for storing the CSS icon style (added)
     */
    public static final String ICON_STYLE = "IconStyle";
 
    public <X> X get(String key);
    public <X> X set(String key, X value);
 
    public void setEnabled(boolean b);
    public boolean isEnabled();
 
    public void addChangeListener(ChangeListener... listener);
    public void removeChangeListener(ChangeListener... listener);
}

A few comments on this:

  • get/set methods are now generic (which must be good),
  • we use GXT’s ChangeListener instead of Java’s PropertyChangeListener, because that’s what GXT’s BaseModel supports
  • not all properties make sense in GXT, but I have kept them nonetheless. You never know…
  • I have added a property ICON_STYLE, which might be useful in a GWT context if we want to provide an icon as a CSS style rather than an Image.

Now we need an implementation of the Action interface that provides default behaviours for get/set methods and property change listeners; thanks to our little changes in Action, we just have to extend GXT’s BaseModel, which will handle most of the job.

import com.extjs.gxt.ui.client.data.BaseModel;
 
public abstract class AbstractAction extends BaseModel implements Action {
 
    /**
     * Specifies whether action is enabled; the default is true.
     */
    protected boolean enabled = true;
 
    public AbstractAction() {
    }
 
    public AbstractAction(String name) {
        set(Action.NAME, name);
    }
 
    public boolean isEnabled() {
        return enabled;
    }
 
    public void setEnabled(boolean newValue) {
        boolean oldValue = this.enabled;
 
        if (oldValue != newValue) {
            this.enabled = newValue;
            notifyPropertyChanged("enabled", newValue, oldValue);
        }
    }
 
}

That’s it! The get/set/addChangeListener/removeChangeListener methods are directly implemented by BaseModel and don’t even need to be redeclared here. I have dropped Cloneable and Serializable from the implemented interfaces because it doesn’t make much sense in a GWT context.

Step 2: the UI components

OK, now we have Actions that have properties, can notify changes, and can do something when they are performed. What we need to really make this useful is UI components that can accept an Action and will do what’s needed to translate that into UI stuff.

Let’s extend Menu so that we can call add(Action):

public class Menu extends com.extjs.gxt.ui.client.widget.menu.Menu {
 
    public boolean add(final Action action) {
 
        final MenuItem item = new MenuItem(action.get(Action.NAME));
 
        // Use the action's long description as tooltip
        String longDesc = action.get(Action.LONG_DESCRIPTION);
        if (longDesc != null) {
            item.setToolTip(longDesc);
        }
        item.setEnabled(action.isEnabled());
 
        item.addSelectionListener(new SelectionListener() {
            public void componentSelected(ComponentEvent event) {
                action.actionPerformed(event);
            }
        });
 
        // make sure changes in the "enabled" state of the action are propagated
        // to the MenuItem
        action.addChangeListener(new ChangeListener() {
            public void modelChanged(ChangeEvent event) {
                PropertyChangeEvent propertyChangeEvent = (PropertyChangeEvent) event;
                if (propertyChangeEvent.getName().equals("enabled")) {
                    boolean enabled = (Boolean) propertyChangeEvent.getNewValue();
                    item.setEnabled(enabled);
                }
            }
        });
 
        return add(item);
    }
}

The outline is very simple:

  • create a MenuItem
  • give it name, tooltip, state according to the Action
  • make sure that selecting it will trigger the action
  • make sure that if the “enabled” status of the action changes, the “enabled” status of the MenuItem changes accordingly
  • add the MenuItem

Now let’s do the same for a Button; in this case we’ll add a constructor that takes an Action :

public class Button extends com.extjs.gxt.ui.client.widget.button.Button {
 
    public Button() {
    }
 
    public Button(String text) {
        super(text);
    }
 
    public Button(String text, SelectionListener listener) {
        super(text, listener);
    }
 
    public Button(final Action action) {
        this(action.get(Action.NAME));
 
        // Use the action's long description as tooltip
        String longDesc = action.get(Action.LONG_DESCRIPTION);
        if (longDesc != null) {
            setToolTip(longDesc);
        }
 
        setEnabled(action.isEnabled());
 
        addSelectionListener(new SelectionListener() {
            public void componentSelected(ComponentEvent event) {
                action.actionPerformed(event);
            }
        });
 
        // make sure changes in the "enabled" state of the action are propagated
        // to the Button
        action.addChangeListener(new ChangeListener() {
            public void modelChanged(ChangeEvent event) {
                PropertyChangeEvent propertyChangeEvent = (PropertyChangeEvent) event;
                if (propertyChangeEvent.getName().equals("enabled")) {
                    boolean enabled = (Boolean) propertyChangeEvent.getNewValue();
                    setEnabled(enabled);
                }
            }
        });
 
    }
}

You get the picture, it’s easy to generalize this to any UI component.

Conclusion

With this pattern, you can now easily create a menu item and a button, linked to the same action. If the action becomes unavailable, you call Action.setEnabled(false), and automagically the button and menu item become disabled…

PS: some of the code here is directly taken from the JDK source; I believe this is legit use under the Java Research License, but if it is not, let me know.

, , ,

No Comments

What’s RIA anyway ?

RIA or Rich Internet Applications have been a buzzword for a while now, but it’s hard to find two persons who agree on what it means. It appears the word was coined by Macromedia, now Adobe, when defining the requirements for the new version of their successful browser plug-in (Flash).

Let’s break it down:

  • Application: a RIA is an Application, which doesn’t mean much except that it’s not system software. In short, it helps the user do something useful.
  • Internet: a RIA uses the Internet. Actually, not necessarily the internet, but more generally speaking a network. What this really means is that the application’s responsibilities are distributed between the user interface and one ore more servers. Exactly what is distributed, how and when is all the difference between various kinds of RIAs. What we are talking about here is presentation, validation, business logic, persistence, etc.
  • Rich: a RIA is Rich. This is probably in contrast with web 1.0 page-based applications where the interaction was limited to filling in a form and pressing submit. A RIA is Rich UI-wise, which means it has widgets that are similar to those found in desktop applications: formatted text fields, combo boxes, drop down-menus, tables with sorting and column re-ordering, to name a few.

To sum it up, a RIA is an application, with a near-desktop user interface, that requires a network to one (or more) servers for some (or all) of its functionality. This definition is voluntarily very vague, because many combinations of technology and architecture qualify as RIA.

Based on this definition however, we can refute some ideas that commonly surround RIA:

  • RIA applications require  a browser plugin: WRONG! It is true that some vendors (Adobe, Microsoft) have chosen to bypass the challenges of browser incompatibilities and poorness of native widgets by providing a plugin (Flash, Silverlight) that gives a uniform look on all (supported) platforms. But the current level of JavaScript implementation in major browsers makes it possible to implement a full-blown RIA in native JavaScript without the use of any plugin. This is even more true now that GWT is there, because GWT will take care of platform differences and let you concentrate on the programming itself. This will be even more true in the future with HTML5.
  • RIA applications are necessarily JavaScript-based: WRONG! see above…
  • A RIA application is an application that runs in a browser: WRONG! If most RIA applications run in a browser, be it in a plugin or as JavaScript, a Swing-based application launched through JNLP (WebStart) also qualifies as a RIA. And even applications that normally run in a browser can be packaged in such a way that they appear not to (see Prism for example)
  • A RIA application only works online: WRONG! This might be true for most RIAs, but it is not a requirement. A RIA application can have an online mode where it communicates with a server, and an offline mode where you work locally, and your work is later synchronized with the server as soon as it becomes available. Google Gears is one of the ways this can be accomplished. And Google Docs is one of the examples of an online/offline RIA.
  • A RIA application cannot be integrated into the OS like a native application: WRONG! Tools exist to make any web application (not even necessarily RIA) look like a native application to the user. For example, Prism from Mozilla will let you create an icon for any web page and launch it in its own window, without any browser decoration. Google Chrome will let you do the same right from the browser itself. Adobe Air goes further and provides all the missing “glue” between RIAs (Flex and JavaScript) and the OS, including creating a native shortcut, but also letting the application interact with the OS in ways usually reserved for native apps like drag&drop, access to local storage, etc.
  • RIA applications are difficult to develop and debug: WRONG! plugin-based RIAs rely on vendor IDEs that let you develop just like you would develop a desktop app. For JavaScript-based RIAs, GWT lets you develop and debug your application in pure Java, and takes care of translating it to JavaScript for deployment.

All this points to a direction: the line between RIAs and classic desktop application is getting thinner every day. Technologies used for one can now be used for the other, thanks to frameworks such as Adobe Air or GWT. And as a consequence, the user eventually will not be able to tell one from the other. And probably he won’t even care.

, , , , , , , , , ,

No Comments