en

designoir

addEvent & removeEvent

Mit addEvent() und removeEvent() lassen sich Event-Listener browserübergreifend und einheitlich verwalten.

Events entfesselt!

Im Unterschied zu vielen andern Implementierungen, macht dieses addEvent einiges mehr als nur Event-Listener zu registrieren. Normalerweise ist der Umgang mit Events wegen vieler Browser-Inkonsistenzen grausam, oder genauer gesagt: wegen der Unfähigkeit des Internet Explorer. Diese Punkte werden von addEvent für den IE umgesetzt:

  1. Teile des W3C DOM Level 2 Event interface:
    • Methoden: event.stopPropagation(), event.preventDefault()
    • Attribute: event.currentTarget, event.target, event.relatedTarget (MouseEvent), event.eventPhase
    • Konstanten: Event.AT_TARGET, Event.BUBBLING_PHASE
  2. Unterstützung für das W3C DOM Level 2 EventListener interface (handleEvent)
  3. this wird mit dem Zielobjekt referenzieren, auf das der Listener registriert wurde (statt mit window)
  4. Listener-Funktionen werden in FIFO-Reihenfolge aufgerufen (statt chaotisch).
  5. Speicherleck wird geschlossen, indem alle Listener beim Verlassen der Seite entfernt werden.

Anmerkungen

  1. Im Unterschied zu addEventListener hat addEvent keinen useCapture-Parameter. Meiner Meinung nach lässt sich capture im Internet Explorer nicht implementieren.
  2. Gecko 1.8 und ältere Versionen sind ebenfalls von einem Speicherleck betroffen, was allerdings mit Gecko 1.8.1 (Firefox 2) behoben wurde. Und da das unload-Event angeblich den bfcache deaktiviert, habe ich die Säuberung nur für den IE implementiert.

addEvent.js

/**
 * addEvent & removeEvent -- cross-browser event handling
 * Copyright (C) 2006-2007  Dao Gottwald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Contact information:
 *   Dao Gottwald  <dao at design-noir.de>
 *
 * @version  1.2.1
 */

function addEvent(o, type, fn) {
  o.addEventListener(type, fn, false);
}
function removeEvent(o, type, fn) {
  o.removeEventListener(type, fn, false);
}
/*@cc_on if (!window.addEventListener) {
  var addEvent = function (o, type, fn) {
    if (!o._events) o._events = {};
    var queue = o._events[type];
    if (!queue) {
      o._events[type] = [fn];
      if (!o._events._callback)
        o._events._callback = function (e) { Event._callListeners(e, o) };
      o.attachEvent("on" + type, o._events._callback);
    } else if (Event._fnIndex(o, type, fn) == -1)
      queue.push(fn);
    else return;
    Event._mem.push([o, type, fn]);
  };
  var removeEvent = function (o, type, fn) {
    var i = Event._fnIndex(o, type, fn);
    if (i < 0) return;
    var queue = o._events[type];
    if (queue.calling) {
      delete queue[i];
      if (queue.removeListeners)
        queue.removeListeners.push(i);
      else
        queue.removeListeners = [i];
    } else
      if (queue.length == 1)
        Event._detach(o, type);
      else
        queue.splice(i, 1);
  };
  var Event = {
    AT_TARGET: 2,
    BUBBLING_PHASE: 3,
    stopPropagation: function () { this.cancelBubble = true },
    preventDefault: function () { this.returnValue = false },
    _mem: [],
    _callListeners: function (e, o) {
      e.stopPropagation = this.stopPropagation;
      e.preventDefault = this.preventDefault;
      e.currentTarget = o;
      e.target = e.srcElement;
      e.eventPhase = e.currentTarget == e.target ? this.AT_TARGET : this.BUBBLING_PHASE;
      switch (e.type) {
        case "mouseover":
          e.relatedTarget = e.fromElement;
          break;
        case "mouseout":
          e.relatedTarget = e.toElement;
      }
      var queue = o._events[e.type];
      queue.calling = true;
      for (var i = 0, l = queue.length; i < l; i++)
        if (queue[i])
          if ("handleEvent" in queue[i])
            queue[i].handleEvent(e);
          else
            queue[i].call(o,e);
      queue.calling = null;
      if (!queue.removeListeners)
        return;
      if (queue.length == queue.removeListeners.length) {
        this._detach(o, e.type);
        return;
      }
      queue.removeListeners = queue.removeListeners.sort(function(a,b){return a-b});
      var i = queue.removeListeners.length;
      while (i--)
        queue.splice(queue.removeListeners[i], 1);
      if (queue.length == 0)
        this._detach(o, e.type);
      else
        queue.removeListeners = null;
    },
    _detach: function (o, type) {
      o.detachEvent("on" + type, o._events._callback);
      delete o._events[type];
    },
    _fnIndex: function (o, type, fn) {
      var queue = o._events[type];
      if (queue)
        for (var i = 0, l = queue.length; i < l; i++)
          if (queue[i] == fn)
            return i;
      return -1;
    },
    _cleanup: function () {
      for (var m, i = 0; m = Event._mem[i]; i++)
        if (m[1] != "unload" || m[2] == Event._cleanup)
          removeEvent(m[0], m[1], m[2]);
    }
  };
  addEvent(window, "unload", Event._cleanup);
} @*/

Beispiel #1

addEvent.html

Bei dem Beispiel handelt es sich um die Seite, die beim addEvent() recoding contest zu verwenden war. Ich habe sie leicht modifiziert, denn die ursprüngliche noBubble-Funktion ist dank stopPropagation() nicht mehr nötig.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>addEvent() recoding contest entry</title>
<script type="text/javascript" src="addEvent.js"></script>
<script type="text/javascript">//<!--

/*
  Original idea by John Resig
  Tweaked by Scott Andrew LePera, Dean Edwards and Peter-Paul Koch
*/

var menu = {
  init: function () {
    var menu = document.getElementById("navigation");
    addEvent(menu, "mouseover", this);
    addEvent(menu, "mouseover", showBorder);
    var items = menu.getElementsByTagName("li");
    for (var i = 0; i < items.length; i++) {
      addEvent(items[i], "mouseout", this);
      addEvent(items[i], "mouseout", hideBorder);
    }
  },
  handleEvent: function (event) {
    switch (event.type) {
      case "load":
        this.init();
        break;
      case "mouseover":
        if (event.target.nodeName.toLowerCase() == "li")
          event.target.className += " over";
        break;
      case "mouseout":
        if (event.relatedTarget.parentNode != event.target &&
            event.relatedTarget.parentNode.parentNode != event.target &&
            event.relatedTarget.parentNode.parentNode.parentNode != event.target)
          event.target.className = event.target.className.replace(/over/g, "");
        event.stopPropagation();
        break;
    }
  }
};

addEvent(window, "load", menu);

function showBorder(event) {
  if (event.target.nodeName.toLowerCase() == "li")
    event.target.className += " current";
}

function hideBorder(event) {
  if (event.relatedTarget.parentNode != event.target &&
      event.relatedTarget.parentNode.parentNode != event.target &&
      event.relatedTarget.parentNode.parentNode.parentNode != event.target)
    this.className = this.className.replace(/current/g,'');
  event.stopPropagation();
}

function removeBorders() {
  var menu = document.getElementById("navigation");
  removeEvent(menu, "mouseover", showBorder);
  removeEvent(menu, "mouseout", hideBorder);
}

//--></script>
<style type="text/css">
<!--

ul#navigation {
  width: 150px;
}

li {
  border: 1px solid #ffffff;
}

li ul {
  display: none;
}

.over ul {
  display: block;
}

.current {
  border-color: #cc0000;
}

-->
</style>

</head>

<body>

<h1><code>addEvent()</code> recoding contest entry</h1>

<ul id="navigation">
  <li><a href="#">Item 1</a>
    <ul>
      <li><a href="#">Item 1.1</a></li>
      <li><a href="#">Item 1.2</a></li>
      <li><a href="#">Item 1.3</a></li>
    </ul>

  </li>
  <li><a href="#">Item 2</a>
    <ul>
      <li><a href="#">Item 2.1</a></li>
      <li><a href="#">Item 2.2</a></li>
      <li><a href="#">Item 2.3</a></li>
    </ul>

  </li>
  <li><a href="#">Item 3</a>
    <ul>
      <li><a href="#">Item 3.1</a></li>
      <li><a href="#">Item 3.2</a></li>
      <li><a href="#">Item 3.3</a></li>
    </ul>

  </li>
</ul>

<p><a href="#" onclick="removeBorders()">Remove border effect</a>.</p>

</body>
</html>

Kommentare / zeige alle

  1. Dao meinte am 17. November ’06, 23:53 Uhr ():
    joe, quite right. Only IE will compile the commented code and then test for addEventListener (which of course doesn’t exist for any released version of IE, but it could in future).
  2. Valter Borges meinte am 20. November ’06, 16:40 Uhr ():
    In order to debug how would you recommend intercepting all events.
    Would we add a generic pre event before the other events? or is there a better way?
  3. Valter Borges meinte am 20. November ’06, 17:05 Uhr ():
    Hi DAO is there a way to be notified of new versions and to see a version log?
  4. Dao meinte am 20. November ’06, 17:10 Uhr ():
    In order to debug how would you recommend intercepting all events.
    Would we add a generic pre event before the other events? or is there a better way?
    I’m not sure what you want to do, but I guess _callListeners would be the point to intercept events.
    is there a way to be notified of new versions
    I’ll see if I can add a microsummary
    and to see a version log?
    Currently not, but maybe I’ll introduce one later on.
  5. Valter Borges meinte am 21. November ’06, 15:43 Uhr ():
    Well as you probably already know when dealing with event debugging and things like onblur, onfocus, if you use a traditional debugger it will cause events to fire as it stops to do a watch. Therefore I like to create a div where I write out the events as they are firing so I can troubleshoot. I wrote my own my I believe Yahoo has aone with more features can be here http://developer.yahoo.com/yui/logger/.
    Therefore instead of putting the statement that writes out what event was fired inside each event I would like to put it once somewhere before the event get’s fired so once I’m done I can just comment it out in one place.

    Is _callListeners still the best place to do this?
  6. Dao meinte am 21. November ’06, 16:35 Uhr ():
    Yes, I still think it’s the appropriate place. There’s |e|, the event object, and |o|, the object the listeners were registered on. That’s all you need, right? You can just do something like |log += 'fired '+e.type+' for #'+o.id+'\n';| (presuming that all the objects have an id). |log| could be |document.getElementById('debug-output').firstChild.nodeValue| or so.
  7. Diego Perini meinte am 4. April ’07, 02:04 Uhr ():
    Hi Dao how are you…

    A couple of things I noticed while browsing your addEvent code.
    Are you handling the IE returnValue as returned by each listeners ?
    The first listener returning „false“ should also break the chain execution.

    Cheers,
    Diego
  8. Dao meinte am 10. Juni ’07, 12:57 Uhr ():
    Are you handling the IE returnValue as returned by each listeners ?
    I’m using returnValue internally for emulating event.preventDefault(). returnValue isn’t part of the DOM spec and won’t work across browsers when set inside of a listener.
    The first listener returning „false“ should also break the chain execution.
    Should it? I don’t think the DOM spec says so.
  9. Diego Perini meinte am 18. Juni ’07, 15:45 Uhr ():
    Dao,
    I mistyped my question, it should have been:

    Are you handling the IE return value as returned by each listeners ?

    I meant the handler return value and not „returnValue“ which as you said is a non standard IE property…
    Yes ! The first event returning false should stop subsequent event in the same chain run (same event type).

    This is needed where „Event Managers“ build chains of events to maintain cross-browser compatibility and to be able to implement event ordering (missing in IE). The standard „addEventListener“ DOM method in the remaining browsers already handle these requirements correctly.
  10. Dao meinte am 18. Juni ’07, 17:29 Uhr ():
    This is needed where „Event Managers“ build chains of events to maintain cross-browser compatibility and to be able to implement event ordering (missing in IE).
    Not sure what you mean. addEvent takes care of the order of events. Therefore, multiple event listeners attached to the same target can just share custom code if they want to communicate with each other.
    The standard „addEventListener“ DOM method in the remaining browsers already handle these requirements correctly.
    It’s a bit odd to say „correctly“ if it’s not spec’ed anywhere. If this remains the case, I tend to not implement it.
  11. Diego Perini meinte am 14. Juli ’07, 19:13 Uhr ():
    Dao,
    you are probably correct, no specs saying how an event return value should affect the firing of next events.
    However I believe it may be useful in some situations and in event capturing also.
    I believe the specs have purposely skipped that since there was a browser firing event unordered… :-)

    Now that events are run ordered in everyone’s library (like your) I thought it could be used to block the other events of the same type, on the same chain bound to the same object. There have been talks about this on Dean site.

    In the old DOM0 registration world and especially with the inline registration model a return of false was a mean of blocking the default action bound to that element (to make links not follow the URL for example).

    In the new days nearly all browsers accept a string as a return value from the „onbeforeonload“ event, and that string will be showed in the standard dialog box brought up by that event, if you override that return value you are practically skipping the „hint“ to the user. I know it is not standard, but is everywhere in web apps…

    However keep up with the nice work.

    Cheers,
    Diego
  12. Diego Perini meinte am 17. Juli ’07, 14:22 Uhr ():
    I mistyped the event name in the last paragraph of the above message, it should be „onbeforeunload“…

    Diego
  13. Ari Kivimäki meinte am 1. August ’07, 13:00 Uhr ():
    Excellent code to tackle IE’s event calling randomness!
  14. mack pexton meinte am 14. Februar ’08, 23:22 Uhr ():
    I had troubles with Event._cleanup() because _mem was undefined when executed. The reason was because the last line addEvent(window, „unload“, Event._cleanup); changes the meaning of „this“ to be the window object, not the Event object. To cure, I changed the last line to addEvent(window, „unload“, function(){Event._cleanup()});
  15. paslanmaz boru meinte am 10. Februar ’09, 11:23 Uhr ():
    video tutorial pls
  16. Delan Ahmad meinte am 7. April ’09, 13:12 Uhr ():
    Thank you very much for this script, I have used it (with credit) in my InterModule javascript library (intermodule.sf.net). This has finally fixed up the problems with cross-browser event handling. :D
  17. oyun meinte am 27. Januar ’10, 22:52 Uhr ():
    Thank You
  18. Michel meinte am 16. Februar ’10, 22:04 Uhr ():
    Hi,
    On FF I cant get rid of my events with :
    function removeEvent(o, type, fn) {
    o.removeEventListener(type, fn, false);
    }
    fn is passed the same way it is passed in addEvent but the mouseover function is still working !
    type is ’mouseover’ in add and in remove.
    Everything works in the addEvent but not in the remove…

    Did I missed something about returning something false ?
    Thank you
  19. Michel meinte am 18. Februar ’10, 00:02 Uhr ():
    I reply to myself :
    I made a small example test case to minimize the code but now it’s working !
    I will have to analyse my big code to see what happen :-(
  20. program indir meinte am 24. April ’10, 10:45 Uhr ():
    Thank you very rich indeed a site

HTML wird nicht interpretiert.

Geändert am 15. Oktober ’07 Dão G., 2005–2010
aggressiv akt andromeda bar beine blue efeu frontal fugaetu industriell komet land noir rost rot sonnenblume splash split winter wolke zeit