Technical Training “How to program a Widget in the new Content Server GUI” with the CSUI SDK

Hi folks

Beeing Content Server Trainer, I was asked to provide a training “How to program a Widget for the new Content Server GUI” to several customers. This is about the content of such a training and the reason why such a training is technically really advanced.

CSUI SDK introduces a shift in paradigma, the server is no longer providing the GUI, all is done by a Javascript Application at the client side.

Basic Training (newest version 1.005 from Feb/2018)

The basic training is very compact and lasts 5 days.

First, lets take a look on the components:

As a prerequisite, a firm knowledge of CSIDE, How to build a module, the Node Structure and whats to do with Content server Nodes is necessary.

At the end of the training, you’ll have a working knowledge of:

  1. Content Server Perspectives. What are they and how you can use them to provide a user specific interface.
  2. Content Server REST. This is the only possibility for the client to communicate with the server, so its mandantory to know the REST interface from the application point of vue. And, as REST can be very slow due to unneccesary data, its also mandantory to know, how to add REST services to the Content Server to get the data you want in the fastest way.
  3. Javascript. There are Javascript Patterns, which are used heavily in the SDK. If “Javascript Object Inheritance”, “Currying/Schoenfinkelizing”, “Decorators” etc are weird words for you, this chapter is the right chapter for you.
  4. Next is the infrastructural world of the sdk.
    CSUI Components
    The CSUI Components

    There are several components, which must be understood prior to build a Widget.

    1. node.js is the base Javascript system for our development
    2. grunt.js is a Javascript task runner, which we use to build, test and debug our widget
    3. yeoman is a scaffolding system, which will be used by a Opentext extension to initialize the development folder. It also creates the skeleton of a Content Server module, which is used as a “Carrier” for our Widget(s)
    4. backbone.js is the base framework to be used
    5. marionette.js is an extension to backbone.js, making Views easier.
    6. handlebars.js is the html templating framework used in the sdk
    7. require.js is the javascript module loader to be used
    8. bootstrap.js/binf.js is originally the public domain CSS/JS framework from Twitter for the appearance of the Widget. Binf is the Opentext variant, allowing to override CSS without negative effects
  5. Next is the SDK itself. Due to time restrictions, in the base training are only the base functions, the advanced training covers the other aspects. It contains
    1. Installing the SDK
    2. Building the Demo Widget
    3. CSS Style Overrides binf
    4. General Overview of the SDK
    5. Content of the SDK
    6. Routing (Preview of the Advanced Training)
    7. New Commands
    8. Custom Columns
    9. Metadata
    10. Define a new NodeType
    11. Create a Widget
    12. Base Widgets
    13. Controls
    14. Models
  6. Mockup and Test Data. How to setup mockup REST data. How to build test facilities in the SDK
  7. Anatomy of the Hello Widget. A walktrough through this widget
  8. Anatomy of the MyAssignment Widget
  9. Add another Widget to the Content Server. Change the Hello Widget and add additional fields
  10. Build a Custom Widget to be used as Client with a custom REST service. Here, a custom widget is build, which uses an extended version of the custom REST service from Part 2.
  11. If time allows: Some Tips and Tricks from a “Work-in-Progress” Widget


Advanced Training (Version 1.005 Feb/2018)

OK, thats the basic training. There are a lot of additional things inside the SDK, but to understand these, there must be a couple of weeks with practical experience in between. The advanced part has beed remodelled to include a couple of interesting things.  The advanced part contains:

  1. Chapter 1 Extended SDK Parts
    1. Additional Widgets
      1. NodesListReportView
      2. TilereportView
      3. FilteredCountChartView
      4. Carousel Widget View
      5. Userwidget eSocial
      6. ActivityFeedWidgetView
      7. ActivityFeedContent
      8. ChatWidget
    2. Datepicker
    3. switch
    4. Workflow (new in Content Server 16.2)
      1. Workflow Components
      2. Workflow in smartUI
      3. Starting Workflows
      4. URL Routes
      5. Workitem Actions
      6. Workitem Extension
      7. Writing Workflow Extensions
    5. New REST API Support (16.2) for Workflows
    6. Widgets not part of the SDK
      1. Mobile Scanning
      2. xECM: Header Widget with Business Object Infos
      3. xECM: Snapshot of current document Attachments
      4. xECM: Dossier View Widget
      5. Engineering Doc Management: Search
      6. xECM: Office365 Groups
  2. Chapter 2 Extended SDK Features
    1. Build Language Packs for Internationalization
    2. Commands
      1. Implementation and Inheritance from “CommandModel”
      2. Best Practices
      3. Using Commands
    3. Custom URL Router. Routing, adding custom Routers. Using Routers as Navigation.
    4. Behaviours. What are Behaviours?
      1. DefaultActionBehaviour
      2. BlockingBehaviour
      3. ExpandingBehaviour
      4. InfiniteScrollingBehaviour
      5. PerfectScrollingBehaviour
      6. PageLeavingBehaviour
    5. Mixins. What are Mixins? All available Mixins.
    6. Browsable Support for Collections. Using the “Browsable” support for Model-Collections.
  3. Chapter 3 Additional Things to consider
    1. Tips and Tricks (Work in Progress- List can change)
      1. Add a OTDS Ticket already in the browser to the connection object
      2. Re-using a OTDS Ticket as LLCookie
      3. Checking the paths in the test/index.html
      4. Using Helpers for supporting a select box with Handlebars
      5. Adding non CSUI supported JQuery functions in a view
    2. Handlebars advanced. A deeper look into Handlebars
    3. LESS advanced. A deepter look into LESS, the CSS language used in Bootstrap
    4. Accessibility in Bootstrap. What can be done to add support for screenreades etc to Bootstrap/Binf? Whats to avoid? Which tools are available
    5. Best Practices in Development


As you can see, there is a lot of stuff. The basic training last 5 days, the advanced training 2. But on the other side, you’ll get happy users with your new Widgets.

And that’s all what counts.


  • “JavaScript Application Design “as a general introduction (covering Grunt)
  • Stoyanow’s JavaScript patterns
  • Flanagan’s “JavaScript: The Definitive Guide”
  • Crockfords “Javascript The good Parts”
  • Flanagans “JavaScript Pocket Reference”
  • Spurlock: Bootstrap Responsive Web Development
  • Syed Fazle Rahman: Jump Start Bootstrap: Get Up to Speed With Bootstrap in a Weekend
  • Alan Forbes: The Joy of Bootstrap: A smarter way to learn the world’s most popular web framework
  • Greg Franko: Instant Dependency Management with RequireJS How-to
  • David Sulc: Structuring Backbone Code with RequireJS and Marionette Modules
  • Developing Backbone.js Applications Adnan Osmani
  • js Patterns and Best Practices Swarnendu De
  • js Cookbook Vadim Mirgorod
  • js Blueprints Andrew Burgess


  • Getting Started with Backbone Marionette Raymundo Armendariz, Arturo Soto
  • Marionette.js: A Gentle Introduction David Sulc
  • Marionette.js: A Serious Progression David Sulc
Mockjax and Jasmine
  • js Cookbook Vadim Mirgorod Chapter 8
  • KnockoutJS Essentials Jorge Ferrando
  • JavaScript Testing with Jasmine Eva Hahn


Content Server Trainings
  • 4-0140 Content Server IDE (CSIDE) Fundamentals
  • 971-301 Livelink ECM Enterprise Server Workflow Customization Fundamentals
  • 03-0117/8 Webreports Design



Setting Node ACLs via Web Services API

One of the nicest things in the content server area is, you can set Access Control Lists not only for the owner or the default group of a node, and you can add or revoke rights to the node for virtually all users and groups defined in the content server.

Here, we want do discuss how to do this using Content Server Web services. First, let’s see how ACLs are organized. A simple object would display something like this:

The ACLs for this object

On the left hand side, you see the default access, there is always one Owner, one default group and a public group.

If you want to assign further access, you can select a user or a group by clicking on the bottom at the lower left hand side. This will select a user or a group. (Btw: It’s recommended to use groups instead of users)

On the right hand side, there are the ACLs for the selected user/group on the object. Here, we used the Administrator, therefore all rights are switched on.

Ok, how to use this on a c# client?

First, the ACLs for a given user are called NodePermissions. Use them like this. Let’s say, this is utils.setNodeRights(….)

/// <summary>
 /// Creates a Noderight Structure and returns it
 /// </summary>
 /// <param name="rights">"all" or see (see,seecontents)</param>
 /// <param name="id">the member ID for this node rights</param>
 /// <param name="type">Owner, Ownergroup, Public or ACL </param>
 /// <returns></returns>
 public DocumentManagement.NodeRight setNodeRights(string rights, long id, string type )
 DocumentManagement.NodePermissions newPerm = new DocumentManagement.NodePermissions();

newPerm.SeeContentsPermission = true;
 newPerm.SeePermission = true;
 if (rights.Equals("all"))
 newPerm.AddItemsPermission = true;
 newPerm.DeletePermission = true;
 newPerm.DeleteVersionsPermission = true;
 newPerm.EditAttributesPermission = true;
 newPerm.EditPermissionsPermission = true;
 newPerm.ModifyPermission = true;
 newPerm.ReservePermission = true;
 DocumentManagement.NodeRight newRight = new DocumentManagement.NodeRight();
 newRight.Permissions = newPerm;
 newRight.RightID = id;
 newRight.Type =type;
 return newRight;


The Nodepermissions is simply a container with the single access rights set to true or false.

When the definition is finished, the NodePermissions must be encapsulated in a structure called NodeRight. This contains the ACLs defined and the user/group for the ACL. Use the id as long integer, this is the unique identifier of a user/group in the content server. You can use MemberServices to get this number, if you know the login-name.

Then, the system wants to know, which kind this user/group is. Use ACL, if these are additional users/groups, or use something like Owner, Ownergroup or Public, if the ACLs should belong to the predefined entities.

How to use this?

First, login to the Webservices.

Second. Get the node, onto which you want to set ACLs.

Thirth. Get the node rights

 NodeRightsUtilities utils = new NodeRightsUtilities();
 DocumentManagement.NodeRights nodeRights = docclient.GetNodeRights(ref otauth, pargs.nodenumber);
 Console.WriteLine("Got Noderights ");
 DocumentManagement.NodeRight[] nodesrights = nodeRights.ACLRights;

Forth. Set the ACLs for the standard entities (if needed). Define a NodeRight for every entity (user/group) you want to set. Define these 2 right groups.

 // setup standard permissions for base ACL group
 DocumentManagement.NodeRight newRightstandardGroup = utils.setNodeRights("see", standardGoupID, "ACL");
 DocumentManagement.NodeRight newRightsmanagerGroup = utils.setNodeRights("all", managergroupID, "ACL");

Define a special right group

 DocumentManagement.NodeRight newOwnerright = utils.setNodeRights("all", ownerID, "Owner");
 nodeRights.OwnerRight = newOwnerright;


The nodesrights on a node can contain something or be null. First, lets check, if the thing is null.

 if (nodesrights == null)
 Console.WriteLine("Nodesrights not found- no external Users/groups assigned");
 Console.WriteLine("Setting a group with rights see/seecontent");
 DocumentManagement.NodeRight[] allrights = new DocumentManagement.NodeRight[2];
 allrights[0] = newRightstandardGroup;
 allrights[1] = newRightsmanagerGroup;
 nodeRights.ACLRights = allrights;
 docclient.SetNodeRights(ref otauth, pargs.nodenumber, nodeRights);
 Console.WriteLine("All Rights and Groups set --- Finish");

In this case we simply define a NodeRight array with two entries containing our new RightstandardGroup and our newRightmanagerGroup.

Let’s store them into our newly created array.

Let’s store this array in our nodeRights under ACLRights. This will change our copy of our structure which we downloaded before.

Next is simply a SetNodeRights with the nodenumber and the updated nodeRights array to write our changes back to the server.

If there are already entries, you should set them like this:

// Just display the first Entry of the first assigned Users/groups
 DocumentManagement.NodeRight right = nodesrights[0];
 DocumentManagement.NodePermissions nperms = right.Permissions;
 // check, if we do habe the groups already set

int newLen = nodesrights.Length;

 // copy rights into new longer Array
 DocumentManagement.NodeRight[] allrights = new DocumentManagement.NodeRight[newLen+2];
 for ( int i = 0; i < newLen;i++ )
 allrights[i] = nodesrights[i];
 allrights[newLen] = newRightsmanagerGroup;
 allrights[newLen + 1] = newRightstandardGroup;
 nodeRights.ACLRights = null;
 nodeRights.ACLRights = allrights;
 docclient.SetNodeRights(ref otauth, pargs.nodenumber, nodeRights);
 Console.WriteLine("All Rights and Groups set --- Finish");

In this case, our nodesrights come from the nodeRights.ACLRights, because there are alredy entries.

Now, let’s do the same thing like we did on a new structure. But, we don’t want to delete existing entries, lets simply add our new RightstandardGroup and our newRightmanagerGroup to the ACLRights array.

One thing, if finished, first set the nodesRights.ACLRight to null and then fill it with the new extended ACL array.

Then do a SetNodeRights with the nodenumber and the updated nodeRights array to write our changes back to the server.


Finished. Quite easy, isn’t it?


BTW: There is no equivalent to do this on the REST api at the time of this post.




Calling OScript from Java Code (2)

In December 2016, we discussed how to call Java code from OScript . Now, we’ll discuss the other way round, how to call OScript from Java. This can be quite useful, if you want to use your business logic implemented in java against the content server.

The Java code must be put in the ojlib directory (see the previous post on this topic).

As always, if you want to deploy your java code within a module, do this

  • Build your code into jar files.
  • Add the jar files in OTHOME/ojlib/ or OTHOME/module/yourmodule_yourversions/ojlib/ directory (and their subdirectories) to be recognized by Content Serverk JVM’s application classloader.

The base thing is, you call OScript built-in functions through the OScriptObject.runScript method from a java coding.

For example, the java code


will display the unique integer for the current thread ID at the console (or in the logs, if you do not use CSIDE)

You can call all OScript functions and scripts. This example will list all nodes in the enterprise workspace

     // List nodes in the Enterprise workspace.
            result = (Map<String,Object>) OScriptObject.runScript( "$LLIAPI.NodeUtil.ListNodes",
				args );

prgCtx is the standard rogram Context, args is the ArrayList containing the arguments and A1 points to the first entry in the args array to be used as ParentID.

The next example can be used to get the current user from java coding, extract its userID and then derives the user name from ths user id. A standard logger is used to log the output, replace this with the logger of your preference.

    public static String getUserName( OScriptObject prgCtx )
    throws Exception
        String retval = " user not found";
            // get the user session object
            OScriptObject uSession = (OScriptObject) prgCtx.invokeScript( "USession" );
            // get the userID from the User Session
            Integer userID = (Integer) uSession.getFeature( "fUserId" );
            // display it
            logger.log( Level.INFO, "UserID is " + userID );
            retval = "The current login User: " + userID;
            // Get the Users name from the User ID
            Map<String,Object> status = (Map<String,Object>)
			OScriptObject.runScript( "$LLIAPI.UsersPkg.NameGetByID", uSession, 1000 );
            logger.log( Level.INFO, String.valueOf( status ) );
            logger.log( Level.INFO, (String) status.get( "Name" ) );
        catch( Exception e )
            logger.log( Level.SEVERE, "Caught Exception", e );
            throw e;
        return retval;


In the next posting on this topic, we’ll discuss the Mappings from JAVA to OScript and vice versa.


Calling Java Code from OScript (1)

Sometimes it would be nice to use existing Java coding from a module instead of recoding this in OScript.

There is a facility in the content server which does exactly this bridging from OScript to Java, the so called JavaObject class. You’ll find the exact documentation in the “OScript API/Build-In Package Index”

In this first post of the series we’ll discuss the  basic calls from OScript.

First, you need some Java Code, compiled and in the form of a jar. Put this jar either in OTHOME/ojlib or (much better) in a ojlib directory in your module structure. After installing the module, the jar(s) are copied automatically to the OTHOME/ojlib. Then, the jvm classloader will find your jar(s).

From OScript it is possible to access static classes and instances.

Dynamic InvokeStaticMethod(String classname, String methodName, List parameters)

Dynamic GetStaticField( String classname, String fieldName)

Dynamic SetStaticField( String className, String  fieldName, Dynamic value)

The return values are either Error or Dynamic if the call is successful.

JavaObject New (String className, List parameters)
Dynamic GetField(String fieldname)

Dynamic SetField(String fieldname, Dynamic value)

Dynamic InvokeMethod(String methodName, List parameter)

The return values are either Error or Dynamic if the call is successful.

An example
function void javaTest()

   JavaObject myObject

   myObject ="my.own.package.class")
   Dynamic res = myObject.InvokeMethod("myMethod",{"aa","bb})

   if (isError(res))

     echo ("Init failed")



   Dynamic res1 = myObject.GetField("myResult")

   if (IsError(res)) 

     echo("Calculation failed")



   echo("The result is "+res1)


In the next post we’ll discuss how to get the JNI exceptions and the error stack from the jvm.


Upload a File to Content Server using REST and Javascript

This is an example how to upload a file using JavaScript and REST.

This example uploads the file c:\test.txt with the name of “MyFile123” under the folder with the node id 485336. This snipped relies on a previous login. The subtype of the file to be uploaded is “document” (144). The authorization token is saved under the variable name of “otcsticket”.

This example does not consider any categories (mantadory or not). We’ll discuss this in a later post.

6 Steps:

  1. Declare all variables needed. This is done by defining the array bodyData. At least there must be the subtpe, the parent_id, the name and the local file name.
  2. Fire an AJAX request to the URL, where your content server is, Use “api/v1/nodes” as REST command.
  3. Put the authorization ticket in the header field
  4. Put the bodyData in the data field
  5. Set the Mime Type to “application/x-www-form-urlencoded”
  6. If the request is done, process the “success” or the “failure” clauses

Put some nice HTML around it, add the authorization code and then you are done.

(At least for this example. Normally, you should provide some name check for the node name)

function upload() {
  var bodyData = {
        type: 144,
        parent_id: 485336,
        name: "Myfile123",
        file: "c:\\test.txt"
      formData = new FormData();
  formData.append( "body", JSON.stringify( bodyData ) );

  formData.append( "file", "c:\\test.txt" );
  return $.ajax( {
    type: "POST",
    url: "http://[yourserver/yourcs/cs.exe/api/v1/nodes",
    data: bodyData,
    headers: { otcsticket: otcsticket },
    beforeSend: function( xhr ) {    
    xhr.overrideMimeType( "application/x-www-form-urlencoded" )
  } ).then( function ( response ) {
  }, function ( jqxhr ) {
  } );


Authentication (3/3) Authenticate with Webservices and Java against Content Server

To authenticate with a JAVA client against a Content Server, you should first create all client proxys. This example can be used against a Servlet Container, like Tomcat. Here, it is assumed, that it runs on port 8080.

The creation of the proxys must be done manually by typing

wsimport -keep http://yourserver:8080/cws/services/Authentication?wsdl
 [add all services you want to use]

jar cvfM webservices.jar com/opentext/*

Add the webservices.jar file in the build path of your Java application.

Then, use something like this to authenticate

String username = "[yourUser]";
  String passsword = "[yourpassword]";
  Authentication_Service authsrv = new Authentication_Service();
  Authentication authclient = authsrv.getbasicHttpBindingAuthentication();
  // the Token
  String authToken=null;
  // authenticate
      authToken = authclient.authenticateUser(username,password);
  } catch (SOAPFaultException e)
      System.out.println("Failed! "+e.getFault().e.getFaultCode()+" : "+e.getMessage());

This will, if succesful, store the authenticaten token in the string authToken.

Use this token in this way:

DocumentManagement_Service docmanSrv = new DocumentManagement_Service();

DocumentManagement docman = docmanSrv.getbasicHttpDocumentManagement();

OTAuthentication otAuth = new OTAuthentication();


Now, we have to set the token manually in the SOAP header



    final String API_NAMESPACE = "";
     SOAPHeader header = MessageFactory.newInstance().createMessage().getSOAPPart().getEnvelope().getHeader();
     SOAPHeaderElement authElement = header.addHeaderElement (new QName(API_NAMESPACE,"OPAuthentication"));
     SOAPElement authTokenElement = authElement.addChildElement (new QName(API_NAMESPACE, "AuthenticationToken"));
     authTokenElement.addTextNode (otAuth.getAuthenticationToken());
     ((WSBindingProvider) docMan).setOutboundHeaders (Headers.create(otAuthElement));
  } catch (SOAPExeption e)


[Print Error and return]


// now, we are ready to perform some functionality

Node node = docman.getNode(nodeId);
  1. Login and get the auth token
  2. set the auth token in the SOAP header
  3. tell your services about the auth token in the SOAP header
  4. perform operations on the server

Do not forget to refresh the token before it gets invalid. We discuss the refesh in a later posting.

Authentication (2/3) Authenticate with Webservices against the Content Server in c#

This is the second post on a series about authentication against the content server. This post explains the authentication from a c# application using Webservices.

Normally, the Webservices are used from a Java application container like Tomcat. To use this snippet, a Service Reference exist inside Visual Studio with the URL


with the name of “Authentication”.

The Authentication itself is done with something like this:

Authentication.AuthenticationClient authClient = new Authentication.AuthenticationClient();

string authToken = null;
     Console.Write(“Authenticate User…”);
     authToken = authClient.AuthenticateUser(userId, password);
     Console.WriteLine(“Success \n”);
} catch (Exception e) {
     Console.WriteLine(“{0} : {1} \n”, e.Message, e.Source);

Here, the userid and the password is send to the Authentication service. If all is correct, the authentication token is send back.

This token can be used like in the following snippet. Here, the Node 485227 is requested.

DocumentManagement.DocumentManagementClient docclient = new    DocumentManagement.DocumentManagementClient();
DocumentManagement.OTAuthentication otauth = new    DocumentManagement.OTAuthentication();
otauth.AuthenticationToken = authToken;

// Get the Node 485227 
DocumentManagement.Node node = docclient.GetNode(ref otauth, 485227);

Like the Authentification Service, the DocumentManagement Service must be declared as a Service Reference in Visual Studio. When a Tomcat is used, then the declaration looks like


To use the token, declare an instance of Documentmanagement.OTAuthentication and put the token in the AuthenticationToken field of this instance.

Then use the webservices calls as usual, but add the instance of this OTAuthentication as a reference in the first argument of the call.

Tipp: save this instance as a copy, from time to time the first argument returns as null. Then you can re-instate the original OTAuthentication.

If you want to use Webservices in IIS, pls refer to the post from January 2017.

Authentication (1/3) How to authenticate against the Content Server in REST/Javascript

This is the first post on a series about authentication against the content server. The first post explains the authentication from a html page with Javascript and JQuery using REST

The REST API can be used to perfom things on the Content Server from nearly every thinkable language. Here is the example how to do a login from Javascript.

Replace [yourserver] with the DNS name of your Content Server, replace [yourCSInstance] with your CS instance and the cgi.

A valid URL would be for example

for the Authentication request.

  var otcsticket = "";
  * This function needs to be run first to authenticate the user against Content Server.
  function authenticate() {
  // Set up the ajax request
  // Authenticate is a POST
  type: "POST",
  url: "http://[yourserver]/[yourCSInstance]/api/v1/auth",
  data: { username: [username], password: [password] },
  beforeSend: function( xhr ) {
  // Set the correct MimeType for the request.
  xhr.overrideMimeType( "application/x-www-form-urlencoded" )
  }).done( function( data ) {

var val = JSON.parse( data );
  alert( "setting otcsticket to: " + val[ "ticket" ] );
  // Store the ticket for later use.
  otcsticket = val[ "ticket" ];


To authenticate, a $.ajax call is used. The REST call to do this is “/api/v1/auth”. The data must contain a valid username and a valid password for this user.

If the call is finished, then the JSON array (in the response) must be parsed for the key “ticket”. The value is the authentication token which should be stored somewhere for further use. Normally, the name otcsticket is used for this.

The token should be send as a header in any request, like in this example:

$.ajax( {
  type: "POST",
  url: "[yourURL]/api/v1/nodes",
  data: bodyData,
  headers: { otcsticket: otcsticket },
  beforeSend: function( xhr ) {
  // Set the correct MimeType for the request.
  xhr.overrideMimeType( "application/x-www-form-urlencoded" )
  } ).then( function ( response ) {
  }, function ( jqxhr ) {
  } );

(This is a call to upload a file, for the complete example on uploading files with REST see the posts in January 2017)