Category Archives: Intrexx

Intrexx is a portal software by United Planet.
www.unitedplanet.com

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.

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.