I have created a function that allows a user to click a visible div that drops down a "hidden" div/submenu. To the right of the visible div there is an arrow image. In total there are 5 visible divs and 5 arrows. I am needed to have the arrows rotate 180 when the "hidden" div is opened, and rotate back to 0 deg when closed. With the current code written, I am able to select the first arrow image to rotate. If I am to click on the second div, which I would like the second arrow to rotate, the first arrow is getting the script. I would believe to use querySelectorAll, but the console does not pick it up. Any tips are always greatly appreciated!
//
const questionBox = document.getElementsByClassName("question__container");
const arrows = document.querySelector(".question__container--img");
[...questionBox].forEach((el) =>
el.addEventListener("click", (event) => {
const subMenu = event.target.parentElement.parentElement.querySelector(
".options__container"
);
subMenu.classList.toggle("open");
if (subMenu.classList.contains("open")) {
arrows.style.transform = "rotate(180deg)";
} else {
arrows.style.transform = "rotate(0deg)";
}
})
);
//
Building on your current logic, here's what you could do.
Get all arrows with querySelectorAll
Create a function collapseAllArrows that basically loops through all arrows and collapses them.
When any of the div element is clicked and the subMenu for that element is toggled to open then call the collapseAllArrows function to collapse all expanded arrows.
The find the arrow for the clicked div element and expand it.
Your code should look something like this
const questionBox = document.getElementsByClassName("question__container");
const arrows = document.querySelectorAll(".question__container--img");
const collapseAllArrows = () => {
arrows.forEach(arrow => arrow.style.transform = "rotate(0deg)";)
}
[...questionBox].forEach((el) =>
el.addEventListener("click", (event) => {
const subMenu = event.target.parentElement.parentElement.querySelector(
".options__container"
);
subMenu.classList.toggle("open");
if (subMenu.classList.contains("open")) {
collapseAllArrows();
const currentArrow = el.querySelector(".question__container--img");
currentArrow.style.transform = "rotate(180deg)";
}
})
);
Related
i use modules for this work.it is supposed to become a programm, where you can create and learn virtual vocabulary cards.
this is my main.js:
import {Mainmenu} from "./aggregator.js";
let mainmenu = new Mainmenu();
mainmenu.start();
mainmenu.start() creates the html basis of my page:
start() {
let _body = document.querySelector("body");
_body.insertAdjacentElement("afterbegin" , this.createMainmenu());
_body.insertAdjacentElement("beforeend" , this.createHomeButton());
}
createMainMenu creates two Buttons, of which one gets an EventListener:
mainmenu.createMainmenu():
[...]
let _mFrameButtonEditVokabs = document.createElement("button");
_mFrameButtonEditVokabs.setAttribute("id" , "mFrameButtonEditVokabs");
_mFrameButtonEditVokabs.innerText = "Sets bearbeiten";
_mFrameButtonEditVokabs.addEventListener("click" , (e) => {
try {
_toggleFrame.erstelleToggleFrame();
}
catch {
let _toggleFrame = new Toggleframe();
_toggleFrame.erstelleToggleFrame();
}
e.target.classList.toggle("buttonsClicked");
setTimeout(() => e.target.classList.toggle("buttonsClicked"), 300);
_masterFrame.style.transform = "translateX(-50%)";
document.getElementById("homeButton").style.transform = "translateX(200%)";
});
_main.insertAdjacentElement("afterbegin" , _mFrameButtonLearnVokabs);
_main.insertAdjacentElement("beforeend" , _mFrameButtonEditVokabs);
_mainFrame.insertAdjacentElement("afterbegin" , _header);
_mainFrame.insertAdjacentElement("beforeend" , _main);
let _toggleFrame = document.createElement("div");
_toggleFrame.setAttribute("id" , "toggleFrame");
_masterFrame.insertAdjacentElement("afterbegin" , _mainFrame);
_mainFrame.insertAdjacentElement("afterend" , _toggleFrame);
return _masterFrame;
AND THIS WORKS PERFECTLY FINE. I get my button, it gets its eventListener AND THE LISTENER WORKS.
Now comes the fun part, that makes me rethink my choices of becoming a programmer:
I also create a homeButton WITH EXACT THE SAME TECHNIQUE, BUT IT HAS NO EVENTLISTENER!!
mainmenu.createHomeButton() creates the Homebutton:
mainmenu.createHomeButton():
createHomeButton() {
let _homeButton = document.createElement("div");
_homeButton.setAttribute("id" , "homeButton");
_homeButton.addEventListener("click" , () => {
console.log("Home wurde geklickt");
});
return _homeButton;
}
Two HTML Elements, the same kind of creating them, setting their attributes and setting an Eventlistener. The first one works, the other one doesnt. Why?
WHAT. I have 3 boxes: blue, red and black and I'm trying to transition them one after another, using JavaScript event listeners, in the following order: black, red and then blue.
HOW: https://codepen.io/gremo/pen/XWVeQVP?editors=1010
Basic HTML structure and nesting (pen for the complete example):
<div id="container">
<div id="blue"></div>
<div id="red">
<div id="black"></div>
</div>
</div>
JavaScript code is very simple:
const blue = document.getElementById('blue');
const red = document.getElementById('red');
const black = document.getElementById('black');
const hideElement = element => {
console.log(`Hiding element ${element.getAttribute('id')}`);
element.classList.add(element.dataset.hideClass);
};
black.addEventListener('transitionend', () => hideElement(red)); // when black transition ends, hide the red
red.addEventListener('transitionend', () => hideElement(blue)); // when red transition ends, hide the blue
hideElement(black); // hide the black (start the chain)
THE PROBLEM: after the black ends, blue and red start at the same time... and this is wrong because blue should start hiding after red completes. In addition, console show that the transitionend event listener for blue is called 2 times.
Any help is much appreciated, I've be struggling with this problem since days.
It seems that because black is a child of red, the transitionend event got bubbled up, causing red to fire the event twice:
document.addEventListener('DOMContentLoaded', () => {
const blue = document.getElementById('blue');
const red = document.getElementById('red');
const black = document.getElementById('black');
const hideElement = element => {
console.log(`Hiding element ${element.getAttribute('id')}`);
element.classList.add(element.dataset.hideClass);
};
black.addEventListener('transitionend', (e) => {
e.stopPropagation();// <-- added this line
hideElement(red);
});
red.addEventListener('transitionend', () => hideElement(blue));
hideElement(black);
});
(Edited from your codepen; I can't fork it because I'm too lazy to register)
transitionend event will trigger all the listeners once executed. As red and black both register the event listener before the first event triggers both a run. To prevent this, you could either pass a function which will add the event listener inside the hide element function or more preferably pass the event alongside the element and call stopPropagation. stopPropagation is an event function which will prevent the propagation of the event, thus only triggering the first one in this case
e?.stopPropagation()
Below is a modified function which should work in your context
document.addEventListener('DOMContentLoaded', () => {
const blue = document.getElementById('blue');
const red = document.getElementById('red');
const black = document.getElementById('black');
const hideElement = (element,e) => {
console.log(`Hiding element ${element.getAttribute('id')}`);
e?.stopPropagation()
element.classList.add(element.dataset.hideClass);
};
black.addEventListener('transitionend', (e) => hideElement(red,e));
red.addEventListener('transitionend', (e) => hideElement(blue,e));
hideElement(black);
});
I have a woking accessible responsive navigation which for DEMO perpuses I have to make it reusable so I can show different sort of senarios. However tho, the click burger menu is not working.
This header with responsive navigation should work indipenently multiple times.
On click and matchMedia
typial responsive nav behavious with an extra touch of matchMedia in case user moves the window.
open navigation on click and remove navigation if > 900
navigation remove Attribute hidden on desktop and on click.
All of this should work multiple times.
if (navBlocks.length > 0){
Array.prototype.forEach.call(navBlocks, function(el) {
the forEach function shoud do the work right? how come is not really working?
DEMO HERE
const navBlocks = document.querySelectorAll('.nav-container');
const nav = document.querySelector('.sliding-nav');
const menu = document.querySelector(".sliding-nav ul");
const toggleMenu = document.querySelector(".nav-container .nav-cta");
const mediaQuery = window.matchMedia('(min-width: 900px)');
let isMenuOpen = false;
if (navBlocks.length > 0) {
Array.prototype.forEach.call(navBlocks, function(el) {
el.addEventListener('click', e => {
e.preventDefault();
isMenuOpen = !isMenuOpen;
toggleMenu.setAttribute('aria-expanded', String(isMenuOpen));
menu.hidden = !isMenuOpen;
if (isMenuOpen) {
nav.classList.add('is-open');
document.body.classList.add("is-no-scroll", "is-fixed");
//console.log(isMenuOpen);
} else {
nav.classList.remove('is-open');
document.body.classList.remove("is-no-scroll", "is-fixed");
//console.log(!isMenuOpen);
}
});
function handleTabletChange(e) {
// Check if the media query is true
if (e.matches) {
toggleMenu.setAttribute("aria-expanded", false);
menu.removeAttribute("hidden");
nav.classList.remove('is-open');
document.body.classList.remove("is-no-scroll", "is-fixed");
} else {
toggleMenu.setAttribute("aria-expanded", false);
//nav.removeAttribute("aria-expanded");
}
}
// Register event listener
mediaQuery.addListener(handleTabletChange);
// Initial check
handleTabletChange(mediaQuery);
});
}
I think you should do something like this,
add event listener on burger menu icon to toggle an active class in nav bar and you should write css for that active class that if nav contains "active" class it should be set to display or else it should display none!
Thanks for your time.
I'm developing a page which has a top Calendar component( which show a week) and below that, a Scroll component which shows information for each one of the days of the week.
This is my page so far:
The problem here is that I need to know what day the scroll is showing to mark it at the calendar, like this example where the user is at day 3 and day 3 is marked at the calendar:
I have seen scroll tracking questions where the solutions are linked to ScrollY and ScrollX position, but in this case I need some info of the DOM element, like id or something, and I don't know if it's possible.
I also have tried onScroll method of the react infinite scroll, but it returns the whole document.
This is the code:
And the console print:
Thank you very much!
You could use the useRef() hook to reference the parent container within the onScroll function passed to the InfiniteScroll component. In that function you could use the parent reference to calculate which element is currently visible (closest to the middle of visible container). Then, pass item's date to a higher order component which is also shared with the calendar component.
Note: you would need to set the scrollableTarget property of the InfiniteScroll (because we want our parent element to be responsible for
scrollbars).
function InfiniteScrollContainer({ setFocusedItem }) {
const container = useRef();
function handleScroll() {
// Calculate which item is currently in the middle of the container
const containerMiddle = container.current.scrollTop + container.current.getBoundingClientRect().height / 2;
const infiniteScrollItems = container.current.children[0].children;
let index = 0;
let itemFound = false;
const itemNo = infiniteScrollItems.length;
while (!itemFound && index < itemNo) {
const item = infiniteScrollItems[i];
const itemTopOffset = item.offsetTop;
const itemBottomOffset = item.getBoundingClientRect().height + itemTopOffset;
if (itemTopOffset < containerMiddle && itemBottomOffset > containerMiddle) {
setFocusedItem(item);
itemFound = true;
}
index += 1;
}
}
useEffect(() => {
handleScroll();
}, [])
/* ... */
return (
<div ref={container} id="container-id">
<InfiniteScroll
<!-- ... -->
scrollableTarget={"container-id"}
onScroll={handleScroll}
>
<!-- ... -->
</InfiniteScroll>
</div>
);
}
i am using the ARCGIS Javascript API and trying to override the default right click behavior of the vertex points of a shape.
in ESRI's help it does list the onVertexClick event however from here it seems there is no way to determine if this is a right or left click event so i cannot override just the rightclick.
https://developers.arcgis.com/javascript/jsapi/edit.html
I am trying to set the right click behavour to just delete the current node/vertex instead of showing a menu with the option Delete.
EDIT
Here is the current event that exists within the ARCGIS api.
this.eventsList.push(dojo.connect(this._editToolbar, 'onVertexClick', $.proxy(this.addCustomVertexClickEvent, this)));
this event is already in the api however it does not return any way for me to determine left/right click.
your comment "listen for the click event then test the button attribute of the MouseEvent object" would work however i cant actually add a click event to the vertex points directly as these are inside the ARCGIS api code.
For anyone else who is looking for a way to do this without hacking around. You can listen to "contextmenu" (right click) events on the body, set a flag in the "contextmenu" handler to let the application know the current state. Simulate a click event to the "vertex handle" with a "mousedown", "mouseup" combination. In the "vertex-click" handler check for the right click flag set in the "contextmenu" handler
var editToolbar = new Edit(map, options);
var rightClick;
$('body').on('contextmenu', function(e) {
var target = e.target;
if(target.tagName === 'circle') {
// We only care about this event if it targeted a vertex
// which is visualized with an SVG circle element
// Set flag for right click
rightClick = true;
// Simulate click on vertex to allow esri vertex-click
// to fill in the data for us
var mouseDownEvt = new MouseEvent('mousedown', e.originalEvent);
target.dispatchEvent(mouseDownEvt);
var mouseUpEvt = new MouseEvent('mouseup', e.originalEvent);
target.dispatchEvent(mouseUpEvt);
// Since this event will be handled by us lets prevent default
// and stop propagation so the browser context menu doesnt appear
e.preventDefault();
e.stopPropagation();
}
});
editToolbar.on('vertex-click', function(e) {
if(rightClick) {
// Handle the right click on a vertex
rightClick = null;
}
});
after hearing back from ESRI it seems they do not provide this detail in their API so this is not possible yet.
I ended up doing this differently. I wanted to add a UI so the user could enter the XY of the point
// setup to allow editing
this.editToolbar = new EditToolbar(this.map, { allowDeleteVertices: false });
const rcMenuForGraphics = new RightClickVertexContextMenu();
const menu = rcMenuForGraphics.createMenu();
// bind to the map graphics as this is where the vertex editor is
this.map.graphics.on("mouse-over", (evt)=> {
// bind to the graphic underneath the mouse cursor
menu.bindDomNode(evt.graphic.getDojoShape().getNode());
});
this.map.graphics.on("mouse-out", (evt)=> {
menu.unBindDomNode(evt.graphic.getDojoShape().getNode());
});
this.editToolbar.on("vertex-click", (evt2) => {
rcMenuForGraphics.setCurrentTarget(evt2);
// evt2.vertexinfo.graphic.geometry.setX(evt2.vertexinfo.graphic.geometry.x - 1000);
})
// when the graphics layer is clicked start editing
gl.on("click", (evt: any) => {
this.map.setInfoWindowOnClick(false);
// tslint:disable-next-line: no-bitwise
const t: any = EditToolbar.MOVE | EditToolbar.EDIT_VERTICES;
this.editToolbar.deactivate();
this.editToolbar.activate(t, evt.graphic);
})
The code for the menu uses esri's vertex editor to grab the point, change its XY and then manually call the events to refresh the geometry. Only tested with polygon
import Menu = require("dijit/Menu");
import MenuItem = require("dijit/MenuItem");
import Graphic = require("esri/graphic");
import Edit = require("esri/toolbars/edit");
import Point = require("esri/geometry/Point");
class RightClickVertexContextMenu {
private curentTarget: { graphic: Graphic; vertexinfo: any; target: Edit; };
public createMenu() {
const menuForGraphics = new Menu({});
menuForGraphics.addChild(new MenuItem({
label: "Edit",
onClick: () => {
// this is a bit hooky. We grab the verx mover, change the x/y and then call the _moveStopHandler
console.log(this.curentTarget.vertexinfo);
const e: any = this.curentTarget.target;
const mover = e._vertexEditor._findMover(this.curentTarget.vertexinfo.graphic);
const g: Graphic = mover.graphic;
// add in a UI here to allow the user to set the new value. This just shifts the point to the left
g.setGeometry(new Point(mover.point.x - 1000, mover.point.y ))
e._vertexEditor._moveStopHandler(mover, {dx: 15});
this.curentTarget.target.refresh();
}
}));
menuForGraphics.addChild(new MenuItem({
label: "Delete",
onClick: () => {
// call the vertex delete handler
const ct: any = this.curentTarget.target;
ct._vertexEditor._deleteHandler(this.curentTarget.graphic)
}
}));
return menuForGraphics;
}
public setCurrentTarget(evt: { graphic: Graphic; vertexinfo: any; target: Edit; }) {
this.curentTarget = evt;
}
}
export = RightClickVertexContextMenu;