Categories: Servoy Mastery

The Demise of the TreeView

This is a Servoy tutorial on using an alternative to the TreeView control in Servoy. This Servoy tutorial picks up where we left off in “Using CSS Components with Callbacks“. You should study that Servoy tutorial first, as this one will be a little more advanced.

Photo Credit: Gwen Vanhee via Compfight.

Still with me? Okay, let’s kick it up a notch! You ever seen one of these? Well, as far as tree views go, this one looks pretty nice (if I do say so myself). The problem with tree views is, that users hate them. I have been involved in numerous large-scale commercial projects, and I can tell you that users despise the tree. In most cases, they find it hard to navigate and locate individual programs among all the nodes, expanding and collapsing nodes requires some precise mouse-control to click the little “+” or “-“, and they probably have just been over-used. Look, I don’t really know what the  problem is, I just know that the users groan when they see another tree being used for navigation.

So what is a developer to do? Do we have viable alternatives to the tree? Certainly we can’t use buttons to control all these programs; that would be a total mess. Even a menu would be hard to use because of all the parent groups used in this massive app. The bottom-line is we used the tree because its the best Servoy control to handle this much information in a compact space. Well, let’s think…who else is having problems like this, and solving them with elegant solutions. Ah, of course, the web developers! Why don’t we grab one of their components and hook it up to Servoy and see how it does? Absolutely! Let’s build this:

This is a JQuery/CSS accordion menu control, and it’s pretty sweet! You can stylize the whole thing, changing the background color, highlight color, selected color, the font, font color, font size, everything; its all CSS. You click anywhere on a line and with a beautiful animated effect the group collapses or expands, hiding or revealing child nodes. Icons can be assigned to every row and the hover highlight color (light blue shown here) follows your mouse as you move over the nodes. It even has a collapse all / expand all feature that I put at the bottom. Well this rocks, it’s running in Servoy, and I’m going to show you how to do it!

Were going to start with three variables that we are going to add to scopes.api that I create in my modBase. We will talk about encapsulation some other time, but basically, I use scopes to organize my information into neat namespaces. My modBase is accessible to all my other modules in a large application, and is typically where all my relations, media, and scopes (like scopes.api, scopes.events, scopes.utils, etc.)  are maintained.

var html_accordian = '';
var html_collapseAll = '';
var html_eventCallBack = '';

In scopes.api, I also include a start-up script that is run when my application launches. It’s purpose is to prepare the application by restoring user preferences, initializing things, etc. In this case, I am going to load the css style sheet for the control, the font-awesome style sheet (icons), and the jQuery.js file for the accordion control. I use the WebClientUtils plugin to do load my css and js files at start-up,  which allows me to use the control on any form, and not have to worry about initializing them. Load it once and use them anywhere.

The other thing I will do in the start-up function is to build my “html_eventCallBack” handler’s HTML. This HTML includes the WebClientUtils plugin to capture a “fireEvent” callback and call my main “scopes.events.eventHandler”, passing it some parameters. What this allows me to do is place a non-editable HTML area on any form, use “scopes.api.html_eventCallBack” as the dataprovider, and I can capture any callback event from any CSS control on that form. Effectively, I am using the same event handler on all my forms to capture all my callbacks from all my controls. The two parameters it expects are the “eventName” ,which will be unique for each event fired from a control, and an “eventMsg” type, like “Nav”, which tells the event handler what type of event is being received, so it can be properly routed from one central location.

plugins.WebClientUtils.addCssReference('/assets/navgoco/jquery.navgoco.css');
plugins.WebClientUtils.addCssReference('/assets/font-awesome/css/font-awesome.min.css');
plugins.WebClientUtils.addJsReference('/assets/navgoco/jquery.navgoco.js');

scopes.api.html_eventCallBack = 
 '<html>'
 +'<head> '
 +'<script type="text/javascript" charset="utf-8">'
 +'function fireEvent(eventName, eventMsg){'
 +  plugins.WebClientUtils.generateCallbackScript(scopes.events.eventHandler, ["eventName", "eventMsg"])
 +'}' 
 +'</script>'  
 +'</head> '
 +'<body> '
 +'</body> '
 +'</html>';

In my modBase scopes.events, I create my central event handler method. This is the method that will receive all the events fired from controls in use on forms, and route the requests to the appropriate functions by “eventMsg” type. In this example, we are only going to handle “Nav” types, and I will use a single “notImplementedYet” function for all events (just to keep it simple).

/**
* @param {String} eventName
* @param {String} type
*/
function eventHandler(eventName, type) {

 if(type === "Nav"){
  scopes.events[scopes.enums.NAV[eventName]](eventName);
 }else{
  application.output("Event Fired: " + eventName + " - " + type );
 }
}

/**
* @param {String} eventName
*/
function notImplementedYet(eventName){
 application.output("Have not yet implemented handler for: " + eventName);
}

The event handler will check modBase scopes.enums for the routing details to determine which method to call for a given event. All my events call the “notImplementedYet” function, but you would call specific public methods in a real-world scenario. Enums, in case you do not know, are nothing more than a list of constants consisting of name/value pairs. They are a great way to keep these organized, and can be used in relations. Here is my scopes.enums.NAV:

/**
* @enum  
 */
var NAV = {
 navHome : "notImplementedYet",
 navCustomers : "notImplementedYet",
  navCompanies : "notImplementedYet",
  navContacts : "notImplementedYet",
 navEstimating : "notImplementedYet",
// 
// Just more of the same; removed to keep it short
//
}

The next step is to work on the form where we are going to use the accordion control. Here I will place a non-editable HTML area on the form and use scopes.api.html_accordian as the dataprovider. I then create an onLoad() method and add the code that will load scopes.api.html_accordian on form load.

/**
 *  @private
 */
function loadHTML() {

 scopes.api.html_accordian = 
 '<html>'
 +'<head> '
    +'<script type="text/javascript">  '
    +'$(document).ready(function() {  '
    +' $("#accordian").navgoco({  '

//    +' accordion: true,  '
    +"      accordion: false,  "
    +"      openClass: 'open',  "
    +"      save: true,  "
    +"      cookie: {  "
    +"          name: 'navgoco',  "
    +"          expires: false,  "
    +"          path: '/'  "
 +"      },  "
 +"   slide: {  "
 +"          duration: 400,  "
 +"          easing: 'swing'  "
 +"      }, "

    +' onClickAfter: function(e, submenu) {  '
    +'     e.preventDefault();  '
    +"     $('#accordian').find('li').removeClass('active');  "
    +'         var li =  $(this).parent();  '
    +"         var lis = li.parents('li');  "
    +"         li.addClass('active');  "
    +"         lis.addClass('active');  "
 +'  }  '
 +' });  '
 +'}); '
 +'var EventMsg = "Nav"; '
 +'</script> '    
    +'</head> '
 +'<body> '
 +' <ul id="accordian" class="nav"> '
 +'     <li class="active"><a id="navHome" >'
 +'     <li><a id="navCustomers" >'
 +'         <ul> '
 +'             <li><a id="navCompanies" >'
 +'             <li><a id="navContacts" >'
 +'         </ul> '
 +'     </li> '
 +'     <li><a id="navEstimating" >'
//
// Just more of the same; removed to keep it short
//
 +' </ul> '
 +'</body> '
 +'</html>';
}

Now, I should mention a couple of things here.

  • You don’t have to wrap each HTML line in quotes like I do here, building up one big HTML string. You can instead create an XML object (wrap the whole thing in brackets) and use .toXMString() to convert it into a string.
  • If you have functions that contain invalid characters for XML, then you can wrap those using the CDATA tag. You would then have to remove the tags using something like:
var html = (
//
// all your xml goes here
//
).toXMLString().replace(']]>', '').replace('<![CDATA[','');
  • There is another way to interact with the browser environment without using the WebClientUtils plugin, which uses a hidden button in the body of the HTML to call a global method. I personally never use this approach and find the WebClientUtils plugin easier to work with.
  • I use a specific workflow for creating the HTML for the form.
    • The css control comes with sample HTML. I grab that and paste it into WebStorm. I will now edit the HTML adding my id for the event name, the onclick events, obviously my data, images, etc.
    • Once I am satisfied with the HTML, I then copy it from WebStorm and paste it into a simple Servoy HTML converter that will wrap it all in quotes for me. Here is the method I use on a form which has two text areas with form variables, _in and _out, as dataproviders.
function btnConvertHTML(event) {
 var lines = _in.split('\n'),
  i = 0,
  iLen = lines.length;

 _out = "";
 for(i = 0;i < iLen; i++){
  _out += "+ '" + lines[i] + "'" + "\n";
 }
}

The last thing that I add to the onLoad() method of my form is the code to load “scopes.api.html_collapseAll” with some jQuery/HTML code. This is the dataprovider for a non-editable HTML area that I place on the form immediately below the accordion’s HTML area.

scopes.api.html_collapseAll = 
 '<html>'
 +'<head> '
    +'<script type="text/javascript">  '
    +'$(document).ready(function() {  '
 +" $('#collapseAll').click(function(e) {  "
 +"     e.preventDefault();  "
 +"     $('#accordian').navgoco('toggle', false);  "
 +" });  "

 +" $('#expandAll').click(function(e) {  "
 +"     e.preventDefault();  "
 +"     $('#accordian').navgoco('toggle', true);  "
 +" });  "
 +'}); '
 +'</script> '
 +'</head> '

 +'<body> '
 +' <p class="external"> '
 +'    <center> <a href="#" id="collapseAll">Collapse All</a> | <a href="#" id="expandAll">Expand All</a> </center>'
 +' </p> '
 +'</body> '
 +'</html>';

I also place onto the form a non-editable HTML area that has “scopes.api.html_eventCallBack” as the dataprovider. Here I show it with a purple background so you can see it is there. In a real use case scenario, this HTML area is hidden, will capture the callbacks fired from the control, and route them to my event handler in scopes.events. Make sense?

So, that’s it for this Servoy tutorial. We now have an accordion control that replaces our tree, which will fire events when we interact with it. The events are handled by the event handler in scopes.events, and routed to the appropriate function using the enum mapping we setup. Everything is easy to maintain from centralized areas, and we have built it so we can easily capture events from other controls, on other forms, as well. We can expand the event handler to process any type of event, building out new enum maps as required.

I hope that this Servoy tutorial helps you further understand how to use CSS controls in Servoy, and how to organize your code so it will be easy to maintain and expand, as you add more and more controls, and need to handle more and more events.

That concludes this Servoy tutorial. I hope you enjoyed it!

Dotzlaw Consulting

Dotzlaw Consulting brings over 20 years of experience in professional software development, serving over 100 companies across the USA and Canada. Specializing in all facets of the project lifecycle—from feasibility analysis to deployment—we deliver cutting-edge solutions such as AI-powered workflows, legacy system modernization, and scalable applications. Our expertise in Servoy development and advanced frameworks allows us to modernize fixed-positioning solutions into responsive platforms like ng Titanium with Bootstrap and core.less styling. With a passion for knowledge-sharing, our team has authored numerous tutorials on topics like object-oriented programming, AI agent development, and workflow automation, empowering businesses to achieve scalable, future-ready success.

View Comments

  • Hi Gary,

    Great tutorials. Could you point me to where you downloaded the icons for the tree in this section.

Recent Posts

Optimizing Code Performance

This is a Servoy tutorial on how to optimize code performance. A while back, I had…

12 years ago

Servoy Tutorial: Using an Object as a Cache

This is an object-oriented Servoy tutorial on how to use an object as a cache in…

12 years ago

Function Memoization

This is an object-oriented Servoy tutorial on how to use function memoization with Servoy. Function memoization…

12 years ago

Object-Oriented Programming

This is an object-oriented Servoy tutorial on how to use object-oriented programming in Servoy. Javascript’s core…

12 years ago

Inheritance Patterns

This is an object-oriented Servoy tutorial on how to use inheritance patterns in Servoy. I use…

12 years ago

Prototypal Inheritance

This is an object-oriented Servoy tutorial on how to use prototypal inheritance in Servoy. When…

12 years ago