3

I'm new to jQuery and am facing now a weird problem. I succeeded to narrow it down to the fact that a mouseenter event is called twice: once for the containing div (this was my intention) and again for elements within this div (not good). I tried to use return false and stopPropagation but it doesn't seem to work. Here's the code:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>

<title></title>


<!-- JS files (order matters!) -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.js"></script>


<script type="text/javascript">
$(function (){
    $(".testDiv").hover(
    function(e) /* IN */ {
        $(this).data("htmlBackup", $(this).html());
        $(this).html("TEST 123");
        e.stopPropagation();
        return false;
    }, function(e) /* OUT */ {
        $(this).html($(this).data("htmlBackup"));
        e.stopPropagation();
        return false;
    });
});         
</script>
<!-- this one works -->
<div class="testDiv" style="border: solid">ORIG HTML</div>

<br /> <br /> <br />

<!-- this doesn't work -->
<div class="testDiv" style="border: solid"> <p style="border: solid">ORIG HTML</p></div>

</body>
</html>

You can also see it here: http://jsfiddle.net/rFqyP/3/

Any help will be very much appreciated!

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Raviv
  • 33
  • 1
  • 3

4 Answers4

3

What's happening is that the mouseenter event is firing twice in a row (you can test this easily with some console.log calls). This is problematic since is will change the html of the element (and make it just the "test" string), and then on the second consecutive run it will take the html and save it to the element's data. But since there was no mouseleave event fired, as mentioned, the html is the "test" string, so now it saves that to the element data.

After that, the mouseenter and mouseleave events continue to fire just fine, but both the element's html and its data have the same "test" string, so it doesn't change.

The reason the mouseenter fires twice consecutively is because of the borders. The div's dimensions change and so the following series of events takes place:

  1. mouseenter (div becomes thinner)
  2. mouseleave (div becomes thicker which will cause an almost automatic mouseenter)
  3. mouseenter as mentioned in 2 because even though the mouse left the div, the div expanded, and so the mouse entered. But since mouseenter fired, the div went to the "test" string, and became thinner, so now the mouse is beyond the boundaries of the div, but without firing a mouseleave.
  4. Next time I mouseover the div, mouseenter will fire again <== this is twice in a row

You can see this happening, if for example you enter with your mouse slowly some of the above events don't happen and it will actually work as expected.

As an aside, I don't think it's the best idea to be swapping html contents. If you want to toggle things, I suggest hide() and show(). Firstly, that saves event handlers, and it makes more intuitive sense. You're interested in hiding things, not serialising and saving.

davin
  • 44,863
  • 9
  • 78
  • 78
  • correct. he must have some checks whether the callee element is div. Also why the p is calling events even though it's not attached to? – Umair A. Jul 31 '11 at 09:48
  • I came to the same conclusion. – Guffa Jul 31 '11 at 09:49
  • @Umair Ashraf: The `p` element doesn't cause any events at all, it's only the size change of the `div` that causes the extra events. You can see this by logging the tag name of the elements: http://jsfiddle.net/rFqyP/14/ – Guffa Jul 31 '11 at 09:51
  • changing "this.tagName" to "e.target", it shows me paragraph too. – Umair A. Jul 31 '11 at 09:54
  • @Umair: from the jquery docs (http://api.jquery.com/event.target/): `The target property can be the element that registered for the event or a descendant of it.` – davin Jul 31 '11 at 09:55
  • Strange. Why descendant is there when the callee has to be just the div? – Umair A. Jul 31 '11 at 09:57
  • @davin: thanks for the thorough investigation!! I am not sure it has to do with the borders but I will check it now. Maybe I should use hide()/show() - I'll be sure to check it out. Umair, Guffa, Nicola: thanks for your help! I'll be sure to update once I'll try it where the promlem originally appeared. – Raviv Jul 31 '11 at 10:12
  • 1
    @Umair Ashraf: You have to distinguish between events and event handlers. Events always occur even if there is no event handler, so the `mouseover` event occurs for the `p` element and bubbles up to the `div` element where there is an event handler for it. The `target` property is the element where the event occured, not the element where it's handled. – Guffa Jul 31 '11 at 10:13
  • Ok, here are some conclusions after further checks: (1) using 'event.target == this' indeed prevents '

    ' from sending events. (2) I suspect that using 'hide() & show()' will not solve this problem since they will be event triggered anyhow (though I agree that it is more elegant to use them). (3) the main problem is not solved since I have elements within the 'div' that reside on its boundaries, that means that if a user hovers its cursor there I must receive events that they fire otherwise I won't be able to switch the contents correctly. This sucks... *Any suggestions??*

    – Raviv Jul 31 '11 at 11:48
  • @Raviv, you could give the div a height that is big enough to contain everything. You can do this statically with css or dynamically with jquery. This will solve your problem. – davin Jul 31 '11 at 12:06
  • @Raviv: (1) It's not a problem that the `p` element catches events as you don't have an event handler for it. (2) It would make the code simpler. Another alternative is to use `toggleClass` and use CSS to show and hide elements. (3) It's not a problem having child elements. The problem occurs when the element changes size by hovering it. – Guffa Jul 31 '11 at 20:09
  • @guffa: (1) yes, you are correct, the problem is that the containing element's event handler is called for child elements' events and I couldn't find a way to manage it properly the tweak I did on your 'inside' suggestion did the trick. (2) you have a point. I feel more comfortable holding it as data and swapping it for some reason. (3) no, the problem is that when a child element shares an external border with its containing element I get only one event fired (only from the child) so I have to take it into account. – Raviv Aug 01 '11 at 07:13
1

You can keep the code from getting stuck by using a flag, so that you can detect when you get double mouseenter events:

$(function(){

  var inside = false;

  $(".testDiv").hover(
    function(e) /* IN */ {
      if (!inside) {
        inside = true;
        $(this).data("htmlBackup", $(this).html());
        $(this).html("TEST 123");
      }
    }, function(e) /* OUT */ {
      inside = false;
      $(this).html($(this).data("htmlBackup"));
    }
  );

});

http://jsfiddle.net/rFqyP/16/

This will however not solve the problem with the size difference. When you leave the element by moving out by the bottom border, it grows and causes a mouseenter event, which again changes the size so that the mouse is outside but without causing a mouseleave event, leaving the element looking like the mouse is still hovering it.

Remving the border from the p elements solves the problem completely, without a need for a flag, as it's the border that is causing the size difference.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • It seems that this is the way to do it. I was hoping for a more elegant solution but I guess that there isn't any. I thank you very much for all the help. I would like to take this opportunity to thank everyone else too. – Raviv Jul 31 '11 at 19:57
  • One last comment - I also wrapped the hover-out callback with an 'if (event.target == this) {..}' to make sure that there won't be any internal "race" conditions when leaving an internal element. Hope this will help anyone that will encounter similar issues. – Raviv Jul 31 '11 at 20:08
0

i think that the second one does'nt work because the html is different. If you use the same html you don't need stopPropagation or return false at all. Look at the fiddle: http://jsfiddle.net/aC3TG/1/

$(function (){
    $(".testDiv").hover(
    function(e) /* IN */ {
        $(this).data("htmlBackup", $(this).html());
        $(this).html("TEST 123");
    }, function(e) /* OUT */ {
        $(this).html($(this).data("htmlBackup"));

    });
});         
Nicola Peluchetti
  • 76,206
  • 31
  • 145
  • 192
  • he has "p" tag inside the div which is firing its events too and causing the issue. simply removing "p" tag isn't the solution I believe – Umair A. Jul 31 '11 at 09:46
  • Yes, but the p tag gets deletd after the element hovers on the div: in this case i'd change the html, i don't see the point in keepin it like it is – Nicola Peluchetti Jul 31 '11 at 10:04
  • No, the `p` tag doesn't have any event handlers, so it's not its precence that is causing the problem. Removing the border from the `p` element solves the problem, as that is what is causing the size difference. – Guffa Jul 31 '11 at 10:36
  • @Nicola: this is just a sample code that demonstrates my problem. my real html is a div that contains several 'p's and 'img's and other 'div's too. And these elements have boundaries and fixed sizes as well so changing that is not an option. – Raviv Jul 31 '11 at 11:52
0

The issue (after debugging) I see is. You have div and p inside it so...

When you mouse over the div having p, first the div "mouseover" event is being fired which stores the whole html including p tag.

But before writing this html back to the div the "p" mouseover is fired which replaces your data with the text and not the html.

And then your text is being written out to the div finally.

Umair A.
  • 6,690
  • 20
  • 83
  • 130
  • No, that is not correct. There is no event handler for the mouseover event for the `p` element. You can check this by logging the tag names in the event handler: http://jsfiddle.net/rFqyP/14/ – Guffa Jul 31 '11 at 10:01