Data for Bootstrap Treeview from a Notes View?

In a modernization project (increase of browser compatibility) I needed to find a solution for a list of links, categorized and sorted like a Notes View.

Since I already use Bootstrap as CSS framework I decided to check the following Bootstrap Treeview project: https://github.com/jonmiles/bootstrap-treeview .  I was satisfied with the following example(s):

treeview_ex.png

The initialisation is pretty simple:

snipp01

Now I just needed to set up a REST service to provide me the data. The service is setup on an XPage and bounded to a Java class:

snipp02

The Java class I will reuse for providing multiple data streams so it detects the different method parameters:

snipp03

As mentioned the data for the list comes from a Notes View. As you all know views entries have different characteristics which I have to bear in mind:

  • Documents can land anywhere in the view since it is categorized on a text field where the multiple levels can set individually eg Europe\\Sweden\\Stockholm or Afrika\\Johannesburg.
  • The view also holds links for other menus organized under a category so I decided to us a viewnavigator and start collecting data from a specific category.
  • For each entry in the view I have to check what type it is: category, document or total. The last one is not of my interest so I have to skip if that option occurs.
  • The columnindentlevel tells me all about where I am in the view in comparison with the columnindentlevel of the previous entry. Here are the scenarios:

// case 1: current indent level < columnindentlevel
// -> new category, propobably start situation
// case 2: current indent level = column indentlevel
// -> new category (sibling) but close the previous
// one first (just one level)
// case 3: current indent level > column indent
// level -> new category but closes the previous
// one(s) first. how many depends on difference curr
// and column level

Ofcourse categories behave different that documents.

So here is the code:

serv01.PNG
serv02
serv03.PNG

Some notes:

  • I have set the header of the REST service to text/html and the plugin needs a JavaScript object. Therefor capture my data response in an eval() method.
  • A target attribute is not provided by the plugin, so I add one myself. Categories have the # as href so based on that info I include a target attribute or not. I do this via a function:
snipp04
  • If the treeview is collapsed there is no anchor element for underlying list-items so you cannot add the target for all links.
  • Also when collapsing and expanding a category the added target attributes are gone. So for opening of every category you need to re-apply the href attribute for underlying anchors.
  • A category can be opened via a ‘twistie’ image or the text link so we need to register an action on these onclick events
snipp05
snipp06

The result is a nice looking ‘Notes View data-driven’ treeview with Bootstrap styling:

snipp07

Happy coding ūüôā

PS. I noticed I have a lot of unused local variables in my code, you may clean that up ūüôā

Draggable modal

For a project a request was to make a Bootstrap modal (a Dialog control in the Extension Library) as draggable. By default, the modal does not have that feature. So can you make it draggable again?

In xsp-mixin stylesheet the class xsp-responsive-modal is using !important for the left and top properties which prevents that the modal is draggable. A work-around can be achieved by replacing these class properties with your own that does not use !important.

To replace the default class add for the onShow event of the dialog:

<xe:this.onShow>
<![CDATA[
x$(“#{id:dialog1}”).removeClass(“xsp-responsive-modal”).addClass(“draggable-responsive-modal”);
]]>
</xe:this.onShow>

Your custom class could look as followed:

.draggable-responsive-modal {
display: block;
width: auto;
left: 0;
top: 0;
z-index: 1050 !important;
}

The x$() function is the infamous utility function from Mark Roden to work with JQuery.

I added the code to GitHub Gist: https://gist.github.com/PatrickKwinten/1d442e28ff0d59f8e01728bffab13e4f

Add 20 years of experience to your workforce

 

You can 20 years of experience within IBM Notes and Web development to your workforce by hiring me.

Interested? Read my curriculum vitae on LinkedIn: http://www.linkedin.com/in/patrickkwinten and get in contact.

I am happy to work WITH you !

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 (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

Another Graph sample from Domino Explorer

Introduction

In a previous post I dove into Domino Explorer, an XPages application that cans the Domino Directory and the Catalog by using the Graph capabilities in the OpenNTF Domino API.

In this post will describe another XPage in that application. Perhaps it helps to get a better picture on the Graph db and how to build an application around it using XPages, JavaScript, & Java.

Hopefully I will be ready writing before a European football final kicks off…

AllACL.xsp

The xpage I discuss is allacl.xsp. Basically it displays at start a table with ACL entries for the entire catalog. When I click on an entry or row in the table I get presented a list of applications where the selected ACL entry resides in the ACL.

The result of the scenario above captured in the following screen:

de_acl

As the image suggests¬†the entry [Anonymous] resides in 4 applications.¬†Let’s dive in a little deeper into the XPage to see how it’s done…

On document ready

The XPage contains a JS library whichs fires an AJAX call to collect the data for the DataTable object. The rest service resides on the XPage and is accessed via it’s pathinfo property. The restservice is bound to a custom servicebean which resides in the application.

The AJAX call does not provide a parameter to the restservice, so the java class will collect all the vertices of type DXACLEntry.class. For each vertice a JSONJavaObject containing name and level and placed in a JSONJAVAArray object. This array is placed in a JSONJavaObject and returned as a string to the restservice.

When the data is received in the DataTable object only the name property is placed.

The routing is visualized in the next image:

de_acl_rest

On row click

In the JS library for the DataTable object is also defined what should happen when a row in the table is clicked.

Here the name value for the selected row is used in a function that initiates a second DataTable object. Again a call is made to the same restservice. However now the name value is send as a parameter (“?name=” + name value).

The servicebean picks this parameter up (String name = request.getParameter(“name”)) and get’s all the vertices from the Graph with the provided name, matching the DXACLEntry (DXACLEntry aclEntry = graph.getElement(name, DXACLEntry.class)).

Similar as in the document ready event for each found DXACLEntry object a JSONJavaObject is created, now with some more properties (title,filepath, replicaId,server) and placed in a JSONJavaArray. This array is returned as a string to the restservice.

When the results are received by the DataTable object the four properties are displayed per column.

de_acl_rest2

Summary

The scenario is that simple. The DataTable plugin is really a great plugin that does a lot for you and can save you multiple design elements (view controls) by defining in your code what you want in it as result and in which order.

Now let me enjoy my evening of football with a cold beer! Happy development =)

 

 

prettytime for your XPages

Introduction

I guess most of us believe that not all users are robots, and especially in social applications where you try to approach humans as human friendly as possible the display of date and time in such format can be off added value.

Timeago

There are many scripts to display date and time in a human friendly format. For a project we are using timeago,¬†a jQuery plugin that makes it easy to support automatically updating fuzzy timestamps (e.g. “4 minutes ago” or “about 1 day ago”).

However, in combination with an infinite scroll feature to navigate through document collections we notice that the script not always return instantly the transformed date/time.

That is why I looked at a server-side transformation.

PrettyTime

PrettyTime is an OpenSource time formatting library. Completely customizable, it creates human readable, relative timestamps like those seen on Digg, Twitter, and Facebook.

It turned out the implementation of PrettyTime was quiet simple. I will describe the steps for you.

Install the jar & call the Java class

Download the project jar file and place it in the WebContent \ WEB-INF \ lib folder.  For a computed field on a custom control in a repeat control I have as SSJS code to call the Java class:

<xp:text>
<xp:this.value><![CDATA[#{javascript:var theDate = compositeData.Date;
p = new org.ocpsoft.pretty.time.PrettyTime();
p.format(new Date(theDate))}]]></xp:this.value>
</xp:text>

As a result I get to date displayed as:

prettytime

Pro’s & Con’s

In comparison with client-side solutions I have detect some con’s and pro’s for a server side solution:

  • Static; the client side solutions are capably of updating the date/time value on the fly.
  • Speed; the server side generated human friendly date¬†is there when a document row is presented.
  • Extensibility; the number of display languages is more numerous for most client-side solutions. Also you can often “configure” the text format when desired.
  • Consistency; having a human friendly date in a local language may be in conflict with internationalization of your XPages app since that might require more work.

Anyway: I happy to hear how you have solved the human friendly date issue.

Happy development!

Enforce HTTPS for my XPages

Sometimes it can be benefitial to enforce the https protocol. My use-case was that an XPages app was going to be opened from the mobile Connections app and just the HTTP protocol would case an additional login dialog to appear.

So the following SSJS code is what I came up with:

<xp:this.beforePageLoad><![CDATA[#{javascript:currURL = context.getUrl().getScheme();
if (currURL ==”http”){
var baseURL = context.getUrl().toString().toLowerCase();
safeURL = baseURL.replace(‘http’,’https’);
facesContext.getExternalContext().redirect(safeURL);
}
}]]></xp:this.beforePageLoad>

The code will check if the xpage is called with the http protocol, if so the same page is called but then with the https protocol enforced.

Since the application aggregates a lot of information from Notes views, which the application administrator can add, I also check if any URL’s with the http protocol are being displayed and transform them with the https protocol to reduce the first check:

<xp:eventHandler event=”onClientLoad” submit=”false”>
<xp:this.script><![CDATA[function toSSL() {
var current_host = location.host;
var hosts = [‘serverx.acme.com’,’servery.acme.com’,”serverz.acme.com’,current_host];
$.each( hosts, function( i, l ){
$(‘a[href*=”‘ + l +'”]’, ‘body’).each(function() {
var $a = $(this);
var href = $a.attr(‘href’);
if(href.indexOf(‘https’) == -1) {
var ssl = href.replace(‘http’, ‘https’);
$a.attr(‘href’, ssl);
}
});
});
};
$( document ).ready(function() {
toSSL();
});]]></xp:this.script>
</xp:eventHandler>

Hiding Notes links for Mobile Web users

Probably you are working in a collaborative environment where users access their beloved Notes apps with multiple clients: The Notes client, The Notes Browser plugin, Web browser and mobile devices.

Not all Notes applications have defined business cases in which they should become web-enabled. This could be because of application complexity or that the life-cycle / ownership of the application is poorly defined.

Nevertheless it is hard to prohibit that links to these applications or documents in them are being published on web-pages (probably you want to promote the distribution of information). Luckily there is that lovely tool called the IBM Notes Browser Plugin but this plugin is restricted to normal web browsers and not to all of them.

And then you have your ‘leading’ technology adapting usergroup that prefer¬†to access Notes data in app and web-page form. For them the Notes Browser Plugin is not available so why should you bother to show them links to documents in applications that have not been web-enabled yet?

The following script will hide these Notes link on web-pages  and replace them with just text. It uses the deviceBean which provides an easy to use and easy to program way of identifying a popular range of mobile and tablet devices.

var isMobile = ‘#{javascript:return deviceBean.isMobile()}’;
if (isMobile==’true’){
$( document ).ready(function() {
$(“a[href^=’Notes://’]”).each(function () {
$(this).replaceWith($(this).text());
});
});
}

Add it to the onClientLoad event of your XPage.

Building a Live Search function with Domino Access Services & jQuery Tokeninput

Introduction

A live search is a function where get search results returned while you type. In this post I will describe how you can add such a feature using the jQuery Tokeninput plugin and perform a full text search with Domino Access Services (DAS)as your data provider.

But first let’s take a look at how the end result looks like:

searchres

As in other examples we will apply the live search to the infamous fakenames application. You can grab this directory application from Codestore.

Step 1 – Download the resources and add them to your XPage

Add the following resources to a custom control:

Step 2 – Add the filter and search field

In the result screenshot you noticed the following elements:

  • A filter (People, Groups, Holidays)
  • A search field (with typeahead).
  • A (formatted) result list.

Therefor add the following HTML on your custom control:

As you notice we will be using Bootstrap to make the appearance a bit more attractive.

search_param

The hidden input element search_param will be the container for our selected filter.  The following code will set the value:

livesearch-input

The Tokeninput plugin will inject an (initially empty) unordered result list and input field in the livesearch-input input element.

Include a function to initiate the Tokeninput plugin:

Step 3 – Activate the plugin

In order to activate the Tokeninput plugin you need to include a script block control on your custom control and ad the following script:

function getObjType() {
var val = $(“#search_param”).val();
return val;
}

var restPrefix = “./api/data/collections/name/”
var hintText = “Enter text, use asterix(*) for Fulltext wildcards”;
var searchingText = “Searching…”;
var queryParam = “search”;
var propertyToSearch = “name”;
var searchDelay = 2000;
var minChars = 2;
var resultsLimit = 5; //not working??
var noResultsText = “No matches”;
var tokenLimit = 1;
var preventDuplicates = true;
var onAdd = function(item) {
var objType = getObjType();
var docID = item[‘@unid’];
var URL = “”;
switch (objType) {
case “Groups”:
URL = “Group.xsp?unid=” + docID;
break;
case “Holidays”:
URL = “Holiday.xsp?unid=” + docID;
break;
default:
URL = “Person.xsp?unid=” + docID;
}
window.location = URL;
};
var resultsFormatter = function(item) {
var objType = getObjType();
switch (objType) {
case “Group”:
sub = “dummy group sub”;
break;
case “Holiday”:
sub = “dummy Holiday sub”;
break;
default:
sub = “” + item.job;
}
if (sub == “”) {
sub = “No title available“;
}
if (sub.length > 30) sub = sub.substring(0, 30) + “…”;
var name = item.name;
if (name.length > 30) name = name.substring(0, 30) + “…”;
var img = ‘‘;
var text = ‘

‘ + name + ‘

‘ + sub + ‘


return ‘

  • ‘ + ‘
    ‘ + img + text + ‘

‘;
};

function setPlaceHolder() {
$(“#token-input-livesearch-input”).attr(“placeholder”, “Enter text”);
}

function init() {
$(“#livesearch-input”).tokenInput(restPrefix + getObjType(), {
hintText: hintText,
searchingText: searchingText,
queryParam: queryParam,
propertyToSearch: propertyToSearch,
searchDelay: searchDelay,
minChars: minChars,
resultsLimit: resultsLimit,
noResultsText: noResultsText,
tokenLimit: tokenLimit,
onAdd: onAdd,
preventDuplicates: preventDuplicates,
resultsFormatter: resultsFormatter
});
setPlaceHolder();
}

function re_init(objType) {
$(“#token-input-livesearch-input”).remove();
$(“.token-input-list”).remove();
$(“#livesearch-input”).tokenInput(restPrefix + getObjType(), {
hintText: hintText,
searchingText: searchingText,
queryParam: queryParam,
propertyToSearch: propertyToSearch,
searchDelay: searchDelay,
minChars: minChars,
resultsLimit: resultsLimit,
noResultsText: noResultsText,
tokenLimit: tokenLimit,
onAdd: onAdd,
preventDuplicates: preventDuplicates,
resultsFormatter: resultsFormatter
});
setPlaceHolder();
}

function submitSearch() {
//not required, overwritten by onAdd function.
}

As you see the Tokeinput plugin can be steered via diverse parameters. A clear description of the available options you can read here.

In our case we determine:

Data Provider

var restPrefix = “./api/data/collections/name/”

This defines we will be using Domino Access Services as data provider. We will use the names of the views People, Groups, Holidays which correspond with the options in our filter list.

Remember you need to enable DAS for the database AND the views.

onAdd = function(item)

The variable onAdd is a function that will be called when we select an item from the result list. We have set to allow only one item to be selected and the onAdd function will open a new window location with the corresponding object/document.

URL = “Person.xsp?unid=” + docID;
}
window.location = URL;

resultsFormatter = function(item)

This variable defines how the items in the collection provided by Domino Access Services will be presented in the UI. You can be as creative with it as you like. I choose for the option for an image, distinguished name and a sub-title e.g. the function for a person.

Ready

Are we done already? Yes we are! All the heavy lifting is done by the Tokeninput plugin and Domino Access Services.

Notice that Full Text search in Domino Access Services supports the usage of an asterix. Otherwise you get only results returned with an exact match.

firebugUse a plugin for your browser to check the URL call and response to DAS.

Some final thoughts

Do I found the live search feature useful?

– Yeah. Especially in mobile web applications you want to avoid that a user has to go back to home navigation each and every time. And with large data-sets infinite scrolling is a good option.

Do I find the Tokeninput plugin valuable?

РI find the plugin easy to understand but I noticed not all properties are implemented :-/  An example of something what I expected to be implemented is the number of returned results (fixed to max 10 items).

Can I recommend the plugin?

РIn case you use Bootstrap applying a bootstrap theme meant mostly stripping the default style sheet. In case you use the Application Layout control from the Extension Library you will discover some problems:

  • When applying the live search in the search bar you get overlay with the Utility links.
  • When applying a filter option you get scrolling issues¬†when opening the dropdown menu in the navbar section.
  • The result list can not be displayed direct under the search field due to the overlay of the navbar and it’s margins.

The consequence of these problems above made me decide to place the search bar in the main column section instead of the search bar and align it to the right via the pull-right class in Bootstrap. The result looks similar but I loose some white space. (perhaps the solution lies in understanding Bootstrap a bit more).

In case you can live with these conditions or restrictions I think you have an awesome desired feature!

Your turn!

What are your thoughts? In case you have provide a live search feature via some other plugin or custom code I am happy to hear from you.

I have uploaded a sample on Dropbox. I am happy to hear the improvements you have made.

Happy coding =)