A modular, extensible, customizable platform for healthcare applications
In the preceding post, we saw how to create the beginnings of a plugin for the CareWeb Framework using a Maven plugin archetype. Today, we will continue where we left off, so if you missed the last post you should review it before proceeding.
In a previous post we saw an example of using the CWF’s property editor to modify property settings for a plugin and how these property settings are serialized when the associated layout is saved. Right now if we try to use the property editor on our sample plugin, we wouldn’t see much – just a message to the effect that the plugin has no editable properties. So how do we add properties to our plugin and how do we let the CWF know about them? Let’s add a couple of properties to demonstrate this, one to change the text of the “Hello World!” banner and one to change the background color of our plugin.
First, we need to add an id to the label that displays the banner so we can easily reference it in code. In Eclipse, open the myPlugin.zul web page and add the id lblBanner as shown here. Close the document, saving your changes.
Next, open the controller class, MyPluginController.java. At the end of the class declaration, add a private field variable of type Label with the same name as the id of the ZK label component we want to reference, lblBanner. Recall that ZK will automatically set this variable to reference the matching label component in the zul page. Then add method declarations for a getter and setter for our banner property, following standard Java naming conventions.
At this point we have created the necessary elements for a banner property, at least as far as Java is concerned. However, if we were to invoke the CWF property editor on it, we still would not see it. How do we let the CWF know about the new property? There are still two steps remaining.
First, we must tell the plugin container about the new property since the container mediates the plugin’s interaction with the CWF. The best place to do this is in the onLoad method. Recall that this method is called by the container when the plugin is first loaded (instantiated). It also conveniently passes a reference to the container itself. So, modify the onLoad method of the MyPluginController.java file to include the property registration code as shown. This tells the container that there is a property called banner whose implementation is provided by the controller itself (aka, this). Now you may close this file, saving your changes.
Finally, we have to declare the property in our plugin definition within the Spring configuration file. To do this, open the Spring configuration file for the plugin, myPlugin-spring.xml. Immediately after the cwp:plugin opening tag, insert a cwp:serialization opening and closing tag. This defines the section within the plugin definition where we can now declare properties. Within the cwp:serialization section, insert the cwp:property tag with attributes as shown. Note the four attributes. The name attribute provides a display-friendly name for the property. The id attribute reflects the actual property name. The CWF property editor will match the name to the corresponding getter and setter methods that we created earlier. The type attribute tells the property editor the kind of editor required to modify the property’s value. The CWF property editor recognizes a number of types out of the box, and custom types may be registered as well if needed. Finally, the description attribute provides some informational text that can be displayed to the user.
Save all the changes you have made and fire up the test harness. Select the sample plugin from the selector pane on the left. It should look the same as before. Now, turn on Design Mode, right-click on the sample plugin and choose Properties from the context menu. You should now see the banner property with its current value prominently displayed in the property editor. Note how your property description displays in the bottom pane. Try editing the property value and click OK when done. The banner text should now be updated in the UI.
Now let’s try adding one more property – one to change the background color of the sample plugin. You may be surprised to learn that there is only one step to this. Re-open the myPlugin-spring.xml file and add this entry to the cwp:serialization section, just like you did before. Now fire up the test harness and you should now see both properties.
So why did adding the color property take only one step, whereas the banner property took several? It is because the plugin container provides a simple implementation for a color property that is suitable for many plugins. So the implementation and registration steps are already done for you. Only the declaration step in the Spring configuration file remains. Should this default implementation not prove suitable, you are free to provide your own using the same steps that you followed for the banner property.
There are a lot more attributes and property data types supported by the cwp:property tag than shown here. Take a look at the CWF documentation for more information. As I mentioned earlier, custom property data types are also possible. This is an advanced topic for another day.
The next couple of posts will further extend our sample plugin’s capabilities. Among other topics, we will examine generic and context events.
In this post, we begin a series on how to develop plugins for the CWF. Plugins can range from the simple to the highly complex with varying degrees of granularity. A simple plugin could be a button with an associated action; a complex plugin – an order entry module, perhaps. The CWF provides a couple of avenues for creating plugins. One, which is less commonly used, involves extending one of the base UI element classes to add the desired capabilities. We won’t be discussing this approach right now as it is a bit more advanced. Rather, we will be talking about implementing plugins using the CWF plugin container class, which takes care of the necessary interactions with the UI framework and lets you focus on your plugin’s feature set.
This tutorial on plugin development makes some basic assumptions about you and your environment (I’ve provided some useful links to get you started):
- You have a working knowledge of the underlying technologies:
- You are using the Eclipse IDE.
- You have downloaded and compiled the core CWF project.
The CWF core project provides a skeleton plugin project in the form of a Maven archetype. If you don’t know what that is, don’t worry. Just know that it is a template (wizard) for creating a project. Though you can certainly create a plugin without using it, it is a good place to start.
To create a plugin project using the Maven archetype, go to File | New | Other… in the Eclipse menu and choose Maven Project. Accept the defaults by clicking Next.
In the dialog that follows, be sure the Include snapshot archetypes box is checked. Enter “careweb” in the Filter box to narrow your search. Choose the CWF archetype plugin from the list and click Next.
This brings us to the template dialog where we can provide custom inputs for our plugin. The first three represent the so-called Maven coordinates and uniquely identify your plugin within the land of Maven. The Package input names the Java package that will be created. The property prompts customize class and identifier names (UCC is upper camel case, LCC is lower camel case, LC is lower case). For now, duplicate what you see here, then click Finish.
Now go to Eclipse’s Package Explorer view and open the project named myplugin. It should look something like this. Examining the project’s structure, you can begin to see how the archetype properties impact the naming of various artifacts.
We are going to examine the project’s structure in more detail in a moment, but for now let’s look at the file named “pom.xml” by double-clicking it to open it in the editor. Known as the Maven Project Object Model, this file provides information to Maven about the plugin’s unique identity and its requirements. For now, we are just interested in the former, the identity, in the form of the groupId, artifactId, and version tags. Together, these three values, known as coordinates in MavenSpeak, uniquely identify the plugin. To see how this works, copy the block containing these three tags into the clipboard. We are going to use them shortly.
Now let’s get the plugin up and running in the test harness. Recall in a previous post that I described how to get the test harness running within Eclipse. Review those instructions now if necessary. Open the test harness project (org.carewebframework.testharness.webapp) in Eclipse’s Package Explorer and open its pom.xml file.
Find the dependencies section and add an opening and closing dependency tag. Between those tags, paste from the clipboard the Maven coordinates for your plugin. Close the file, saving the changes you just made.
Now let’s fire up the test harness by right-clicking on the index.zul file under src/main/webapp and choosing Run As | Run on Server. If you don’t see this option, it’s probably because you don’t have the ZK Studio Eclipse plugin installed. This should bring up the test harness in your web browser. (Note: Eclipse has an annoying habit of failing to start a web application on the initial attempt. If you get an error when attempting to start the test harness, right-click on your server runtime in the Eclipse Servers view and select Clean… and then Restart).
From the list of detected plugins, select your plugin. You should see not only the “Hello World” banner and the current date, but also a custom button appears on the tool bar and a custom menu in the menu bar. Try clicking on each to invoke the actions associated with them. Note that the button and menu items are only visible when the plugin is active. If another plugin is activated, they disappear as well.
Now let’s line up what we see in our simple plugin with the corresponding parts of our plugin project. Return to the plugin project in the Package Explorer view. Open the file src/main/resources/META-INF/myPlugin-spring.xml. This is the Spring configuration file for our plugin. First you will note that there are two profiles in the file, one called root and one called desktop. Bean declarations in the root profile are global to the web application. In contrast, bean declarations in the desktop profile are local to the running ZK desktop instance. This means that a ZK desktop has shared access to bean instances generated from the root profile, but gets its own copy of bean instances generated from the desktop profile.
In the root profile, you will notice tags with a namespace prefix of cwp. This represents the plugin definition entry and uses a feature of Spring known as namespace extensions. The cwp namespace extension exists to simplify the syntax required to declare a plugin definition. At the top level is the cwp:plugin tag with several attributes. The id attribute uniquely identifies the plugin. The name attribute provides a more display friendly identifier for the plugin (this is the text that appears in the Layout Designer). The url attribute describes the path to the main web resource for the plugin, which contains the ZK markup that describes its visual appearance. Note the “~./” prefix. This identifies the target as a jar-embedded resource. Such resources are anchored within the jar file at a folder called “web“. Consequently, you will find the referenced file in the plugin project at src/main/resources/web/myplugins/myPlugin.zul. Find and open that file now.
Here you see some simple ZK markup. There is an outer div component with an image, a separator, and two label components within it. Even without a knowledge of ZK markup, you can pretty easily extrapolate these component declarations to what you see in the UI. Let’s look at the file line by line. The first two lines are tag library references, the first for a security library (not used in this plugin) and the second for a library of core functions. Next is the zk opening tag along with several XML namespace prefixes. Next we have the div tag with an apply attribute. The apply attribute wires the zul page to a backing controller. In this case, the controller is referenced using Expression Language (EL) syntax using its bean name (myPluginController). If you refer back to the Spring configuration file, you will find a bean declaration with the same identifier in the desktop profile section. We’ll look at the code for that controller in a moment. Next we have an image tag with a src attribute that is also an EL expression. In this case, the expression references a function in one of the tag libraries. This function returns a url to a requested icon resource. Next we have the first of the two label tags. This one has an EL expression for its value attribute. The “labels.” prefix of the reference indicates that this is an external label reference.
The values corresponding to label references may be found in the zk-label.properties file at src/main/resources/web/myplugins. This file is part of ZK’s internationalization support that we’ll talk more about in a later post
The separator tag is just UI sugar, so we can ignore it. Finally, the second label tag has only an id attribute and no value. However, when we look at the UI this label has a date and time value in it. How did it get there? To answer that question, let’s take a look at the controller class.
Open the MyPluginController class file in the myplugins.controller package. Recall that this class was wired to our zul page by way of the apply attribute on the div tag. The apply attribute referenced a bean declaration in the Spring configuration file that in turn referenced the MyPluginController class. Our controller class is pretty simple. It extends a class called PluginController and overrides five methods. The first, doAfterCompose, is a ZK method that is called after the associated component (in this case our div component) and all its children are created. Here it simply calls the super method and logs some text. This is a common place to insert initialization code.
The four remaining methods in our controller class represent plugin lifecycle callbacks as defined in the IPluginEvent interface. The onLoad and onUnload methods are called once when the plugin is initialized and when the plugin is destroyed, respectively. The onLoad method also provides a reference to the container within which the plugin runs. This will prove useful later as we extend our plugin. The onActivate and onInactivate methods are called each time the plugin’s activation state changes. A plugin is activated when it becomes visible in the UI and is inactivated when it is no longer visible. Notice that the onActivate method sets the value property of a variable called lblDate. But the variable was never initialized in code, so why don’t we get a null pointer exception here? It is because ZK wires the controller to the zul page, associating any uninitialized field variables with ZK components with a matching type and identifier. In this case, ZK injected the field variable named lblDate with the ZK component instance corresponding to the label declaration with a matching identifier. So this code simply sets the value property of the label to the current date and time value, and this value is updated each time the plugin is activated (to see this happen, select on one of the other detected plugins and then switch back to this one – the date and time are updated).
We’ve covered a lot a material this week. At this point you should have a good understanding of the anatomy of a plugin. Next week, we will explore how to add additional capabilities to our new plugin.
Welcome to the third and final installment on CWF layouts. In the previous two posts we focused on composing UI layouts using various features of the Layout Designer. We saw how to add, remove, and reorganize widgets and how to set their property values. None of this is too useful if you can’t save and recall the layouts that you have created. In this post, we look at managing layouts.
Recall the Design Mode menu. We used this to invoke the Layout Designer from the menu item by the same name. Thus far, we haven’t tried the other menu actions. Let’s look at them now, not necessarily in order. The Desktop Properties menu invokes the property editor for the desktop widget. We already saw this in the previous post, though we invoked it from the Layout Designer instead. Clear Desktop does just that – it clears the desktop of all widgets and starts with a clean slate. It provides the starting point for composing a layout from scratch.
So what do we do with a layout once we have created it? Well, we save it. There are two ways to do this. We can save it as private, so that only the author can access it; or we can save it as shared, so that anyone can use it. The choice is yours. Let’s pick up where we left off last week by saving the layout in the state where we left it. Select the Save Layout menu option to save your layout with private visibility.
You are prompted to select a name for your saved layout. You can use the default, if present, or choose a name of your own. Leave the visibility setting as Private. Click OK when you are ready to save it.
Your layout has now been saved as a private layout with the name you have given it. So where did it get saved? The CWF stores layouts (as XML representations as we learned in previous posts) to the underlying property store. The standalone CWF web application used here employs a temporary property store that goes away when the application terminates. A production implementation would provide a non-volatile store for such properties.
So how does one reconstitute a saved layout? There are several ways this can be done, but here we will use the Design Mode menu to demonstrate this. First, to fully appreciate what is happening, let’s clear the desktop by choosing the Clear Desktop action from the Design Mode menu. In doing so, we end up with a blank slate – just an empty desktop widget.
Now select the Load Layout action from the Design Mode menu. You should see the layout you just saved in the list of private layouts in the Load Layout dialog. Make sure that layout is selected and click OK. You should see the layout that you saved reappear in the UI in the exact state when you saved it.
Once you accumulate several saved layouts, you will likely find the need to manage them. This is where the Layout Manager comes in. Select it from the Design Mode menu. It looks similar to the Load Layout dialog, but with a different set of options. Here you can rename your layout, clone it, delete it, or export it. Let’s see what happens when we export it.
Again make sure that the layout you wish to export is selected and click the Export button. Depending on the browser you are using, you should see some indication that a file with the same name as your layout and a file extension of “.xml” was created.
If you want, find the file that you just created and open it for inspection. You’ll see that it is an XML-based representation of our UI layout. Here I opened the exported file using the Chrome browser which formats it nicely so that you can easily see the hierarchical relationships among the widgets.
One nice aspect of exporting layouts is the ability to exchange them with others. Having exported a layout, you could attach it to an email and send it to a colleague, for example. The recipient can simply import the layout and be good to go.
Importing layouts is very straightforward. Simply click the Import button in the Layout Manager, choose the file to be imported and click Upload. You’ll be given the opportunity to rename the layout before saving it.
The remaining options available in the Layout Manager are fairly self-explanatory. Clone creates a copy of a selected layout under another name. Rename simply renames the selected layout. And, not surprisingly, Delete deletes the selected layout. So you can see that the Layout Manager gives you a complete toolset for managing and sharing your layouts.
Before we finish our discussion of layouts, there is one more cool thing you can do with them. Recall from our previous post when we added a new tab to the tab view widget and then in turn added a widget to that tab that we were presented with several choices of widgets that could be added. Now that we have one or more saved layouts, let’s return to that same scenario. First bring up the Layout Designer and select the node corresponding to the Tab View widget. Click the tool bar button and add a new Tab View Pane. Now select the node corresponding to the new Tab View Pane and click again.
This time not only do you see the same set of choices that you saw before, but now you also see your layouts. This means that you can insert entire layouts into your UI just as you would individual widgets. The same rules apply, so that if the top level widget in a layout isn’t allowed as a child for the widget you want to add it to, it won’t appear in the list. So what happens if we add our TestHarness layout to the newly created tab? Try it and see. Select the TestHarness layout from the list and click OK. The result may look a bit confusing because we just put a copy of our current layout onto a new tab in that same layout, but if you study it carefully you’ll see that it makes sense.
In the Layout Designer you will see a node immediately below the new Tab View Pane that is labeled Linked Layout followed by the layout’s name. Below that you see the widget hierarchy that reflects the structure of the layout. One thing you may find confusing is that if you try to modify any of the widgets belonging to the layout we just added, you will not be able to do so. To understand why this is you need to know the difference between a layout that is linked vs. one that is embedded. When you insert a layout as we just did, it is by default inserted in linked form. This means that we are really just inserting a link to the layout, not the layout itself. If someone makes a change to the original layout, that change will be reflected in the layout that references it. In contrast, embedded layouts represent a copy of the original layout. So a layout that is embedded can be freely modified and such modifications have no affect on the original (the converse is also true). So how do we insert an embedded layout? By converting a linked layout to embedded form.
To do this, select the node corresponding to the linked layout, then click the tool bar button to open its property editor. Set the Linked Layout property value to false and click OK. You’ll see the TestHarness layout now labeled as embedded, but otherwise it looks the same. Try selecting one of its child nodes. You’ll see that the tool bar options for manipulating the associated widget are now active. Note that if you switch an embedded layout back to linked form, any changes that you made to the layout while it was embedded are lost.
Hopefully you can appreciate the power and flexibility of layouts. In future posts, we will see how you can choose which layout appears when a user logs into a CWF-based application.
Last week we began our discussion of layouts in the CWF. We left off in the midst of describing the capabilities of the Layout Designer. Today we pick up from there. Recall that we were analyzing tool bar features from left to right. We saw how to use the clipboard feature set and how to add and remove widgets from the layout. The next group of tool bar buttons is a series of arrows. These may be used to change a widget’s position in the UI hierarchy. Like other tool bar buttons, they are aware of the allowable actions for a given widget and will be disabled if the associated action is not allowed. A widget type that supports all of these actions is the menu item.
Let’s try it on the menu item labeled Logout Application by first selecting that node in the Layout Designer. You’ll notice that all arrow buttons are now enabled.
Let’s first promote the menu item by clicking on the left arrow button . Notice that the menu item has now moved under the main desktop menu. You can also see the change in the UI itself as the menu item now appears to the right of the Actions menu in the menu bar.
Now let’s move this same menu item back to its original position by clicking the right arrow button to demote it. Notice that while it has been demoted, it is not in its original position relative to its siblings. You can easily fix this using the to move it into place. As an alternative, try dragging its tree node from its current position and dropping it on the node labeled Refresh.
This illustrates another nice feature of the Layout Designer – you can drag any node and drop it anywhere in the tree as long as the move is considered allowable. Try it by dragging the node labeled Splitter View Pane – Pane #2 and dropping it on Splitter View Pane – Pane #1. Notice how the nodes switch positions in the tree and the positions of the panes in the UI change accordingly.
Moving to the next button grouping in the tool bar, we see a pair of buttons, the first one resembling a gear. This button invokes the property editor for the selected widget. Let’s try it on the Desktop widget.
First, select the Desktop node (the one at the very top level of the tree) in the Layout Designer. Then click the gear button in the tool bar (alternatively, you can simply double-click the node itself). This brings up the property editor for the widget. On the left are the property names and on the right, their current values. To change a value simply click it. Depending on the type of property, you may type the new value or you will be given a set of choices or you may see a whole new property editor appear. Let’s change the Application Title property by first clicking its value, then entering the text of your choice there. Notice that the Apply and Restore buttons are now enabled, but no change has yet occurred in the UI. Clicking Restore would simply restore the property’s value to its last committed state. Instead, let’s commit our change by clicking the Apply button. Now you can see the change you made in the UI itself.
The Application Title is an example of a simple text property. There are many different property types supported by the CWF (in fact, one can create new property types and property editors for them, but this is an advanced topic saved for a later post). Let’s look at a property type that has fixed choices. The Application Icon property is an example of this. It is an image property type. Try clicking its value in the right panel.
Now we see a very different result. This time we are presented a list of icons from which to choose. Pick your favorite and click Apply. The selected icon will now appear in the title bar of the application.
Some complex property types require special custom editors. The Menu Bar property is an example. Try clicking its value in the right panel.
This brings up yet another type of editor, this one for managing the menu items belonging to the menu bar. Note that all of the actions you can do here (adding menus, promoting and demoting menus, modifying menu properties) can also be done directly from the Layout Designer itself. If you are doing a lot of changes to the menu structure, you will likely find it more efficient to do them here. Feel free to experiment with the menu editor. You will find a similar looking editor for the Tool Bar property, but without the hierarchical orientation. When you’re done, close the property editor to return to the Layout Designer before moving on.
Having sufficiently explored properties, let’s move on to the next tool bar button, the one that looks like this . Click it and you will see an informational dialog that reveals various metadata about the selected widget. This can be useful to determine what version of a widget you have, for example.
Finally, we have arrived at the last group of tool bar buttons. The first is a toggle that controls whether a widget is brought to the front of the UI when it is selected in the Layout Designer. By default, this behavior is enabled, but if it annoys you, you can turn it off. The final button simply refreshes the tree view. You probably won’t need to do this since this view is synchronized with the state of the UI, but it is there if you find the need for it.
One thing you may not have noticed is that the Layout Designer dialog is not modal. That is, you are free to directly interact with individual widgets in the UI while it is still open. This means you can easily move back and forth between the UI and the designer as you compose your layout.
To see how this works, try right-clicking on the tab of a tab view pane widget. This brings up the popup context menu like the one we saw in last week’s post. Note that all of the actions listed here can also be done from the tool bar of the Layout Designer. Pick the Properties action and edit the Tab Label property to the text of your choice. Now click the OK button to commit the change and close the dialog. You should see your change reflected not only in the UI, but also in the corresponding node in the Layout Designer.
As you can see, there are a lot of ways to compose layouts. Next week we will see how to save, manage, reuse, and share the layouts that you create.
Last week I demonstrated how to get a minimal implementation of the CareWeb Framework up and running. This week we’ll take a look at how to create and manage layouts. If you want to try these features out as you follow along, refer back to last week’s post. I originally intended to cover this topic in a single blog post, but I realized that there is a lot to cover, so I’m going to split it across consecutive posts. This week I’ll focus on the design mode feature of CWF.
One of the strengths of a modular framework is the flexibility to configure the application in a manner that best fits individual workflows. The CWF supports this flexibility in the form of layouts. A layout is simply a snapshot of a user interface configuration, represented internally as an XML document. Layouts can be persisted to the underlying datastore (for the minimal CWF implementation, this is an in memory datastore that goes away when the application instance terminates) and can be reconstituted into an exact replica of the user interface from which it was derived. Layouts can also be exported to and imported from external files, providing interesting opportunities for sharing layouts among collaborators.
Let’s begin exploring layouts by activating a special mode, design mode, in a running CWF application instance. Access to design mode is controlled by a security privilege (PRIV_CAREWEB_DESIGNER). This privilege is already granted to the default user for the minimal implementation. If you are using another implementation of the CWF, you may need to grant yourself this privilege in order to access this feature. Assuming you have the necessary privilege, you should see a top-level menu item in your CWF instance that looks like a wrench.
Click the check box to activate design mode and reveal several hidden menu selections. The wrench icon also turns green to indicate that design mode has been activated.
Notice also how activating design mode changes the appearance of the user interface widgets. They are dimmed with overlying labels identifying the type of widget. While you may still navigate the user interface as you normally would, the widgets themselves are disabled.
Right-clicking on a widget produces a popup context menu for manipulating that widget. For example, right-click on the widget labeled Current User Header and you will see this popup menu. From this menu you may delete the widget from the UI, perform clipboard-related operations, view and edit the widget’s properties, or reveal metadata about the widget.
Before we explore these options, let’s return to the design mode menu by clicking on the wrench icon and selecting Layout Designer.
The Layout Designer provides a hierarchical view of the current user interface with the parent-child relationships of the individual UI widgets reflected in the structure of the tree. The Desktop widget is always at the root of this tree and each node of the tree represents a single UI widget. The tool bar at the top of the dialog provides access to operations specific to the selected widget. Most of these operations are the same ones you saw in the popup context menu above. When you select a node from the tree, the corresponding widget is brought to the forefront of the UI if it is not already (this option can be toggled on and off from the tool bar) and is briefly highlighted. The tool bar buttons are also updated to reflect which ones are applicable to the current selection.
To see how this works, let’s select the tree node labeled Tab View.
You’ll see a brief flash of yellow highlighting the selected widget. Let’s take a closer look at the tool bar. From left to right, the first grouping of buttons relates to the internal clipboard. The first two buttons of this group are cut and copy operations, respectively. Cut places the widget and all of its children into the clipboard and removes them from the UI. Copy does the same but does not remove the widget. Try clicking the button. Notice how the tab view widget and all of its children have disappeared from the UI and from the Layout Designer tree. Now let’s put it back. Select the Splitter View Pane – Pane #2 node, which was the original parent node of what we just moved to the clipboard. Notice that the paste button is now active. Click it and you will see the UI restored to its previous state. The paste button is sensitive to what is in the clipboard and what widget is currently selected. A widget can restrict what type of widget may be its parent and what type of widget may be a child. If you select a widget that does not allow the type of child that is in the clipboard, the paste button will be disabled.
So what is the last button in the clipboard group – the one that looks like a magnifying glass ? Click it now and you will see the current contents of the clipboard in the Clipboard Viewer. Remember at the beginning of this post that I mentioned that a layout was represented internally as an XML document? This is the XML representation of the widget we copied to the clipboard along with all of its children. Each tag name represents a widget’s unique name and the associated attributes the property values for the widget. So when we save a layout, whether it is internally to the underlying data store or externally as an exported layout, this is the format that is used.
Let’s close the Clipboard Viewer and return the the Layout Designer. The next grouping of tool bar buttons are for adding and removing widgets, respectively. Removing a widget is similar to the cut operation we performed earlier except that nothing is copied to the clipboard. Adding a widget is much more interesting. Before we do this, a few words about container widgets are in order. A container widget is a widget that may contain other widgets (child widgets). The tab view widget is an example of a container widget. A tab view widget may contain any number of tab pane widgets. A tab pane widget is also a container widget but, unlike a tab view widget, it is less particular about what type of widget it may contain. Many widgets are not of the container type (for example, a button widget) and cannot accept any children at all. The CWF designer is aware of these restrictions and will only present options that are appropriate for the widget type you are manipulating.
So let’s add a new widget to our UI. Make sure the Tab View node is selected in the Layout Designer. Click the button on the tool bar to add a widget. You will now see this dialog. Note that because a tab view widget can only contain tab pane widgets, this is the only choice you are given. Click the OK button to add the new tab pane. You should now see a new tab labeled “New tab” in the UI and a corresponding node in the Layout Designer.
Let’s add something to the new tab. In the Layout Designer, select the node corresponding to the new tab you just added. Note how the new tab flashes yellow in the UI. Now let’s add something to the new tab. Click the button on the tool bar. The dialog to add a widget appears again, but this time with a very different set of choices. Let’s add the Current User Header widget to the new tab. Select that widget from the choices and click the OK button. The new widget now appears in the UI with a corresponding node in the Layout Designer. Select the widget’s node in the Layout Designer and notice how the button is now disabled. The Current User Header widget is not a container widget, so we can’t add anything to it.
We’ve covered a lot in this post and there is a lot more left to discuss. Next week I’ll pick up where we left off.
In future blogs, I will be talking about specific features and capabilities of the CWF. Before we do this, it would be useful to have a working version of the CWF, albeit a minimal one, to try alongside the examples. So we are going to fire up the sample web app that comes with the core distribution. This web app uses basic implementations for things like security services, property storage and event management. It is enough to get a good sense of what the CWF can do. Later, as we venture into more advanced topics, we’ll want a more serious implementation with a real EMR underneath. At that point, we will talk about running the sample web app on top of one of the supported open source EMRs (currently, VistA, RPMS, and OpenMRS).
There are several ways to launch the sample web application. I’ll describe 3 methods. Choose the one that suits you best.
This is the simplest technique and the fastest way to get the CWF running. This method requires that Java be installed on your computer. If you need to install it, you may get it from here.
- Download the standalone web app here.
- Run the following command in the same directory where you saved the app: java -jar cwf-testharness.jar
- Open your favorite browser and enter the following url: http://localhost:8080/standalone
- If all goes as expected, you should now see the login screen.
This is the best way to get the sample web app up and running if you are a programmer and want to explore the CWF from that perspective. While other IDE’s that support Java can be used, we use Eclipse and these instructions assume that you are too. You will need an Eclipse environment that has plugins installed for git (to check out the source code from GitHub), Maven (for building the web app), and ZK Studio (for ease of debugging ZK code). I recommend downloading Spring Tool Suite, available here, an Eclipse environment preconfigured with all the necessary plugins, except ZK Studio. To install the latter, use ZK’s update site specified here.
Once you have Eclipse running, perform the following steps:
- From the Git Repositories view, clone the carewebframework-core repo from GitHub.
- From the File menu, choose the Import.. submenu.
- Choose Existing Maven Projects as the source.
- Enter the directory where you cloned carewebframework-core.
- From the Package Explorer view, open the org.carewebframework-parent project.
- Right-click on the pom.xml file at the top level of the project and choose Run As followed by Maven clean.
- Repeat step 6, this time choosing Maven install. This will take a while and should build all subprojects.
- From the Package Explorer view, open the org.carewebframework.testharness.webapp subproject.
- Open the src/main/webapp folder and right-click on the index.zul file, choosing Run As followed by Run on Server.
- If you have not yet set up a web server entry, you will be prompted to do so now. Simply follow the prompts.
- Once the web app finishes initializing, you should see the login screen in whatever browser you have configured Eclipse to use.
If you still want to build the CWF source, but prefer the command line, this approach is for you. You will need to have Maven installed on your computer. You may download it from here. From the command line, follow these steps:
- Clone the carewebframework-core repo from GitHub. There are several ways to do this. GitHub provides guidance on this.
- Change to the top-level directory where you cloned the repo in the previous step.
- Enter the following Maven command: mvn clean install. This will build all the CWF core subprojects.
- Under your current directory, switch to the subdirectory org.carewebframework.testharness.webapp.
- Enter the following Maven command: mvn tomcat:run-war.
- Finally, enter the following url in your favorite browser (note that the portion after the hyphen will depend on which version of the CWF you are using): http://localhost:8080/org.carewebframework.testharness.webapp-3.1.0-SNAPSHOT
- You should now see the login screen.
Whichever method you choose, you should see the following login screen:
Go ahead and login using the default username and password and start exploring. Next week, I’ll be talking about creating and manipulating layouts and you will be ready to follow along with your own running version of the CWF.
Welcome to the second installment of the CareWeb Framework blog. This week I’d like to talk about the overall architecture of the framework since a basic understanding of this is important to fully leverage its capabilities. The CareWeb Framework (CWF) is a tiered architecture that spans the presentation and service layers. It makes no assumptions about the underlying data storage and access methodologies. Using JDBC for data access? Or SOA with an enterprise service bus? Not a problem since the framework sits above these lower tiers. Rather, the CWF focuses on the user interface and the services it consumes.
The following graphic is a favorite of mine since I think it gives the best visual overview of the CWF and its structure.
The CWF occupies the top two tiers, its native components highlighted in green. The bottom tier, labeled “External Services“, represents resources unique to your operating environment that may be consumed by the upper tiers. The middle tier, “Internal Services“, represents plug-in services that live within the CWF. These often provide an abstraction between the physical implementation in the lower tier and the consumers in the upper tiers, such as providing an implementation-independent representation of domain objects (like patient, user, encounter) derived from the underlying data store. The uppermost tier, “User Interface“, also supports pluggable services. The main distinction from those in the internal services tier is that when invoked they may visually manifest themselves in the user interface and typically provide for some sort of user interaction. For example, a patient selection service can present a selection dialog from which the user may choose a patient. In fact, most services in the upper tier have middle tier counterparts as well. The patient selection service is a good example where the interactive components live in the upper tier while the APIs they invoke occupy the middle tier.
Finally, we have plug-in widgets in the upper tier. These are visual elements that plug into the CWF’s layout framework. A plug-in widget can be as simple as a button or as complex as an order-entry module. The CWF provides several useful general purpose widgets out of the box. Some of these are container widgets designed to help organize other widgets. Tab and tree view widgets are examples of these. Others, like button and menu widgets, can be associated with specific actions. While these are useful, they are obviously insufficient in and of themselves. This is where custom widgets come into play. The CWF provides a software development toolkit (SDK) with templates for creating custom widgets and a test harness for, well, testing them. Once created, a custom widget is no different from a native widget as far as the CWF is concerned.
The CWF provides several native services (the green boxes in the diagram) that provide crucial support for plug-in services and widgets. Context management, borrowing heavily from the HL7 CCOW specification, permits sharing common contexts (such as the currently selected patient) in a collaborative fashion. Event management provides a simple yet powerful subscribe/publish event model that can be used for intra- and cross-application communication (the chat plug-in provided with the CWF distribution demonstrates the power and flexibility of this event infrastructure). Component registration allows a plug-in to declare its presence and be discovered by other plug-ins in the environment. The help subsystem provides the capability of accessing help content supplied in one of the supported formats in context-sensitive and context-independent modes. Theme support allows users to select from alternative visual themes. The layout designer allows users with the appropriate permissions to create and persist user interface layouts built of available plug-in widgets while the layout manager provides tools for managing layouts so created.
All of these myriad capabilities will be addressed in greater detail in future blogs. For those who can’t wait, I encourage you to read the documentation downloadable from http://www.carewebframework.org.
Welcome to the first installment of the CareWeb Framework (CWF) blog. I plan to publish a new blog about once a week on a topic of interest related to the CWF. This first blog is essentially an evolutionary history of the CWF. Future blogs will get into the nuts-and-bolts of the CWF and how to leverage its power to create modular, web-based clinical applications.
My journey with modular frameworks started about 15 years ago as an informatician with the Veterans Health Administration (VHA). VHA had just released its computerized order entry system, CPRS, to the field with mixed reviews. While subsequent iterations of the software saw vast improvements in usability, one central frustration persisted: the architectural design was monolithic and closed, limiting its extensibility and customizability. With the support of a consortium of like-minded VA medical centers, I took on the task of creating a more open, modular and extensible design for CPRS. The result was the VistAtion Framework. Leveraging Microsoft’s COM specification to achieve modularity, the VistAtion Framework enabled us to refactor CPRS into discrete pluggable modules at both the presentation and service layers. With an integrated user interface design capability, UI plugins could be arranged into user-customizable layouts. New modules could be created and seamlessly integrated into the application. Services such as context and event management facilitated the coordination of plugin behavior within the application.
The VistAtion Framework, while a technical success, never achieved traction within VHA. It did enjoy a resurgence a few years later after a group of former VHA employees, myself included, refined the design and produced a commercial product, the VueCentric Framework, under the auspices of a newly formed company, Clinical Informatics Associates, Inc. The VueCentric Framework with modular components derived from CPRS and custom components providing additional functionality for clinical domains not covered by CPRS became the electronic health record for the Indian Health Service (IHS), known as the RPMS EHR. Now, under the stewardship of Medsphere Systems Corp., the VueCentric Framework continues to be used in over 200 healthcare venues throughout the IHS.
Six years ago, I had the opportunity to return to where my career in informatics began in the early 80’s – the Regenstrief Institute. The Institute has a 30-year history of creating clinical applications and then studying the impact of specific computerized interventions on patient care. Many of the technologies underlying these applications were aging or deprecated. Faced with the task of re-architecting 30 years worth of software, adopting a modular approach again made sense. However, the requirement that it be done in a web environment and the desire for it to be open source presented unique challenges. Fortunately, we were able to identify several foundational technologies in the open source space that we could leverage to do much of the heavy lifting. The result was the release four years ago of the first iteration of the CareWeb Framework along with plugins that provide various views of clinical data residing in our statewide health information exchange. This was followed two years later by the release of a framework-based version of our computerized order entry system, the Medical Gopher.
We continue to develop and evolve the CWF. One such effort has been to separate functional components that are specific to our domain from the framework itself and to create adapters for other domains. We currently have ports for OpenMRS, VistA, RPMS, and SMART. Ultimately, as unified models for clinical entities mature, I envision greater independence from the underlying clinical information system.
Last year we released into open source version 3.0 of the CareWeb Framework under the MPL 2.0 license. We are hopeful that others will derive benefit from a modular approach to clinical application design. We are equally hopeful that having a common framework will promote collaboration and innovation to the benefit of all.