22 July 2013

APEX: Tree with Checkboxes

The APEX Tree component is based on jsTree which is a jQuery Tree Plugin. However not all functionality is implemented in APEX. Knowing the component on which it is based, we can find the documentation and enhance the Tree in APEX. One of these enhancements is to have checkboxes in the APEX Tree component. In this blogpost I will show you how to change the default folders to checkboxes. I will assume that you already have an APEX Tree on one of your pages, and that this one needs to be ammended to have checkboxes instead of folders. The first thing that you want to do to make the APEX Tree easier to locate in the javascript code is to add a static ID to the Tree Region.
Navigate to the Tree Region and entry a name as the static ID, I choose "catalog-tree".
Next is to add some javascript code to the page which will modify the tree folders.
At the highest level on your page, right click and choose "Edit". Add the following code to the section labelled "Execute when Page Loads"
regTree = apex.jQuery("#catalog-tree").find("div.tree");
regTree.tree({
 ui : {
  theme_name : "checkbox"
 },
 callback : {
  onchange : function(NODE, TREE_OBJ) {
   if (TREE_OBJ.settings.ui.theme_name == "checkbox") {
    var $this = $(NODE).is("li") ? $(NODE) : $(NODE).parent();
    if ($this.children("a.unchecked").size() == 0) {
     TREE_OBJ.container.find("a").addClass("unchecked");
    }
    $this.children("a").removeClass("clicked");
    if ($this.children("a").hasClass("checked")) {
     $this.find("li").andSelf().children("a").removeClass("checked").removeClass("undetermined").addClass("unchecked");
     var state = 0;
    } else {
     $this.find("li").andSelf().children("a").removeClass("unchecked").removeClass("undetermined").addClass("checked");
     var state = 1;
    }
                $this.parents("li").each(function() {
     if (state == 1) {
      if ($(this).find("a.unchecked, a.undetermined").size() - 1 > 0) {
       $(this).parents("li").andSelf().children("a").removeClass("unchecked").removeClass("checked").addClass("undetermined");
       return false;
      } else
       $(this).children("a").removeClass("unchecked").removeClass("undetermined").addClass("checked");
     } else {
      if ($(this).find("a.checked, a.undetermined").size() - 1 > 0) {
       $(this).parents("li").andSelf().children("a").removeClass("unchecked").removeClass("checked").addClass("undetermined");
       return false;
      } else
       $(this).children("a").removeClass("checked").removeClass("undetermined").addClass("unchecked");
     }
    });
   }
  }
       ,onopen : function(NODE, TREE_OBJ) {
           $(NODE).removeClass("open").addClass("closed");
        }
       ,onclose : function(NODE, TREE_OBJ) {
           $(NODE).removeClass("closed").addClass("open");
        }
 }
});
Line 1: This will find the Tree object that we want to manipulate. Note that the jQuery selector that is used references the static ID that we assigned in the earlier step.
Lines 2 through 5:
This will change the folder-icons to checkboxes... yes, it's that easy.
Lines 6 through 37:
When you check a checkbox at a high level, all the lower levels will also be checked. When you check a node at a lower level, the level above will either get a checkmark or a square depending whether all lower levels are checked or not.
In order to equip the checkboxes with this functionality all these lines of javascript are necessary,... yes, it's that hard. :)
Lines 38 through 43:
The last two events "onopen" and "onclose" might seem strange, and they are. It seems that the little triangles used to open and close the nodes fire the opposite event. Thanks to Christian Rokitta for discovering this oddity and providing a solution for it.


When you include the "Contract All" and "Expand All" buttons on your page, you will notice that they won't work anymore.
Change the "Expand All" button URL target to
javascript:$("#catalog-tree ul li:not(.leaf)").addClass('open').removeClass('closed');
and change the "Contract All" button URL target to
javascript:$("#catalog-tree ul li:not(.leaf)").addClass('closed').removeClass('open');

To see a working example, check out my demo
In the next blogpost I will show you how to startup the page with the pre-selected values from the database.

12 comments:

  1. Alex,

    I just came accross this post regarding checkboxes in trees. I was able to incorporate it into my application without any problems. Now I would like to have the checking or unchecking of a node update a row in the database. Can you suggest how I might be able to implement this?

    Any help would be appreciated.

    Tom

    ReplyDelete
    Replies
    1. Hi Tom,

      I havent't forgotten about this question. I started writing a blogpost on it - just haven't found the time to finish it. it's coming... soon...

      Alex

      Delete
    2. Thanks Alex I'm looking forward to it!

      Tom

      Delete
  2. Alex,
    Thanks for this post. Exactly what I was looking for.
    --Jeff

    ReplyDelete
  3. Excellent Alex. The tree menu may be changed in this way?: When i press a parent menu, automatically open the submenus, without pressing the left triangle??

    ReplyDelete
    Replies
    1. You mean differently than the "Collapse All" and "Expand All"?

      Delete
  4. Alex, thank you very much for this post!
    Would you please help me with my question? Is there any way to use jstree search in APEX tree?
    Like this: regtree.jstree("search","target")?
    .. as APEX tree is based on jstree, it seems possible to acess some methods

    Helene

    ReplyDelete
    Replies
    1. Hi Helene,
      hmm, that sounds like a nice challenge to figure out.... From what I see in the demo's with the search functionality it mainly works on the tree after it has been loaded. I mean the data needs to be in the tree before the search filter is applied.
      If there is a lot of data to retrieve this means that the loading time would take a considerable amount of time, especially if the filter would only show 2 or 3 results.
      Maybe it is a better idea to filter first before retrieving the data from the database.
      Still it is an interesting challenge which I will add to my "things to figure out" - my problem currently is time - or lack thereof. I still need to finish the third blogpost on APEX Trees...
      Alex

      Delete
  5. It's very nice to get your reply so soon!
    Thank you for advice!
    I wish to highlight searched nodes without re-selecting and page submit.

    However. I found the solution here:
    http://tpetrus.blogspot.ru/search?q=jstree+search

    I just could not get a tree reference:
    $.tree.reference('tree3138412706208499').search("1");

    I'm not good at jquery :(

    And I'm still looking forward your future posts!

    Helene

    ReplyDelete
  6. Thank you!!!! This is awesome!

    ReplyDelete
  7. Thank you !! very helpful.
    But how to start an Action with selected tree lines ?
    Regards
    Frank

    ReplyDelete
  8. In two follow up blogposts I describe how you can save the checked values from the tree to the database and how you can use the values from the database to check the appropriate boxes in the tree.
    You can find these posts here:
    Save to the database
    Check the boxes with database values

    ReplyDelete