Bootstrap Pills navigation for Dynamic Content control

Introduction

Besides parameters you can include a hash (#) property in the URL in your XPages application. This “fragment identifier” is a short string of characters that refers to a resource that is subordinate to another, primary resource.

So it is mainly used to set a location anchor to a part in a page.

In this post I will show how to get value of Hash property in XPages and make a nice Bootstrap Pills navigation control for the Dynamic Content Control

XPages

In XPages you can use it within the xp:link control but the dynamic content control also makes use of it.

XSnippet Dynamic Content Control

The following XSnippet demonstrates how to set up a dynamic content control:

<?xml version="1.0" encoding="UTF-8"?>
  <xp:link escape="true" text="Load Dynamic Content Server side" id="link1">
    <xp:eventHandler event="onclick" submit="true" refreshMode="partial">
      <xp:this.action>
        <![CDATA[#{javascript:getComponent("dynC").show("key1")}]]>
      </xp:this.action>
    </xp:eventHandler>
  </xp:link>
  <xp:link escape="true" text="Load Dynamic Content Client side" id="link2">
    <xp:eventHandler event="onclick" submit="false">
      <xp:this.script><![CDATA[XSP.showContent("#{id:dynC}","key2")]]></xp:this.script>
    </xp:eventHandler>
  </xp:link>
  <xe:dynamicContent id="dynC" useHash="true">
    <xp:this.facets>
      <xp:panel xp:key="key1">Content1</xp:panel>
      <xp:panel xp:key="key2">Content2</xp:panel>
    </xp:this.facets>
  </xe:dynamicContent>
</xp:view>
With this snippet you get the following Hash set in the URL of your XPage: #content=…

Getting value of Hash property

I suggest to use one of the following 2 options to collect the value of the hash property:

  1. Use a custom function
  2. Use Dojo

The first option is another XSnippet by Thomas Adrian. The second is a suggestion by Mark Leusink to (re)use Dojo.

getHashUrlVars

Include the function in a shared CSJS library and call it via the function getHashUrlVars()["content"].

dojo.queryToObject

The dojo approach is also straightforward. Call the function dojo.queryToObject( dojo.hash() ); and then abstract the content property var active = hash[“content”];.

Pills navigation for Dynamic Content Control

Since there is no list item control in XPages it is a bit hard to set the properties via computation. However via CSJS you can manipulate the DOM a lot and add some responsiveness yourself.

The following script builds an unorderlist containing links to set a part of the Dynamic Content control as visible:

<xp:div id=”nav-pills-container”>
<ul class=”nav nav-pills”>
<li id=”Pictures” class=”dclist”>
<xp:link escape=”true” text=”Pictures” id=”link4″>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”partial”>
<xp:this.action>
<![CDATA[#{javascript:getComponent(“dynC”).show(“Pictures”)}]]>
</xp:this.action>
</xp:eventHandler>
</xp:link>
</li>

<li id=”Albums” class=”dclist”>
<xp:link escape=”true” text=”Albums” id=”link1″>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”partial”>
<xp:this.action>
<![CDATA[#{javascript:getComponent(“dynC”).show(“Albums”)}]]>
</xp:this.action>
</xp:eventHandler>
</xp:link>
</li>
<li id=”Profiles” class=”dclist”>
<xp:link escape=”true” text=”Profiles” id=”link2″>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”partial”>
<xp:this.action>
<![CDATA[#{javascript:getComponent(“dynC”).show(“Profiles”)}]]>
</xp:this.action>
</xp:eventHandler>
</xp:link>
</li>

</ul>

The second script checks which content section is set “visible” for the Dynamic Content control via the hash property in the URL. The corresponding list item in the Pills navigation is set as active.

<xp:eventHandler event=”onClientLoad” submit=”false”>
<xp:this.script><![CDATA[function setActive(id){
$( “li.dclist” ).removeClass( “active” );
var id = “#” + id;
$(id).addClass( “active” );
}

var hash = dojo.queryToObject( dojo.hash() );
var active = hash[“content”];
setActive(active);
]]></xp:this.script>
</xp:eventHandler>

As a result I get the correct list item displayed as active for my Pills navigation:

navpills

Code

In case you want to see the code working in an example download the Bildr application on OpenNTF. There I have applied this technique for the search function.

 

Adding a sliding menu to your Bootstrap Application Layout control

Introduction

Since responsive webdesign (RWD) is on top of the list in most application development projects these days I assume most of us have been looking at the options within IBM Notes.

Bootstrap

In case you use the Bootstrap plugin in the Extension Library you have noticed that the menu in the left column get presented above the main column in smaller devices.

bs_desktop

Image: Display on desktop

bs_phone

Image: Display on phone

As you can see in a more advanced application the menu options already suppress the content too much at the bottom. So what are your options?

Off Canvas Slide Menu For Bootstrap

In this post I will describe how I implemented in XPages a slide menu which is described here. We still use the Application Layout control with the Bootstrap RWD functionality since it contains more functionality than just to provide a navbar.

Step 1 – Provide a Menu icon

We need something (text, button, icon) from where we can initiate the appearance of the menu. In our case I choose to include a ‘hamburger’ menu icon in the navBarLogo property:

<xe:this.configuration>
<xe:simpleResponsiveConfiguration navbar=”true”
loaded=”true” navbarLogo=”/1432224591_menu-alt.png”
navbarLogoStyleClass=”extraMenu” navbarText=”MyApp”>
</xe:simpleResponsiveConfiguration>
</xe:this.configuration>

For example iconfinder provide great icons.

Step 2 – Register Click event for Menu icon

I included “extraMenu” as an additional styleclass. This styleclass we will use when the document is ready. We do this in a Script Block control:

<xp:scriptBlock id=”onLoadScript” type=”text/javascript”>

<xp:this.value><![CDATA[
$(document).ready(function(){
$(“.extraMenu“).attr(“id”,”nav-expander“);
//Navigation Menu Slider
$(‘#nav-expander‘).on(‘click‘,function(e){
e.preventDefault();
$(‘body’).toggleClass(‘nav-expanded’);
});
$(‘#nav-close’).on(‘click’,function(e){
e.preventDefault();
$(‘body’).removeClass(‘nav-expanded’);
});

// Initialize navgoco with default options
$(“.main-menu”).navgoco({
caret: ‘<span class=”caret”></span>’,
accordion: false,
openClass: ‘open’,
save: true,
cookie: {
name: ‘navgoco’,
expires: false,
path: ‘/’
},
slide: {
duration: 300,
easing: ‘swing’
}
});
});]]></xp:this.value>
</xp:scriptBlock>

When the document is ready we assign the ID attribute to the navbarLogo since this ID is used to bind an click event on. We could have bind the event directly on the class but I am not sure if the ID is used in any other way (don’t break what isn’t broken).

Step 3 – Provide a menu that slides in.

In this example I simply provide the menu that is used in the demo:

<nav>
<ul class=”list-unstyled main-menu”>
<!–Include your navigation here–>
<li class=”text-right”>
<a href=”#” id=”nav-close”>X</a>
</li>
<li><a href=”#”>Menu One<span class=”icon”></span></a></li>
<li><a href=”#”>Menu Two<span class=”icon”></span></a></li>
<li><a href=”#”>Menu Three<span class=”icon”></span></a></li>
<li>
<a href=”#”>Dropdown</a>
<ul class=”list-unstyled”>
<li class=”sub-nav”><a href=”#”>Sub Menu One<span class=”icon”></span></a></li>
<li class=”sub-nav”><a href=”#”>Sub Menu Two<span class=”icon”></span></a></li>
<li class=”sub-nav”><a href=”#”>Sub Menu Three<span class=”icon”></span></a></li>
<li class=”sub-nav”><a href=”#”>Sub Menu Four<span class=”icon”></span></a></li>
<li class=”sub-nav”><a href=”#”>Sub Menu Five<span class=”icon”></span></a></li>
</ul>
</li>
<li><a href=”#”>Menu Four<span class=”icon”></span></a></li>
<li><a href=”#”>Menu Five<span class=”icon”></span></a></li>
</ul>
</nav>

Step 4 – Include the supporting files

The demo uses the following files which you should add to your application as resources:

js

<script
src=”/jquery.navgoco.js”>
</script>

css

<xp:this.resources>
<xp:styleSheet href=”/main.css”></xp:styleSheet>
</xp:this.resources>

The result

The image below gives you an indication what the result looks like. When you click the hamburger icon the menu appears from the right. With the X marker you can close the menu again.

result

Notice that my content is not suppressed below the menu as the Bootstrap plugin would do.

Download

An demo XPages application is available on my dropbox account

breadcrumbs in XPages (non extension library style)

I have still got a question open on the XPages development forum regarding how to use the breadcrumb control from the Extension Library. In case you happen to have an answer on the question, please post  it there (or here…).

Previously I have written more on providing breadcrumbs in Domino and XPages. I overlooked the unshift JavaScript method which allows you to add items in the beginning of an array.

In combination with the getParentID function for a NotesDocument you can simply create an reversed array of all parents and use the array as data a repeat control. Within the repeat control you place a link control and populate the label and document reference.

Here is the code sample:

<?xml version=”1.0″ encoding=”UTF-8″?>
<xp:view xmlns:xp=”http://www.ibm.com/xsp/core”&gt;
<xp:this.data>
<xp:dominoDocument var=”doc” formName=”Document”></xp:dominoDocument>
</xp:this.data>
<xp:div styleClass=”lotusBreadcrumbs” role=”navigation”>
<xp:link value=”/” text=”${database.title}” />
 &gt;&gt; 
<xp:repeat var=”breadcrumbs”>
<xp:this.value><![CDATA[#{javascript:var result = [];
var ref = doc.getParentId();
while(ref){
var parent = database.getDocumentByUNID(ref);
result.unshift({
unid: ref,
label: parent.getItemValueString(“Tx_Document_Title”)
})
ref = parent.getParentDocumentUNID();
}
return result;
}]]></xp:this.value>
<xp:link value=”/0/#{breadcrumbs.unid}” text=”#{breadcrumbs.label}” />
 &gt;&gt; 
</xp:repeat>
<xp:text value=”#{doc.Tx_Document_Title}” />
</xp:div>
</xp:view>

Here is how a result could look like:

breadcrumbs

 

Not really exciting but hopefully helpful for you.

Job Wanted

Looking for a creative brain? Choose me!

job-wanted

Navbar with dropdown menu from Notes view in Twitter Bootstrap (with XPages)

Introduction

Hey hey, sorry to bother you again with a post about using a Notes view in combination with Twitter Bootstrap.

In my previous post I was not sure about using nav-pills. I fear not all users will understand them. I assume that most users are common with a site header with navigational options. This navigation can be like tab bars (hihi watch some real old work) some with nested tabs in fancy drop down menus. Twitter Bootstrap have probably taken notice of this expected behavior and provided therefor the navbar component.

Implementation

I used the same conditions as in my previous post (Mark Leusink’s Bootstrap4XPages demo site and my Notes view:

notesview clean

 

Here is how the code on my XPage looks like:

<div class=”navbar navbar-inverse navbar-fixed-top”>
<div class=”navbar-inner”>
<div class=”container-fluid”>
<a data-target=”.nav-collapse” data-toggle=”collapse”
class=”btn btn-navbar”>
<span class=”icon-bar”></span>
<span class=”icon-bar”></span>
<span class=”icon-bar”></span>
</a>
<a href=”#” class=”brand”>
<xp:text escape=”true” id=”computedField1″ value=”#{javascript:@DbTitle()}”></xp:text></a>
<div class=”nav-collapse”>
<ul class=”nav”>
<li class=”dropdown”>
<a data-toggle=”dropdown” class=”dropdown-toggle” href=”#”>Site Index<b class=”caret”></b></a>
<xp:text escape=”false” disableTheme=”true” value=”#{javascript:getList()}” rendered=”true”></xp:text>
</li>
<li class=”active”>
<xp:link escape=”true” text=”Home” id=”link1″ value=”/index.xsp”></xp:link>
</li>
<li>
<a href=”#”>Static Link 1</a>
</li>
<li class=”divider-vertical”></li>
<li>
<a href=”#”>Static Link 2</a>
</li>
</ul>
<form action=”” class=”navbar-search pull-left”><input type=”text” placeholder=”Search” class=”search-query span2″ /></form>
<ul class=”nav pull-right”>
<li>
<a href=”#”>Static Link 3</a>
</li>
<li class=”divider-vertical”></li>
<li>
<a href=”#”>Static Link 4</a>
</li>
</ul>
</div><!– /.nav-collapse –>
</div>
</div>
</div>

Note: I had to set attributes for escape and disableTheme for the computed field otherwise the navbar would get messed up.

In the computed field I call a SSJS function that is similar to the one in the previous post:

function getList(){
var nav:NotesViewNavigator = database.getView(“$v-treeview-clean”).createViewNav();
var entry:NotesViewEntry = nav.getFirst();
if (entry !=null){
var countLevel:Integer = 0;
var curLevel:Integer;
var list = “<ul class=\”dropdown-menu\”>”;
while (entry !=null){
var edoc:NotesDocument = entry.getDocument();
entryValue = entry.getColumnValues().elementAt(1).toString();
var col:NotesDocumentCollection = edoc.getResponses();
curLevel = entry.getColumnIndentLevel();
if (col.getCount()>0){
//prep for new entry with response(s)
var difLevel = countLevel – curLevel ;
var closure = “”
for (var i=0; i<(difLevel); i++){
closure = closure + “</ul></li>”
}
list = list + closure;
list = list + “<li class=\”dropdown-submenu\”>”;
list = list + “<a href=\”#\”>” + entryValue + “</a>”;
list = list + “<ul class=\”dropdown-menu\”>”;
//increase counter
countLevel = curLevel + 1;
}
else{
if(curLevel==countLevel){
list = list + “<li><a href=\”#\”>” + entryValue + “</a></li>”;
}
else if(curLevel<countLevel){
var difLevel = countLevel – curLevel ;
var closure = “”
for (var i=0; i<(difLevel); i++){
closure = closure + “</ul></li>”
}
list = list + closure
countLevel = curLevel;
}
else {
//
}
}
var tmpentry:NotesViewEntry = nav.getNext(entry);
entry.recycle();
entry = tmpentry;
}
//final closure, last entry could be response doc
var closure = “”
for (var i=0; i<(countLevel); i++){
closure = closure + “</ul></li>”
}
list = list + closure
//closure nav nav-list
list = list + “</ul>”;
return list;
}
else{
return “no documents found”;
}
}

C’est tout!

Result

Here is how the result looks like for desktop:

navbar

 

Reflections

Not bad for something “out of the box”!

I placed the “Site Index” at the beginning because it appears that the expanding of the nested lists continues to the right, even when it has reached the end of your screen 😕 There is the option to set the class for nested list to dropdown-submenu pull-left but I think it will be odd if you collapse one level open to the right and the next to the left.

Mobile users

Mobile users will find it difficult accessing the nested lists as the following image demonstrates:

mobilenavbar

 

Only the first 7 entries seem to become displayed, so when you open nested lists the items below just get pushed away. Luckily they return when you close the nested lists.

Alternative

The options for providing deep-level navigation seem to be a bit limited in Twitter Bootstrap. an alternative might be a collapsible tree menu for TB which you can find an example described here.

Job Wanted

Looking for a creative brain? Choose me!

job-wanted

Dropdown menu from Notes view in Twitter Bootstrap (with XPages)

Introduction

OK, so I was a bit unsatisfied with the navigation list in my previous post. The nav-list component seems not to be destined to contain too many category sub levels.

Dropdowns

Toggleable, contextual menu for displaying lists of links.

Pills

I am not sure how you would describe pills in Twitter Bootstrap but to me they look more like buttons you build a navigation with.

tb pills

Implementation

Again I used Mark Leusink’s Bootstrap4XPages demo site. As source for my documents I am re-using the same view from my previous post:

notesview clean

I found an example code for nesting lists on Fiddle and modified it a bit to my needs.

I display the results via a computed field that looks as followed:

<xp:text escape=”false” id=”computedField1″ value=”#{javascript:getList()}” ></xp:text>

The function getList() is very similar to the one in my previosu post and looks as followed:

function getList() {
var nav: NotesViewNavigator = database.getView(“$v-treeview-clean”).createViewNav();
var entry: NotesViewEntry = nav.getFirst();
if (entry != null) {
var countLevel: Integer = 0;
var curLevel: Integer;
var list = “<div class=\”dropdown\”>”;
list = list + “<ul class=\”nav nav-pills\”>”;
list = list + “<li class=\”dropdown active\” id=\”accountmenu\”>”;
list = list + “<a class=\”dropdown-toggle\” data-toggle=\”dropdown\” href=\”#\”>Home</a>”;
list = list + “<ul class=\”dropdown-menu\”>”
while (entry != null) {
var edoc: NotesDocument = entry.getDocument();
entryValue = entry.getColumnValues().elementAt(1).toString();
var col: NotesDocumentCollection = edoc.getResponses();
curLevel = entry.getColumnIndentLevel();
if (col.getCount() > 0) {
//prep for new entry with response(s)
var difLevel = countLevel – curLevel;
var closure = “”
for (var i = 0; i < (difLevel); i++) {
closure = closure + “</ul></li>”
}
list = list + closure;
list = list + “<li class=\”dropdown-submenu\”>”;
list = list + “<a href=\”#\” tabindex=\”-1\”>” + entryValue + “</a>”;
list = list + “<ul id=\”” + entryValue + “\” class=\”dropdown-menu\”>”;
//increase counter
countLevel = curLevel + 1;
} else {
if (curLevel == countLevel) {
list = list + “<li><a href=\”#\”>” + entryValue + “</a></li>”;
} else if (curLevel < countLevel) {
var difLevel = countLevel – curLevel;
var closure = “”
for (var i = 0; i < (difLevel); i++) {
closure = closure + “</ul></li>”
}
list = list + closure
countLevel = curLevel;
} else {
//
}
}
var tmpentry: NotesViewEntry = nav.getNext(entry);
entry.recycle();
entry = tmpentry;
}
//final closure, last entry could be response doc
var closure = “”
for (var i = 1; i < (countLevel); i++) {
closure = closure + “</ul></li>”
}
list = list + closure
//closure nav nav-list
list = list + “</ul></li></ul></div>”;
//closure well sidebar-nav

return list;
} else {
return “no documents found”;
}
}

Here is what the result looks like:

dropdowndemo

Alternative

The example above looks nice but I am not sure what the purpose of nav-pills are? (don’t ask a Dutch the pills Q)  I guess users would expect site navigation to be in a navigational bar, so expect another post on the nav-bar component.

Job Wanted

Looking for a creative brain? Choose me!

job-wanted

Navigation list from Notes view in Twitter Bootstrap (with XPages)

Introduction

In my previous post I demonstrated how you create breadcrumbs from Notes view in Twitter Bootstrap. A typical Notes application contains an outline (I assume you don’t the Navigator design element anymore). In this post I demonstrate how to create something similar in Twitter Bootstrap, a Nav List.

navlist example

Nav Lists

A Nav list is a simple and easy way to build groups of nav links with optional headers. They’re best used in sidebars like the Finder in OS X. You can nest nav-lists and then they come quit close to categorized or hierarchical structured documents (e.g. parent & response).

Implementation

As  a start database I used Mark Leusink’s Bootstrap4Xpages demo site which you can download here.

As source I am also using a similar view as I used in my previous post. Here is how it looks like:

notesview clean

I found an example code for nesting lists on Fiddle. Note that you can extend nav-list with mouse over behavior but I fear for mobile phones and tablets this will be a crime.

I display the results via a computed field that looks as followed:

<xp:text escape=”false” id=”computedField2″ value=”#{javascript:getNavList()}”></xp:text>

The function getNavList() looks as followed:

function getNavList(){
var nav:NotesViewNavigator = database.getView(“$v-treeview-clean”).createViewNav();
var entry:NotesViewEntry = nav.getFirst();
if (entry !=null){
var countLevel:Integer = 0;
var curLevel:Integer;
var list = “<div class=\”well sidebar-nav\”><ul class=\”nav nav-list\”>”;
while (entry !=null){
var edoc:NotesDocument = entry.getDocument();
entryValue = entry.getColumnValues().elementAt(1).toString();
var col:NotesDocumentCollection = edoc.getResponses();
curLevel = entry.getColumnIndentLevel();
if (col.getCount()>0){
//prep for new entry with response(s)
var difLevel = countLevel – curLevel ;
var closure = “”
for (var i=0; i<(difLevel); i++){
closure = closure + “</ul></li>”
}
list = list + closure;
list = list + “<li>”;
list = list + “<a href=\”#\” data-toggle=\”collapse\” data-target=\”#” + entryValue + “\”>” + entryValue + “</a>”;
list = list + “<ul id=\”” + entryValue + “\” class=\”collapse in\”>”;
//increase counter
countLevel = curLevel + 1;
}
else{
if(curLevel==countLevel){
list = list + “<li><a href=\”#\”>” + entryValue + “</a></li>”;
}
else if(curLevel<countLevel){
var difLevel = countLevel – curLevel ;
var closure = “”
for (var i=0; i<(difLevel); i++){
closure = closure + “</ul></li>”
}
list = list + closure
countLevel = curLevel;
}
else {
//
}
}
var tmpentry:NotesViewEntry = nav.getNext(entry);
entry.recycle();
entry = tmpentry;
}
//final closure, last entry could be response doc
var closure = “”
for (var i=1; i<(countLevel); i++){
closure = closure + “</ul></li>”
}
list = list + closure
//closure nav nav-list
list = list + “</ul>”;
//closure well sidebar-nav
list = list + “</div>”;
return list;
}
else{
return “no documents found”;
}
}

In order to make it look nice embed the computed field in div element and give it a span class.

Below you see the final result:

result nav list

 

 

You notice directly an obvious problem: the long title references which are typical for Notes documents.

Alternative

Twitter Bootstrap offer other alternatives which might be more suitable for transforming Notes views. And example is the dropdown. This you can extend with the dropdown JavaScript plugin.

Point for improvement

A class I did not use for the LI element is nav-header. This gives a nice outstanding header, which are great for categorized views.

A class I did not use for the LI element is active which highlights a list item. When using the nav-list on documents opened via an XPage I guess it is best practice to add the class name when the document is ready on the client.

The HREF attribute is not computed but this can be extracted from the viewentry.

Performance. The view above contains very little documents. I still have to test it with larger databases. Some examples where I want to use the nav-list contain more than 15000 documents in the view. I wonder how much I can use sessionScope or place the HTML in a profile document. Any suggestions here are welcome.

Tools used

Beside DDE is used heavily NotePad++ to check the generated HTML.

Job Wanted

Looking for a creative brain? Choose me!

job-wanted

Breadcrumbs from Notes view in Twitter Bootstrap (with XPages)

Long long long time ago I wrote how to create breadcrumbs in Notes and on the Web with help of JQuery. In case you are interested using Twitter Bootstrap with XPages chances are you want to provide similar navigational options for your beloved users.

The code below demonstrates how to achieve this.

<xp:this.data>
<xp:dominoDocument var=”currentDocument” formName=”Document”></xp:dominoDocument>
</xp:this.data>

<xp:text escape=”false” id=”computedField1″
value=”#{javascript:getBreadCrumb()}”>
</xp:text>

and the SSJS function looks like:

function getBreadCrumb(){
var nav:NotesViewNavigator = database.getView(“$v-treeview”).createViewNav();
var doc:NotesDocument = currentDocument.getDocument();
if (nav.gotoEntry(doc)) {
var entry:NotesViewEntry = nav.getCurrent();
var list = “”;
list = “<li>” + entry.getColumnValues().elementAt(1).toString() + “</li>” + list;
while (nav.gotoParent(entry)){
entry = nav.getCurrent();
list = “<li>” + entry.getColumnValues().elementAt(1).toString() + “<span class=\”divider\”>/</span></li>” + list;
}
return “<ul class=\”breadcrumb\”>” + list + “</ul>”;
}
return “no docs found”
}

The Notes view $v-treeview has already href references in the column where I collect the information from:

notes view

Don’t forget to set the Display options for Web for the form. I select the form that contains the computed text that calls the getBreadCrumb() function.

Here is how the view looks like in the browser:

Notes view for web

And here the result when I open a document:

breadcrumb tb

Please leave a note when you have suggestions for improvement OR if you have code to put view info into a nav list to create a collapsible menu.

Job Wanted

Looking for a creative brain? Choose me!

job-wanted