I have a menu button that appears multiple times on a site that i've put a click event on.
When I click on the button nothing happens (when there was just one instance of the button it worked fine). I know I need to loop through the node list created by having multiple instances of a class, but when I click on the button now, nothing happens and I don't get an error in the console either to give me any pointers?
Javascript and a simplified illustration in a Codepen are below.
Codepen: https://codepen.io/emilychews/pen/owWVGz
JS
var $mainMenuButton = document.getElementsByClassName('desktopmenubutton');
var $mainMenuButtonAll = function () {
for(h = 0; h < $mainMenuButton.length; h+=1) {
$mainMenuButton[i];
}
};
$mainMenuButtonAll.onclick = function () {
$mainMenuButtonAll.style.background = "black";
};
Any help would be awesome.
Emily
I edited the code for you, check the comments and the jsFiddle.
// get all the buttons with this tag name (or use class name)
var allButtons = document.getElementsByTagName('desktopmenubutton');
// add a click listener to each button
for(h = 0; h < allButtons.length; h+=1) {
allButtons[h].addEventListener("click", function(e){
console.log("yo yo");
e.currentTarget.style.backgroundColor = "black";
})
}
var $el = document.getElementsByClassName('desktopmenubutton');
// Solution 1
for (var i = 0; i < $el.length; i++) {
// If on clicking any element, only that should change
$el[i].onclick = function () {
this.style.background = 'black';
}
}
// Solution 2
for (var i = 0; i < $el.length; i++) {
// If on clicking any element, all should change
$el[i].onclick = function () {
for (var j = 0; j < $el.length; j++) {
$el[j].style.background = 'black'
}
}
}
First, we get all the elements and bind seperate click handlers for all the elements seperately. Solution 1 when you want to change only the element clicked and solution 2 when you want all the elements to change.
var $mainMenuButton = document.getElementsByClassName('desktopmenubutton');
var $mainMenuButtonAll = function () {
for(h = 0; h < $mainMenuButton.length; h+=1) {
$mainMenuButton[h].addEventListener("click", function(){
this.style.background = 'black';
});
}
};
$mainMenuButtonAll();
You need to bind each and every occurence of the button with an event click to perform some action over it.
var $mainMenuButton = document.getElementsByClassName('desktopmenubutton');
for(var h = 0; h < $mainMenuButton.length; h++) {
$mainMenuButton[h].addEventListener('click', function() {
this.style.background = "black";
});
}
Here is a working example.
What I did:
Removed the $mainMenuButtonAll variable since it was not needed.
Made the h variable on the for loop local, instead of global(more on that here)
Added an event listener to each $mainMenuButton, since it's never good to overwrite the onclick event.
Related
I have worked on this for a couple of days and I feel like I am almost there. I inherited a navigation component that behaves like any navigation -- User clicks a nav item to show the dropdown menu and clicks the nav-item or dropdown menu to close it. HOWEVER, I now need to be able to close any open dropdown menu when the user clicks anywhere else on the page.
The issue I'm facing is that I need to get my window event to ignore the nav. Otherwise, clicking the window removes the users' ability to close the nav on nav-click.
I have pasted my JavaScript code below. Can someone please assist me? Thank you.
The navClick() function was already there. I have added closeMenuOnWindowClick().
function initNav() {
const primaryNavElement = document.querySelector("#dcom-nav-primary");
const secondaryNavElement = document.querySelector("#dcom-nav-secondary");
const secondaryNavLinks = document.querySelectorAll(".dcom-nav-secondary-links");
//These variables are defined in the markup and dumped at that level. Added eslint lines below so it doesn't scream at us.
// eslint-disable-next-line
let navigationArray = [];
if(primaryNavElement){
navigationArray = primaryNavElement.getElementsByTagName("li");
}
let i;
const navClick = function() {
let navElm = this;
//Navigation buttons are relying on the navlevel attribute in the markup to determine how they behave
if(navElm,navigationArray.length,secondaryNavLinks){
if(navElm.attributes.navlevel){
if(navElm.attributes.navlevel.nodeValue == 'primary'){
//Primary Nav Click
//Reset and apply active link tab
for(i = 0; i<navigationArray.length; i++){
navigationArray[i].desktopDOMs[0].classList.remove('dcom-c-navigation-bar__li--active');
navigationArray[i].desktopDOMs[1].classList.add('dcom-c-navigation-bar__secondary-row--hidden');
}
navigationArray[navElm.index].desktopDOMs[0].classList.add('dcom-c-navigation-bar__li--active');
navigationArray[navElm.index].desktopDOMs[1].classList.remove('dcom-c-navigation-bar__secondary-row--hidden');
} else if(navElm.attributes.navlevel.nodeValue == 'secondary'){
//Secondary Nav Click
let dropdown = navElm.querySelector(".dcom-c-navigation-bar__dropdown");
if(dropdown){
for(i = 0; i<secondaryNavLinks.length; i++){
for(let ii = 0; ii<secondaryNavLinks[i].children.length; ii++){
let sDropdown = secondaryNavLinks[i].children[ii].querySelector(".dcom-c-navigation-bar__dropdown");
if(sDropdown != dropdown){
sDropdown.classList.add('dcom-c-navigation-bar__dropdown--hidden');
}
}
}
if(dropdown.children.length > 0){ //In the event the secondary dropdown has no tertiary links, do nothing, let it navigate.
// If there are only 2 items, remove flex-wrap
if(dropdown.children.length < 3) {
dropdown.style.flexWrap = 'nowrap';
}
if(dropdown.classList.contains('dcom-c-navigation-bar__dropdown--hidden')) {
dropdown.classList.remove('dcom-c-navigation-bar__dropdown--hidden');
dropdown.style.width = "auto"; //Reset width
//Handle dropdown width
//First, find out how many columns we need;
let dropdownCols = [[],[],[]];
let c = 0;
let r = 0;
for(i = 0; i < dropdown.children.length; i++){
dropdownCols[c].push(dropdown.children[i]);
r++;
if(r > 5){
r = 0;
c++;
}
}
//Then step through the links we have, find the largest, and set the largest size in each column
let colWidths = [0,0,0];
for(i = 0; i<dropdownCols.length; i++){
for(let ii = 0; ii<dropdownCols[i].length; ii++){
let linkWidth = dropdownCols[i][ii].offsetWidth;
if(linkWidth > colWidths[i])colWidths[i]=linkWidth;
}
}
//Set the width of the dropdown to the size of the largest columns
dropdown.style.width = (colWidths[0]+colWidths[1]+colWidths[2]+20)+"px";
//For IE fix we have to manually set the height. It's stupid, I know.
let linkHeight = dropdownCols[0][0].offsetHeight + 3; //get the height of one link element
//Check if its less than 6 (which wraps)
if(dropdown.children.length < 6){
//If its less than 6, make the height the height of the number of children
dropdown.style.height = (linkHeight * dropdown.children.length)+"px";
} else {
//Otherwise, set it to 6
dropdown.style.height = linkHeight * 6+"px";
}
//Check if this div is going off the screen
dropdown.style.right = "";
dropdown.style.left = "";
var dropdownRect = dropdown.getBoundingClientRect();
if((dropdownRect.left + dropdown.offsetWidth) > window.innerWidth){
dropdown.style.right = 0;
dropdown.style.left = "auto";
}
} else {
dropdown.classList.add('dcom-c-navigation-bar__dropdown--hidden');
//hide it if its open
}
}
}
}
/* Note: Since we don't have a Single Page Application here (haha), the assumption is that tertiary links will cause a page reload/navigation.
* In the event this is used in a SPA framework, can add a check here for tertiary controls, and add a click handler above.
*/
} else {
//Otherwise, using a switch based on the element id (for now! have had success with custom attributes before to determine button purpose that has less risk of conflict)
switch(navElm.id) {
default:
//Do nothing
}
}
}
};
if(primaryNavElement,secondaryNavElement,secondaryNavLinks.length){ //check all elements exist
//Find and add click handlers to all primary nav links
for(i = 0; i<primaryNavElement.children.length; i++){
if(primaryNavElement.children[i].nodeName == "LI"){ //double checking I'm grabbing list items
navigationArray[i].desktopDOMs = [];
navigationArray[i].desktopDOMs.push(primaryNavElement.children[i]); //Assign to reference array for future use
primaryNavElement.children[i].index = i; //I add an index here for reference
primaryNavElement.children[i].addEventListener('click', navClick);
//each primary nav's secondary navs
}
}
//Find all secondary links bars, store reference
for(i = 0; i<secondaryNavElement.children.length; i++){
navigationArray[i].desktopDOMs.push(secondaryNavElement.children[i]);
secondaryNavElement.children[i].index = i;
//secondaryNavElement.children[i]
}
//For all links, add handler, push to desktopDOMS
//console.log(secondaryNavLinks);
for(i = 0; i<secondaryNavLinks.length; i++){
for(let ii = 0; ii<secondaryNavLinks[i].children.length; ii++){
secondaryNavLinks[i].children[ii].index = i;
secondaryNavLinks[i].children[ii].addEventListener('click', navClick);
}
}
}
function closeMenuOnWindowClick() {
//Get all dropdowns
let menus = document.getElementsByClassName('dcom-c-navigation-bar__dropdown');
//Convert to array
menus = [...menus];
let openMenu;
//Loop through to find secondary level menu that is currently open
menus.forEach(menu => {
//If it's a secondary level nav and it's showing
if(menu.attributes.navlevel = 'secondary' && !menu.classList.contains('dcom-c-navigation-bar__dropdown--hidden')){
//Get it and label it
openMenu = menu;
openMenu.classList.add('dcom-c-navigation-bar__dropdown--hidden');
}
})
}
window.addEventListener('mouseup', function() {
closeMenuOnWindowClick();
navClick();
}, false);
}
module.exports = initNav;
Thanks to stackoverflow, I found bits of snippets here and there and was able to succeed!
Here's how I accomplished the solution:
// Click anywhere on the page to close an open menu.
window.addEventListener('click', function (ev) {
// Click anywhere, excluding element with a class you want to ignore, to hide the dropdown.
if (ev.target.className !== 'ignoreThisClass') {
dropdown.classList.add('addYourOwnHideClass');
}
});
I have been trying to write a script that changes an image src every two seconds based on a list.
So, everything is inside a forloop that loops over that list:
$(document).ready(function() {
var lis = {{dias|safe}}; <----- a long list from django. This part of the code works fine.
for (i=0; i<lis.length; i++){
src_img = lis[i][1];
var timeout = setInterval(function(){
console.log(src_img)
$("#imagen").attr("src", src_img);
}, 2000)
}
});
It doesn't work, the console logs thousands of srcs that correspond to the last item on the list. Thanks a lot for your help.
you don't need to run cycle in this case, you just save "pointer" - curentImage and call next array item through function ever 2 sec
var curentImage = 0;
function getNextImg(){
var url = lis[curentImage];
if(lis[curentImage]){
curentImage++;
} else {
curentImage = 0;
}
return url;
}
var timeout = setInterval(function(){
$("#imagen").attr("src", getNextImg());
}, 2000)
var curentImage = 0;
var length = lis.length;
function NewImage(){
var url = lis[curentImage];
if(curentImage < length){
currentImage++;
}
else{
currentImage = 0;
}
return url;
}
var timeout = setInterval(function(){
$("#imagen").attr("src", getNextImg());
}, 2000)
PS: Better than the previous one, Checks for lis length and starts from first if you reach end.
You need something like this
$(document).ready(function() {
var index = 0;
setInterval(function(){
src_img = lis[index++ % lis.lenght][1]; // avoid arrayOutOfBounds
$("#imagen").attr("src", src_img);
}, 2000)
});
Im trying to implement this.value in a multiple elements add.EventListener context to activate the stuff to do on the element currently under the mouse:
var i = 0;
var max = 500; //ms DELAY MOUSE-IN
function time(){
i++;
if(i>max/10){
i=0;
ELEMENTS = document.getElementsByTagName('div');
for(i=0; i<ELEMENTS.length; i++) {
document.getElementsByTagName('div')[i].style.height = "100px";
}
clearInterval(interval1);
}
ELEMENTS = document.getElementsByTagName('div');
for(i=0; i<ELEMENTS.length; i++) {
document.getElementsByTagName('div')[i].innerHTML = i;
}
}
window.onload = function(e) {
ELEMENTS = document.getElementsByTagName('div');
for(i=0; i<ELEMENTS.length; i++) {
document.getElementsByTagName('div')[i].addEventListener('mouseover', function(){
interval1 = setInterval(time, 10); //ms intervalSpeed
}, false);
document.getElementsByTagName('div')[i].addEventListener('mouseout', function(){
clearInterval(interval1);
i = 0;
document.getElementsByTagName('div')[i].innerHTML = "0";
document.getElementsByTagName('div')[i].style.height = "80px";
}, false);
}
}
Actually in this code there are some repetition to improve, but what i want is to IMPLEMENT this.value to control the element currently under the mouse.
Here a DEMO: http://jsfiddle.net/voltk/uA5M2/
#YEM SALAT I'm sorry because I found a problem in your solution:
The timer of divs does not reset....
UPDATE: I found the problem: the function time() must be included into the window load, or the 'i' on mouseout is not defined.
Many thanks to YemSalat for his help.
You would need to bind the event listener callback to the current DOM element.
Otherwise, this will refer to the global scope (browser window)
From your example:
var _div = document.getElementsByTagName('div')[i];
_div.addEventListener('mouseout', (function(){
clearInterval(interval1);
i = 0;
this.innerHTML = "0";
this.style.height = "80px";
}).bind(_div), false);
}
}
Something like that.
EDIT: added a jsfiddle: http://jsfiddle.net/zAMK3/3/
Ok, so suppose. I have ten images in documents.images array. When I click on an image. How do I get an alert telling me what image I have clicked on.
EDIT
The reason that I want the index of the image I clicked on is because I am trying to bind it to an event and the previous way doesn't seem to work
document.images[i].addEventListener("click", function(){MakeMove(i)}, false);
This statement is in a for loop, and I intended it to bind to every image but that doesn't seem to be working.
Here is pure JavaScript way to do that:
window.onload = function() {
for (var i = 0; i < document.images.length; i++) {
var image = document.images[i];
image.setAttribute("index", i + "");
image.onclick = function() {
var index = this.getAttribute("index");
alert("This is image #" + index);
};
}
};
The trick is assigning custom attribute with the index, then read that attribute.
Live test case.
Put this at the bottom of the html file, just before the closing tag
<script type="text/javascript">
function listen(evnt, elem, func) {
if (elem.addEventListener) { // W3C DOM
elem.addEventListener(evnt,func,false);
} else if (elem.attachEvent) { // IE DOM
var r = elem.attachEvent("on"+evnt, func);
return r;
}
}
listen("click", document.getElementsByTagName("body")[0], function(event) {
var images = document.images,
clicked = event.target;
for(var i =0, il = images.length; i<il;i++) {
if(images[i] === clicked) {
return alert("You clicked on the " + (i + 1) + "th image");
}
}
});
</script>
window.onload = function() {
for (var i = 0; i < document.images.length; i++) {
var image = document.images[i];
image.onclick = function() {
var images = Array.prototype.slice.call(document.images);
alert(Array.indexOf(images,this));
}
};
};
JSFiddle: http://jsfiddle.net/kmendes/8LUzg/1/
If you want it to work on old browsers too add the function under Compatibility on https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
I have some JavaScript code and a button which already has an event/action assigned to it, and I want to add a second event to the button. Currently the relevant bit of code looks like this:
events: {onclick: "showTab('"+n+"')"},
How do I amend this code so that it also increases the 'width' property of a div with class='panel', by a fixed amount e.g. 50px?
EDIT:
I understand. Problem is I don't know how to change id, only classname. The div is constructed in JavaScript using buildDomModel as follows:
this.buildDomModel(this.elements["page_panels"],
{ tag: "div", className: "tab_panel",
id: "tab_panel",
childs: [...]}
)
But the id doesn't get changed to 'tab_panel'. HOWEVER, classname does get changed to 'tab_panel' which is why i tried to use getelementbyclass. So the div has no id and I don't know how to create one using the buildDomModel but I can give unique class. I have put the code in original post too.
This:
events: {onclick: "showTab('"+n+"'); $('div.panel').width('50px');"},
Or you might want to add below line to showTab function.
$('div.panel').width('50px');
Update:
Or you can do with vanilla javascript:
function setWidth()
{
// since you are using a class panel, means more than one div, using loop
var divs = document.getElementsByTagName('div');
for(var i = 0; i < divs.length; i++)
{
// check if this div has panel class
if (divs[i].getAttribute('class') === 'panel')
{
// set the width now
divs[i].setAttribute('width', '50px');
}
}
}
Or if you want to apply the width to just one specific div, give that div an id and use this function instead:
function setWidth()
{
var div = document.getElementById('div_id');
div.setAttribute('width', '50px');
}
And later use it like this:
events: {onclick: "showTab('"+n+"'); setWidth();"},
Or you might want to add below line to showTab function.
setWidth();
Update 2:
Ok modify the function like this:
function setWidth()
{
var divs = document.getElementsByTagName('div');
for(var i = 0; i < divs.length; i++)
{
// check if this div has panel class
if (divs[i].getAttribute('class') === 'tab_panel')
{
// get previous width
var prevWidth = getComputedWidth(divs[i]);
// set the width now
divs[i].setAttribute('width', (prevWidth + 50));
}
}
}
function getComputedWidth(theElt){
if(is_ie){
tmphght = document.getElementById(theElt).offsetWidth;
}
else{
docObj = document.getElementById(theElt);
var tmphght1 = document.defaultView.getComputedStyle(docObj, "").getPropertyValue("width");
tmphght = tmphght1.split('px');
tmphght = tmphght[0];
}
return tmphght;
}
Note that with jQuery it should be this code in all:
function setWidth(){
jQuery('.tab_panel').each(function(){
jQuery(this).attr('style', 'width:' + (jQuery(this).width() + 50));
});
}
instead of the function you can use this line of JQuery code:
$('div.tab_panel').attr('style',"width:100px");
any way if you dont want it to be JQuery then this function should work :
function setWidth() {
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
// check if this div has panel class
if (divs[i].getAttribute('class') === 'tab_panel') {
// set the style now
divs[i].setAttribute('style', 'width:100px');
}
}
}
putting width attribute to div wouldnt work, you need to set its style,
so i expect any of the code i provided to work.
oh no the one i sent early just set width to a new value
this one should fix your problem :
var x = $('div.tab_panel')[0].style.width.replace("px", "");
x = x + 50;
$('div.tab_panel').attr('style', "width:" + x + "px");
ok here is a non JQuery Version :
function setWidth() {
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
if (divs[i].getAttribute('class') === 'tab_panel') {
var x = divs[i].style.width.replace("px", "");
x = x + 50;
divs[i].setAttribute('style', 'width:' + x + 'px');
}
}
ok here it is :
JQuery:
function Button1_onclick() {
var x = $('div.tab_panel')[0].clientWidth;
x = x + 50;
$('div.tab_panel').attr('style', "width:" + x + "px");
}
non JQuert:
function setWidth() {
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
if (divs[i].getAttribute('class') === 'tab_panel') {
var x = divs[i].offsetWidth;
x = x + 50;
divs[i].setAttribute('style', 'width:' + x + 'px');
}
}
}
this should work