Intended Audience
This document was created as a walk-through tutorial on how to create custom plug-in modules that extend the functionality of the base i2b2 Web Client.
The author(s) assume that the reader is an experienced software developer or architect who has some experience programming JavaScript and some knowledge of advanced JavaScript topics such as JSON, AJAX, and using JavaScript toolkit (APIs).
Plug-In Example #1 – Hello World
The first example plug-in is a Hello World example. This example demonstrates the steps needed to setup the directory structure, create a simple plug-in configuration file, register a plug-in with the web client framework, and have an example HTML interface loaded by the framework.
Creating a Plug-in's base directory
Your deployment of the i2b2 web client will have a directory which contains all the JavaScript code related to the i2b2 web client Framework. The default location of this directory is /js-i2b2.
This directory contains the Framework configuration file (i2b2_loader.js) and two directories (cells and hive). Within the cells directory are the directories for the core cells in addition to the plug-ins folder. Within the plug-ins folder we will create a new folder to hold our examples called "examples".
Within the examples folder create a new folder called ExampHello. We will be using the string "ExampHello" as the code that will be used to uniquely identify the plug-in from other plug-ins and cells. For our example we chose to use the abbreviation code "ExampHello" for our aptly-named "Hello World" example plug-in.
Within every plug-in directory there is an assets directory to contain all multimedia assets that are used by the plug-in. All CSS, HTML and image files are contained within this directory.
Create the directory "/js-i2b2/cells/plugins/examples" |
Registering a Plug-in with the Web Client Framework
For the Framework to be aware of our new plug-in, we must add an entry to the module loader configuration file. Within the file /js-i2b2/i2b2_loader.js is a section containing JSON-based configuration information which has the following structure:
// THESE ARE ALL THE CELLS THAT ARE INSTALLED ONTO THE SERVER : [] |
This JSON structure is used to register a list of cells and plug-in modules that the framework will attempt to load if the authenticated user has been authorized to use them (via the data returned from the PM cell during successful login). The above example code listing has information for registering the following cells/plug-ins (in order):
- Project Management Cell (forced to automatically load when the framework is loaded)
- Ontology Cell
- Data Repository Cell
- Plugin Viewer Module (used to manage all non-Cell plug-in modules)
Unless a custom module is going to be running as an i2b2-compliant cell, configuration options must be included in this file using the "forcing" options below:
Configuration Option |
Description |
---|---|
forceLoading: (Boolean) |
Is the module automatically loaded during framework initialization |
forceConfigMsg: (Object) |
This data object is automatically populated to i2b2.CELLCODE.cfg.config when the module is loaded |
These options can also be used to override configuration information that is being returned to the web client framework from the Project Management Cell during login authorization.
To do so we add the following to the configuration that results in the following file with our added configuration information:
Tip
Remember to add a comma to the end of the previous configuration in the list.
// THESE ARE ALL THE CELLS THAT ARE INSTALLED ONTO THE SERVER : [] : [] |
Important
It is important to know that the name provided in the code configuration attribute will be the namespace that mustbe used by your plug-in. the code "ExampHello" resolves to an absolute namespace of thei2b2.ExampHellowithin the JavaScript VM.
Creating a Plug-in's Configuration File
For the Framework to be able to properly load our new plug-in module, we must declare which files comprise the new module. This is accomplished by creating a JSON-based configuration file within the module's root directory. For our example that would be
/js-i2b2/cells/plugins/examples/ExampHello/cell_config_data.js
The file would have the following structure:
// This file contains a list of all files that need to be loaded dynamically :[ :[ |
The configuration file has three main parts to it:
- A list of JavaScript files
- A list of HTML CSS files
- A configuration section.
The files and CSS configuration sections are self-explanatory. All filenames listed will be prepended the with the plug-in's base directory to generate the full file access location used by the framework in loading the files.
Important
It is important to note that all version of Microsoft Internet Explorer limit the number of dynamically loaded style sheets to a total of 31 files. The web client framework uses several style sheets and each cell or plug-in may have dynamically loaded style sheets as well.
An important best practice is to only define one CSS file in your plug-in's configuration file.
To use more than one CSS file in your plug-in, create a CSS file to subsequently load your other CSS files using the @import (file.css) command.
The configuration section contains various pieces of information that are used by the Framework. They are explained below:
JSON Configuration Variable |
Description |
---|---|
config.short_name |
Is displayed in the title tab area of the plug-in viewer's display window. |
config.name |
The title string that is displayed in the plug-in viewer's listing window. |
config.description |
The description that is displayed in the plug-in viewer's listing window. |
config.category |
A list of categories that this plug-in is a member of. All plug-ins must include "plugin" value. If the plug-in does not have its own backend cell then "celless" value should also be present. |
config.icons |
JSON object defining one or more icon files. These files must be located in the assets directory of the plug-in's base directory. |
config.icons.size32x32 |
Filename for 32x32 pixel icon used in the plug-in listing window when in detailed view mode. (The fill must be in the plug-in's assets directory). |
config.icons.size16x16 |
Filename for 16x16 pixel icon used in the plug-in listing window when in summary view mode. (The fill must be in the plug-in's assets directory). |
config.plugin |
JSON object which defines and configures the module as a plug-in. |
config.plugin.isolateHtml |
Boolean, should the framework isolated the plug-in's HTML an IFRAME. |
config.plugin.html |
JSON object that contains information about the plug-in's display HTML. |
config.plugin.htm.source |
Filename for the plug-in's display HTML (in the local assets directory). |
config.plugin.html.mainDivId |
The unique ID of the HTML Element (in the above declared source file) whose contents will be initially displayed. |
|
|
|
|
|
|
Displaying HTML
The first step in the development of any plug-in module is the creation of a user interface. The i2b2 web framework eliminates the complexity of transforming simple HTML screens into an AJAX-based application.
To start, an HTML file must be created and put into the plug-in module's asset directory (/js-i2b2/cells/plugins/examples/ExampHello/assets). For our "Hello World" we will create a file called injected_screens.html and fill it with the following HTML:
<html> |
The text which emphasis added is what we want to display in the plug-in viewer's display window when the plug-in is selected and loaded.
The next step is to configure the plug-in (via the cell_config_data.js file) with the correct HTML file and ID for the HTML Element that contains the initial UI display.
plugin: { |
Since our configuration file has defined a CSS file to load we must create that file for the plug-in module to work properly. We will fully utilize the separate CSS file later in this document but will need to create a blank file (/js-i2b2/cells/plugins/ExampHello/assets/vwHello.css) for now.
Tip
For more information on the configuration files and in particular the plug-in configuration files please see the document called Web Client Configuration Files.
After saving all files, clearing your browser's cache (just in case) and refreshing the screen, you should see the "Hello World – Simple HTML" plug-in in the "Plugins" list window. When the listing is clicked, it should load the plug-in and display the HTML you created above.
Creating Constructor & Destructor Functions
Tip
Although it is not absolutely necessary, it is good practice to create constructor and destructor functions for your plug-in.
The plug-in constructor function is called after the Plugin Viewer has loaded the requested plug-in's initial HTML into the main DOM tree. When the constructor is called it is passed a reference to the DOM node that contains the plug-in's HTML.
If your plug-in contains a destructor function, it is called by the Plugin Viewer before it removes the plug-in's GUI from the main DOM tree.
Be Careful
If your destructor function does not return true then the unload process is canceled.
i2b2.ExampHello.Init = function(loadedDiv) { |
i2b2.ExampHello.Unload = function() { |
Plug-In Example #2 – Tabs and Drag / Drop
Starting from our Hello World plug-in it is easy to build a second plug-in that uses standard tabs display and can accept SDX drop messages. Begin building this plug-in by copying the previous plug-in example into the directory /js-i2b2/cells/plugins/ExampTabs and change the filenames to reflect the naming convention of the new plug-in, resulting in the filenames:
- ExampTabs.js
- ExampTabs.css
The new filenames should be reflected in the cell configuration file found at:
/js-i2b2/cells/plugins/ExampTabs/cell_config_data.js
To register the new plug-in in the module loader configuration file add the following lines:
// THESE ARE ALL THE CELLS THAT ARE INSTALLED ONTO THE SERVER : [] : [] |
In this "Tabs Example" we will have the plug-in read a configuration variable called DefaultTab and use its value to display a specific tab when the plug-in is loaded. This is to present an example of how to use a single variable but the same principle applies if several values are needed. You will see how this value is used later in the section called Setup Standard Display Tabs.
Setup Standard Display Tabs
It is also possible for your plug-in to use standardized tabs to display of more than one page while loaded in the Plugin Viewer window. To do this, you need to add specific HTML code to your display file. The two parts of HTML represent the tab markup and the sub-screens markup.
All markup related to tabs-based functionality must exist within a DIV located at the root level of the loading DIV, in this example the following is needed:
- The DIV will need to have an id value of ExampTabs-mainDiv.
- The tab-encapsulating DIV must be assigned the class yui-navset.
- The tab rendering routines are expecting to find three child nodes, an unordered list assigned a class of yui-nav and a DIV assigned the class yui-content.
<html> |
A line must be added to the plug-in configuration file so that the tabs menu will be recognized and properly drawn by the Plugin Viewer.
plugin: { |
The final change required will be to put the following line into the initialization routine of the plug-in. This example also used the configuration variable set in the module loader configuration file as the default tab shown when the plug-in is loaded.
i2b2.ExampTabs.Init = function (loadedDiv) { |
Add HTML to First Tab
In this example the first tab will be related to data entry. As such, we are going to create a DIV and register it with the SDX subsystem to allow it to recognize when data objects are dropped onto it.
In order for the DIV to be registered, it must have an id attribute, in this example we will call it ExampTabs-DROPTRGT.
<html> > [removed in this example] |
The above HTML contains the following three DIV
- Directions for the screen
- A label for the drop target
- The drop target DIV.
The next step is to create the code needed to initialize and manage the SDX drop operations.
Add SDX Registration Code
In the plug-in's initialization routine we need to register the above-defined DIV to accept SDX drag drop operations for various data types handled by the SDX subsystem.
i2b2.ExampTabs.Init = function (loadedDiv) { |
Add Code for Drop Event Handler
A function needs to be added to the plug-in's main code file to accept the data from the SDX drop event. In this example we are going to be adding the function call doDrop() to the plug-in's root namespace i2b2.ExampTabs.
This function, like all SDX Drop event handlers, must accept an array containing one or more SDX Data Messages. For this example we will only be processing the first message.
i2b2.ExampTabs.doDrop = function (sdxData) { |
The above code performs the following actions:
- saves the dropped data into the data model namespace of this plug-in,
- provides visual feedback to the application user informing them that the drag & drop they performed was successful,
- set a flag within the plug-in's data model namespace to indicate that whatever calculated data is now invalidated (and thus "dirty") because the origin data has changed.
This step, along with processing being triggered on tab change, is the preferred method for managing the data input / processing / output display user interaction by plug-ins.
Register Drop Handler with the SDX Subsystem
Once a handler function is created we can register it to accept the drop events by having the SDX system call the example's local drop event handler instead of using the default handler for that SDX Data Message data type.
To do this properly, we are going to use a simple demultiplexer function that simply calls our real drop handler in the plug-in's root namespace. There are two reasons we do this instead of registering the real drop handler directly:
- To remap the scope of JavaScript's this identifier so that it refers to the plug-in's base namespace
- To save memory
When the SDX system is told to use an alternative drop event handler function, it saves an isolated clone of that function. If the same function is going to be registered to process 10 different types of SDX data then that function would be in memory 11 times (10 in the SDX system + the original copy of the function). This does not have a big impact if the cloned function is a small router function but can have a much larger impact if the drop handler is very large.
The code to map the drop event handler for this example is shown below:
i2b2.ExampTabs.Init = function (loadedDiv) { |
Add HTML to the Second Tab
At this point in the example's development we have three working tabs with the first tab containing text and a target DIV that accepts SDX drop events and saves the dropped information. The next step is to create an output screen to display the results of processing the dropped SDX data.
<html> > [removed in this example] [TAB 1's screen removed in this example] |
Add a Processing Routine to the Plug-in
Now that we have our output defined we can create the code needed to process the previously saved dropped data into the form that will be displayed in the second tab. The processing done by this example is to simply display the information that was contained within the SDX message.
i2b2.ExampTabs.getResults = function () { )[ )[ )[ )[ )[ )[ )[ )[ ) [ |
Cause Processing to Occur on Tab Change
At this point the example, the plug-in has a working input method, a processing routine, and an output screen. All that is left is to do is to capture an event to initiate processing and displaying of the data.
The most user-transparent event we can capture would be the user switching to the results tab. If the data is dirty when the user switches to the results tab then the input will be reprocessed. The code needed to perform in this way operates as follows:
- Create a function and attach it to YUI's tab change event.
- Check to see if the tab that the user has switched to is the results tab.
- See if the plug-in has received any information from a drop event.
- See if the saved data has been processed already (via "dirty data" flag).
i2b2.ExampTabs.Init = function (loadedDiv) { |
Plug-in Example #3 – PDO Example
The third example plug-in adds the concept of communications with a cell in the form of a PDO request. To make this PDO Example plug-in we've taken the Tabs & Drag / Drop Example plug-in and made some changes and additions.
Setup Example
Begin building the PDO example plug-in by copying the previous plug-in example into the directory /js-i2b2/cells/plugins/ExampPDO and change the filenames to reflect the naming convention of the new plug-in, resulting in the following filenames:
- ExampPDO.js
- ExampPDO.css.
Update the plug-in configuration file with the information shown below. This file can be found at:
/js-i2b2/cells/plugins/ExampPDO/cell_config_data.js
// This file contains a list of files that need to be dynamically loaded for this : [ : [ : [ |
The new plug-in was registered with the module loader configuration file by adding the following lines:
// THESE ARE ALL THE CELLS THAT ARE INSTALLED ONTO THE SERVER : [] : [] |
HTML for Data Entry
The first data entry tab was modified to accept a patient record set and an Ontology concept. To do that we make the HTML for the first tab to be as follows:
<div class="ExampPDO-MainContent"> |
Setup Standard Tabs
This plug-in uses the standardized tabs system provided by the Plugin Viewer framework.
i2b2.ExampPDO.Init = function (loadedDiv) { |
Registering DIVs to Accept SDX Drop Events
The first and second highlighted parts in the previous section called "HTML For Data Entry" are the DIVs that will be used to accept SDX Drop operations. To register the first DIVs as Drop event targets the following code was incorporated into the plug-in's initialization routine:
i2b2.ExampPDO.Init = function (loadedDiv) { |
Building the Drop Event Handlers
The SDX Drop messages need to be processed differently depending on the associated data type requiring two separate event handlers to be created in the plug-in's main namespace:
i2b2.ExampPDO.prsDropped = function (sdxData) { |
i2b2.ExampPDO.conceptDropped = function (sdxData) { |
Connecting the Drop Event Handlers
Once the drop event handlers were created they were registered to the proper Drop Target DIVs inside the plug-in's initialization code:
i2b2.ExampPDO.Init = function (loadedDiv) { |
Implementing the Output Options Functionality
To manage the user selecting one or more different types of output an outputOptions configuration namespace was created within the plug-in's data model namespace (i2b2.ExampPDO.model). This set of namespace extensions need to be setup before any other code attempts to access them and as such will be placed at the beginning of the plug-in's initialization code like so:
i2b2.ExampPDO.Init = function (loadedDiv) { |
To update the information in the outputOptions data model namespace we needed to connect the user's interaction with the output checkbox group with a function that performs the changes to the output options. First, a function was created and added to the plug-in's base namespace:
i2b2.ExampPDO.chgOutputOption = function (ckBox,option) { |
The HTML checkbox elements are now updated and connected to the created function via an onChange event handler. The HTML updates are as so:
<div class="ExampPDO-MainContent"> |
HTML for Processing Output
The output screen for this example plug-in consists of two boxes that display the following:
- The XML request sent to the PDO function
- The XML returned from the PDO request.
<div id="ExampTabs-TABS" class="yui-navset"> [Tab |
Processing via PDO Request
The plug-in's processing routine builds a PDO request message and then sends it. Once the AJAX call returns with the response the routine then displays the message request and message response text in the output tab.
The function getResults() was added to the plug-in's base namespace which first checks to see if the dirty data flag is set before extracting information from the Ontology concept that was previously saved.
i2b2.ExampPDO.getResults = function () { |
The routine then checks the outputOptions configuration namespace and generates the output section of the request message.
var output_options = ' '; |
Finally, the query's main filter message is built using data from the saved Patient Record Set and the values extracted from the saved Ontology concept.
var msg_filter = <input_list> |
Once the XML of the filter message has been created a "Scoped-callback" object is created.
- All interactions with the Core Cell Communicators utilize the scoped-callback object.
The scoped callback object consists of two parts:
- A callback function that contains code that will be executed when the AJAX call returns with the result from the cell server
- A JavaScript scope identifier which sets the context that the callback function is executed in (what the value of this keyword will be set to.
// callback processor // errorStatus: string [only with error=true] // errorMsg: string [only with error=true] )[ )[ )[ )[ |
Once a scoped callback object is ready, the GUI is updated to provide visual feedback to the user that something is happening.
Finally, the request is sent to the CRC using the core CRC Communicator object (namespace i2b2.CRC.ajax).
// Remove the waiting status line and show results from AJAX call to Cell )[ )[ )[ |
Connecting Processing to Tab Change
The event which starts the processing of input data is the user switching to the "Results" tab after entering or changing the input data. The code is set during the plug-in's initialization routine and is simply capturing the tabChange event in the YUI tabs object.
i2b2.ExampPDO.Init = function (loadedDiv) { |
Clear Plug-in Data on Unload
It is good practice to clear the data from the plug-in once it has been unloaded by the Plugin Viewer framework by adding the following:
i2b2.ExampPDO.Unload = function () { |