Creating Context-Sensitive Capabilities for File-Based Nodes (Part 3)

Rather than finding a TopComponent for each Node (as done yesterday), let's share a single TopComponent between multiple Nodes. The benefit is that, well, you'll only have one TopComponent to maintain. You'll simply assign a different ChildFactory to the TopComponent's ExplorerManager, i.e., on the fly, programmatically, as and when needed.

Above, the Node hierarchy in the Details view is built from a ChildFactory registered in the layer. Hence, when a module is added from the outside, it will not provide its own TopComponent. Instead, it will provide its own ChildFactory, registered as an attribute of a virtual file in the layer file. In other words, only the model needs to be provided (i.e., the Nodes), rather than the view, which is shared between all the models. Of course, if a module from the outside needs its own view, it can provide one in its own module, but then it will not be able to integrate with the "Open Window" menu item above. However, preferably, the module from the outside should simply bring its own Node hierarchy with it (via the ChildFactory registration), so that for a minimum amount of work the new Node can be integrated into the application.

How to do this? Well, first follow part 1 and part 2 of this series. Then follow the steps below.

  1. Delete the ".imrc" files in your module. We should probably have defined the "window" key as an attribute in the layer to begin with. Plus, we now won't need to find a TopComponent for each Node anymore. So also delete each TopComponent class that you had defined for your Nodes, i.e., the ones you created via the New Window Component wizard. Then create just one TopComponent, named "DetailsTopComponent". Implement ExplorerManager.Provider, initialize the ExplorerManager, add a BeanTreeView (or whatever explorer view you like), and (as always) associate the ActionMap and the ExplorerManager to the Lookup of the TopComponent.

  2. Now we'll add some attributes to the layer, for each of the two virtual files we registered there, and on top of which the Node hierarchy has been constructed:
    <folder name="Reference">
        <file name="Cruises.imrc">
            <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/no/imr/viewer/customer.png"/>
            <attr name="displayName" stringvalue="Details"/>
            <attr name="window" stringvalue="DetailsTopComponent"/>
            <attr name="childFactoryClass" newvalue="no.imr.viewer.CruisesChildFactory"/>
        <file name="Logbooks.imrc">
            <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/no/imr/viewer/customer.png"/>
            <attr name="displayName" stringvalue="Logbooks"/>d
            <attr name="window" stringvalue="LogbooksTopComponent"/>
            <attr name="childFactoryClass" newvalue="no.imr.viewer.LogbooksChildFactory"/>

    The most important attribute in the context of this blog entry is highlighted above. The "newvalue" attribute will cause a new instance of the registered "childFactory" class to be created. In other words, you now need to create two ChildFactory classes, one for each of your Nodes, as indicated by the "childFactoryClass" attribute above. For example:

    public class CruisesChildFactory extends ChildFactory<String>{
        protected boolean createKeys(List<String> list) {
            //Make a connection to the Cruises database,
            //i.e., Lookup.getDefault().lookup(DataServiceProvider.class)
            //and then call "getData("cruises")" on the interface,
            //but let's add some dummy data for prototyping purposes:
            list.add("cruise A");
            list.add("cruise B");
            list.add("cruise C");
            return true;
        protected Node createNodeForKey(String key) {
            Node node = new AbstractNode(Children.LEAF);
            return node;
    public class LogbooksChildFactory extends ChildFactory<String> {
        protected boolean createKeys(List<String> list) {
            //Make a connection to the Logbooks database,
            //i.e., Lookup.getDefault().lookup(DataServiceProvider.class)
            //and then call "getData("logbooks")" on the interface,
            //but let's add some dummy data for prototyping purposes:
            list.add("logbook A");
            list.add("logbook B");
            list.add("logbook C");
            return true;
        protected Node createNodeForKey(String key) {
            Node node = new AbstractNode(Children.LEAF);
            return node;

    As you can see from the above code, you would bring the data connection with you via the ChildFactory.

  3. Now you need to use the various attributes in your own DataObject to use the registered ChildFactory in the construction of the shared TopComponent:
    public class IMRCategoryDataObject extends MultiDataObject {
        public IMRCategoryDataObject(final FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
            super(pf, loader);
            CookieSet cookies = getCookieSet();
            cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies));
            final String windowAttribute = (String) pf.getAttribute("window");
            OpenCapability oc = new OpenCapability() {
                public void open() {
                    DetailsTopComponent tc = DetailsTopComponent.findInstance();
                    Children children = Children.create((ChildFactory) 
                           pf.getAttribute("childFactoryClass"), true);
                    tc.getExplorerManager().setRootContext(new AbstractNode(children));
            if (windowAttribute != null) {
                getCookieSet().assign(OpenCapability.class, oc);
        protected Node createNodeDelegate() {
            final DataNode node = new DataNode(this, Children.LEAF, getLookup());
            return node;
        public Lookup getLookup() {
            return getCookieSet().getLookup();

As a result of the above code, when you right-click a Node in the viewer TopComponent, if the "window" attribute is defined, the menu item is enabled. Then the registered ChildFactory is assigned to the root context of the ExplorerManager that is implemented in the shared TopComponent.


The childFactoryClass in the file type is a good idea, but sometimes it is necessary to create the keys dependent on the current node selected. Lets say I choose Cruise no. 2009401 in the view, and choose open to see the master-grid, then I would ask the DB for items in a spesific table given the cruise-no. Do you have a suitable solution for that?

Posted by Åsmund Skålevik on May 04, 2010 at 08:56 AM PDT #

@Åsmund: when you create the childfactory you have the context. So you could e.g. make childfactory configurable & call a method to set a property that defines context or change. sthg. like:

pf.getAttribute("childFactoryClass") ).setContext(dataObject);

Posted by Toni Epple on May 05, 2010 at 01:06 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.


« March 2015