Need index number of item in array on hover - javascript

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>

Related

How to check redundancy in for loop

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.

Skycons/Dark Sky Forecast API, displaying animated icons for dynamically created DIVs

As my first app working with an API I've decided to use the Dark Sky API. I'm trying to add animated Skycons for each of the forecast days I generate from my JS file. I've read the other posts on StackOverflow regarding Skycon issues but have made no progress on my own.
I've changed my initJS so that is looks for the weather type by class name, but still have been unsuccessful in appending the icons.
My script in the HTML:
<script>
var icons = new Skycons({"color": "#fff"}),
list = [
"clear-day", "clear-night", "partly-cloudy-day",
"partly-cloudy-night", "cloudy", "rain", "sleet", "snow", "wind",
"fog"
],
i;
for(i = list.length; i--; ) {
var weatherType = list[i],
elements = document.getElementsByClassName( weatherType );
for (e = elements.length; e--;){
icons.set( elements[e], weatherType );
}
}
icons.play();
How I create the forecast divs that hold the icons:
//HTML to append to document
var html =
'<div class="forecast-list"><ul class="list">' +
'<li class="item" id="day">' + forecastDay + '</li>' +
//The canvas tag is where the SKYCON should show up
'<li class="item"><canvas class="' + forecastIcon + '" width="128" height="128"></canvas></li>' +
'<li class="item forecastTemp" id="max">' + forecastMax + '</li>' +
'<li class="item forecastTemp" id="min">' + forecastMin + '</li>' +
'</ul></div><br><br>';
//Append HTML to document
$('.forecast').append(html);
A link to my codepen: http://codepen.io/DDD37/pen/GozGGx
A link to Skycons for refrence: https://github.com/darkskyapp/skycons/blob/master/skycons.js
I found where I went wrong. I was creating the icons in the wrong spot. This code...
var icons = new Skycons({"color": "#111"}),
list = [
"clear-day", "clear-night", "partly-cloudy-day",
"partly-cloudy-night", "cloudy", "rain", "sleet", "snow", "wind",
"fog"
],
i;
for(i = list.length; i--; ) {
var weatherType = list[i],
elements = document.getElementsByClassName( weatherType );
console.log(elements);
for (e = elements.length; e--;){
icons.set( elements[e], weatherType );
}
}
icons.play();
...needed to be placed inside the AJAX call.

Javascript print() method not working

I have a simple program that stores student objects in an array and then print the students info to the webpage. I have a print method in my program which takes a parameter and access the div element and then sets elements inner html to the parameter passed to print the message. Please consider the code snippet to see where the document.write method working fine but not the print method.
var student;
var message = "";
var students =
[
{
name: "Alice",
track: "IOS",
achievements: "Bronze",
points: 20400
},
{
name: "Linda",
track: "Front End Development",
achievements: "Silver",
points: 26000
}
];
function print( msg )
{
var output = document.getElementById("output");
output.innerHTML = msg;
}
for( var i = 0; i < students.length; i++)
{
student = students[i];
message = "<h2> Student : " + student.name + "</h2>";
message += "<p> Track : " + student.track + "</p>";
message += "<p> Achievements : " + student.achievements + "</p>";
message += "<p> points : " + student.points + "</p>";
// print(message);
document.write(message);
}
<div id="output">
</div>
It's because your print method replaces the entire output div. So each iteration of the loop overwrites the previous ones output.
You can change:
output.innerHTML = msg;
to:
output.innerHTML += msg;
and it will work correctly.
It works just fine you just need to respect the text currently in the div.
I added a += to the innerHtml call to append the new text
var student;
var message = "";
var students =
[
{
name: "Alice",
track: "IOS",
achievements: "Bronze",
points: 20400
},
{
name: "Linda",
track: "Front End Development",
achievements: "Silver",
points: 26000
}
];
function print( msg )
{
var output = document.getElementById("output");
output.innerHTML += msg;
}
for( var i = 0; i < students.length; i++)
{
student = students[i];
message = "<h2> Student : " + student.name + "</h2>";
message += "<p> Track : " + student.track + "</p>";
message += "<p> Achievements : " + student.achievements + "</p>";
message += "<p> points : " + student.points + "</p>";
print(message);
// document.write(message);
}
<div id="output">
</div>
In this case, your print method is substituting the innerHTML of output each time you call it.
Therefore, if you call print inside a loop, the only displayed result will be the last item on the loop.
If you add a + to the method, you will get what you want. You will need a method to clear the div later on, though.
var student;
var message = "";
var students = [{
name: "Alice",
track: "IOS",
achievements: "Bronze",
points: 20400
},
{
name: "Linda",
track: "Front End Development",
achievements: "Silver",
points: 26000
}
];
function print(msg) {
var output = document.getElementById("output");
output.innerHTML += msg;
}
for (var i = 0; i < students.length; i++) {
student = students[i];
message = "<h2> Student : " + student.name + "</h2>";
message += "<p> Track : " + student.track + "</p>";
message += "<p> Achievements : " + student.achievements + "</p>";
message += "<p> points : " + student.points + "</p>";
print(message);
}
<div id="output">
</div>

Trouble passing entire two dimensional array as parameters in JavaScript/jQuery

I am trying to generate divs around each of the elements of a two dimensional array using the methods below. So far the code only outputs the last 3 elements in the array (the 3 elements of third nested array). I am passing the array elements as parameters using .apply. How could I modify this to output each element of the array catArray in order? And why would it only pass the last 3 as it is? Any advice would be appreciated, I am trying to understand this better. I have spent hours on this, hopefully someone can help.
Here is a codepen:
http://codepen.io/anon/pen/kzEdK
function cats(catName, catFur, catEyes) {
$("#row").html('<div>' + catName + '</div>' + '<div>' + catFur + '</div>' + '<div>' + catEyes + '</div>');
}
var catArray = [
["fluffy", "soft", "green"],
["mittens", "coarse", "fire"],
["wiskers", "none", "grey"]
];
function catGenerator() {
for (var i = 0; i < catArray.length; i++) {
var blah = catArray[i];
cats.apply(this, blah);
}
}
catGenerator();
You probably want something like:
function cats(catName, catFur, catEyes) {
// note the difference (append() instead of html())
$("#row").append('<div>' + catName + '</div>' + '<div>' + catFur + '</div>' + '<div>' + catEyes + '</div>');
}
function catGenerator() {
$("#row").html(""); // in case you wish to call catGenerator() multiple times, clear the row before appending to it
for (var i = 0; i < catArray.length; i++) {
var blah = catArray[i];
cats.apply(this, blah);
}
}
It shows only the last 3 elements because $("#row").html("...") overwrites the contents of #row three times and the value set in the last iteration remains visible. I fixed that by replacing html() with append(), which does what you want.
The problem is that you have $("#row").html() which will replace the markup within the div tag after each iteration. Consider using $("#row").append()
function cats(catName, catFur, catEyes) {
$("#row").append('<div>' + catName + '</div>' + '<div>' + catFur + '</div>' + '<div>' + catEyes + '</div>');
}
var catArray = [
["fluffy", "soft", "green"],
["mittens", "coarse", "fire"],
["wiskers", "none", "grey"]
];
function catGenerator() {
for (var i = 0; i < catArray.length; i++) {
var blah = catArray[i];
cats.apply(this, blah);
}
}
catGenerator();

store ID from listview in localStorage

I've a listview created by a loop:
for(var i = 0; i < data.length; i++) {
var garage = data[i];
$("#content-p").append(
"<ul data-role=\"listview\">" +
"<li><a href=\"#\" id=\"submitID\" >" +
"<h2>"+garage.location+"</h2>" +
"<p><b>"+garage.description+"</b></p>" +
"<p>"+garage.address+"</p>" +
"</a></li>" +
"</ul><br>"
);
$("#submitID").click(function() {
localStorage.setItem("garageID",garage.ID);
$.mobile.changePage("#resP");
});
}
Now the user can click on a listview item and the ID from this item should be stored in the localStorage.
Problem: It first creates the complete listview with all items. If I click now on item#2 from the list, I get the ID from the last item.
the submit function is being overwritten with every iteration of the loop, and every single #submitID is only calling that function. So, of course it will only pull up the last one in the list.
Also, the code is generating all items within data.length, so you'll need to alter that if you don't want everything in the list.
Here's a fix to the first part (assuming no styling is assigned to #submitID):
for(var i = 0; i < data.length; i++) {
var garage = data[i];
$("#content-p").append(
"<ul data-role=\"listview\">" +
"<li><a href=\"#\" id=\"submitID-" + i + "\" >" +
"<h2>"+garage.location+"</h2>" +
"<p><b>"+garage.description+"</b></p>" +
"<p>"+garage.address+"</p>" +
"</a></li>" +
"</ul><br>"
);
$("#submitID-" + i).click(function() {
localStorage.setItem("garageID",garage.ID);
$.mobile.changePage("#resP");
});
}
I think the problem was that the method was trying to reference garage when there wasn't a reference, or something like that. And you should have made the click method for a specific ID, because it just kept getting overridden each time. I fixed it for you by making it one click method for a class, but the ID of each garage element is its actual garage ID.
http://jsfiddle.net/Fq3TF/1/
Also, just a tip, if you're constructing a string which is going to be inserted into an HTML document, you can use an apostrophe instead of a quote to avoid escaping. e.g. "<a class='submitID'>Hello World</a>".
Here is my take on the problem. I use a attribute to save the id in the a element. The registering of the click handler has been moved outside the loop.
var data = [
{
id: "id1",
location: "location1",
description: "description1",
address: "address1"
},
{
id: "id2",
location: "location2",
description: "description2",
address: "address2"
}
];
for (var i = 0; i < data.length; i++) {
var garage = data[i];
$("#content-p").append(
"<ul data-role=\"listview\">" +
"<li><a href=\"#\" class=\"submitClass\" garageid=\"" + garage.id + "\">" +
"<h2>" + garage.location + "</h2>" +
"<p><b>" + garage.description + "</b></p>" +
"<p>" + garage.address + "</p>" +
"</a></li>" +
"</ul><br>"
);
}
$(".submitClass").click(function(e) {
e.stopPropagation();
alert($(this).attr("garageid"));
/*localStorage.setItem("garageID", garage.ID);
$.mobile.changePage("#resP");*/
});

Categories