Save & Close Pattern for IBM BPM Coaches

When working on tasks in BPM users often do not complete their work in a single session. Instead, they will open their task, enter some data on the page and then close their task to resume work at a later point. Users may or may not properly close their task by pressing a dedicated button, or they may click on another link in the Process Portal, or open a new link in the current browser tab, or even simply close the tab or the entire browser altogether. This raises a number of questions and this article discusses a simple pattern for handling the saving and closing of unfinished tasks in IBM BPM.

The idea is to provide a “Save & Close” button that users can press when they want to save the current progress of their task and return to the work list. Of course, you could implement variations of this by providing separate “Save” and “Close” buttons where “Save” would not close the task and “Close” would work the way as the “Save & Close” described here.

First, let’s take a look at the diagram for the Heritage Human Service.

We will use a Boolean variable resumeAfterPostpone that is set to true after the Postpone step. In the On Load step this variable is reset to false and the Boolean variable showWarningMessage is set depending on whether the task was resumed from postpone or not.

After Postpone:

tw.local.resumeAfterPostpone = true;
tw.local.showWarningMessage = false;

On Load:

tw.local.showWarningMessage = !tw.local.resumeAfterPostpone;
tw.local.resumeAfterPostpone = false;

So how does this “On Load” event work? The Coach contains a Coach View “Trigger on Load” that fires a boundary event during its View event. So every time the Coach is generated – be it the first time the task is opened or any subsequent access to the task – the event is fired and the “On Load” script is executed.

For this simple example the Coach consists of a section which contains a Custom HTML element with the warning message to be shown if the user closed their task in any other way than pressing the “Save & Close” button.

Displaying the message is controlled through a Visibility rule based on the showWarningMessage variable.

Now, if you open the task and did not use “Save & Close” last time, the text box is shown:

The above solution obviously does not prevent users from closing the tab or browser anyway. However, implementing an event handler for the window’s onbeforeunload, which is the usual (annoying!) way of preventing users from leaving your site is not really an option for BPM coaches. You have to handle both cases: 1) the Human Service is openend alone and 2) the Human Service is opened in the iframe of the Process Portal. The event does not work with boundary events that have already been sent off. The implementation described here is a compromise between technical feasability and educating users how to use the system.

Cached Data Library

In my previous post I described a technique for storing the results of Ajax calls in the browser and looking them up before making subsequent calls. This way unneeded Ajax calls can be prevented and a huge performance gain can be achieved. This is especially effective for Coach Views that are used inside of Tables in IBM Business Process Manager.

Since I am lazy and don’t want to think about how to actually implement this technique every time I want to make an Ajax call in a custom Coach View, I prefer to keep functions like these in a utility JavaScript library that handles everything for me. This could look like this:

var cachedData = (function(){
	// Create a unique string identifier from the ajax service's url and the given params
	var _createAjaxId = function(fnAjax,params){
		var url = fnAjax.toString().replace(/[^]*url\s=\s\"(.*)\"[^]*/,"$1"),
			serviceId = url.replace(/.*service\/(.*)\?.*/,"$1").replace(/-/g,'_').replace(/[^\w]/g,"");
			paramid = JSON.stringify(params).replace(/\s*"[^"]+"\s*:/g,"").replace(/[^\w]/g,"");
		return 'ajx'+ serviceId + paramid;
	},
	
	// Creates a shared object for the given properties:
	_createShared = function(p_sName){
		cachedData._shared = cachedData._shared||{};
		cachedData._shared[p_sName] = cachedData._shared[p_sName]||{ ready: false, loading: false};
		return cachedData._shared[p_sName];	
	},

	// Returns the globally shared object. If it cannot be found, it is created.
	_getSharedObj = function(p_sName){
		try{ return cachedData._shared[p_sName]||_createShared(p_sName); }
		catch(e){ return _createShared(p_sName);}
	},

	// Returns the content of the globally shared object:
	getShared = function(p_sName){
		return _getSharedObj(p_sName).content;
	},

	// Sets the shared object and executes all registered handler functions:
	setShared = function(sName,oData){
		if(oData){
			_getSharedObj(sName).content = oData;
			_getSharedObj(sName).ready = true;
			
			var handlers = _getSharedObj(sName).onloadhandlers, i;
			for(i=0; i<handlers.length; i++){
				 if(typeof handlers[i] == "function")handlers[i](oData);
			}
		}
		_getSharedObj(sName).loading = false;	
	},
	
	// The main function which wraps the ajax call:
	ajaxCached = function(props){
		if(!props.load||!props.context)return;
		var fnAfterLoad = props.load,
			sServiceName = props.serviceName,
			oContext = props.context,
			params = props.params,
			sName = _createAjaxId(oContext.options[sServiceName],params);

		// Load data and store in shared object:
		if(_getSharedObj(sName).ready){
			fnAfterLoad(getShared(sName));
		}
		else {
			_getSharedObj(sName).onloadhandlers = _getSharedObj(sName).onloadhandlers || [];
			_getSharedObj(sName).onloadhandlers[_getSharedObj(sName).onloadhandlers.length] = fnAfterLoad;
			if(!_getSharedObj(sName).loading){
				oContext.options[sServiceName]({
					params: JSON.stringify(params),
					load:function(data){
						setShared(sName,data);
					}
				});
				_getSharedObj(sName).loading = true;
			}
		}
	};

	// Expose public methods:
	return {
		get : getShared,
		set : setShared,
		ajax : ajaxCached
	}
})();

What it does is provide a wrapper function for the Ajax call. All that needs to be done to get this to work is upload it to your project (Process App or Toolkit), link to it in the Coach View where you make your Ajax call, and then make the call in the following fashion:

var _this = this;
cachedData.ajax({
	context: _this.context,
	serviceName: "sampleAjaxService",
	params: {text: "sampleInput"},
	
	// Callback after shared data is loaded:
	load: function(data){
		console.log(data);
	}
});

Adjust sampleAjaxService and params to your specific use case and voilà: Your Ajax calls’ results will be cached and unneccessary calls will be prevented using the cached data instead.

Note that in order to store the results a unique key is created from the Ajax service’s name (based on its URL, not the name of the configuration option as that is not unique) and any parameters that were submitted via the params property. This means that the same Ajax service only uses the cache if the parameter configuration is the same as from a previous call.

Shared Data across Coach Views

This article describes a design pattern for having Coach Views share commonly used data in a sensible way in the browser. This can drastically decrease redundant Ajax calls and thus have a very positive impact on the Coach’s performance.

Use Case: Dynamically loading localization

To illustrate the technique we will build a simple example where we can save a great number of unnecessary Ajax calls. Imagine the following use case: You have a list of objects (think Task List) where each item has its own status. This status is stored as a key. In the browser the user must be presented with a properly spelled out status name instead of the technical key. This is handled by a specific Coach View which retrieves the status names via an Ajax service.

Building the basic assets

To get the example running we create a few assets as shown below.

Demo – Share Data (Heritage Human Service)

This Human Service does nothing more than display our list of statuses. The data model is a simple list of Strings stored as a local variable taskList.

In the diagram a Server Script Init initializes the taskList with a couple of Strings, which will be the keys that need to be converted to proper status names.

The Coach UI contains a Table stock control that is bound to the taskList. It has the Buttons for Adding and Deleting entries enabled. Inside the table we use a stock Text control and our custom Status Coach View to display the values of the current entries of the list.

Common (Localization Resource)

The Localization Resource Common stores the status names in two languages – Default (English) and German. This is the basis for the status names and it is what should be used by the Coach View for displaying the statuses.

loadLocalization (Ajax Service)

This Ajax Service returns a Map with the status keys as, well, the key and the respective localized values as the – you guessed it – value.

Note how we can take advantage of the way the localization keys are structured in Common. Since the keys are identical we can e.g. get the localized value for the status approved by writing the following:

tw.resource.Common.status['approved']

For this example we chose to let the service return a subset of all possible statuses. It only returns the active states inprogress, open and sentback – the remaining statuses which represent terminated states (approved, completed and rejected) are not included.

Status (Coach View) – Initial Solution

Finally, this is the center-piece of this example. The Coach View shall take the status string and display it as a properly localized text. For this it has a binding to a string, which is the status key. To retrieve the localization values the loadLocalization Ajax service is set up as a Configuration Option.

The layout consists of a simple Custom HTML element which provides the markup for the status name to be inserted.

To get this going we make this first implementation, which is not very well-thought-out but all the more intuitive. We will point out the problems of this implementation and approach better solutions from there.

In the load event the Coach View fires the bound Ajax Service (line 10) which retrieves the localization map. If this is successful, the returned data is assigned to the statusMap variable (14) and the setStatus function is called (15). This will then get the localized status from the map and insert it into the markup (7), using “-” as a fallback in case the status key could not be found in the map.

Identifying Superfluous Ajax Calls

The problem with the above implementation becomes apparent if we open developer tools in the browser (e.g. Firebug on Firefox or the built-in tools) and take a look at the network traffic:

The taskList variable in the Human Service is initialized with seven items, resulting in seven requests sent to the server as the table control builds its rows each one with its own Status Coach View. What’s more, if we click the + button to add a new entry, another eight requests are made as the load event for the Status controls is fired, once for each row. Similarly, if we click one of the X buttons to remove an item another seven requests are made. By now we have sent a total of 22 requests to the server, which probably should be no more than a single one. Clicking on one of the Text inputs and changing a status (e.g. from “inprogress” to “open”) results in another request as the Coach View is loaded anew.
It is easy to imagine how this gets out of hand when dealing with large data sets. Also, there may be other controls that cause reloads – perhaps you want your table to automatically update every few minutes to always display the most current data? All these requests quickly become a huge strain on the Coach. So let’s see what we can do about it.

Shared Data after Ajax Callback

Unfortunately, the various instances of the Status control don’t know about each other. They don’t know whether the data they need has already been loaded before and even if they did, how would they access this data?

While the Coach Views can’t directly access each other they all share the same global context. This means they share the same window object or any other property of window. However, one must always be extremely careful when pushing anything to the global namespace. Deciding to do so should never be taken lightly. If you are not careful, then your global objects are removed or overwritten by the next best JavaScript library or tiny code snippet, resulting in unexpected and erroneous behavior.

In this example we will use the global com_ibm_bpm_coach object and its property currentprojectsnapshot as a hook for our own data. While this may make us feel less bad about polluting the global namespace, we are faced with the same question again: How can be store objects here and be sure they don’t get overwritten? The answer is simple: We cannot. So to mitigate the risk we must choose a unique identifier for our own namespace.

There are numerous sensible ways for defining a name that is most likely unique. You can use the acronym of your process app or toolkit or come up with creative names. If you want to use the technique described in this post as part of a assets which belongs to a certain project or toolkit you might want to create global object by that name. Or maybe you already use a global namespace derived from your company name or your client’s company name.

The code for creating a globally available object for all Coach Views might look something like this:

// Create global namespace for this coach view's shared content:
var snapshot = com_ibm_bpm_coach.currentprojectsnapshot;
var cvspace = "statusCoachView"; // namespace for this control
var sharedObject = "statusMap"; // name for the shared object
com_ibm_bpm_global = com_ibm_bpm_global||{};
com_ibm_bpm_global[snapshot] = com_ibm_bpm_global[snapshot] || {};
com_ibm_bpm_global[snapshot].shared = com_ibm_bpm_global[snapshot].shared || {};
com_ibm_bpm_global[snapshot].shared[cvspace] = com_ibm_bpm_global[snapshot].shared[cvspace]||{};
com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject] = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject]|| false;

Note how no object is ever overwritten. If com_ibm_bpm_global already exists, it is assigned itself. Only otherwise it is created as an empty object. The same pattern is used for the subsequent properties, except for the shared object property. This is because the empty object {} is truthy in JavaScript. So if we want to check whether it has been filled already, it is the easiest to have it initialized with a falsy value.

With this in mind the full implementation of the Status control’s load event now looks like this:

var _this = this;

function setStatus(){
    var sStatus = _this.context.binding?_this.context.binding.get("value"):'';
    var oSpan = _this.context.element.querySelector('.statusText');
    oSpan.innerHTML = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject][sStatus]||'-';
}

// Create global namespace for this coach view's shared content:
var snapshot = com_ibm_bpm_coach.currentprojectsnapshot;
var cvspace = "statusCoachView"; // namespace for this control
var sharedObject = "statusMap"; // name for the shared object
com_ibm_bpm_global = com_ibm_bpm_global||{};
com_ibm_bpm_global[snapshot] = com_ibm_bpm_global[snapshot] || {};
com_ibm_bpm_global[snapshot].shared = com_ibm_bpm_global[snapshot].shared || {};
com_ibm_bpm_global[snapshot].shared[cvspace] = com_ibm_bpm_global[snapshot].shared[cvspace]||{};
com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject] = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject]||false;

if(com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject]){
    setStatus();
}
else {
    this.context.options.getLocalizations({
        params: {},
        load: function(data){
            if(data && data.statusMap){
                com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject] = 
                    data.statusMap;
                setStatus();
            }
            else console.warn("Coach View Status: No values retrieved.");
        }
    });
}

Now, each Status control instance will first check whether the [sharedObject] has been set to a truthy value. If this is the case, the status is set using the existing map. Only otherwise the Ajax call is made. In this the global object is filled with the retrieved data for later reuse.

If we now take another look at the browser, there is no difference during the initial load: There are still seven requests being made. However, if we now add a new item or delete an existing one, no further requests are made.

What is going on?

Shared Data from the Beginning

In the implementation presented previously the data returned from the Ajax Services is made available globally for all to use and reuse. However, these requests take time. When the page is built and all the Coach Views are created, their load events are fired quickly after each other. In fact, this happens so quickly that by the time the second, third and all other subsequent events are fired the response from the first Ajax call has not come back yet to store its data. Therefore, all the remaining Coach Views think they should retrieve the data themselves, resulting in the same number of request during page load.

Only by the time the user itself takes further actions, such as adding or deleting items, the Ajax calls have returned and stored the data successfully so that it can be reused then.

To fix this situation we must improve the communication between the Coach Views on what is going on regarding the loading process. We must ensure that only the first instance of the Coach View makes the Ajax call and no other instances do so. In addition, when this first call comes back, it must do what the other instances wanted to do, namely adjusting their individual status names.

Firstly, we introduce another level to the hierarchy. The shared object now is no longer simply the data which should be retrieved. Instead it is an object with three properties:

  • loading: If this is true, there is currently an Ajax service on the way, busy loading the data. Initially this is false. Once the Ajax returns it is set back to false again.
  • ready: If this is true, the data has been successfully loaded. Initially this is false.
  • content: This is the actual data to be stored and shared.
  • responsehandlers: This is an array of functions which are all executed in the Ajax service’s callback.

With these properties the Status instances can communicate: Once the first instance started the loading process, the others do not need to trigger an Ajax call themselves.

Next we must ensure that the response handlers do what the second and other Status control instances did not get to do the first time: set their statuses. We achieve this by having each instance add a function to the responsehandler object. The functions serve as response handlers when the Ajax Service comes back from the server. Each instance adds its own function to the array. Once the callback is made, the shared data is stored and all these functions are executed.

With all the above incorporated into the Coach View’s load event, the code looks like this:

var _this = this;

function setStatus(){
    var sStatus = _this.context.binding?_this.context.binding.get("value"):'';
    var oSpan = _this.context.element.querySelector('.statusText');
    oSpan.innerHTML = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].content[sStatus]||'-';
}

// Create global namespace for this coach view's shared content:
var snapshot = com_ibm_bpm_coach.currentprojectsnapshot;
var cvspace = "statusCoachView"; // namespace for this control
var sharedObject = "statusMap"; // name for the shared object
com_ibm_bpm_global = com_ibm_bpm_global||{};
com_ibm_bpm_global[snapshot] = com_ibm_bpm_global[snapshot] || {};
com_ibm_bpm_global[snapshot].shared = com_ibm_bpm_global[snapshot].shared || {};
com_ibm_bpm_global[snapshot].shared[cvspace] = com_ibm_bpm_global[snapshot].shared[cvspace]||{};
com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject] = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject]||{ ready: false, loading: false};


if(com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].ready){
    setStatus();
}
else {
    if(com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].loading){
        com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].onloadhandlers = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].onloadhandlers || [];
        com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].onloadhandlers[com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].onloadhandlers.length] = function(){
            setStatus();
        };
    }
    else {
        this.context.options.getLocalizations({
            params: {},
            load: function(data){
                if(data && data.statusMap){
                    com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].content = data.statusMap;
                    com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].ready = true;
                    setStatus();
                    
                    var handlers = com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].onloadhandlers;
                    for(var i=0; i<handlers.length; i++){
                         if(typeof handlers[i] == "function")handlers[i]();
                    }
                }
                else console.warn("Coach View Status: No values retrieved.");
                com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].loading = false;
            }
        });
        com_ibm_bpm_global[snapshot].shared[cvspace][sharedObject].loading = true;
    }
}

Now the Ajax Service is truly only ever called once – no matter how many instances of the Coach View are created and no matter how many times they get reloaded.

Good Practices for Using Dialogs in IBM BPM

The Dialog control from the Dashboards toolkit gives you a great tool for creating pop-ups and dialogs in IBM BPM 8.x. This post describes a few practices that help get the most out of the control to increase user experience and make development easier.

Dialog Container

When using Dialogs they take up a lot of empty space in the Coach. Once they are opened, the empty space collapses and will remain collapsed subsequently. However, the initial space before the first use is quite annoying. Especially when using many Dialog controls the empty space adds up to a large area. This can be prevented easily by using the following pattern:

  1. Add a Vertical Section at the end of the Coach.
  2. Under HTML Attributes add a style attribute with the value display:none.
  3. Put all Dialog controls inside this section.

Note that if you use the new client-side human services this will make the section disappear in the web-based designer! This is obviously not ideal for development. In this case assign a class or Control Id and use that for some CSS via a Custom HTML element which is not executed during authoring time.

If you happen to use a custom template Coach View it makes sense to incorporate the dialog container into the template. This makes it very easy for those working with the template to put the Dialogs into their appropriate place without the hassle of creating a specific container on each Coach.

Close Button

Users expect to have a Close or Cancel button to close the dialog window. By default dialogs have a small X icon at the upper right-hand side corner to do this. A better user experience is a proper button similar to what users are known from their operating system. Unfortunately, you cannot simply use a stock button and bind it to the same Boolean variable as the dialog, because when clicked, buttons always set their bound variable to true – they don’t switch the value. However, it is quite simple to build a custom coach view that creates a button which will switch its bound variable, which then closes the dialog.

Dialog Content Wrapper

If your dialog contains numerous controls it may make sense to create a Coach View that wraps all the content. This can be either a composite Coach View that directly contains the specific controls for reuse. But it can also be a generic Coach View with a Content Box, similar to how you would build a Coach View template. The template can then already include the Close button. In both cases the Content Wrapper Coach View provides a way to control what is going on in the dialog, e.g. by implementing “open” and “close” handlers.

“Open” and “Close” Handler

Sometimes you need to perform certain actions when the dialog is opened and when it is closed again. If you use a Dialog Content Wrapper as described above, this is easily possible. Give the wrapper a configuration option (or binding) for a Boolean variable called showDialog and assign to it the same variable as the dialog’s binding. So whenever the dialog opens and closes, that variable is changed, and you can handle it in the wrapper Coach View’s change event handler:

if(event.type == "config"){
	if(event.property == "showDialog" && event.newVal != event.oldVal){
		if(event.newVal == true){
			console.log('The dialog is opening...');
		}
		if(event.newVal == false){
			console.log('The dialog is closing...');
		}
	}
}

Soft Validation Popup in IBM BPM

When it comes to doing validations in IBM Business Process Manager (IBM BPM) it is usually sufficient to use the standard techniques of the CoachValidation framework. But what if you only want to warn the user and give them the option to either review their input or continue anyways? How can this be modelled?

In this example we have a Coach where the user can enter input for three Decimal fields. If any of the Decimals is empty when the user hits Submit we want to open a modal window which warns the user. Below you can see the Coach Step 1 with the Decimal controls, a Submit button and the Dialog (from the Dashboards toolkit).

To control when the Dialog is shown we need a Boolean variable showWarning. The text in the dialog is filled dynamically and stored in the warningMessage String. Together with the Decimals the variables in our Heritage Human Service look like this:

The showWarning variable is initialised in the step Prepare Coach right before we enter the Coach.

The full diagram looks like this:

When the user clicks Submit the validation is executed. In case the validation was successful the flow continues to the Show Warning step. This contains the logic for the “soft validation”. While the Validate script contains blocking validations as usual (such as required fields which are empty), the Show Warning script contains the logic for showing the warning dialog (e.g. if any of our optional Decimal fields is still empty).

If this “soft validation” results in the warning dialog needing to be shown the showWarning variable is set to true and the Show Warning? gateway leads to a stay on page instead of continuing to the next Coach Step 2.

Now the user is presented with the warning dialog and can choose to either Submit anyway, which will lead the process flow to Step 2 (as if the warning had not appeared), or they can click Review input which will cause the user to stay on the page and close the dialog by setting showWarning to false in the Close Warning script.

This results in a “soft validation”, this case checking whether the optional Decimals fields were set at all.

Wrapping Existing Event Handlers in JavaScript

When working with frameworks, such as Intrexx or IBM Business Process Manager, you often want to extend existing UI controls. You want to prepend or append event handlers to the ones already in place. This post explains how this can be done.

Why not addEventListener?

The obvious solution is simply using addEventListener. However, sometimes you want to do a little more, and need a little more control. Perhaps the event handler that needs to be extended is not a standard event. Or perhaps you need to do something specifically before or after the existing function is called.

Wrapping an existing function

Suppose we have an object obj with a property change(). The trick is to store the original function away and replace it with a new function which calls the original function. The concept looks like this:

// Store original function:
var _change = obj.change;

// Replace with new function:
obj.change = function(){
  // Do something before...
  _change();
  // Do something after...
}

A full example can be seen below. Changing the value of the text field triggers the change funtion. In the JavaScript tab you can see the additional function that “wraps” the original function, i.e. calls the original function.

See the Pen Example 1 by Christian Templin (@cianty) on CodePen.

Fixing this

The above pattern is a bit buggy. Since we “move” the original function into our new function any code which uses the this keyword gets broken. Take a look at the following example: On every change the original functionality calls an alert which prints the text input’s value. By moving the function, this no longer refers to the text input.

See the Pen Example 2: We broke this by Christian Templin (@cianty) on CodePen.

The solution to this is using call or apply. When executing the function via call/apply you can explicitly provide the this context, so that the original functionality is kept intact.

See the Pen Example 3: Fixed this by Christian Templin (@cianty) on CodePen.

Conclusion

The following pattern can be used to prepend or append code to an existing JavaScript function. Whether to use call or apply depends on how you want (need) to pass your arguments.

// Store original function:
var _change = obj.change;

// Replace with new function:
obj.change = function(){
  // Do something before...
  _change.call(this);
  // Do something after...
}

On Opening modal Tooltips in Intrexx

Sooner or later when running an intranet portal with numerous applications you will want to provide the users with links. Not simply links to the portal’s home page, probably not even to the starting page of some application: You will need links that direct the user to a specific data record/entry. These links could be needed within an application – maybe as part of some table or list. Most commonly though, they appear in email notifications where the specific data is linked directly for the user’s convenience.

Generally, linking to data records is not much of a problem in Intrexx. All you need is a few request paremters and you’re good to go. However, if your applications get fancier, chances are that you do not have a simple viewpage to open in the main window. We do have a fair number of applications that consist of a starting page where all the entries are listed and the individual data records are viewed only in a modal tooltip window on top of that starting page. So how can you provide links to specific data records when your application is built like this?

The URL and Request Parameters

In order to create a link that goes somewhere specific and that even transports additional information you need to set some request parameters. The minimum to create a meaningful url that goes beyond the portal start page is to speficy the application guid (rq_AppGuid), the guid of the page to display (rq_TargetPageGuid) and – if needed – the record id (rq_RecId). In Velocity you can create these links with the $UrlBuilder context object (see API here).

To make the application behave in some special way I find it the easiest to provide a custom request parameter and then deal with that in the applications onload event.

Suppose your url looks something like this:


http://<baseurl>?rq_AppGuid=D8D...E6E&rq_TargetPageGuid=ED1...EFD&rq_OpenRecId=31

Note that instead of the standard rq_RecId I introduce my own custom rq_OpenRecId. The rq_AppGuid and rq_TargetPageGuid parameters are enough to open the application’s starting page, which usually does not need a specific record id because it is a viewpage on the top level and not an editpage below a data group.

On the starting page place a static text field set to “Programming”. This allows you to define some JavaScript functionality which depends on the custom request parameter. It could look something like this:

<script>
function onLoad_openTooltip(){
  #if($Request.containsKey("rq_OpenRecId"))
    #set($strTTRecId = $Request.get("rq_OpenRecId") )
    var strTTRecId = "$!strTTRecId";
    // JavaScript code to open tooltip:
    // ...
  #end
}
</script>

If the function onLoad_openTooltip() is called in the page’s onload event it will use rq_OpenRecId to execute the JavaScript code that opens the modal tooltip.
Note that the conditional Velocity #if ... #end must not wrap the function definition itself as that would cause an error whenever rq_OpenRecId is missing.

In the above example line 7 is still missing the actual JavaScript code to open the modal tooltip. Let’s look at how this can be achieved.

Open Tooltips with JavaScript

Using the Anchor tag

It is very easy to create the HTML markup for modal tooltips in Intrexx. It is also well documented, too. All you need is the URL with the neccessary request parameters – rq_AppGuid, rq_TargetPageGuid and rq_RecId (in hex format) – and a few data attributes in the anchor tag.

<a href="?rq_AppGuid=D8D...E6E&rq_TargetPageGuid=ED1...EFD&rq_RecId=31" 
  data-hijax="tooltip" 
  data-hijax-tooltip="modal" 
  data-hijax-tooltip-props="strTitle:false"
  id="ID_mylink">
    click me</a>

To trigger this link, a simple jQuery call will suffice:

$('#ID_mylink').click();

Hide the anchor away, put the jQuery call in line 7 mentioned above and you’re ready!

But…

Creating markup when it is not really needed is never the best idea. You basically pollute your HTML with hidden elements, so this is really a – ugh! – workaround. We can do better than that! Let’s look at an alternative JavaScript-only solution.

Using the showTTP() function

The standard method for opening an Intrexx application using JavaScript is calling the makeAppRequest() function. Given the values for rq_AppGuid, rq_TargetPageGuid and rq_RecId you can open a page in either the main window (stage) or as a popup window.

var strAppGuid = 'D8D...E6E';
var strPageGuid = 'ED1...EFD';
var strRecId = '1';
makeAppRequest(strAppGuid, strPageGuid, strRecId, true, true, false);

What you cannot do with this function is open a tooltip. For this, there is showTTP().

In order to open a tooltip window, use the showTTP() function. This function is listed in the official JavaScript reference for Intrexx but it is not well documented. It is also not that easy to use because of the many parameters that need to be passed. Let’s take a look at a sample call of this function:

var strAppGuid = 'D8D...E6E';
var strTTPageGuid = 'ED1...EFD';
var strTTRecId = '1';
var strTTName = 'MyTooltip';
var oRQParams = {
	rq_AppGuid: strAppGuid,
	rq_TargetPageGuid: strTTPageGuid,
	rq_RecId:strTTRecId
};
var oTTSettings = {
	bModal:true,
	strTitle:false,
	bCloseButton:true,
	bCloseByKey:true
};
Helper.showTTP(false,false,strTTName,oRQParams,false,oTTSettings);

No unneccessary markup, just a bit of JavaScript. Again, merge the above snippet into the example onload function and you’re done.

Using Intrexx buttons

I mentioned above that uneccessary elements should be avoided where possible. Unfortunately, they are super convenient to use. So just for reference, let’s look at what is probably the dirtiest albeit most intuitive of all solutions:

On the starting page, instead of creating an anchor tag as described above, place a button that opens the desired page and define the modal settings. If the target page is an edit page you can simply choose it in the dialog. If it is a viewpage, choose an editpage instead – we’ll correct that next.

The JavaScript code to adjust the tooltip’s page and record id and to trigger the button looks like this:

var strTTPageGuid = 'ED1...EFD';
var oButton = getElement("158...8A8");
oButton.oUp.oTarget.rq_TargetPageGuid = strTTPageGuid;
oButton.oUp.oTarget.rq_RecId = strTTRecId ; // from the request parameter
oButton.click();

In closing, opening data in modal viewpages is an increasingly common design pattern in our portal, so I hope that the Intrexx API will be extended with a more convenient version of showTTP(), one that is similar to makeAppRequest(). In the meantime, you can always wrap showTTP() in a utility function and make that available across all applications using custom.js.

Open Data Records in new Intrexx Popups

This post is in response to a question asked on the Intrexx Live! forum.

In Intrexx you can configure links to open the target page in a new window. This is called a Popup and is opposed to Tooltips which are opened within the current window, either displayed as a contex help or as a modal overlay on top of the page.

Open as Popup

To open the target page as a popup go to the Actions tab within the button properties. Under Destination chose Open as popup.

Configure a link as popup

Individual Popups from View Tables

A common use case is to have a list of data records shown in a view table. Then, by clicking on the name or description of the entries or via a dedicated button the data records open in separate popup windows. This allows the user to move and arrange the windows next to each other so that they can compare the data records.
By default, Intrexx will create a single popup window and update it when the user clicks on another entry. To have links in a view table open as individual popups open the properties window. In the Expert tab add a new attribute called sameWindow with the value no.

Individual Popups from Shaped Tables

When you want to have an especially stylized interface you usually opt for a Free Layout Table, also called Shaped Table, instead of a standard View Table. The shaped table uses a view page to display the entries. If you want to open individual popups from the shaped table’s view page, however, the above technique will fail. You can add a button to the shaped table’s view page and configure it to open as popup. But the sameWindow expert attribute has no effect here.

If you still want to have your individual popups you can just build the link manually. Place a static text field, set to Programming, on the shaped table’s view page. Using HTML and Velocity you can write the code to create the link in here. To do this you need to know a few things:

  • The neccessary HTML markup is described in the Links in Intrexx tutorial. Basically you need an A element with the data-hijax="popup" attribute and a href attribute that targets the Application (rq_AppGuid), the page to open (rq_TargetPageGuid) and the data record’s id (rq_RecId).
  • To access the current data record’s id when inside of a view table or a shaped table use the $drRecord Velocity context object: $drRecord.getRecId().
  • The url in the A element expects hex-encoded record IDs. You can use the $Codec Velocity context object for this: $Codec.hexEncodeString("Hello", "UTF-8")

The resulting code for the programmable text field looks like this:

<a href="?rq_AppGuid=D8D...E6E&rq_TargetPageGuid=ED1...EFD&rq_RecId=$Codec.hexEncodeString($drRecord.getRecId(), "UTF-8")"
  data-hijax="popup"
  class="Button_Standard">Click me</a>

Replace the GUIDs for rq_AppGuid and rq_TargetPageGuid with yours.

Referencing data fields in the page

Chances are that you do not want to have a static text like “open” as the link. You could replace the Click me in the snippet above with a nice image. Usually you will want to display the value of a data field such as a title or name and wrap that in the link. To do so you need to reference the field’s value for the current data record.

You can make individual data fields from the data group excplicitly available in view/edit pages. First, get the GUID of the data field that you want to use on your page by going to the data group and looking up the properties of the data field.
Now open the view page that is used in the shaped table and go to the Expert tab. Switch to the Settings pane and add a new setting. Up to Intrexx 5.2 you had to enter two separate keys:
page.requiredDataField.?.guid takes the GUID of the data field you want to access.
page.requiredDataField.?.identifier defines the name by which you want to access the data field. This can be any name, such as myVar, name, title, you get the idea.
By assigning a key index you map the guid to the identifier. If you want to reference multiple data fields use different keys (1, 2, 3, …) for each pair of guid and identifier.

As of Intrexx 6 this is even better supported: Simply enter page.requiredDataField.?. The dialog now contains a table where you can fill in the GUID and name.

Once you told Intrexx to make the data field available in the page you can acces it in Velocity with $drRecord.getValueHolder('<name>').getValue(). Note that you have to use $drRecord because the page is used inside a shaped table. If you want to access a data field in a normal edit or view page, use $DC.getValueHolder('<name>').getValue() instead.

The resulting code for a link that opens individual popups and displays the name of the data records looks like this:

<a href="?rq_AppGuid=D8D...E6E&rq_TargetPageGuid=ED1...EFD&rq_RecId=$Codec.hexEncodeString($drRecord.getRecId(), "UTF-8")"
  data-hijax="popup"
  class="Button_Standard">$drRecord.getValueHolder('name').getValue()</a>

Improving Intrexx Session Security

In Intrexx a user’s session is identified by the session ID. This ID is usually hidden from the user, i.e. it is stored in a browser cookie (co_SId). When the session ID is not saved as a cookie it is passed as a request parameter: default.ixsp?rq_SId=521...F0F. This is when things get dangerous: If another user gets hold of your session ID, they can log in with your account – without needing to know your username or password. If your Intrexx portal features applications with various user rights and confidential information you should take steps to prevent this from happening.

Session Hijacking

You may be thinking: Why would a user want to steal another colleague’s session ID? We are mostly speaking of intranets here. And how would they know how to do it?
The point is, when cookies are disabled in your browser, Intrexx has no choice other than to store the session ID via the URL. A few years ago, considering cookies a security risk was all the rage. Things are different today and with Web Storage there is another option with good browser support. But the way things are, the session ID gets exposed as easily as disabling cookies in your browser. To make things worse, there are still a few situations where Intrexx will put the session ID in the URL even with cookies enabled. These situations are rare and will most likely die off over the coming updates and as new versions of the software are released. Still, as of today session IDs in URLs are a viable security risk.

Session Fixation

Once you have another user’s session ID session fixation is very easy to do. All you need to do is insert the parameter to the portal’s URL: rq_SId=521...F0F. The problem is that users will share their session ID unconsciously. They have no idea what the various parameters such as rq_AppGuid, rq_RecId or rq_MenuGuid stand for and they shouldn’t need to. Often times they will want to share a specific page of the intranet with a colleague so naturally they will copy and send their URL address, just like they are used to from most websites.

Session Security Concepts

To more reliably bind a session to a specific user you need to store additional information about the client as part of their session. Then, whenever a page is opened check if these data still match the requesting client. Two common parameters for PHP session security are the following. I will use these ideas as the basis for an Intrexx implementation. To learn more about this topic, visit the PHP Security Consortium or read a book.

Check for User Agent

The user agent describes the user’s browser type. Chances are that a different user will also have a different user agent. However, this is mostly useful for public internet websites. In a closely controlled corporate environment most colleagues will be using the same browser. Another aspect to be aware of is that the user agent can be easily modified on the client-side, decreasing its reliability. Still, it is an indicator that something might be fishy. Also you might explicitly want to allow users to browse your site with two different browsers (though typically you won’t plus the average user wouldn’t know how to “reuse” their own session anyway).

Check for IP address

Most of the time different users will have different IP addresses and it is highly unlikely that they will change during a session. Again, a corporate environment can be problematic as users surfing behind a proxy will probably expose the proxy’s IP address. The PHP Security Consortium has this to say about it:

It is unwise to rely on anything at the TCP/IP level, such as IP address, because these are lower level protocols that are not intended to accommodate activities taking place at the HTTP level. A single user can potentially have a different IP address for each request, and multiple users can potentially have the same IP address.
PHP Security Consortium

So see for yourself whether this applies to your target group and environment’s set-up. Verifying the IP address can be a viable and suitable solution in some cases.

Implementing Session Security in Intrexx

The above concepts can be easily implemented in Intrexx. The client’s user agent can be retrieved using Velocity with $Browser.getUserAgentId() (or $Request.get("HTTP_USER_AGENT")), the IP address is available via $Request.REMOTE_ADDR. The full code for a Velocity macro that sets the user’s fingerprint based on these two attributes and verifies it against the current session could look like this:

#*
        Creates a fingerprint for the client based on the IP address and the user agent
        and stores it as part of the session.
        On every page where this macro is called the session will be verified against the
        actual current client data.
*#
#macro(verifySession)

        #set($strUserAgent = $Browser.getUserAgentId())
        #set($strIPAddress = "$Request.REMOTE_ADDR")
        #set($strFingerprint = "${strUserAgent}${strIPAddress}")
        #set($strFingerprint = $TextUtil.stringToHex($strFingerprint))
       
        ## Initialize Session:
        #if(!$Session.containsKey('USER_FINGERPRINT'))
                $DEBUG.info("Init session fingerprint")
                 $Session.put('USER_FINGERPRINT',$strFingerprint);
        ## If Session has been initialized...
        #else
                 #set($strOriginalFingerprint = $Session.get('USER_FINGERPRINT'))

                 ## Session does not fit to user:
                 #if($strOriginalFingerprint != $strFingerprint)
                        $DEBUG.error("[custom_preload.vm] Possible session fixation detected! Logging out...")
                        $Session.logout()
                 #end
        #end

#end

If a mismatch between the client’s data and the session data is detected $Session.logout() will immediately log out the current user and redirect them to the start page. Note, that if you have Single-Sign-On enabled, the user may not be redirected, but instead the page may open but with no working links (since the user is logged out). In that case you might want to trigger a client-side redirect, e.g. with makeAppRequest().

Normaly you would use some cryptographic hash function to obscure the stored IP address and user agent information just like you would do with passwords. For the sake of simplicity I am using $TextUtil.stringToHex($strFingerprint) instead here, which wouldn’t be a sensible solution for production code since it is reversible via $TextUtil.hexToString(). However, creating a proper getHash() function is not all that hard and nicely described in the wiki of the Open Web Application Security Project (OWASP). All you need to do is create a Java class with the given code, export as JAR-file and include it in Intrexx as a customcallable (read here). If you are running a larger portal with lots of functionality chances are that you already have some utility class that is just waiting for another useful method.

Now where to put this code? Simply copy the code into the custom_preload.vm file at internal/system/vm/custom/custom_preload.vm. This file is automatically included into each Intrexx page. Now you can call the macro #verifySession() on those pages that you want to protect using the programmable static text field or simply call it for all pages.

Generating Table of Contents with JavaScript

Inspired by Chris Coyier’s article at CSS-Tricks I decided to implement dynamically generated tables of contents for this blog.

Goals and Features

The purpose of the JavaScript function is to generate a table of contents for articles based on the headings found inside. So far, so good. Now, if you take a deeper look at it, there are a couple of things to consider.

  • Take all heading tags (H1 to H6) into account
  • Add ID attributes to the article headings where missing so they are not needed in the original markup
  • Handle multiple articles per page and make proper tables for each
  • Offer some settings for the wrapping jQuery selector, headline text and class name

The Code

Mostly the “handle multiple articles” aspect required special attention. How to set up a working jQuery selector will depend on the structure of your site’s markup – mine is based on WordPress’ Twenty Twelve default theme.
For the development I set up a pen on CodePen:

See the Pen JS-generated Table of Contents by Christian Templin (@cianty) on CodePen

Include JavaScript into WordPress

There are various ways for using JavaScript in WordPress. I put the code into a separate file custom.js and uploaded it to the blog’s folder. Then in footer.php the file is included right before the closing body tag:

<script src="http://code.jquery.com/jquery-1.10.1.min.js" type="text/javascript"></script>
<script type="text/javascript" src="<path to your file>/custom.js"></script>
</body>

Now the tables are automatically inserted into each article.

If you wanted you could easily include an enumeration by either using CSS counter-increment or the JavaScript variable that is already there in the function for the generation of the IDs.
For additional inspiration check out this pen by Joel Newcomer using the jQuery Waypoints plugin.