16

I am using the treeview of KendoUI and want to give the user the possibility to filter it. There is even a demo that does what I want (http://demos.kendoui.com/web/treeview/api.html)

The Problem is that the filter is only applied to the 1st hierarchy of the TreeView, so if the filter-text is present in a child but not the parent, then the child won't be displayed.

Example:

  • Item 1
  • Item 2
    • Item xzy
    • Item abc

If the search text would be "abc", no item would be displayed. Instead I would like to have the following result:

  • Item 2
    • Item abc

Does anyone know how to do this? This is the code I am using:

   var tree_view_data = new kendo.data.HierarchicalDataSource({
        transport: {
            read: {
                url: "getall/items",
                dataType: "json"
            }
        },
        schema: {
            model: {
                children: "ChildItems"
            }
        }
    });
    //init tree view itself
    var $treeview = $("#div-treeview").kendoTreeView({
        dataSource: tree_view_data,
        dataTextField: [ "Text", "ChildrenText" ]
    });

    //allow filter of navigation tree
    var refreshTree = function () {
        tree_view_data.filter({
            field: "Text", //if I would use "ChildrenText" here nothing be displayed at all if filtertext is set
            operator: "contains",
            value: $("#tree-text-search").val()
        });
    };

    $("#tree-text-search").change(refreshTree).keyup(refreshTree);
Preli
  • 2,953
  • 10
  • 37
  • 50

6 Answers6

11

I found a way to make this happen just using jQuery selectors to hide and show the necessary child nodes.

First thing is when you are creating your tree view, add this parameter to your options:

loadOnDemand: false

This way the tree will render out all the HTML of your child nodes before being requested, thereby allowing you to use jQuery to navigate through.

Here is the jQuery code I have working that filters the nodes out that don't match, opens the group of nodes that do match, and shows them.

$("#searchTextInputField").keyup(function () {

        var filterText = $("#searchTextInputField").val();

        if(filterText !== "") {   
            $("#myTree .k-group .k-group .k-in").closest("li").hide();
            $("#myTree .k-group .k-group .k-in:contains(" + filterText + ")").each(function() {
                $(this).closest("ul").show();
                $(this).closest("li").show();
            });
        } else {
            $("#myTree .k-group").find("ul").hide();
            $("#myTree .k-group").find("li").show();
        }
    });
Mike Woj
  • 165
  • 1
  • 7
  • +! for this. The only thing I had to do was to extend jQuery contains to make it case-insensitive. – eadam Sep 05 '13 at 10:29
  • This is great, but I have a treeview that goes down to 4 levels, and this code only seems to search the first two levels.. how do I get it to search the whole tree? – user2206329 Mar 06 '14 at 23:59
  • This is great, but I have a treeview that goes down to 4 levels, it looks like it is searching the whole tree, but doesn't display anything.. something to do with the element being found being 4 levels down and the parents not being seen? how do I get it to display the parents? – user2206329 Mar 07 '14 at 01:20
8

Update 2016-01-13: There is now a help topic that shows how to perform TreeView filtering based on a user string.

You need to manually filter the child DataSources, so that only the necessary nodes are shown. Having different dataTextFields for the different levels makes it harder to grasp, so this code uses the text field only. Also, as this filtering is performed on the client-side, it assumes that you have loaded all nodes.

var treeview = $("#treeview").data("kendoTreeView"),
    item = treeview.findByText("Item 1.3"), // find the node that will be shown
    dataItem = treeview.dataItem(item),
    nodeText = dataItem.text;

// loop through the parents of the given node, filtering them to only one item
while (dataItem.parentNode()) {
    dataItem = dataItem.parentNode();
    dataItem.children.filter({ field: "text", operator: "contains", value: nodeText });
    nodeText = dataItem.text;
}

treeview.dataSource.filter({ field: "text", operator: "contains", value: nodeText });
Alex Gyoshev
  • 11,929
  • 4
  • 44
  • 74
  • Looks,it works only if there exists a node (need to tweak to handle the case when there are multiple nodes with same text) with text that matches exactly with the filter text. Let' say if a node text is "Item abc" and if user enters "abc" as filter text, findByText("abc") returns null as it tries to match the exact node text. correct me if m y assumption was wrong. – Naga Kiran Nov 22 '12 at 14:43
  • indeed it does. if you need to loosely match the nodes, you can use the [jQuery contains selector](http://api.jquery.com/contains-selector/) like this: `$(".k-in:contains('photo')")` – Alex Gyoshev Nov 24 '12 at 19:14
  • Tried jQuery contains selector, but the problem is that it searches only the nodes that are expanded (rendered in HTML) and won't match against the child nodes of collapsed nodes. Any other alternative for this? – Naga Kiran Dec 01 '12 at 10:08
  • Go recursively through the datasource, searching for the item. – Alex Gyoshev Jan 10 '13 at 08:24
  • I fixed this issue by creating a new field (added a new property inside tree object), added the appended text (appendeded the text of all child nodes along with parent node) to that field and made that property as filter field... Just a workaround, not a great solution... – Learner Jan 17 '13 at 17:42
  • Just to update the update, the URL is now:http://docs.telerik.com/kendo-ui/controls/navigation/treeview/how-to/filtering/filter-out-search-results – Doug Apr 24 '17 at 10:38
5

For more than 4 levels traverse all parents of type UL and LI and call show().

$("#filterText").keyup(function (e) {
    var filterText = $(this).val();

    if (filterText !== "") {
        $("#treeview-standards .k-group .k-group .k-in").closest("li").hide();
        $("#treeview-standards .k-group .k-group .k-in:contains(" + filterText + ")").each(function () {
            $(this).parents("ul, li").each(function () {
                $(this).show();
            });
        });
    } else {
        $("#treeview-standards .k-group").find("ul").hide();
        $("#treeview-standards .k-group").find("li").show();
    }
});
user2620454
  • 51
  • 1
  • 2
  • Thank you for this solution. Only thing is it wasn't hiding nodes that didn't match so I changed it a little: `$("#treeview-standards .k-in").closest("li").hide();` and it worked perfectly. Much faster than the solution the Kendo documentation gives you. – Sunden Jul 28 '17 at 17:10
0

First of all. KendoTreeView is very low-level control compared to Teleriks RadDropDownTree from ASP.NET http://www.telerik.com/help/aspnet-ajax/dropdowntree-overview.html (i mean the js of course!) The should have taken this to jquery/kendo... it needed to improve this filter, so if you prefer proper filtering on dataitem instead of "findByText", this does:

.1) finds all dataitems .2) checks your conditions (here lowercase contains on value/text) .3) flag item, flag parents .4) clean up, remove nodes left in tree by parent

that.nodeFilter = { logic: "or", filters: [] };
that.nodeFilter.filters.push({ field: "hidden", operator: "eq", value: false });
tree.element.find(".k-in").each(function () {
    var dItem = tree.dataItem($(this).closest("li"));
    dItem.hidden = false;
    if (dItem[that.options.dataValueField].toLowerCase().indexOf(searchTerm) != -1 ||
        dItem[that.options.dataTextField].toLowerCase().indexOf(searchTerm) != -1) {
        that.nodeFilter.filters.push({ field: that.options.dataValueField, operator: "eq", value: dItem[that.options.dataValueField] })
        while (dItem.parentNode()) {
            dItem = dItem.parentNode();
            dItem.hidden = false;
            that.nodeFilter.filters.push({ field: that.options.dataValueField, operator: "eq", value: dItem[that.options.dataValueField] })
        }
    } else {
        dItem.hidden = true;
    }
});
tree.dataSource.filter(that.nodeFilter);
tree.element.find(".k-in").each(function () {
    var node = $(this).closest("li");
    var dataItem = tree.dataItem(node);
    if (dataItem.hidden) {
        tree.remove(node);
    }
});
csharpnoob
  • 505
  • 1
  • 9
  • 24
0

This version searches the whole tree, is case insensitive, and hides nodes that do not contain the search query (jQuery 1.8+).

$("#search").keyup(function (e) {
        var query = $(this).val();

        if (query !== "") {
            $("#tree-view .k-in").closest("li").hide();
            $("#tree-view .k-item .k-in:Contains(" + query + ")").each(function () {
                $(this).parents("ul, li").each(function () {
                    $(this).show();
                });
            });
        } else {
            $("#tree-view .k-group").find("ul").hide();
            $("#tree-view .k-group").find("li").show();
        }
    });

jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function (arg) {
    return function (elem) {
        return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
    };
});
Sunden
  • 843
  • 3
  • 11
  • 24
0

If I read the question well, it is about filtering the data in the view and not the treeview itself. It could be done by recursion.

Recursion example that works:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Kendo UI Snippet</title>

    <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.common.min.css"/>
    <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.rtl.min.css"/>
    <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.silver.min.css"/>
    <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.mobile.all.min.css"/>

    <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
    <script src="https://kendo.cdn.telerik.com/2018.1.117/js/kendo.all.min.js"></script>
</head>
<body>
  <div class="demo-section k-content">
    <div id="treeview1"></div>
    <div id="showit"></div>
    <div id="treeview2"></div>
  </div>
<script>
    //
    // Define hierarchical data source
    //
    var mydata = new kendo.data.HierarchicalDataSource({ 
      name: "Food", items: [
      { name: "Meat", items:
        [
          { name: "Pork" },
          { name: "Beef" }
        ]
      },
      { name: "Vegetables", items:
        [
          { name: "Pepper" }          
        ]
      }
      ]
    });

    //
    // When debugging
    //    
    var debug=false;
  
    //
    // Find and return Item when found.
    //
    function FindByName(items, myName)
    {
      //Query definition
      var query = kendo.data.Query.process(items, {
              filter: {
                logic: "or",
                filters: [{
                  field: "name",
                  value: myName,
                  operator: "eq"
                }]
              }
            });
      
       if (debug) $("#showit").html($("#showit").html()+"  found:" + JSON.stringify(query.data));
      
       //
       // return Item when found.
       //
       if (query.data != "")       
         return query.data; //ready
       else
       {
         //
         // if sub-items, search further
         //
         for (let i=0; i<items.length; i++)            
         {
           if (debug) $("#showit").html($("#showit").html()+"  test:" + JSON.stringify(items[i]));
           if (items[i].items!=null)
           {           
             if (debug) $("#showit").html($("#showit").html()+"  search sub....");
             var r = FindByName(items[i].items, myName);
             if (r!=null) return r; //ready, else continue searching further
           };
         }
       }
      if (debug) $("#showit").html($("#showit").html()+"  not found.");
      return null; //nothing found.
    }
  
    //
    // print the input
    //
    $("#showit").html($("#showit").html()+"  Food:" + JSON.stringify(mydata.options.items));
    //
    // print the result
    //
   var ret=FindByName(mydata.options.items,"Beef");
    $("#showit").html($("#showit").html()+"<p>  Beef:" + JSON.stringify(ret));
    
    $("#treeview1").kendoTreeView({
      dataSource: mydata.options.items,
      dataTextField: ["name"]
    });

    ret=FindByName(mydata.options.items,"Meat");
    $("#showit").html($("#showit").html()+"<p>  Meat:" + JSON.stringify(ret));
    ret=FindByName(mydata.options.items,"Pepper");
    $("#showit").html($("#showit").html()+"<p>  Pepper:" + JSON.stringify(ret));
    ret=FindByName(mydata.options.items,"Vegetables");
    $("#showit").html($("#showit").html()+"<p>  Vegetables:" + JSON.stringify(ret));
    //
    // Example: bind return value [ret] to treeview.
    //
    $("#treeview2").kendoTreeView({
      dataSource: ret,
      dataTextField: ["name"]
    });
</script>
</body>
</html>
David
  • 11
  • 3