Reputation: 574
I have a container div containing other div elements:
<div id="container">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
How can I rewrite the following jQuery in pure JS?
$('#container').on('mouseenter', 'div', myFunction)
That is, how can I listen which child mouse is hovering over in order to apply to it myFunction
? I've tried the following, but this only applies myFunction
to container div:
container.addEventListener('mouseenter', function(event) {
let hoveredElement = event.target;
if (hoveredElement.tagName === 'DIV') {
myFunction(hoveredElement);
}
});
#container { width: 200px; }
#container > div { height: 100px; background-color: red; }
<div id="container">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
I've also tried event.currentTarget
, but it still gives the same results.
p.s. I do not want to put event listeners on children of the container div, because there is a great number of children.
Edit: I've removed console.log(hoveredElement);
It was a line for debugging.
Upvotes: 0
Views: 263
Reputation: 8670
What you're trying to do is a practice called Event Delegation. It's pretty easy to do given the state of JavaScript today. To replicate JavaScript syntax you're used to I created a returnable object to use.
You use $get( query )
to get the element you want to perform delegation on. From there you use the on
method with the parameters (event, query for child elements, function)
When you're writing the function for the on
method, it will be passed the event
parameter, per usual, but the next parameter will be the index of the element that is, in this case, mouse overed.
There is no safe guard for the parameters because that's not part of the question and it's something that can easily be solved with a little elbow grease :)
let $get = (query) => {
if (document.querySelector(query)) {
let returnableObj = {
ele: document.querySelector(query),
list(q) {
return Array.from(this.ele.querySelectorAll(q))
},
getIndex(ele) {
return function(q) {
let foundIndex;
returnableObj.list(q).filter((itm, ind) => {
let bool = ele.isSameNode(itm);
if (bool) foundIndex = ind;
return bool;
});
return foundIndex >= 0 ? foundIndex : false;
}
}
}
returnableObj.on = function(evt, childquery, fn) {
this.ele.addEventListener(evt, function(e) {
if (e.target.matches(childquery)) {
let myIndex = returnableObj.getIndex(e.target)(childquery);
fn(e, myIndex);
}
});
}
return returnableObj;
} else alert('invalid query');
}
let $get = (query) => {
if (document.querySelector(query)) {
let returnableObj = {
ele: document.querySelector(query),
list(q) {
return Array.from(this.ele.querySelectorAll(q))
},
getIndex(ele) {
return function(q) {
let foundIndex;
returnableObj.list(q).filter((itm, ind) => {
let bool = ele.isSameNode(itm);
if (bool) foundIndex = ind;
return bool;
});
return foundIndex >= 0 ? foundIndex : false;
}
}
}
returnableObj.on = function(evt, childquery, fn) {
this.ele.addEventListener(evt, function(e) {
if (e.target.matches(childquery)) {
let myIndex = returnableObj.getIndex(e.target)(childquery);
fn(e, myIndex);
}
});
}
return returnableObj;
} else alert('invalid query');
}
let ele = $get("#mydiv");
ele.on("mouseover", ".child", function(e, ind) {
alert('Peanut Butter! Child ' + (ind+1));
})
h1,h2,h3 {
width: 200px;
}
<div id="mydiv">
<span>hiya</span>
<h1 class="child"> child 1 want some candy?</h1>
<h2 class="child"> child 2 I choose you, spirit dragon</h2>
<h3 class="child"> child 3, Scoreboard </h3>
</div>
Upvotes: 0
Reputation: 3782
I debugged jQuery on Chrome, and the jQuery mouseenter
event is actually using a mouseover
event under the hood.
In the jQuery source, line 4095, there's a line that's run every time an event is triggered:
jQuery.event.dispatch.apply( elem, arguments );
If you pause there and inspect arguments
, you'll find that it's a MouseEvent with a type of mouseover
.
Which sort of makes sense, since you're using jQuery's delegate event handler syntax, and jQuery is being clever enough to substitute mouseenter
which doesn't bubble and doesn't trigger as you move through the event handler's descendents with mouseover
which does fire when you move through the listener's children.
So, your solution is essentially going to be doing what jQuery is doing--use mouseover
:
container.addEventListener('mouseover', function(event) {
console.log("mouseover target", event.target);
console.log("mouseover currentTarget", event.currentTarget);
});
container.addEventListener('mouseenter', function(event) {
console.log("mouseenter target", event.target);
console.log("mouseenter currentTarget", event.currentTarget);
});
$('#container').on('mouseenter', '.child', function(event) {
console.log('jquery mouseenter target', event.target);
console.log('jquery mouseenter currentTarget', event.currentTarget);
console.log('jquery mouseenter delegateTarget', event.delegateTarget);
});
.child {
border: 1px solid #007;
height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js"></script>
<div id="container">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
Upvotes: 1
Reputation: 2537
You need to start at the e.target
, and traverse up the ancestors, invoking the handler on each div
that you find. You stop traversing the ancestors when you get to the bound element.
Also change the event type to mouseover
, since mouseenter
doesn't bubble, and so the event.target
is always the bound element.
const container = document.querySelector("#container");
container.addEventListener('mouseover', function(event) {
let el = event.target;
do {
if (el.tagName === 'DIV') {
myFunction.call(el, event);
}
} while((el = el.parentNode) !== this);
});
function myFunction(event) {
console.log(this.id);
}
<div id=container>
<section>
<div id=foo>
<p>Hover me to invoke the function</p>
</div>
<p>This one does not invoke</p>
</section>
</div>
Didn't notice that you had lowercase "div"
, so I uppercased that too.
Note that you can abstract this out to a reusable function for convenience. You can also make it more generic by using a CSS selector and .matches()
instead of being limited to tag name comparison.
const container = document.querySelector("#container");
delegate("mouseenter", container, "div", myFunction);
function myFunction(event) {
console.log(this.id);
}
// Reusable event delegation maker
function delegate(type, container, selector, handler) {
if (type === "mouseenter") {
type = "mouseover";
} else if (type === "mouseleave") {
type = "mouseout";
}
container.addEventListener(type, function(event) {
let el = event.target;
do {
if (el.matches(selector)) {
handler.call(el, event);
}
} while ((el = el.parentNode) !== this);
});
}
<div id=container>
<section>
<div id=foo>
<p>Hover me to invoke the function</p>
</div>
<p>This one does not invoke</p>
</section>
</div>
Upvotes: 0