JavaScript DOM parent/child relation on mouse event - javascript

O.K. Maybe I have asked something similar to this, but this is another problem I have. I will start by showing the code:
var MyClass = {
// Set up the events for the parent
// MyParent is a DIV
SetEvents: function() {
MyParent.onmouseover = function() { MyClass.MouseHover(MyParent); };
MyParent.onmouseout = function() { MyClass.MouseOut(MyParent); };
},
// Function activated when moving the mouse over the parent
MouseHover: function(Parent) {
if (Parent) {
// Here I create the child, when moving the mouse over the parent control
// The child is an IMG
var child = document.createElement("img");
child.id = "child";
child.src = "child.png";
child.style.float = "right";
Parent.appendChild(child);
}
},
// Function activated when moving the mouse out of the parent control
MouseOut: function(Parent) {
if (Parent) {
var child = document.getElementById("child");
if (child) {
// On mouse out I remove the child
Parent.removeChild(child);
}
}
}
}
So, this is just about a simple image appearing when I move the mouse over a div, then disappears when I move the mouse out. Yet, this is not behaving as I want.
The problem is that when, while the mouse is over the parent div, if I move it over the child img (still over the parent), the MouseOut() function is fired.
I have been searching for solutions and tricks all these days but everything was in vain. Now, I hope somebody can identify the problem. There must be some JavaScript chapter that I missed out. Thanks to everybody!
I added some images, to explain this better
UPDATES
Looks like this happens only when the IMG child is created dinamically. If I create it before and only change it's src, it works.
JSSFIDLE DEMO
You can see this script working (well, not working, better said) at http://jsfiddle.net/E9q6e/1/

You can try to check, if Parent contains the child in MouseOut():
var ref=event.toElement || event.relatedTarget; // This line is pseudocode
if(!Parent.contains(ref)){
Parent.removeChild(child);
}

Related

JavaScript - weird bug doesn't remove element

I have a weird problem with my simple to-do app. Its main features are adding new elements to "ul" and clicking on "li" to change its class. After clicking element it also adds an "i" with font-awesome icon and after clicking it again it should remove it. It works fine when I click added elements from top to bottom, but when I click from bottom to top, "i" is removed only from the first element from top. I can't figure out why is that, so I would appreciate any help.
Here is live demo where you can reproduce this bug:
https://michalgrochowski.github.io/to-do/
And here is the code responsible for that action:
document.getElementById("list").addEventListener("click", function(e) {
if (e.target && e.target.matches("li.item")) {
e.target.className = "itemDone";
var check = document.createElement("i");
check.className = "fa fa-check done";
e.target.appendChild(check);
} else if (e.target && e.target.matches("li.itemDone")) {
e.target.className = "item";
var done = document.getElementsByClassName("done");
var i;
for (i = 0; i < done.length; i++) {
e.target.removeChild(done[i]);
};
};
});
I've tested it in firefox and chrome, so it's not browser-related, it must be something in the code. I was thinking maybe the problem is with the loop inside the function, but I don't know how to change it.
var done = document.getElementsByClassName("done"); gets a list of all the .done elements in your document (including those who aren't child elements of e.target). So when executing e.target.removeChild(done[i]) if the current done[i] is a child of e.target, it will be removed, otherwise it will throw an error saying that the element done[i] is not a child of e.target. Try this:
var done = e.target.getElementsByClassName("done");
Note: if there could be sub-elements (children of children ...) of e.target that have the class .done. This will throw an error too. Because then the sub-children won't be direct children of e.target. removeChild work only on direct children. You can avoid trouble by using this:
var done = e.target.children; // to get the direct children only
var i;
for (i = 0; i < done.length; i++) {
if(done[i].matches('.done') // if it matches the criteria
e.target.removeChild(done[i]); // remove it
}

Remove an active class by clicking on a parent's brother item in JavaScript

I am working on a get a card script, that consists of 2 phases.
Selecting a card > opens a relevant window on the right
Pressing an Apply now button on that opened window > opens a relevant window on the bottom.
Right now everything works as intended, except:
I want to make so that if a person removes a card selection (.gacca1 / .gacca2), it also removes the "activated" class from the "Get card" div (.gaccaiGET), thus removing the bottom div (.gara). Right now, it keeps the "Get a card" activated, even if you deactivate the card selector.
No matter how I try (with my beginner js skills), i can't make it work.
Also, if you look at the html and css, you will see that most of the elements are positioned absolute, which is a very bad way of making this work. Unfortunately, I could not figure a way to make the positions relative, because the
.divclass.active + .otherdivclass { css }
works only for brother elements, and, even, only for the brother elements that are next to each other. Which resulted in writing poor html and css, to make it look and work the way I want it to. If you have any tips on this matter, please let me know!
Thanks
http://jsfiddle.net/4XM9A/10/
(function () {
var links = Array.prototype.slice.call(document.querySelectorAll('.gacca1'));
var links2 = Array.prototype.slice.call(document.querySelectorAll('.gacca2'));
var links3 = Array.prototype.slice.call(document.querySelectorAll('.gaccaiGET'));
var toggleClass = function (className, element) {
element.classList.toggle(className);
};
var clickHandler = function (linksToDisable) {
return function () {
toggleClass('active', this);
linksToDisable.filter(function (link) {
return link.classList.contains('active');
})
.forEach(function (link) {
toggleClass('active', link);
});
};
};
links.forEach(function (link) {
link.addEventListener('click', clickHandler(links2));
});
links2.forEach(function (link) {
link.addEventListener('click', clickHandler(links));
});
links3.forEach(function (link) {
link.addEventListener('click', function () {
toggleClass('active', this);
});
});
}());
As far as I understand you should firstly toggle '.active' of all divs of the '.gaccai' that goes after the ".active" parent. Try changing function toggleClass a bit:
var toggleClass = function (className, element) {
var array = document.querySelectorAll('.' + element.classList + ' + .gaccai .active');
for (var i = 0; i < array.length; i++) {
array[i].classList.toggle(className);
}
element.classList.toggle(className);
};

JavaScript removeChild does not remove div element

I am facing a problem with JavaScript removeChild function and unfortunately its the first time I can't find what's wrong on the forum. Here is the scenario:
-Create div elements with a for loop and put the word test in it
-When one of those div is clicked, append to it the div element stored in a Foo object
-If the div from Foo object is clicked, remove it from DOM
Here is the code I use. Tried in IE and FF. No error is displayed, removeChild SEEMS to work ok, BUT the foo div is never removed. Hopefully someone can help me find out why that doesn't work.
function Foo() {
this.container = document.createElement("div");
this.container.appendChild(document.createTextNode("foo"));
this.container.onclick = function() {
// in the function'this' refers to the clicked element, i.e. container
console.log(this.parentNode); // the clicked div has a parent
console.log(this.parentNode.removeChild(this)); // the div is removed from DOM
console.log(this.parentNode); // null, the clicked div has no more parent but...
// foo is STILL DISPLAYED ?!
}
}
var foo = new Foo();
function Test() {
// create 3 div with the word test inside
for(i=0; i<3; i++) {
var t = document.body.appendChild(document.createElement("div"));
t.appendChild(document.createTextNode("test"));
// when test div is clicked, append to it the container (a div element) from object foo
// due to the for loop we need a closure for t
t.onclick = function(t){ return function() {
t.appendChild(foo.container); // here it works great and t is moved to the clicked test div
}; }(t);
}
}
new Test();
Many thanks for taking the time to investigate my issue
When you click the container with the word foo in it, you also click the t div with the word test in it. Which has a click handler that does append your foo.container back to the element from which your foo.container.onclick handler has just removed it.
Quick fix: After the container has handled the click event, it stops propagation of the event to elements higher in the DOM tree:
…
this.container.onclick = function(e) {
// in the function'this' refers to the clicked element, i.e. container
console.log(this.parentNode); // the clicked div has a parent
console.log(this.parentNode.removeChild(this)); // the div is removed from DOM
console.log(this.parentNode); // null, the clicked div has no more parent
e.stopPropagation();
// foo is NO MORE DISPLAYED
}
…
Advanced fix: Don't attach the handler that adds foo to the t element, but only to the test word. You will need an extra element for that, though:
…
var t = document.body.appendChild(document.createElement("div"));
var s = t.appendChild(document.createElement("span"));
s.appendChild(document.createTextNode("test"));
// when test div is clicked, append to it the container (a div element) from object foo
// due to the for loop we need a closure for t
s.onclick = function(s){ return function() {
s.appendChild(foo.container); // here it works great and t is moved to the clicked test div
}; }(s);
…
A variant of this would be to not append the foo.container to t itself, but as a sibling or so.
you added onclick event to the container, so the correct code would be:
function Foo() {
this.container = document.createElement("div");
this.container.appendChild(document.createTextNode("foofoofoooooo"));
this.container.onclick = function() {
this.removeChild(this.firstChild);
}
}
var foo = new Foo();
function Test() {
// create 3 div with the word test inside
for(i=0; i<3; i++) {
var t = document.body.appendChild(document.createElement("div"));
t.appendChild(document.createTextNode("test"));
// when test div is clicked, append to it the container (a div element) from object foo
// due to the for loop we need a closure for t
t.onclick = function(t){ return function() {
t.appendChild(foo.container); // here it works great and t is moved to the clicked test div
}; }(t);
}
}
new Test();
I tested and it works!

Javascript return id of the element that triggers the event

I have a range of divs (projects) which have a display:none-ed overlay container inside of them, containing additional info.
If the mouse enters the outer div, that overlay container should receive another class making it visible. On mouse leaving the class should be removed.
I solved it using onmouseover="setactive('DIV ID')", but it made the code look pretty messed up so I tried to switch to Eventlisteners. It won't work though and I can't figure out why.
This is my script so far:
// Init Eventlisteners for each container
window.addEventListener("load", start, false);
function start() {
var project_containers = document.getElementsByClassName('content-project')
for (var i = 0; i < project_containers.length; i++) {
project_containers[i].addEventListener("mouseover", setactive(), false)
project_containers[i].addEventListener("mouseout", setinactive(), false)
}
}
// If mouse is over container, add overlay_active class
function setactive() {
var container = document.getElementById(event.currentTarget);
var overlay_class = container.getElementsByClassName("element-overlay")[0];
if (!(overlay_class.className.match(/(?:^|\s)overlay_active(?!\S)/))) {
overlay_class.className += " overlay_active";
}
}
// If mouse is outside the container again, remove overlay_active class
function setinactive() {
var container = document.getElementById(event.currentTarget);
var overlay_class= container.getElementsByClassName("element-overlay")[0];
if (overlay_class.className.match(/(?:^|\s)overlay_active(?!\S)/)) {
overlay_class.className = overlay_class.className.replace(/(?:^|\s)overlay_active(?!\S)/g, '')
}
}
You don't need the id to set container, your functions could be like this:
function setinactive(e) {
var container = e.currentTarget;
//your code
}
}
And then the call:
project_containers[i].addEventListener("mouseout", setinactive, false);

Erratic mouseover behavior with nested items inside mouseover layer

So let's say we have:
A container for everything
A baseDiv inside that container
//let's create a base layer
var container = document.getElementById('container')
var baseDiv = document.createElement('div')
baseDiv.id = 'baseDiv'
baseDiv.innerText = 'this is the base div'
baseDiv.addEventListener('mouseover', createLayer)
container.appendChild(baseDiv)
When the user mouses over:
A layerOnTop, of the same size is put on top of the baseDiv.
When the user mouses out:
The layerOnTop is removed.
function createLayer(){
console.log('creating layer')
layerOnTop = document.createElement('div')
layerOnTop.id = 'layerOnTop'
layerOnTop.addEventListener('mouseout',
function(){
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})
container.appendChild(layerOnTop) }
Simple and works great.
However, when layerOnTop contains elements as well (buttons, inputs), the behavior gets very erratic and starts flicking as you're technically exiting the layerOnTop.
//it contains two textareas
layerOnTop.appendChild(document.createElement('textarea'))
layerOnTop.appendChild(document.createElement('textarea'))
I wish I could use mouseenter but it doesn't seem to be supported by Chrome.
Here's my jsfiddle: http://jsfiddle.net/DjRBP/
How can I stop this? I wish I could merge the textareas and layerOnTop into one large mouseover-handling conglomerate.
You need to check in your mouse out event that it's actually leaving the element. Change your mouseout function to:
function(event) {
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
// We're not actually leaving the parent node so don't remove layer
return;
}
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})

Categories

Resources