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.

39 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
  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. As per your example:
      http://imgur.com/r1AIRxW
      My report should appear like this when i go to flat view from tree view
      Departments Employees CBOX

      ACCOUNTING CLARK *
      ACCOUNTING KING
      ACCOUNTING MILLER
      RESEARCH SMITH
      RESEARCH JONES
      RESEARCH SCOTT
      RESEARCH ADAMS
      RESEARCH FORD
      SALES ALLEN *
      SALES WARD *
      SALES MARTIN *
      SALES BLAKE *
      SALES TURNER *
      SALES JAMES *
      OPERATIONS --

      Delete
    4. 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
    5. 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