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: d
    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{ @Override protected boolean createKeys(List list) { //Make a connection to the Cruises database, //i.e., Lookup.getDefault().lookup(DataServiceProvider.cla ss) //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; } @Override protected Node createNodeForKey(String key) { Node node = new AbstractNode(Children.LEAF); node.setDisplayName(key); return node; } } public class LogbooksChildFactory extends ChildFactory { @Override protected boolean createKeys(List list) { //Make a connection to the Logbooks database, //i.e., Lookup.getDefault().lookup(DataServiceProvider.cla ss) //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; } @Override protected Node createNodeForKey(String key) { Node node = new AbstractNode(Children.LEAF); node.setDisplayName(key); return node; } }
    As you can see from the above code, you would bring the data connection with you via the ChildFactory.
    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() { @Override public void open() { DetailsTopComponent tc = DetailsTopComponent.findInstance(); Children children = Children.create((ChildFactory) pf.getAttribute("childFactoryClass"), true); tc.getExplorerManager().setRootContext(new AbstractNode(children)); tc.getExplorerManager().getRootContext().setDispla yName((String) pf.getAttribute("displayName")); tc.open(); tc.requestActive(); } }; if (windowAttribute != null) { getCookieSet().assign(OpenCapability.class, oc); } } @Override protected Node createNodeDelegate() { final DataNode node = new DataNode(this, Children.LEAF, getLookup()); node.setDisplayName(node.getName()); return node; } @Override 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.


Read More about [Creating Context-Sensitive Capabilities for File-Based Nodes (Part 3)...