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)}')
I have the latest extension library installed running on the latest version of Domino. I am using a simple listBox with values from a DBColumn ( which works to populate the listBox ).
However, I can't seem to get the selected value of the listBox. I've poked around the net a while and found several different things which I have tried unsuccessfully. Then I started reading about having to install other libraries etc. Now I am confused. Below is my code. All I want to do is get the selected value from the listBox once it changes but I really want to use the select2 features to search the listBox. Can someone point me in the right direction on how to get the selected value when it changes?
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:bx="http://www.openntf.org/xsp/bootstrap">
<xp:scriptBlock id="scriptBlock2">
<xp:this.value>
<![CDATA[
$(document).ready(
function() { x$( "#{id:listBoxProperties}" ).select2()
.on("change", function(e) { XSP.partialRefreshPost(
"#{id:computedField2}" );
}
}
);
]]>
</xp:this.value>
</xp:scriptBlock>
<xp:panel>
<xp:listBox id="listBoxProperties" value="#{viewScope.selectedProperty}"
style="width:250px">
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:listOfProperties = #DbColumn( #DbName(), 'vwAuditDocsByPropertyNo', 1 );
if( #IsError( listOfProperties ) )
"Error looking up properties: " + listOfProperties;
listOfProperties;
}]]></xp:this.value>
</xp:selectItems>
<xp:eventHandler event="onchange" submit="true"
refreshMode="complete">
</xp:eventHandler></xp:listBox>
<bx:select2PickerCombo id="select2Property"
for="listBoxProperties" placeHolder="-Select a Property-"
binding="#{javascript:viewScope.selectedProperty}">
</bx:select2PickerCombo>
<xp:br></xp:br>
<xp:text escape="true" id="computedField1"
value="#{javascript:viewScope.selectedProperty;}">
</xp:text>
<xp:text escape="true" id="computedField2">
<xp:this.value><![CDATA[#{javascript:getComponent( "listBoxProperties").getValue()}]]></xp:this.value>
</xp:text>
</xp:panel>
</xp:view>
I have tested your scenario and can verify that the onchange event of the lixtBox does not fire when using bx:select2PickerCombo to style the listBox as a Select2 control. If you remove the use of bx:select2PickerCombo, the onchange event fires as expected. Here's a simple example (without the use of bx:select2PickerCombo):
<xp:listBox id="listBoxProperties" value="#{viewScope.selectedProperty}" style="width:250px">
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:["1","2","3"]}]]></xp:this.value>
</xp:selectItems>
<xp:eventHandler event="onchange" submit="true" refreshMode="partial" refreshId="refreshField"></xp:eventHandler>
</xp:listBox>
<!--<bx:select2PickerCombo id="select2Property" for="listBoxProperties" placeHolder="-Select a Property-" binding="#{javascript:viewScope.selectedProperty}"></bx:select2PickerCombo>-->
<xp:text escape="true" id="refreshField" value="#{javascript:viewScope.selectedProperty;}"></xp:text>
You need to manually add Select2 to your listBox to get the onchange event to work. See this answer for more details: Bootstrap4XPages plugin: how to catch a change event with Select2 Picker?
Have a database where I only want one document for a certain category. So when the user goes to this Xpage I want to test to see if there is already a doc, and if so grab that one, if not, then create and save one.
I wrote some SSJS in the datasource to do this, but the first time I run it it creates two docs. I put a print in the code and it executes this part twice. Why does it do that?
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:this.beforePageLoad><![CDATA[#{javascript:sessionScope.selectedPage = "page001"}]]></xp:this.beforePageLoad>
<xp:this.resources>
<xp:script src="/xpValidationDocument.jss" clientSide="false" />
<xp:styleSheet href="/custom.css" />
</xp:this.resources>
<xp:this.data>
<xp:dominoDocument var="document1" action="editDocument">
<xp:this.documentId><![CDATA[#{javascript:sessionScope.selectedPage = "page001";
var v:NotesView = database.getView(sessionScope.selectedPage)
var doc:NotesDocuent = v.getFirstDocument()
if (doc == null)
{doc = database.createDocument();
doc.appendItemValue("form","document");
doc.appendItemValue("key",sessionScope.selectedPage);
doc.appendItemValue("crtUsr",session.getCommonUserName());
doc.appendItemValue("crtDte",session.evaluate('#Today'))
doc.save();
print ("here");
return doc.getUniversalID();}
else
{
print ("here2");
return doc.getUniversalID()}}]]></xp:this.documentId>
</xp:dominoDocument>
</xp:this.data>
<xp:panel style="width:900.00px" id="pnlForm">
<xe:widgetContainer id="widgetContainerHeader"
style="width:100%">
<xp:panel id="plContainer">
<xe:formTable id="frLocationMaster"
disableErrorSummary="true" disableRowError="true"
style="lotusForm2" styleClass="scllotusui30dojo">
<xp:this.facets />
<xe:formRow id="formRow5" labelPosition="none"
style="padding-bottom:10.0px">
<xp:table style="width:99%" border="0"
cellpadding="0" role="presentation" cellspacing="0"
id="table2">
<xp:tr>
<xp:td
style="width:80.00px;min-width:120px">
<xp:label id="label2" for="formRow1"
value="Notes" />
</xp:td>
<xp:td style="width:px">
<xp:inputRichText
id="inputRichText1" value="#{document1.Body}">
<xp:this.attrs>
<xp:attr name="toolbar">
<xp:this.value><![CDATA[
[
["Format", "Font", "FontSize"],
["Bold", "Italic", "Underline", "Strike", "-", "TextColor", "BGColor", "-", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock", "NumberedList", "-", "BulletedList"],
["Indent", "Outdent"],
["Subscript", "Superscript"],
["RemoveFormat", "-", "MenuPaste", "-", "Undo", "Redo", "Find", "LotusSpellChecker", "-", "Image", "Table", "Link", "Flash", "-", "PageBreak", "HorizontalRule", "SpecialChar", "Blockquote", "Smiley", "ShowBlocks"],
["Maximize", "Source"]
]
]]></xp:this.value>
</xp:attr>
</xp:this.attrs>
<xp:this.dojoAttributes>
<xp:dojoAttribute
name="enterMode" value="2" />
</xp:this.dojoAttributes>
</xp:inputRichText>
</xp:td>
</xp:tr>
</xp:table>
</xe:formRow>
</xe:formTable>
</xp:panel>
</xe:widgetContainer>
</xp:panel>
</xp:view>
After you have created the document det the UNID or the NoteID in a viewScope variable and at the top of you code check if the scope variable is null if not use that one.
the reason for this is that the datasource is recalculated several times while loading the page.
So the code would be something like this
sessionScope.selectedPage = "page001";
if(viewScope.thisUNID==null){
var v:NotesView = database.getView(sessionScope.selectedPage)
var doc:NotesDocuent = v.getFirstDocument()
if (doc == null)
{doc = database.createDocument();
doc.appendItemValue("form","document");
doc.appendItemValue("key",sessionScope.selectedPage);
doc.appendItemValue("crtUsr",session.getCommonUserName());
doc.appendItemValue("crtDte",session.evaluate('#Today'))
doc.save();
print ("here");
viewScope.thisUNID=doc.getUniversalID()
return viewScope.thisUNID;}
else
{
print ("here2");
viewScope.thisUNID=doc.getUniversalID()
return viewScope.thisUNID}
}else{
return viewScope.ThisUNID
}
I see a couple of problems on a quick review.
As Per says, documentId may need to be page load binding, not runtime - the datasource had to be loaded in before render response, and runtime binding may not run early enough.
But the bigger problem is your documentId code is not going to have any effect, because you are not setting ignoreRequestParams="true". Consequently the documentId will be picked up from the URL parameters and, if there's nothing there, it will create a new document each time.
I have a problem with a pager performing a partial refresh (since we have server side redirection rules, full refresh is not an option here). If I place the pager on the same page rather than in a custom control then it refresh the main panel. But if I place the pager in a custom control, then it only updates the repeat control, which means the pager control itself doesn't get refreshed. Here is a simple example to demonstrate the problem.
<?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:panel id="mainPanel">
<xc:ccPager pagerFor="repeat1" id="myPager" />
<xp:repeat id="repeat1" rows="3" var="row">
<xp:this.value><![CDATA[#{javascript:var cars = ["Saab", "Volvo", "BMW", "Saab", "Volvo", "BMW"];
return cars; }]]></xp:this.value>
<xp:text escape="true" id="computedField1" value="#{row}">
</xp:text>
</xp:repeat>
</xp:panel>
</xp:view>
And here is the code for the custom control:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:pager layout="Previous Group Next" partialRefresh="true"
id="pager1" for="#{compositeData.pagerFor}" partialExecute="false">
</xp:pager>
</xp:view>
Any tips would be welcome. In this case the pager is simple, but I have a more complex pager and a pagesizes control with 45 lines of source code, so I would prefer to create a re-useable custom component if possible at all.
UPDATE
My current pager custom control:
<div id="page-result" class="pagination pagination-small clearfix">
<div class="col-lg-12 pagination clearfix">
<div class="results form-inline left">
<fieldset>
<xp:pager id="pager1" alwaysCalculateLast="true"
for="#{compositeData.strPagerFor}" styleClass="pag-control"
partialRefresh="true" partialExecute="false">
<xp:pagerControl id="pagerControl1" type="FirstImage"
styleClass="firstPage" image="/PagerArrowhead2-L_d.png">
</xp:pagerControl>
<xp:pagerControl id="pagerControl2" type="PreviousImage"
styleClass="previousPage" image="/PagerArrowhead-L_d.png">
</xp:pagerControl>
<xp:pagerControl id="pagerControl5" type="Group"
currentStyleClass="active" styleClass="pages">
</xp:pagerControl>
<xp:pagerControl id="pagerControl3" type="NextImage"
styleClass="nextPage" image="/PagerArrowhead.png">
</xp:pagerControl>
<xp:pagerControl id="pagerControl4" type="LastImage"
styleClass="lastPage" image="/PagerArrowhead2.png">
</xp:pagerControl>
</xp:pager>
<label>
<xp:text escape="true" id="computedField1" value="#{compositeData.strLabelText}" />
</label>
</fieldset>
</div>
<div class="results form-inline">
<fieldset>
<label> Results per page: </label>
<xe:pagerSizes id="pagerSizes1" for="#{compositeData.strPagerFor}"
sizes="7|15|25|50|all" text="{0}" styleClass="resultPage"
partialRefresh="true" partialExecute="false"
refreshId="#{compositeData.strRefreshPanel}" />
</fieldset>
</div>
</div>
</div>
I've hit this problem before. I think it's because the view container cannot find the pager when it's in a separate custom control. I presume it's using something like getComponent, which can't navigate down into custom controls, so it can't locate the pager to update it.
Can you extract the code for your pagesizes control into a Java dataObject / bean / util or dataContext / SSJS library? Then reference it with one line of SSJS or EL?
The other option would be to use a plugin and build a component that extends the repeat control and adds the pager to the relevant header?
Tim Tripcony gave a great demo of a quick and dirty way of doing it http://www.notesin9.com/2012/04/04/notesin9-064-global-custom-controls-fixed/
I'm not sure if I am missing out a very simple thing. Normally, XPages maintain "aria-required" and "aria-invalid" attributes for validation.
However, for DateTime Picker (standard one), it's always aria-invalid="false".
Here is a simple test I have used in Domino 9.0.1:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoDocument var="document1" formName="TestForm"></xp:dominoDocument>
</xp:this.data>
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="panel1">
</xp:eventHandler>
</xp:button>
<xp:panel id="panel1">
<xp:inputText id="inputText1" required="true">
<xp:dateTimeHelper id="dateTimeHelper1"></xp:dateTimeHelper>
<xp:this.converter>
<xp:convertDateTime type="date"></xp:convertDateTime>
</xp:this.converter>
</xp:inputText>
<xp:messages id="messages1"></xp:messages>
</xp:panel>
</xp:view>
Generated HTML before the button clicked contains:
<input type="text" aria-haspopup="true" role="textbox" data-dojo-attach-point="textbox,focusNode" autocomplete="off" class="dijitReset dijitInputInner" aria-invalid="false" tabindex="0" aria-required="true" id="view:_id1:inputText1" value="">
After clicking, I can see Messages component has been aggregated but aria-invalid is false.
<input type="text" aria-haspopup="true" role="textbox" data-dojo-attach-point="textbox,focusNode" autocomplete="off" class="dijitReset dijitInputInner" aria-invalid="false" tabindex="0" aria-required="true" id="view:_id1:inputText1" value="">
(I removed dojo stuff wrapping the input)
After some digging, I found that this problem is not related to XPages.
XPages provides the aria-invalid attribute correctly if validation fails. However, dojo controls automatically change it during rendering. So it's not possible unless you inject a correction into Dojo component rendering code.