JS - mouseover and mouseout events are inconstant - javascript

HTML
<a class="level-item like-icon">
<div class="icon is-medium">
<i class="far fa-heart" onmouseover="change(true)" onmouseout="change(false)"></i>
</div>
</a>
JS
change = (state) => {
state
? event.currentTarget.setAttribute('data-prefix', 'fas')
: event.currentTarget.setAttribute('data-prefix', 'far');
};
Goal:
To change icon (class, or in this case, attribute) when someone hovers over the icon and revert it back when the user hovers out of it. The above code seems to work but there are a couple issues. 1) It fires way to many times when I hover over it and 2) Many times, it doesn't change the attribute back to "far" (state = false). I tried attaching those events to <a> instead of <li> but the issues persist.
p.s. NOT using JQUERY

Something like this one ?
Here i adding and removing a class hover, but ti also may be any attribute or something else
window.addEventListener('mousemove', e => {
let hovered = document.querySelector('.hover');
if (e.target === hovered) return;
if (hovered) {
console.log('mouse out from', hovered);
hovered.classList.remove('hover');
}
if (!e.target.classList.contains('icon'))
return;
e.target.classList.add('hover');
console.log('mouse over on', e.target)
})
.icon {
display: inline-block;
width: 50px;
height: 50px;
transition: 100ms;
border: solid;
text-align: center;
line-height: 50px;
}
.hover {
color: red;
border-radius: 30%;
transform: rotate(10deg)
}
<div class="icon">1</div>
<div class="icon">2</div>
<div class="icon">3</div>
<div class="icon">4</div>
<div class="icon">5</div>
<div class="icon">6</div>
<div class="icon">7</div>

There are 7 'onmouse...' events...
onmousedown,
onmouseenter,
onmouseleave,
onmousemove,
onmouseout,
onmouseover,
onmouseup
... so it is important to use the right one for the job.
In the example clicking and mouse movement within the Element doesn't apply - all we want is a function to be called once when the mouse enters and the element and once agian when the mouse leaves. Therefore...
<!-- HTML -->
<a class="level-item like-icon">
<div class="icon is-medium">
<i class="far fa-heart"
onmouseenter="change(this)"
onmouseleave="change(this)"></i>
</div>
</a>
So here it seems sensible to use the onmouseenter and onmouseleave attributes to call the change() function, and in this case to passes the HTML Element under the mouse as an argument via the this keywords.
Now the function can scrutinize the element and check if it has the desired and required 'data-prefix' attribute, and if so what that attribute is set to. We can then use this condition to set/reset the 'data-prefix' attribute's value...
/* JavaScript */
change = (elem) => {
// does the Element have a 'data-prefix' attribute
// and what is it set to?
let isSet = elem.hasAttribute("data-prefix") && (
"far" === elem.getAttribute("data-prefix")
);
elem.setAttribute("data-prefix", (isSet ? "fas" : "far"));
}
However, as has already been mentioned using the Element.addEventListener() method is more robust and flexible than relying on HTML attributes like onmouse....

This sounds like a duplicate of How do I simulate a mouseover in pure JavaScript that activates the CSS ":hover"?
It's not ideal to deal with mouseover in pure JS, but here is a working example (insipired by an answer to the post I linked).
var element = document.getElementById('hoverIcon');
element.addEventListener('mouseover', function() {
console.log('Mouse over, set Font Awesome class here');
});
element.addEventListener('mouseout', function() {
console.log('Mouse out, remove Font Awesome class here');
});
var event = new MouseEvent('mouseover', {
'view': window,
'bubbles': true,
'cancelable': true
});
<a class="level-item like-icon">
<div class="icon is-medium">
<i id="hoverIcon" class="far fa-heart">ICON</i>
</div>
</a>

Here is a second version of my initial answer, this time with multiple elements.
var elements = document.getElementsByClassName("hover-icon");
var i;
for (i = 0; i < elements.length; i++) {
element = elements[i];
element.addEventListener('mouseover', function(data) {
console.log('Mouse over, set Font Awesome of ID ' + data.originalTarget.id + " here");
});
element.addEventListener('mouseout', function(data) {
console.log('Mouse out, remove Font Awesome of ID ' + data.originalTarget.id + " here");
});
}
var event = new MouseEvent('mouseover', {
'view': window,
'bubbles': true,
'cancelable': true
});
<a class="level-item like-icon">
<div class="icon is-medium">
<i id="hoverIcon1" class="hover-icon far fa-heart">ICON1</i>
<i id="hoverIcon2" class="hover-icon far fa-heart">ICON2</i>
</div>
</a>

Related

.trigger() jquery function is causing Uncaught RangeError: Maximum call stack size exceeded

I have two divs with dynamic links that sometimes are populated and sometimes aren't. If you a click a link that looks like this:
<a href="#">
Nothing happens, I prevent the default action, but if the link is:
<a href="/path/to/a/page">
It will follow.
I want to able to click on the surrounding div, and using the same logic as above. So if I click on the red and there is a valid link the link will follow. I'm using trigger() to attempt this.
The situation is below:
$(function() {
$(".container").click(function(e) {
var clickTarget = $(e.target).attr("href");
var clickTargetLink = $(this).find(".container-link");
if ((clickTarget != "#") || (clickTarget != undefined)) {
clickTargetLink.trigger("click");
} else {
e.preventDefault();
}
});
});
.container {
padding: 50px;
margin-bottom: 1rem;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<a class="container-link" href="google.com">link</a>
</div>
<div class="container">
<a class="container-link" href="#">link</a>
</div>
What ends up happening is clicking on the red div with google.com in the link throws:
jquery.js:3988 Uncaught RangeError: Maximum call stack size exceeded
at String.replace ()
at camelCase (jquery.js:3988)
at Data.get (jquery.js:4069)
at HTMLDivElement.dispatch (jquery.js:5146)
at HTMLDivElement.elemData.handle (jquery.js:4991)
at Object.trigger (jquery.js:8249)
at HTMLAnchorElement. (jquery.js:8327)
at Function.each (jquery.js:354)
at jQuery.fn.init.each (jquery.js:189)
at jQuery.fn.init.trigger (jquery.js:8326)
Maximum call stack size exceeded error
- this suggests that there is an infinity loop somewhere in this code. I'm lost as to how that could be the case here?
Why is this code causing a maximum call stack size error?
EDIT: more research led me to: Maximum call stack size exceeded on trigger click
going
.triggerHandler() just renders nothing and the link doesn't follow. Is there a different action I need to use that I'm not aware of?
EDIT #2: a few more details:
I cannot change/add markup structure.
the red div needs to be fully clickable, as there is another action tied to it when the link is empty: <a href="#">
I tried e.stopPropagation(); - which resolves the error, but then the link doesn't follow
I tried triggerHandler() - http://api.jquery.com/triggerhandler/
Your click handler is triggering a new click event, which is handled by the click handler, which triggers a new click event, which is handled by the click handler, which... eventually overflows the stack.
In comments (and now in the updated question) you've clarified the purpose of this code:
The red area needs to be clickable, not just the link. That's why I'm trying to use .trigger() so that I can trigger a click in the child link inside the div when the div is clicked on.
This necessitates a few more changes beyond just fixing your infinite loop:
Your existing code was looking for the href on the event target itself, which would not have allowed the red area to be clickable (because the event target isn't necessarily the element with an href attribute). I've changed this to ignore the event target (which isn't really what you care about) and instead just pluck the href from the link inside the container element.
In your if clause you used || when you meant && ("if it's not X or it's not Y" will always be true; "if it's not X and it's not Y" is what you meant there.)
The infinite recursion is resolved here by simply setting the window location directly, rather than firing another click event.
$(function() {
$(".container").click(function(e) {
var clickTarget = $(this).find("a.container-link").attr("href");
if (clickTarget && clickTarget !== "#") {
window.location=clickTarget;
} else {
// prevent link navigation from occurring
console.log("Blocking click");
e.preventDefault();
}
});
});
.container {
padding: 10px;
margin-bottom: 1rem;
background-color: red;
cursor: pointer
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<a class="container-link" href="https://example.com">Normal link</a>
</div>
<div class="container">
<a class="container-link" href="#">Blocked link</a>
</div>
You could optionally check to see if it is the link or the container that was clicked. If it was the link, do not trigger another click.
$(function() {
$(".container").click(function(e) {
var $containerLink = $(this).find('.container-link');
//check to see if the container was clicked, or if the link was clicked
var linkWasClicked = $(e.target).is('.container-link');
if (['#', undefined].indexOf($containerLink.attr('href')) > -1) {
e.preventDefault();
} else if (!linkWasClicked) { //only trigger the link if it was not clicked
$containerLink[0].click();
}
});
});
.container {
padding: 50px;
margin-bottom: 1rem;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<a class="container-link" href="https://google.com">link</a>
</div>
<div class="container">
<a class="container-link" href="#">link</a>
</div>
When the container is clicked, you trigger a click on the link inside it which propagates back to the container, clicking on the link again, and so on. You are recursively clicking on the link over and over again, which is causing the error. In your click event handler for the container, you need to check if the event's currentTarget is equal to the event's target to click on the link (preventing recursion). To trigger the click on the link to redirect to another page, you will need to get the actual DOM element to click on (not the jQuery object) like this: clickTargetLink[0].click() (jQuery always returns an Array of elements).
.container {
padding: 50px;
margin-bottom: 1rem;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<a class="container-link" href="http://www.example.com">link</a>
</div>
<div class="container">
<a class="container-link" href="#">link</a>
</div>
<script>
$(function() {
$(".container").click(function(e) {
var clickTarget = $(e.target).attr("href");
var clickTargetLink = $(this).find(".container-link");
if (e.currentTarget==e.target) {
clickTargetLink[0].click();
} else {
if(clickTarget!==undefined&&clickTarget == "#"){
e.preventDefault();
}
}
});
});
</script>
Because .container-link is inside .container the click event of the .container-link triggers a click also for .container unless you do stopPrpogation(), what causes the infinite loop.
Use stopPropagation() inside the if (after triggering the click).
as you can see in comments you are triggering click in the handler for the click which is causing to go in a loop . click-> handler-> trigger click-> handler --->
so basically attach handler to link itself, or if you want to attach it to parent only check if the target is link
you can also look at this thread for more breadth on topic In jQuery, how can I tell between a programmatic and user click?
Just improve your JS this way:
$(function () {
$(".container").click(function (e) {
var clickTarget = $(e.target).attr("href");
var clickTargetLink = $(this).find(".container-link");
if ((clickTarget === "#") || (clickTarget === undefined)) return false
})
})

JQuery - Not Switching Classes

It is late so I hope that I am missing something simple.
I have a div that uses a font-awesome fa-plus symbol. If clicked I want it to change to fa-minus and vice versa.
There is an additional class, add-team||remove-team, which is what the click event runs on.
It will change from + to - but not back to + when clicked a second time.
I have also done it from - to + and it doesn't change back to -.
Here is my div:
<div class="col-1 fa fa-plus add-team"></div>
And here are my simple jQuery lines:
$(".add-team").click(function () {
$(this).addClass("fa-minus remove-team").removeClass("fa-plus add-team");
});
$(".remove-team").click(function () {
$(this).addClass("fa-plus add-team").removeClass("fa-minus remove-team");
});
Hope that I'm just missing something simple. Thanks.
The problem is that your event listeners are only set to listen to elements that currently have those classes. The listeners do not dynamically change when the element's classes change. So since the element starts with add-team only the add-team listener is going to work.
What you can do is use setup a delegate event listener, a listener that is setup on a parent element that does not change, eg document and run the listeners off of that.
$(document).on('click','.add-team',function(){
//...
});
$(document).on('click','.remove-team',function(){
//...
});
But you can also just use a single event listener and use jQuery's toggleClass() to toggle all the classes in one call
$(this).toggleClass("fa-plus fa-minus add-team remove-team");
Demo
$(".col-1").click(function () {
$(this).toggleClass("fa-plus fa-minus add-team remove-team");
});
.col-1 {
font-size:24px;
font-weight:bold;
}
.fa-plus:after {
content:'+';
}
.fa-minus:after {
content:'-';
}
.add-team {
color:green;
}
.remove-team {
color:red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="col-1 fa fa-plus add-team"></div>

Manipulate FontAwesome with Javascript

I'm working on a little project to learn more about Javascript so I can work on svg animations in the future.
Now I'm working on a button that will change his input when you click on it.
circle = document.getElementById('circle');
$remove = document.getElementById('remove');
remove.style.display = "block";
$undo = document.getElementById('undo');
undo.style.display = "none";
circle.onclick = function() {
remove.style.display = "none";
undo.style.display = "block";
}
.circle {
width: 200px; height: 200px;
background-color: #10ac84;
border-radius: 50%;
}
.circle svg { font-size: 1.5em; }
<div class="circle" id="circle">
<i class="fas fa-trash-alt" id="remove"></i>
<i class="fas fa-undo" id="undo"></i>
</div>
It's a little hard to put it in a snippet because I can't load the font awesome cdn.
-| What I'm trying to do |-
When you enter the page the circle will have an icon on a trashcan.
When the user clicks the circle the trashcan will disappear and the undo icon will show up.
This is for removing an item in my application and undo it when you want it back.
-| Question |-
Can I remove and add style to FontAwesome icons? Because it will transform into SVG's
I hope that I have explained it well and that someone can help me out.
Project: http://www.projectnova.nl/changeClick/
Make sure to target the right element whenever you use the SVG with JavaScript method. There won't be an <i> for you to select after the replacement.
Font Awesome uses mutation observers to know when it needs to change the SVG after you change the class.
document.getElementsByTagName("button")[0].addEventListener("click", evt => {
const icon = evt.currentTarget.querySelector("svg");
if (icon.classList.contains("fa-user")) {
icon.classList.remove("fa-user");
icon.classList.add("fa-github-square");
} else {
icon.classList.remove("fa-github-square");
icon.classList.add("fa-user");
}
});
<script defer src="https://use.fontawesome.com/releases/v5.0.12/js/all.js" integrity="sha384-Voup2lBiiyZYkRto2XWqbzxHXwzcm4A5RfdfG6466bu5LqjwwrjXCMBQBLMWh7qR" crossorigin="anonymous"></script>
<button><i class="fas fa-user"></i></button>
Be advised, this method only works when using Font Awesome with CSS. To use the JavaScript with SVG method, look at the other answer.
This would be easier to accomplish if you used a single i tag and dynamically changed the icon class.
For example:
<div class="circle">
<i class="fas fa-trash-alt" id="remove"></i>
</div>
Then you just flip between the fa-undo and fa-trash-alt classes.
// select the element
var removalIcon = document.querySelector("#remove");
// add event listener
removalIcon.addEventListener("click", function () {
this.className = "fas fa-undo";
});

Display elements in order of click

How can I have elements .show in the order they're clicked and not the order they're appear in the HTML using jquery?
E.g.
Css code:
.sq{
display:none;
}
Html Code:
A
B
C
<span class="sq" id="01">a</span>
<span class="sq" id="02">b</span>
<span class="sq" id="03">c</span>
JavaScript code:
$("#1").click(function(){
$("#01").show();
});
$("#2").click(function(){
$("#02").show();
});
$("#3").click(function(){
$("#03").show();
});
Using this code if I click C,B,A the output will arrange "a b c"
What I would like is if I click C,B,A the output should arrange "c b a"
I've tried various CSS positioning rules to do this, but the best I can do is have them arrange in the same position as each other. I realize I could make a new class for each but would rather not do it that way in the interest of minimal code and I'm learning right now so it would be useful to know a better way around the issue.
Here's a fiddle: http://jsfiddle.net/xuxsuagg/4/
You can do something like
$(".myclass").one('click', function() {
$($(this).data('target')).appendTo('.output').show();
});
a {
text-decoration: none;
}
.sq {
display: none;
}
.output {}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
A
B
C
D
E
F
<p class="output">
<span class="sq" id="01">A</span>
<span class="sq" id="02">B</span>
<span class="sq" id="03">C</span>
<span class="sq" id="04">D</span>
<span class="sq" id="05">E</span>
<span class="sq" id="06">F</span>
</p>
Notes
Used a common event handler instead of using different handlers for each link
Before shown the element the target is moved to the last position of the parent
Used .one() to register the handler so that one element is shown only once
There is a very simple trick: use .append(). When you append a selected element that is already present in the DOM, you are actually moving it around. Also, I recommend that to optimize your code, you can:
Use a common class for the <a> elements
Assigned a HTML5 data- attribute, say data-target, to specify the ID of its intended target
Listen to click events triggered on the common class
An example of the proposed new markup:
A
B
<!-- and more -->
Here is the code (and the demo fiddle here—http://jsfiddle.net/teddyrised/xuxsuagg/9/)
$('.sq-click').click(function(e) {
// Prevent default action
e.preventDefault();
// Use .append() to move element
var $out = $('.output');
$out.find('#'+$(this).attr('data-target')).appendTo($out).show();
});
On a side note, if you do not want the users to rearrange the order after an anchor has been clicked, you will have to rely on the .one() method for listening to click events. Also, it will help that you style the disabled anchors appropriately so the users can see it—see proof-of-concept demo: http://jsfiddle.net/teddyrised/xuxsuagg/26/
$('.sq-click').one('click', function(e) {
// Prevent default action
e.preventDefault();
// Use .append() to move element
var $out = $('.output');
$out.find('#'+$(this).attr('data-target')).appendTo($out).show();
// Add class to change appearance of disabled <a>
$(this).addClass('disabled');
});
And your CSS can look like this:
.disabled {
cursor: default;
opacity: 0.2;
}
You can simplify this code to:
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function() {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
a {
text-decoration: none;
}
.sq {
display: none;
}
A
B
C
D
E
F
<p class="output">
</p>
Bind the click event to the class that they share and not their own unique id.
In the function scope of clickClosure 'this' is referring to the current element.
$(".sq").click(function clickClosure(){
$(this).show();
});
Using styles to achieve this might range from painful to very hard, depending on the exact way you want them displayed. I'd suggest instead to re-order them in DOM. That might look something like this:
<a id="link-1">...</a>
...
<div style="display: none" id="hidden-items">
<span id="item-1">...</span>
</div>
<div id="visible-items"></div>
&
$('#link-1').click(function () {
$('#visible-items').append($('#item-1'));
});
As other respondents suggested, you could also optimize your code in various ways, but that's outside the scope of the question.
Try this: You can put empty <p class="output"> and remove display:none; from CSS .sq{... In jQuery, create a <span> on the basis of clicked link and append it to <p class="output">
HTML:
A
B
C
D
E
F
<p class="output">
</p>
CSS:
.sq{
/*display:none;*/
color: green;
margin-right: 10px;
}
jQuery
$("a[href='#']").click(function(e){
e.preventDefault();
var text = '<span class="sq" id="0'+$(this).prop('id')+'">'
+$(this).text()+'</span>';
$(text).appendTo('p.output');
});
DEMO

Prevent CSS :hover style propagation

In the following example, when I mouse over the 'X' button, the list-item hover style gets enabled as well, I do not want this to happen.
Is it possible to have a hover style on the button independent of the hover style on the list-group-item? Something like prevent the 'hover' propagation?
Is there any other way to achieve that? Maybe assembling all of this HTML/CSS/JS in a different way?
Working sample here
<ul class="list-group">
<li class="list-group-item">
Lalalalaiaia
<button class="btn btn-default btn-xs pull-right remove-item">
<span class="glyphicon glyphicon-remove"></span>
</button>
</li>
<li class="list-group-item">
Panananannaeue
<button class="btn btn-default btn-xs pull-right remove-item">
<span class="glyphicon glyphicon-remove"></span>
</button>
</li>
</ul>
CSS
.list-group-item:hover {
background: #fafafa;
cursor: pointer;
}
JavaScript
$('.list-group-item').on('click', function(){
console.log('clicked item');
});
$('.remove-item').on('click', function(e){
console.log('clicked remove-item btn');
e.stopPropagation();
});
UPDATE
The problem seems to be that when hovering the inner X button, the mouse actually doesn't leave the 'list-group-item' element, thus, it keeps the hover state.
I was able to solve it by manually dispatching mouseenter and mouseleave on the 'list-group-item' in the mouseleave and mouseenter event of the 'remove-item' button, respectively, without the need to use 'event.stopPropagation()' (except for the button click handler).
The drawback is that I need a mouseenter and a mouseleave event handler for both elements. Preferably I'd use only CSS, but that seems to be impossible.
I'm just not sure whether this is a clean solution, what do you think?
Working sample here
CSS
.list-group-item.mouseover {
background: #fafafa;
cursor: pointer;
}
.list-group-item .remove-item.mouseover {
background: #aaf;
cursor: pointer;
}
JavaScript
// LIST-ITEM EVENT HANDLERS
$('.list-group-item').on('mouseenter', function(e){
$(this).addClass('mouseover');
}).on('mouseleave', function(e){
$(this).removeClass('mouseover');
});
$('.list-group-item').on('click', function(){
console.log('clicked item');
});
// LIST-ITEM REMOVE BUTTON EVENT HANDLERS
$('.remove-item').on('mouseenter', function(e){
$(this).addClass('mouseover');
$(this).parent().mouseleave();
}).on('mouseleave', function(e){
$(this).removeClass('mouseover');
$(this).parent().mouseenter();
});
$('.remove-item').on('click', function(e){
console.log('clicked remove-item btn');
e.stopPropagation();
});
This is impossible to do with CSS only, except the not-so-clean way described by #Pointy.
You can do this with javascript by using event.stopPropagation(). So your hover style should become a class that you toggle on mouseover.
This question is a duplicate of css :hover only affect top div of nest
You can make a negation caluse like Pointy suggests but a more solid solution involves adding an extra node. The idea is that the row and the button become proper siblings since you can't style a TextNode.
<ul class="list-group">
<li class="list-group-item">
<div>Lalalalaiaia</div>
<button class="btn btn-default btn-xs pull-right remove-item">
<span class="glyphicon glyphicon-remove"></span>
</button>
</li>
<li class="list-group-item">
<div>Panananannaeue</div>
<button class="btn btn-default btn-xs pull-right remove-item">
<span class="glyphicon glyphicon-remove"></span>
</button>
</li>
</ul>
Now you can do:
.list-group-item div:hover {
background: #fafafa;
cursor: pointer;
}
You will need some extra trickery to get the button in the right place, like:
// untested
.list-group-item {
position: relative;
}
.list-group-item button {
position: absolute;
top: 5px;
left: 5px;
}
Ok so there is actually a solution that only requires the use of CSS (no HTML or JS stuff)
The following selector will only select those elements with the class "parent" on hover, which do not have a child with the class "child" that is also being hovered on.
.parent:has(:not(.child:hover)):hover {}
The only problem I can see with the :has() selector/pseudo class is browser support (especially older versions) - so before you use it check the currerrent compatibility lists to see if it fits your requirements.
I could not find an answer that worked in all cases, and was also simple to implement. Sadly, there appears to be no consistent solution that is purely CSS and/or requires special arrangements of the HTML.
Here is a jQuery solution that seems to work in all cases.
Any element with .ui-hoverable will receive a .ui-hover class that does not propagate. So you can stack .ui-hoverable elements and only the top-most under the mouse will have the .ui-hover class.
$('.ui-hoverable').each(function() {
var el = $(this);
el.on('mousemove', function () {
var parent = $(event.target).closest('.ui-hoverable');
if(parent.length && parent[0] == el[0]) {
el.addClass('ui-hover');
return;
}
el.removeClass('ui-hover');
});
el.on('mouseleave', function () {
el.removeClass('ui-hover');
});
});
This works because the mousemove event searches for the closest .ui-hoverable and if it is not the current element the .ui-hover is removed. So the top most will receive the .ui-hover and an element under it will have it removed.
Enjoy, report any problems.
Thanks,

Categories

Resources