3

My aim is to replicate this structure automatically from a json file.

<ul class="sidebar-menu">
  <li class="treeview">
    Mammals
    <ul class="treeview-menu">
      <li>Goat</li> 
      <li> Sheep
        <ul class="treeview-menu">
          <li>Bone</li>
          <li>Bone
            <ul class="treeview-menu">
              <li>Variant 1</li>
              <li> Variant 2</li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

JSON

[
{"datasetID":"5fd4124900827","higherClassification":"Eukarya","kingdom":"Animalia","phylum":"Chordata","class":"Mammalia","order":"Artiodactyla","family":"Bovidae","genus":"Capra","subgenus":"None","vernacularName":"goat","commonName":"None","elementName":"Calcaneus","commonElementName":"None"},
{"datasetID":"5fd4058e5c8d2","higherClassification":"Eukarya","kingdom":"Animalia","phylum":"Chordata","class":"Mammalia","order":"Artiodactyla","family":"Bovidae","genus":"Capra","subgenus":"None","vernacularName":"goat","commonName":"goat","elementName":"Femur","commonElementName":"None"}
]

The relevant parts are:

"datasetID":"5fd4124900827"

"class":"Mammalia",

"order":"Artiodactyla",

"family":"Bovidae",

"genus":"Capra",

"subgenus":"None",

"vernacularName":"goat",

"elementName":"Calcaneus"},

So the class is on the top level of the hierarchy, it could be mammal, bird, fish...

Taking class: Mammalia as an example, under this is order under that family under that genus

then if there is a subgenus that is on the next level also.

Under that is the vernacularName then elementName.

Each record has a unique id datasetID there may be multiple "elementName": "Calcaneus" for a goat, these need an integer added (i.e. Calcaneus 1, then Calcaneus 2, then Calcaneus 3 etc.

>Mammalia
  >order
    >family
      >genus
        >subgenus (if exists)
          >vernacularName
            >elementName (if more than one append 1,2,3...)

So, my mega question, how to do this in javascript?

My attempt so far:

Php gets the json, yes this could be done in javascript.

<?php
$data = json_decode(file_get_contents("bonify" . $version . "/app/json/data.json"), True);
?>

Javascript picks up the json:

<script type="text/javascript">
const version = "<?php echo $version; ?>";
$.getJSON('bonify'+ version +'/app/json/data2.json', function(json) {
    console.log(json); // this will show the info it in firebug console
    obj = json

This lists all the json data:

function printValues(obj) {
    for(var k in obj) {
        if(obj[k] instanceof Object) {
            printValues(obj[k]);
        } else {
            document.write(obj[k] + "<br>");
        };
    }
};

closing code:

  });
</script>

I'm not convinced document.write is the best way to do this.

I have this code for my search and it seems like I should adapt that but with out the filter capability.

 $('#txt-search').keyup(function(){
             var searchField = $(this).val();
            if(searchField === '')  {
                $('#filter-records').html('');
                return;
            }
             var regex = new RegExp(searchField, "i");
             var output = '<div class="col-12 p-0"><hr />';
             var count = 1;
              $.each(data, function(key, val){
                if ((val.datasetID.search(regex) != -1) || (val.ownerInstitutionCode.search(regex) != -1)|| (val.vernacularName.search(regex) != -1)|| (val.elementName.search(regex) != -1)) {
          output += '<ul class="sidebar-menu">';
          output += '<li><a href="bonify/' + val.datasetID + '"><i class="fas fa-bone" data-fa-transform="rotate-45"></i> <span>' + val.vernacularName + ': ' + val.elementName + '</span></a></li>';
          output += '</ul>';
                      if(count%2 == 0){
                    output += '</div>'
                  }
                  count++;
                }
              });
              $('#filter-records').html(output);
         });
   });
 });

I'm assuming several nested foreach loops is the way to go? I've put the whole aim for clarity. I am trying to learn and I have a learning disability so please be patient with me, thanks for your help. I've tried to included as much info as possible to avoid having my question closed as too broad.

Spatial Digger
  • 1,883
  • 1
  • 19
  • 37
  • Your JSON is an array of objects. There is no nesting at all in the JSON you posted – mplungjan Dec 19 '20 at 12:12
  • 1
    Comma added to title for clarity – Spatial Digger Dec 19 '20 at 12:28
  • @mplungjan my aim is to use these objects to created a nested navbar, thinking this through (as I'm trying to solve this myself while the question is active) this would mean all mammals would need grouping and identical sub categories? I'd rather not actually alter the JSON file, so I guess a groupby or aggregate function would be required in some way? – Spatial Digger Dec 19 '20 at 12:31
  • I would expect some nesting in the object, otherwise it would be very hard to make a nested output – mplungjan Dec 19 '20 at 12:32
  • ok, so the flat json file would be brought into php/javascript and then grouped? Maybe written to disk are imported again? – Spatial Digger Dec 19 '20 at 12:35
  • so, to group, `obj.Order.Mamillia {'Mamillia': {"class": class, "order": order}}` but then with sub-nesting in that logic? – Spatial Digger Dec 19 '20 at 12:43

1 Answers1

1

You have a repeated pattern. If we assume that you have built a hierarchical data structure, then we can use a function using template literals, like:

function buildChildren(children) {
    var tvms = [];
    for (let child of children) {
        tvms.push(myTreeViewMenu(child));
    }
    return tvms;
}

function myTreeViewMenu(treeViewMenu) {
    tvms = buildChildren(treeViewMenu.children);
    return `
        <ul class="treeview-menu">
            <li>${treeViewMenu.name} ${tvms.join("")}</li>
        </ul>
    `;
}

function myTree(tree) {
    tvms = buildChildren(tree.children);
    return `
        <ul class="sidebar-menu">
            <li class="treeview">
                ${tree.name}
                ${tvms.join("")}
            </li>
        </ul>
    `;
}

(NOT TESTED)

This logic can be a starting point for you, basically you nest your pattern into itself. You need to make sure that from your raw JSON you build an object tree whose nodes have a string called name and an array for the subtree called children. Also, make sure there are no cycles in the tree.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • It's a biological hierarchy, so no cycles. So my 1st problem is that my data is not stored hierarchically. Once that is sorted I can move to this. – Spatial Digger Dec 19 '20 at 15:35
  • Looks like `array.reduce` might be the way to go? – Spatial Digger Dec 19 '20 at 15:48
  • @SpatialDigger I know. You will need to build a hierarchical object. In general I would do it the old-fashioned way, by studying the structure of the JSON you have, identifying how the levels would build on top of each-other and implement a function for that purpose. reduce results in a single output, maybe it can be used to build a hierarchy, but it will not work out-of-the-box. The actual array you have is too long to analyze as part of an answer. If you can simplify it and explain how you would like to build a sample, then I would take another look. – Lajos Arpad Dec 19 '20 at 17:00
  • I know you have done some explanation, but it can only be comprehended after a long analysis, which I did not do. So, I think you need to refactor your JSON in the question to be more readable and simplify the problem. We have a nested repeating pattern, so if you figure out how the first pattern can be build into an object's children, then it will be easy to figure the next level. – Lajos Arpad Dec 19 '20 at 17:02
  • Apologies for the complexity of the question, I've condensed the json down to the key attributes I found this fiddle which nearly works I think. http://jsfiddle.net/1f8nx7gc/1/ – Spatial Digger Dec 19 '20 at 17:31
  • Ok, this sorts out the class, now to adapt this so it repeats over the other items http://jsfiddle.net/1f8nx7gc/2/ – Spatial Digger Dec 19 '20 at 17:43
  • And now the general structure is sorted, it hasn't deleted the higher items and they remain undefined. http://jsfiddle.net/1f8nx7gc/3/ – Spatial Digger Dec 19 '20 at 18:29
  • @SpatialDigger please excuse me for the further question. I think the best way we could solve this is if you create a JSFiddle, where you have a JSON input, have the best code which generates an output which is not yet right and an exact output that would be correct. I think if we had those solid grounds for the question, then I might be able to help you out further. – Lajos Arpad Dec 20 '20 at 14:39
  • I think this is about right, I've added `sheep` as an example it will be extended, also this is for `Mamilia` so there could also be birds `Aves` etc at the kingdom level etc. https://jsfiddle.net/cfwyvzq5/ – Spatial Digger Dec 20 '20 at 23:49
  • @SpatialDigger thanks for the answer. Apparently the fiddle you have sent had some syntax errors in the JSON. I have done some fixes, see at https://jsfiddle.net/083qczkd/. Is this JSON looking like the one you need to achieve? Thanks! – Lajos Arpad Dec 21 '20 at 09:07
  • Thanks for the edit, yes that looks right to me! – Spatial Digger Dec 21 '20 at 16:21
  • @SpatialDigger you can go backwards in the array starting from the end and wrap the elements more to the left around the elements to their right, like here: https://jsfiddle.net/5szp9gtL/. Maybe we could try this code with your full input instead of the test input I have been using. – Lajos Arpad Dec 22 '20 at 09:58
  • sorry, can't focus, just recovering from covid. The code in the op should be enough for testing. Will check in again when with it again. – Spatial Digger Dec 23 '20 at 23:47