Monthly Archives: July 2013

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.