Reusing JDesignerPro Objects


One of the advantages of building applications in an object oriented language is that it is relatively easy to create re-usable modules which reduces unnecessary duplication of code. Another reason for modularizing your applications is that it allows for cleaner interfaces between each part of it. This results in not only an application that is easier to understand but also one that is easier to debug.

JDesignerPro is designed to be completely object oriented. BulletProof has built in a feature which allows you to reuse modules as though they were complete Java components. You can make any application screen and import it back into the Object Tree for dropping into other applications later. Typically you might build a search panel, result list, grid or chart that you expect to reuse or even an entire interface.

The Object Tree's custom Classes folder is where you put your own finished application modules for reuse within other applications. Generally it is good practice to create finished classes for things that you will commonly use and then place them on larger application screens.

Two main benefits are smaller overall application size and modular design. This makes it faster for apps to load into the Application Builder. You also only need to work on one small part at a time instead of the whole app.

The Problem

Because the Application Builder is a true WYSIWYG development environment, loading of your applications can take longer as the Application Builder creates instances of objects and even loads small datasets into databound controls. As your application grows this process takes longer and longer. If you are creating distinct modules that are several hundred KBytes or more we recommend breaking your application into smaller modules.

If you are starting a new application from scratch and expect it to be relatively complex, we recommend either of these choices:

  1. Instead of using numerous sub-tabs by splitting one of BulletProof's Tab Panel objects, use Top level tabs. A Top level tab is a menu tab, like the ones that the Application Builder and Enterprise Server sit on. For your own application it is always more efficient with respect to development time and client download time to use Top level tabs. If need be, you can pass data and events between screens Top level tabs.
  2. If you must use sub tabs in a Tab Panel object, start right away by planning how many sub tabs you expect to create and how many objects on each. If there are more than three sub tabs expected with numerous components, create the contents of each as a separate module, then import each into the Custom Classes folder in the Object Tree. Create a new application with the sub tabs and simply drop each Custom class on it's appropriate sub tab. See part 4. below for creating the interactions.

Below is a detailed explanation of how to do this important operation with an existing larger application.

1. Breaking down an existing large application into smaller modules

You'll probably notice that most large apps consist of a tab panel with many panels each having at least one databound component. Generally these are hooked to a ResultList. It's very easy in JDesignerPro to add too many tabs with more databound components. Since you may want to that, there is a more efficient way to handle large modules.

Instead of building one large module with many distinct objects on numerous sub tabs, the contents of each sub tab can become a module in itself and the overall application becomes an umbrella for all of the smaller ones residing on the tab panels. This takes advantage of one of JDesignerPro's greatest strengths - the ability to import any finished JDesignerPro module into the Object Tree then reuse it in another application.

Lets take a specific example. Say we have an application that consists of a ResultList and a Tab Panel split into four tabs. On the first two tabs we have Columns components. On the second two we have DetailGrids. Depending on how many columns are in the Columns component this application could easily be 350Kb when referring to the .jdp file. Subsequently it will take some time to load it each time in the Application Builder.

Instead lets break it up into 5 separate applications. The first would be an application made of just the contents of tab 1. The second of the contents of tab2, the third tab 3 and the last tab 4. To do this with an existing application follow these steps:

  1. Open your existing application then choose Save Application As from the popup that displays when you press the Save button.
  2. Choose a name that will represent the contents of the first tab. Now drag the first panel off the Tab panel and drop it on Main.
  3. Remove everything but that panel from the application. Lastly center the remaining panel by adjusting its Pos property.
  4. If there are buttons on this remaining panel that refer to the getSelectedKey() method of the ResultList, remove the interactions. Finally save this app and do a final build.

Do the same for the other tabs in your existing application. Later we'll replace the Interactions removed earlier. The modules' events are exposed in the Interaction Manager just as any other component. See part 4. below.

You now have several smaller modules that contain only the functionality of a single tab. In the future, working on these applications is very efficient as they are relatively small and load quickly into the Application Builder.

2. Importing the smaller applications into the Object Tree

Once we have built these applications we need to make them available as modules in the Application Builder. To do this we need to import them into the Object Tree. You'll notice that the Object Tree includes a folder named 'Custom Classes'. This is a place holder we have made for just this purpose. Of course you can create your own folder or even a subfolder simply by clicking on the folder you want to add the new one. Here are the steps:

  1. Click on the 'Custom Classes' folder and you'll see a popup menu. Choose 'Import Class'.
  2. When the file picker appears find each of your 4 smaller applications in the directory and double-click on them. They will each be added to the Custom Classes folder.
  3. Now close all the branches of the Object Tree and press the Cancel button at the bottom of the Import dialog. This will cause the Application Builder to save the Object Tree with the new classes added. You can also manually save the Object Tree by clicking twice on the root icon.

You now we have the smaller application sections and have them imported into the Object Tree. Once in the Object Tree they can be dropped onto the screen like any other object.

3. Building an umbrella application

The purpose of the umbrella is to create a sort of shell that holds each of the other modules in tabs, instead of having one large application. Here's how we create the umbrella shell:

  1. Using the initial application example with the ResultList, the tab panel and the 4 tabs, first we remove each of the panels of the Tab Panel by clicking on them in the Layout View and choosing Remove Object
  2. Open the Custom Classes folder of the Object Tree and drag the first module that you imported and drop it on the Tab panel in the Layout view.
  3. You'll see that not only has it added it to the Tab panel but you can also see it has created an instance of the component on the tab panel.
  4. Do the same for the other three. What you end up with should look exactly like what you started with except now it is truly object oriented.

4. Joining the pieces back together

Lastly we need to think about the interactions between the umbrella app and the 4 smaller ones. The loading part is easy. Follow these steps to finish the job:

  1. In the umbrella app click on the Result List in the Layout View and press the Interaction Manager button.
  2. In the Add New Event list, open the object that represents the custom component you placed on the first tab. Here you will see all the methods of that module just as you did when you created the module initially.
  3. Choose the Columns1Load method and give it ResultList1.getSelectedKey() as the parameter. Now do the same with the other 3 custom components that reside on the tab panel. Now we have recreated the interactions for loading your data into the components.

Now the tricky part. We need to hook up any buttons that exist on the custom modules. Here we have two choices: (A) We could have one set of buttons on the bottom of the umbrella app that when pressed will call the update, insert and delete methods on all the sub tabs, or, (B) We could have buttons on the bottom of each of the custom components that do only the updates for that component. Here we leave it up to you to decide the best way for your app to function. Generally this will depend on how it worked before you broke it down into smaller pieces.

Obviously if you have the buttons on the umbrella app it's fairly trivial to hook them up to each of the custom components. You do it in much the same way you did with the load methods. To do it for the case of where you have the buttons on the custom components is a little more involved.

The problem is that in these apps we do not yet have a handle to the umbrella app and therefore we cannot access the Result List of that app. In fact we don't want to rely on that component being there as these modules are supposed to be re-usable.

So here is the solution. It consists of several parts. All of the following refers to changes to be made inside the custom components, not the umbrella app.

1. Lets open up the first of the 4 custom components in the Application Builder.

2. Declare a global array of String to save the last value of the key passed to the load method. We do this by clicking on the Columns component and pressing the Edit Source button. Outside the last curly brace ( } ) type the following:

String[] globalKey;

Then in the Columns1LoadMain method on a new line just after the first curly brace ( { ) enter the following:

globalKey = key;

3. Now we have saved whatever key value is passed into the load method each time it is called. We can use this value to pass to the update, insert and delete methods when the buttons on this component are pressed rather than using getSelectedKey() from the ResultList. The reason being that from within this custom component we don't necessarily know about the ResultList component.

All that remains is to create or modify the existing interactions for the insert, update and delete buttons.

4. Wherever you presently have ResultList1.getSelectedKey() you would simply edit the source and change it to globalKey. When creating a new interaction you would do the same thing. Locate the InsertRow method and drag it to the appropriate ACTION_BTN event and instead of using ResultList1.getSelectedKey() as the parameter you just edit the source by clicking on the source tab and replace <String[]> with globalKey.

5. Optimizing your umbrella application

Note: This optimization is only possible if you have the buttons for each tab within the custom component and not just one set of buttons on the umbrella app.

If you have many tabs it is inefficient to call the load methods of each of the components on the tab tab if only one is being displayed. It may be better to just call the load method of the panel currently selected. Of course you would also need to trap when the tab panel selection is changed by the user and then call the load method for that custom component.

For the first case we simply put an if statement around each of the interactions on the Result List. Click on the Result List and press the Interaction Manager button. Select the first load method and click on the Source tab. Now add the following line before the existing code:

if (TabPanel1.getSelectedIndex() == 0) {

and add a curly brace ( } ) after the existing source. This has the effect of saying that if the first tab is selected when the ResultList is clicked on then call this load method otherwise don't. Do the same for all the other interactions making sure you change the panel number to the appropriate value (i.e. ==1, ==2 and so on).

For the second case we need to trap when the user clicks on a tab of the tab panel. The event that is fired when this occurs is the WINDOW_EXPOSE event. Select the Tab Panel in the Layout View and click on the Interaction Manager button. Now find the load method of the first custom component and drag it to WINDOW_EXPOSE. Then choose the parameter to be ResultList1.getSelectedKey(). Lastly edit the Source and add the following code just as we did above:

if (TabPanel1.getSelectedIndex() == 0) {

Do the same for all of the custom components being sure you get the right panel numbers for each custom component otherwise the wrong tab will load. i.e. tab 1 will load when tab 2 is selected and so on.

The overall solution is more efficient in terms of data being transferred across the network.

Conclusion

You end up with the identical application that you had before we broke it up, with two important improvements. You have made it much much faster and easier to work with a smaller piece of your application rather than having to work with the whole thing every time you want to edit it. Secondly you have created modules that can now be used in other screens where necessary. If you continue to think about how you can modularize a screen before you begin to construct it you will become much more efficient at building solutions with JDesignerPro while realizing true object re-use.