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.

59 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
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hello,
      I'm not very familiar with jquery.
      how do I get it that only the parents can be activated?
      thanks & regards
      Chris

      Delete
    3. Just to clarify: do you want to be able to select only the nodes which are considered parents, i.e. not select the lowest level in the tree? Or do you want to be able to select only the root element, e.g. only select KING in the EMP-hierarchy?

      Delete
    4. Hi Alex,
      thanks for your quick answer !
      I want to be able to select each check box itself.
      I want to decide which checkbox I select.
      regards
      Chris

      Delete
    5. Change the code "Execute when Page Loads" to the following:
      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();
      $this.children("a:first-child").toggleClass("checked");
      } }
      ,onopen : function(NODE, TREE_OBJ) {
      $(NODE).removeClass("open").addClass("closed");
      }
      ,onclose : function(NODE, TREE_OBJ) {
      $(NODE).removeClass("closed").addClass("open");
      }
      }
      });

      Delete
    6. I thank you very much Alex, it works fine.

      Delete
    7. Hi Alex, I Used the above script and It's working, Thank you.
      But here, when we select the child, it doesn't show any mark on parent checkbox. Is it possible to implement that in above code?
      Once, when we select some child, it should have some indication on parent so that it will be easy to know that some checkbox is selected in the Hierarchy without expanding.

      Regards,
      BOSE

      Delete
    8. @BOSE,
      The code in the blogpost it selfs does exactly that.. the code in response to "Anonymous" doesn't, because that's what he asked for.

      Delete
    9. Alex, I did work with both scripts and I understood its working. But Whether is it possible for code in "COMMENT" to mark(Highlight, not select) it's parent while retaining its functionality of selecting only desired checkbox.?

      Regards,
      BOSE

      Delete
    10. If you use the original code it will mark the parent-box, with the class "undetermined". If you want to process only the checked boxes, just don't select the ones with the "undetermined" class.
      But possibly I'm misunderstanding you completely?

      Delete
    11. Alex, My question is, Now when I use the code in "Comment" section, It will select the boxes only I want. That is perfect.
      But suppose when new user uses same the tree with preselect data, there is no marker on parent to tell new user that, there is a child leaf marked in the hierarchy. so is it possible to mark or highlight the parent when there is child selected in hierarchy.

      Regards,
      BOSE

      Delete
    12. Ah, I understand what you mean, but the functionality just doesn't make sense.. It might be better to explain to your users how a tree works (like the original code)

      Delete
  9. thanks "Alex Nuijten" very helpful

    ReplyDelete
  10. thanks Alex ,its a very good post for beginners ,I just wanted one more thing ,is it possible somehow just to keep leaf node enable for check uncheck and disable all other nodes ?

    ReplyDelete
  11. Dear,
    Thank you. But I have a problem, my page is RTL for example (Arabic language) and every things are correct also tree is RTL but when I added the checkboxes to tree. the pointer and the line vertical are left side of node text.How can I put the right side of the node?

    Regards,
    Saeed

    ReplyDelete
    Replies
    1. Saeed,
      A little bit of googling and I stumbled across a default RTL theme created by Taha (https://groups.google.com/forum/#!topic/jstree/fQTJvLHhBq8) You might want to check out the examples that are in the demo.zip file.

      Delete
    2. Dear,
      This sample is for jstree ver 1.0 so its different with jstree 0.9.9a2 in apex 4.2.2.

      Delete
    3. Good catch! In that case you need to include ver 1.0 in your application order for it to work.

      Delete
    4. I don't know how I can use jstree 1.0 instead of tree item in apex.Could you help me about that(with sample).
      Regards.

      Delete
    5. Dear Alex,
      I found some posts about that,But I don't have clue about how I can use checkboxes and RTL in jstree.
      I would be grateful if you make a sample about it.

      https://github.com/tompetrus/oracle-apex-ajax-tree
      http://tpetrus.blogspot.nl/2013_09_01_archive.html

      Regards,
      Saeed

      Delete
    6. I understand this can be a challenging, however my time is limited. Currently I'm very busy and can hardly find time to play with this. Of course you can hire me and I will figure out how to get this to work :)
      When time permits again, I will figure out how it works and will blog about it (I'll put it on my "to-do-blogs" list.

      Delete
  12. Great blog, but I have one problem that you may be able to suggest a solution.
    I have updated the EXPAND_ALL button, URL Taget
    to
    javascript:$("#catalog-tree ul li:not(.leaf)").addClass('open').removeClass('closed');

    The browser updates the page and quickly goes to page that has the URL of javascript:$("#catalog-tree ul li:not(.leaf)").addClass('open').removeClass('closed');
    and displays
    [object Object]
    as the browser content.

    Any suggestions to prevent this behaviour?

    ReplyDelete
    Replies
    1. What is the static ID of the Tree Region? In my case it is "catalog-tree". The code in the EXPAND_ALL button locates the html-element with the id "catalog-tree" (#catalog-tree).

      Delete
  13. Alex - thanks for your response.

    I have solved by problem by pasting your string into the 'Button Attributes' as

    onclick="$('#catalog-tree ul li:not(.leaf)').addClass('closed').removeClass('open');"

    Note that I had to ensure the ' and " quotations are consistent.

    And set the 'Action when Button Clicked' to the

    'Action Defined by Dynamic Action'

    I debugged this error by using Firefox/Firebug to inspect my Apex page and your demo page ( http://apex.oracle.com/pls/apex/f?p=47888:13:15989733398094:::::).
    So having a working example added significant assistance.

    Thaks again

    ReplyDelete
  14. Hey Alex,
    Thanks a lot...
    Can u help me in integrating these check-boxes with check-boxes I am having in flat view of hierarchy.
    I am having a radio button to display data both in tree and report format and now I want to have all those check-box selected in report which are selected in my hierarchical view.

    ReplyDelete
    Replies
    1. From your description I can't deduce what you want to implement on your page. Why don't you create an example on apex.oracle.com so I can look at that?

      Delete
    2. I am having check-box with tree view and leaves of it are represented in a report with check-boxes (APEX_ITEM.CHECKBOX). Now I want to integrate both of them. like from moving from one view to another same rows remain selected.

      Delete
    3. When the data is stored in the table, it shouldn't be too hard to show a "flat report" with the appropriate checkboxes checked? When you don't want to store the data, then you would have to resort to javascript to handle this... but then you would have to consider pagination of the report and the sort order... that might get tricky.
      Storing the data (there is another blogpost on how to do that: http://nuijten.blogspot.nl/2013/11/tree-with-checkboxes-save-data-js-array.html) is the easiest solution.

      Delete
    4. Thanks, will try to work this out.

      Delete
  15. Hey Alex,
    Thanks a lot...
    Can u help me in integrating these check-boxes with check-boxes I am having in flat view of hierarchy.
    I am having a radio button to display data both in tree and report format and now I want to have all those check-box selected in report which are selected in my hierarchical view.

    ReplyDelete
  16. Hi Alex,
    What should be the change if we want check-boxes in leaves only.

    ReplyDelete
  17. Hi Alex,

    Great post.

    I have a flat report based on a view that has some columns coming from its ancestor tables.
    Item Id, Item Name, Item Attr 1, Item Attr2, Item Parent Id, Item Parent Name, Item Grand Parent Id, Item Grand Parent Name, Item Great Grand Parent Id, Item Great Grand Parent Name, Item Great Great Grand Parent id, Item great Great Grand Parent Name
    _______________________________________________________________________________________________________________________________________
    ...

    Users need to be able to filter on the name of the ancestor columns.
    Instead of having 4 multiple select list boxes (one for each parent name, grand parent name, great grand parent name, great great grand parent name), I would rather use a checked box tree.

    - Great Great Grand Parent 1
    | |
    | |__ - Great Grand Parent 1.1
    | | |
    | | |__ - Grand Parent 1.1.1
    | | | |
    | | | |__ - Parent 1.1.1.1
    | | | |
    | | | |__ - Parent 1.1.1.2
    | | |
    | | |__ - Grand Parent 1.1.2
    | | | |__ - Parent 1.1.2.1
    | | | |
    | | | |__ - Parent 1.1.2.2
    | | | |
    | | | |__ - Parent 1.1.2.3
    | | |
    | | |__ + Grand Parent 1.1.3
    ...

    When user press the 'Retrieve Data' button the screen need to dynamic build the SQL based on the list of checked tree node.
    In fact only selecting the checked leaf nodes (which are at parent level) and concatenating their ids should be enough.
    WHERE item.parent_id IN ( :PXX_CHECKED_PARENT_IDS )

    Any idea how in javascript I can select only the leaf nodes of the tree and concatenate their id into an ORACLE APEX item.

    ReplyDelete
  18. Hello Alex, Its very informative blog regarding checkboxes with APEX tree.
    I have used your above code to generate the same, but In my APEX page checkboxes are appearing but "Arrows" are missing.
    Please suggest the solution. I am using the ORACLE APEX vesrion 4.2.1

    ReplyDelete
    Replies
    1. The "arrows" are the ones that open and close the details, I assume. Obvious question: are there details, i.e. is there a hierarchy in the query; do you have different levels?

      Delete
    2. Yes, The "arrows" are the ones that open and close the details. I also have different levels in hierarchy.

      Delete
    3. Strange,... are you using a custom template?
      Note that changing the tree won't work in APEX5 unless you keep using the legacy tree, instead of the new APEX tree.

      Delete
    4. Alex,It was templated based issue. Solved it. Thank you for the help.

      Delete
  19. Hi Alex,
    This is an excellent article!
    I followed the steps you mentioned above and was able to create a tree with checkboxes without difficulty.
    I even got the Expand and Collapse buttons to work.
    There is only one thing that didnt work for me - the tooltip.
    Although I have specified the database column name as tooltip in the tree query it doesnt show up when I mouse over a tree node.

    Would appreciate your inputs on this.
    Thank a lot!
    Regards,
    Priya

    ReplyDelete
    Replies
    1. Hi Priya,
      The tooltip should work, the code just extends the tree-functionality. With APEX 5 the tree has been rewritten (jsTree is still there, but will probably de deprecated). This code doesn't work with that new APEX-tree.

      Delete
  20. Hi Alex, this is great, but I seem to be having issues with a small bug that I hope you can fix. When selecting/deselecting siblings in the last branch of my tree, the correct checked/undetermined classes don't seem to be applied correctly to the parents. as seen here: https://www.dropbox.com/s/m4g7vp6wrxy269h/Capture.PNG?dl=0

    Are you able to offer any clues?

    Thanks!

    ReplyDelete
  21. This comment has been removed by the author.

    ReplyDelete
  22. Hi Alex, how I can change the icon (or theme) for the root node?. It is posible to change other nodes or disable or enable? Thanks. =)

    ReplyDelete
  23. Hi Alex,
    I use the Jslegacy Tree. I want the parent node check box to be hidden. ie. the parent has child level, it should only have the triangle to expand the parent and i do not want a checkbox.
    Please help me to achieve this.

    ReplyDelete
  24. Hi Alex,
    I use the Jslegacy Tree. I want the parent node check box to be hidden. ie. the parent has child level, it should only have the triangle to expand the parent and i do not want a checkbox.
    Please help me to achieve this.

    ReplyDelete
  25. Hi Alex,

    Please ignore my previous post, i have achieved the same. thank you.

    ReplyDelete