On Friday 16 December 2005 18:45, Bob Ippolito wrote: > > If you implement something that doesn't suck with docs and tests, and are > willing to contribute it under the same license terms of MochiKit, then > I'll take it. >
I've attached a diff (made from 'svn diff') of my stuff. I don't know how google groups will handle it... I don't know if the docs are formatted correctly. My experience with ReST began today. I couldn't build the docs because I couldn't get a good version of docutils. I'm going to start using this right away so any bugs that testing didn't uncover should be revealed shortly. Please rip this to pieces and I'll try to incorporate suggestions ASAP. -- Jonathan Gardner [EMAIL PROTECTED]
Index: MochiKit/Signal.js =================================================================== --- MochiKit/Signal.js (revision 0) +++ MochiKit/Signal.js (revision 0) @@ -0,0 +1,245 @@ +/*** + +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.Logging"); +} +if (typeof(JSAN) != 'undefined') { + JSAN.use("MochiKit.Logging", []); +} + +try { + if (typeof(MochiKit.Logging) == 'undefined') { + throw ""; + } +} catch (e) { + throw "MochiKit.Signal depends on MochiKit.Logging!"; +} + +if (typeof(MochiKit.Signal) == 'undefined') { + MochiKit.Signal = {}; +} + +MochiKit.Signal._observers = [] + +MochiKit.Signal._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; +} + +MochiKit.Signal.connect = function(src, sig, slot, func) { + if (typeof src == 'string') { + src = $(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 __listening 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; + 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 (element.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 (filter(function(s) { return operator.ceq(s, slot) }, + src.__signals[sig]) == 0) { + src.__signals[sig].push(slot); + } +}; + +MochiKit.Signal.disconnect = function(src, sig, slot, func) { + if (typeof src == 'string') { + src = $(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] = filter( + function(s) { return 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 (element.attachEvent) { + src.detachEvent(sig, listener); + } else { + src[sig] = undefined; + } + + MochiKit.Signal._observers = filter( + function(o) { return operator.cne(o, [src. sig, listener]) }, + MochiKit.Signal._observers); + + src.__listeners[sig] = undefined; + + } + } + +}; + +MochiKit.Signal.signal = function(src, sig) { + if (typeof src == 'string') { + src = $(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 = extend(null, arguments, 2); + + var i, slot; + for (i=0; i<slots.length; i++) { + slot = slots[i]; + if (slot.length == 1) { + try { slot[0].apply(src, args) } + catch (e) { logFatal(e) } + } else { + if (typeof slot[1] == 'string') { + try {slot[0][slot[1]].apply(slot[0], args) } + catch (e) { logFatal(e) } + } else { + try { slot[1].apply(slot[0], args) } + catch (e) { logFatal(e) } + } + } + } +}; + +MochiKit.Signal.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._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 (element.attachEvent) { + src.detachEvent(sig, listener); + } else { + src[sig] = undefined; + } + } + MochiKit.Signal._observers = []; +}; + +MochiKit.Signal.connect(window, 'onunload', MochiKit.Signal._unloadCache); + + +MochiKit.Signal.NAME = "MochiKit.Signal"; +MochiKit.Signal.VERSION = "1.2"; +MochiKit.Signal.__repr__ = function () { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; +MochiKit.Signal.toString = function () { + return this.__repr__(); +}; + +MochiKit.Signal.EXPORT = [ + "connect", + "disconnect", + "signal", + "register_signals", +]; + +MochiKit.Signal.EXPORT_OK = [ +]; + +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); Index: doc/rst/MochiKit/Signal.rst =================================================================== --- doc/rst/MochiKit/Signal.rst (revision 0) +++ doc/rst/MochiKit/Signal.rst (revision 0) @@ -0,0 +1,189 @@ +.. title:: MochiKit.Signal - Simple universal event handling + +Name +==== + +MochiKit.Signal - Simple universal event handling + + +Synopsis +======== + +:: + // Allow your objects to create events. + var myObject = {}; + register_signals(myObject, ['flash', 'bang']); + + // otherObject,gotFlash() will be called when 'flash' + // signalled. + connect(myObject, 'flash', otherObject, 'gotFlash'); + + // gotBang.apply(otherObject) will be called when 'bang' signalled. + connect(myObject, 'bang', otherObject, gotBang); + + // 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. + signal(myObject, 'flash'); + signal(myObject, 'bang', 'BANG!'); + + // DOM events are also signals. Connect freely! The functions will be + // called with the event as a parameter. + + // 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(...). + +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. + +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. + +7. Signals are triggered with the 'signal(src, 'signal', ...)' function. +Whatever additional parameters are passed to this are passed onto the +connected slots. + +8. Signals triggered by DOM events are called with the event as a parameter. + +9. Exceptions raised by a slot are captured and logged as a fatal error. + +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` + + +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: tests/test_MochiKit-Signal.html =================================================================== --- tests/test_MochiKit-Signal.html (revision 0) +++ tests/test_MochiKit-Signal.html (revision 0) @@ -0,0 +1,105 @@ +<html> +<head> + <script type="text/javascript" src="Test/Builder.js"></script> + <script type="text/javascript" src="Test/More.js"></script> + <script type="text/javascript" src="../MochiKit/Base.js"></script> + <script type="text/javascript" src="../MochiKit/Logging.js"></script> + <script type="text/javascript" src="../MochiKit/Signal.js"></script> +</head> +<body> +Please ignore this button: <input type="submit" id="submit" /><br /> +<div> +Logging Panes: + <ul> + <li><a href="javascript:logger.debuggingBookmarklet();">Debugging + Bookmarklet</a> - Pop-up with LoggingPane</li> + <li><a href="javascript:void(createLoggingPane(true));">Inline + LoggingPane</a> + </ul> +</div> +<pre id="test"> +<script type="text/javascript"> + +try { + + // Counting the number of tests is really lame + plan({'tests': 11}); + + var d = {}; + register_signals(d, ['a', 'b', 'c']); + + var a = 0; + var test_a = function() { + a += 1; + }; + + connect(d, 'a', test_a); + signal(d, 'a'); + is(a, 1, "Connecting functions"); + + disconnect(d, 'a', test_a); + signal(d, 'a'); + is(a, 1, "Disconnecting functions"); + + connect(d, 'a', window, test_a); + signal(d, 'a'); + is(a, 2, "Connecting obj-function"); + + disconnect(d, 'a', window, test_a); + signal(d, 'a'); + is(a, 2, "disconnecting obj-function"); + + connect(d, 'a', window, 'test_a'); + signal(d, 'a', "Connecting obj-string"); + is(a, 3); + + disconnect(d, 'a', window, 'test_a'); + signal(d, 'a', "Disconnecting obj-string"); + is(a, 3); + + var does_excep = function() { + return undefined.attr; + } + connect(d, 'a', does_excep); + signal(d, 'a'); + // TODO: Check that the fatal exception was logged. + is(a, 3, "Checking that an exception in a slot is logged as Fatal."); + + connect('submit', 'onclick', test_a); + $('submit').click(); + is(a, 4, "Checking that HTML onclick event can be connected."); + + disconnect('submit', 'onclick', test_a); + $('submit').click(); + is(a, 4, "Checking that it can be disconnected."); + + var submit = $('submit'); + + connect(submit, 'onclick', test_a); + submit.click(); + is(a, 5, "Checking that a DOM element can be used."); + + disconnect(submit, 'onclick', test_a); + submit.click(); + is(a, 5, "... and then disconnected."); + + +} catch (err) { + + var s = "test suite failure!\n"; + var o = {}; + var k = null; + for (k in err) { + // ensure unique keys?! + if (!o[k]) { + s += k + ": " + err[k] + "\n"; + o[k] = err[k]; + } + } + ok ( false, s ); + +} +</script> +</pre> +</body> +</html>
