You might not need WebReports: Workflow Attachments

You might not need WebReports: Workflow Attachments

Matthew Barben
Driver Lane
Published in
13 min readMar 6, 2021

--

This is part of my series looking at why You may not need WebReports

The Setup

You having your spiffing new Content Server and you now want to create a simple workflow where a user submits a document where it can be reviewed.

As the process relies upon the reviewer being able to view a document you want to ensure that a user attaches a document.

The many ways to solve a riddle so simple…

There are good ways and bad ways to approach this — but in my mind, there are three ways:

  • The straight bat — address the requirement straight on. This will give us a chance to point out some flaws and suggest some improvements
  • The re-engineer — repose the question using some of the other capabilities that exist in the product
  • And fancy pants — a way that addresses the problems with the straight bat, and hopefully offer a nice user experience along the way

The straight bat

In this approach, we won't look to do anything fancy — a few fields and with the attachments enabled.

The workflow map for the straight bat workflow
The straight bat’s workflow map

Does this fit the requirement — well yes but with very little validation. Simply put, using standard features there is no way to enforce users toupload attachments to the workflow when they submit the workflow.

Workflow screen for Straight bat workflow
Showing the fields that are mandatory

This is not a new problem to Content Server workflow — it has been around since the old Livelink ways.

Enter the anti-pattern

Now the any-pattern to this problem is to use a WebReport and have it check the workflow’s attachments volume and confirm that there is a document attachment.

This is a relatively quick change to the workflow — which adds a check for the workflow attachments and a loop-back

The Straight Bat workflow with the WebReport step and loop back
The workflow map with the added WebReport check and loopback

This check takes the form of:

  • a WebReport that checks the workflow’s attachment volume. This will populate an attribute on the workflow that will indicate it has attachments or not.
  • An evaluation step to determine the outcome of the WebReport
  • and an additional user step (assigned to the initiator) to nag the user to correctly attach a document

The WebReport itself is simple enough (it will likely need to be more robust for productive environments)

[LL_WEBREPORT_IF "[LL_REPTAG_&subworkid WFINFO:ATTACHFOLDER NODEINFO:CHILDCOUNT /]" == "0" /]
[LL_REPTAG_'No' SETWFATTR:'hasAttachments' /]
[LL_WEBREPORT_ELSE /]
[LL_REPTAG_'Yes' SETWFATTR:'hasAttachments' /]
[LL_WEBREPORT_ENDIF /]

However, this approach introduces some unintended consequences…

Runtime Issues

Running a WebReport is a one-and-done affair —if the data is not ready in time, then there a chance that the above WebReport will not run correctly and will return to the user with the falsy claim that they have not attached a document.

An ugly user experience

What this introduces is some ugly user experiences into a process where you would ideally want a frictionless experience for your end-users.

You have a user interface that enforces the entry of metadata fields but gives the users a free pass on actually doing the one thing that the workflow is designed for — reviewing the document.

The final insult — not attaching the document the end-user will now have to re-process an additional step finally add that attachment. In time-critical processes, this is kind of time-wasting might be unacceptable.

The re-engineer

Without a heavy amount of customisation, an alternative approach to this problem is to initiate a workflow from an already uploaded document instead.

Using this approach a user would upload a document to Content Server. And then initiate the workflow.

Start a workflow from a document.

From there when the workflow starts — the document (or a shortcut to the document — we will get to that). But it does prefill the attachment volume — but again as it is not mandatory means that it is possible to have a user submit a workflow without attachment

The submission screen for the re-engineered flow

The anti-pattern returns

Again — because we don't have that assurance that the user will always submit the correct information — we have to reach again to WebReports to bail us out. In this situation we have to overcome the following:

  • Confirm that the document is attached
  • If a shortcut is attached — replace the shortcut with a copy of the actual document.

Again — this is reasonably straight forward and we can expand on the previous WebReport — but already we are starting to increase the number of activities that need to be performed (below is an example of what is required):

[LL_WEBREPORT_IF "[LL_REPTAG_&subworkid WFINFO:ATTACHFOLDER NODEINFO:CHILDCOUNT /]" == "0" /]
[LL_REPTAG_'No' SETWFATTR:'hasAttachments' /]
[LL_WEBREPORT_ELSE /]
[LL_REPTAG_&subworkid WFINFO:ATTACHFOLDER NODEINFO:CHILDREN SETVAR:Attachments /]
[LL_REPTAG_%Attachments CURRENTVAL RECARRAY:1 SETVAR:OriginalDoc /]
[LL_REPTAG_%OriginalDoc CURRENTVAL RECORD:origindataid SETVAR:ogDoc /]<br />
[LL_REPTAG_%OriginalDoc CURRENTVAL RECORD:dataid SETVAR:shortCut /]<br />
[LL_REPTAG_%OriginalDoc CURRENTVAL RECORD:subtype /]<br />
[LL_WEBREPORT_IF "[LL_REPTAG_%OriginalDoc CURRENTVAL RECORD:subtype /]" == "1" /]
[// Action Required. This is a document

[// Delete ShortCut
[LL_REPTAG_%shortCut CURRENTVAL NODEACTION:DELETE /]

[// Copy Document to the Attachments volumne
[LL_REPTAG_%ogDoc CURRENTVAL NODEACTION:COPY:[LL_REPTAG_&subworkid WFINFO:ATTACHFOLDER /]:INHERITATTRS:SOURCE /]


[// Set Workflow Attr
[LL_REPTAG_'Yes' SETWFATTR:'hasAttachments' /]


[LL_WEBREPORT_ENDIF /]
[LL_WEBREPORT_IF "[LL_REPTAG_%OriginalDoc CURRENTVAL RECORD:subtype /]" == "144" /]
[// No Action Required. This is a document
[LL_REPTAG_'Yes' SETWFATTR:'hasAttachments' /]

[LL_WEBREPORT_ENDIF /]
[LL_WEBREPORT_ENDIF /]

I have cheated a little bit by assuming only one record is entered. Of course the more complex the requirement the more complex engineering that will be required.

And like our previous it can still be prone to the same issues:

  • Runtime issues (if it does not get the correct context in time)
  • potential for the ugly UX issues (however hopefully reduced)

Fancy Pants

In this approach, we look at using the forms-based functionality that exists within the Content Server. It might take a bit more work but the end result will give the user the immediate feedback that they require.

The primary difference in this approach is that instead of initiating the workflow using a workflow — we will use a form.

The Form

For all the workflow so far I have used the same Form Template object to carry the same two fields. For this example, we will be relying more on the Form Templates functionality. To start with we will just use some very boring looking HTML and then build upon that to add more, to begin with — but by the end, we will add some additional

Instead of using the Smart UI view, we are going to configure our own view using this form template. This is going to allow us to introduce our own business logic into the form.

So let export the form as HTML

And add it as a new HTML view on the form

And finally — lest create the form object. To do this we select the Form Template and choose ‘Make Form’ from the functions menu.

Select Make Form from the Function menu

The Add: Form has two screens, the first selects the submission and revision mechanism for the form. We are select a revision mechanism of none because we don't want the form to contain the data that the last user entered, and we select the submission mechanism as Initiate Workflow

Select the Revision and Submission Mechanism

The next Add: Form screen — you select the Name for the Form, the Workflow Map that you would like to use, and the Custom View.

Select Name of Form, the Workflow Map, and the Custom View

We can now open the form we get a very drab (replete with nested tables being used for layout) form that simply allows us to submit the work which starts our workflow.

Simple Workflow Submit Form

Adding a coat of CCS

Now that we have the basic form, with a bit of fiddling we can import the CCS and HTML from the Smart UI interface. This is a rough cut, but it will suit our purposes.

Classic UI form made to look like Smart UI

Now before we get into JavaScript changes we need to make a few changes to the form.

Update the action from this:

<form NAME="myForm" ACTION="[LL_CGIPATH /]" ENCTYPE="multipart/form-data" METHOD="POST">

To this:

<form>

In this example, we will take advantage of the REST API that has been implemented alongside the Smart UI.

A link to the HTML is here

Add the JavaScript to the Form

Now that we have the HTML sorted out we need to add some JavaScript that will handle the following:

  • Get the information on the workflow process, using we can determine the Attachment Volume
  • Add a mechanism to enable people to upload a document to the Attachment volume
  • Check the Attachment Volume before submitting to ensure that the user has attached a document.

Note: Because we are rolling our own form, we would usually do our own form validation. But for now, we will focus on the document validation.

So let's get some housekeeping out of the way:

Authentication: To get the authentication token I am going to use the LL_Cookie value — now this may not always be available, but it should be on a default installation. In a bit of cheat, I am using https://github.com/js-cookie/js-cookie to pull the cookie.

Workflow Values: To reduced the amount of hardcoding elements to the script I am pulling the workflow values from the form. These are stored on a hidden element on the page:

<INPUT TYPE="hidden" NAME="LL_FUNC_VALUES" VALUE="[LL_FUNC_VALUES /]">

To use this, I am adding an id to this form element

INPUT TYPE="hidden" NAME="LL_FUNC_VALUES" ID="WorkflowValues" VALUE="[LL_FUNC_VALUES /]">

And then creating a little function that will parse the value to a JSON object that we can use:

HTTP Requests: And finally I am adding a function that will handle the HTTP requests — it will return the request in its native form so there will be times we will need to parse it into JSON. Additionally for POST and PUT requests will need to encode as application/x-www-form-urlencoded …. because … Content Server. However, when using the mine type of application/x-www-form-urlencoded it does mess up the format that is being posted to Content Server so to make sure that I am sending form fields I add the following for POST and PUT methods

request.overrideMimeType("multipart/form-data");

Anyway below is a basic handler that we will use to manage the HTTP calls to Content Server.

Cancel Button — In a production-ready version this might be mapped to something better, but for now, we will update the button to be:

<button class="binf-btn binf-btn-default" title="Cancel" onclick="goBack()">Cancel</button>

And add the following function:

function goBack() {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const nextUrl = urlParams.get('nexturl');
const getUrl = window.location;
window.location.href = `${getUrl.protocol}//${getUrl.host}${nextUrl}`;
}

Handling Attachments

Let's sort out the attachments. To start we will add the following HTML, search for <div class=”workitem-attachment-toolbar-view tile-nav”> and add the following HTML within the tag:

<div class="workitem-attachment-toolbar-add">
<br>
<input type="hidden" name="attachmentid" id="attachmentid">
<input type="file" name="Upload Attachment" id="attachmDocument" style="display:none" multiple >
<a href="#" class="binf-dropdown-toggle csui-acc-focusable" title="Add attachment" aria-label="Add attachment" onclick="uploadFile()" >
<span class="icon icon-toolbarAdd"></span>
</a>
</div>

Next, we are writing some JavaScript that will trigger when the page is loaded and it will:

  • Get the workflow information from Content Server (attachment and form information)
  • Update the attachment id on the form
  • Display the attachments
  • Add the event listeners for the upload file, field validation, and submit functions

Below is the main code — but we will run through each function.

Upload a file

Let's build out the File Upload function. Because we are allowing our users to upload multiple files we need to cater for this in our business logic (aka the JavaScript). For now, the logic that we will use is:

  • Check the File Name
  • If that File Name exists in the attachment volume then add a version
  • If that filename does not exist then create a new document

When playing with POST requests and Content Server it is better to wrap information in FormData objects. The short explanation is that the Content Server application (the cs.exe) does not support posts using anything other than application/x-www-form-urlencoded.

Below is are the functions that we can use to manage that upload process.

You will note that I have also included the functions for adding a version and adding a document.

Display Attachments

Let's expand out the display attachment functionality. In our update we created three DIVs in our attachment area:

  • noAttachments — displays when there are no attachments detected
  • displayAttachments — displays when there are attachments
  • loadingAttachments — this is the default state and will display a spinner

Next, we need to add that business logic to that HTML. This is built with two functions:

  • displayAttachments — that return the documents from the ‘attachments volume’
  • renderList — when documents are attach, this function will render the HTML and attach to the displayAttachments div tag.

This will use the nodes method to return a list of documents. You will note from the URL we use additional parameters. This enables allows us to request just what we need from Content Server.

const url = `/otcs/cs.exe/api/v2/nodes/${attachmentid}/nodes?fields=properties{id,name, mime_type}`;

Doing this means will mean we return less data which will make these REST API calls more efficient.

When we have no results — we simply return a simple message indicating that documents need to be attached.

Form with no attachments

Once documents are uploaded we can then display the documents as a list of documents.

Form with attachments

Submit that form

Finally — let's complete the form by implementing a submit form logic. Now given that we don't have to rely on the form template we can clean up the HTML. This means we can remove some of the boilerplate HTML. For this we will replace this:

<input CLASS="valueEditable" TYPE="text" NAME="_1_1_2_1" TITLE="Text Field 1" ID="_1_1_2_1" VALUE="[LL_FormTag_1_1_2_1 /]" MAXLENGTH="32" ONCHANGE="markDirty();">

With this:

<input type="text" name="Text Field 2" id="textfield1"  title="Text Field 2" class="valueEditable dlvalidate" placeholder="Text Field 1" maxlength="32">

Then update the other form element:

<input class="valueEditable" type="text" name="_1_1_3_1" title="Text Field 2" id="_1_1_3_1" value="" maxlength="32" onchange="markDirty();">

To this:

<input type="text" name="Text Field 2" id="textfield1" title="Text Field 2" class="valueEditable dlvalidate" placeholder="Text Field 1" maxlength="32">

Now the submit and cancel buttons. For this we remove the CS submit hook, so we change the submit button from:

<button class=”binf-btn binf-btn-default” title=”Start” ONCLICK=”doFormSubmit( document.myForm );”>Start</button>

To this:

<button class="binf-btn binf-btn-default" title="Start" id="startWorkflow" type="submit">Start</button>

With that done we can wire up our own submission logic. This will take two parts, the event handlers which added to the main ready function.

const validateForms = document.getElementsByClassName('dlvalidate');
Array.from(validateForms).forEach((element) => {
element.addEventListener('input', validateField);
element.addEventListener('focusout', validateField);
});
// Submit Form
const submitControl = document.getElementById('startWorkflow');
submitControl.addEventListener('submit', (event) => {
dlSubmitForm(attachmentid, otcsticket);
});
submitControl.addEventListener('click', (event) => {
dlSubmitForm(attachmentid, otcsticket);
});

And then add the validateField and dlSubmitForm functions.

The dlSubmitForm function will:

  • make a REST API call to check that there are attachments in the attachment volume
  • Check the fields to ensure there are entry
  • Create an object to a submission object
  • Submit the form

Now that is a bunch of work — but the end result is a better result for the end-user... Immediate feedback. Front ending this logic means that your workflow should hopefully progress smoother.

No document attached error message
Form field not entered

So what did we learn?

In short — if Content Server workflow attachments could be treated as a required field out of the box there would be no need for Fancy Pants or any process re-engineering to occur.

If you are going down the Fancy Pants route — given that you are now rolling your own validation, it becomes a nice point to consider using a framework to take some of the validation issues out of it. At Driver Lane, we use Angular as it gives you built-in Form Validation support, reuse of components (like document selectors and dropdowns), and also a service layer that simplifies a lot of the communication with OpenText Content Server.

The other main advantage of going down the Fancy Pants route is the ability to implement your own business logic onto the form e.g. If the user selects an engineering document, then display engineering metadata to enter. Right now that kind of business logic is not available with the Smart UI implementation (there are solutions out there such as Answers Module and Global Cents).

Finally — we end up with a solution that is build using HTML and JavaScript. And whilst it might seem intimidating right now — remember that there are likely to be more resources out there in the job market who can maintain HTML and JavaScript than there are people who can maintain a WebReport.

If you would like to review the code, the gists that are used are available here, and a complete repo is located here.

Coming up in the next few weeks mixing up and Search and Rest APIs to turn Content Server into an application archive.

Matthew Barben is a co-founder, EIM consultant at Driver Lane. You can follow Driver Lane on Twitter, and LinkedIn, or directly on our website.

--

--