How can I get multiple unique values from a JSON with JQuery? - javascript

I have this JSON:
var dataM = '[{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat03"}, {"category":"cat01","subcategory":"subcat03"},{"category":"cat01","subcategory":"subcat02"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat02"},{"category":"cat01","subcategory":"subcat05"}]';
I want to get unique values and build an HTML unordered list structure , so I make this:
var dataM = '[{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat03"}, {"category":"cat01","subcategory":"subcat03"},{"category":"cat01","subcategory":"subcat02"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat02"},{"category":"cat01","subcategory":"subcat05"}]';
var dataJSON = JSON.parse(dataM);
var arrayM = []; var html='';
for(i = 0; i< dataJSON.length; i++){
if(arrayM.indexOf(dataJSON[i].category) === -1){
arrayM.push(dataJSON[i].category);
}
}
html += '<ul>';
for(i = 0; i< arrayM.length; i++){
html += '<li>'+arrayM[i]+'</li>';
}
html += '</ul>';
$("#list").html(html);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="list"></div>
The problem is I only get the unique category values:
And I want to include the unique subcategory values of each category like this:
How can I fix it? I'd like to receive your help.

Instead off using an array, I'd recommend using an object so you can set each category on the key, push each subcategory into an array.
Then, if you only keep the unique subcats, you can use that array to show them:
var dataM = '[{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat03"}, {"category":"cat01","subcategory":"subcat03"},{"category":"cat01","subcategory":"subcat02"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat02"},{"category":"cat01","subcategory":"subcat05"}]';
var dataJSON = JSON.parse(dataM);
var arrayM = {};
var html='';
for(i = 0; i < dataJSON.length; i++) {
if (!arrayM[dataJSON[i].category]) {
arrayM[dataJSON[i].category] = [];
}
arrayM[dataJSON[i].category].push(dataJSON[i].subcategory);
}
html += '<ul>';
for (let [category, subcategorys] of Object.entries(arrayM)) {
html += '<li>'+category+'</li>';
html += '<ul>';
subcategorys = subcategorys.filter((v, i, a) => a.indexOf(v) === i);
for (i = 0; i < subcategorys.length; i++) {
html += '<li>'+subcategorys[i]+'</li>';
}
html += '</ul>';
}
html += '</ul>';
$("#list").html(html);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="list"></div>

For this you can make use of reducer Reducer
By making use of reducer you can run filter and map together.
var dataM = [{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat01"},{"category":"cat01","subcategory":"subcat03"}, {"category":"cat01","subcategory":"subcat03"},{"category":"cat01","subcategory":"subcat02"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat04"},{"category":"cat02","subcategory":"subcat02"},{"category":"cat01","subcategory":"subcat05"}];
result = dataM.reduce(function (r, a) {
r[a.category] = r[a.category] || [];
r[a.category].push(a.subcategory);
return r;
}, Object.create(null));
html = '<ul>';
Object.keys(result).map(keys => {
html += "<li>" + keys + ":"
html += "<ul>"
result[keys].map( data => {
html += "<li>"+data+"</li>"
})
html+="</ul>"
});
html +="</ul>"
$("#list").html(html)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="list"></div>

Related

Display message if array is empty

I'm displaying favorites from localStorage on a page and I'd like to display a message for people that don't have any favorites yet.
This is the div that displays the list which I'd like to repurpose to display the message below when there are no favorites:
<div id='favorites'></div>
And here is the JavaScript that normally shows the favorites:
var options = Array.apply(0, new Array(localStorage.length)).map(function (o, i){
return localStorage.key(i);
});
function makeUL() {
var LIs = '';
var noFavs = 'Hmm, you must\'ve not favorited anything yet. Maybe you\'ll like this one.';
var len = options.length;
if (len === 0) {
document.getElementById('nofavorites').innerHTML = noFavs;
} else {
for (i = 0; i < len; i += 1) {
LIs += '<li>' + options[i] + '</li>';
}
return '<ul>' + LIs + '</ul>';
}
}
document.getElementById('favorites').innerHTML = makeUL();
Right now it just shows undefined.
This is in your html:
<div id='favorites'></div>
<div id='nofavorites'></div>
Your javascript:
var options = Array.apply(0, new Array(localStorage.length)).map(function (o, i){
return localStorage.key(i);
});
function loadFavoriteHTML() {
var favoriteHtml = '';
var noFavs = 'Hmm, you must\'ve not favorited anything yet. Maybe you\'ll like this one.';
var len = options.length;
// Clear html lists
document.getElementById('favorites').innerHTML = '';
document.getElementById('nofavorites').innerHTML = '';
if (len === 0) {
document.getElementById('nofavorites').innerHTML = noFavs;
} else {
for (var i = 0; i < len; i++) {
favoriteHtml+= '<li>' + options[i] + '</li>';
}
var ulHtml= '<ul>' + favoriteHtml+ '</ul>';
document.getElementById('favorites').innerHTML = ulHtml;
}
}
loadFavoriteHTML();
Your code is show undefine because when you dont have favorites list you dont return anything in you makeUI function, and by default the return value is undefined in a function if you dont have return.
I changed your code to set the UI in the function because you edit 2 different div. there is others way to do it. this is a one way.
It's because the makeUL function doesn't return any value when there's no element in the options array.
You'll have to choose between: updating your element inside your function
OR getting the value to insert inside your HTML element returned by your function. But you shouldn't do both.
You might want to change your code into something like this?
var options = Array.apply(0, new Array(localStorage.length)).map(function (o, i) {
return localStorage.key(i);
});
function makeUL() {
var LIs = '';
var noFavs = 'Hmm, you must\'ve not favorited anything yet. Maybe you\'ll like this one.';
var len = options.length;
if (len === 0) {
document.getElementById('favorites').innerHTML = noFavs;
} else {
for (var i = 0; i < len; i += 1) {
LIs += '<li>' + options[i] + '</li>';
}
document.getElementById('favorites').innerHTML = '<ul>' + LIs + '</ul>';
}
}
makeUL();
Plus, you're targeting a nofavorites element that doesn't exist in your example.

JS for loop not working

I have a for loop which looks like this:
for (var i = 0; i < list.length; i++) {
It is looping through Firebase data in the database and returning all the data in the database.
However, I want it to only go up to the first 10 database items. So I changed the loop to:
for (var i = 0; i < 9; i++) {
But this fails to display any results when the there are less than 10 pieces of data in the database. However, if I set the number to however many objects I have in the database, for example 10 because I have 10 objects, it displays them all. But any less than this number and I just get a blank webpage.
Here is the webpage when I have 10 objects in my Firebase database:
And here it is when I remove one of those objects:
I have no idea why this is happening - The logic is correct - if i is less than 9 then display the data - But instead it only displays it when it equals 9.
Here is the full JS:
function refreshUI(list) {
var lis = '';
var lis2 = '';
var lis3 = '';
var lis4 = '';
for (var i = 0; i <= 9; i++) {
lis += '<li data-key="' + list[i].key + '" onclick="addText(event)">' + list[i].book + '</li>';
lis2 += genLinks(list[i].key, list[i].book)
};
for (var i = 10; i < list.length; i++) {
lis3 += '<li data-key="' + list[i].key + '" onclick="addText(event)">' + list[i].book + '</li>';
lis4 += genLinks(list[i].key, list[i].book)
};
document.getElementById('bookList').innerHTML = lis;
document.getElementById('bookList2').innerHTML = lis2;
document.getElementById('bookList3').innerHTML = lis3;
document.getElementById('bookList4').innerHTML = lis4;
};
function genLinks(key, bkName) {
var links = '';
links += '<img src="images/bin.png" style="width: 24px; height: 24px; transform: translateY(-7px); opacity: .4;"></img> ';
return links;
};
function del(key, bkName) {
var response = confirm("Are certain about removing \"" + bkName + "\" from the list?");
if (response == true) {
// build the FB endpoint to the item in movies collection
var deleteBookRef = buildEndPoint(key);
deleteBookRef.remove();
}
}
function buildEndPoint (key) {
return new Firebase('https://project04-167712.firebaseio.com/books/' + key);
}
// this will get fired on inital load as well as when ever there is a change in the data
bookList.on("value", function(snapshot) {
var data = snapshot.val();
var list = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
book = data[key].book ? data[key].book : '';
if (book.trim().length > 0) {
list.push({
book: book,
key: key
})
}
}
}
// refresh the UI
refreshUI(list);
});
If anybody has any help I'd greatly appreciate it!
When the list size is shorter than 10, you will get an error in the loop because you will eventually address a property (like key) that does not exist on list[i] (since it is undefined). If you would check the console, you would notice that this error is reported.
To fix this, change the condition of the first for loop like this:
for (var i = 0; i < Math.min(10, list.length); i++) {
This way, the loop will never iterate to an entry that does not exist. It will stop after 9 or after list.length-1 whichever comes first.
Alternatively, you can just put the two conditions with an && operator:
for (var i = 0; i < 10 && i < list.length; i++) {

How to display list on mouseover in javascript?

Hi i am trying to display list on mouse over if length is >0 then i am displaying count.On mouse over list i want to display list.But in my case list is always displaying when length is >o.How to display list only on mouse over?
what exactly i am trying is
if (feature.attributes.hasOwnProperty("ExceptionType")) {
var exceptionType = 'x,y';
if (exceptionType)
{ var activeExceptions = exceptionType.split(','); }
else
{ var activeExceptions = []; }
var item, items = [];
for (var i = 0; i < activeExceptions.length; i++) {
item = {};
item.activeexception = activeExceptions[i];
items.push(item);
}
var main = $("<ul>");
var str = "<ul>";
for (var i = 0; i < items.length; i++) {
str += "<li>" + items[i].activeexception + "</li>";
}
$('[rel="tooltip"]').tooltip();
main.html(str);
if (activeExceptions.length == 0) {
feature.attributes.Hoverlist = ".showme{display: none}";
feature.attributes.ShowExceptionType = "display:none";
}
else
feature.attributes.Hoverlist = ".showhim:hover .showme{ display : block;}";
feature.attributes.ShowExceptionType = "display:block";
feature.attributes.HoverContent = str;
feature.attributes.ExceptionCount = activeExceptions.length;
}
"description": "<div class='showhim' style='{ShowExceptionType}' ng-mouseover='alert('hi');'>{ExceptionCount}<div class='showme' style='{Hoverlist}'>{HoverContent}</div></div>",
The problem is i need to put my tooltip on top when popup loads.
$('[rel="tooltip"]').tooltip();

Nested unordered lists from nested array (recursive)

I have a bit of code that loops through nested array (it can be unlimited in depth) and I am trying to generate a treelike structure from it.. of basically nested unordered lists..
Here is my fiddle http://jsfiddle.net/aj9TC/6/
I collect items names in a function like this:
function get_list( a ) {
for (var i = 0; i < a.length; i++) {
console.log( i + "---" + a[i].name );
$(".mod-list ul").append('<li>' + a[i].name + '</li>');
get_list( a[i].group );
}
}
get_list( mods.group );
My sample html is simple
<h4>Nested List</h4>
<div class="mod-list">
<ul class="list">
</ul>
</div>
I currently append all items as li in a single unordered list,, but I need to create nested unordered lists that reflect the array nesting accurately.
Can someone help me out with this please.
Thanks!
What about this:
function get_list( a, $parent ) {
var newUl = $("<ul></ul>");
for (var i = 0; i < a.length; i++) {
if (a[i]) {
newUl.append('<li>' + a[i].name + '</li>');
if (a[i].group)
get_list( a[i].group, newUl );
}
}
$parent.append(newUl);
}
get_list( mods.group, $(".mod-list"));
Here's a working fiddle: http://jsfiddle.net/aj9TC/7/
Another solution that doesn't leave empty <ul>'s
I also like to build up all my html and add it to the DOM once, instead of adding lots of little snippets.
function get_list( a, str ) {
str += '<ul>';
for (var i = 0; i < a.length; i++) {
str += '<li>' + a[i].name;
if (a[i].group && a[i].group.length) str += get_list( a[i].group, '' );
str += '</li>';
}
str += '</ul>';
return str;
}
$(".mod-list").append(get_list( mods.group, ''));
http://jsfiddle.net/aj9TC/8/
Here's an approach that also builds all the html in array of strings and only does one append when done. Join array when ready to insert.
$(function(){
var $list=$(get_list(mods.group,[]).join('')).addClass('list')
$('.mod-list').append($list)
});
function get_list(data, strArray) {
strArray.push('<ul>');
$.each(data, function(i, val) {
strArray.push('<li>' + val.name);
if (val.group) {
get_list(val.group,strArray);
}
strArray.push('</li>');
});
strArray.push('</ul>');
return strArray;
}
DEMO

two delimiters output formatting javascript

I thought this would be easier, but running into a weird issue.
I want to split the following:
theList = 'firstword:subwordone;subwordtwo;subwordthree;secondword:subwordone;thirdword:subwordone;subwordtwo;';
and have the output be
firstword
subwordone
subwordtwo
subwordthree
secondword
subwordone
thirdword
subwordone
subwordtwo
The caveat is sometimes the list can be
theList = 'subwordone;subwordtwo;subwordthree;subwordfour;'
ie no ':' substrings to print out, and that would look like just
subwordone
subwordtwo
subwordthree
subwordfour
I have tried variations of the following base function, trying recursion, but either get into infinite loops, or undefined output.
function getUl(theList, splitOn){
var r = '<ul>';
var items = theList.split(splitOn);
for(var li in items){
r += ('<li>'+items[li]+'</li>');
}
r += '</ul>';
return r;
}
The above function is just my starting point and obviously doesnt work, just wanted to show what path I am going down, and to be shown the correct path, if this is totally off base.
It seems you need two cases, and the difference between the two is whether there is a : in your string.
if(theList.indexOf(':') == -1){
//Handle the no sublist case
} else {
//Handle the sublist case
}
Starting with the no sublist case, we develop the simple pattern:
var elements = theList.split(';');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
//Add your element to your list
}
Finally, we apply that same pattern to come up with the implementation for the sublist case:
var elements = theList.split(';');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
if(element.indexOf(':') == -1){
//Add your simple element to your list
} else {
var innerElements = element.split(':');
//Add innerElements[0] as your parent element
//Add innerElements[1] as your child element
//Increment i until you hit another element with ':', adding the single elements each increment as child elements.
//Decrement i so it considers the element with the ':' as a parent element.
}
}
Keep track of the current list to add items to, and create a new list when you find a colon in an item:
var baseParent = $('ul'), parent = baseParent;
$.each(theList.split(';'), function(i, e) {
if (e.length) {
var p = e.split(':');
if (p.length > 1) {
baseParent.append($('<li>').append($('<span>').text(p[0])).append(parent = $('<ul>')));
}
parent.append($('<li>').text(p[p.length - 1]));
}
});
Demo: http://jsfiddle.net/Guffa/eWQpR/
Demo for "1;2;3;4;": http://jsfiddle.net/Guffa/eWQpR/2/
There's probably a more elegant solution but this does the trick. (See edit below)
function showLists(text) {
// Build the lists
var lists = {'': []};
for(var i = 0, listKey = ''; i < text.length; i += 2) {
if(text[i + 1] == ':') {
listKey = text[i];
lists[listKey] = [];
} else {
lists[listKey].push(text[i]);
}
}
// Show the lists
for(var listName in lists) {
if(listName) console.log(listName);
for(var j in lists[listName]) {
console.log((listName ? ' ' : '') + lists[listName][j]);
}
}
}
EDIT
Another interesting approach you could take would be to start by breaking it up into sections (assuming text equals one of the examples you gave):
var lists = text.match(/([\w]:)?([\w];)+/g);
Then you have broken down the problem into simpler segments
for(var i = 0; i < lists.length; i++) {
var listParts = lists[i].split(':');
if(listParts.length == 1) {
console.log(listParts[0].split(';').join("\n"));
} else {
console.log(listParts[0]);
console.log(' ' + listParts[1].split(';').join("\n "));
}
}
The following snippet displays the list depending on your requirements
var str = 'subwordone;subwordtwo;subwordthree;';
var a = []; var arr = [];
a = str;
var final = [];
function split_string(a){
var no_colon = true;
for(var i = 0; i < a.length; i++){
if(a[i] == ':'){
no_colon = false;
var temp;
var index = a[i-1];
var rest = a.substring(i+1);
final[index] = split_string(rest);
return a.substring(0, i-2);
}
}
if(no_colon) return a;
}
function display_list(element, index, array) {
$('#results ul').append('<li>'+element+'</li>');
}
var no_colon_string = split_string(a).split(';');
if(no_colon_string){
$('#results').append('<ul><ul>');
}
no_colon_string.forEach(display_list);
console.log(final);
working fiddle here

Categories