This JavaScript project aims to provide the backbone for Dicoogle Web UI plugins. The essence of this architecture is that Dicoogle web pages will contain stub slots where plugins can be attached to.
Note: These details are only relevant to developers of Dicoogle and its web app. To learn how to develop web UI plugins, please skip this section.
The project can be built by calling npm install
. On Dicoogle, simply install dicoogle-webcore
as a dependency and import
(or require
) the package module. Then:
- Place
<dicoogle-slot>
elements in the page. They must contain a unique slot id attributedata-slot-id
. - Invoke the module's
init()
to initialize the module. It will automatically detect slots, as well as fetch and attach plugins. This method should only be called once. New slots attached dynamically will be automatically filled. - In order to know what menu plugins are available, invoke
fetchPlugins('menu'[, callback])
.
The optional web component attribute data-plugin-name
can be passed to the <dicoogle-slot>
in order to retrieve a specific plugin (rather than all compatible plugins for that slot).
Furthermore, slot elements will emit a plugin-load
custom event (not to be confused with the webcore's event emitter) each time a specific plugin is created and rendered. The event can be listened by adding a typical DOM event listener:
slotElement.addEventListener('plugin-load', fnHandleEvent);
The detail
property of the event will contain the object returned by the render method. In Dicoogle, this can be used for attaching React elements without rendering directly to the DOM.
Plugin web components will be attached to a div in the <dicoogle-slot>
with its class defined as
"dicoogle-webcore-<slotid>-instance"
(e.g. "dicoogle-webcore-query-instance"
). The div of these parents
will have a class "dicoogle-webcore-<slotid>"
. The Dicoogle web application may use these classes to style these
additional UI elements.
The Dicoogle web application can be inspected to see how the Dicoogle Web Core is installed there.
Dicoogle Web Core requires HTML custom element support. Include the "document-register-elements" script in order to extend HTML5 custom element support to other browsers.
You can create your own plugins by providing a directory containing two essential files: a plugin descriptor and a JavaScript module.
A descriptor takes the form of a "package.json", an npm
package descriptor, containing at least these attributes:
name
: the unique name of the plugin (must be compliant with npm)version
: the version of the plugin (must be compliant with npm)description
(optional) : a simple, one-line description of the packagedicoogle
: an object containing Dicoogle-specific information:caption
(optional, defaults to name) : an appropriate title for being shown as a tab (or similar) on the web pageslot-id
: the unique ID of the slot where this plugin is meant to be attachedmodule-file
(optional, defaults to "module.js") : the name of the file containing the JavaScript module
In addition, these attributes are recommended:
author
: the author of the plugintags
: the tags "dicoogle" and "dicoogle-plugin" are recommendedprivate
: if you do not intend to publish the plugin into an npm repository, set this totrue
.
An example of a valid "package.json":
{
"name" : "dicoogle-cbir-query",
"version" : "0.0.1",
"description" : "CBIR Query-By-Example plugin",
"author": "John Doe <jdoe@somewhere.net>",
"tags": ["dicoogle", "dicoogle-plugin"],
"dicoogle" : {
"caption" : "Query by Example",
"slot-id" : "query",
"module-file" : "module.js"
}
}
In addition, a JavaScript module must be implemented, containing the entire logic and rendering of the plugin.
The final module script must be exported in CommonJS format (similar to the Node.js module standard), or using
the ECMAScript Harmony import/export notation (as in ES2015) when transpiled with Babel.
The developer may also choose to create the module under the UMD format, although this is not required. The developer
can make multiple node-flavored CommonJS modules and use tools like browserify to bundle them and embed dependencies.
Some of those however, can be required without embedding: "react" and "dicoogle-client" can be retrieved via require
and must be marked as external dependencies.
The exported module must be a single constructor function (or class), in which instances must have a render(parent, slot)
method:
/** Render and attach the contents of a new plugin instance to the given DOM element.
* @param {HTMLElement} parent the parent element of the plugin component, unique to this component.
* @param {SlotHTMLElement} slot the DOM element of the Dicoogle slot, containing a few additional properties:
`slotId`, `pluginName` and `data`.
* @return Alternatively, return a React element while leaving `parent` intact. (Experimental, still unstable!)
*/
function render(parent, slot) {
// ...
}
Additional data is provided to the plugin through the onReceiveData
method, which is called shortly after render
. This data
argument
is always an object, and will be automatically attached to the data
property of the slot HTML element.
/** Obtain access to other pieces of information, which depend on the plugin's context.
* @param {PluginData} data the data provided to this plugin component
*/
function onReceiveData(data) {
// ...
}
Furthermore, the onResult
method must be implemented if the plugin is for a "result" slot:
/** Handle result retrieval here by rendering them.
* @param {object} results an object containing the results retrieved from Dicoogle's search service
*/
function onResult(results) {
// ...
}
All modules will have access to the Dicoogle
plugin-local alias for interfacing with Dicoogle.
Query plugins can invoke issueQuery(...)
to perform a query and expose the results on the page (via result plugins).
Other REST services exposed by Dicoogle are easily accessible with request(...)
.
See the Dicoogle JavaScript client package and the Dicoogle
Web API section below for a more thorough documentation.
Modules are meant to work independently, but can have embedded libraries if so is desired. In addition, if the underlying web page is known to contain specific libraries, then these can also be used without being embedded. This is particularly useful to avoid replicating dependencies and prevent modules from being too large.
Below is an example of a plugin module.
module.exports = function() {
// ...
this.render = function(parent, slot) {
var e = document.create('span');
e.innerHTML = 'Hello Dicoogle! This is plugin ' + slot.pluginName;
parent.appendChild(e);
};
this.onReceive = function(data) {
};
};
Exporting a class in ECMAScript 2015+ also works (since classes are syntatic sugar for ES5 constructors). The code below can be converted to a more compatible ECMAScript version (e.g. ES5) using Babel:
export default class MyPluginModule() {
render(parent) {
let e = document.create('span');
e.innerHTML = `Hello Dicoogle! This is plugin ${slot.pluginName}`;
parent.appendChild(e);
}
onReceive(data) {
}
};
This section documents each possible type of web UI plugin. Note that not all of them are fully supported at the moment.
- menu: Menu plugins are used to augment the main menu. A new entry is added to the side bar (named by the plugin's caption property), and the component is created when the user navigates to that entry.
- result-option: Result option plugins are used to provide advanced operations to a result entry. If the user activates "Advanced Options" in the search results view, these plugins will be attached into a new column, one for each visible result entry.
- result-batch: Result batch plugins are used to provide advanced operations over an existing list of results. These plugins will attach a button (named with the plugin's caption property), which appear in a division below the search result view.
- settings: Settings plugins can be used to provide addition management information and control. These plugins will be attached to the "Plugins & Services" tab in the Management menu.
- query: (currently unsupported) Create different query user interfaces. Once supported, they will be attached in some way to the Search menu, but only replace the query component (existing search result views will be reused).
- result: (currently unsupported) They are used to expose results of a search. Once supported, they will be attached in some way to the Search menu when the results of a search are successfully retrieved.
- search: (currently unsupported) Create different search user interfaces. Once supported, they will be attached in some way to the Search menu, as a complete substitute to the existing search interface. This type of plugin is particularly useful for non-DICOM content, since other search result views may not be compatible with existing Dicoogle data providers.
Either require
the dicoogle-client
module (if the page supports the operation) or use the alias Dicoogle
to
access and perform operations on Dicoogle and the page's web core. All methods described in
dicoogle-client
are available. Furthermore, the web
core injects the following methods:
Issue a query to the system. This operation is asynchronous and will automatically issue back a result exposal to the page's result module. The query service requested will be "search" unless modified with the overrideService option.
- query an object or string containing the query to perform
- options an object containing additional options (such as query plugins to use, result limit, etc.)
- [overrideService] {string} the name of the service to use instead of "search"
- callback an optional callback function(error, result)
Add an event listener to an event triggered by the web core.
- eventName : the name of the event (can be one of 'load','menu' or a custom one)
- fn : a callback function (arguments vary) --
function(...)
Add a listener to the 'result' event, triggered when a query result is obtained.
- fn :
function(result, requestTime, options)
Add a listener to the 'load' event, triggered when a plugin is loaded.
- fn :
function(Object{name, slotId, caption})
Add a listener to the 'menu' event, triggered when a menu plugin descriptor is retrieved. This may be useful for a web page to react to retrievals by automatically adding menu entries.
- fn :
function(Object{name, slotId, caption})
Emit an event through the webcore's event emitter.
- eventName : the name of the event
- args : variable list of arguments to be passed to the listeners
Emit a DOM custom event from the slot element.
- slotDOM : the slot DOM element to emit the event from
- name : the event name
- data : the data to be transmitted as custom event detail
Full list of events that can be used by plugins and the webapp. (Work in Progress)
- "load" : Emitted when a plugin package is retrieved.
- "result" : Emitted when a list of search results is obtained from the search interface.
- "result-selection-ready" : Emitted on plugin mount for "result-batch" plugins. Provides current list of search results and selected entries.
Place all contents of a plugin in a directory and insert the directory (by copying or linking) into the "WebPlugins" folder at the base working directory. Alternatively, package a "WebPlugins" directory with the same contents in a Dicoogle plugin jar. All plugins will then be retrieved the next time the Dicoogle server loads.
Web UI plugins can be tested by deploying them into Dicoogle.