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);