Reputation: 39
I have a nested array of objects as follows:
result = [
[{"country":"France","continent":"Europe","name":"street a"}],
[{"country":"Brazil","continent":"South America", "name":"street b"},{"country":"Brazil","continent":"South America", "name":"street c"}],
[{"country":"Germany","continent":"Europe","name":"street d"}]
]
Note that this was generated from an array of objects after sorting, using continent and then the country keys.
I want to dynamically generate a list as follows (continent and then countries) and add it to a div
:
Each time the countries and the continents returned would be different but the result array would be grouped in continent,country order.
Since I am new to HTML, CSS and JavaScript, I am not able to figure out how to dynamically create this list. So far I have managed to generate a unique list of only the continents using :
for(var i=0;i<result.length;i++){
if($('#continent-list li').filter(function(){
return $(this).text() == result[i][0].continent;
}).length == 0){
$('#continent-list').append('<li>'+result[i][0].continent+'<ul></ul></li>');
}
ie., I iterate through the list and only bother to see the first element [result[i][0]) and add this to an unordered list called 'continent-list'. Each time I also check if the name already exists in the unordered list and if it does not, only then I add.
Upvotes: 3
Views: 1977
Reputation: 22473
This isn't the simplest requirement / request in the world, but it's not particularly convoluted, either. It bothers me that it takes so much code to solve. I know of no libraries that make this problem trivial, but two that come in very handy:
First, your records are excessively nested. To make them convenient to render, they should be flattened. Underscore can do this in one line:
var flat = _.flatten(result);
That isn't so hard in plain JS:
var flat = [];
result.forEach(sublist => {
sublist.forEach(item => flat.push(item));
});
But it's nice to be able to "do it in one move" and move on.
Then you want several levels of nesting. Underscore has a nice groupBy
feature, but it's single-level. There are other multi-level extensions out there, but that's more fussing around. D3, however, manages the nesting very easily:
var nested = d3.nest()
.key(d => d.continent)
.key(d => d.country)
.entries(flat);
Then the nested data structure needs to be rendered to HTML. You can do that to HTML directly, but that's kind of messy. Thankfully, browsers have good tools for managing the DOM, aka their internal representation of HTML. D3 is great at manipulating it.
function nested2html(e, n, level) {
level = level || 0;
if (typeof e === 'string') e = d3.select(e);
var list = e.append(level ? 'ul' : 'ol')
.attr('class', 'level' + (level+1));
var leafNode = !n[0].key;
n.forEach(nitem => {
if (leafNode) {
list.append('li').text(nitem.name);
} else {
var li = list.append('li');
li.append('span').text(nitem.key);
nested2html(li, nitem.values, level + 1);
}
});
}
// now add it to the document
nested2html('body', nested);
This isn't particularly complicated as recursive routines go, and it renders out into well-structured, pretty easily styled HTML:
<ol class="level1">
<li>
<span>Europe</span>
<ul class="level2">
<li>
<span>France</span>
<ul class="level3">
<li>street a</li>
</ul>
</li>
<li>
<span>Germany</span>
<ul class="level3">
<li>street d</li>
</ul>
</li>
</ul>
</li>
<li>
<span>South America</span>
<ul class="level2">
<li>
<span>Brazil</span>
<ul class="level3">
<li>street b</li>
<li>street c</li>
</ul>
</li>
</ul>
</li>
</ol>
A JSFiddle with the code running, and some optional CSS showing how the result can be styled.
I only wish there were a way to do this more concisely.
Upvotes: 0
Reputation: 32174
i think most of the work can be done using the array reduce function ;)
Step 1: we will convert your array to the structure you need:
var result = [
[{"country": "France", "continent": "Europe", "name": "street a"}],
[{"country": "Brazil", "continent": "South America", "name": "street b"}, {
"country": "Brazil",
"continent": "South America",
"name": "street c"
}],
[{"country": "Germany", "continent": "Europe", "name": "street d"}]
];
// fatten the arrays into single array
var continents = result.reduce(function (a, b) {
return a.concat(b);
});
// group by continent
continents = continents.reduce(function (a, b) {
if (a[b.continent]) {
a[b.continent].push(b);
} else {
a[b.continent] = [b];
}
return a;
}, {});
// group by country
Object.keys(continents).forEach(function (continent) {
continents[continent] = continents[continent].reduce(function (a, b) {
if (a[b.country]) {
a[b.country].push(b);
} else {
a[b.country] = [b];
}
return a;
}, {});
});
now this is how your continents
data object should look like:
{
"Europe": {
"France": [
{
"country": "France",
"continent": "Europe",
"name": "street a"
}
],
"Germany": [
{
"country": "Germany",
"continent": "Europe",
"name": "street d"
}
]
},
"South America": {
"Brazil": [
{
"country": "Brazil",
"continent": "South America",
"name": "street b"
},
{
"country": "Brazil",
"continent": "South America",
"name": "street c"
}
]
}
}
Step 2: we will convert this object into html elements using JQuery:
// create continents list
var $list = $('<ol id="continent-list"/>');
Object.keys(continents).forEach(function (continentName) {
// create continent
var $continent = $('<li class="continent"/>').append($("<span/>").text(continentName));
// add countries
var $countries = $('<ul class="country-list"/>');
var continent = continents[continentName];
Object.keys(continent).forEach(function (countryName) {
var $country = $('<li/>').append($("<span/>").text(countryName));
// add streets
var $streets = $('<ul class="street-list"/>');
var country = continent[countryName];
country.forEach(function (street) {
var $street = $('<li/>').text(street.name);
$streets.append($street);
});
$country.append($streets);
$countries.append($country);
});
$continent.append($countries);
// add continent to the list
$list.append($continent);
});
now this is how $list
should look like:
<ol id="continent-list">
<li class="continent"><span>Europe</span>
<ul class="country-list">
<li><span>France</span>
<ul class="street-list">
<li>street a</li>
</ul>
</li>
<li><span>Germany</span>
<ul class="street-list">
<li>street d</li>
</ul>
</li>
</ul>
</li>
<li class="continent"><span>South America</span>
<ul class="country-list">
<li><span>Brazil</span>
<ul class="street-list">
<li>street b</li>
<li>street c</li>
</ul>
</li>
</ul>
</li>
</ol>
Step 3 Just append it to the document, e.g:
$("body").append($list);
Upvotes: 0
Reputation: 386680
You could flat the array, sort by properties continent
, country
and street
and build all new lists when changing the groups.
var result = [[{ "country": "France", "continent": "Europe", "name": "street a" }], [{ "country": "Brazil", "continent": "South America", "name": "street b" }, { "country": "Brazil", "continent": "South America", "name": "street c" }], [{ "country": "Germany", "continent": "Europe", "name": "street d" }]],
flat = result.reduce(function (r, a) {
return r.concat(a);
}, []),
ol = document.createElement('ol'),
ul1, ul2;
flat.sort(function (a, b) {
return a.continent.localeCompare(b.continent) || a.country.localeCompare(b.country) || a.name.localeCompare(b.name);
});
flat.forEach(function (a, i, aa) {
var li;
if (!i || aa[i - 1].continent !== a.continent) {
li = document.createElement('li');
li.appendChild(document.createTextNode(a.continent));
ol.appendChild(li);
ul1 = document.createElement('ul');
ol.appendChild(ul1);
}
if (!i || aa[i - 1].country !== a.country) {
li = document.createElement('li');
li.appendChild(document.createTextNode(a.country));
ul1.appendChild(li);
ul2 = document.createElement('ul');
ul1.appendChild(ul2);
}
li = document.createElement('li');
li.appendChild(document.createTextNode(a.name));
ul2.appendChild(li);
});
document.getElementById('out').appendChild(ol);
<div id="out"></div>
Upvotes: 0
Reputation: 627
I'll let you handle the duplicates, but this should give you the general idea:
function displayData(continent, country, street) {
console.log(continent + ', ' + country + ', ' + street);
var output = $('#output');
var continentLI = document.createElement('li');
var continentTextNode = document.createTextNode(continent);
continentLI.appendChild(continentTextNode);
var countryUL = document.createElement('ul');
var countryLI = document.createElement('li');
var countryTextNode = document.createTextNode(country);
countryLI.appendChild(countryTextNode);
countryUL.appendChild(countryLI);
var streetUL = document.createElement('ul');
var streetLI = document.createElement('li');
var streetTextNode = document.createTextNode(street);
streetLI.appendChild(streetTextNode);
streetUL.appendChild(streetLI);
countryLI.appendChild(streetUL);
continentLI.appendChild(countryUL);
output.append(continentLI);
};
$.each(result, function(resultKey, resultValue) {
var country = resultValue;
$.each(country, function(countryKey, countryValue) {
var continent = countryValue.continent;
var country = countryValue.country;
var street = countryValue.name;
displayData(continent, country, street);
});
});
Upvotes: 0
Reputation: 631
var countries =[[{"country":"France","continent":"Europe","name":"street a"}], [{"country":"Brazil","continent":"South America", "name":"street b"}{"country":"Brazil","continent":"South America", "name":"street c"}], [{"country":"Germany","continent":"Europe","name":"street d"}]];
var modified = {};
for(var i =0; i<countries.length; i++){
var continent = countries[i];
for(var j =0;j<continent.length;j++){
var country = continent[j];
if(!modified[country.continent]){
modified[country.continent] ={}
}
if(!modified[country.continent][country.country]){
modified[country.continent][country.country] =[]
}
modified[country.continent][country.country].push(country);
}
}
var liText = "<ul>";
for (var key in modified) {
liText += "<li>"+key + "<ul>";
for (var country in modified[key]){
liText += "<li>"+country + "<ul>";
for(var i=0;i< modified[key][country].length;i++){
liText += "<li>"+modified[key][country][i].name + "</ll>";
}
liText += "</ul></li>";
};
liText += "</ul></li>";
};
liText += "</ul>"
console.log(liText);
document.getElementById('continent-list').innerHTML = liText;
Upvotes: 0
Reputation: 100
I think one way to do this is to loop through the objects in the array and create divs for the continents available while making sure no continent is created more than once then still using the same loop you can use something like
for(i=0;i<=results.length;i++){
if (result[i].continent == continent) //here talking about the present continent in the main loop{
//code to create new div for the current country....then same procedure for the street part....
}
}
Sorry i'm not sounding too jqueryish :D but really i hope you get the scope
Upvotes: 2