How to check redundancy in for loop - javascript

I am pulling some information from an API and one of my fields may or may not have duplicate content. How do I identify the redundancy and not print it on the screen if it's a duplicate?
Here's my loop, and "Description" is the field that sometimes has the same content as its previous sibling:
for (i = 0; i < data.length; i++) {
htmlString += '<li><h3 class="session-item">' + data[i].Title + '</h3>';
htmlString += '<p class="session-description">' + data[i].Description + '</p></li>';
}
Is this something I should check/compare with the previous sibling or is there a better way?
I tried a simple comparison but this didn't work:
var tempDesc = document.getElementsByClassName('session-description');
for (i = 0; i < data.length; i++) {
htmlString += '<li><h3 class="session-item">' + data[i].Title + '</h3>';
if(tempDesc === data[i].Description) {
htmlString += '</li>';
} esle {
htmlString += '<p class="session-description">' + data[i].Description + '</p></li>';
}
}

You already have the index so a simple solution is to get the last index with i - 1:
for (i = 0; i < data.length; i++) {
htmlString += '<li><h3 class="session-item">' + data[i].Title + '</h3>';
if (data[i].Description !== data[i - 1].Description) {
htmlString += '<p class="session-description">' + data[i].Description + '</p></li>';
} else {
htmlString += '</li>';
}
}
But you want to check if it data[i - 1] exists first:
if (data[i].Description !== (data[i - 1] && data[i - 1].Description)) {
If you want a shorter answer then you could use the more advanced optional chaining:
if (data[i].Description !== data[i - 1]?.Description) {

const isEqual = (...objects) => objects.every(obj => JSON.stringify(obj) === JSON.stringify(objects[0]));
data.reduce((x,y) => (x.filter(x => isEqual(x, y)).length > 0) ? x : x.concat([y]), []);
then, use data with every way you want, all elements are unique :)

Presuming your dataset looks something like this:
const apiResponse = [
{ Title: 'Sanity and Tallulah', Description: 'Description 1' },
{ Title: 'Zita the Space Girl', Description: 'Description 2' },
{ Title: 'Lumberjanes', Description: 'Description 1' },
];
...and I am understanding your goal correctly that you want to suppress duplicate descriptions but keep the titles, I would suggest one approach is to use a Set as you are looping to add to the DOM, and check to see if the description is already present in the Set before writing it. Something like:
const apiResponse = [
{ Title: 'Sanity and Tallulah', Description: 'Description 1' },
{ Title: 'Zita the Space Girl', Description: 'Description 2' },
{ Title: 'Lumberjanes', Description: 'Description 1' },
];
const descriptions = new Set();
for (let item of apiResponse) {
document.body.append(item.Title)
document.body.append(document.createElement('br'))
if (!descriptions.has(item.Description)) {
descriptions.add(item.Description);
document.body.append(item.Description)
document.body.append(document.createElement('br'))
document.body.append(document.createElement('br'))
}
}
This approach would allow a single loop and avoid having to do comparisons in/against the DOM as a source of truth.
Another option (explored in other answers already) is to first loop the responses and eliminate duplicate values and then loop to push to the DOM.

Related

Array list display online one result from json

I wrote this code and it works:
function getJsonResult(retrieve) {
var result = retrieve.results;
for (var i = 0; i < result.length; i++) {
responseJson.push({ id: result[i].id, title: result[i].title });
var search = '<a id="' + result[i].id + '">' + result[i].title + '</a><br/>';
document.write(search);
}
}
When I tried to display the results in a div, I change the last line with:
$("#divId").html(search);
But it only displays the first result. How can I make the whole list appear?
That happened because you're overriding the search variable in every iteration :
var search = '<a id="' + result[i].id + '">' + result[i].title + '</a><br/>';
You need to declare the search variable outside of the loop then append the string in every iteration like :
function getJsonResult(retrieve) {
var result = retrieve.results;
var search = "";
___________^^^^
for (var i = 0; i < result.length; i++) {
responseJson.push({ id: result[i].id, title: result[i].title });
var search += '<a id="' + result[i].id + '">' + result[i].title + '</a><br/>';
___________^^
document.write(search);
}
}
Then finally you could put your variable content to the div :
$("#divId").html(search);
$('#divId').append(search);
This appends the element included in search to the div element.

javascript logic to append/prepend div on html template

I wrote a very basic web app that pulls recipe data back from an API. The data is rendered via being pushed to an html template defined in the javascript file. The layout is controlled via a float-grid in CSS.
The code portion that renders the result and pushes to the template:
function displayRecipeSearchData(data) {
var results = ' ';
if (data.hits.length) {
data.hits.forEach(function(item) {
results += template.item(item);
});
}
else {
results += '<p> No results </p>';
}
$('#js-search-results').html(results);
}
The html template through which responses are displayed:
const template = {
item: function(item) {
return '<div class ="col-4">' +
'<div class ="result">' +
'<div class="recipelabel">' +
'<div class="reclist">' + item.recipe.ingredientLines + '</div><!-- end reclist -->' +
'<p class="label">' + item.recipe.label + '</p>' +
'<div class="thumbnail">' +
'<a href="'+ httpsTransform(item.recipe.url) + '" target="_blank">' +
'<img src="' + item.recipe.image + '"alt="' + item.recipe.label + '">' +
'</a>' +
'<div class="recipesource">' +
'<p class="source">' + item.recipe.source + '</p>' +
'</div><!-- end recipesource -->' +
'</div><!-- end thumbnail -->' +
'</div><!-- end recipelabel -->' +
'</div><!-- end result -->' +
'</div><!-- end col-4 -->';
}
};
I am trying to change the logic in the displayRecipeSearchData function such that, for each group of three results, a <div></div> surrounds the block of three results. This is so the rows/columns always work in the flex grid. I have tried several ways but have yet to get the syntax/logic correct. Would an if statement nested in the existing statement be effective?
if(i % 3 === 0 ){ results. += '<div class="row">''</div>'}
Any suggestions would be appreciated.
You could use another variable for storing one row of HTML:
function displayRecipeSearchData(data) {
var results = ' ', row = '';
if (data.hits.length) {
data.hits.forEach(function(item, i) {
row += template.item(item);
if (i % 3 == 2) { // wrap row and add to result
results += '<div class="row">' + row + '</div>';
row = '';
}
});
if (row.length) { // flush remainder into a row
results += '<div class="row">' + row + '</div>';
}
}
else {
results += '<p> No results </p>';
}
$('#js-search-results').html(results);
}
you are definitely doing this the hard way in my opinion.
instead of manually writing the template as a string and trying to inject the string at the right place (potentially creating invalid html) you should use javascripts built-in element creation. also it'll be more modular to create children in their own functions. It will also be much easier to use a function instead of an object to hold your object creator. My version may have a lot more code, but it will be much easier to modify in the long run
const Itemizer = function(){
this.items = [];
const createEl = function(elType, classes, attributes, text, html){
let el = document.createElement(elType)
for(let i = 0; i < classes.length; i++){
el.classList.add(classes[i]
}
for(let attr in attributes){
el.setAttribute(attr, attributes[attr])
}
if(text){
el.innerText = text
}
if(html){
el.innerHTML = html
}
return el
};
const createThumbnail = function(url, image, alt, source){
let thumbnail = createEl("DIV", ["thumbnail"]),
link = createEl("A", [], {href: httpsTransform(url)}),
img = createEl("IMG", [], {src: image, alt: label});
rSource = createRecipeSource(source)
link.appendChild(img);
thumbnail.appendChild(link);
thumbnail.appendChild(rSource)
return thumbnail
};
const createRecipeSource = function(source){
let wrapper = createEl("DIV", ["recipe-source"]);
wrapper.appendChild(createEl("P", ["source"], {}, source))
return wrapper
}
const createRecipeLabel = function({
recipe: {
ingredientLines,
label,
url,
source
}
}){
let labelWrapper = createEl("DIV", ["recipe-label"),
ingredients = createEl("DIV", ["rec-list"], {}, false, ingredientLines),
recipeLabel = createEl("P", ["label"], {}, label),
thumbnail = createThumbnail(url, image, label, source)
labelWrapper.appendChild(ingredients)
labelWrapper.appendChild(recipeLabel)
labelWrapper.appendChild(thumbnail)
return labelWrapper
}
const createNewItem = function(data){
let columnWrapper = createEl("DIV", ["col-4"]),
result = createEl("DIV", ["result"]),
label = createRecipeLabel(data)
columnWrapper.appendChild(result)
result.appendChild(label)
this.items.push(columnWrapper)
return columnWrapper
}.bind(this)
const getItems = function(){
return this.items
}.bind(this)
const getRows = function(){
const rows = []
let row;
for(let i = 0; i < this.items.length; i++){
const item = this.items[i]
if(i % 3 === 0){
row = createEl("DIV", ["row"])
rows.push(row)
}
row.appendChild(item)
}
return rows;
}.bind(this)
return {
add: createNewItem,
get: getItems,
rows: getRows
}
}
You can then use the function like so:
const template = new Itemizer()
function displayRecipeSearchData(data) {
let rows
if (data.hits.length) {
for(let i = 0; i < data.hits.length; i++){
template.add(data.hits[i])
}
rows = template.rows()
} else {
const p = document.createElement("P")
p.innerText = "No Results")
rows = [p]
}
const resultsWrapper = document.getElementById("js-search-results");
for(let i = 0; i < rows.length; i++){
resultsWrapper.appendChild(rows[i])
}
}
it's also good form to separate css classes with hyphens, so I replaced a few of your class names to reflect that
It's also important to note that you don't actually need more than 1 row. if you wrap all of your items in one row section columns will automatically overflow to the next row when they hit the grid limit
My last note is never use target blank. it goes against proper UX, and creates security holes in your application. if your users need to open in a new tab they can hold ctrl or click "open in new tab"

How to sort a multidimensional array using items in Javascript [duplicate]

This question already has answers here:
Sort array of objects by string property value
(57 answers)
Closed 7 years ago.
Basically all I want is to sort this array based on each item that is shown below, with the exception of the "Action' and 'Thumb Image' ones. So the way I have it set up is that the header for each of rows is a link, and when that link is clicked the list will be sorted based on what was clicked. So for example, if Title is clicked, then I want to have a "titleSort()" function that will sort based on title. I have no idea how to accomplish this, so any help is much appreciated. I was hoping that VideoList.sort(Title) would work, for example.
Thanks,
JS
for(var i = 0; i<VideoList.length; i++) {
content += "<tr>";
content += "<td width='20%'><a href='https://www.youtube.com/watch?v=" + VideoList[i].VideoID + "'onclick='playVideo("+i+")'>" + "<img src ='https://i.ytimg.com/vi/" + VideoList[i].VideoID + "/hqdefault.jpg' width=175 height=130></a></td>";
content += "<td>" + VideoList[i].Title + "</td>";
content += "<td>" + VideoList[i].VideoID + "</td>";
content += "<td>" + VideoList[i].DateUploaded + "</td>";
content += "<td>" + VideoList[i].Category+ "</td>";
content += "<td>" + VideoList[i].Time+ "</td>";
content += "<td width='20%'>" + VideoList[i].Action + "</td>";
content += "</tr>";
You can use sort to sort VideoList according to title this code may work for you
VideoList.sort(function(a,b){
return a.Title > b.Title;
});
I agree with #manishrw about lodash. AND any number of libraries would make this easier - like jQuery and Angular. There are a ton of table-specific libraries out there that have sort function built in. However, I built it to show how you could do it, including re-building the table once it's sorted. To do that I had to create the array with mock data. Here's a jsfiddle:
https://jsfiddle.net/mckinleymedia/c02nqdbz/
And here's the code:
<div id="target"></div>
<script>
var VideoList = [],
content,
fields = ["Title", "VideoID", "DateUploaded", "Category", "Time", "Action"],
num = 10,
sortField = "Title",
sortDirection = 1,
compare = function(a, b) {
if (a[sortField] < b[sortField]) return -1 * sortDirection;
if (a[sortField] > b[sortField]) return 1 * sortDirection;
return 0;
},
sortArray = function(field) {
if( sortField === field ) sortDirection = -1 * sortDirection;
sortField = field;
VideoList.sort(compare);
buildTable();
},
creatVideos = function() {
for (var x = 0; x < num; x++) {
var video = {},
z = Math.floor(Math.random() * 200);
for (var i in fields) {
if(fields[i]==='VideoID') {
video[fields[i]] = z;
} else {
video[fields[i]] = fields[i] + "-" + z;
}
}
VideoList.push(video);
}
},
buildTable = function() {
content = "<table>";
content += "<tr>";
content += "<th>image</th>";
for (var x in fields) {
content += "<th class='field field-" + fields[x] + "' onclick='sortArray(\"" + fields[x] + "\")'>" + fields[x] + "</th>";
}
content += "</tr>";
for (var i in VideoList) {
content += "<tr>";
content += "<td width='20%'><a href='https://www.youtube.com/watch?v=" + VideoList[i].VideoID + "'onclick='playVideo(" + i + ")'>" + "<img src ='https://i.ytimg.com/vi/" + VideoList[i].VideoID + "/hqdefault.jpg' width=175 height=130></a></td>";
for (var x in fields) {
content += "<td>" + VideoList[i][fields[x]] + "</td>";
}
content += "</tr>";
}
content += "</table>";
document.getElementById('target').innerHTML = content;
};
creatVideos();
buildTable();
</script>
Here's a generic function for you
function sortBy(list, field) {
return list.sort(function(a,b) {
return a[field] < b[field];
});
}
sortBy(VideoList, 'Title');
Warning: sortBy will mutate the list input
You could also make it take a comparator so you control the 'direction' of the sort
// you you need to return -1, 0, or 1 for the sort to work reliably
// thanks, #torazaburo
function compareAsc(a,b) {
if (a < b) return -1;
else if (a > b) return 1;
else return 0;
}
function compareDesc(a,b) {
return compareAsc(a,b) * -1;
}
function sortBy(list, field, comparator) {
return list.sort(function(a,b) {
if (comparator instanceof Function)
return comparator(a[field], b[field]);
else
return compareAsc(a[field], b[field]);
});
}
// default sort ascending
sortBy(VideoList, 'Title');
// sort descending
sortBy(VideoList, 'Title', compareDesc);
Use Lodash library. It's easy to use and efficient in run-time. It has a function sortBy, which can be used to sort a collection based on they key you provide.
P.S. Lodash is my goto Library for any operation to be performed on any collection.

Need index number of item in array on hover

I'm relatively new to JavaScript and I'm working on what should be a simple project. I'm stuck though and I'd love some opinions on ways to get a solution.
I have a short products array like:
var products = [
{
name: "paper",
price: 2.00,
description: "White College-ruled Paper, 100 sheets",
location: "Aisle 5"
},
{
name: "pens",
price: 5.00,
description: "10 Pack, Black Ink Ball Point Pens"
location: "Aisle 2"
},
{
name: "paper clips",
price: 0.50,
description: "Silver Paper Clips, 100 count"
location: "Aisle 6"
}
]
I'm looping through this array using JS and printing the results to the page in a DIV with id of "output".
function print(message) {
var outputDiv = document.getElementById('output');
outputDiv.innerHTML = message;
}
for (var i = 0; i < products.length; i += 1) {
product = products[i];
message += '<div class="col-md-4" id="prod-block">';
message += '<p id="prod-description">' + product.name + ' ' + product.description + '</p>';
message += '<h2 id="price">$' + product.price + '</h2>';
message += '</div>'
}
print(message);
All of this works just fine. I have my products on the page. Now, what I want is when the mouse hovers over any of the item divs, to show additional information (such as location) in a separate div.
My question is - how do you identify the index number of the item that is being hovered over? As of now, the index number only exists in my for loop and I can't figure out how to access it in a different function.
Again, my knowledge is limited, so I'm not sure if writing the HTML in a for loop is even the best way to do this. I really appreciate any advice or criticism!!
Here's something that should help.
I updated your list to include an id attribute and I used that to assign a data attribute to the div that is being created. On hover it looks for the data-prodid and displays that in the additional section.
Fiddle
var products = [{
id: 0,
name: "paper",
price: 2.00,
description: "White College-ruled Paper, 100 sheets",
location: "Aisle 5"
}, {
id: 1,
name: "pens",
price: 5.00,
description: "10 Pack, Black Ink Ball Point Pens",
location: "Aisle 2"
}, {
id: 2,
name: "paper clips",
price: 0.50,
description: "Silver Paper Clips, 100 count",
location: "Aisle 6"
}],
message = '';
function print(message) {
var outputDiv = document.getElementById('output');
outputDiv.innerHTML = message;
}
for (var i = 0; i < products.length; i += 1) {
product = products[i];
message += '<div data-prodid="' + product.id + '" class="col-md-4" id="prod-block">';
message += '<p id="prod-description">' + product.name + ' ' + product.description + '</p>';
message += '<h2 id="price">$' + product.price + '</h2>';
message += '</div>'
}
print(message);
$('.col-md-4').hover(function() {
$('#additional').html($(this).data('prodid'));
});
Also
The javascript you posted has an error in your products variable and message was never declared.
try to add a tip tool as and when you create the div with the index number you want.
<div title="The index number">
There is something really wrong in your code: the abuse of id. Many elements will have the id prod-block, prod-description but an id has to be unique.
Doing this, you can easily detect which element is hovered just by checking the id. There is multiple techniques to do that, if you want to learn jQuery this is really easy to start this way.
If you use jQuery, you could use data() to define the data attribute of the DOM element, but you shoudl also use jQuery to add this element to the DOM.
$.each(products, function(i, product) {
// Create DOM element
var _ = $('<div class="col-md-4" id="prod-block">'
+ '<p id="prod-description">' + product.name + ' ' + product.description + '</p>'
+ '<h2 id="price">$' + product.price + '</h2>'
+ '</div>');
// Set product data
_.data("product", product);
// Add element to the DOM
$("#output").append(_);
});
There are multiple options here, such as adding the data as an attribute of the element, but I believe your best option is to create the HTML elements explicitly so you can attach event listeners to them, then append them to the DOM; this would be instead of setting the innerHTML of the output div element to a string representation of the desired HTML.
var output = document.getElementById("output");
var hoverOutput = document.getElementById("hovertext");
for (var i = 0, len = products.length; i < len; i++) {
product = products[i];
var newDiv = document.createElement("div");
newDiv.className = "col-md-4";
newDiv.innerHTML = '<p class="prod-description">' + product.name + ' ' + product.description + '</p><h4 class="price">$' + product.price + '</h4>';
(function() {
var num = i;
var target = hoverOutput;
newDiv.onmouseover = function() {
target.innerHTML = num;
};
})();
output.appendChild(newDiv);
}
Check out the working example below:
var products = [{
name: "paper",
price: 2.00,
description: "blah blah",
location: "aisle 5"
}, {
name: "paper clips",
price: 0.5,
description: "blah bloo blab",
location: "aisle 6"
}];
var output = document.getElementById("output");
var hoverOutput = document.getElementById("hovertext");
for (var i = 0, len = products.length; i < len; i++) {
product = products[i];
var newDiv = document.createElement("div");
newDiv.className = "col-md-4";
newDiv.innerHTML = '<p class="prod-description">' + product.name + ' ' + product.description + '</p><h4 class="price">$' + product.price + '</h4>';
(function() {
var num = i;
var target = hoverOutput;
newDiv.onmouseover = function() {
target.innerHTML = num;
};
})();
output.appendChild(newDiv);
}
#hovertext {
font-weight: bold;
color: red;
min-height: 10px;
}
#output div {
border: 1px solid black;
}
.col-md-4{line-height:.2em;}
<div id="output"></div>
<div id="hovertext">Hover over an item to see its index here</div>

Trying to iterate over

I'm trying to iterate through a JSON object and wrap each item in the array in an li tag.
Here is my JSON structure
var classYear = {
"1921": [
'name1', 'name2', 'name3', 'name4'
],
"1933": [
'name5', 'name6', 'name7', 'name8'
],
"1943": [
'name9', 'name10', 'name11', 'name12', 'name13'
]
};
Here is my javascript
var container = document.getElementById('classYearContainer');
function classYearOutput(yClass, yId, listId, key, name) {
return '<div class="'+ yClass +'" id="'+ yId +'">' +
'<h4>' + key + '</h4>' +
'<ul id="'+ listId +'">' + name + '</ul>' +
'</div>';
}
function nameList(name) {
return '<li>' + name + '</li>';
}
for(var year in classYear) {
container.innerHTML += classYearOutput(
'category',
'y-' + year,
year + '-list',
year,
nameList(classYear[year])
);
}
With this setup my output is returning all the names in one li as opposed to separate li tags. Strangely, when I console.log I get the expected result, but when I return it puts all names in the same li.
Thanks for any help.
classYear[year] is an array, and you're passing it to the nameList function.
You have to iterate in that function as well
function nameList(name) {
var html = '';
for (var i=0; i<name.length; i++) {
html += '<li>' + name[i] + '</li>';
}
return html;
}
The issue is your passing the array into name list, you'll need to loop through the array and create an li for each name.
function nameList(names) {
var out = "";
names.forEach(function (el) {out += "<li>" + el + "</li>";});
return out;
}
Fiddle: http://jsfiddle.net/noy3dshx/
function nameList(names) {
return names.map(function(name) {
return '<li>' + name + '</li>';
});
}
Names is an array, so you need to iterate over that instead you are essentially calling the toString method of the array which is doing it's best and giving you a comma seperated list of the contents (e.g. console.log(['a', 'b', 'c'].toString()); or console.log('' + ['a', 'b', 'c']);)

Categories