2

So I have this javascript on a project I'm working on:

<script type="text/javascript">

    document.getElementById('contact').onmouseover = function () 
    {
        var w = 130;
        function step() 
        {
            if (w < 250) 
            {
                middle.style.width = (w++) + "px";
                setTimeout(step, 5);
            }
        }
        setTimeout(step, 1500); 
    };

</script>

I want this to run only once. After it detects a mouseover, I want it to run the function and then never run again until the page refreshes. How would I accomplish this?

Code Maverick
  • 20,171
  • 12
  • 62
  • 114
Bill
  • 91
  • 8
  • 3
    Use `addEventListener` (or `attachEvent`) to add it, and then `removeEventListener` (or `detachEvent`) to detach it – Ian Jul 21 '14 at 21:04
  • @T.J.Crowder - The reason I am hesitant to suggest using the event listener is because to have it work with ie8 (which still unfortunately has a user base) you need to use a poly fill when it isn't available. Any suggestions on what to do with that? Should the answer always also include the polyfill? Is ie8's user base really to be ignored entirely? – Travis J Jul 21 '14 at 21:10
  • 1
    @T.J.Crowder I was thinking about it, and then I saw someone else already answered with the same suggestion, and they're currently improving their answer :) – Ian Jul 21 '14 at 21:11
  • 3
    @TravisJ: Well, I would probably point them at [this answer](http://stackoverflow.com/a/23799448/157247), which has a thorough polyfill for it. But as it's my answer, that could seem to be self-serving... :-) (Actually, let me CW it...done, now it's a CW.) And no, I don't think we can ignore IE8, I would always mention `attachEvent` (in fact, I edited it into the first `addEventListener` answer here). – T.J. Crowder Jul 21 '14 at 21:16
  • @TravisJ I'd say that depends heavily on the target user base. Certain crowds are much less likely to be using IE than others (such as gamers for one). – mechalynx Jul 21 '14 at 21:21

5 Answers5

4

I'd either use jQuery's one method or if you want to use 'plain' JavaScript you could just remove the event after the function has been triggered. Here's an example:

// Create a named function for the mouseover event
function myFunc() {
    // Remove the `myFunc` function event listener once `myFunc` is run
    document.getElementById('contact').removeEventListener('mouseover', myFunc, false);

    var w = 130;
    function step() {
        if (w < 250) {
            middle.style.width = (w++) + "px";
            setTimeout(step, 5);
        }
    }
    setTimeout(step, 1500);
};

// Add an event listener to run the `myFunc` function on mouseover
document.getElementById('contact').addEventListener('mouseover', myFunc, false);

Note that if you have to support IE8 (or even earlier), you need to use ...attachEvent("onmouseover", myFunc) and detachEvent("onmouseover", myFunc); instead; you can tell by checking if the element has addEventListener:

var elm = document.getElementById('contact')
if (elm.addEventListener) {
    // Use addEventListener
}
else {
    // Use attachEvent
}

(Perhaps hidden away in a utility function.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
pseudosavant
  • 7,056
  • 2
  • 36
  • 41
  • 2
    FWIW, you can use `this` instead of repeating the `document.getElementById` call within `myfunc` (which would also make it possible to use this on other elements). – T.J. Crowder Jul 21 '14 at 21:09
  • I didn't want to complicate it too much. My preference would have been to do something like this `var $contact = document.getElementById('contact');` and then used the `$contact` variable reference everywhere. – pseudosavant Jul 21 '14 at 21:13
  • 1
    Thanks for the edit for IE8 support. Its funny how this is a very correct way to answer the OP's problem, but jQuery would handle all of the browser compatibility stuff for them. – pseudosavant Jul 21 '14 at 21:14
  • 1
    It would (and I use it), but not everyone wants the overhead, and so much of the old insanity is finally gone now that nearly all of us can say bye-bye to IE7... IE8 has `querySelectorAll`, for instance; *quelle modern*! ;-) – T.J. Crowder Jul 21 '14 at 21:20
  • 1
    @T.J.Crowder Yes, but clearly this user is not *that* experienced. So we give them a solution, and another problem (browser compatibility) that they need to learn about. `jQuery.one` is probably the best *solution* to the OP's question, but a non-jQuery example like mine is more educational...I suppose. – pseudosavant Jul 21 '14 at 21:23
  • For me, the OP ***really*** should have accepted this answer rather than the one he accepted. You took the time to ask the question he asked, **and** mentioned the jQuery alternative (turns out he's using jQuery). – T.J. Crowder Jul 21 '14 at 21:28
3

All you need to do is remove the event listener from within the listener (so that it will stop listening to the event). However, in order to remove the listener, you need a reference to it, so you can't do it with a predefined listener directly attached to mouseover. Instead, use addEventListener to attach the listener, keep the returned reference and then use removeEventListener to remove the listener from within the callback.

var contact = document.getElementById('contact');

contact.addEventListener('mouseover', tehlistener);

function tehlistener() {
        // yada yada do whatever

        // ...

        // I think it's ok to use `this` here, but since this is so specific
        // its better to be specific about which listener you want removed
        contact.removeEventListener('mouseover', tehlistener);
    };

Here's a link to the lovely MDN article on addEventListener.

mechalynx
  • 1,306
  • 1
  • 9
  • 24
  • 1
    This is probably the best answer, but you should include a code example, just to more fully complete the solution :) – Ian Jul 21 '14 at 21:09
  • +1 Thank you for your explanation and example. But being a bit inexperienced with Javascript, I found @useSticks answer more practical for my situation. Although, maybe once I become more familiar with the language, I'll appreciate and understand your solution more. :) – Bill Jul 21 '14 at 21:22
  • @EricDongJuLee No problem, use whatever works for you :P However, as a friendly suggestion, if you're going to use jQuery for just this one fix, it might be better to use another solution. jQuery is fine, but including just for a one-time listener trick is probably overkill. good luck :) – mechalynx Jul 21 '14 at 21:25
2

If you are interested in using JQuery, there is a nice function called "one" that may be exactly what you need.

http://api.jquery.com/one/

Edit: Adding more code to show more of the solution:

$( "#contact" ).one( "mouseover", function() {
    var w = 130;
    function step() 
    {
        if (w < 250) 
        {
            middle.style.width = (w++) + "px";
            setTimeout(step, 5);
        }
    }
    setTimeout(step, 1500); 
});
useSticks
  • 883
  • 7
  • 15
  • 3
    As well as jQuery-only answers to non-jQuery questions. *(Not my dv either)* – T.J. Crowder Jul 21 '14 at 21:06
  • 3
    Wasn't from me. But in general, if jQuery isn't tagged and there is an answer suggesting it, people will downvote the answer. – Travis J Jul 21 '14 at 21:06
  • Hmm, I disagree with not suggesting jQuery, but the community will do what it will. – useSticks Jul 21 '14 at 21:08
  • 1
    +1 Agreed the answer could explain the solution a little bit more but jQuery is JavaScript after all. It's not like he gave an answer in Python. Using jQuery in this situation is far easier for a less experienced coder than many of the other answers, and this person may have never heard of jQuery to even think to tag the question with it. – pseudosavant Jul 21 '14 at 21:10
  • 1
    @pseudosavant - I agree, but it's still a link-only answer. Not worth a +1 yet in my opinion. – Code Maverick Jul 21 '14 at 21:11
  • @CodeMaverick I probably wouldn't have given it a +1 yet either but the stupid downvote swayed me. :P Now it has a proper example. – pseudosavant Jul 21 '14 at 21:15
  • @pseudosavant - Yup ... now it's a valid answer. – Code Maverick Jul 21 '14 at 21:19
  • +1 I probably should have added jQuery in the tags but completely forgot about it. But I like your solution, it's nice and simple. – Bill Jul 21 '14 at 21:19
  • 1
    @EricDongJuLee: ***Always*** tag `jquery` if you're using jQuery, it can have a *serious* effect on the answer to your question. Not doing so wastes people's time. – T.J. Crowder Jul 21 '14 at 21:21
1

You can use a once function.

function once(fn){
   var called = false;
   return function(){
       if (called) {
           return;
       }
       called = true;
       return fn.apply(this, arguments);
   }
}

Example:

something.onmouseover = once(function(){
    // this will happen only once
});
Brigand
  • 84,529
  • 20
  • 165
  • 173
  • 3
    Can you explain how the `called` variable gets set to true the second time the function is called? – Elmer Jul 21 '14 at 21:09
  • Sorry, I missed a line :-) Thanks for the correction. – Brigand Jul 22 '14 at 07:46
  • Ah, that makes more sense. I honestly think this is the best solution to this question. Very elegant and can be used with anything. – Elmer Jul 22 '14 at 19:57
1

You could just overwrite the event handler

<script type="text/javascript">
 document.getElementById('contact').onmouseover = function() {
 var w = 130;
 function step() {
  if (w < 250) {
    middle.style.width = (w++) + "px";
    setTimeout(step, 5);
  }
 }
 setTimeout(step, 1500); 
 this.onmouseover = null;//overwrite event handler with a blank callback
};
</script>
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • 2
    (I think you could use `null` instead of a blank callback...just so that way a blank function won't to be continually called) – Ian Jul 21 '14 at 21:07
  • 1
    @Ian - I had considered that as well. I will edit it in. – Travis J Jul 21 '14 at 21:08