How to Create One-Time Events in JavaScript

Sometimes an event need only be called once in your page. For example, clicking a thumbnail which loads and plays a video file or clicking a “more” icon which retrieves and displays extra content via Ajax. However, you’ve probably defined an event handler which is called every time that action occurs. At best, it’s a little inefficient and the browser is retaining unnecessary resources. At worst, your handler could do something unexpected or reload data which is already available.

Fortunately, it’s relatively easy to create one-time event handlers in JavaScript. The process:

  1. A handler is assigned to an event, such as clicking an element.
  2. When the element is clicked, the handler runs.
  3. The handler is removed. Further clicks on that element will no longer call the function.

jQuery

Let’s look at the simplest solution first. If you’re using jQuery, there’s a little-known one() event binding method which implements one-time events.

$("#myelement").one( "click", function() { alert("You'll only see this once!"); } );

It’s used identically to other jQuery event methods. For more information, refer to api.jquery.com/one/.

Self-Removing Handlers

If you’re using raw JavaScript, any handler function can remove itself using a single line of code:

document.getElementById("myelement").addEventListener("click", handler);

// handler function
function handler(e) {
	// remove this handler
	e.target.removeEventListener(e.type, arguments.callee);

	alert("You'll only see this once!");
}

Presuming your handler event argument is named ‘e’, the line:

e.target.removeEventListener(e.type, arguments.callee);

will remove the handler the first time it’s invoked. It doesn’t matter what event type or name you use for your handler — it may even be an in-line anonymous function.

Note I’ve used standard event calls which won’t work in IE8 and below. OldIE’s require a call to detachEvent and the type requires an “on” prefix, e.g. “onclick”. But, if you’re supporting oldIE’s, you’re probably using jQuery or your own custom event handler.

Self-removing handlers may be the best option if you require some flexibility, e.g. you only want to unhook certain event types or remove the handler after different conditions, e.g. two or more clicks.

A One-Time Event Creation Function

Like me, you may be too lazy or forgetful to add an event removal line to every handler function. Let’s create a onetime function which does the hard work for us:

// create a one-time event
function onetime(node, type, callback) {

	// create event
	node.addEventListener(type, function(e) {
		// remove event
		e.target.removeEventListener(e.type, arguments.callee);
		// call handler
		return callback(e);
	});

}

We can now use this function whenever we require a one-time only event:

// one-time event
onetime(document.getElementById("myelement"), "click", handler);

// handler function
function handler(e) {
	alert("You'll only see this once!");
}

While you won’t require one-time events in every page, it’s good to find there are several options available to JavaScript developers.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Chris C

    I like the use of arguments.callee to remove anonymous event listeners. Is it still considered good practice to use the “callee” property, as I heard it was deprecated.

  • Ben

    A couple of things:

    1) You shouldn’t use arguments.callee. As long as the function is named, you can just remove it by name:

    function namedHandler(e) {
    e.target.removeEventListener(e.type, namedHandler);
    // …
    }

    2) When using jQuery’s “one” function, it’s important to note the handler will be called exactly once PER EVENT TYPE. If you use “one” with more than one event, the handler will be called once when each event type fires. For example,

    $(‘img’).one(‘load error’, function () {
    console.log(‘once’);
    });

    You might expect, when images load, that the handler is called once and then removed; but what happens is that it is called for the LOAD event and removed, but still persists for the ERROR event type. This is very important to note, because if you use ‘one’ on more than one event type this way, you could be leaking memory.

    To demonstrate:

    var $div = $(‘

    ‘);
    $div.one(‘a b’, function () { console.log(‘once’); });
    $div.trigger(‘a’);
    $div.trigger(‘a’);
    $div.trigger(‘b’);
    $div.trigger(‘b’);

  • Ben

    N.B., my point in 1) isn’t subjective; arguments.callee is disallowed in ES5 strict and will throw an exception if you try to access it.

  • Ben

    And one final note: in the first example, you are assuming that e.target points to the element on which you attached the event listener. This isn’t necessarily the case. You could have an event listener on an ancester, and the event will bubble up to trigger that handler, but the target node will still be the descendent on which you clicked. For example, if an is wrapped in an , and you have a click handler registered on the , but you click on the image, the target will be the , which has no event listeners attached.

    • Anonymous

      Agreed, although it’s unlikely you’d have the same event handler applied to a child node. If you did, you’re possibly not using the bubbling phase effectively.

  • Anonymous

    I usually use .on and into the callback $(this).off(‘event’)

  • Joan

    It seems hard to use raw (vanilla) JavaScript to write such a thing and make it compatible with IE 8 or lower versions.

  • Adnan

    Jquery functions are best…

    • Anonymous

      Really? It’s easier on the eye, but it would be simple to implement the same API with a few lines of code.

  • Anonymous

    you don’t need arguments.callee in your example, name the function using a declaration and it will work in IE8 polyfills too. Also, handler is not necessarily a callback, it can be an object, with a handleEvent method. Your suggestion breaks with standards such W3C, for event handling, and use strict directive, due unnecessary use of arguments callee.

    • Anonymous

      Yes you can name the function. It’s a shame that’s always necessary and makes closure functions more cumbersome.

      You should be aware that the dropping of arguments.callee is being questioned by vendors such as Mozilla.

      • Simeon Vincent

        “You should be aware that the dropping of arguments.callee is being questioned by vendors such as Mozilla.”

        For most intents and purposes arguments.callee was deprecated in ES5 with the introduction of strict mode. More information on this is available at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/callee.

      • Anonymous

        The dropping of argument.callee is being questioned by Mozilla? What do you mean? Source please. It has been banned from the strict language. We should not be writing code that is not valid strict code.

        • Anonymous
        • Anonymous

          See the bottom of the MDN page.

        • KD

          simevidas where are you getting your information from what books do you recommend or sites to get more in depth Javascript programming training.

          • Anonymous

            I’m entirely “home schooled” :-) I used to analyze the ECMAScript spec in detail (in my free time and during several Summer vacations). Also, I used to spend a lot of time on Stack Overflow, answering questions about JavaScript. This is where most of my JavaScript insight comes from. (When you have to explain knowledge to others, you learn it even better in the process.) Back in 2008, I started with Crockford’s JavaScript courses at Yahoo (available on YouTube) – that’s a solid starting point.

  • Anonymous

    Nice post – I honestly never knew about the jQuery one() method and it looks like it’s been there since the beginning!

  • Anonymous

    Why is that cumbesome? It’s not supposed to pollute the global namespace, so what’s the problem?

    • Anonymous

      JavaScript permits anonymous functions and they’re very useful. Closures is a prime example. Having to name them just because arguments.callee is deprecated feels like a backward step. The MDN page provides an example where arguments.callee is essential too.

      • Anonymous

        That part of MDN links to a bug ticket which has been opened by an individual. Mozilla responded that it was invalid, as the issue was not with their implementation but with the language spec. Then, on ECMAScript’s bugtracker, Allen Wirfs-Brock (from TC-39) provides solutions for dealing with this issue without using .callee.

        Note that the argument for arguments.callee in the above bug reports is based on a very localized situation which includes the use of the Function constructor. Your code, on the other hand, does not fall into that category, so you can’t use it as an argument here.

  • nnnnnn

    “JavaScript permits anonymous functions and they’re very useful. Closures is a prime example.” – Closures and anonymous functions are different topics. The concept of “closures” applies to _all_ JS functions, named or not. The MDN example where arguments.called is “essential” is hardly a common pattern – if they couldn’t come up with a less contrived example than that I think we can live without it, especially when there are multiple ways to rewrite that function to produce the same effect without using arguments.callee.

    • Anonymous

      I’m not saying you can’t name every function — and I admit the MDN example is a little contrived. It’s just a shame it’s necessary to do so in every situation you need to reference the enclosing function. From what I can tell, it was mainly dropped because it was tricky to optimize and older browsers had some quirks.

      • Anonymous

        It was dropped for security reasons. The arguments object, when passed to an 3rd party API, exposes information to that API about the caller/callee and also enables that API to mutate the arguments of your own function call. This is a unnecessary security risk, which is why it has been removed from the language. Please note that the strict language has been shaped by very experienced JS programmers (including Brendan Eich and Douglas Crockford) over a lengthy period of debate at the TC-39, and has been since then advocated by others (like Nicholas Zakas) since years. Please make sure to provide only strict code in your future blog posts.

        • Anonymous

          I’d be interested to know whether it’s ever been exploited. I’m not saying it wasn’t dropped for good reasons but it is useful and it’s a shame no alternative was proposed (other than function naming).

  • MDN

    “Warning: The 5th edition of ECMAScript (ES5) forbids use of arguments.callee() in strict mode.”

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/callee

  • onemanclapping

    Why returning the call handler and not just execute it instead? Is there any advantage?

    • Anonymous

      The advantages of arguments.callee is that the same line of code can be used in any script without modification and it works on anonymous functions. Unless you’re using strict mode in a compatible browser — then it’ll fail.

    • Anonymous

      imagine having 10,000 handlers…

  • Njanga

    Great tutorial. This one time event is coming from time to time in projects and it’s always good to be ready.

  • Ben

    A lot of things in Javascript are “useful”, but detrimental to the language.

    • Anonymous

      True, but the same can be said for any language or system. What’s better? A language which is elegant and pure or one which lets you get the job done? They shouldn’t be mutually exclusive — but often are. Let’s not forget that the original implementation of JavaScript was devised in a few weeks.

  • brothercake

    If the handler function contains a second function which wraps around its code, then you can save a reference to that as a property of the handler, and then use that reference to remove it later:

    function addEvent(context, type, handler)
    {
    	if(context.addEventListener)
    	{
    		context.addEventListener(type, handler.__wrapper = function(e)
    		{
    			return handler.call(context, e);
    		
    		}, false);
    
    		return { silence : function()
    		{
    			context.removeEventListener(type, handler.__wrapper, false);
    		}};
    	}
    	else if(context.attachEvent)
    	{
    		context.attachEvent('on' + type, handler.__wrapper = function(e)
    		{
    			return handler.call(context, e);
    		}
    		
    		return { silence : function()
    		{
    			context.detachEvent('on' + type, handler.__wrapper);
    		}};
    	}
    }
    
    
    //then
    var foo = addEvent(node, "click", function(e)
    {
    	foo.silence();
    });