collapsible menu with persistent state

Introduction

For a project I needed to have collapsible menus where the state is persistent, in other words after a refresh or a reload it should now if the menu was opened or closed.

The content of the menu is dynamic and rather complicated containing sub levels with multiple actions in them. Each level item would trigger to rebuild the presented document collection via Java (perhaps more common known as a faceted search).

dijitTitlePane

Since we are using the Extension Library I took a look at the controls but I could not find one that fulfills my requirements (accordion, dojo accordion pane, dojo tab pane, outline). To prevent to embed “another” Jquery plugin I looked across the Dojo site and found the titlePane dijit.

A TitlePane is a pane that can be opened or collapsed, with a title on top. The visibility of the pane’s contents is toggled by activating an arrow “button” on the title bar via the mouse or keyboard. It extends ContentPane but since it isn’t used inside other layout widgets it’s not in the dijit.layout module.

To be honest I am not a Dojo warrior and mostly are satisfied with jQuery plugins but this sounded like something that I could use. For now I only have to menu sections to collapse; a KPI section and a Filter section. Each section has there level 1 and level 2 items.

Since the application works as much as possible with partial refresh and each filtering affects the content of these 2 sections I also needed to save the state of the panes (open or closed). This sounded like a combination of client (open / close the pane) and server side (save the state in a scope variable) actions. Hmmm. From day 1 I have found this mixture of CSJS and SSJS a problem child which you tend to forget/mess up. I decided to implement the approach Oliver Busse promotes in the following post “Writing and reading scoped variables via client side Javascript“. This approach probably makes the code less cluttered with mixture of languages.

I also noticed that the titlePane dijit is not error-proof implemented in the Bootstrap4XPages project in the Extension Library but I found a fix described in the post “fix for down caret icon in dijit.TitlePane when using Bootstrap from ExtLib“. I got a response from Brian Gleeson (IBM): A fix for the dijit.TitlePane issue has been delivered, and should show up in the next Extension Library release. Great & thank you Brian!

So having conquered the circumstances described above what became the result and how did I implement it?

Result

Let’s start with the result:

menu01

Image 1: first section opened.

menu2

Image 2: both sections opened.

Implementation

Each of the KPI and Filter item (can) have sub levels with different presentation and functionality which I will not describe for simplicity reasons.

dijitTitlePane

The titlePane dijit is not so hard to implement. Add the dojo module to your resources:

<xp:this.resources>
<xp:dojoModule name=”dijit.TitlePane”></xp:dojoModule>
</xp:this.resources>

Next add a div to your XPage and set the dojoType and title property. The mat-card styleclass provides a material design presentation.

Attributes

Also define custom attributes for open and toggleble for the div element. These are properties of the titlepane dijit.

Event handler

The event handler checks the state of the pane and writes the state to a scoped variable when the title section of the pane is clicked. For a complete description how to implement Oliver Busse’s utility I would like to direct you to his site.

<xp:div
id=”titlePaneKPI”  dojoType=”dijit.TitlePane” title=”Sales KPI”  styleClass=”mat-card”>
<xp:this.attrs>
<xp:attr name=”open” value=”false”></xp:attr>
<xp:attr name=”toggleable” value=”true”></xp:attr>
</xp:this.attrs>
<xp:eventHandler event=”onclick” submit=”false”>
<xp:this.script><![CDATA[var KPI = dojo.dijit.byId(“#{id:titlePaneKPI}”);
if (KPI.open){
setScopeValue(“view”, “MenuKPI”, “1”);
}
else{
setScopeValue(“view”, “MenuKPI”, “0”);
}]]></xp:this.script>
</xp:eventHandler>

</xp:div><!– /dijit.TitlePane –>

Odd behaviour

I also noticed a strange behaviour of the titlePane. At start I had multiple controls on my xpage to see their differences and capabilities. When I removed the Dojo Accordion Pane the carets for the dijitPane behaved odd:

menuodd

Note the plus (+) and minus (-) signs after the caret 😕

So in front of the TitlePane dijit divs I placed a Dojo Accordion Pane and set the display style property to none (via a styleclass):

<xe:djAccordionPane id=”djAccordionPane1″

styleClass=”paneAccordian”>
</xe:djAccordionPane>

titlePane content

The content of the titlepane can be anything, in my case via a repeat controls a list group with list-group-items is generated.

  • Lead Flow

    <!– /col-xs-12 col-sm-12 col-md-12 col-lg-12 –>
    </div><!– /row –>

    Qualified Opportunities

    <!–/col-xs-12 col-sm-12 col-md-12 col-lg-12 –>
    </div><!– /row –>

    </div><!–/list-group–>

  • Personally I find it a good practice to comment the closure of elements, especially when using Bootstrap nowadays.

    Event handler when loading the page/custom control

    The last thing to include is an event handler that opens or closes the titlePane dijits depending on their last states that are stored in scoped variables.

    I placed the event handler on the custom control where the titlepanes reside in so it gets initiated every time a partial refresh takes place for the custom control.

    <xp:eventHandler event=”onClientLoad” submit=”false”>
    <xp:this.script><![CDATA[function getMenuState(){
    var KPI = dojo.byId(“#{id:titlePaneKPI}”);
    var Filter = dojo.byId(“#{id:titlePaneFilter}”);
    var KPI_status = getScopeValue(“view”, “MenuKPI”);
    var Filter_status = getScopeValue(“view”, “MenuFilter”);
    if(KPI_status==”1″){
    dijit.byId(“#{id:titlePaneKPI}”).set(‘open’,true);
    }
    if(Filter_status==”1″){
    dijit.byId(“#{id:titlePaneFilter}”).set(‘open’,true);
    }
    }
    getMenuState();]]></xp:this.script>
    </xp:eventHandler>

    I was not able to store the function in a CSJS library because it kept telling me then that dojo could not find any note to act upon, but this is probably due to my lack of guru knowledge of Dojo.

    Wrap up

    That is about it! With little programming and a couple of hordes I managed to apply a menu with a consistent state when performing partial refreshes!

    I am looking forward to hear what alternative solutions you have produced, perhaps they can serve me in the future. Happy coding!

     

    fix for down caret icon in dijit.TitlePane when using Bootstrap from ExtLib

    For unknown reason the dijit.TitlePane is not part of the Dojo controls in the ExtLib. You can implement your own version and Mark Roden posted a nice description how to achieve this. HOWEVER in case you are using the Bootstrap theme in the Extension Library you will notice when opening a pane the down caret icon can not be displayed.

    Here is the build up for 2 TitlePanes dijits:

    <xp:div id=”titlePaneGemCars” dojoType=”dijit.TitlePane” title=”German Cars”>
    Audi
    BMW
    Mercedes
    <xp:this.attrs>
    <xp:attr name=”open” value=”false”></xp:attr>
    <xp:attr name=”toggleable” value=”true”></xp:attr>
    </xp:this.attrs>
    </xp:div>
    <xp:div id=”titlePaneJapBikes” dojoType=”dijit.TitlePane”
    title=”Japanese Bikes”>
    <xp:this.attrs>
    <xp:attr name=”open” value=”false”></xp:attr>
    <xp:attr name=”toggleable” value=”true”></xp:attr>
    </xp:this.attrs>
    Honda
    Suzuki
    Yamaha
    </xp:div>

    Here is how it looks like in the browser:

    titlepane_error

    In case you choose another theme e.g. oneUI (*cough*) the panes are displayed as followed:

    titlepane_oneUI

    Note the display of carets is different (no carets for closed panes).

    So how are we going to fix this? Via your friend CSS! Include the following CSS snippet at your page/stylesheet/theme:

    .xsp.dbootstrap .dijitTitlePane .dijitArrowNode::before {
    color: #428bca;
    font-family: “Glyphicons Halflings”;
    content: “\e114”;
    font-size: 12px;
    padding-left: 10px;
    position: relative;
    top: -1px;
    }

    The definition is similar for a closed dijit but the content is set here to specific Glyphicon (the chevron down icon). The content is set to a hexidecimal value for an icon? On the glyphicons site you can search for halflings and it will tell for the chevron-down icon the value is UTF+e114.

    As a result your opened titlepane wil have the correct icon displayed:

    titlepane_fixed

    Happy coding!

    Ps I assume there is someone smart here to register this break as a request at IBM?

    Landing page … Google maps

    Some time ago I asked if anyone has recommendations for a geotargeting service.

    Since the service from Google is simple and free I implemented this one for a demo for a customer. Below you can find the code. For now I display a dojo dialog when there are visitors from Canada, United States and … Sweden (because I am located there). You could of course do a redirect automatically or store the user selection in a cookie for next time. You get the point.

    <?xml version=”1.0″ encoding=”UTF-8″?>
    <xp:view xmlns:xp=”http://www.ibm.com/xsp/core&#8221; dojoTheme=”true”
    dojoParseOnLoad=”true”>
    <xp:this.resources>
    <xp:styleSheet href=”../resources/dojo.css”></xp:styleSheet>
    <xp:styleSheet href=”../dijit/themes/tundra/tundra.css”></xp:styleSheet>
    </xp:this.resources>
    <script type=”text/javascript”
    src=”http://www.google.com/jsapi?key=ACME_GOOGLE_MAPS_KEY”&gt;
    </script>
    <script type=”text/javascript”
    src=”https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js”&gt;
    </script>
    <script>
    dojo.require(“dojo.parser”);
    dojo.require(“dijit.Dialog”);
    dojo.require(“dijit.form.TextBox”);
    dojo.addOnLoad(checkPref);
    function checkPref(){
    var location_redirect = getCookie(“site_pref”);
    if (location_redirect == “”){
    showDialog();
    }
    else{
    top.location.href = window.location = location_redirect;
    }
    }
    function showDialog(){
    var cl = google.loader.ClientLocation;
    var ccode = cl.address.country_code;
    if ((ccode == “CA”)||(ccode == “US”)||(ccode == “SE”)){
    //document.getElementById(‘your_location’).innerHTML = “Your country code = ” + ccode;
    var dlg = dijit.byId(‘dialog1’);
    dlg.show();
    }
    }
    function setCookie(c_name,value,expiredays){
    var exdate=new Date();
    exdate.setDate(exdate.getDate()+expiredays);
    document.cookie=c_name+ “=” +escape(value)+
    ((expiredays==null) ? “” : “;expires=”+exdate.toUTCString());
    }
    function getCookie(c_name){
    if (document.cookie.length>0){
    c_start=document.cookie.indexOf(c_name + “=”);
    if (c_start!=-1){
    c_start=c_start + c_name.length+1;
    c_end=document.cookie.indexOf(“;”,c_start);
    if (c_end==-1) c_end=document.cookie.length;
    return unescape(document.cookie.substring(c_start,c_end));
    }
    }
    return “”;
    }
    function loadPreferences(){
    var location_redirect = getCookie(“site_pref”);
    top.location.href = location_redirect;
    }
    </script>
    <div dojoType=”dijit.Dialog” id=”dialog1″ title=”Select country”
    style=”display:none;width:600px;”>
    <h1>
    Welcome to
    <b>ACME</b>
    </h1>
    <div id=”your_location”></div>
    <table border=”0″ width=”100%”>
    <tr>
    <td colspan=”2″>Visit:</td>
    </tr>
    <tr>
    <td>
    <b>USA site</b> <a href=”http://www.acme.com/us&#8221; target=”_top”>(English)</a>
    <b>USA site 2</b> <a href=”javascript:setCookie(‘site_pref’,’http://www.acme.com/us&#8217;,’1000′);loadPreferences();” target=”_top”>(English)</a>
    </td>
    <td>
    <b>Global site</b> <a href=”http://www.acme.com/&#8221; target=”_top”>(English)</a>
    <b>Global site2</b> <a href=”javascript:setCookie(‘site_pref’,’http://www.acme.com/&#8217;,’1000′);loadPreferences();” target=”_top”>(English)</a>
    </td>
    </tr>
    </table>

    <div id=”divider”
    style=”border-top:dashed 1px grey;margin:10px 0 10px 0″>
    </div>

    <div style=”float:left;width:272px;”>
    <table width=”100%”>
    <tr>
    <td>
    <b>Americas</b>
    <br />
    Canada,
    <a href=”http://www.acme.com/us&#8221;
    target=”_top”>
    English
    </a>
    <br />
    Latin America regional site,
    <a href=”http://www.acme.com/cl&#8221;
    target=”_top”>
    Español
    </a>
    </td>
    </tr>
    <tr>
    <td>
    <b>Asia Pacific</b>
    <br />
    Asia Pacific regional site,
    <a href=”http://www.acme.com/sg&#8221;
    target=”_top”>
    English
    </a>
    <br />
    Australia,
    <a href=”http://www.acme.com/au&#8221;
    target=”_top”>
    English
    </a>
    <br />
    中国(China),
    <a href=”http://www.acme.com/cn&#8221;
    target=”_top”>
    中文
    </a>
    <br />
    日本 (Japan),
    <a href=”http://www.acme.com/jp&#8221;
    target=”_top”>
    日本語
    </a>
    <br />
    남한 (South Korea),
    <a href=”http://www.acme.com/kr&#8221;
    target=”_top”>
    한국어
    </a>
    </td>
    </tr>
    </table>
    </div>

    <div>
    <table width=”300px;”>
    <tr>
    <td>
    <b>Europe</b>
    <br />
    CIS,
    <a href=”http://www.acme.com/ru&#8221;
    target=”_top”>
    Россия
    </a>
    <br />
    Suomi (Finland),
    <a href=”http://www.acme.com/fi&#8221;
    target=”_top”>
    Suomeksi
    </a>
    <br />
    Norge (Norway),
    <a href=”http://www.acme.com/no&#8221;
    target=”_top”>
    Norsk
    </a>
    <br />
    Polska (Poland),
    <a href=”http://www.acme.com/pl&#8221;
    target=”_top”>
    Polski
    </a>
    <br />
    Sverige (Sweden),
    <a href=”http://www.acme.com/se&#8221;
    target=”_top”>
    Svenska
    </a>
    <br />
    United Kingdom &amp; Ireland,
    <a href=”http://www.acme.com/uk&#8221;
    target=”_top”>
    English
    </a>
    </td>
    </tr>
    <tr>
    <td>
    <b>Middle East and Africa</b>
    <br />
    Africa regional site,
    <a href=”http://www.acme.com/za&#8221;
    target=”_top”>
    English
    </a>
    </td>
    </tr>
    </table>
    </div>
    </div>

    </xp:view>

    Here an example how it will look like:

    XPages development practices: developing a common Tree View Custom Controls

    In an earlier article I wrote about displaying information from a Notes View in a Dojo tree dijit.

    A scan via Google what others have been doing on this matter did not result in so much valuable information untill I found an article on IBM developerWorks … in chinese.

    If you translate the page via Yahoo! Babel Fish you get some really odd result. However, if you translate the page via Google Translate you get a really good translation in a format that I can understand.

    I wonder if IBM has ever released this article in English? I hope they do not find it a trouble if I make it available in a PDF format: developing a common Tree View Custom Controls

    Unfortunately I haven’t had the time to test the article in a demo database, but if you have, please let me know…

    Displaying a Domino view in the dojo dijit.tree

    Just before I will stamp some time in displaying a categorzied Domino View in a dojo dijit.tree I am wondering if anyone has done this before and is willing to share the code? Google brought me do far no fortune (allthough I was near).

    For now I have managed to display a flat view in the dijit.tree, for generating the proper JSON I use an agent that collects the data in a View. Calling directly a view via the ?ReadViewEntries&OutputFormat=JSON delivers an overload of information.

    As source I woud like to use a View with documents in a response hierarchy like I described before here.