This is a migrated thread and some comments may be shown as answers.

hierarchical view with autocomplete

8 Answers 282 Views
DropDownTree
This is a migrated thread and some comments may be shown as answers.
Luc
Top achievements
Rank 1
Luc asked on 07 Nov 2019, 07:52 AM

Hello!

 

First of all I'm posting tihs in the DropDownTree section but I'm not convinced that the DropDownTree is necessarily the right component for what I want.

Basically I need to display something exactly like the autocomplete component but it must have a hierarchy, so the items displayed in this "autocomplete" can be a parent item or a child item and each child is linked to a parent and should be somehow intended. This looks like what the DropDownTree offers but I'm having several problems working with it.

 

In order to get my data, I need to call an external service which takes two parameters, the language and the search string (which should be whatever the user typed in the autocomplete box).

This is how I setup my DropDown:

01.@(Html.Kendo().DropDownTree()
02.  .Name("search")
03.  //.DataTextField("name")
04.  .Placeholder("Search")
05.  .Filter(FilterType.Contains)
06.  .MinLength(2)
07.  .DataSource(dataSource => dataSource
08.    .Read(read => read
09.      .Action("Search", "Blabla", new { culture = Localizer.CultureName })
10.      .Data("onAdditionalData")
11.    )
12.  )
13.)
14.<input type="hidden" id="searchFilter" name="searchFilter" />

 

In onAdditionalData, I setup the two parameters that my Search action from the Blabla controller requires:

1.<script>
2.    function onAdditionalData() {
3.        return {
4.            language: "@Localizer.CultureName",
5.            searchString: $("#searchFilter").val()
6.        };
7.    }
8.</script>

Since I didn't find an easy way to get the value that the user typed, I'm binding the filtering event like this and setting the value in a hidden field, which is then used by onAdditionalData to pass the search string:

01.function dropdowntree_filtering(e) {
02.    //get filter descriptor
03.    var filter = e.filter;
04.    $("#searchFilter").val(filter.value);
05.}
06.
07.$(function () {
08.    $(document).ready(function() {
09.        var dropdowntree = $("#search").data("kendoDropDownTree");
10.        dropdowntree.bind("filtering", dropdowntree_filtering);
11.    });
12.});

 

Then my controller calls the service with the 2 parameters and that's fine, I get my results in essentially this format:

01.[
02.  {
03.    "id":851,
04.    "name":"Some name 1",
05.    "idParent":null,
06.    "children":[
07. 
08.    ]
09.  },
10.  {
11.    "id":402,
12.    "name":"Some name 2",
13.    "idParent":null,
14.    "children":[
15.      {
16.        "id":403,
17.        "name":"Some name 3",
18.        "idParent":402,
19.        "children":null
20.      },
21.      {
22.        "id":404,
23.        "name":"Some name 4",
24.        "idParent":402,
25.        "children":null
26.      }
27.    ]
28.  },
29.  {
30.    "id":923,
31.    "name":"Some name 5",
32.    "idParent":null,
33.    "children":[
34.      {
35.        "id":929,
36.        "name":"Some name 6",
37.        "idParent":923,
38.        "children":null
39.      }
40.    ]
41.  },
42.  {
43.    "id":728,
44.    "name":"Some name 7",
45.    "idParent":727,
46.    "children":[
47.      {
48.        "id":734,
49.        "name":"Some name 8",
50.        "idParent":728,
51.        "children":null
52.      }
53.    ]
54.  },
55.  {
56.    "id":757,
57.    "name":"Some name 9",
58.    "idParent":727,
59.    "children":[
60.      {
61.        "id":763,
62.        "name":"Some name 10",
63.        "idParent":757,
64.        "children":null
65.      }
66.    ]
67.  },
68.  {
69.    "id":405,
70.    "name":"Some name 11",
71.    "idParent":null,
72.    "children":[
73.      {
74.        "id":406,
75.        "name":"Some name 12",
76.        "idParent":405,
77.        "children":null
78.      }
79.    ]
80.  }
81.]

 

Now my problem is that I don't know how to build a result set that the dropdowntree can display properly. I've tried to build a list of DropDownTreeItem like this:

01.public async Task<JsonResult> Search(string language, string searchString)
02.{
03.  IReadOnlyCollection<MyDto> results = await _myService.Search(language, searchString);
04.  IList<DropDownTreeItem> items = new List<DropDownTreeItem>();
05.  foreach(var r in results)
06.  {
07.    DropDownTreeItem item = new DropDownTreeItem
08.    {
09.      Id = r.Id?.ToString(),
10.      Text = r.Name,
11.      HasChildren = r.Children.Count > 0
12.    };
13.    if(r.Children.Count > 0)
14.    {
15.      foreach(var c in r.Children)
16.      {
17.        item.Items.Add(new DropDownTreeItem
18.        {
19.          Id = c.Id?.ToString(),
20.          Text = c.Name,
21.          HasChildren = false
22.        });
23.      }
24.    }
25.    items.Add(item);
26.  }
27.  return Json(items);
28.}

 

But that doesn't really work, the autocomplete does not contain any items then, for some reason that also doesn't populate anything in the autocomplete (I get "no data found" even though there are items). From the examples I've seen, it seems that DropDownTreeItem needs to call the search method recursively to get its children and this doesn't work for me because my service already returns me all the relevant children based on my search string.

I've tried using the ToTreeDataSourceResult method like this:

1.public async Task<JsonResult> Search(string language, string searchString)
2.{
3.  IReadOnlyCollection<MyDto> results = await _myService.Search(language, searchString);
4.  var tree = results.ToTreeDataSourceResult(new DataSourceRequest(), a => a.Id, a => a.IdParent);
5.  return Json(tree.Data);
6.}

 

that also doesn't populate anything in the autocomplete. (I get "no data found" even though the tree.Data has data).

 

I'm starting to think that this DropDownTree is not the right component and maybe I can achieve what I want with a simple AutoComplete and some styling ?

Hopefully my problem is clear enough with those details.

Any help would be greatly appreciated,

Thanks!

 

 

8 Answers, 1 is accepted

Sort by
0
Luc
Top achievements
Rank 1
answered on 07 Nov 2019, 08:53 AM

I also tried something else based on something I found in another post on this forum:

01.function dropdowntree_filtering(e) {
02.        //get filter descriptor
03.        var filter = e.filter;
04.        var ddt = $("#search").getKendoDropDownTree();
05.        var ddtVal = ddt.value();
06. 
07.        $.ajax({
08.            type: "GET",
09.            url: "/en/Blabla/Search",
10.            data: { searchString: filter.value, language: 'en' },
11.            dataType: "json",
12.            success: function (result) {
13.                ddt.dataSource.data(result);
14.            }
15.        });  
16.    }
17. 
18.    $(function () {
19.        $(document).ready(function() {
20.            var dropdowntree = $("#search").data("kendoDropDownTree");
21.            dropdowntree.bind("filtering", dropdowntree_filtering);
22.        });
23.    });

 

For this test I've changed the controller like this:

01.private string lastQuery = null;
02.private IReadOnlyCollection<MyDto> lastResult = null;
03. 
04.private async Task<IReadOnlyCollection<MyDto>> GetData(string language, string searchString)
05.{
06.  if(!string.IsNullOrWhiteSpace(searchString) && searchString.Equals(lastQuery))
07.  {
08.    return lastResult ?? new MyDto[0];
09.  }
10.  lastResult = await _myService.Search(language, searchString);
11.  lastQuery = searchString;
12.  return lastResult;
13.}
14. 
15.public async Task<JsonResult> Search(int? id, [CanBeNull] string language, [CanBeNull] string searchString)
16.{
17.  var r = await GetData(language, searchString);
18.  var result = r.Where(x => id.HasValue ? x.IdParent == id : x.IdParent == null).Select(item => new
19.  {
20.      id = item.Id,
21.      Name = item.Name,
22.      hasChildren = item.Children.Count > 0
23.  }).ToArray();
24.  return Json(result);
25.}

I still get in my controller with the correct values, I thought this would work with the recursion but it doesn't, but still I get 4 results (without children), but they don't appear in the dropdowntree.

 

 

0
Luc
Top achievements
Rank 1
answered on 07 Nov 2019, 08:57 AM
I've added this screenshot to illustrate what we're hoping to achieve. We don't need the highlighting but basically the results are based on the input and the output should be on 2 levels, in bold for the parent and the children underneath with some indentation
0
Luc
Top achievements
Rank 1
answered on 07 Nov 2019, 09:25 AM

Based on the test from my 2nd post, if I change the javascript dropdowntree_filtering function to this:

01.function dropdowntree_filtering(e) {
02.    //get filter descriptor
03.    var filter = e.filter;
04.    var ddt = $("#search").getKendoDropDownTree();
05.    var ddtVal = ddt.value();
06.    $.ajax({
07.        type: "GET",
08.        url: "/en/Blabla/Search",
09.        data: { searchString: filter.value, language: 'en' },
10.        dataType: "json",
11.        success: function (result) {
12.            //ddt.dataSource.data(result);
13.            var ds = new kendo.data.HierarchicalDataSource({
14.                data: result
15.            });
16. 
17.            ddt.setDataSource(ds);
18.        }
19.    });  
20.}

 

Then the dropdowntree contains results! But I still don't have the children...

0
Luc
Top achievements
Rank 1
answered on 08 Nov 2019, 07:08 AM

I finally managed to get it to almost work, there's still one problem. Basically there was an issue in my linq query, just for reference here's the code that almost works here:

index.cshtml

01.@(Html.Kendo().DropDownTree()
02.  .Name("search")
03.  .DataValueField("Id")
04.  .DataTextField("name")
05.  .HtmlAttributes(new { style = "width:100%" })
06.  .Filter(FilterType.Contains)
07.  .LoadOnDemand(false)
08.  .MinLength(2)
09.  .DataSource(dataSource => dataSource
10.      .Read(read => read
11.          .Action("Search", "Blabla", new { culture = Localizer.CultureName })
12.          .Data("onSearchAdditionalData")
13.      )
14.  )
15.)
16. 
17.<input type="hidden" id="searchFilter" name="searchFilter" />
18.<script>
19.  function onSearchAdditionalData() {
20.    return {
21.      language: "@Localizer.CultureName",
22.      searchString: $("#searchFilter").val()
23.    };
24.  }
25.</script>
26. 
27. 
28.<script>
29.    function dropdowntree_filtering(e) {
30.        //get filter descriptor
31.        var filter = e.filter;
32.        $("#searchFilter").val(filter.value);
33. 
34.        var ddt = $("#search").getKendoDropDownTree();
35.        ddt.dataSource.read();
36.    }
37. 
38.    $(function () {
39.        $(document).ready(function() {
40.            var dropdowntree = $("#search").data("kendoDropDownTree");
41.            dropdowntree.bind("filtering", dropdowntree_filtering);
42.        });
43.    });
44.</script>

 

BlaBlaController.cs

01.public class BlablaController : Controller
02.{
03.  private readonly IMyService _myService;
04. 
05.  private string lastQuery = null;
06.  private IReadOnlyCollection<MyDto> lastResult = null;
07. 
08.  public BlablaController(IMyService myService)
09.  {
10.    _myService = myService;
11.  }
12. 
13.  private async Task<IReadOnlyCollection<MyDto>> GetData(string language, string searchString)
14.  {
15.    if(!string.IsNullOrWhiteSpace(searchString) && searchString.Equals(lastQuery))
16.    {
17.      return lastResult ?? new MyDto[0];
18.    }
19.    lastResult = await _myService.Search(language, searchString);
20.    lastQuery = searchString;
21.    return lastResult;
22.  }
23. 
24.  public async Task<IActionResult> Search(int? id, string language, string searchString)
25.  {
26.    IReadOnlyCollection<MyDto> baseSet = await GetData(language, searchString);
27.    IReadOnlyCollection<MyDto> flattenedSet = baseSet.SelectMany(x => x.Children).Union(baseSet).ToArray();
28. 
29.    var result = flattenedSet.Where(x => id.HasValue ? x.IdParent == id : (x.IdParent == null || (x.Children != null && x.Children.Any()))).Select(item => new
30.    {
31.        id = item.Id,
32.        Name = item.Name,
33.        hasChildren = item.Children?.Any() ?? false
34.    }).ToList();
35.    return Json(result);
36.  }
37.}

 

The problem now is that with some queries it seems that the Search method is not being called recursively for the children like it normally is. Here are some examples of a query that works:

01.id=null, language="de", searchString="hol"
02.  flattenedSet.Select(f => new { f.Id, f.Name}).ToArray():
03.    [0]: {{ Id = 449, Name = Holzhandwerker/in (Drechslerei) EFZ }}
04.    [1]: {{ Id = 450, Name = Holzhandwerker/in (Weissküferei) EFZ }}
05.    [2]: {{ Id = 862, Name = Holzbearbeiter/in EBA }}
06.    [3]: {{ Id = 820, Name = Holzbildhauer/in EFZ }}
07.    [4]: {{ Id = 448, Name = Holzhandwerker/in EFZ }}
08.    [5]: {{ Id = 447, Name = Säger/in Holzindustrie EFZ }}
09.  result:
10.    [0]: { id = 862, Name = "Holzbearbeiter/in EBA", hasChildren = false }
11.    [1]: { id = 820, Name = "Holzbildhauer/in EFZ", hasChildren = false }
12.    [2]: { id = 448, Name = "Holzhandwerker/in EFZ", hasChildren = true }
13.    [3]: { id = 447, Name = "Säger/in Holzindustrie EFZ", hasChildren = false }
14. 
15.#this then yields another query:
16. 
17.id=448, language="de", searchString="hol"
18.  flattenedSet.Select(f => new { f.Id, f.Name}).ToArray():
19.    [0]: {{ Id = 449, Name = Holzhandwerker/in (Drechslerei) EFZ }}
20.    [1]: {{ Id = 450, Name = Holzhandwerker/in (Weissküferei) EFZ }}
21.    [2]: {{ Id = 862, Name = Holzbearbeiter/in EBA }}
22.    [3]: {{ Id = 820, Name = Holzbildhauer/in EFZ }}
23.    [4]: {{ Id = 448, Name = Holzhandwerker/in EFZ }}
24.    [5]: {{ Id = 447, Name = Säger/in Holzindustrie EFZ }}
25.  result:
26.    [0]: { id = 449, Name = "Holzhandwerker/in (Drechslerei) EFZ", hasChildren = false }
27.    [1]: { id = 450, Name = "Holzhandwerker/in (Weissküferei) EFZ", hasChildren = false }

This is exactly what I expect. The Search method is called again with the id of each element that has children.

Now here's another similar query:

01.id=null, language="de", searchString="back"
02.  flattenedSet.Select(f => new { f.Id, f.Name}).ToArray():   
03.    [0]: {{ Id = 403, Name = Bäcker/in-Konditor/in-Confiseur/in (Bäckerei-Konditorei) EFZ }}
04.    [1]: {{ Id = 404, Name = Bäcker/in-Konditor/in-Confiseur/in (Konditorei-Confiserie) EFZ }}
05.    [2]: {{ Id = 929, Name = Detailhandelsassistent/in (Bäckerei, Konditorei, Confiserie) EBA }}
06.    [3]: {{ Id = 734, Name = Detailhandelsfachmann/-frau Beratung (Bäckerei/Konditorei/Confiserie) EFZ }}
07.    [4]: {{ Id = 763, Name = Detailhandelsfachmann/-frau Bewirtschaftung (Bäckerei/Konditorei/Confiserie) EFZ }}
08.    [5]: {{ Id = 406, Name = Lebensmitteltechnologe/in (Backwaren) EFZ }}
09.    [6]: {{ Id = 851, Name = Bäcker/in-Konditor/in-Confiseur/in EBA }}
10.    [7]: {{ Id = 402, Name = Bäcker/in-Konditor/in-Confiseur/in EFZ }}
11.    [8]: {{ Id = 923, Name = Detailhandelsassistent/in EBA }}
12.    [9]: {{ Id = 728, Name = Detailhandelsfachmann/-frau EFZ, Schwerpunkt Beratung }}
13.    [10]: {{ Id = 757, Name = Detailhandelsfachmann/-frau EFZ, Schwerpunkt Bewirtschaftung }}
14.    [11]: {{ Id = 405, Name = Lebensmitteltechnologe/-login EFZ }}
15.     
16.  result:
17.    [0]: { id = 851, Name = "Bäcker/in-Konditor/in-Confiseur/in EBA", hasChildren = false }
18.    [1]: { id = 402, Name = "Bäcker/in-Konditor/in-Confiseur/in EFZ", hasChildren = true }
19.    [2]: { id = 923, Name = "Detailhandelsassistent/in EBA", hasChildren = true }
20.    [3]: { id = 728, Name = "Detailhandelsfachmann/-frau EFZ, Schwerpunkt Beratung", hasChildren = true }
21.    [4]: { id = 757, Name = "Detailhandelsfachmann/-frau EFZ, Schwerpunkt Bewirtschaftung", hasChildren = true }
22.    [5]: { id = 405, Name = "Lebensmitteltechnologe/-login EFZ", hasChildren = true }

The result variable contains the correct data, we have 6 parent entries, only the first one doesn't have children. And this query does not result in any recursive calls, this is the end of it and there is no data in my dropdown. Any idea why?

 

 

 

0
Luc
Top achievements
Rank 1
answered on 08 Nov 2019, 07:42 AM

found it, needed to add .ServerFiltering(true) like so:

01.@(Html.Kendo().DropDownTree()
02.  .Name("search")
03.  .DataValueField("Id")
04.  .DataTextField("name")
05.  .HtmlAttributes(new { style = "width:100%" })
06.  .Filter(FilterType.Contains)
07.  .LoadOnDemand(false)
08.  .MinLength(2)
09.  .DataSource(dataSource => dataSource
10.      .Read(read => read
11.          .Action("Search", "Blabla", new { culture = Localizer.CultureName })
12.          .Data("onAdditionalData")
13.      )
14.      .ServerFiltering(true)
15.  ))

 

 

a

0
Ivan Danchev
Telerik team
answered on 08 Nov 2019, 05:30 PM

Hello Luc,

Thank you for the follow up. We are glad you managed to properly configure the DropDownTree to use remote data and server filtering.

Regards,
Ivan Danchev
Progress Telerik

Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Luc
Top achievements
Rank 1
answered on 12 Nov 2019, 07:33 AM

Thanks for the reply.

 

I do have one more question regarding this component. It does not seem that I can set all the messages in particular error messages for the DropDownTree component: I'm able to set the clear, nodata, singletag and deletetag from the Messages method. But if for some reason there's a problem in fetching the data, I get a "request failed" with a "retry" button in my DropDownTree and I cannot find any ways to set the text for them. I tried including telerik js/messages/kendo.message.XXX.js files based on the current language but that doesn't fix the issue. Any idea how this can be done? In order to reproduce this, I start the site in visual studio, everything is loaded properly, then I stop the app from visual studio, and then I type something in the filter box of the DropDownTree component.

 

Thanks!

0
Ivan Danchev
Telerik team
answered on 14 Nov 2019, 08:23 AM

Hello Luc,

These messages come from the TreeView embedded in the DropDownTree. You can change them through the API, by getting a reference to the DropDownTree, then accessing its embedded treeview and setting the messages with the setOptions method, as shown below:

<script>
	$(document).ready(function () {
		var ddt = $("#search").data("kendoDropDownTree");
		var treeview = ddt.treeview;

		treeview.setOptions({
			messages: {
				retry: "custom retry",
				requestFailed: "custom request failed",
			}
		})
	})
</script>

Regards,
Ivan Danchev
Progress Telerik

Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
Tags
DropDownTree
Asked by
Luc
Top achievements
Rank 1
Answers by
Luc
Top achievements
Rank 1
Ivan Danchev
Telerik team
Share this question
or