Tag Archives: JavaScript

Love it, or hate it – it’s got the power.

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.

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.

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.

Turning Edit Pages into View Pages

When creating the front end for an Intrexx application you usually set up an edit page where the user can create new data records and edit them later on. Then, to display the data you will often want to have a dedicated view page for presentation. When the data tables are large, however, i.e. when they consist of many different fields, maintenance of these different edit and view pages becomes increasingly difficult and time-consuming. In this post I want to show how Intrexx edit pages can be reused for presentation purposes.

Input vs Output

It is often best to have similar if not identical layouts for the edit and view pages. Different arrangement and positioning of fields and sections between edit and view pages is likely to confuse users. Most of the time the nature of the data will lend itself to one specific way of presentation that feels intuitive and logical anyway. So when you find yourself rebuilding an edit page as a view page, replacing all input controls with display controls, it may be worthwhile to enhance the edit page so that it can be used for presentation.

The easiest way of depriving an edit page of its edit functionality is by removing all save buttons. Fortunately, Intrexx provides the Conditional display option for buttons so you can write some sort of logic that controls the rendering of the buttons using Velocity. This is particularly important because it can prevent the rendering of the save button on the server side. If you were simply hiding the save buttons with CSS one could easily hack the site by manipulating the markup with Dev Tools, Firebug and the like.

There are countless possibilities for determining whether a page should behave as an edit page or as a view page. The most obvious is probably the use of a request parameter that is set when the user opens the page by clicking entries of a certain table, such as an “Archive”, whereas another table “My Tasks” would open the edit perspective by providing a different value for the request parameter or just omitting it altogether. The problem with request parameters is that a savvy user could potentially (re)set them through the url.

Another thing to consider is user rights. While it is easy to assign access rights for the edit page only to an Intrexx group of editors you would have to be more careful if all the magic now was to happen in the same edit page for both editors and readers. Again, fortunately all the neccessary tools are there, with Velocity context objects such as the $AccessController. In fact, you should see this as an opportunity to carefully re-think the setup of your application’s pages with regards to security. Are your edit pages savely protected against access of users who must not edit the data? Consider a situation where edit/view perspective is determined by some sort of context such as a status field in the data record, so that e.g. “finished” items are shown in a table from where they can only be viewed while “open” items are shown in a different table that opens the edit page: What if a user explicitly enters the url using rq_TargetPageGuid? Would it be possible to access any edit page with any data record (by changing rq_RecId)? If you haven’t thought about these questions before chances are that your application becomes much safer if you only have one edit page where you consciously and explicitly control the behaviour.

Types of input controls in Intrexx

Once you made sure that there is no save button that shouldn’t be there, it is time to disable the page’s input controls. To do so it is probably a good idea to look at the markup and (JavaScript) functionality of the various Intrexx input controls. With jQuery being included in Intrexx by default the markup allows for a convenient way of iterating over the controls. The following table lists all relevant input controls as of Intrexx 6 along with their Intrexx names in both English and German and with their respective markup by which they can be identified:

Control type Selector
Button (Button)Schaltfläche (Schaltfläche) input[type="submit"]
Button (Text)Schaltfläche (Text) a, generic link with no recognizable attributes :(
Button (Image)Schaltfläche (Bild) a[role="button"]
Edit fieldEingabefeld input[type="text"]
Text areaTextfeld textarea
Option fieldOptionsfeld input[type="radio"]
CheckboxKontrollkästchen input[type="checkbox"]
Selection listAuswahlliste select
List fieldListenfeld select
Multiple selectionMehrfachauswahl div[id^="ID_distributioncontrol"], complex markup
Sortable listSortierbare Liste div[data-selectcontrol] or div[id^="ID_sortablelisteditcontrol"], complex markup based on ul
File selectionDateiauswahl input[type="file"]
Image selectionBildauswahl input[type="file"] (has accept="image/*")
TreeBaum div[data-selectcontrol] or div[id^="ID_treecontainer"], complex markup based on ul

Since the save functionality has been disabled on the server side we can probably ignore the three button types*. Basically what we end up with is two categories of input controls: Firstly, standard HTML form elements that can be easily targeted by their markup such as input, select and textarea; secondly, complex controls that consist of various elements that are difficult to identify and disable. Let’s deal with the easier ones first.

*If you do use buttons for non-saving purposes, don’t worry, though – we’ll look at that later on.

Disable standard form elements with jQuery

Writing a simple JavaScript function that makes use of jQuery to select all standard form elements of a page and disable them is not too hard. Especially since jQuery conveniently comes with the appropriate (non-CSS) :input selector already. Also make sure to use prop() instead of attr().

function disableInputControls(p_strWrapperId){
	if(!p_strWrapperId)return;
	$('#'+p_strWrapperId).find(':input').prop("disabled", true);
}

Note that it is important to provide a hook for the selector in form of the page id. Without specifying the parent element p_strWrapperId the selector would target all input elements on the current page, including the surrounding portal with its portal search field and button as well as other tooltip windows that may be open when the function executes.

This leaves us with three remaining complex controls still enabled. Let’s look at each of them:

Multiple selection: This control contains a wrapping DIV element that can’t really be targeted other than by its default class (DistributionControlHorizontal) or id (e.g. ID_distributioncontrol7C168C4E). Assuming that an application developer is much less likely to change the control’s name attribute (via the expert attributes tab in the Portal Manager), which determines the HTML id attribute, than they are likely to change the class, going with a selector like this is probably successful 99% of the time: div[id^="ID_distributioncontrol"]. But what to do with this element now? The control consists of two buttons for moving (i.e. selecting) entries from the left to the right and back (i.e. deselecting). These are inputs so they were already targeted by the jQuery selector above. However, the user can still double-click on entries of the lists in order to move them to the respective other list. Looking at the markup shows that this control is made up of two unordered lists.

Sortable list: The Sortable list is another control with complex functionality. Similarly to the Multiple selection it has a containing DIV element; this one with a custom attribute called data-selectcontrol. However, this attribute is far from suffiently distinct: the Multiple selection also has a descendant div with data-selectcontrol. A more reliable solution is probably, again, to work with the control’s id structure: div[id^="ID_sortablelisteditcontrol"].

Tree: Just like the Sortable list control there is a div[data-selectcontrol] element but it’s it’s probably better to go with div[id^="ID_treecontainer"]

So there are ways to find these controls within a page. The problem is because of their markup there is no reliable generic way to identify their parent elements. Often descendant elements have ids that are based on the parent id and have some sort of extension, like ID_distributioncontrol7C168C4E_selected_hidden. Depending on how the control is to be disabled this ambiguity can be a problem.

Disable elements with CSS

The often overlooked CSS property pointer-events allows to completely disable an HTML element and its children when set to none. Browser support for this property is decent enough, with only Internet Explorer spoiling the party. If you have to support IE though (like me), this is not an option. If it is, your function might look like this:

function disableInputControls(p_strWrapperId){
	if(!p_strWrapperId)return;
	var l_ojPage = $('#'+p_strWrapperId);
	l_ojPage.find(':input').prop('disabled', true);
	l_ojPage.find('div[id^="ID_distributioncontrol"]').css('pointer-events','none'); //Multiple selection
	l_ojPage.find('div[data-selectcontrol]').css('pointer-events','none'); //Sortable list & Tree
}

And you’re done. Life could be so easy.

Disable elements with clicktrap DIV

A simple alternative to the CSS pointer-events approach could be to just create DIV elements and place them above the controls so that they function as a clicktrap for the mouse.

l_ojPage.find('div[id^="ID_distributioncontrol"]').wrap('<div style="position:relative;" />')
	.parent().prepend('<div style="position:absolute;left:0;top:0;height:100%;width:100%;z-index:1;" />');

Unfortunately there are several problems with this. While the aforementioned ambiguity of the selectors is no problem for the pointer-events solution, it is here: The above code messes with the layout as it is applied to child elements. To make things worse, the Sortable list control in my application is not even a DIV element – it is a TD!

<td valign="top" id="ID_sortablelisteditcontrol654224A2" colspan="12" rowspan="2">

Crazy! I have no idea why Intrexx thinks that this is sensible markup, but that is what is generated.

I did consider and experiment with a few other solutions such as creating more complex selectors to make sure the correct element is found or searching for registered controls in Intrexx’ internal JavaScript objects. Ultimately I found all this to be way too much for such a simple task. If you think about, how many and how often do you use these controls in your pages? The answer to this question led me to the following solution.

Disable additional elements

The Multiple selection, Sortable list and Tree control are so rarely used and complex that it makes little sense to explicitly cater to them in a generic disable function. They are so special that it is fair to assume that an application developer wishing to disable all input elements on an edit page can take care of these controls explicitly. Therefore the function is extended but another parameter that allows to pass the guids of those elements that should be disabled in addition to standard input elements.

function disableInputControls(p_strWrapperId, p_aExplicitInputs){
	if(!p_strWrapperId)return;
	var l_ojPage = $('#'+p_strWrapperId);
	l_ojPage.find(':input').prop('disabled', true);
	
	var l = p_aExplicitInputs? p_aExplicitInputs.length : 0;
	for(var i=0; i<l; i++){
		var key = p_aExplicitInputs[i];
		key = getElement(key)? getElement(key).id : document.getElementById(key)? key : false;
		if(!key)continue;
		$('#'+key).filter('div').wrap('<div style="position:relative;" />')
			.parent().prepend('<div style="position:absolute;left:0;top:0;height:100%;width:100%;z-index:1;" />');
	}
}

To deal with TD parent elements I simply chose to filter for DIVs. Now this makes the selector ignore the Tree and Sortable List, which are both TD in my current page, but this is easily made up for by the fact that you can now group elements within a DIV container in the Portal Manager and pass the container’s guid to this function. Thus you can easily disable any section of the page, not just these specific controls.

I also threw in a bit of extra code to allow the passing of both guids and ids for identifying the elements.

Excluding Cancel and Close Buttons

When a page is opened as a modal tooltip it usually has some kind of Close button. Not the X button in the upper right corner but a proper button like the Cancel that usually sits next to Save. These buttons – Close and Cancel – should be excluded from the disable function. Luckily they can be identified rather easily: in Intrexx each button has a linktype. You can look up each button’s linktype in the expert attributes tab in the Portal Manager. The integer value of the linktype is determined by the button’s behaviour. If the button executes no action (such as save or delete) and does not open a new page, then its linktype is -2. In JavaScript you can check a button’s linktype like this:

getElement('C68...476').oUp.linkType == '-2'

So with a slight adjustment to the function we can no disable all inputs except for those buttons that do no real action.

// Disable standard input elements:
var l_ojPage = $('#'+p_strWrapperId);
l_ojPage.find(':input').each(function(){
	var l_oElem = getElement(getGuidById(this.id));
	if(l_oElem && l_oElem.oUp.linkType == '-2')return;
	$(this).prop('disabled', true);
});

Note the use of getGuidById(). This global function takes an id as a parameter and returns the corresponding Intrexx control’s guid. This allows to iterate over all input elements using the jQuery function and connecting the resulting elements to the Intrexx control JavaScript object.

The Finishing Touches and Food for Thought

The header of the edit page hopefully already consists of Velocity to communicate the page’s purpose to the user:
#if($DC.getRecId==-1)Create new Data#{else}Edit Data
If you use a request parameter (or other Velocity variable set in some way, like e.g. $bIsViewPage) to determine whether the edit page should behave as a view page then this should be incorporated into the title:
#if($DC.getRecId==-1)Create new Data#elseif($bIsViewPage)View Data#{else}Edit Data

When an edit page is meant to be used as a view page the idea behind disabled input elements changes. Normally, in an input form you have greyed out read-only fields that are clearly distinguishable from editable input elements. In a page layout that is purely meant for presentation of data this default styling makes little to no sense. You will probably want to create some CSS that makes the disabled elements on your edit page look nice and friendly (no grey background, no borders(?)). Maybe it is a good idea to add a certain class to the page container which can serve as a hook for the CSS selectors like .viewPage input{}.

I haven’t mentioned Data Pickers yet. They shouldn’t be a problem though, since the input fields as well as the Data Picker buttons themselves should be successfully disabled at this point.

Textareas may still have the little resize handle in the bottom right corner which gives the elements an iappropriately editable feel. You will probably want to style your textareas, irrespective of whether the page is in write or read-only mode.

I haven’t come across a use case yet, but theoretically it is possible to have complex filter on the edit page that should not be disabled, yet fall victim to the function. In that case you will probably have to manually enable them again after calling disableInputControls().

Summary

The JavaScript function disableInputControls() allows to disable all standard input elements of an Intrexx edit page. Additional elements and containers to disable can be passed via an optional parameter. This is especially neccessary for Intrexx’s complex controls such as Multiple selection, Sortable List and the input Tree.
In addition to calling this function you must ensure that save and delete buttons do not render in the HTML by using Velocity.
Here’s the final version of the function:

/**
	Disables all input elements (input, select, button, textarea).
	Usage: disableInputControls('ID_editpageC3FD4043',['1B3..8AC','ID_simplegroup1859F722']);
	@param p_aExplicitInputs - Optional array of guids or ids of additional elements to disable.
	These must belong DIV elements.
*/
function disableInputControls(p_strWrapperId, p_aExplicitInputs){
	if(!p_strWrapperId)return;
	
	// Disable standard input elements:
	var l_ojPage = $('#'+p_strWrapperId);
	l_ojPage.find(':input').each(function(){
		var l_oElem = getElement(getGuidById(this.id));
		if(l_oElem && l_oElem.oUp.linkType == '-2')return;
		$(this).prop('disabled', true);
	});
	
	// Explicitly disable elements from p_aExplicitInputs:
	var l = p_aExplicitInputs? p_aExplicitInputs.length : 0;
	for(var i=0; i<l; i++){
		var key = p_aExplicitInputs[i];
		key = getElement(key)? getElement(key).id : document.getElementById(key)? key : false;
		if(!key)continue;
		$('#'+key).filter('div').wrap('<div style="position:relative;" />')
			.parent().prepend('<div style="position:absolute;left:0;top:0;height:100%;width:100%;z-index:1;" />');
	}
}

Data Picker and readonly input

The Data picker in Intrexx is a very versatile and convenient input control. It provides an easily configurable user interface for filtering and selecting from huge lists of data, where a conventional drop down list would simply fail on many aspects. However, in a recent project we encountered a use case that seems not to be covered yet by the existing configuration options. When clicking on the data picker button and selecting an entry from the list the data picker will always insert the chosen value – even if the text input is set to readonly. Of course, there are situations where you want exactly that behaviour. In our case, however, we did not. The problem was that we were dealing with many data pickers where the corresponding input fields are dynamically set to readonly depending on a certain context. And we had a couple of edit pages like this.

Because of the amount of data pickers it was not really an option to manually insert some type of function call for each button’s onclick event, explicitly passing the guid of the input field as an argument every time. This would have been very tedious to both set up and maintain. A much more general solution was needed. In situations like these it is usually a good idea to open your browser’s console and have a look at the possibilities.

Solution 1: Long and dirty

The first thing I looked at was the button’s oUp property:

getElement('151...D8F').oUp

Unfortunately, there seems to be no direct connection between the button and the data picker. If you look at the button’s XML structure in the Portal Manager you’ll notice that the data picker is actually a property of the button and has its own guid.

<buttoncontrol guid="151...D8F" name="buttoncontrol5947FCD9" actionid="actNone" app-image="false" app-image-over="false" fixedheight="false" fixedwidth="false" image="" image_over="" link-sourcedataapp="D8D...E6E" link-sourcedatafield="551...93F" link-sourcedatagroup="495...D3B" linktype="5" newwindow="false" rect="150,40,30,20" sendemail="false">
    <property>
        <title de="DP" en=""/>
        <events>
            <event guid="B46...10B" action="datapicker" link-datapicker="A5F...3C3" type="onclick"/>
        </events>
        <datapicker guid="A5F...3C3" link-control="199...F12">
            <map guid="6D4...072" link-control="199...F12" link-datafield="551...93F"/>
            <triggers>
                <trigger link-event="B46...10B"/>
            </triggers>
        </datapicker>
    </property>
</buttoncontrol>

Still, how could we get the data picker JavaScript object using the button as a starting point? Since there is no suitable property that would direct you to the data picker I chose to parse the button’s onclick value. If you set up a button as a data picker the button’s onclick will look something like this:

function onclickHandlerbuttoncontrol5947FCD9(e) {
	try{var rv = new Boolean(true);
		rv = true;
		if(typeof(e)!="undefined"){self.oUp.oEvent=e;}
		else if(typeof(event)!="undefined" && event!=null){
			self.oUp.oEvent=event;
		}
		rv=triggerDatapicker('A5F...3C3', '151...D8F', self.oUp.oEvent);
		if(!rv){
			if(bProcessFormAction){return;}
			else{return false;}
		}
		this.oUp.processRequest(e); 
		if(bProcessFormAction){bProcessFormAction = false;}
		else{return false;}
	}
	catch(exc){
		Notifier.status.error("Error in onclick:" + exc.message, "onClick");return false;
	}
}

The relevant part is the call of triggerDatapicker():

triggerDatapicker('A5F...3C3', '151...D8F', self.oUp.oEvent);

The first argument is the guid of the data picker, the second is the guid of the button itself. So we can obtain the data picker’s guid from any button by parsing the onclick code:

getElement('151...D8F').onclick.toString().match(/triggerDatapicker\('(.*?)',.*?\)/)[1]

This will return the guid of the data picker. To get a proper JavaScript object of this data picker, you can use getDatapicker(). The returned object has a property called strControlGuid, which contains the guid of the input field. Conveniently, it also has a direct reference to said input field via getElement():

var l_strInputFieldGuid = getDatapicker('A5F...3C3').strControlGuid;
var l_oInputField = getDatapicker('A5F...3C3').getElement();

Note that while getElement() is included in the current JavaScript documentation for Intrexx, getDatapicker() is not.

With the above code snippets it is now an easy thing to define a function in the application’s JavaScript file which uses a button object as an argument, finds the corresponding input field, checks whether it is set to readonly and if so returns false. Then you can add a function call to each data picker button in the Portal Manager which could look something like this (note that it must be executed before the internal Data Picker call):

checkInput(this);

No need to look up the guids of the input fields anymore, but still a bunch of work to do manually. If you want to achieve a very specific functionality this may be a viable solution. Obviously it is far from perfect, though, as it makes assumptions on how Intrexx creates its JavaScript code. A simple change such as switching the order of the two guid arguments would be enough to break the entire logic. Anyone who has been through the upgrade of Intrexx 4.5 to 5.0 knows that these things can happen.

Solution 2: Short and sweet

While having a closer look at the actual data picker method defined in external\htmlroot\include\all.js I came across the data picker registry object. It can be accessed via the global oHtmlRoot object and contains a map of all the data pickers. All that is needed is the guid of the current page.

oHtmlRoot.oUp.oRegistry.oFuncPartReg['<page guid>'].oRegistry.oDatapickerReg

This allows us to loop over all data pickers and extend their standard pick() method so that it will always check whether the corresponding input field is readonly.

We’re almost done, but there is still one last thing to consider. Setting the input field to read-only will create a readonly property in the input’s markup like this:

<input id="ID_textcontrolABC" value="" readonly>

However, a common way of controlling the state of input elements in Intrexx is by calling disable() and enable() like so:

getElement('151...D8F').oUp.disable();

This will add the disabled property to the markup, so we need to check for that also. This can be done with getElement().oUp.isEnabled().
Attention: Note that getElement().oUp.bEnabled does not work – it returns true even after calling disable()!

The Code

The finished function that can be put in the application script looks like this:

function makeAllDatapickersRespectReadonly(p_strPageGuid){

	var l_oFPR = oHtmlRoot.oUp.oRegistry.oFuncPartReg[p_strPageGuid];
	if(l_oFPR){
		var l_oAllDataPickers = l_oFPR.oRegistry.oDatapickerReg.oMap
		for(key in l_oAllDataPickers){
			if( !l_oAllDataPickers.hasOwnProperty(key) || l_oAllDataPickers[key].constructor.name != 'upDatapicker' ){
				continue;
			}
			var oDp = l_oAllDataPickers[key];
			oDp.pick_original = oDp.pick;
			oDp.pick = function(e,p){
				// Injected JS code:
				if(this.getElement().readOnly || !this.getElement().oUp.isEnabled())return false;
				// Now call original method:
				this.pick_original(e,p);
			};
		}
	}
}

Note the use of hasOwnProperty() in line 7. This ensures that we’re only looking at those properties defined on the map itself, discarding any inherited properties. This is considered a best practice when iterating over an object’s properties with for in loops.
The second check of .constructor.name verifies that the object we’re about to look at is actually a proper upDatapicker instance.

The pick_original name was chosen for illustration purposes really. By choosing any such name you can potentially ruin the entire functionality if such a property already exists. Unless you come up with a very specific name that is completely unlikely to be chosen by United Planet as a new property for the data picker object (maybe including your company’s name or something like that) a generated guid for a name is the safe way.

Now the function makeAllDatapickersRespectReadonly() can be called in the onload script of each edit page where we want to have this special data picker behaviour. In fact, I believe this function is sufficiently useful and general to warrant inclusion in the portal-wide custom.js file or in a custom helper JS library if there is such a thing in your environment. A useful extension for this function could be to pass an array of guids as a second parameter which contains only those data pickers that should actually be affected by the function, defaulting to all data pickers when no array is given.