wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

View driven accordeon


The Extension Library features a Dojo Accordion control. In the sample application the panels and their content are created static using xe:basicContainerNode and xe:basicLeafNode. A customer asked: " Can I populate the accordion using view entries?"
Working my way backwards I first designed the format for the SSJS I need for the menu and then how to generate this format from an categorized view. The format that suited this need looks like this:

[
   { name : "Level 1"; items : ["red 1":"blue 1":"green 1"]},
   { name : "Level 2"; items : ["red 2":"blue 2"]},
   { name : "Level 3"; items : ["red 3":"green 3"]}
]

In a live application, the items rather would be objects with a label and an action ( items : [{"label": "red 2", "action" : "paintred"}:{"label" : "blue 2", "action" : "playBlues" }]) or URL, but for the demo a string is sufficient. I created a accordeon with a xe:repeatTreeNode containing a xe:basicContainerNode as sole child element (which would repeat).
This child then contains one xe:repeatTreeNode that would again contain one xe:basicContainerNode as sole child (which again would repeat).
In theory that all looks brilliant, except the inner repeatTreeNode would not get access to the repeat variable from the parent basicContainerNode.
This has been accnowledged as a bug and is tracked as SPR # MKEE9SKENK
(I'm, inside IBM, outside the development team, the undisputed XPages SPR champion).
So a little hack was necessary to make that menu work.The result is this:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
 xmlns:xe="http://www.ibm.com/xsp/coreex">
 <xp:this.resources>
  <xp:script src="/MenuTools.jss" clientSide="false"></xp:script>
 </xp:this.resources>
 <xp:panel>
 <xp:text escape="true" id="computedField1"
  value="#{javascript:sessionScope.menuList}">
 </xp:text>
 <xp:button value="Clear cache" id="button1">
  <xp:eventHandler event="onclick" submit="true"
   refreshMode="complete">
   <xp:this.action><![CDATA[#{javascript:sessionScope.remove('menuList');}]]></xp:this.action>
  </xp:eventHandler></xp:button>
 </xp:panel>
 <xe:accordion id="accordion1" style="width: 400px; height:600px">
  <xe:this.treeNodes>
   <xe:repeatTreeNode var="tree" indexVar="index"
    value="#{javascript:menuTools.getMenu(database,'Menu');}">
    <xe:this.children>
     <xe:basicContainerNode>
      <xe:this.label><![CDATA[#{javascript:requestScope.currNode = tree; tree.name}]]></xe:this.label>
      <xe:this.children>
       <xe:repeatTreeNode var="subtree"
        indexVar="subindex"
        value="#{javascript:requestScope.currNode.items}">
        <xe:this.children>
         <xe:basicLeafNode
          label="Leaf #{subtree}">
         </xe:basicLeafNode>
        </xe:this.children>
       </xe:repeatTreeNode>
      </xe:this.children>
     </xe:basicContainerNode>
    </xe:this.children>
   </xe:repeatTreeNode>
  </xe:this.treeNodes>
 </xe:accordion>
</xp:view>

The panel above the accordeon is to manage the cache of my menu code.
Depending on most use cases the menu is quite static and it doesn't make sense to read and read and read it from the database.
The clear button belongs to an admin panel, so after a menu edit, the cache can be cleared there.
Also using the applicationScope will improve performance, since only the very first user of the application will actually read the database.
In my SSJS I read the whole view based on the view name. In your application you might hardcode the view and use the name to lookup a category of entries and render only the subentries. Whatever suits you.

var menuTools = {
  
 "getMenu" : function(nsf:NotesDatabase, menuName:string) {
  /*Checks cache first could be session or applicationScope*/
  if (!sessionScope.menuList) {
   sessionScope.put("menuList", new java.util.HashMap());
  }
  if (sessionScope.menuList.containsKey(menuName)) {
   return sessionScope.menuList.get(menuName);
  }

  /*Retrieves menu entries based on a view name*/ 
  var v:NotesView = nsf.getView(menuName);
  var vn:NotesViewNavigator = v.createViewNav();
  var ve = vn.getFirst();
  var result = [];
 
  while (ve != null) {
   var nextVe = vn.getNextSibling(ve);
   var lineResult = {};
   lineResult.name = ve.getColumnValues()[0];
   lineResult.items = [];  
   var child = vn.getChild(ve);
   while (child != null) {
    var nextChild = vn.getNextSibling(child);
    lineResult.items.push[child.getColumnValues(](1));
    child.recycle();
    child = nextChild;
   }
   result.push(lineResult);
   ve.recycle();
   ve = nextVe;
  }

  try {
   vn.recycle();
   v.recycle();
  } catch (e) {
   print(e);
  }
  // Save it to the cache
  sessionScope.menuList.put(menuName, result);
  return result;
 } 
}

As you can see, I use a naming convention that would allow me to swap the SSJS out for a managed bean. I strongly encourage you to follow this.
Enjoy - and as usual YMMV

Posted by on 08 January 2015 | Comments (0) | categories: XPages

Comments

  1. No comments yet, be the first to comment