Geertjan is a DZone Zone Leader and has posted 468 posts at DZone. You can read more from them at their website. View Full User Profile

Hello EclipseLink + Modularity on the NetBeans Platform

01.21.2009
| 7080 views |
  • submit to reddit
In this part (which follows from this part) we add another view to our application, which will ultimately be the editor for the selected Customer in the tree view. For now, it will simply display the values of the selected Customer:

There are two interesting things worth noticing here, before going through the instructions for creating everything that you see above:

  • The Editor component is in a different module to the Viewer component. Look at the screenshot below and you'll see that by the end of this article, the application will have one additional module:

    The "Viewer-UI" module already contains the tree hierarchy that you see in the top left of the screenshot, while the editor component (which covers the main area of the screenshot above) will be created in a separate module, which you will create in this article, called "Editor-UI". Despite being in separate modules, these two components, i.e., the tree hierarchy and the editor component, are synchronized so that selecting an item in the tree hierarchy causes the same data to be displayed in the editor component.

  • The Edit menu item is in a separate module to the Customer node. Look at the first screenshot above and especially at the two menu items in the tree hierarchy. One of these menu items is named "Edit". That menu item comes from the "Editor-UI" module, while the tree hierarchy is in the "Viewer-UI" module. That's good news, since the editor is now completely replaceable by another editor, which would bring with it its own Edit menu item, which it will be able to register on the Customer node, from outside the "Viewer-UI" module. Hence, the editor is completely replaceable, i.e., not just the editor itself, but also its supporting contextual menu item.

How both of the above flexible features are implemented is the subject of this article. Broadly, the topic for this article is "modularity", which, when you consider the two points above, is a good thing to have for an application, especially as it becomes larger and you want to give your users more flexibility. Maybe they don't want this editor, maybe they want another editor. Maybe a 3rd party vendor wants to provide their own super smart editor and sell it to the users of your application. All this and more is possible with modularity.

Some aspects of modularity should already be clear from earlier parts, since right now we have 4 modules already, meaning that we can easily replace one database vendor or persistence provider with another, simply by removing a module and adding an alternative that does something similar but different. Now we'll address this question of modularity on the UI level, while creating the viewer/editor application shown above. In a future topic, we'll address approaches to how changes can be persisted from the editor to the database. For now, we'll focus on creating the UI only.

Let's get started.

  1. Create a New Module. As indicated by the arguments above, it makes perfect sense to create a distinct module for our editor, for editing our Customer entity. In doing so, we enable all functionality for the editor to be isolated within a particular module, making it exchangeable with another module providing comparable features. So, as before, right-click the Modules node which you'll find in the Projects window for the application, and then create a new module called "Editor-UI".

  2. Create an Editor Component. As before, create a new Window Component (i.e., a TopComponent class) via the New Window Component wizard, using "Editor" as the class name prefix, and then design it as shown in the first screenshot: you'll need three JLabels and three JTextFields. That's where the current Customer entity values will be displayed once the menu item on the tree hierarchy is selected.

  3. Synchronize the Tree Hierarchy with the Editor Component. This is where the magic begins! Despite the fact that the tree hierarchy is in a separate module to where the editor component is found, we need to keep them in sync. Look inside the ChildFactory class and you will notice your CustomerNode's constructor is defined like this:
    private class CustomerNode extends AbstractNode {

    private Customer customer;

    private CustomerNode(Customer customer) {
    super(Children.LEAF, Lookups.singleton(customer));
    this.customer = customer;
    setDisplayName(customer.getName());
    setIconBaseWithExtension(IMAGE_NODE_BASE);
    }

    ...
    ...
    ...

    Look in the call to the super class. That's where you see... that the current Customer object is added to the Lookup. What does this mean, in simple terms? In simple terms, this means that the current Customer object has been added to the application's context. In other words, the Customer entity is now available BEYOND the module where the Customer is found, ALSO beyond the module where the CustomerNode (defined above) is found. Right now, when the CustomerNode is created, the WHOLE application (i.e., ALL the modules within the application) can access that specific instance of the Customer entity!

    Now, imagine if we were to have a Listener on that context? Then we'd be able to listen to the context and identify that a new instance of the Customer is available (which happens whenever a new CustomerNode is created) and then DO something with that Customer! That's what we'll be doing in the next step.

  4. Listen to the Lookup. So, whenever a new Customer is introduced into the context, we want the Editor component to display the Customer's data in the TopComponent. Change the class signature of the Editor to the following:
    public final class EditorTopComponent extends TopComponent implements LookupListener

    Now we have a LookupListener on the Editor component, which will listen to new objects being introduced into the application's context (which will happen, as stated above, whenever a new node is created).

    You will find that, because you have now implemented "LookupListener", you will need to define a method called "resultChanged". Here it is for our scenario:

    @Override
    public void resultChanged(LookupEvent lookupEvent) {
    Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
    Collection c = r.allInstances();
    if (!c.isEmpty()) {
    for (Iterator i = c.iterator(); i.hasNext();) {
    Customer o = (Customer) i.next();
    nameField.setText(o.getName());
    cityField.setText(o.getCity());
    stateField.setText(o.getState());
    }

    } else {
    nameField.setText("[no name]");
    cityField.setText("[no city]");
    stateField.setText("[no state]");
    }
    }

    As you can see, we populate the three fields in our Editor component based on what is found in the current Customer entity, which is found in the context of our application. And it is found in the context of our application because our CustomerNode puts it there whenever it is created.

    The Editor component's "componentOpened" and "componentClosed" methods need to be redefined to make the above possible, as follows:

    private Lookup.Result result = null;

    @Override
    public void componentOpened() {
    Lookup.Template tpl = new Lookup.Template(Customer.class);
    result = Utilities.actionsGlobalContext().lookup(tpl);
    result.addLookupListener(this);
    }

    @Override
    public void componentClosed() {
    result.removeLookupListener(this);
    result = null;
    }

    So, when the Editor component opens, we look for the Customer class. Then we add it to a Result object, which is the Lookup object to which we can listen. Then, back in our "resultChanged", whenever that change takes place, we update the JTextFields. Finally, we detach the listener when the Editor component closes.

    In fact, this is identical to how Tim Boudreau describes editor/viewer interaction in the first part of his 4-part series on Selection Management. Try that tutorial to get used to this NetBeans Platform idiom.Seriously, try it. If you are a Swing developer, that tutorial should change your life significantly (for the better).

  5. Attach the "Edit" Menu Item to the Customer Node. Now that we have the Editor component synchronized with the Viewer component, how do we enable the Editor component to open the current content of the Viewer component? First, remember that when we created the Editor window, via the New Window Component wizard, a new menu item was registered in the layer.xml file. Change that registration to the following:
    <folder name="Edit">
    <folder name="Customer">
    <file name="EditorAction.shadow">
    <attr name="originalFile" stringvalue="Actions/Window/org-editor-ui-OpenEditorAction.instance"/>
    </file>
    </folder>
    </folder>

    And then create the action itself, like this, in a package structure that matches the string shown above in the layer.xml file:

    public final class OpenEditorAction extends AbstractAction {

    public void actionPerformed(ActionEvent e) {
    WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
    public void run() {
    EditorTopComponent editor = new EditorTopComponent();
    editor.open();
    editor.requestActive();
    }
    });
    }
    }

    The above folder structure is unique. The NetBeans Platform, by default, will do nothing at all with a folder structure of "Edit/Customer". Therefore, by default, our menu item will not appear anywhere at all. It will not be displayed so it will never be selected.

    Except... if we make the display of this menu item possible. The layer.xml file where the above is defined is one small contribution to a potentially massive filesystem, shared by all the modules in the application. So, in our "Viewer-UI" module, we can LOAD the menu item defined above. Let's do so now. Open the "Viewer-UI" module and create a new class called "ActionBuilderAction", with all of this content:

    public class ActionBuilderAction extends AbstractAction implements Presenter.Popup {

    public void actionPerformed(ActionEvent e) {
    //Nothing needs to happen here.
    }

    public JMenuItem getPopupPresenter() {
    String folderName = "Edit/Customer";
    FileObject folder = Repository.getDefault().getDefaultFileSystem().findResource(folderName);
    JMenuItem item = buildEditMenuItem(folder);
    return item;
    }

    private JMenuItem buildEditMenuItem(FileObject folder) {
    DataFolder df = DataFolder.findFolder(folder);
    DataObject[] folderKids = df.getChildren();
    Object instanceObj;
    DataObject dob = folderKids[0];
    InstanceCookie ck = (InstanceCookie) dob.getCookie(InstanceCookie.class);
    try {
    instanceObj = ck.instanceCreate();
    } catch (Exception ex) {
    instanceObj = null;
    }
    if (instanceObj instanceof Action) {
    JMenuItem plainMenuItem = new JMenuItem("Edit");
    Actions.connect(plainMenuItem, (Action) instanceObj, true);
    return plainMenuItem;
    }
    return null;
    }

    }

    What's going on here?! Well, look closely at the code. (And then read this interview with Tonny Kohar.) We're reading the "Edit/Customer" folder in the shared filesystem and then constructing (thanks to InstanceCookie) our menu item from that. In "Edit/Customer", we will find the registration of our menu item, defined within the separate "Editor-UI" module.

    Therefore, if there were to be no "Editor-UI" module, but a "Foo" module instead, so long as it had a menu item in its own "Edit/Customer" folder, the menu item would be attached to the CustomerNode, thanks to the layer.xml entries that you see above. And if it had a component listening for the Customer entity, it would be able to do something (edit, probably, but not exclusively, since each module can define for itself what it does with what it listens to) with the Customer entity located in the application's context.

    Finally, we need to bind our special action to the Customer node, together with the Properties menu item that you saw in the first screenshot. Here's how to do that, within the definition of the CustomerNode:

    @Override
    public Action[] getActions(boolean context) {
    return new Action[]{
    new ActionBuilderAction(),
    SystemAction.get(PropertiesAction.class),
    };
    }

    You can see above that the Properties action is one of the default actions provided by the NetBeans Platform. You'll need a dependency on the Actions API to use it. Several other similar default actions are available, which we will use in this series of articles as we need to make use of them. Let's make the Properties action the default action, i.e., the action that will be invoked when a node is double-clicked by the user:

    @Override
    public Action getPreferredAction() {
    return SystemAction.get(PropertiesAction.class);
    }

     

And that's NetBeans "Modularity" in a nutshell. If you understand the above, and can implement it within an application similar to the one described above, you can indeed call yourself a NetBeans Platform developer. Many avenues are then open to you because you are then able to create loosely coupled components that interact with each other independently. And, soon, you'll even be able to integrate these components with OSGI bundles, but that's another story, though watch this space for details.

In the next part, we'll focus on the Editor component and on ways in which it can store its data back into the database.

 

AttachmentSize
fig-1-editor.png37.72 KB
fig-2-editor.png7.39 KB
Published at DZone with permission of its author, Geertjan Wielenga.

Comments

Rocco Casaburo replied on Mon, 2009/02/23 - 8:50am

Hi Geertjan!

 I find your articles very useful, and I am eagerly waiting for next part of this tutorial...

 Thank you for the help that you give out.

 

jiji530 (not verified) replied on Fri, 2009/06/26 - 8:18pm

thanks for your post.perhaps you will like abercrombie,ed hardy,mortgage rates,tiffanysanded hardyIs not it?

Luc Talbot replied on Sat, 2009/10/31 - 3:38am

Hi Geetjan, This is an incredibly good tutorial. I can't locate the next one in the series. I do look forward to it.

Lee Rock replied on Wed, 2010/04/21 - 6:10pm

this is what i'm waiting for.

mahogany dining table | lucite coffee table

Normal 0 false false false MicrosoftInternetExplorer4

We Dada replied on Thu, 2010/07/01 - 4:13am

I like your information. i am always look for this type of interesting and good information Louis Vuitton and Gucci What you provide is very good,i like it so much ,thank you ,the article you write is perfect!

pedro lastorgas replied on Wed, 2010/12/22 - 9:52pm in response to: Lee Rock

We`ve all been waiting for it :)

 Pedro from magos

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.