Reputation: 134
I came across a weird issue. Here is the situation:
I am using my solution regarding displaying and hiding content based on the screen size: https://xpagesandme.wordpress.com/2015/01/20/diving-into-bootstrap-and-font-awesome-part-3-displaying-hiding-content-based-on-device-type/
So, I set up a custom control, containing the UI for a mobile device. I am using the "rendered" property of said custom control to hide or display it, based on the param value from my post.
Here is the weird issue:
Inside the custom control, I have a button that is supposed to set a scoped variable, do a partial refresh on a panel inside a modal and then display the modal (the call to open the modal is inside the onComplete event of the event handler).
The modal opens, but the scoped variable was not updated.
When I remove the rendered property of the custom control, it works. If I put the rendered property back in again, it doesn't work.
Also, any simple action (i.e. opening a page) won't work. However, CSJS that I put into the onComplete or onStart event of the event handler, will be executed.
Any idea?
P.S.: Here is an example XPage. Remove the rendered property of any of the buttons and the code in the onClick event will work:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core">
<xp:eventHandler
event="onClientLoad"
submit="false">
<xp:this.script><![CDATA[$(window).on('load resize', function(){
var screenWidth = $(window).width();
var sDevice = '';
switch(true){
case (screenWidth < 768):
sDevice = 'mobile';
break;
case (screenWidth < 922):
sDevice = 'tablet';
break;
case (screenWidth >= 922):
sDevice = 'desktop'
}
XSP.partialRefreshGet( '#{id:pnlList}', {params: {'sDevice': sDevice}});
});]]></xp:this.script>
</xp:eventHandler>
<xp:panel
id="pnlList">
<xp:button
value="Button Mobile"
id="button1" rendered="#{javascript:param.sDevice == 'mobile';}">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="partial"
refreshId="pnlToUpdate"
onStart="alert('onStart')"
onComplete="alert('onComplete')">
<xp:this.action><![CDATA[#{javascript:sessionScope.put('sTestValue', 'Button Mobile clicked')}]]></xp:this.action>
</xp:eventHandler></xp:button>
<xp:br></xp:br>
<xp:button
id="button2" value="Button Tablet" rendered="#{javascript:param.sDevice == 'tablet';}">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="partial"
refreshId="pnlToUpdate"
onStart="alert('onStart')"
onComplete="alert('onComplete')">
<xp:this.action><![CDATA[#{javascript:sessionScope.put('sTestValue', 'Button Tablet clicked')}]]></xp:this.action>
</xp:eventHandler></xp:button>
<xp:br></xp:br>
<xp:button
value="Button Desktop"
id="button3" rendered="#{javascript:param.sDevice == 'desktop';}">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="partial"
refreshId="pnlToUpdate"
onStart="alert('onStart')"
onComplete="alert('onComplete')">
<xp:this.action><![CDATA[#{javascript:sessionScope.put('sTestValue', 'Button Desktop clicked')}]]></xp:this.action>
</xp:eventHandler></xp:button></xp:panel>
<xp:panel id="pnlToUpdate">
<xp:text
escape="true"
id="computedField1" value="#{sessionScope.sTestValue}">
</xp:text></xp:panel></xp:view>
Upvotes: 1
Views: 686
Reputation: 30960
You have to set option "Set partial execution mode" execMode="partial"
in your button. This will execute button's SSJS first and for sure.
Otherwise it will recalculate the whole page first without an URL parameter sDevice
. Because sDevice
is not set the XPage won't render the current part (e.g. desktop-panel) and won't execute button's SSJS. Later on client side, it will execute the partial refresh with URL parameter sDevice=desktop
and render the desktop-panel but that is too late for executing button's code.
I modified your example from your link. It prints the current URL parameter sDevice
in panel "pnlList" and prints "button clicked" when button's SSJS gets executed. The button writes a random value to viewScope variable and refreshes the "computedField1". This way you can see what happens on server console when you click the button, refresh page or resize browser window.
As you can test, button works with rendered condition (but won't work without execMode="partial"
). You can still do a full update instead of the partial refresh in example if you need to.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core">
<xp:eventHandler
event="onClientLoad"
submit="false">
<xp:this.script><![CDATA[$(window).on('load resize', function(){
var screenWidth = $(window).width();
var sDevice = '';
switch(true){
case (screenWidth < 768):
sDevice = 'mobile';
break;
case (screenWidth < 922):
sDevice = 'tablet';
break;
case (screenWidth >= 922):
sDevice = 'desktop'
}
XSP.partialRefreshPost( '#{id:pnlList}', {params: {'sDevice': sDevice}} );
});]]></xp:this.script>
</xp:eventHandler>
<xp:panel
id="pnlList"
rendered="#{javascript:print('param.sDevice: ' + param.sDevice); return true}">
<xp:label
value="I am going to be displayed if I am on a mobile device"
id="label1"
rendered="#{javascript:param.sDevice == 'mobile';}">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete"></xp:eventHandler></xp:label>
<xp:br></xp:br>
<xp:label
value="I am going to be displayed if I am on a tablet device"
id="label2"
rendered="#{javascript:param.sDevice == 'tablet'}">
</xp:label>
<xp:br></xp:br>
<xp:label
value="I am going to be displayed if I am on a desktop device"
id="label3"
rendered="#{javascript:param.sDevice == 'desktop'}">
<xp:button
value="Label"
id="button1">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="partial" execMode="partial" refreshId="computedField1">
<xp:this.action><![CDATA[#{javascript:
print("button clicked");
viewScope.currentItem = Math.random();}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:label>
</xp:panel>
<br />
<xp:text
escape="true"
id="computedField1"
value="#{viewScope.currentItem}">
</xp:text>
</xp:view>
Update
Not executing buttons SSJS code is a symptom of the approach itself. The panel "pnlList" gets rendered by default empty. Only after client's load or resize event panel will be partially refreshed and be filled with a device depended content. Unfortunately, the criteria for rendering is fragile as it is only part of partial refresh URL. The button might be work with option execMode="partial"
but you might run into other issues with this approach.
The better approach is to let the client tell the server what kind of device it is and the server will save this information in an viewScope variable and the rendered conditions using the viewScope variable only. This way the server already renders the panel for the current device and will just change it on load and resize event.
Save the parameter sDevice to a scope variable panel's "pnlList" rendered property (return always true) and use this variable in rendered condition in device specific panels:
<xp:panel
id="pnlList"
rendered="#{javascript:
if (param.sDevice) {
viewScope.device = param.sDevice;
}
return true}">
<xp:button
value="Button Mobile"
id="button1"
rendered="#{javascript:viewScope.device == 'mobile';}">
...
Upvotes: 3