XPages and jQuery/Bootstrap Date Fields
I was creating an XPage to be used for input. It contained a table that was 4 columns wide and many rows long. In the first 3 columns were date fields ("original", "expected", and "actual") and the 4th column was a difference between the expected and actual values. I wanted to have the difference update when either date in the 2nd or 3rd column changed. In the "dojo world" of XPages, this is pretty easy to accomplish. You set the onchange event for the expected and actual components to execute a partial refresh and target the id of the difference component. But when you change to use Bootstrap/jQuery for the date fields, that no longer works.
First, I'll describe how to use Bootstrap/jQuery for the input date fields. You need some CSS files and JavaScript files that can be found on the web. I put these into a theme so they would be available in all my XPages and would be cached by the browser (the download "hit" would happen only once). Here's my theme code:
<!-- jquery -->
<resource>
<content-type>application/x-javascript</content-type>
<href>jQuery/jquery-3.2.0.min.js</href>
</resource>
<!-- bootstrap js -->
<resource>
<content-type>application/x-javascript</content-type>
<href>bootstrap/js/bootstrap.min.js</href>
</resource>
<!-- bootstrap css-->
<resource>
<content-type>text/css</content-type>
<href>bootstrap/css/bootstrap.min.css</href>
</resource>
<resource>
<content-type>text/css</content-type>
<href>bootstrap/css/bootstrap-responsive.min.css</href>
</resource>
<!-- date picker CSS and JS -->
<resource>
<content-type>text/css</content-type>
<href>datepicker/bootstrap-datepicker3.css</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>datepicker/bootstrap-datepicker.min.js</href>
</resource>
Next, on all my
<xp:inputText value="#{document1.ProjectStartActual}" id="ProjectStartActual" styleClass="datePicker">
Finally, at the bottom of the XPage I added the jQuery code to turn everything with that class into a date picker:
<xp:scriptBlock type="text/javascript">
<xp:this.value><![CDATA[
$(document).ready(function(){
var options={
format: 'mm/dd/yyyy',
container: 'body',
todayHighlight: true,
autoclose: true
};
$(".datePicker").datepicker(options);
})
]]></xp:this.value>
</xp:scriptBlock>
When the document is loaded, jQuery will find all the elements that have a class attribute of "datePicker" and apply the date picker method to them.
When using this type of date picker, the regular onchange event won't fire unless you manually go in to edit on the field (not using the picker) and change the value. Luckily, there's a jQuery equivalent that can be triggered when the date is changed. But getting it to work with an XPages partial refresh took a bit of work. That work was made more difficult by the fact that I had several rows of dates. And not every date triggered a refresh, and the only refresh I wanted to do was one field for each date. I decided to set up a JavaScript array with the ID's of the "expected" and "actual" components and then the corresponding "difference" component that should be updated.
<xp:scriptBlock type="text/javascript">
<xp:this.value><![CDATA[
var componentIds = [
"#{id:ProjectStartExpected}", "#{id:ProjectStartActual}", "#{id:ProjectStartDifference}",
...
"#{id:Phase9Expected}", "#{id:Phase9Actual}", "#{id:Phase9Difference}"
];
]]></xp:this.value>
</xp:scriptBlock>
In the example above, I eliminated a bunch of rows of that code, but they were all the other table rows in groups of three.
Then, back in my script block where I had the jQuery to initialize all the date pickers, I added the "change" method to trigger when a date is changed. The code would run through the component ID's. If it found a match to either the "expected" or "actual" ID, then I'd trigger a refresh on the "difference" ID. Here's the updated script block at the bottom of the XPage:
<xp:scriptBlock type="text/javascript">
<xp:this.value><![CDATA[
$(document).ready(function(){
var options={
format: 'mm/dd/yyyy',
container: 'body',
todayHighlight: true,
autoclose: true
};
$(".datePicker").datepicker(options);
$(".datePicker").on("change", function (e) {
for (var i=0; i<componentIds.length; i=i+3) {
if (this.id == componentIds[i]) XSP.partialRefreshPost(componentIds[i+2]);
if (this.id == componentIds[i+1]) XSP.partialRefreshPost(componentIds[i+2]);
}
});
})
]]></xp:this.value>
</xp:scriptBlock>
The code runs through the array of component ID's in groups of 3. For each group, there's "expected" (which is one that could trigger a refresh), "actual" (which is one that could trigger a refresh), and "difference" (the one being refreshed). If the ID of the component that triggered the jQuery event is one that is supposed to cause a refresh, then trigger a partial refresh on the dependent component. Hopefully everything makes sense. It's working wonderfully and I get the Bootstrap look and feel and the Dojo functionality.
Note: Using the "changeDate" method worked when I initially posted this, but then stopped working. The "change" method now seems to work. If you have trouble getting the partial refresh to recognize one or the other, try the other method and see if you get better results.