Wednesday, May 29, 2013

Performing a case-insensitive search on a tree in apex

Someone mailed me recently about my previous posting about the apex tree, wondering how to implement a case-insensitive search. Indeed, the default search behaviour is case-sensitive. You might know how to do some toUppercase() magics, but how and where would you implement this?
I thought this question deserved some spotlight, so here we are. I do want to point out that my solution here is specific to the tree and the methods I used in my previous post. Other than that, it is still useful and can be used outside the context of the tree too.
Now, basically when you call the search function of jsTree, it will perform this search on the titles of the nodes in the tree with a jQuery pseudo-selector. By default this is the ":contains" selector, which will look for an occurence of a given string in the element. And this is not case insensitive.
Wait, pseudo-what? Don't fret, you've most likely encountered one of these beasts already. Some examples: ":first-child",":last-child",":hover",":visited",":enabled",... You can find some more info on them here (http://css-tricks.com/pseudo-class-selectors/) or just google them!
When you call the search on the tree, I have simply used search('somestring'). "search" however has another parameter!
search(needle [, compare_function])
Don't be deceived however by the name of the parameter. You do not actually pass on a function. The docs say this:

Optional argument. The jQuery function to be used for comparing titles to the string - defaults to "contains".

What is actually used is not a function per se, but a jQuery pseudo class selector. That is great actually. Pseudo classes can be created to extend the basic functionality of jQuery after all! This allows us to create a pseudo-class which performs the compare we require, and then pass this along to the tree search function.
You can find excellent documentation on how to create a pseudo-class by googling it of course, but this document helped me a lot: http://www.jameswiseman.com/blog/2010/04/19/creating-a-jquery-custom-selector/
I added the below code to my "treeOnload" function, so that jQuery is extended at the correct time. This means that once the document has finished loading, jQuery will be extended, and the custom selector will be available.
$.extend($.expr[':'], {
    ciContains: function(elem, i, match) {
        return $(elem).text().toUpperCase().indexOf(match[3].toUpperCase()) >= 0;
    }
});
In short, this will create a new pseudo-class "ciContains", which will look for the occurence of a given string in the element text while ignoring case. "match[3]" is the input text if you're wondering.
This selector can now be used anywhere on this page! For example, search for "tEsT" in each "td" element:
$("td:ciContains('tEsT')")
To get the search in the tree to work case-insensitive simply add "ciContains" as the second parameter to the search function.
For example:
$.tree.reference(l$Tree).search("d","ciContains");
I also built this feature into my tree demo page. By using the "case sensitive?" checkbox you can search either case sensitive or insensitive. One caveat though is using the same search string with different sensitivity. For example, searching the tree for "oliv" with no case sensitivity will highlight the "OLIVIA" node. Toggling the checkbox and searching again so that the search would be case sensitive will still highlight the "OLIVIA" node! This is because of how jstree performs a check on the provided search value. If the value is identical to the previous value, the already highlighted nodes will remain highlighted!

Tree demo page

6 comments:

  1. Very nice. Could you explain please how the selected node gets highlighted blue after it is clicked. - Thanks

    ReplyDelete
    Replies
    1. Hi Kiamran,
      It is explained in my previous post about the tree, but in short:
      the search function triggers the onsearch callback, with the nodes found as a parameter.By default, nodes which contain the text searched for will have a class "search" appended to them.

      So to color the nodes blue or any other color you can just define a css rule, eg:
      .search{background-color: red !important;}

      Delete
    2. Thank you Tom. It is nice of you to respond so promptly. I will try to reproduce, but it is a bit hard stuff for me :-) I wish I could get a view of a simple tree code to see just that part when tree gets defined and blue/red color highlight stays after click instead of default behavior of staying transparent.. I browsed the web intensely and found that kind of code only for trees in apex 3.x, which does not work for apex 4.2. Perhaps, when you get a chance you could show a simple example on your blog just for the highlight.

      Delete
    3. Actually, I noticed that if you reload your tree page example, then the selected node and highlight disappears. I would like it to have it persistent after page submit in order to mark the node that has been selected even after page is reloaded, I would like to have the selected node number kept in some text field associated with my database column NODE and display that node both in the text field on the page and in the tree. Either change of the node (by clicking on the tree on changing value in the text field should keep the highlight displayed after page sumbit.. At present I am using a tree query with the following
      case
      when NODE = :P12_SELECTED_NODE then
      '{'|| TO_CHAR(NODE) || '}'
      else TO_CHAR(NODE)
      end as title,
      'javascript:pageItemValue(' || "NODE" || ')' As link
      ...
      and pageItemValue script simply writes the NODE into text field and submits the page.
      Unfortunately I cannot put html tags into "title" as they get interpred as plain text and the only distinction that I use is curly brackets for the selected node number, e.g. {5} in the tree.

      Delete
    4. Hi Kiamran,
      Node highlight: I understood you wrong on your first comment. You meant node highlight as a node is selected, I was refering to the highlighting of a node after the tree has been searched. Simply having a different highlight for a selected node is only having to provide a css rule which will override the default color. I'm not really covering css overrides in this post nor dom inspection, but ideally you would use some sort of developer tool like firebug or chrome dev tools. Select a node in your tree. Rightclick the node and select 'inspect element'. In firebug you can then see the html element and on the style pane you can see the associated styles. The element will also show certain classes assigned, and these you can use to control style. A selected node for example will have the class "clicked", and this class has a css rule which will color the node in light blue.
      Retain Node selection: I just did not account for selected node retention. As you can see however in the example I put the selected node in an item. Basically this node is the selected node. If there was a page submit on the page then that value would be saved in session state and thus I could access it on page load. Without page submit I'd have to make sure that an ajax call would save the value to session state each click. This is from the viewpoint of not submitting the page for each action on the tree, such as the standard apex behaviour would be when defining a link on the tree. The only missing piece then is the reselection of the node after page load, but this can be achieved by calling the "select_branch"-function on the tree object (you can find this in the jstree documentation), and is identical to what apex does.
      Again, the reason why these extra steps would be necessary is because my philosophy behind these tree-related posts and demo is to not do constant page submits/rendering which can make the whole system feel slow and sluggish, especially when a tree starts to hold quite a bit of data.
      You have defined a link in your tree source query which will put javascript in the href tag of the node link. That's fine and I would call this a deliberate choice, and I made another choice in my demo, where I would not use the link at all and define what should happen when I click a node through the onselect option of the tree - and mostly because I dislike things like javascript event handler attributes or javascript in the anchor href attribute.

      Delete
    5. Tom,

      Thank you very much for detailed reply. Indeed, your approach without constantly submitting page is better. I will implement it in my next design for tree, but now I am a bit stuck with submit (executing a javascript to read the selected node and submit the page from the tree link). You are actually right about javascript again because my implementation seem to work in Firefox, but not in IE 8.

      The conversation with you and analysis of your example resulted in me finding the root of my problem for not getting the highlight in apex 4.2, where it seems to be given by default in the search class. My tree select-code with concatenated strings for the NODE prevented proper default highlight behavior.

      --------------- wrong ---------------------
      when NODE = :P12_SELECTED_NODE then
      '{'|| TO_CHAR(NODE) || '}'
      else TO_CHAR(NODE)
      end as title,
      -----------------------------------------------

      It needs to use simply the NODE without any concatenation, i.e.

      ---------------- correct -----------------------
      NODE as title,
      ------------------------------------------------

      Then, the highlight class works fine and the selected node text stays highlighted (default blue).

      Thank you again for responding to my request.

      Cheers,
      Kiamran

      Delete