Here's the latest. Beau Hartshorne has done a lot of work bringing it up to 
standard.

Here are some of the changes:

1. Fixed bug in IE.

2. Handles errors differently. It will aggregate them then raise a single 
error with the original errors intact rather than log them.

3. Should conform to MochiKit standards a lot better.

4. Turns off bubbling of events. They can be "uncancelled" if you really want 
to.

I am thinking of either modifying IE's event object OR creating a proxy object 
that behaves according to DOM standard. That way, when you get an event, you 
don't have to guess which interface to use. Thoughts?

-- 
Jonathan Gardner
[EMAIL PROTECTED]
Index: doc/rst/MochiKit/index.rst
===================================================================
--- doc/rst/MochiKit/index.rst	(revision 490)
+++ doc/rst/MochiKit/index.rst	(working copy)
@@ -16,6 +16,7 @@
 - :mochiref:`MochiKit.Logging` - we're all tired of ``alert()``
 - :mochiref:`MochiKit.LoggingPane` - interactive :mochiref:`MochiKit.Logging`
   pane
+- :mochiref:`MochiKit.Signal` - event handling
 - :mochiref:`MochiKit.Visual` - visual effects
 
     
Index: doc/rst/MochiKit/Signal.rst
===================================================================
--- doc/rst/MochiKit/Signal.rst	(revision 0)
+++ doc/rst/MochiKit/Signal.rst	(revision 0)
@@ -0,0 +1,232 @@
+.. title:: MochiKit.Signal - Simple universal event handling
+
+Name
+====
+
+MochiKit.Signal - Simple universal event handling
+
+
+Synopsis
+========
+
+::
+
+    // Allow your objects to signal 'flash' and 'bang'.
+    var myObject = {};
+    register_signals(myObject, ['flash', 'bang']);
+
+    // Connect myobject's 'flash' signal to otherObject's 'gotFlash' method.
+    // This means that otherObject,gotFlash() will be called when 'flash'
+    // signalled.
+    connect(myObject, 'flash', otherObject, 'gotFlash');
+
+    // Connect myobject's 'bang' signal to otherObject and gotBang. This
+    // means that gotBang.apply(otherObject) will be called when 'bang'
+    // signalled.
+    connect(myObject, 'bang', otherObject, gotBang);
+
+    // Connect myObject's 'flash' signal to myFunc. This means that
+    // myFunc.apply(myObject) will be called when 'flash' signalled.
+    connect(myObject, 'flash', myFunc);
+
+    // You may disconnect with disconnect() and the same parameters.
+
+    // Signal can take parameters. These will be passed along to the connected
+    // functions.
+
+    // This calls otherObject['gotFlash'].apply(otherObject) and
+    // myFunc.apply(myObject)
+    signal(myObject, 'flash');
+
+    // This calls gotBang.apply(otherObject, 'BANG!')
+    signal(myObject, 'bang', 'BANG!');
+
+    // DOM events are also signals. Connect freely! The functions will be
+    // called with the event as a parameter. Automatically prevents bubbling.
+
+    // calls myClicked.apply($('myID'), event)
+    connect('myID', 'onclick', myClicked);
+
+    // calls wasClicked.apply(myObject, event)
+    connect($('myID'), 'onclick', myObject, wasClicked);
+
+    // calls myObject.wasClicked(event)
+    connect($('myID'), 'onclick', myObject, 'wasClicked');
+
+
+Description
+===========
+
+Event handling was never so easy!
+
+This module takes care of all the hard work---figuring out which event module
+to use, trying to retrieve the event object, and handling your own internal
+events, as well as cleanup when the page is unloaded to handle IE's nasty
+memory leakage.
+
+Here are the rules for the signal and slot system.
+
+1. Don't use the browser event handling. That means, no 'onclick="blah"', no
+elem.attachEvent(...), and certainly no elem.addEventListener(...). If you
+really need to do this, you should not use the MochiKit Signal library for
+that particular event.
+
+2. Objects other than DOM objects (window, document, or any HTMLElement) must
+have its signals declared via 'register_signals()' before they can be used.
+
+3. For DOM objects (window, document, or any HTMLElement), the signals already
+exist and are named 'onclick', 'onkeyup', etc... just like they are named
+already. The short versions used in DOM2 ('click', 'keyup', etc...) are not
+valid.
+
+4. The following are acceptable for slots.
+
+    - A function.
+    - An object and a function.
+    - An object and a string.
+
+5. You may connect or disconnect slots to signals freely using the 'connect()'
+and 'disconnect()' methods. The same parameters to 'disconnect' will only
+remove a previous connection made with the same parameters to 'connect'. Also,
+connecting multiple times only leaves one connection in place.
+
+6. Slots that are connected to a signal are called when that signal is
+signalled.
+
+    - If the slot was a single function, then it is called with 'this' set to
+      the object originating the signal with whatever parameters it was
+      signalled with.
+      
+    - If the slot was an object and a function, then it is called with 'this'
+      set to the object, and with whatever parameters it was signalled with.
+      
+    - If the slot was an object and a string, then "object[string]" is called
+      with the parameters to the signal.
+
+If an exception is caused by execution of one of the slots, that exception is
+trapped. It will be logged as a fatal message if the Loggin module is loaded.
+Otherwise, at the end of the signal, it will raise a special error with all
+the errors intact.
+
+The results of a slot call are ignored, never to be seen again.
+
+7. Signals are triggered with the 'signal(src, 'signal', ...)' function.
+Whatever additional parameters are passed to this are passed onto the
+connected slots. (DOM events that have listening slots will trigger this
+call.)
+
+8. Signals triggered by DOM events are called with the event as a parameter.
+They are also cancelled so that they won't bubble up automatically. If you
+want them to bubble up, you have two options:
+
+    - Signal the event explicitly. This is kind of hard to do right because of
+      browser compatibility issues.
+
+    - Uncancel the event.
+
+This event system is largely based on Qt's signal/slot system. You should read
+more on how that is handled and also how it is used in model/view programming
+at http://docs.trolltech.com/.
+
+Dependencies
+============
+
+- :mochiref:`MochiKit.Base`
+- :mochiref:`MochiKit.Logging`
+- :mochiref:`MochiKit.DOM`
+
+Future Directions
+=================
+
+Here are some things I should probably do better in the next version.
+
+	1. Make events compatible. This can be done by massaging the actual
+	event so that it looks like a proper DOM event.
+
+	2. Provide two events for each browser event: One for bubbling, and
+	the other for capturing.
+
+	3. Do not explicitly cancel the event, but make that easier for users
+	by making the event behave like a DOM2 event.
+
+	4. Allow the simple creation of events that are reasonably authentic.
+
+
+Overview
+========
+
+
+API Reference
+=============
+
+Functions
+---------
+
+:mochidef:`connect(src, signal, dest, func)`:
+
+    Connects a signal to a slot.
+
+    'src' is the object that has the signal. You may pass in a string, in
+    which case, it is interpreted as an id for an HTML Element.
+
+    'signal' is a string that represents a signal name. If 'src' is an HTML
+    Element, Window, or the Document, then it can be one of the 'on-XYZ'
+    events. Note that you must include the 'on' prefix, and it must be all
+    lower-case. If 'src' is another kind of object, the signal must be
+    previously registered with 'register_signals()'.
+
+    'dest' and 'func' describe the slot, or the action to take when the signal
+    is triggered.
+
+    - If 'dest' is an object and 'func' is a string, then 'dest[func](...)' will
+      be called when the signal is signalled.
+
+    - If 'dest' is an object and 'func' is a function, then 'func.apply(dest,
+      ...)' will be called when the signal is signalled.
+
+    - If 'func' is undefined and 'dest' is a function, then 'func.apply(src,
+      ...)' will be called when the signal is signalled.
+
+    No other combinations are allowed and should raise and exception.
+
+    You may call 'connect()' multiple times with the same connection
+    paramters. However, only a single connection will be made.
+
+:mochidef:`disconnect(src, signal, dest, func)`:
+
+    When 'disconnect()' is called, it will disconnect whatever connection was
+    made given the same parameters to 'connect()'. Note that if you want to
+    pass a closure to 'connect()', you'll have to remember it if you want to
+    later 'disconnect()' it.
+
+:mochidef:`signal(src, signal, ...)`:
+
+    This will signal a signal, passing whatever additional parameters on to
+    the connected slots. 'src' and 'signal' are the same as for 'connect()'.
+
+:mochidef:`register_signals(src, signals)`:
+
+    This will register signals for the object 'src'. (Note that a string here
+    is not allowed--you don't need to register signals for DOM objects.)
+    'signals' is an array of strings.
+
+    You may register the same signals multiple times; subsequent register
+    calls with the same signal names will have no effect, and the existing
+    connections, if any, will not be lost.
+
+
+Authors
+=======
+
+- Jonathan Gardner <[EMAIL PROTECTED]>
+
+
+Copyright
+=========
+
+Copyright 2005 Jonathan Gardner <[EMAIL PROTECTED]>.  This program
+is dual-licensed free software; you can redistribute it and/or modify it under
+the terms of the `MIT License`_ or the `Academic Free License v2.1`_.
+
+.. _`MIT License`: http://www.opensource.org/licenses/mit-license.php
+.. _`Academic Free License v2.1`: http://www.opensource.org/licenses/afl-2.1.php
Index: MochiKit/MochiKit.js
===================================================================
--- MochiKit/MochiKit.js	(revision 490)
+++ MochiKit/MochiKit.js	(working copy)
@@ -36,7 +36,8 @@
     "DOM",
     "LoggingPane",
     "Color",
-    "Visual"
+    "Visual",
+    "Signal"
 ];
 
 if (typeof(JSAN) != 'undefined' || typeof(dojo) != 'undefined') {
@@ -56,6 +57,7 @@
         JSAN.use("MochiKit.LoggingPane", []);
         JSAN.use("MochiKit.Color", []);
         JSAN.use("MochiKit.Visual", []);
+        JSAN.use("MochiKit.Signal", []);
     }
     (function () {
         var extend = MochiKit.Base.extend;
Index: MochiKit/Signal.js
===================================================================
--- MochiKit/Signal.js	(revision 0)
+++ MochiKit/Signal.js	(revision 0)
@@ -0,0 +1,291 @@
+/***
+
+MochiKit.Signal 1.2
+
+See <http://mochikit.com/> for documentation, downloads, license, etc.
+
+(c) 2005 Jonathan Gardner.  All rights Reserved.
+
+***/
+
+if (typeof(dojo) != 'undefined') {
+    dojo.provide('MochiKit.Signal');
+    dojo.require('MochiKit.Base');
+    dojo.require('MochiKit.DOM');
+}
+if (typeof(JSAN) != 'undefined') {
+    JSAN.use('MochiKit.Base', []);
+    JSAN.use('MochiKit.DOM', []);
+}
+
+try {
+    if (typeof(MochiKit.Base) == 'undefined') {
+        throw '';
+    }
+} catch (e) {
+    throw 'MochiKit.Signal depends on MochiKit.Base!';
+}
+
+try {
+    if (typeof(MochiKit.DOM) == 'undefined') {
+        throw '';
+    }
+} catch (e) {
+    throw 'MochiKit.Signal depends on MochiKit.DOM!';
+}
+
+if (typeof(MochiKit.Signal) == 'undefined') {
+    MochiKit.Signal = {};
+}
+
+MochiKit.Signal.NAME = 'MochiKit.Signal';
+MochiKit.Signal.VERSION = '1.2';
+
+MochiKit.Signal._observers = [];
+
+MochiKit.Base.update(MochiKit.Signal, {
+    __repr__: function () {
+        return '[' + this.NAME + ' ' + this.VERSION + ']';
+    },
+    toString: function () {
+        return this.__repr__();
+    },
+
+    _get_slot: function(slot, func) {
+        if (typeof(func) == 'string' || typeof(func) == 'function') {
+            slot = [slot, func];
+        } else if (!func && typeof(slot) == 'function') {
+            slot = [slot];
+        } else {
+            throw new Error('Invalid slot parameters');
+        }
+
+        return slot;
+    },
+
+    _unloadCache: function() {
+        for (var i = 0; i < MochiKit.Signal._observers.length; i++) {
+            var src = MochiKit.Signal._observers[i][0];
+            var sig = MochiKit.Signal._observers[i][1];
+            var listener = MochiKit.Signal._observers[i][2];
+
+            if (src.addEventListener) {
+                src.removeEventListener(sig.substr(2), listener, false);
+            } else if (src.attachEvent) {
+                src.detachEvent(sig, listener);
+            } else {
+                src[sig] = undefined;
+            }
+        }
+        MochiKit.Signal._observers = [];
+    },
+
+    connect: function(src, sig, slot, /* optional */func) {
+        if (typeof(src) == 'string') {
+            src = MochiKit.DOM.getElement(src);
+        }
+
+        if (typeof(sig) != 'string') {
+            throw new Error("'sig' must be a string");
+        }
+
+        slot = MochiKit.Signal._get_slot(slot, func);
+
+        // Find the signal, attach the slot.
+        if (src.addEventListener || src.attachEvent || src[signal]) { // DOM object
+            // Create the __listeners object. This will help us remember which
+            // events we are watching.
+            if (!src.__listeners) {
+                src.__listeners = {};
+            }
+
+            // Add the signal connector if it hasn't been done already.
+            if (!src.__listeners[sig]) {
+                var listener = function(ev) {
+                    ev = ev || event;
+                    
+                    // Cancel bubbling
+                    ev.cancelBubble = true;
+                    if (ev.stopPropagation) {
+                        ev.stopPropagation();
+                    }
+
+                    MochiKit.Signal.signal(src, sig, ev);
+
+                    return true;
+                };
+                MochiKit.Signal._observers.push([src, sig, listener]);
+
+                if (src.addEventListener) {
+                    src.addEventListener(sig.substr(2), listener, false);
+                } else if (src.attachEvent) {
+                    src.attachEvent(sig, listener);
+                } else {
+                    src[sig] = listener;
+                }
+
+                src.__listeners[sig] = listener;
+            }
+
+            if (!src.__signals) {
+                src.__signals = {};
+            }
+            if (!src.__signals[sig]) {
+                src.__signals[sig] = [];
+            }
+        } else {
+            if (!src.__signals || !src.__signals[sig]) {
+                throw new Error("No such signal '" + sig + "' registered.");
+            }
+        }
+
+        // Actually add the slot... if it isn't there already.
+        if (MochiKit.Base.filter(
+            function(s) {
+                return MochiKit.Base.operator.ceq(s, slot);
+            },
+            src.__signals[sig]) == 0) {
+
+            src.__signals[sig].push(slot);
+        }
+    },
+
+    disconnect: function(src, sig, slot, /* optional */func) {
+        if (typeof(src) == 'string') {
+            src = MochiKit.DOM.getElement(src);
+        }
+
+        if (typeof(sig) != 'string') {
+            throw new Error("'signal' must be a string");
+        }
+
+        slot = MochiKit.Signal._get_slot(slot, func);
+
+        if (src.__signals && src.__signals[sig]) {
+            src.__signals[sig] = MochiKit.Base.filter(
+                function(s) {
+                    return MochiKit.Base.operator.cne(s, slot);
+                },
+                src.__signals[sig]);
+        }
+
+        if (src.addEventListener || src.attachEvent || src[signal]) {
+            // DOM object
+
+            // Stop listening if there are no connected slots.
+            if (src.__listeners && src.__listeners[sig] &&
+                src.__signals[sig].length == 0) {
+
+                var listener = src.__listeners[sig];
+
+                if (src.addEventListener) {
+                    src.removeEventListener(sig.substr(2), listener, false);
+                } else if (src.attachEvent) {
+                    src.detachEvent(sig, listener);
+                } else {
+                    src[sig] = undefined;
+                }
+
+                MochiKit.Signal._observers = MochiKit.Base.filter(
+                    function(o) {
+                        return MochiKit.Base.operator.cne(o,
+                            [src. sig, listener]);
+                    },
+                    MochiKit.Signal._observers
+                );
+
+                src.__listeners[sig] = undefined;
+
+            }
+        }
+
+    },
+
+    signal: function(src, sig) {
+        if (typeof src == 'string') {
+            src = MochiKit.DOM.getElement(src);
+        }
+
+        if (typeof(sig) != 'string') {
+            throw new Error("'signal' must be a string");
+        }
+
+        if (!src.__signals || !src.__signals[sig]) {
+            if (src.addEventListener || src.attachEvent || src[sig]) {
+                // Ignored.
+                return;
+            } else {
+                throw new Error("No such signal '" + sig + "'");
+            }
+        }
+        var slots = src.__signals[sig];
+
+        var args = MochiKit.Base.extend(null, arguments, 2);
+
+        var slot;
+        var errors = [];
+        for (var i = 0; i < slots.length; i++) {
+            slot = slots[i];
+            try {
+                if (slot.length == 1) {
+                    slot[0].apply(src, args);
+                } else {
+                    if (typeof slot[1] == 'string') {
+                        slot[0][slot[1]].apply(slot[0], args)
+                    } else {
+                        slot[1].apply(slot[0], args);
+                    }
+                }
+            } catch (e) {
+                // TODO: Should we use the Logging module? Hartshorne says no.
+                // I need to ask Bob.
+                errors.push(e);
+            }
+        }
+        if (errors.length) {
+            var e = new Error("There were errors in handling signal 'sig'.");
+            e.errors = errors;
+            throw e;
+        }
+    },
+
+    register_signals: function(src, signals) {
+        if (!src.__signals) {
+            src.__signals = {};
+        }
+        for (var i = 0; i < signals.length; i++) {
+            if (!src.__signals[signals[i]]) {
+                src.__signals[signals[i]] = [];
+            }
+        }
+    }
+});
+
+MochiKit.Signal.connect(window, 'onunload', MochiKit.Signal._unloadCache);
+
+MochiKit.Signal.EXPORT_OK = [];
+
+MochiKit.Signal.EXPORT = [
+    'connect',
+    'disconnect',
+    'signal',
+    'register_signals',
+];
+
+MochiKit.Signal.__new__ = function (win) {
+    var m = MochiKit.Base;
+    this._document = document;
+    this._window = win;
+
+    this.EXPORT_TAGS = {
+        ':common': this.EXPORT,
+        ':all': m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+
+    m.nameFunctions(this);
+
+};
+
+MochiKit.Signal.__new__(this);
+
+MochiKit.Base._exportSymbols(this, MochiKit.Signal);

Reply via email to