Reader question – How does the Flexible View Control handle response documents?
Posted: January 11, 2021 Filed under: DataTables, Domino, HCL, JavaScript, jQuery, low-code, XPages | Tags: DataTables, Domino, HCL, jQuery, low-code, XPages 2 CommentsRecently, I received a comment on a blog post asking how the FVC handles response documents:
One question I have is how best to deal with response documents in views, I am not sure if I have missed something in the tutorials but I am unsure how to render responses or if the control handles views with responses?
In building the control, response documents were never even a consideration for me since we don’t use them in our environment so the Flexible View Control does not contain specific functionality for handling response documents. However, because the framework was built to be as flexible and configurable as possible, the tools provided make creating that functionality incredibly easy. Based on the comment above I created a demo to prove that:
http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewGeneral.xsp?viewdef=response-docs
Building the Demo
For this demo I created a new used car database and added some records from the original used car database. In addition to the listing form that already existed for the main docs, I added a response form and response to response form so I could add comments to listings and comments to comments:

On both the response and response to response forms the ID field from the original document is being inherited to have a common value to link all of the documents besides docids, but it’s not really necessary to make the demo function properly.

Notes View Configuration
To get the View set up I need to make sure that Show response documents in a hierarchy is turned on and add the comments and parent_docid columns.
Note: No column is set to “show responses only”.

After adding a couple of responses in the Notes Client the back-end view looks like this:

The View Definition
In the View Definition I’m setting the columns for the responses to hidden because I don’t need that data to display. We *do* need the comments column to display but you will see momentarily how that will be done.

The REST Service
Another important step is making sure the REST service being utilized to serve the data contains the appropriate document hierarchy information. Since we are using the standard <xe:viewJsonService>, the system columns can be configured to include the position data in addition to the unid. By default, all of the columns are enabled but I prefer to remove the ones I don’t need to cut down on payload size.

Now we have the data we need:

Configuring the Flexible View Control
So after all this setup we still haven’t actually displayed anything with the control yet. How do we go about doing that? Before adding functionality to create the response hierarchy in the control the response docs look like standard docs, but with little information:

The Callbacks
The first step I take to build the response hierarchy is adding a createdRow callback function to the control. Normally, I would probably use a rowCallback out of habit but in the process of tinkering around, I moved my rowCallback code into the createdRow callback. Here is what the DataTables documentation says about each:
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. This means that the contents of the row might not have dimensions ($().width()
for example) if it is not already in the document.
This function might be used for setting the row class name or otherwise manipulating the row’str
element (although note thatcreatedRow
can often be more efficient).
createdRow
This callback is executed when aTR
element is created (and allTD
child elements have been inserted), or registered if using a DOM source, allowing manipulation of theTR
element.
This is particularly useful when using deferred rendering (deferRender
) or server-side processing (serverSide
) so you can add events, class name information or otherwise format the row when it is created.
There are a few things that need to be accomplished in the createdRow:
- For responses, reconstruct the response row to be more like the “classic” Notes response document and add classes and attributes that connect it to its parent row.
- For non-responses, add that document’s docid as a row class and add a click event that will expand and collapse any responses.
- For responses with responses, add a click event to expand/collapse its child documents.
- Use the position data to provide classes and attributes to the response rows to make it easier to build our hierarchy.
rowCallbackResponse : 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;
}
// action item view on pcspDocument
$(row).attr("data-docid",data['@unid']);
// get the colspan
var colspan = $('td',row).length;
// if this is a response then add some info to this row
var position = data['@position'];
if (position.split('.').length > 1) {
// remove cells and replace with 1
$(row).html('<td colspan="' + colspan + '"><div><i class="fa fa-comment-o right5"></i>'+ data['comments'] + '</div></td>')
// add some classes and attributes
$(row).addClass('response response-'+data['parent_docid']);
$(row).attr('data-position',position);
$(row).addClass('response-level-'+position.split('.').length)
// add a reference to the parent doc
$(row).attr('data-parent-docid',data['parent_docid']);
}
$(row).click(function(ev) {
$('[data-position^="' + position + '."]').toggleClass('hidden');
$('td:first div .fa',this).toggleClass('fa-caret-down');
$('td:first div .fa',this).toggleClass('fa-caret-right');
});
}
I also add a little CSS to create the indentation of the responses:
table.dataTable tr.response-level-2 td {padding-left:100px}
table.dataTable tr.response-level-3 td {padding-left:125px}
table.dataTable tr.response-level-4 td {padding-left:150px}
Now looking at the View, we are starting to see a result more like what we would expect. We can expand and collapse the responses by clicking the parent row and any response row that has responses, but we have a little bit more work to do. I need to add a visual indicator to those rows so the user can see they have responses.

To do that I’m going to add a drawCallback function. With that I want to accomplish the following:
- Add a visual indicator to any row that has response documents. Using FontAwesome, a twistie icon is added to the first column of any row with responses to simulate the classic Notes look. The great thing is this is completely customizable. I chose the first column for simplicity.
- Move the node of each response so they appear after the parent. This is not an issue when the View first loads, but is necessary when the View is sorted or filtered.
drawCallbackResponse : function() {
// retrieve data from browser sessionStorage
var responses = sessionStorage.getItem('responses') != '' ? JSON.parse(sessionStorage.getItem('responses')) : {};
// loop through each item
// responses are stored in an object with the parent docid as key
for (var key in responses) {
if (responses.hasOwnProperty(key)) {
// prevent dupes
$('[data-parent-docid="' + key + '"]').remove();
var pos = '.'+key;
// insert each response after
for (var x=0;x<responses[key].length;x++) {
pos = $(responses[key][x]).insertAfter(pos);
}
}
}
$('.response').each(function() {
var post = $('.'+$(this).attr('data-parent-docid'));
if ($('.'+$(this).attr('data-parent-docid') + ' td:first div .fa-caret-down').length==0 &&
$('.'+$(this).attr('data-parent-docid') + ' td:first div .fa-caret-right').length==0
) {
$('.'+$(this).attr('data-parent-docid') + ' td:first div').prepend('<i class="fa fa-caret-down right5"></i>');
}
pos = $(this).insertAfter(pos);
if ($('.fa').hasClass('fa-caret-right')) {
$(this).addClass('hidden');
}
});
}
Now we have a fully functional View with response documents in a hierarchy. We can click the column headers to change the sort order of the View and the responses will still travel with their parent.

But we still have an issue .. filtering the View will make the responses disappear because the response rows don’t contain the same data as their parent.

Again, we have all of the tools we need to overcome this. Using the initComplete callback functionality of the control, we can store the html of all response rows in the sessionStorage of the browser when the View initialization is complete:

initCompleteResponse : function(_v,dataTableClass) {
var responses = {};
var oa = [];
var o = {};
$('.response').each(function() {
//store the html of response rows
if (typeof (responses[$(this).attr('data-parent-docid')]) == 'undefined') {
responses[$(this).attr('data-parent-docid')] = [];
}
var html = $(this).clone().wrap('<p/>').parent().html();
responses[$(this).attr('data-parent-docid')].push(html);
});
sessionStorage.setItem('responses',JSON.stringify(responses));
}
You may have noticed in the drawCallback function there was a reference to the sessionStorage:
// retrieve data from browser sessionStorage
var responses = sessionStorage.getItem('responses') != '' ? JSON.parse(sessionStorage.getItem('responses')) : {};
By doing this little bit of caching we can ensure that we can get the responses back into the table even if the DataTables filtering removes these records from the display.

Conclusion
The combination of the createdRow (or rowCallback), initComplete, and the drawCallback functions give us the power to rebuild the response hierarchy of a Notes View in the Flexible View Control for XPages.
Hello Michael, I am following along using the latest version on Github (2021-01-24b). When creating adminViewDefinitionDoc the database option only has *Current Database*… I am not able to select the database that was set in the Config doc. Also, after adding multiple databases in the Config Doc the adminViewDefinitionDoc errors on open:
Script interpreter error, line=52, col=54: [TypeError] Error calling method ‘toArray()’ on an object of type ‘Array [JavaScript Object]’ at [/ssjsCCRestView.jss]
Line 52: v = (typeof v) == “string” ? [v] : v.toArray()
Looking forward to moving through the steps and eventually implementing this in my application.
LikeLiked by 1 person
Hello Michael. How can i get flexview to work at the same time with categories and response documents?
Thank.
LikeLiked by 1 person