A Flexible View Control for XPages Part 4 – Callbacks & Click Events
Posted: January 6, 2020 Filed under: Bootstrap, Custom Control, DataTables, Domino, JavaScript, jQuery, XPages | Tags: Bootstrap, Domino, jQuery, XPages 1 CommentRegardless of the type of application you’re working with, when interacting with view data you typically want to be able to take some type of action on that data, such as open a document or manipulate the data in some way.
Callbacks
DataTables, being very callback “heavy”, has two callbacks that can be utilized to add row-level functionality to take action on a clicked/double-clicked row and its data:
Callback | From the DataTables documentation |
---|---|
rowCallback | This callback allows you to ‘post process’ each row after it have been generated for each table draw, but before it is rendered into the document. |
createdRow | This callback is executed when a TR element is created (and all TD child elements have been inserted), or registered if using a DOM source, allowing manipulation of the TR element. |
The Flexible View Control has properties that make incorporating these callbacks into your table build quite easy.

The View Control has a scriptBlock that builds a JavaScript object at runtime which contains the DataTable initialization code. This initialization code contains placeholders for rowCallback and createdRow (as well as other callbacks) that are evaluated by Expression Language to create function calls at runtime. The necessary function parameters are passed in automatically.

You’ll notice in the createdRow callback above that by default the documentId of each row is added as a class parameter. As you will see, this makes it easy to identify rows that have been clicked/double-clicked when needing to take action.
The Flexible View Control source code has a default rowCallback function that can be utilized to add some basic click/double-click actions. Building on the demo created in the previous post, I’m going to add this rowCallback to that control.

Note: It’s important to use the correct syntax when supplying a function to a callback property in the view control. Since Expression Language is being used to dynamically create the function call, the “()” are not needed.
Correct | ccRestView.defaultRowCallback |
Incorrect | ccRestView.defaultRowCallback() |
The defaultRowCallback code is below:
defaultRowCallback : function(row,data,index,params,o) {
var attr = $(row).attr("data-docid");
// For some browsers, `attr` is undefined; for others, `attr` is false. Check for both.
if (typeof attr !== typeof undefined && attr !== false) {
// Element has this attribute
return;
}
// add some row attr
$(row).attr("data-docid",data['@unid']);
$(row).attr("data-xpage",data[o.xpage]);
// These are defined by the return values field on the view definition
for (var x=0;x<params.length;x++) {
params[x] != "@unid" ? $(row).attr("data-value-"+params[x],data[params[x]]) : "";
}
var retData = ccRestView.getReturnData(o,data);
$(row).attr("data-return",JSON.stringify(retData));
$(row).click(function(ev) {
// Get the row data
var retData = $(row).attr("data-return");
if ($("td",$(this)).hasClass("rowSelectOn")) {
if (o.multiValue=="true") {
if (ev.ctrlKey && ev.shiftKey) {
// do nothing
} else if (ev.ctrlKey) {
// Remove the selected class from the selected row
ccRestView.removeSelectedRow(o,retData,this);
} else if (ev.shiftKey) {
if (index > window[o.thisView].config.firstIndex) {
$("."+o.dataTableClass + " tbody tr").each(function(rowIndex) {
if (rowIndex >= window[o.thisView].config.firstIndex && rowIndex <= index) {
retData = $(this).attr("data-return");
ccRestView.insertSelectedRow(o,retData,this)
}
})
} else {
console.log("select rows " + index + " to " + window[o.thisView].config.firstIndex);
}
} else {
// no keys pressed. select this row only
ccRestView.clearSelectedRows(o,this);
ccRestView.insertSelectedRow(o,retData,this);
window[o.thisView].config.firstIndex = index;
}
} else {
// Remove the selected class from the selected row
ccRestView.removeSelectedRow(o,retData,this);
}
} else {
if (o.multiValue!="true") {
// Remove the selected class from all rows first
ccRestView.clearSelectedRows(o,this);
ccRestView.insertSelectedRow(o,retData,this);
} else {
// multi
if (ev.shifKey && ev.ctrlKey) {
} else if (ev.ctrlKey) {
ccRestView.insertSelectedRow(o,retData,this);
window[o.thisView].config.firstIndex = index;
} else if (ev.shiftKey) {
ccRestView.clearSelectedRows(o, this);
if (index > window[o.thisView].config.firstIndex) {
$(o.dataTableClass + " tbody tr").each(function(rowIndex) {
if ($(this).attr("data-index") >= window[o.thisView].config.firstIndex && $(this).attr("data-index") <= index) {
retData = $(this).attr("data-return");
ccRestView.insertSelectedRow(o,retData,this)
}
})
} else {
console.log("select rows " + index + " to " + window[o.thisView].config.firstIndex);
}
} else {
ccRestView.clearSelectedRows(o,this);
ccRestView.insertSelectedRow(o,retData,this, function() {
});
window[o.thisView].config.firstIndex = index;
}
}
}
}); // end click
$(row).dblclick(function() {
// get the unid of the double clicked row
var docid = $(this).attr("data-docid");
href = location.href.split(".nsf");
location.href=href[0]+".nsf/"+o.xpage+".xsp?documentId="+docid+"&action=editDocument";
});
Click Events
So what exactly is the rowCallback doing?
First, some attributes are added to the row dom to make it easier to reference this row by the @unid when it’s clicked or double-clicked:
var attr = $(row).attr("data-docid");
// For some browsers, `attr` is undefined; for others, `attr` is false. Check for both.
if (typeof attr !== typeof undefined && attr !== false) {
// Element has this attribute
return;
}
// add some row attr
$(row).attr("data-docid",data['@unid']);
The data-xpage attribute is added to store the XPage that should be opened when this row is double-clicked. This value typically comes from the View Definition:
$(row).attr("data-xpage",data[o.xpage]);
Next, more parameters are added. This portion of the code is useful when data besides @unid needs to be extracted from a row and passed on to another process.
// These are defined by the return values field on the view definition
for (var x=0;x<params.length;x++) {
params[x] != "@unid" ? $(row).attr("data-value-"+params[x],data[params[x]]) : "";
}
var retData = ccRestView.getReturnData(o,data);
$(row).attr("data-return",JSON.stringify(retData));
Now, we add the click event to every row. When a row is clicked the following evaluations are made:
- Does this view allow multiple selections?
- If so, check for other key presses (ctrl, shift). The control handles multiple selections like windows explorer.
- Is this row already selected?
- If not, add a class that changes the row color and add the row @unid to a hidden field (more on this in the next post).
- If so, remove the class that changes the row color and remove the @unid from the hidden field.
$(row).click(function(ev) {
// Get the row data
var retData = $(row).attr("data-return");
if ($("td",$(this)).hasClass("rowSelectOn")) {
if (o.multiValue=="true") {
if (ev.ctrlKey && ev.shiftKey) {
// do nothing
} else if (ev.ctrlKey) {
// Remove the selected class from the selected row
ccRestView.removeSelectedRow(o,retData,this);
} else if (ev.shiftKey) {
if (index > window[o.thisView].config.firstIndex) {
$("."+o.dataTableClass + " tbody tr").each(function(rowIndex) {
if (rowIndex >= window[o.thisView].config.firstIndex && rowIndex <= index) {
retData = $(this).attr("data-return");
ccRestView.insertSelectedRow(o,retData,this)
}
})
} else {
console.log("select rows " + index + " to " + window[o.thisView].config.firstIndex);
}
} else {
// no keys pressed. select this row only
ccRestView.clearSelectedRows(o,this);
ccRestView.insertSelectedRow(o,retData,this);
window[o.thisView].config.firstIndex = index;
}
} else {
// Remove the selected class from the selected row
ccRestView.removeSelectedRow(o,retData,this);
}
} else {
if (o.multiValue!="true") {
// Remove the selected class from all rows first
ccRestView.clearSelectedRows(o,this);
ccRestView.insertSelectedRow(o,retData,this);
} else {
// multi
if (ev.shifKey && ev.ctrlKey) {
} else if (ev.ctrlKey) {
ccRestView.insertSelectedRow(o,retData,this);
window[o.thisView].config.firstIndex = index;
} else if (ev.shiftKey) {
ccRestView.clearSelectedRows(o, this);
if (index > window[o.thisView].config.firstIndex) {
$(o.dataTableClass + " tbody tr").each(function(rowIndex) {
if ($(this).attr("data-index") >= window[o.thisView].config.firstIndex && $(this).attr("data-index") <= index) {
retData = $(this).attr("data-return");
ccRestView.insertSelectedRow(o,retData,this)
}
})
} else {
console.log("select rows " + index + " to " + window[o.thisView].config.firstIndex);
}
} else {
ccRestView.clearSelectedRows(o,this);
ccRestView.insertSelectedRow(o,retData,this, function() {
});
window[o.thisView].config.firstIndex = index;
}
}
}
}); // end click
The double-click event is much simpler. It simply opens the selected document using the XPage defined on the View Definition:
$(row).dblclick(function() {
// get the unid of the double clicked row
var docid = $(this).attr("data-docid");
href = location.href.split(".nsf");
location.href=href[0]+".nsf/"+o.xpage+".xsp?documentId="+docid+"&action=editDocument";
});
Selecting Multiple Rows
Set the multiValue property of the view control to true:

Now, I can select multiple documents by holding <ctrl> or <shift> when clicking:

Building a Demo
In order to demonstrate everything described above, I’m going to make a copy of my viewBasic XPage that was created in Part 3, call it viewBasicCallbacks and add the rowCallback:

I’m leaving everything else intact – using the same settings and the same View Definition.
To demo the double-click, I create a basic XPage to display a used car record and I need to update my View Definition so that when I double click a row in the view it opens this carDocument XPage:



Here’s the link: http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewBasicCallbacks.xsp
In the next post …
We will see how the selected rows can be accessed both client and server side.
Really nice stuff 🙂 and thanks for sharing!
LikeLike