Quick responsive type-ahead function

For an XPages form I needed a workaround for a combobox with a large data-set. At first I tried the select2 jQuery plugin so I could also have a filter option as some form of type-ahead. However it turned out that my select2 list became quiet unresponsive when an entry far in the list was selected (generating the list and filtering was fine).

So my next option was to go for a type-ahead approach. First hit on Google was a NotesIn9 vid about a fancy type based upon the original post of Tim Tripcony (hello 2009).

Due to the large data-set instead of the getAllEntriesByKey method I decided to go for a getEntryByKey method and from the found view entry continue with createViewNavFrom. This turned out to be extremely responsive!

Further I noticed in the original code a hash object is used so that does not guaranteed me a correct order of my result list so I added an additional array, just for a returning a list with correct sort order.

I like especially in this approach the option to control the returned markup so think here about Bootstrap Media object lists. And of course the ease to set the value that you want to have returned in the edit box.

This is not my first blog-post on delivering search function to your application so now you have just another solution available for your toolbox.

The script is available as a gist.

Happy development =)

 

Building a search function with DataTables plugin (VIII)

Introduction

Dates, everybody loves to filter on dates. In Notes views to filter (select) on dates is a criminal offence but luckily filtering on dates in the datatable component is not that hard.

For our search function we are going to apply something as followed:

datatables08

It are two input fields, set as type ‘date’. In some browsers you get a datepicker presented. If you want to have a more fancy one you can always bring in an add-on.

Implementation

So for our HTML structure we included in our XPage:

datatables08b

So what are we going to filter on? In my example I have choosen to filter on the birthday. This is not a field available in the names.nsf so we have to add this ourselfs (now you know why HR always forget your birthdays). To do so I have updated my agent to generate person documents. This was my quickest approach:

bday = Round(Rnd()* 28 ,0)
If bday = 0 Then
bday = 1
End If

bmonth = Round(Rnd()* 12 ,0)
If bmonth = 0 Then
bmonth = 1
End If

byear = arr_birthyear( Round(Rnd()* UBound(arr_birthyear) ,0) )
birth = CStr(bmonth) + “-” + CStr(bday) + “-” + byear

Dim dateTime As NotesDateTime
Set dateTime = session.CreateDateTime( birth )
Call doc.ReplaceItemValue(“Birthday”, dateTime)

If you have a better one (I am sure of that) please drop a line how I can obtain it.

So with this birthday field on the person document, we need to updated our Notes view (adding a column). I had to hard-code the format otherwise I did not get a full year displayed:

@Text(@Year(birthday)) +”-“+
@If(
@Length(@Text(@Month(birthday))) <2;
“0” + @Text(@Month(birthday));
@Text(@Month(birthday))
) +”-“+
@If(
@Length(@Text(@Day(birthday))) <2;
“0” + @Text(@Day(birthday));
@Text(@Day(birthday))
)

 

Also we need to “broadcast” this additional value via our custom service bean:

String birthday = String.valueOf(columnValues.get(12));
if (null!=birthday){
jo.put(“bday”,birthday);
}

Nothing that fancy. We also need to include the extra column to our table header:

<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Company</th>
<th>Job</th>
<th>Birthday</th>
</tr>
</thead>

THEN comes the main modification, in our JavaScript.

We need a function to normalize the date (from string to Date object):

var normalizeDate = function(dateString) {
var date = new Date(dateString);
var normalized = date.getFullYear() + ” + ((“0″ + (date.getMonth() + 1)).slice(-2)) + ” + (“0” + date.getDate()).slice(-2);
return normalized;
}

To apply a date filter to the datatable I have setup the next function:

var filterByDate = function(column, startDate, endDate) {
$.fn.dataTableExt.afnFiltering.push(
function(oSettings, aData, iDataIndex) {
var rowDate = normalizeDate(aData[column]),
start = normalizeDate(startDate),
end = normalizeDate(endDate);
if (start <= rowDate && rowDate <= end) {
return true;
} else if (rowDate >= start && end === ” && start !== ”) {
return true;
} else if (rowDate <= end && start === ” && end !== ”) {
return true;
} else {
return false;
}
}
);
};

In our datatables initialitation script we set the dates for the input fields (let’s hope people may retire in your country at age 65):

var d = new Date();
d.setYear(d.getYear() – 65);
document.getElementById(‘start’).valueAsDate = d;
document.getElementById(‘end’).valueAsDate = new Date();

We need to register any change in the input date fields and call the filterByDate function:

$(“.datePicker”).on(“keyup change”, function() {
var startDate = $(‘#start’).val(),
endDate = $(‘#end’).val();
filterByDate(4, startDate, endDate); // call our filter function
$(“#persons”).dataTable().fnDraw(); // manually redraw the table after filtering
});

To remove the filter we register a function on the onclick event of the Clear Data Filter button:

$(‘#clearFilter’).on(‘click’, function(e){
e.preventDefault();
$.fn.dataTableExt.afnFiltering.length = 0;
$(“#persons”).dataTable().fnDraw();
});

With all this in place we are good to go!

Wrap-up

Yet another great feature for our search form! Perhaps you prefer a slider more suitable for selecting date-ranges or a more fancy date-pciker that works across browsers. The choices are up to you!

Building a search function with DataTables plugin (VII)

Introduction

From my experience some Notes may contain a lot of columns (not sure what the limit is?) and represented on the web, often users need so see the same amount of columns. So what do you do then?

Column visibility to the rescue!

Part of the buttons module is the column visibility. This requires to load an additional resource:

<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/buttons-1.2.1/js/buttons.colVis.js</href>
</resource>

Also remember here to disable AMD loading.

From here it is quiet easy to have the visibility option available as a button in the ‘button bar’. Just add the colvis option in the configuration.

In case you want to result of (in)visibility of columns represented in any export option you need to specify the exportOptions property for the buttons e.g. as followed:

buttons: [
‘copy’,
‘csv’,
‘excel’,
{
extend: ‘pdfHtml5’,
orientation: ‘landscape’,
pageSize: ‘LEGAL’,
title: ‘Person Details’
},
{
extend: ‘print’,
exportOptions: {
columns: ‘:visible’
}
},
‘colvis’
],

Result

As a result a new button is presented and when clicked you can choose which column to make (in)visible:

datatables07

column API

In case you do not want to present a button, but for example anchor links the column API comes at help.

Here is a sample how to register change in a column visibility when clicking an anchor:

$(‘a.toggle-vis’).on( ‘click’, function (e) {
var db = $(“#persons”).DataTable();
e.preventDefault();

// Get the column API object
var column = db.column( $(this).attr(‘data-column’) );

// Toggle the visibility
column.visible( ! column.visible() );
} );

And here is the HTML for presenting the anchors:

Column visibility:FirstnameLastnameCompanyJob title

Finally the end result. Ofcourse you can be creative with CSS for the anchor links, adding different layout for each column state. Be creative!

datatables07b

In another post I will describe how you can accomplish something similar in Xpages with a viewpanel (with a bit of more code)…

Building a search function with DataTables plugin (VI)

Introduction

Often business analists who work with the data in tables eat & sleep in their spreadsheet world so a common asked question is: can we export the data? “Ofcourse you can!” with the datatable component.

Modifications

Below is a short summary of the changed/added items.

Configuration

In order to add the buttons you specify in the datatable configuration which buttons you would like to have e.g.:

dom: ‘Bfrtip’,
buttons: [
‘copy’,
‘csv’,
‘excel’,
{
extend: ‘pdfHtml5’,
orientation: ‘landscape’,
pageSize: ‘LEGAL’,
title: ‘Person Details’
},
‘print’
]

 

Required files

Which files are required for the buttons feature depend on which buttons you would like to display. In the download builder you can specify for which options you would like to go.

AMD fix

An important thing to notice is that most JS files use AMD loading. There have been posted multiple questions and answers how to fix AMD loading with XPages. In my repository I have just removed the amd checking.

Files overview

Probably the files you would like to use will be listed in the Theme design element:

<!– adding buttons –>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/buttons-1.2.1/js/dataTables.buttons.js</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/buttons-1.2.1/js/buttons.flash.js</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/jszip-2.5.0/jszip.js</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/pdfmake-0.1.18/build/pdfmake.js</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/pdfmake-0.1.18/build/vfs_fonts.js</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/buttons-1.2.1/js/buttons.html5.js</href>
</resource>
<resource>
<content-type>application/x-javascript</content-type>
<href>DataTables/buttons-1.2.1/js/buttons.print.js</href>
</resource>
<resource>
<content-type>text/css</content-type>
<href>DataTables/buttons-1.2.1/css/buttons.dataTables.min.css</href>
</resource>

 

Result

As a result you will see the buttons displayed above the table. If you apply filtering to the table the buttons will pick up the provided terms.

datatables06

A little less coding than an X-agent to create a PDF or spreadsheet 🙂

A downsize might be that users are not aware of the amount of data that might reside in your datatable (the only x-number of entries are displayed). Pressing the PDF button with 40K of documents in your notes view takes a bit of time…

Building a search function with DataTables plugin (V)

Introduction

In the last post we moved the input fields out of the table and into a separate form. In this post I will include a nice usability feature for search term highlighting. This feature is particular helpful if you allow search in columns where values do not differ so much from each other like order numbers or article numbers.

Say hello to Mark()

Mark.js is a JavaScript keyword highlighter function. You can use it as pure JavaScript or jQuery plugin.

First let me show the end-result and then we will walk through the changes:

datatables05

Modifications

In order to enable Mark I made chances to the following items:

  • webcontent folder
  • theme
  • custom stylesheet
  • xpage
  • javascript library

In the webcontent folder I included the distributed files. I included only the jQuery plugin as a resource in the Theme document:

<resource>
<content-type>application/x-javascript</content-type>
<href>mark/jquery.mark.js</href>
</resource>

In the custom stylesheet I added a class definition to distinguish the searched term with some highlight:

span.markSearched {
background: yellow;
color: black;
}

In our xpage I added the name attribute for each input element e.g. name=firstname. This attribute will be used as reference to apply the Mark feature.

The main changes take place in the JavaScript. I have written a separate function for it so we do not need to call it on loading:

$(function() {
var db = $(“#persons”).DataTable();
db.destroy();
localStorage.clear();
// Map inputs with columns (nth-child(X))
var inputMapper = {
“firstname”: 1,
“lastname”: 2,
“company”: 3,
“job”: 4
};

// Initialize DataTables
var db = $(“#persons”).DataTable({

stateSave : saveState,
fixedHeader: true,
“language” : {
“lengthMenu” : “Entries per page _MENU_”,
// “info” : “Page _PAGE_ of _PAGES_”,
“infoEmpty” : “No entries found”,
“infoFiltered” : “”
},
scrollY : yScroll,
“ajax” : “api.xsp/Persons” ,
“columns” : [
{
data : “firstname”,
“defaultContent”: “<i>Not set</i>”
},{
data : “lastname”,
“defaultContent”: “<i>Not set</i>”
},{
data : “company”
}
,{
data : “job”
}
],

// Set elements per page
pageLength: 10,
// Disable entry selection
bLengthChange: false,
// Act on table rendering
drawCallback: function() {
// Iterate over all inputs (by mapper names)
$.each(inputMapper, function(colName, index) {
// Determine the entered keyword

var val = $(“input[name='” + colName + “‘]”).val();
// Determine the column related to the input
var $col = $(“.datatables-table tbody tr > td:nth-child(” + index + “)”);
// Remove marks in related column
$col.unmark({
“element”: “span”,
“className”: “markSearched”
});
// Mark in related column
$col.mark(val, {
// Define mark.js options (see https://markjs.io/)
“element”: “span”,
“className”: “markSearched”
});
})
}
});

// Trigger table redraw on search keyword change
$(“input”).on(“keyup change”, function() {
var db = $(“#persons”).DataTable();
var $this = $(this);
var val = $this.val();
var key = $this.attr(“name”);

// Search inside DataTable column
// subtract -1 because :nth-child starts with 1,
// DataTables with 0
db.columns(inputMapper[key] – 1).search(val).draw();
});

});

In the inputMapper variable we define the input fields and their order appearance in the table.

Instead the initComplete callback we will use the drawCallback function instead. The initComplete runs once, the drawCallback runs everytime the table is redrawn, e.g. when we enter search terms. For each input element we take the entered value and match it with values in the corresponding column. When a match is found a span element with the classname markSearched is applied.

Finally we register events for the input elements so if characters are entered or removed the table gets redrawn.

Wrap up

That’s it! I hope you like this feature. I wonder how easily you could do this with a viewPanel control…

 

 

 

Building a search function with DataTables plugin (IV)

Introduction

In the previous posts I demonstrated how to setup your application to generate a datatable component from your Notes view(s) and how to apply filtering & search capabilities to the columns.

In this post I will demonstrate how to build an external form to filter the datatable. I guess in most cases you want to provide some sort of search form and displayed separate from your table (left, right, top). For now we restrict the search form with input boxes.

Modifications

I have applied some modifications to the application. The complete and final code you can view in my Github repository.

Modifications:

  • java class that works as a custom service bean
  • javascript library for initializing datatable
  • xpage to display the datatable

Let’s add a form

The first thing we will do is by adding a Bootstrapped styled form to the xpage:

datatables04pre

Notice the following things:

  • The filter class for the input element
  • The data-column-index attribute for the input element

The filter class will be used to register events on:

$(‘.filter’).on(‘keyup change’, function () {
//clear global search values
db.search(”);
db.column($(this).data(‘columnIndex’)).search(this.value).draw();
});

The data-column-index attribute directs to the index of the corresponding column in the datatable.

With the form in place and the script updated we need to extend our java class so the values for the job title are included:

String job = String.valueOf(columnValues.get(10));
if (null!=job){
jo.put(“job”,job);
}

Let’s see the result

For example if I am looking for a person with firstname starting with Car.., from company Firm (something) acting as a host I get presented:

datatables04

Now with the initial search form in place we will extend it with features to improve it’s usability…

Building a search function with DataTables plugin (III)

Introduction

In the previous post about the datatables jquery component I demonstrated how to create input select controls for each column. In some cases an select control may however not be desired, due to the diversity of the entries in the column (e.g. track number, firstname, date).

Text inputs

In such cases you probably wont to use text inputs where a substring is being matched against the string entries in the column.

To achieve this our function to build the table have to be adapted slightly:

function initPersons(){

$(‘#persons tfoot th’).each( function () {
var title = $(this).text();
$(this).html( ‘<input type=”text” placeholder=”Search ‘+title+'” />’ );
} );

var db = $(“#persons”).DataTable();
db.destroy();
localStorage.clear();
var table = $(“#persons”).DataTable( {
stateSave : saveState,
fixedHeader: true,
“language” : {
“lengthMenu” : “Entries per page _MENU_”,
// “info” : “Page _PAGE_ of _PAGES_”,
“infoEmpty” : “No entries found”,
“infoFiltered” : “”
},
scrollY : yScroll,
“ajax” : “api.xsp/Persons” ,
“columns” : [
{
data : “firstname”,
“defaultContent”: “<i>Not set</i>”
},{
data : “lastname”,
“defaultContent”: “<i>Not set</i>”
},{
data : “company”
}
],
initComplete: function(){
this.api().columns().every( function () {
var column = this;

$( ‘input’, this.footer() ).on( ‘keyup change’, function () {
if ( column.search() !== this.value ) {
column
.search( this.value )
.draw();
}
} );

} );
}
});
}

As a result under each column input boxes which column filter functionality are added underneath the columns:

datatables03