Can I display HTML in Error Messages control? - xpages

In my XPages application we are using Bootstrap for theming.
If I look at the alert component:
<div class="alert alert-danger" role="alert"> <strong>WARNING</strong> SYSTEM FAILURE. </div>
I see a element is used.
Now in my XPages application I used something as followed:
FacesContext.getCurrentInstance().addMessage(null, new javax.faces.application.FacesMessage(javax.faces.application.FacesMessage.SEVERITY_ERROR, "<strong>WARNING</strong> SYSTEM FAILURE", ""));
but this is displayed as text:
Is it somehow to make the Error Messages control display HTML?

You can write your own message control like this:
<xp:panel styleClass="xspMessage"
rendered="#{javascript:facesContext.getMessages().hasNext()}">
<xp:repeat id="repeat1" rows="30" var="msg">
<xp:this.value><![CDATA[#{javascript:var v = new java.util.Vector();
var itr = facesContext.getMessages()
while(itr.hasNext()) {
var element:javax.faces.application.FacesMessage = itr.next();
v.add(element.getSummary())
}
return v;}]]></xp:this.value>
<xp:text escape="false" id="computedField1" value="#{msg}" tagName="div">
</xp:text>
</xp:repeat>
</xp:panel>

A better way would be to use CSS to format the error messages control output.

Related

Trouble with handling attachments in Xpages

I am trying to write a fileUpload/fileDownload custom control with a Bootstrap like look. I am fairly happy with the look (see below).
However, I am getting very inconsistent behavior. I would very much like the user to be able to click the delete button and the attachment is removed and the repeat control is refreshed. In a similar fashion if the user selects "Upload" I think the attachment should be uploaded to the document and the repeat control refreshed as well.
I am storing the attachments in a single document that is separate from the main document [it is stored in a separate db from the code].
I am using the js fileInput library with the upload control.
I am using a repeat control to roll my own file download viewer.
The code works some of the time, but not always. When the page fails I get this error message (see below).
Any suggestions would be greatly appreciated.
Context Path: /scoApps/OTM1/OTM1.nsf
Page Name: /xpTest.xsp
javax.faces.FacesException
at com.sun.faces.lifecycle.ApplyRequestValuesPhase.execute(ApplyRequestValuesPhase.java:106)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:210)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:96)
at com.ibm.xsp.controller.FacesControllerImpl.execute(FacesControllerImpl.java:256)
at com.ibm.xsp.webapp.FacesServlet.serviceView(FacesServlet.java:228)
at com.ibm.xsp.webapp.FacesServletEx.serviceView(FacesServletEx.java:157)
at com.ibm.xsp.webapp.FacesServlet.service(FacesServlet.java:160)
at com.ibm.xsp.webapp.FacesServletEx.service(FacesServletEx.java:138)
at com.ibm.xsp.webapp.DesignerFacesServlet.service(DesignerFacesServlet.java:103)
at com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(ComponentModule.java:576)
at com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(NSFComponentModule.java:1335)
at com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(ComponentModule.java:853)
at com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(ComponentModule.java:796)
at com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(ComponentModule.java:565)
at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:1319)
at com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(NSFService.java:662)
at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:482)
at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:357)
at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:313)
at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272)
Caused by: java.lang.IllegalArgumentException
at javax.faces.model.ListDataModel.getRowData(ListDataModel.java:141)
at com.ibm.xsp.component.UIRepeatContainer.addIndexedDataContext(UIRepeatContainer.java:173)
at com.ibm.xsp.component.UIRepeatContainer.getDataContexts(UIRepeatContainer.java:86)
at com.ibm.xsp.util.DataPublisher.getDataContextList(DataPublisher.java:349)
at com.ibm.xsp.util.DataPublisher.revokeControlData(DataPublisher.java:271)
at com.ibm.xsp.component.UIDataPanelBase.revokeControlData(UIDataPanelBase.java:319)
at com.ibm.xsp.component.UIDataPanelBase.processDecodes(UIDataPanelBase.java:334)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at com.ibm.xsp.component.UIRepeat.processDecodes(UIRepeat.java:526)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at com.ibm.xsp.component.UIDataPanelBase.processDecodes(UIDataPanelBase.java:331)
at javax.faces.component.UIForm.processDecodes(UIForm.java:166)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1177)
at javax.faces.component.UIViewRoot.processDecodes(UIViewRoot.java:343)
at com.ibm.xsp.component.UIViewRootEx._processDecodes(UIViewRootEx.java:1438)
at com.ibm.xsp.component.UIViewRootEx.processDecodes(UIViewRootEx.java:1399)
at com.sun.faces.lifecycle.ApplyRequestValuesPhase.execute(ApplyRequestValuesPhase.java:98)
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xc="http://www.ibm.com/xsp/custom">
<xp:this.data>
<xp:dominoDocument var="document1" databaseName="scoApps\OTM1\OTM1Data.nsf" formName="attachment" action="editDocument" documentId="349CDB2FB259D5D3862581090076AC50" />
</xp:this.data>
<script src="fileinput/js/fileinput.js" type="text/javascript" />
<link href="fileinput/css/fileinput.min.css" media="all" rel="stylesheet" type="text/css" />
<xp:scriptBlock id="scriptBlockInitFile">
<xp:this.value><![CDATA[$(document).ready(
function() {
$('input[type=file]').fileinput({
previewFileType: "image",
browseClass: "btn btn-primary",
browseLabel: "Browse...",
browseIcon: '<i class="glyphicon glyphicon-plus"></i>',
removeClass: "btn btn-danger",
removeLabel: "Delete",
removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
uploadClass: "btn btn-info",
});
}
);]]></xp:this.value>
</xp:scriptBlock>
<xp:div themeId="container" style="width:800px">
<xp:br />
<xp:fileUpload id="fileUpload1" value="#{document1.attachments}">
<xp:this.attrs>
<xp:attr name="multiple" value="true" />
<xp:attr name="data-show-preview" value="false" />
</xp:this.attrs>
</xp:fileUpload>
<xp:br />
<xp:button value="Save Changes" id="button1" styleClass="btn btn-primary">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete" disableValidators="true">
<xp:this.action>
<xp:actionGroup>
<xp:save />
</xp:actionGroup>
</xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:div>
<xp:repeat rows="30" id="attrepeat" first="0" var="att" indexVar="attachmentIndex">
<xp:this.facets>
<xp:text disableTheme="true" xp:key="header" escape="false">
<xp:this.value><![CDATA[<table class="table table-striped table-bordered table-hover"><col width="150"><col width="450"><th>File Name</th><th style="text-align:right">Delete</th>]]></xp:this.value>
</xp:text>
<xp:text disableTheme="true" xp:key="footer" escape="false">
<xp:this.value><![CDATA[</table>]]></xp:this.value>
</xp:text>
</xp:this.facets>
<xp:this.value><![CDATA[#{javascript:var bckDoc = document1.getDocument()
var attachments:java.util.Vector = session.evaluate("#AttachmentNames",bckDoc);
attachments}]]></xp:this.value>
<xp:tr>
<xp:td>
<xp:link escape="true" id="link1" target="_blank" text="#{javascript:att.toString();}">
<xp:this.value><![CDATA[#{javascript:var tmpStr:String;
var str:String;
var bckDoc = document1.getDocument()
var attachments:java.util.Vector = session.evaluate("#AttachmentNames",bckDoc);
tmpStr = attachments.toString;
tmpStr
var bckDoc = document1.getDocument()
var attachments:java.util.Vector = session.evaluate("#AttachmentNames",bckDoc);
tmpStr = attachments.elementAt(0);
var unid = "349CDB2FB259D5D3862581090076AC50"
var dbPath = "scoApps/OTM1/OTM1.nsf/"
var dbDataPath = "scoApps/OTM1/OTM1Data.nsf/"
var url = "http://localhost/";
url += dbPath;
url += "/xsp/.ibmmodres/domino/OpenAttachment/";
url += dbDataPath + "/";
url += unid
url += "/" + "attachments" + "/";
url += tmpStr;
url}]]></xp:this.value>
<xp:image id="image1" rendered="false">
<xp:this.url><![CDATA[#{javascript:var pdfImage = 'pdf.gif';
//if(attachment.indexOf("pdf")> 0)
return pdfImage; }]]></xp:this.url>
</xp:image>
&#160;
</xp:link>
</xp:td>
<xp:td style="text-align:right">
<xp:button value="Delete" id="button2" styleClass="btn btn-danger">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:function removeAttachment(targDB,doc_Url,fileName,docUnid2) {
//passing parameters by reference
var docUrl:String= doc_Url;
var targetDB:NotesDatabase = session.getDatabase(session.getCurrentDatabase().getServer(),targDB);
var attachmentName:String =fileName;
//deducing the document's unid from the document's url
//docUrl=#Left(docUrl,"?");
//var docUnid=#RightBack(docUrl,"/");
print (docUnid2);
//setting the handle to the document
var docContext:NotesDocument=targetDB.getDocumentByUNID(docUnid2);
if (docContext==null) {
viewScope.CodeError="Either the UNID is invalid or the target db does not contain the doc or both";
return;
}
//getting the handle to the concerned attachment
var embObj:NotesEmbeddedObject=docContext.getAttachment(attachmentName);
if (embObj==null) {
viewScope.CodeError="No attachment is found by the name "+ attachmentName;
return;
}
//remove the attachment
embObj.remove()
docContext.save(true,false);
}
var fleNme = att.toString();
print (fleNme);
removeAttachment("scoApps/OTM1/OTM1Data.nsf","http://localhost/scoApps/OTM1/OTM1.nsf//xsp/.ibmmodres/domino/OpenAttachment/scoApps/OTM1/OTM1Data.nsf//349CDB2FB259D5D3862581090076AC50/attachments/ITReport.xlsx",fleNme,"349CDB2FB259D5D3862581090076AC50");}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:td>
</xp:tr>
</xp:repeat>
</xp:view>
It's failing in the ApplyRequestValues phase, something related to running code for rows within the repeat. I've had that before in a Data View with using the caching was ID only. The key is identifying which component's code is failing (the value of the repeat, the link, the image or the button - if it's a component, which property) and whether it needs to run in that phase. It may not.
Looking at the stack trace, the "caused by" bit failing on getRowData() implies it might be a problem with value of the repeat, but I'm not totally sure. It could be one of the repeat's child components that's failing.
If it's a read-only bit of code, it could be wrapped in an if (view.isRenderingPhase()) block. Just make sure you output something valid for other phases! E.g. for a rendered property, set the result for other phases to true (I've missed that before and caused myself problems!)
If it's because the document datasource has not been initialised at that phase, wrapping everything in a Panel may solve the problem, by associating the dominoDocument datasource to be a child of the Panel, which may encourage it to be better re-initialised during the restoreView phase. This is a total stab in the dark, but I know during page load a dominoDocument datasource gets loaded at a different time when it's a child of a Panel as opposed to a child of the XPage itself.

Xpages Custom Control: Putting compositeData value in an SSJS Function

I am posting the code that I used to solve this. Thanks to Per and Eric McCormick and Paul Withers.
<xp:scriptBlock id="scriptBlock2">
<xp:this.value><![CDATA[$(document).ready(
function() {
x$("#{javascript:return getComponent(compositeData.fieldName).getClientId(facesContext);}").select2({
placeholder : "Choose an employee",
allowClear: true,
minimumResultsForSearch : 3
})
}
);
]]>
</xp:this.value>
</xp:scriptBlock>
The answer to my previous question was incredibly useful. I am making a Select2 custom control and need to use the dynamically generated ID in an SSJS function
I am dynamically creating the id of the field in the custom control by giving it the fieldName, like so:
id="${javascript:compositeData.fieldName}"
In other parts of my CC I use that computation to access the id number, for example:
<xp:message
id="message1"
for="#{javascript:compositeData.fieldName}"
styleClass="help-block">
</xp:message>
However in building my Select2 CC I need to add some SSJS in script, like so:
<xp:scriptBlock id="scriptBlock2">
<xp:this.value><![CDATA[
$(document).ready(
function() {
x$("#{id:[compositeData.fieldName]}").select2({
placeholder: "Select An Employee",
allowClear: true
});
}
);
]]></xp:this.value>
</xp:scriptBlock>
But this doesn't work. I cannot figure out how to dynamically generate the ID.
x$("#{id:[compositeData.fieldName]}")
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core"
id="view1">
<xp:scriptBlock id="scriptBlock2">
<xp:this.value><![CDATA[
$(document).ready(
function() {
x$("#{id:[compositeData.fieldName]}").select2({
placeholder: "Select An Employee",
allowClear: true
});
}
);
]]></xp:this.value>
</xp:scriptBlock>
<xp:div>
<xp:this.styleClass><![CDATA[#{javascript:"form-group" + (getComponent(compositeData.fieldName).isValid() ? "" : " has-error")}]]></xp:this.styleClass>
<xp:label
styleClass="control-label"
for="#{javascript:compositeData.fieldLabel}"
value="${compositeData.fieldLabel}" />
<div class="">
<xp:comboBox
id="${javascript:compositeData.fieldName}"
value="#{compositeData.dataSource[compositeData.fieldName]}"
required="${compositeData.required}">
<xp:selectItems
value="${javascript:'#{CacheBean.'+compositeData.cacheItem+'}'}">
</xp:selectItems>
<xp:this.validators>
<xp:validateRequired message="#{javascript:compositeData.fieldLabel + ' is required'}"></xp:validateRequired>
</xp:this.validators>
</xp:comboBox>
<xp:scriptBlock
id="scriptBlock1">
<xp:this.value>
<![CDATA[x$("#{id:comboBox5}").select2({minimumResultsForSearch:5});]]>
</xp:this.value>
</xp:scriptBlock>
<xp:text
escape="true"
id="computedField1"
styleClass="help-block"
value="${compositeData.helpText}">
<xp:this.rendered><![CDATA[#{javascript:(getComponent(compositeData.fieldName).isValid()) && compositeData.helpText != null}]]></xp:this.rendered>
</xp:text>
<xp:message
id="message1"
for="#{javascript:compositeData.fieldName}"
styleClass="help-block">
</xp:message>
</div>
</xp:div>
<xp:text escape="true" id="computedField2"
value="${javascript:'#{id.'+compositeData.fieldName+'}'}">
</xp:text>
</xp:view>
You can get the dynamically generated id from client side JS (using SSJS) by using the SSJS function getClientId(). So in your case it will look like this combined with the x$ function:
x$('#{javascript:getClientId(compositeData.fieldName)}')

How do I refresh a repeat that is updated using a dialog

I'm having a problem getting a repeat to refresh when the underlying value is changed using a dialog.
This is the div that contains the repeat:
<xp:div style="display:none;">
<xp:inputText id="linkages" value="#{procureDoc.Linkages}" multipleTrim="true" style="color:cornflowerblue;" multipleSeparator=";">
</xp:inputText>
</xp:div>
<xp:label value="Linkages:" id="linkageLabel" style="font-weight:bold;"></xp:label>
<xp:div id="linkageDiv">
<ul>
<xp:repeat id="linkagesDisplayRepeat" rows="30" var="rowData" indexVar="index" value="#{procureDoc.Linkages}">
<li>
<xp:text escape="true" id="computedField7">
<xp:this.value><![CDATA[#{javascript:rowData;}]]></xp:this.value>
</xp:text>
</li>
</xp:repeat>
</ul>
</xp:div>
Here's the save button from the dialog. It does happen to sit in another custom control, but I don't think that's the problem.
<xp:button value="Save" id="saveButton">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.script><![CDATA[var linkageSelection = document.getElementById("#{id:linkageComboBox}").value;
var linkageUpload = "";
if (linkageSelection == "Use Category") {
linkageUpload = document.getElementById("#{id:linkageCategoryComboBox}").value;
}
else { linkageUpload = linkageSelection;}
var currentLinkages = document.getElementById("#{id:linkages}").value;
if ( currentLinkages == "" ) {
document.getElementById("#{id:linkages}").value = linkageUpload;
} else {
document.getElementById("#{id:linkages}").value = document.getElementById("#{id:linkages}").value + ";" + linkageUpload;
}
XSP.closeDialog('#{id:linkageDialog}');]]></xp:this.script>
</xp:eventHandler>
</xp:button>
I can put a button on the XPage that just refreshes and it will refresh my repeat with the value selected in the dialog, but just using my save button doesn't do it.
I'm sure I'm missing something simple, but I just can't see it.
You could use the onComplete event from your SSJS eventhandler to call a CSJS script like
XSP.partialRefreshGet("#{id:linkagesDisplayRepeat}");
to refresh the repeat control.
There's a second parameter to the client-side closeDialog() method, denoting the ID of the component to partially refresh. That basically adds the onComplete for you.
SSJS corresponding method uses the ID of the component to subsequently refresh as the only parameter (obviously the method is called on the component being closed, which is the first parameter in CSJS).

Xpages - Creating Repeat or View from different database with ACL set to No Access

I am trying to create a repeat element from a view in a different database that does not allow Anonymous ACL access.
PubDb.nsf has ACL set for user Anonymous to Author
PrivDb.nsf has ACL set for user Anonymous and Default "no access".
The database, PrivDb has information that I want to secure, but would like to pull a couple of views out for public display.
When I create a repeat in PubDb.nsf using PrivDb.nsf view, It will not display any data. I am able to use sessionAsSigner to get SSJS to see the view and open documents at the server side level, but when I try to display the view or repeat elements there is no data ? If I change ACL in PrivDb for Anonymous to Reader everything works, but now the entire db is open to Anonymous access.
I understand that sessionAsSigner and sessionAsSignerWithFullAccess allows me to use db signers effective rights, and I can use SSJS to access the notesdocuments and publish data using computed fields for individual docs, but I can't find any information that tells me I can or can't display an xpages element (repeat or view) using the sessionAsSigner. Maybe I can create a lotusscript agent that populates the block?
Below is the code I use to create the repeat element in PubDb.nsf. Note, I assigned the DB twice, once in Application and again in View because when I didn't use sessionAsSigner in the view's computed value I would get prompted for authentication, and it's required in the app field.
<xp:this.data>
<xp:dominoView var="view2">
<xp:this.databaseName><![CDATA[${javascript:
var DB:NotesDatabase=sessionAsSigner.getDatabase(database.getServer(),"PrivDb.nsf");
DB;
}]]>
</xp:this.databaseName>
<xp:this.viewName><![CDATA[${javascript:
var dbOther:NotesDatabase = sessionAsSignerWithFullAccess.getDatabase(database.getServer(), "PrivDb.nsf");
var lookupView:NotesView = dbOther.getView( "PrivView" );
lookupView.recycle();
lookupView}]]>
</xp:this.viewName>
</xp:dominoView>
</xp:this.data>
<div class="container">
<div class="page-header">
<h1>This is a test.</h1>
</div>
<xp:br></xp:br>
<xp:br></xp:br>
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-10">
<xp:repeat id="repeat1" rows="30" var="playerData"
value="#{view2}" repeatControls="true">
<div class="panel panel-default">
<xp:text escape="true" id="computedField1"
value="#{playerData.$5}">
</xp:text>
hcp:  
<xp:text escape="true" id="computedField5"
value="#{playerData.$6}">
</xp:text>
<xp:br></xp:br>
<xp:text escape="true" id="computedField2"
value="#{playerData.$7}">
</xp:text>
   
<xp:text escape="true" id="computedField3"
value="#{playerData.$8}">
</xp:text>
   
<xp:text escape="true" id="computedField4"
value="#{playerData.$9}">
</xp:text>
<xp:br></xp:br>
</div>
<div class="col-md-1"></div>
</xp:repeat>
</div>
</div>
</div>
Try changing your repeat to return for instance the view entries directly using sessionAsSigner instead of going through a view data source. So in your case:
<xp:repeat id="repeat1" rows="30" var="playerData">
<xp:this.value><![CDATA[#{javascript:
sessionAsSigner.getDatabase(database.getServer(), "PrivDb.nsf").getView("PrivView").getAllEntries();
}]]></xp:this.value>
...
</xp:repeat>

XPages repeat control and button

I have a repeat control on an XPage displaying item summary. To view other details, the user clicks on a button (inside the repeat) which will set a viewScope variable to the rowIndex of the repeat and then open up a modal to display the remeaining item details.
I am unable to set the viewScope variable from the button. It's like it cannot get a handle to the rowIndex.
I have a computed text field set to display the rowIndex at the beginning of the repeat (which works fine).
I'm obviously missing something elementary. Any assistance would be appreciated.
Relevant source code follows:
<xp:repeat id="repeat1" rows="50" var="entry" indexVar="rowIndex">
<xp:this.value><![CDATA[#{javascript:var m = sessionScope.assetMap;
if(m!=null){
m.entrySet()
}}]]></xp:this.value>
<div>
<small>
<label>
<xp:text escape="true" id="num">
<xp:this.value><![CDATA[#{javascript:var itemNum:Number = parseInt(rowIndex + 1);
itemNum.toPrecision(0);}]]></xp:this.value>
</xp:text>
.  Description:   
</label>
<xp:text escape="true" id="desc">
<xp:this.value><![CDATA[#{javascript:#Word(entry.getValue(),"~",3);}]]> </xp:this.value>
</xp:text>
</small>
</div>
<br></br>
<div>
<xp:button type="button" value="Details ..." id="button2">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:try{var itemNum:Number = parseInt(rowIndex + 1);
viewScope.currentItem = itemNum.toPrecision(0);
}catch(e) {
requestScope.errstatus = e.toString();}}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
02/04/2015 Update:
I have isolated the problem to the rendered property of the custom control. The code works if I don't use the rendered property.
The code in the onCLientLoad of my XPage (CSJS) is:
$(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}} );
});
And the source for my custom control in the XPage with the rendered property is:
<xp:panel rendered="#{javascript:param.sDevice == 'mobile'}">
<xc:ccSYS_AppLayoutReq_p xp:key="panelContentFacet">
<xp:this.facets>
<xc:cc_cartracking_p xp:key="contentFacet"></xc:cc_cartracking_p>
</xp:this.facets>
</xc:ccSYS_AppLayoutReq_p>
</xp:panel>
Thanks,
Dan
You have to set option "Set partial execution mode" execMode="partial" in your button.
Look for the whole answer at your colleague's question https://stackoverflow.com/a/28330481/2065611

Resources