Inspired by the last post of Mark, I have created a small CSJS snippet which allows to optimize the behaviour of a Partial Refresh. Normally, if you execute a Partial Refresh, all elements of a form are sent to the server. Take a look at this XPage:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:inputText
id="inputText01"
value="#{sessionScope.inputText01}" />
<xp:inputText
id="inputText02"
value="#{sessionScope.inputText02}" />
<xp:inputText
id="inputText03"
value="#{sessionScope.inputText03}" />
<xp:inputText
id="inputText04"
value="#{sessionScope.inputText04}" />
<xp:inputText
id="inputText05"
value="#{sessionScope.inputText05}" />
<xp:inputText
id="inputText06"
value="#{sessionScope.inputText06}" />
<xp:inputText
id="inputText07"
value="#{sessionScope.inputText07}" />
<xp:inputText
id="inputText08"
value="#{sessionScope.inputText08}" />
<xp:inputText
id="inputText09"
value="#{sessionScope.inputText09}" />
<xp:inputText
id="inputText10"
value="#{sessionScope.inputText10}" >
</xp:inputText>
<xp:div id="refreshMe">
<xp:label
value="#{javascript:java.lang.System.currentTimeMillis()}"
id="labelNow" />
</xp:div>
<xp:button
value="Normal Refresh"
id="buttonNormal">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="partial" refreshId="refreshMe">
</xp:eventHandler>
</xp:button>
</xp:view>
The button refreshes only a small portion of the XPage, the DIV element refreshMe. It does not require any of the posted field values, it just refreshes the DOM element in the frontend. But when clicking the button, the posted data to the server contain all the fields and their values, which is not necessary in this case.
This is how the request looks like in Firebug (I have prefilled all fields with the values 1..9):
The response of the server is – as expected – the actual HTML code for the refreshed DOM element:
The optimized version adds the option clearForm to partial refreshs. When using this option, only the XPages internal fields are sent to the server, but DOM will be refreshed correctly:
<xp:button
value="Cleaned Refresh"
id="buttonCleaned">
<xp:eventHandler
event="onclick"
submit="false">
<xp:this.script>
<![CDATA[
XSP.partialRefreshPost(
'#{id:refreshMe}',{
clearForm: true,
}
);]]>
</xp:this.script>
</xp:eventHandler>
</xp:button>
Now, the POST looks like this:
You may think „This is useless, you can do a XSP.partialRefreshGet instead!“
That’s why there is the second option additionalFields. This allows to define all fields you want to update during the refresh:
<xp:button
value="Cleaned Refresh"
id="buttonCleaned">
<xp:eventHandler
event="onclick"
submit="false">
<xp:this.script>
<![CDATA[
XSP.partialRefreshPost(
'#{id:refreshMe}',{
clearForm: true,
additionalFields: ['#{id:inputText01}',
'#{id:inputText02}' ],
}
);]]>
</xp:this.script>
</xp:eventHandler>
</xp:button>
When clicking the button now, the specified fields are added to the POST request (and will be updated in the JSF tree:
Here is the snippet (Tested on IE 11, Chrome 33 & FF 27 with ND9 & ND 8.5.3 )
<xp:scriptBlock id="scriptBlockPROptimized">
<xp:this.value><![CDATA[
XSP.addOnLoad(function(){
// hijack the existing partial refresh method
if( !XSP.__partialRefresh ){
XSP.__partialRefresh = XSP._partialRefresh;
}
// add the new one to the XSP object
XSP._partialRefresh = function x_prfh(method, form, refreshId, options){
// clear the form?
if( options.clearForm ){
// create a new HTML form...
var newForm = document.createElement( "form" );
newForm.setAttribute( "method", form.method );
newForm.setAttribute( "action", form.action );
// ... and loop all existing fields
for( var i = 0; i<form.length; i++ ){
var field = form[i];
var fieldName = field.name;
var includeField = false;
try{
// check for addition fields
if( options.additionalFields ){
includeField = dojo.indexOf(options.additionalFields, fieldName)!=(-1)?true:false;
}
// only add XPages relevant fields and addtional fields
if( fieldName == form.id || fieldName.substr(0,2) == '$$' || includeField ){
var newField = null;
if( field.options ){
// special handling for fields with options
for( var j=0; j<field.length; j++ ){
if( field.options[j].selected ){
newField = document.createElement( "input" );
newField.setAttribute( "type", "hidden" );
newField.setAttribute( "name", fieldName );
newField.setAttribute( "value", field.options[j].value );
newForm.appendChild( newField );
}
}
}else{
// default field handling: just clone the DOM element
newField = field.cloneNode( true );
newForm.appendChild( newField );
}
}
}catch(e){
console.log(e);
}
}
// call the original refresh method with the new form
return XSP.__partialRefresh(method, newForm, refreshId, options);
}
XSP.__partialRefresh(method, form, refreshId, options);
};
});]]></xp:this.value>
</xp:scriptBlock>
Just add the script block above to your XPage, or move it into a CSJS library. But keep in mind that you have to think twice when removing data from the request. It can lead in an inconsistence between the data in the client and the data stored in the component tree on the server.
That is really smart Sven – great post mate!! 🙂
I’m confused. You said „But wait, this is useless, you can do a XSP.partialRefreshGet instead!“ and then you show another POST.
Oops Sorry for that! For clarification what I mean:
If you just want to reload a DOM element, you can do a PartialRefreshGet. Then, some phases of the JSF lifecycle are skipped and the DOM is reloaded (and recalculated if it is a label). In this case, it is useless to do a PartialRefreshPost, cleaned or not.
But you cannot fire an event handler this way. To fire an event handler, it is required to send a PartialRefreshPost.
Sven, I have extended your solution to include support for server-side actions: http://per.lausten.dk/blog/2014/09/xpages-optimized-partial-refreshes-for-event-handlers.html.
Pingback: XPages: Optimized partial refreshes for event handlers | Per Henrik Lausten
Pingback: Using 2 lines of code to solve 2 days of frustration | Do Not Replicate