I'm really new to javascript from C# and i'm having a little trouble. I wrote this function to make adding menu's a bit easier on my site. It works well except I can't seem to give my div's an individual url, even though I can give them an individual innerHtml.
I've been stuck trying different things such as divs[i].location.url etc.. but I can't seem to have anything work. My current solution has each div link to /contact.html which I'm a little confused by.
function DrawMainMenu() {
var btns = [
["About", "/about.html"],
["Portfolio", "/portfolio.html"],
["Resume", "/resume.html"],
["Contact", "/contact.html"]
];
var numOfBtns = btns.length;
var divs = new Array(numOfBtns);
for (var i = 0; i < numOfBtns; i++) {
divs[i] = document.createElement("div");
divs[i].className = "menuBtn";
divs[i].innerHTML = btns[i][0];
divs[i].style.height = (30 / numOfBtns) + "%";
divs[i].style.lineHeight = 3.5;
var link = btns[i][1];
divs[i].addEventListener('click', function() {
location.href = link;
}, false);
document.getElementById("buttons").appendChild(divs[i]);
}
}
Thanks
The problem is that the variable link gets overwritten each iteration, so when the event handler it gets link, which is the string '/contact.html', since that was the last value given to it.
You can try setting onclick attribute to elements, which will store the variable in the attribute onclick. Therefore, it will have the old and correct value.
function DrawMainMenu() {
var btns = [
["About", "/about.html"],
["Portfolio", "/portfolio.html"],
["Resume", "/resume.html"],
["Contact", "/contact.html"]
];
var numOfBtns = btns.length;
var divs = new Array(numOfBtns);
for (var i = 0; i < numOfBtns; i++) {
divs[i] = document.createElement("div");
divs[i].className = "menuBtn";
divs[i].innerHTML = btns[i][0];
divs[i].style.height = (30 / numOfBtns) + "%";
divs[i].style.lineHeight = 3.5;
var link = btns[i][1];
divs[i].setAttribute('onclick', 'location.href = "' + link + '"');
document.getElementById("buttons").appendChild(divs[i]);
}
}
DrawMainMenu();
<div id="buttons"><div>
Updated answer
Here we make use of closures. Using a closure (closing the values of link) we bind the value to the scope of the click handler.
function DrawMainMenu() {
var btns = [
["About", "/about.html"],
["Portfolio", "/portfolio.html"],
["Resume", "/resume.html"],
["Contact", "/contact.html"]
];
var numOfBtns = btns.length;
var divs = new Array(numOfBtns);
for (var i = 0; i < numOfBtns; i++) {
(function() {
divs[i] = document.createElement("div");
divs[i].className = "menuBtn";
divs[i].innerHTML = btns[i][0];
divs[i].style.height = (30 / numOfBtns) + "%";
divs[i].style.lineHeight = 3.5;
var link = btns[i][1];
document.getElementById("buttons").appendChild(divs[i]);
divs[i].addEventListener('click', function() {
location.href = link;
}, false);
}());
}
}
DrawMainMenu();
<div id="buttons"><div>
Related
I have been playing with this code for a while and I was wondering why when I try to add img’s to the array on the js code makes the images appear on DOM but also makes a bunch of Undefined elements appear, How can I just make the 15 images appear without the undefined? Thanks
enter link description here
var previous = document.getElementById('btnPrevious')
var next = document.getElementById('btnNext')
var gallery = document.getElementById('image-gallery')
var pageIndicator = document.getElementById('page')
var galleryDots = document.getElementById('gallery-dots');
var images = ["https://exoplanets.nasa.gov/internal_resources/1763/",
"https://cdn.britannica.com/56/234056-050-0AC049D7/first-image-from-James-Webb-Space-Telescope-deepest-and-sharpest-infrared-image-of-distant-universe-to-date-SMACS-0723.jpg",
"https://assets.newatlas.com/dims4/default/ac389ce/2147483647/strip/true/crop/1620x1080+150+0/resize/1200x800!/quality/90/?url=http%3A%2F%2Fnewatlas-brightspot.s3.amazonaws.com%2Farchive%2Funiverse-expanding-acceleration-1.jpg",
"https://media.newyorker.com/photos/590966ee1c7a8e33fb38d6cc/master/w_2560%2Cc_limit/Nissan-Universe-Shouts.jpg",
"https://www.thoughtco.com/thmb/NY5k_3slMRttvtS7mA0SXm2WW9Q=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/smallerAndromeda-56a8ccf15f9b58b7d0f544fa.jpg",
"https://static.scientificamerican.com/sciam/cache/file/05B8482C-0C04-4E41-859DCCED721883D2_source.jpg?w=590&h=800&7ADE2895-F6E3-4DF4-A11F51B652E9FA88",
"https://qph.cf2.quoracdn.net/main-thumb-66277237-200-huqebnzwetdsnnwvysbxemlskpcxnygf.jpeg",
"http://www.pioneertv.com/media/1090/hero_shot_1080x720.jpg?anchor=center&mode=crop&width=600&height=400&rnd=133159257140000000",
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRSWFW1EpMNFM5-dbZEUUnzJkzT3KbUCeuhPHx_eseFCpPeX4Q_DIVPopjS0LeKVmKdQho&usqp=CAU",
"https://cdn.mos.cms.futurecdn.net/rwow8CCG3C3GrqHGiK8qcJ.jpg",
"https://static.wixstatic.com/media/917d103965314e2eacefed92edb6492c.jpg/v1/fill/w_640,h_356,al_c,q_80,usm_0.66_1.00_0.01,enc_auto/917d103965314e2eacefed92edb6492c.jpg",
"https://astronomy.com/~/media/A5B9B6CF36484AB9A6FFAE136C55B355.jpg",
"https://discovery.sndimg.com/content/dam/images/discovery/fullset/2022/9/alien%20planet%20GettyImages-913058614.jpg.rend.hgtvcom.616.411.suffix/1664497398007.jpeg",
"https://images.newscientist.com/wp-content/uploads/2017/06/21180000/planet-10-orange-blue-final-small.jpg?crop=16:9,smart&width=1200&height=675&upscale=true",
"https://images.hindustantimes.com/img/2022/07/20/1600x900/Viral_Instagram_Planet_Rainbow_Nasa_1658316556293_1658316573815_1658316573815.PNG"
]
for (var i = 0; i < 15; i++) {
images.push({
title: "Image " + (i + 1),
source: images[i]
});
}
var perPage = 8;
var page = 1;
var pages = Math.ceil(images.length / perPage)
// Gallery dots
for (var i = 0; i < pages; i++){
var dot = document.createElement('button')
var dotSpan = document.createElement('span')
var dotNumber = document.createTextNode(i + 1)
dot.classList.add('gallery-dot');
dot.setAttribute('data-index', i);
dotSpan.classList.add('sr-only');
dotSpan.appendChild(dotNumber);
dot.appendChild(dotSpan)
dot.addEventListener('click', function(e) {
var self = e.target
goToPage(self.getAttribute('data-index'))
})
galleryDots.appendChild(dot)
}
// Previous Button
previous.addEventListener('click', function() {
if (page === 1) {
page = 1;
} else {
page--;
showImages();
}
})
// Next Button
next.addEventListener('click', function() {
if (page < pages) {
page++;
showImages();
}
})
// Jump to page
function goToPage(index) {
index = parseInt(index);
page = index + 1;
showImages();
}
// Load images
function showImages() {
while(gallery.firstChild) gallery.removeChild(gallery.firstChild)
var offset = (page - 1) * perPage;
var dots = document.querySelectorAll('.gallery-dot');
for (var i = 0; i < dots.length; i++){
dots[i].classList.remove('active');
}
dots[page - 1].classList.add('active');
for (var i = offset; i < offset + perPage; i++) {
if ( images[i] ) {
var template = document.createElement('div');
var title = document.createElement('p');
var titleText = document.createTextNode(images[i].title);
var img = document.createElement('img');
template.classList.add('template')
img.setAttribute("src", images[i].source);
img.setAttribute('alt', images[i].title);
title.appendChild(titleText);
template.appendChild(img);
template.appendChild(title);
gallery.appendChild(template);
}
}
// Animate images
var galleryItems = document.querySelectorAll('.template')
for (var i = 0; i < galleryItems.length; i++) {
var onAnimateItemIn = animateItemIn(i);
setTimeout(onAnimateItemIn, i * 100);
}
function animateItemIn(i) {
var item = galleryItems[i];
return function() {
item.classList.add('animate');
}
}
// Update page indicator
pageIndicator.textContent = "Page " + page + " of " + pages;
}
showImages();
I checked your code and make it work with a small modification.
You are reusing the same array with the links of images and push inside the new object with the shape of { title, source }.
You just need to do this changes:
Change the name of your array of images. Something from images to arrayOfImages.
const arrayOfImages = ["https://exoplanets.nasa.gov/internal_resources/1763/", ...]
Declare an empty array before your first for loop. Make something like const images = []
On your for loop, instead of loop over the images variable, do it over the arrayOfImages variable.
const images = [];
for (var i = 0; i < 15; i++) {
images.push({
title: "Image " + (i + 1),
source: arrayOfImages[i]
});
}
With those changes, everything works for me.
Also, as a recommendation, try to avoid the var keyword. If you want more details about this, this answer is very helpful: https://stackoverflow.com/a/50335579/17101307
You can use Array#map to create a new array of objects from the array of URLS, then replace the original array.
images = images.map((x, i) => ({
title: "Image " + (i + 1),
source: x
}));
So I'm still pretty new at JavaScript, I can do some basic stuff, but a lot of it, not so much. Anyway I am trying to create a gallery that opens a larger slideshow when one of the elements is clicked. The problem I'm running into is I don't know how to find the index of the clicked elements. I've tried Google, and I even looked here and lots of answers to the question, but none of them seem to work for my bit of code.
var getThumbs = Array.from(document.querySelectorAll('.retail-thumbnail'));
for (var i = 0; i < getThumbs.length; i++) {
getThumbs[i].onclick = function(e) {
var createSlideshow = document.createElement('div');
createSlideshow.setAttribute('class', 'retail-slideshow');
var createClsBtn = document.createElement('div');
createClsBtn.setAttribute('class', 'close');
createClsBtn.innerHTML = '×';
createSlideshow.append(createClsBtn);
var slides = '';
const index = getThumbs
getThumbs.forEach(gall => {
if (gall.classList.contains('video-thumb')) {
gall.childNodes.forEach(source => {
source = Array.from(source.src.split('.'));
source = source[source.length - 3];
slides += '<div class="retail-slide">\n' +
'<iframe src="https://player.vimeo.com/video/' + source + '" width="100%" height="100%" frameborder="0" webkitallowfullscreen mozallowfullscreen></iframe>\n' +
'</div>\n\n'
});
} else {
slides += '<img src="' + gall.src + '" class="retail-slide">\n\n'
}
});
slides += '<div id="retail_ss_prev"></div><div id="retail_ss_next"></div>';
createSlideshow.innerHTML += slides;
var activeModal = document.querySelector('.modal.active');
activeModal.append(createSlideshow);
var activeCls = document.querySelector('.retail-slideshow > .close');
activeCls.addEventListener('click', function(e) {
activeCls.parentNode.remove();
});
};
};
};
So this is what I have, and it writes everything, but I can't get the index when I click on one of the .retail-thumbnail elements. Any help is greatly appreciated. Preferably in Vanilla JS. I try to stay away from Jquery
You can take advantage of closures.
var getThumbs = Array.from(document.querySelectorAll('.retail-thumbnail'));
for (var i = 0; i < getThumbs.length; i++) {
getThumbs[i].onclick = function(index) {
return function(e) {
//Your current function, where index now is the same as i at the moment of assigning the event listener
};
}(i);
}
Or, you could add a data attribute to each element, either in the html (something like data-index="1"), or setting them from js:
var getThumbs = Array.from(document.querySelectorAll('.retail-thumbnail'));
for (var i = 0; i < getThumbs.length; i++) {
getThumbs[i].dataset.index=i;
getThumbs[i].onclick = function(e) {
var index=this.dataset.index;
//....
};
}
Use an anonymous function to pass both the event and the index.
Below is the code that passes the index to the function in your code, in addition to the event.
By using an anonymous function, your are able to create a function that takes the event as an input (e), but also know what the index is (see about closures - the anonymous function knows lexical scope from when it was created).
I used anonymous function and the ES-6 fat arrow function syntax below ()=>{}. You can do it without these, but it will look less clean.
const clicked = (e,i) =>{
var createSlideshow = document.createElement('div');
createSlideshow.setAttribute('class', 'retail-slideshow');
var createClsBtn = document.createElement('div');
createClsBtn.setAttribute('class', 'close');
createClsBtn.innerHTML = '×';
createSlideshow.append(createClsBtn);
var slides = '';
const index = getThumbs
getThumbs.forEach(gall => {
if (gall.classList.contains('video-thumb')) {
gall.childNodes.forEach(source => {
source = Array.from(source.src.split('.'));
source = source[source.length - 3];
slides += '<div class="retail-slide">\n' +
'<iframe src="https://player.vimeo.com/video/' + source + '" width="100%" height="100%" frameborder="0" webkitallowfullscreen mozallowfullscreen></iframe>\n' +
'</div>\n\n'
});
} else {
slides += '<img src="' + gall.src + '" class="retail-slide">\n\n'
}
});
slides += '<div id="retail_ss_prev"></div><div id="retail_ss_next"></div>';
createSlideshow.innerHTML += slides;
var activeModal = document.querySelector('.modal.active');
activeModal.append(createSlideshow);
var activeCls = document.querySelector('.retail-slideshow > .close');
activeCls.addEventListener('click', function(e) {
activeCls.parentNode.remove();
}
var getThumbs = Array.from(document.querySelectorAll('.retail-thumbnail'));
for (var i = 0; i < getThumbs.length; i++) {
getThumbs[i].onclick = (e) =>{clicked(e,i);}
};
};
};
for (var i=0; i<5; i++){
var abc = function(){
alert(i) //You can very well access i here
}
abc();
}
You can pretty much access i inside getThumbs[i].onclick without having to write extra lines of codes.
for (var i = 0; i < getThumbs.length; i++) {
getThumbs[i].onclick = function(e) {
var createSlideshow = document.createElement('div');
var index = i //You can access i due to the lexical scope
}
I'm looping through some elements by class name, and adding event listeners to them. I then grab the id of the selected element (in this case "tom"), and want to use it to find the value of "role" in the "tom" object. I'm getting undefined? can anyone help?
var highlightArea = document.getElementsByClassName('highlightArea');
for (var i = 0; i < highlightArea.length; i++) {
highlightArea[i].addEventListener("mouseover", showPopup);
highlightArea[i].addEventListener("mouseover", hidePopup);
}
function showPopup(evt) {
var tom = { title:'tom', role:'full stack man' };
var id = this.id;
var role = id.role;
console.log(role)
}
You are not selecting the elements correctly, the class is hightlightArea and you are querying highlightArea (missing a 't'), so, no elements are found (you can easily discover that by debugging or using console.log(highlightArea) that is the variable that holds the elements found.
Just because the id of an element is the same name as a var, it doesn't mean that it have the properties or attributes of the variable... So when you get the Id, you need to check which one is and then get the variable that have the same name.
Also, you are adding the same listener two times mouseover that way, just the last would work, it means just hidePopup. I changed to mouseenter and mouseleave, this way will work correctly.
After that, you will be able to achieve your needs. Below is an working example.
var highlightArea = document.getElementsByClassName('hightlightArea');
var mypopup = document.getElementById("mypopup");
var tom = { title:'tom', role:'marketing'};
var jim = { title:'jim', role:'another role'};
for (var i = 0; i < highlightArea.length; i++) {
highlightArea[i].addEventListener("mouseenter", showPopup);
highlightArea[i].addEventListener("mouseleave", hidePopup);
}
function showPopup(evt) {
let ElemId = this.id;
let role;
let title;
if (ElemId == 'tom'){
role = tom.role;
title = tom.title;
}else if (ElemId == 'jim'){
role = jim.role;
title = jim.title;
}
let iconPos = this.getBoundingClientRect();
mypopup.innerHTML = role;
mypopup.style.left = (iconPos.right + 20) + "px";
mypopup.style.top = (window.scrollY + iconPos.top - 60) + "px";
mypopup.style.display = "block";
}
function hidePopup(evt) {
mypopup.style.display = "none";
}
<div class="hightlightArea" id="jim">Div Jim</div>
<div class="hightlightArea" id="tom">Div Tom</div>
<div id="mypopup"></div>
in your function 'showPopup' you have this:
var id = this.id
but this.id is not defined. You probably meant to write this:
var title = dom.title;
In Google App Scripts (GAS), I want to be able to add and remove TextBox and TextArea elements to a FlexTable (that's being used as a form) and not worry about how many there are. I've named the text elements based on a counter to make this process easier.
So, is there a way to get the number of inputs (TextBox + TextArea) passed to e.parameter after the form is submitted?
Here's the relevant code from the FlexTable:
function doGet() {
var app = UiApp.createApplication();
var flex = app.createFlexTable().setId('myFlex');
var counter = 0;
var row_counter = 0;
...
var firstnameLabel = app.createLabel('Your FIRST Name');
var firstnameTextBox = app.createTextBox().setWidth(sm_width).setName('input' + counter).setText(data[counter]);
flex.setWidget(row_counter, 1, firstnameLabel);
flex.setWidget(row_counter, 2, firstnameTextBox);
row_counter++;
counter++;
var lastnameLabel = app.createLabel('Your LAST Name');
var lastnameTextBox = app.createTextBox().setWidth(sm_width).setName('input' + counter).setText(data[counter]);
flex.setWidget(row_counter, 1, lastnameLabel);
flex.setWidget(row_counter, 2, lastnameTextBox);
row_counter++;
counter++;
...
var submitButton = app.createButton('Submit Proposal');
flex.setWidget(row_counter, 2, submitButton);
var handler = app.createServerClickHandler('saveProposal');
handler.addCallbackElement(flex);
submitButton.addClickHandler(handler);
var scroll = app.createScrollPanel().setSize('100%', '100%');
scroll.add(flex);
app.add(scroll);
return app;
}
And here's the code for the ClickHandler (notice that I currently have 39 elements in my FlexTable):
function saveProposal(e){
var app = UiApp.getActiveApplication();
var userData = [];
var counter = 39;
for(var i = 0; i < counter; i++) {
var input_name = 'input' + i;
userData[i] = e.parameter[input_name];
}
So, is there a way to get the number of elements (in this case 39) without manually counting them and assigning this value to a variable?
I'm new at this stuff and I'd appreciate your help.
Cheers!
The simplest way is to add a hidden widget in your doGet() function that will hold the counter value like this :
var hidden = app.createHidden('counterValue',counter);// don't forget to add this widget as a callBackElement to your handler variable (handler.addCallBackElement(hidden))
then in the handler function simply use
var counter = Number(e.parameter.counterValue);// because the returned value is actually a string, as almost any other widget...
If you want to see this value while debugging you can replace it momentarily with a textBox...
You can search for arguments array based object.
function foo(x) {
console.log(arguments.length); // This will print 7.
}
foo(1,2,3,4,5,6,7) // Sending 7 parameters to function.
You could use a while loop.
var i = 0;
var userData = [];
while (e.parameter['input' + i] != undefined) {
userData[i] = e.parameter['input' + i];
i++;
};
OR:
var i = 0;
var userData = [];
var input_name = 'input0';
while (e.parameter[input_name] != undefined) {
userData[i] = e.parameter[input_name];
i++;
input_name = 'input' + i;
};
I want to create a delay between two document.writes. I used setTimeout to do so, but once it executes, it writes over the previous text. I would like to be able to display text, wait some delay, the display some other text below it without erasing any previous text. This code is appending to an otherwise empty HTML file. Also, I haven't had any success with using <br> for this.
var numOfDice = prompt("How many dice?");
var numOfSides = prompt("How many sides on these dice?");
var rollDice = function(numOfDice) {
var rollResults = [];
for (var i = 0; i < numOfDice; i++) {
rollResults[i] = Math.floor((Math.random() * numOfSides) + 1);
}
return rollResults;
}
var printResults = function() {
var i = 0;
while (i < rollResults.length - 1) {
document.write(rollResults[i] + ", ");
i++;
}
document.write(rollResults[i]);
}
alert("Roll the dice!");
var rollResults = rollDice(numOfDice);
printResults();
setTimeout(function() {document.write("These numbers...")}, 1000);
First, take a look at this answer why you shouldn't be using document.write.
Then, after you understand why using document.write is bad practice, you can replace it with element.appendChild.
For more information about the function, visit the Mozilla Development Network.
Code example:
var numOfDice = prompt("How many dice?");
var numOfSides = prompt("How many sides on these dice?");
var rollDice = function(numOfDice) {
var rollResults = [];
for (var i = 0; i < numOfDice; i++) {
rollResults[i] = Math.floor((Math.random() * numOfSides) + 1);
}
return rollResults;
}
var printResults = function() {
var i = 0;
while (i < rollResults.length - 1) {
var node = document.createElement("p");
node.innerHTML = rollResults[i] + ", ";
document.querySelector("body").appendChild(node);
i++;
}
var node = document.createElement("p");
node.innerHTML = rollResults[i];
document.querySelector("body").appendChild(node);
}
alert("Roll the dice!");
var rollResults = rollDice(numOfDice);
printResults();
setTimeout(function() {
var node = document.createElement("p");
node.innerHTML = "These numbers...";
document.querySelector("body").appendChild(node);
}, 1000);
Note that this code will show each rolled dice on a new line, since I'm creating a new p element for every roll. If you want them to appear on the same line, create one p element, and then in the while loop, append the rollresult to that p elements `innerHTML´
.