AJAX cascading response for accessing objects that have "hasChildren" that are not included in the JSON object

I am trying to create a drop-down list with child and grandchild-level objects that I need to access that are not included in the initial ajax call response.

I use this structure for my cascading ajax call:

$.ajax({ url:"../../api/getEnum", data: {itemFilter: "", Id: "~some guid~"}, dataType: "json", success: function(data){ var dropdownObject = []; dropdownObject.push('<option value=""></option>'); $(data).each(function(){ //optgroup level if($(this)[0].HasChildren == true){ dropdownObject.push('<optgroup label="' + $(this)[0].Text + '">'); $.ajax({ url:"../../api/getEnum", data: {itemFilter: "", Id: $(this)[0].Id}, dataType: "json", success: function(data){ $(data).each(function(){ //prepend level if($(this)[0].HasChildren == true){ var prependage = $(this)[0].Text; $.ajax({ url:"../../api/getEnum", data: {itemFilter: "", Id: $(this)[0].Id}, dataType: "json", success: function(data){ $(data).each(function(){ dropdownObject.push('<option value="' + $(this)[0].Id +'">' + prependage + ": " + $(this)[0].Text + '</option>'); }) } }) } }) } }) dropdownObject.push('</optgroup>'); } else { dropdownObject.push('<option value="' + $(this)[0].Id +'">' + $(this)[0].Text + '</option>'); } }); $('#dropDown').html(dropdownObject.join('')); $("#dropDown").trigger("chosen:updated"); //needs to be done to trigger the update of my drop down with "Chosen plugin" after all options have been built. } }) 

Here is an example of the data I'm working with:

 ...{Id: "~some guid~", Text: "Medium", Name: "Medium", HasChildren: true,…}... 

So, if HasChildren is true, I need to cascade a new API call against the parent's GUID. Due to AJAX asynchrony, I think the user interface only builds top-level options that don't have children. For performance, I do not want to disable async, but I do not know how to get the answers at the right time and in the correct sequence.

I guess I probably need to build a callback function to handle the sequence, and then execute an update trigger at the end of this, but I don't have much experience with this, and I'm not sure if the direction is right,

Thanks for the help!

0
source share
1 answer

First of all, there are 2 statements that I do not understand (lines 7-8):

 var areaOption = []; dropdownObject.push('<option value=""></option>'); 

Maybe the first one is useful somewhere else, so good (although you are probably better off putting it outside the success: function).
But I do not understand how the second can be legal.

The structure of the source data.

As for the nested levels, I am surprised that you have 3 levels, while it seems that only 2 can do the job. When there are children, you build something like this:

 <optgroup label="level-1-text"> <option value="level-3-id">Level-2 text: Level-3 text</option> </optgroup> 

where level 2 is only useful for getting "level 2 text."
Therefore, it seems schematically that the corresponding source structure was as follows:

 calling Id: "~some guid~" returns {Id: "level-1-id", Text: "Level-1 text", HasChildren: true} >>> <optgroup label="level-1-text"> calling Id: "level-1-id" returns {Id: "level-2-id", Text: "Level-2 text", HasChildren: true} >>> (nothing) calling Id: "level-2-id" returns {Id: "level-3-id", Text: "Level-3 text", HasChildren: false} >>> <option value="level-3-id">Level-2 text: Level-3 text</option> 

So, if you have a hand in the source data structure, you'd better use something like this:

 calling Id: "~some guid~" returns {Id: "level-1-id", Text: "Level-1 text", HasChildren: "Level-2 text"} >>> <optgroup label="level-1-text"> calling Id: "level-1-id" returns {Id: "level-3-id", Text: "Level-3 text", HasChildren: false} >>> <option value="level-3-id">Level-2 text: Level-3 text</option> 

Note: here I saved the names "level-2" and "level-3" for consistency, whereas now there are only 2 levels.

Using jQuery.

I was surprised that you $(data).each(function() over the resulting JSON array using $(data).each(function() : this form is designed to handle jQuery objects, whereas for arrays and other non-jQuery objects we should use $.each(data, function() .

So, I had some tests, and I was surprised to find that this works too, although it somehow shouldn't! You can find a detailed study reported in the question I posted about this .

Another point is how you address each element that has passed the above iteration: $(this)[0].HasChildren unnecessarily complex because it sequentially encapsulates this into a jQuery object and then retrieves it!
You should just use this.HasChildren .

The proposed simple way to achieve all the work.

Building HTML as a linear stream of strings is not an easy path because:

  • closing tag for <optgroup> should appear _after internal <option> been created
  • but asynchronous Ajax mode is hard to figure out when to do this

In contrast, it is easy to use jQuery to build <option> and <optgroup> each in turn when necessary.
Note that this means that for this we must use jQuery.append() instead of jQuery.html() .

Regarding the nested aspect of the source data, the easiest way is to use recursion rather than using $.ajax several times at nested levels.
It also has the advantage of reducing code and making it more readable.

Here is a snippet based on all of the above considerations.
Note that I also simplified this by following my previous sentence, just use HasChildren (renamed Children for clarity) to provide information on nested <optgroup> . For <option> it should not even be present.

 function buildMenu(Id, $parent, prependage) { $parent = $parent || $('#dropdown'); $.ajax({ url:"../../api/getEnum", data: {itemFilter: "", Id: Id}, dataType: "json", success: function(data) { $.each(data, function() { if (!this.Children) { // create a simple <option> $parent.append('\ <option value="' + this.Id + '">\ ' + (!!prependage ? (prependage + ': ') : '') + this.Text + '\ </option>\ '); } else { // create an <optgroup> $parent.append('\ <optgroup label="' + this.Text + '">\ </optgroup>\ '); // call for <option>s in this optgroup buildMenu(this.Id, $('optgroup:last', $parent), this.Children); } }); } }) } buildMenu('~some guid~'); 

Of course, I tested a slightly modified version, which you can check, works as expected:

 <?php $menus = [ '~some guid~' => [ ["Id" => "level-1-1", "Text" => "Text-1-1"], ["Id" => "level-1-2", "Text" => "Text-1-2"], ["Id" => "level-1-3", "Text" => "Text-1-3", "Children" => "level-1-3"], ["Id" => "level-1-4", "Text" => "Text-1-4"], ["Id" => "level-1-5", "Text" => "Text-1-5", "Children" => "level-1-5"], ], 'level-1-3' => [ ["Id" => "level-1-3-1", "Text" => "Text-1-3-1"], ["Id" => "level-1-3-2", "Text" => "Text-1-3-2"], ["Id" => "level-1-3-3", "Text" => "Text-1-3-3"], ], 'level-1-5' => [ ["Id" => "level-1-5-1", "Text" => "Text-1-5-1"], ["Id" => "level-1-5-2", "Text" => "Text-1-5-2"], ["Id" => "level-1-5-3", "Text" => "Text-1-5-3"], ], ]; if ($Id = @$_REQUEST['Id']) { echo json_encode($menus[$Id]); exit; } ?> <select id="dropdown"> </select> <script src="http://code.jquery.com/jquery-1.11.3.js"></script> <script> function buildMenu(Id, $parent, prependage) { $parent = $parent || $('#dropdown'); $.ajax({ url:'', data: {itemFilter: "", Id: Id}, dataType: "json", success: function(data) { $.each(data, function() { if (!this.Children) { // create a simple <option> $parent.append('\ <option value="' + this.Id + '">\ ' + (!!prependage ? (prependage + ': ') : '') + this.Text + '\ </option>\ '); } else { // create an <optgroup> $parent.append('\ <optgroup label="' + this.Text + '">\ </optgroup>\ '); // call for <option>s in this optgroup buildMenu(this.Id, $('optgroup:last', $parent), this.Children); } }); } }); } buildMenu('~some guid~'); </script> 
-one
source

All Articles