Developing with IBM Content Navigator

I really think IBM Content Navigator lacks a good documentation, especially the JavaScript model. This is definitely not as good as for other IBM’s products, like FileNet P8 for instance. That’s why I thought I would write small examples in the same way that IBM does for developers in the P8 documentation (Working with …).

Of course before looking for information is this post, I can’t recommend you enough the Red Book for developing with IBM Content Navigator, the Javadoc and the JSDoc. This is the first thing every developer should read. However as good as it is, what’s next might come handy for your developments.

Update: Actually the JavaScript model really needs more documentation/examples so I will split the page in several of them to keep this understandable.


Working with document

I extracted this section into another page.

Working with folders

I extracted this section into another page.

Working with classes

I extracted this section into another page.

Working with Entry Templates

I extracted this section into another page.

Working with desktop

Sometime you may be stucke becasue you don’t how to access some information when developing on the client side. I figured out that usually the desktop object, which is globally available, has often the answer to your question. Here are some useful features available, for more please refer to the desktop’s JSDoc.

Is the user connected:

ecm.model.desktop.connected

If you do to want something only when the user will be connected, since plugin are loaded when loading ICN, even it the user isn’t connected yet, you can hook the onLogin method of the desktop object which is called after the user successfully logged in:

aspect.after(ecm.model.desktop, "onLogin", lang.hitch(this, this._onLogin));

To have access to all repositories defined in the desktop, if you don’t have this access where you are:

for (var i in ecm.model.desktop.repositories) {
  var repository = ecm.model.desktop.repositories[i];
}

Add a message

This message is shown in the lower message bar, and displayed as an error popup if it has a level error.

ecm.model.desktop.addMessage(message);

Example:

ecm.model.desktop.addMessage(new Message({
    number: 0,
    level: 0, // The level of the message: 0 = information, 1 = warning, 2 = error.
    text: "Your text"
}));

Do something when a message is added

FYI, they use also this to display error in the ErrorDialog. The ErrorDialog is hooked up on ecm.model.dialog.onMessageAdded and is checking if the level is error.

aspect.after(ecm.model.desktop, "onMessageAdded", function(message) {
    // Do something with the message
    // ....
});

Get the list of all File Types

ecm.model.desktop.filetypes

ICN_desktop_filetypes

Modify the default StatusDialog text

If you want to change the default text of the Status Dialog once it is already displayed, you can use the following code:

var layout = ecm.model.desktop.getLayout();
layout._statusDialog.contentNode.innerHTML = 'Your new text...';

 

Working with Features

Get the selected feature’s ID

Features are actually widget added in a dijit.layout.StackContainer. The main difficulty here is to access the StackContainer. This can be done via the main layout as follow:

var layout = ecm.model.desktop.getLayout();
var stackContainer = layout.launchBarContainer.launchBarContentArea;
var selectedFeature = stackContainer.selectedChildWidget;
var selectedFeatureId = selectedFeature.UUID;

You can then check the id to see if you are on a specific feature:

if (selectedFeatureId == 'browsePane') {
    // Do something with the feature
}

Get the selected Feature widget (LaunchBarPane)

One you got the selecture feature’s Id, you might want to get the actual widget to invoke function on it.

var feature = layout.launchBarContainer.getContentPaneByID(selectedFeatureId);

Get the list of all features

ecm.model.desktop.features

ICN_desktop_features

Select a specific item in the tree of the Browse feature

Sometimes you may need to change the selected item in the tree, for instance if you deleted the item the user is currently on. Here is how to change the selected item in the Browse feature view:

var browseFeature = layout.launchBarContainer.getContentPaneByID('browsePane');
browseFeature.folderTree._setSelectedItemAttr(itemToSelect);

Working with the Tree

Test if a path is selected in the Tree

// See in the feature section how to get the browseFeature
browseFeature.folderTree._tree.isPathSelected(path);

Get the selected path of the Tree

// See in the feature section how to get the browseFeature
browseFeature.folderTree._tree.get("path");

Select a path in the Tree

browseFeature.folderTree._tree._setPathsAttr(Item[][] || String[][])

 

Working with Security

Test user privileges for a document

Every ecm.model.ContentItem has a method named hasPrivilege, used as follow:

item.hasPrivilege('privCheckInOutDoc');

Please refer to the Red Book for all available privileges constants, page 515.

Test is the user is an administrator

The Repository object has an attribute telling you if the user is administrator. I still need to investigate a bit more to figure out if it is administrator of ICN of a special privilege on the repository, since it is not said in the API documentation, but it is working for me until now so you can use this:

ecm.model.Repository.isAdminUser

Usually you have the repository as parameter of all methods in an action. However if for any reason you don’t have this, you can retrieve all repositories associated to the current desktop byt accessing the global desktop object which has the array of repositories.

ecm.model.desktop.repositories

 All privileges constants

You can find all privileges constants in the Red Book given at the beginning of he post, page 515

 

Working with Drag and Drop

Sometimes it can be handy to modify the comportment of the global Drag And Drop in ICN, for instance to protect folders to being drop over or drag from. A basic use case is a Recycle Bin you don’t want user to drag from or drop to because they have to use custom action like Remove or Restore.

Modify external drop acceptance rules for the BrowsePane tree (left panel)

Here is usually what I do if I want to forbid dropping external content (triggering the Add Document wizard) on the specific node.

aspect.around(DndFromDesktopAddDoc.prototype, "_canDropOnTargetItem", function(originalFunction){
    return function(targetItem){
        // Do some tests wit targetItem so you can forbid drop on a specific node or maybe also all its descendants
        if (targetItem.id == myForbiddenNodeID) {
            return false;
        } else {
            return originalFunction(targetItem);
        }
    }
});

Modify the external drop acceptance rules for the content panel (right panel)

And this is meant to forbid dropping external content (triggering the Add Document Wizard) in a specific folder in the content panel (right panel).

aspect.after(Tree.prototype, "onExternalDragOver", function(evt){
    // If there is an hovered row
    if (this._lastHoveredTargetRow) {
        // Get the widget associated to the row
        var current = registry.getEnclosingWidget(this._lastHoveredTargetRow);
        // It is supposed to be true but check to make sure
        if (current && current.item) {
            current = current.item; // Get the item associated to the row
            // Do a loop on all parent to check if one of the parent is forbidden
            while (current) {
                if (current.id == myForbiddenId) {
                    // Item or one of its parent is forbidden, do not allow drop here
                    evt.dataTransfer.dropEffect = "none";
                    break;
                }
                current = current.parent;
            }
        }
    }
}, true);

Here we check all node’s parents because we want to forbid drop into one folder and all descendants. But you can just check the current item’s ID if you want to permit drop into sub-folders.

Modify the Internal Drag and Drop acceptance rules

That’s good to forbid external drop, but if you let users use D&D within ICN, this is kind of useless. So here is how to modify the behavior of the internal D&D.

aspect.around(Dnd.prototype, "canDrop", function(originalCanDrop){
    return function(sourceItems, targetItem, isCopy, rootItem){
        // First check if the standard method allow the drop
        var res = originalCanDrop.apply(this, [sourceItems, targetItem, isCopy, rootItem]);
        if (res) {
            // If it does, check that we are not trying to drop into the Recycle Bin
            res = !Utils.isItemUnderMyForfiddenFolder(targetItem);
            // Also Check if the source items contains the a forbidden item
            for (var i in sourceItems) {
                var item = sourceItems[i];
                if (Utils.isItemUnderMyForbiddenFolder(item)) {
                    res = false;
                    break;
                }
            }
        }
        return res;
    }
});

Here we forbid D&D from and to a folder and all its descendants. You can do whatever you need in this function, since you have access to all the source items and the target item (they are all ecm.model.Item).

Working with login

Do something before user logs in

If you need to do something before the user logs in, for instance show a warning, or cancel the logon for some reason, you can aspect the logon function from the desktop

aspect.around(ecm.model.desktop, "logon", function (originalLogonFunction) {
    return function (password, callback, reqParams, reqHeaders) {
        // Do something, even asynchronous, then when it's done call originalLogonFunction
        originalLogonFunction.apply(this, [password, callback, reqParams, reqHeaders]);
    };
});

Do something when user logs in

If you want to do something right after the user logged in, you can aspect the function ecm.model.desktop.onLogin from the desktop. This is called right after a successful logon.

var signalOnLogin = aspect.after(ecm.model.desktop, "onLogin", function(repository) {
    // Do whatever you need to
    // ....
    // Delete the aspect if it needs to be done only once
    signalOnLogin .remove();
});

Do something when the user session expires

If you need to do something when the user session expires, you can aspect the function ecm.model.desktop.onSessionExpired. This is called as soon as a request gets session expired from the server. Of course that means you’ll have this only when a request is made.

aspect.after(ecm.model.desktop, "onSessionExpired", function(request, error) {
    // Do whatever you need to
    // ....
});

Do something when the user reconnect successfully after a session expired

If you need to do something when the user reconnects after a session expired, you can aspect the function ecm.widget.dialog.LoginDialog.onConnected. This is called right after the user successfully reconnects. You can get the singleton instance of the LoginDialog with the static function getLoginDialog(), or you could also aspect the ecm.widget.dialog.LoginDialog.prototype

aspect.after(LoginDialog.getloginDialog(), "onConnected", function(repository) {
    // Do whatever you need to
    // ....
});

13 thoughts on “Developing with IBM Content Navigator

  1. marco

    Hello, how to acces to ICN omitting the login page? any idea for sending of credentials by another method? in need to connect ICN a frontend of atentication. Thanks.

    Reply
  2. Paul Atreides

    Hello, Guillaume.
    I was developing back-end on CE API using JAVA while my teammate was developing front – on Content Navigator with dojo.
    Then he left our team and I have to fix the bug in plugin.
    Could you tell me please, some sufficient information about specifics of being connected to desktop (ecm.model.desktop.connected field)?

    I’ll explain my problem:
    the custom CN plugin was developed – it adds a button “Open Client” to Search form (will call it main window) which opens a NEW window with a parent folder parameters.
    Basically “Open Client” button constructs a link:

    var link = ecm.model.Item.getBookmark(itemList[0]);

    link = link.replace(encodeURIComponent(thisItem), encodeURIComponent(clientFolderId));
    link = link.replace(thisTemplateName, clientFolderClass);

    var win = window.open(link, '_blank');
    win.focus();

    and opens a new window.

    This new window executes one of the js files which has folloving code:
    constructor: function() {
    this._timer = setInterval(lang.hitch(this, function() {
    var isConnected = ecm.model.desktop.connected;
    if (isConnected) { ………… here are our code that initiates object that we use in our business logic……}

    and there is no ” else {} ” statement. So the only way our objects is initiated is when isConnected = TRUE;

    When the new window first opens, the value of isConnected = TRUE and everything goes well;
    but if I refresh the new window OR close it and push “Open Client” button again – the newly opened window will have isConnected = FALSE;
    My question is – what could go wrong if ecm.model.desktop.connected on newly opened window is FALSE? What features of CN (ECM) depends on that?
    Because the only way to fix our problem I have found is to comment this check:
    if (isConnected) {……. }

    After that I rebuilt the project, updated the plugin and everything went well with our business logic.
    But I still wander what influence does this parameter have on CN plugin behavior?
    Thank you .
    I look forward to your reply

    Reply
    1. Guillaume Post author

      Hi Paul,

      I’m really sorry for the late reply. I was out of the country lately and didn’t get a change to answer comments.

      I’m not sure I really understanding your question nor what you are doing exactly, but let’s first answer the last question as I understand it.

      From what I know, ecm.model.desktop.connected is used in by ICN only in three places, two of them being to show the login window when first loading ICN to allow logging in. Last one being in the entry template builder to be able to close it. So I would say it has no affect on plugins.

      Now I don’t understand why it even works the first time for you since the new window shouldn’t have access to the ICN context, and should see an undefined ecm object when calling ecm.model.desktop.connected (I implemented it to check).

      Or maybe I misunderstood how you were doing this. I never open new window (I like having a single window app and use dialogs) so I am unfamiliar with this but if you give me more details maybe I can help more 🙂

      Reply
      1. venky

        Hi Guillaume,

        I have a requirement like in below.
        when i select specific folder (let call it is Folder1) in the navigator left side tree then a specific content list toolbar action should enable otherwise if any other folder selected then it should disable.

        Please provide your input asap . it is priority work please do reply

        thanks & regards,
        Venky

        Reply
  3. Rahul

    Hello, Guillaume,

    Thanks for such nice information regarding the ICN custom development. I wanted to check with you regarding some questions.

    We have a requirement to allow user to select users from the ICN step processor to be added to workflow properties.

    We are using the ecm.widget.dialog.SelectUserGroupDialog dialog to users to select users from the Directories.

    Wanted help regarding this for below:

    We are storing the result from above in ecm.model.User object but when we get the display name its same as the short name. Is the display name set at the Directory configuration level?

    Also in the ecm.widget.dialog.SelectUserGroupDialog can we allow user to search by first name and last name as currently they can only search by the short name. Again for this do we have a out of the box ecm dialog and do we need to modify the search criteria in the Directory configurations of the CE.

    Thanks for the help

    Reply
    1. Guillaume Post author

      Hi Rahul,
      Usually yes the display name is the one configured at the server level for the LDAP. In WAS it’s under security > Configure the LDAP > Advanced property something (I don’t remember exactly). Check how users arr mapped there.

      Hope that help.

      Reply
  4. Fernando Andrade

    Hi Guillaume, how are you?
    I’ve got a project to work on the next few months where basically, the user search for a document in the desktop search, move it to a folder and them set some business attributes to this document. After that, there should be a button in the right click menu of the document and an action must call a service that opens an new window with lots of others fields to be input by the user. And theses attributes will be persisted to a database. Some fields can also be pre filled using database or webservices retrieved data and after user input will be client and server side validated. What is the best way to do it? Using a plugin extension or implementing a eds interface? I’m thinking of using a plugin extension to do it and open a url using javascript in new window? But I was also considering the dialogboxes, Does the dialogbox function allow all this?

    Reply
  5. Federico Luppi

    Hello there, I need to develop a web java application that should connect to content navigator in order to retrieve documents and class properties.. is there any example of how to connect to CN using Java API.

    Thanks in advance.

    Reply
  6. Srvni

    Hii in the workitem, properties pane the properties should come in two panes side by side. I tried in the stepprocessor but not getting exactly how to do. can u suggest.

    Thanks in advance

    Reply
  7. Ronaiza Cardoso

    Hello, do you have the full code of this application? now I need to make one drag and drop box on my launch bar container and can’t find where the dnd is been blocked.

    Reply

Leave a Reply to Federico Luppi Cancel reply