IBM Support

How to integrate Content Navigator with your applications to view content

Technical Blog Post


Abstract

How to integrate Content Navigator with your applications to view content

Body

Background

Document viewing is a central feature in any Enterprise Content Management product.  And it is common for customers of our ECM client products to have a need to integrate its content viewing features into their own custom-developed user interfaces.

With IBM Content Navigator, the primary mechanism made available for content viewing from external applications is the URL-addressable bookmark link.  This is the link that is generated with the “View Link” action of a repository document in ICN.  This link is also generated as part of the email “send as link” action.

The bookmark link opens a single browser page to an instance of content viewer for the identified document.  This window is presented as a stand-alone viewer.

More often though, customers would like to present content viewing as a widget within their own customized UI.  Commonly, this UI consists of a document list to the left or right of a viewer widget, allowing users to click each of the documents to open in the viewer.

This article presents a simple ICN plug-in that implements a thin wrapper around the ICN Content Viewer widget.  The plug-in allows the content viewer to be loaded as a stand-alone ICN feature, suitable for presentation within the <iframe> element of a custom application.  A basic JavaScript function is provided for opening one or more documents in this ContentViewer widget.

For more information about plug-in development and customizing IBM Content Navigator, refer to the “Customizing and Extending IBM Content Navigator” Redbook.

Embedded Viewer plug-in

The ContentViewer widget, available in IBM Content Navigator, presents a tabbed viewing user interface, where multiple documents can be opened.  Each document is opened within a new tab – in this widget.

Options are available for splitting/opening a second copy of the tabs either vertically or horizontally.  This makes it possible to open two different documents side by side, for example.

The ContentViewer widget also provides access to view the document properties, or to add comments to a document – where supported.

The widget has the capability to be able to recognize when a document requested to be viewed is already open in a tab.  And instead of opening a new tab, just brings the correct tab to the foreground.

All of these features make for an easy to use, easy to integrate UI component for content viewing:

image

IBM Content Navigator itself has the notion of “features”, which represent logical groupings of application features.  For example, browsing, searching and administration are all presented as features within ICN.

Features are also URL-addressable.  When loading ICN, it is possible to specify the feature ID as a request parameter, to have that feature opened directly when loading ICN.  To facilitate seamless integration, there is also a request parameter that allows for the window dressing of the application to be hidden (sideChrome=0).

Taken together, these capabilities make it easy to present an ICN feature as a standalone, embeddable UI component.

As an ICN plug-in developer, it is possible to write new custom features for ICN.  So, by implementing a simple plug-in with a single feature which works as a thin wrapper around the ContentViewer widget, we can make it possible to embed ContentViewer into an external, custom application.  This approach sets up a very clean boundary that eliminates any risk of crosstalk between the custom application logic and the content viewing features of ICN.

Under the Hood

The UML Diagram shown here illustrates the structure of the EmbeddedViewerPlugin:


image

The plug-in defines a single Feature, ContentViewerFeature, with the id “embeddedContentViewer”.

The Java classes that make up the plug-in, define the basic framing necessary to set-up the ContentViewerFeature in ICN.  The substance of this plug-in is contained within the ContentViewerFrame widget, so we’ll take a look at that source code here (full source for this example is included in the zip linked at the bottom of this article):

  define([      "dojo/_base/declare",      "dojo/_base/lang",      "dojo/dom-attr",      "dojo/dom-style",      "dojo/dom-construct",      "dojo/io-query",      "idx/layout/BorderContainer",      "dijit/layout/ContentPane",      "ecm/model/Desktop",      "ecm/model/Request",      "ecm/model/Repository",      "ecm/model/ResultSet",      "ecm/widget/layout/_LaunchBarPane",      "ecm/widget/viewer/ContentViewer",      "dojo/text!./templates/ContentViewerFrame.html"  ],    function(declare, lang, domAttr, domStyle, domConstruct, ioQuery, idxBorderContainer, ContentPane, Desktop, Request, Repository, ResultSet, _LaunchBarPane, ContentViewer, template) {        /**       * @name embeddedContentViewerDojo.ContentViewerFrame       * @class Provides an embeddable viewer widget. <code>ContentViewer</code> creates instances of this widget, to       *        open single viewer instances.       * @augments ecm.widget.layout._LaunchBarPane       */      return declare("embeddedContentViewerDojo.ContentViewerFrame", [          _LaunchBarPane      ], {          /** @lends embeddedContentViewerDojo.ContentViewerFrame.prototype */            templateString: template,          widgetsInTemplate: true,          _documentQueue: [],            postCreate: function() {              this.logEntry("postCreate");              this.inherited(arguments);              window.icnContentViewer = this;                            this.connect(ecm.model.desktop, "onLogin", lang.hitch(this, function() {                  this.loggedIn();              }));                            this.connect(ecm.model.desktop, "onLogout", lang.hitch(this, function() {                  this.closeAllViewers();              }));                            this.logExit("postCreate");          },            /**           * Open a document in the viewer.           */          openDocument: function(repositoryId, docId, classId, vsId, version) {              if ( this.isLoaded ) {                  this._openDocument(repositoryId, docId, classId, vsId, version);              } else {                  this._queueDocument(repositoryId, docId, classId, vsId, version);              }          },                    /**           *  Parses a bookmark URL to a document and opens it in the viewer if found.           */          openBookmark: function(bookmarkUrl) {              var query = ioQuery.queryToObject(bookmarkUrl);              this.openDocument(query.repositoryId, query.docid, query.template_name, query.vsId || null, query.version || null)          },                    closeAllViewers: function() {              if (this.contentViewer.splitTabContainer != null) {                  this.contentViewer.splitTabContainer.unloadViewers();              }                this.contentViewer.mainTabContainer.unloadViewers();          },                    _openDocument: function(repositoryId, docId, classId, vsId, version) {              var repository = ecm.model.desktop.getRepository(repositoryId);                repository.retrieveItem(docId, lang.hitch(this, function(item) {                  this.contentViewer.open(item, false);              }), classId, version, vsId);          },                    _queueDocument: function(repositoryId, docId, classId, vsId, version) {              this._documentQueue.push({                  repositoryId: repositoryId,                  docId: docId,                  classId: classId,                  vsId: vsId,                  version: version              });          },                    ...                });  });

This Dojo widget contains a corresponding template HTML – ContentViewerFrame.html:

<div class="ecmCenterPane" data-dojo-attach-point="containerNode">

    <div data-dojo-type="dijit/layout/ContentPane" style="width: 100%; height: 100%" >

        <div data-dojo-attach-point="contentViewer" data-dojo-type="ecm.widget.viewer.ContentViewer"

             style="width: 100%; height: 100%" resetAboutBlank="true" isEntireWindow="false" showNextPrev="false" />

    </div>

</div>

The template HTML contains the declaration of the contained ContentViewer (ecm.widget.viewer.ContentViewer), which is the widget that it wraps.  In the JavaScript, this is referenced as “this.contentViewer”.

The ContentViewerFrame widget, contains one significant line in its postCreate, which assigns window.icnContentViewer an instance of itself:

            …           

            window.icnContentViewer = this;

            …

Also, in postCreate, you will find event handlers that listen for login and logout events.  The login handler will open any queued viewer requests that were made prior to the user's logging in.  The logout handler closes all open viewer windows.

There are three other functions declared as helpers:

        …

        openDocument: function(repositoryId, docId, classId, vsId, version) {

            …

        },

        openBookmark: function(bookmarkUrl) {

            …

        }

        closeAllViewers: function() {

            …

        }

        …

Usage via IFRAME

By assigning an instance of itself to window.icnContentViewer, and exposing the openDocument function, the widget makes itself readily accessible to an outside caller, as shown in this sample HTML (full source for this example is included in the zip linked at the bottom of this article):

<html>

    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge;chrome=1"/>
        <title>ICN Content Viewer Launch Sample</title>
        <script>
            var icnWindow = null;
            
            function openDocument(repositoryId, docId, classId, vsId, version) {
                getContentViewer(function(contentViewer) {
                    contentViewer.openDocument(repositoryId, docId, classId, vsId, version);
                }, true);
                
                return false;
            }
            
            function openBookmark(bookmarkUrl) {
                getContentViewer(function(contentViewer) {
                    contentViewer.openBookmark(bookmarkUrl);
                }, true);
                
                return false;
            }
            
            function closeAllViewers() {
                getContentViewer(function(contentViewer) {
                    contentViewer.closeAllViewers();
                }, false);    
                
                return false;
            }
            
            function getContentViewer(callback, create) {
                var icnFrame = document.getElementById("icnFrame");

                if (icnFrame) {
                    // Open in the iframe on this page...
                    if (icnFrame.contentWindow && icnFrame.contentWindow.icnContentViewer) {
                        callback(icnFrame.contentWindow.icnContentViewer);
                    } else if ( create ) {
                        alert("Content viewer was not found");
                    }

                }
            }
            ...           
        </script>
    </head>
    <body>
        <iframe id="icnFrame" width="100%" height="600px" src="launch.jsp?feature=embeddedContentViewer&sideChrome=0">
        </iframe>
        <br>
        <a href='#' onclick='openDocument("grnvm138", "86 3 ICM8 grnvm1387 NOINDEX59 26 A1001001A13L03B61341E5080618 A13L03B61341E508061 14 1000", "NOINDEX", null, "current")'>Sample</a>
        <br>
        <a href="#" onclick='openBookmark("http://localhost/navigator/bookmark.jsp?desktop=default&repositoryId=NexusDSDB2P8&docid=Document%2C%7B336211AE-4EB8-4DB3-8E95-BD40317C20BA%7D%2C%7B99ED8D52-DE69-466F-A8F4-836EC76C0AA8%7D&template_name=Document&version=released&vsId=%7B6C221C95-68A4-4CCB-AFB7-3686D68E96CA%7D")'>Sample URL</a>
        <br>
        <a href="#" onclick='closeAllViewers()'>Close all viewers</a>
    </body>
</html>

The HTML above contains an iframe that brings up the embeddedContentViewer feature within itself.  It specifies sideChrome=0, which hides the side and top banners, so that only the feature widget, ContentViewerFeature, will be shown.  In order for this to work, the EmbeddedViewerPlugin must be installed, and it’s ICN Content Viewer feature be selected into a desktop named “demo”.

It is recommended that when using this feature, that it be selected into its own separate desktop.  If the feature is included in your existing desktop, it will be visible in the feature navigation, which is not desirable, as it is intended for standalone use.

A simple JavaScript function is also included in the HTML, which gets the iframe DHTML object by Id, checks for existence of the icnContentViewer in the window object of the iframe, and if present, calls openDocument, forwarding the parameters that were sent from the caller.

This is all that it takes to open a document in the contained ContentViewer widget.

Here is an example screenshot to show how the presentation is expected to look for the iframe approach above:

image

Usage via Window

Alternative to an iframe, this feature could be opened in a separate browser window.  Below is an example (full source for this example is included in the zip linked at the bottom of this article):

<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge;chrome=1"/>
        <title>ICN Content Viewer Launch Sample</title>
        <script>
            var icnWindow = null;
            
            function openDocument(repositoryId, docId, classId, vsId, version) {
                getContentViewer(function(contentViewer) {
                    contentViewer.openDocument(repositoryId, docId, classId, vsId, version);
                }, true);
                
                return false;
            }
            
            function openBookmark(bookmarkUrl) {
                getContentViewer(function(contentViewer) {
                    contentViewer.openBookmark(bookmarkUrl);
                }, true);
                
                return false;
            }
            
            function closeAllViewers() {
                getContentViewer(function(contentViewer) {
                    contentViewer.closeAllViewers();
                }, false);    
                
                return false;
            }
            
            function getContentViewer(callback, create) {

                // Open in a browser window...
                if (icnWindow && icnWindow.closed && icnWindow.closed == true) {
                    icnWindow = null;
                }
                
                if (icnWindow != null && icnWindow.icnContentViewer) {
                    icnWindow.focus();
                    callback(icnWindow.icnContentViewer);
                } else if ( create ){
                    icnWindow = window.open("launch.jsp?debug=true&feature=embeddedContentViewer&sideChrome=0", "_icnWindow");
                    _checkOpenStatus(callback, 1, 15);
                }
            }

            function _checkOpenStatus(callback, attempt, retrylimit) {
                // Wait for the window to open, and call the callback once it is available and ready...
                ...
            }        
        </script>
    </head>
    <body>
        <a href='#' onclick='openDocument("grnvm138", "86 3 ICM8 grnvm1387 NOINDEX59 26 A1001001A13L03B61341E5080618 A13L03B61341E508061 14 1000", "NOINDEX", null, "current")'>Sample</a>
        <br>
        <a href="#" onclick='openBookmark("http://localhost/navigator/bookmark.jsp?desktop=default&repositoryId=NexusDSDB2P8&docid=Document%2C%7B336211AE-4EB8-4DB3-8E95-BD40317C20BA%7D%2C%7B99ED8D52-DE69-466F-A8F4-836EC76C0AA8%7D&template_name=Document&version=released&vsId=%7B6C221C95-68A4-4CCB-AFB7-3686D68E96CA%7D")'>Sample URL</a>
        <br>
        <a href="#" onclick='closeAllViewers()'>Close all viewers</a>
    </body>
</html>

In this second example, the iframe is not included on the page.  Instead, the function getContentViewer will either open a new window, loading the feature, or reuse the already opened window that has the feature loaded.

Some notable features to be aware of when using this widget:

  1. A call to open a document that is already open will select the document’s tab into the foreground, so that only one copy of each document is open at any time.
  2. The viewer that is opened for a document is dictated by the viewer map configured for the desktop.  If a different viewer is required for a particular document type, it is simply a matter of defining a custom viewer map in ICN, to make that happen.
  3. The widget supports split modes so that two copies of the viewer stack can be opened either side-by-side or top and bottom.
  4. The widget supports viewing and editing of document properties, and adding notes – if supported by the underlying repository.

Conclusion

More details on the ICN plug-in development and the ContentViewer widget can be found in the section titled Developing Applications with Content Navigator, in the IBM Knowledge Center.  Links:

IBM Content Navigator, Version 2.0.3 / Developing applications with IBM Content Navigator

IBM Content Navigator, Version 2.0.2 / Developing applications with IBM Content Navigator

A zip containing the plug-in source and sample ContentViewer.html as discussed in this article can be downloaded here:

EmbeddedViewerPlugin - Github

[{"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Product":{"code":"SSCTJ4","label":"IBM Case Manager"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":"","Line of Business":{"code":"LOB45","label":"Automation"}}]

UID

ibm11280704