Handling event listeners on multiple pages - javascript

I've written a javascript file which has some event listeners in for things like tabs, accordions and so on. However these aren't present in every single page so it's looking for elements that don't exist and throws the entire js out of wack.
I know I could get around it by using multiple if statements, but it doesn't sound like it would be correct.
// Accordion
const accordions = document.querySelectorAll('.accordion li');
accordions.forEach(accordion =>{
accordion.addEventListener('click', (e) => {
e.preventDefault();
accordion.classList.toggle('open');
})
});
// Inline toggle
const inlineToggle = document.getElementById('inline-toggle');
inlineToggle.addEventListener('click', () => {
inlineToggle.nextElementSibling.classList.toggle('active');
});
const inlineToggleOptions = document.querySelectorAll('.inline-toggle-options button');
inlineToggleOptions.forEach(option => {
option.addEventListener('click', (e) => {
// Prevent default
e.preventDefault();
// Update sentence text
inlineToggle.innerHTML = option.dataset.payType;
// Remove selected class from options
inlineToggleOptions.forEach(option => {
option.classList.remove('selected');
});
// Add selected class to chosen option
option.classList.add('selected');
// Close dialog
inlineToggle.nextElementSibling.classList.remove('active');
})
});
// Cover bubbles
// Create the slidepanel
const placeholder = document.getElementById('slidepanel');
// Find all buttons
const button = document.querySelectorAll('.trigger-aside');
button.forEach((button => {
// Listen for clicks on buttons
button.addEventListener('click',(e) => {
// Prevent default
e.preventDefault();
// Get the target
const target = button.dataset.target;
console.log(target);
// Call the API
fetch(`http://****.****.uk/****/****/****/${target}`)
.then((res) => res.json())
.then(function(res) {
// Load HTML into slider panel
placeholder.innerHTML = res.object.content;
// Stop body overflow
document.body.classList.add('overflow-hidden');
// Create overlay and append
const overlay = document.querySelector('.overlay');
overlay.classList.add('active');
document.body.appendChild(overlay);
// Show the panel
placeholder.classList.add('active');
document.body.appendChild(placeholder);
// Listen for close
overlay.addEventListener('click', (e) =>{
// Close requested
document.body.classList.remove('overflow-hidden');
placeholder.classList.remove('active');
overlay.classList.remove('active');
});
})
.catch(function(err) {
// Log error
console.log(err);
});
})
}));
How do other people generally get around this issue? Any guidance appreciated!!

Event Delegation Pattern
If you have these UI elements grouped under a parent element in common across all pages(like a div wrapper), you could try making use of the Event Delegation pattern. Essentially, you can assign a click event to that parent element and make use of a function to only take action if the desired element is returned - i.e. your buttons. It would go something like...
const parent = document.querySelector('div.wrapper'); //Change selector to suit a common parent
const buttons = [...document.querySelectorAll('.inline-toggle-options button')]; // convert to array to make it easier to work with
const elementsToChange = document.querySelectorAll('.elements .to .change');
parent.addEventListener('click', toggleOptions);
function getEventTarget(e) {
e = e || window.event;
return e.target || e.srcElement; // For IE compatibility
}
function toggleOptions {
let target = getEventTarget(e);
if(buttons.includes(target)) {
// Trigger options on UI elements if any of the buttons are among the clicked elements
// Target refers to the buttons in particular, not the UI elements you want to change
}
}
Whichever way you want to refactor the code to take action on specific elements is up to you. You can group buttons by specific functionality into distinct arrays. If you had 2 or 3 arrays, you'd only need to write 2 or 3 options from a conditional statement.
For this purpose, you'll also save memory with this pattern since you're only assigning one event handler and letting child events on child elements bubble up to be taken care of by that single handler. Also, you shouldn't run into errors since the initial event handler is assigned to a parent element that's common across all pages.
Some Caveats
From the guide linked above:
Not all events bubble. The blur, focus, load and unload events don’t
bubble like other events. The blur and focus events can actually be
accessed using the capturing phase (in browsers other than IE) instead
of the bubbling phase but that’s a story for another day.
You need caution when managing some mouse events. If your code is
handling the mousemove event you are in serious risk of creating a
performance bottleneck because the mousemove event is triggered so
often. The mouseout event has a quirky behaviour that is difficult to
manage with event delegation.

I'd generally advise to think first about code organisation and second about efficiency. I agree with StevenB.'s suggestion and Luke Tubby's answer, to make the best of your current situation with a minimum of effort.
For more elaborate solutions, I would suggest to familiarise with build / packaging tools (e.g. Webpack ), that offer you ways to structure your code (and assets) in files and directories of your choice, and create page specific minified packages.
Another (completely different and independent) approach to the problem (of improving efficiency and code organisation) would be to build a single page application...

Related

mousemove with two layers on top of each other (overlapping)

The closest to my question are: Mapbox gl js - overlapping layers and mouse event handling, Mapbox add popup on hover (layer), close on mouseleave, but keep open on popup hover and How to ignore mouse events on a mapbox layer - but they don't answer it.
I have two layers, let's imagine one is for a country, and another is for a city. Both of them a handled like this:
// CountryLayer.js
map.on("mousemove", "country-layer", e => {
// show info about the country
featureProps = e.features[0] // display some notification from the props
...
// CityLayer.js
map.on("mousemove", "city-layer", e => {
// show info about the city
featureProps = e.features[0] // display some notification from the props
...
It's done in different components. But when I mouseover city-layer mapbox thinks that I'm still "mousemoving" on top of the country-layer as well, so I get two notifications from separate components, where I need only one - in that case from the city-layer cause it's on top of country-layer.
Handling the mousemove without layerId in one place is gonna be a mess and breaks all the good rules about programming. Creating external "event manager" which will track whether I'm hovering the city and if is so will remove mousemove event from country-layer - is complex. I didn't find any good alternatives. At least, I would be glad to disable pointer events for a layer like this:
map.on("mousemove", "city-layer", e => {
map.getLayer("country-layer").setStyle({ "pointer-events": false })
featureProps = e.features[0]
...
or something like this. Is it possible? Is there more adequate way around it?
e.originalEvent.stopPropagation(); does not work
It appears, that you can do e.originalEvent.preventDefault(); in the city-layer and add a check e.originalEvent.defaultPrevented in the country-layer from the example.
However, I have issues with z-index position of layers: map.moveLayer("city-layer", "country-layer"); or vice-vise doesn't actually change the way, event propogates, so for some reason my country-layer always come first, so when it checks for e.originalEvent.defaultPrevented, preventDefault() wasn't fired yet, so it comes always false.
But technically, this answers my question.
map.on can listen on multiple layers at once. With events like mousemove it returns features in reverse layer order (from "highest" to "lowest").
So the way to do this is:
map.on("mousemove", ["city-layer", "country-layer"], e => {
feature = e.features[0]
if (feature.layer.id === 'city-layer') {
// ...
} else {
// ...
}
});

Javascript 'click' onto wrong element when dynamically generating elements

I am adding some elements dynamically to a page when making this chrome extension. It is a list added like this
var result = document.createElement("UL");
var checkoutArea = document.getElementById("shipping");
result.setAttribute("id", "resList");
if (document.getElementById("resList") === null) {
checkoutArea.appendChild(result);
}
I am also adding <'li'> child to it later. But when I am trying to add onclick function to each item, it does not work. I checked when I click on the item dynamically generated, and it seems that those items are not clicked, as this gives wrong results:
document.addEventListener('click', function(e) {
e = e || window.event;
var target = e.target || e.srcElement,
text = target.textContent || text.innerText;
console.log(text);
}, false);
a screenshot of the DOM of dynamically generated list
Also when I 'inspect' on that 'home' item, chrome is giving me that highlighted element, which is the 'save & continue' button besides. I am thinking there's an overlap, so how should add any 'onclick' function to those items?? Great thanks in advance.
You probably need to try the Event delegation pattern. The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them – we put a single handler on their common ancestor.
Read about it on this link: http://javascript.info/event-delegation
I managed to understand Event delegation thanks to video 15 that has the name LocalStorage and Event Delegation in the course javascript30 of Wes Bos. The course is free and worthwhile. I hope this information serves you

How to automatically trigger a hover if a div contains a class? [duplicate]

I've been trying to find code to simulate mouseover in Chrome but even though the "mouseover" listener gets fired, the CSS "hover" declaration is never set!
I tried also doing:
//Called within mouseover listener
theElement.classList.add("hover");
But nothing seems to change the element to what is declared in its hover declaration.
Is this possible?
You can't. It's not a trusted event.
Events that are generated by the user agent, either as a result of user interaction, or as a direct result of changes to the DOM, are trusted by the user agent with privileges that are not afforded to events generated by script through the DocumentEvent.createEvent("Event") method, modified using the Event.initEvent() method, or dispatched via the EventTarget.dispatchEvent() method. The isTrusted attribute of trusted events has a value of true, while untrusted events have a isTrusted attribute value of false.
Most untrusted events should not trigger default actions, with the exception of click or DOMActivate events.
You have to add a class and add/remove that on the mouseover/mouseout events manually.
You can simulate the mouseover event like this:
HTML
<div id="name">My Name</div>
JavaScript
var element = document.getElementById('name');
element.addEventListener('mouseover', function() {
console.log('Event triggered');
});
var event = new MouseEvent('mouseover', {
'view': window,
'bubbles': true,
'cancelable': true
});
element.dispatchEvent(event);
Background
I stumbled upon this question while trying to write automated tests, to verify, that a certain set of elements on a given page all receive have the some set of css properties set by the css for on hover events.
While the above answer perfectly explains, why it is not possible to simply trigger the hover event by JS and then probe some css value of interest, it does answer the initial question "How do I simulate a mouseover in pure JavaScript that activates the CSS “:hover”?" only partly.
Disclaimer
This is not a performant solution. We use it only for automated testing, where performance is not a concern.
Solution
simulateCssEvent = function(type){
var id = 'simulatedStyle';
var generateEvent = function(selector){
var style = "";
for (var i in document.styleSheets) {
var rules = document.styleSheets[i].cssRules;
for (var r in rules) {
if(rules[r].cssText && rules[r].selectorText){
if(rules[r].selectorText.indexOf(selector) > -1){
var regex = new RegExp(selector,"g")
var text = rules[r].cssText.replace(regex,"");
style += text+"\n";
}
}
}
}
$("head").append("<style id="+id+">"+style+"</style>");
};
var stopEvent = function(){
$("#"+id).remove();
};
switch(type) {
case "hover":
return generateEvent(":hover");
case "stop":
return stopEvent();
}
}
Explanation
generateEvent reads all css files, , replaces :hover with an empty string and applies it. This has the effect, that all :hover styles are applied. Now one can probe for a howered style and set back to initial state by stopping the Simulation.
Why do we apply the hover effect for the whole document and not just for the element of interest by getting the from the sheets and then perform a element.css(...)?
Done as that, the style would be applied inline, this would override other styles, which might not be overriden by the original css hover-style.
How would I now simulate the hover for a single element?
This is not performant, so better don't. If you must, you could check with the element.is(selectorOfInterest) if the style applies for your element and only use those styles.
Example
In Jasmine you can e.g. now perform:
describe("Simulate CSS Event", function() {
it("Simulate Link Hover", function () {
expect($("a").css("text-decoration")).toBe("none");
simulateCssEvent('hover');
expect($("a").css("text-decoration")).toBe("underline");
simulateCssEvent('stop');
expect($("a").css("text-decoration")).toBe("none");
});
});
What I usually do in this case is adding a class using javascript.. and attaching the same CSS as the :hover to this class
Try using
theElement.addEventListener('onmouseover',
function(){ theElement.className += ' hovered' });
Or for older browsers:
theElement.onmouseover = function(){theElement.className += ' hovered'};
you will of course have to use onmouseout to remove the "hovered" class when you leave the element...
You can use pseudo:styler, a library which can apply CSS pseudo-classes to elements.
(async () => {
let styler = new PseudoStyler();
await styler.loadDocumentStyles();
document.getElementById('button').addEventListener('click', () => {
const element = document.getElementById('test')
styler.toggleStyle(element, ':hover');
})
})();
Disclaimer: I am a coauthor of this library. We designed it to additionally support cross-origin stylesheets, specifically for use in Chrome extensions where you likely lack control over the CSS rules of the page.
I'm assuming you want to inspect the CSS after dom manipulation, but as soon as you move your mouse back to the devtools, the event isn't active on that html element anymore. You probably would like to have something like the :hover option in devtools for your javascript events. That doesn't exist, but you can simulate it.
Open your devtools and click in it to make it active.
Trigger the event on the element you're interested in.
Without moving the mouse, open the devtools command panel with ctrl + shift + p and select 'disable javascript' with your keyboard.
Since javascript is disabled, it doesn't get the chance to modify the element(s) back again. You can go to the devtools and inspect the css and html as if you were hovering, clicking or doing something else with it. After you're done, go to the command panel again and select 'enable javascript'.

How do I simulate a mouseover in pure JavaScript that activates the CSS ":hover"?

I've been trying to find code to simulate mouseover in Chrome but even though the "mouseover" listener gets fired, the CSS "hover" declaration is never set!
I tried also doing:
//Called within mouseover listener
theElement.classList.add("hover");
But nothing seems to change the element to what is declared in its hover declaration.
Is this possible?
You can't. It's not a trusted event.
Events that are generated by the user agent, either as a result of user interaction, or as a direct result of changes to the DOM, are trusted by the user agent with privileges that are not afforded to events generated by script through the DocumentEvent.createEvent("Event") method, modified using the Event.initEvent() method, or dispatched via the EventTarget.dispatchEvent() method. The isTrusted attribute of trusted events has a value of true, while untrusted events have a isTrusted attribute value of false.
Most untrusted events should not trigger default actions, with the exception of click or DOMActivate events.
You have to add a class and add/remove that on the mouseover/mouseout events manually.
You can simulate the mouseover event like this:
HTML
<div id="name">My Name</div>
JavaScript
var element = document.getElementById('name');
element.addEventListener('mouseover', function() {
console.log('Event triggered');
});
var event = new MouseEvent('mouseover', {
'view': window,
'bubbles': true,
'cancelable': true
});
element.dispatchEvent(event);
Background
I stumbled upon this question while trying to write automated tests, to verify, that a certain set of elements on a given page all receive have the some set of css properties set by the css for on hover events.
While the above answer perfectly explains, why it is not possible to simply trigger the hover event by JS and then probe some css value of interest, it does answer the initial question "How do I simulate a mouseover in pure JavaScript that activates the CSS “:hover”?" only partly.
Disclaimer
This is not a performant solution. We use it only for automated testing, where performance is not a concern.
Solution
simulateCssEvent = function(type){
var id = 'simulatedStyle';
var generateEvent = function(selector){
var style = "";
for (var i in document.styleSheets) {
var rules = document.styleSheets[i].cssRules;
for (var r in rules) {
if(rules[r].cssText && rules[r].selectorText){
if(rules[r].selectorText.indexOf(selector) > -1){
var regex = new RegExp(selector,"g")
var text = rules[r].cssText.replace(regex,"");
style += text+"\n";
}
}
}
}
$("head").append("<style id="+id+">"+style+"</style>");
};
var stopEvent = function(){
$("#"+id).remove();
};
switch(type) {
case "hover":
return generateEvent(":hover");
case "stop":
return stopEvent();
}
}
Explanation
generateEvent reads all css files, , replaces :hover with an empty string and applies it. This has the effect, that all :hover styles are applied. Now one can probe for a howered style and set back to initial state by stopping the Simulation.
Why do we apply the hover effect for the whole document and not just for the element of interest by getting the from the sheets and then perform a element.css(...)?
Done as that, the style would be applied inline, this would override other styles, which might not be overriden by the original css hover-style.
How would I now simulate the hover for a single element?
This is not performant, so better don't. If you must, you could check with the element.is(selectorOfInterest) if the style applies for your element and only use those styles.
Example
In Jasmine you can e.g. now perform:
describe("Simulate CSS Event", function() {
it("Simulate Link Hover", function () {
expect($("a").css("text-decoration")).toBe("none");
simulateCssEvent('hover');
expect($("a").css("text-decoration")).toBe("underline");
simulateCssEvent('stop');
expect($("a").css("text-decoration")).toBe("none");
});
});
What I usually do in this case is adding a class using javascript.. and attaching the same CSS as the :hover to this class
Try using
theElement.addEventListener('onmouseover',
function(){ theElement.className += ' hovered' });
Or for older browsers:
theElement.onmouseover = function(){theElement.className += ' hovered'};
you will of course have to use onmouseout to remove the "hovered" class when you leave the element...
You can use pseudo:styler, a library which can apply CSS pseudo-classes to elements.
(async () => {
let styler = new PseudoStyler();
await styler.loadDocumentStyles();
document.getElementById('button').addEventListener('click', () => {
const element = document.getElementById('test')
styler.toggleStyle(element, ':hover');
})
})();
Disclaimer: I am a coauthor of this library. We designed it to additionally support cross-origin stylesheets, specifically for use in Chrome extensions where you likely lack control over the CSS rules of the page.
I'm assuming you want to inspect the CSS after dom manipulation, but as soon as you move your mouse back to the devtools, the event isn't active on that html element anymore. You probably would like to have something like the :hover option in devtools for your javascript events. That doesn't exist, but you can simulate it.
Open your devtools and click in it to make it active.
Trigger the event on the element you're interested in.
Without moving the mouse, open the devtools command panel with ctrl + shift + p and select 'disable javascript' with your keyboard.
Since javascript is disabled, it doesn't get the chance to modify the element(s) back again. You can go to the devtools and inspect the css and html as if you were hovering, clicking or doing something else with it. After you're done, go to the command panel again and select 'enable javascript'.

how do I know if the mouse pointer is on the HTML element?

I have a timed event I want to behave differently accordingly to what HTML element the mouse pointer is on.
Is there a way, assuming I have the HTML element, to know if the mouse pointer is currently on top of it.
I am well aware of the onmouseover/onmouseout events and how to use them.
I am using JQuery.
I am obviously looking for some kind of flag, as I need to check a state and not handle an event.
again, I know how to implement this with events.
I'm not aware of any built-in way to ping an element for the status of mouse hovering.
However, you can create one by updating a flag at mouseenter and mouseleave -- which is where Brian Driscoll's suggestion of .hover comes in:
jQuery.fn.tracking = function () {
this.data('hovering', false);
this.hover(function () {
$(this).data('hovering', true);
}, function () {
$(this).data('hovering', false);
});
return this;
};
jQuery.fn.hovering = function () {
return this.data('hovering');
}
You'll need to initialize tracking for each element you care about:
$('#elem1,#elem2').tracking();
But then you can get the status of any of them:
if ($('#elem1').hovering()) {
// ...
} else if ($('#elem2').hovering()) {
// ...
}
Demo: http://jsbin.com/amaxu3/edit
Have you looked into jQuery.hover()? http://api.jquery.com/hover/
You need to give name to html andme and on mouseover you need to check document.getelementsbyName. Then check what your are getting as output. Now you can take decision is it html control or asp.net.
When you use collObjects = object.getElementsByName("htmlcontrol") then compare id of both.
1 morething why you needed to check this in javascript. there may be some other solution for that. Just share with us.
You might have some luck with document.elementFromPoint, although I believe there are some inconsistencies in older browser implementations (http://www.quirksmode.org/dom/w3c_cssom.html#documentview).
$('#elem').mousemove(function(e){
if (this == document.elementFromPoint(e.clientX, e.clientY)) {
// Do stuff
}
});
Or outside of a handler
if ($('#elem').get(0) == document.elementFromPoint(x, y)) {
// Do stuff
}
Aside from that, the only other option that comes to mind is using event handlers to keep track of which element the mouse is over.

Categories

Resources