SGN::Test::WWW::Mechanize should use the SGN_TEST_SERVER env var
[sgn.git] / js / MochiKit / Signal.js
blobeda07ac823bfb32a97ed5fffeec1b387decc39aa
1 /***
3 MochiKit.Signal 1.4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito.  All rights Reserved.
9 ***/
11 if (typeof(dojo) != 'undefined') {
12     dojo.provide('MochiKit.Signal');
13     dojo.require('MochiKit.Base');
14     dojo.require('MochiKit.DOM');
15     dojo.require('MochiKit.Style');
17 if (typeof(JSAN) != 'undefined') {
18     JSAN.use('MochiKit.Base', []);
19     JSAN.use('MochiKit.DOM', []);
20     JSAN.use('MochiKit.Style', []);
23 try {
24     if (typeof(MochiKit.Base) == 'undefined') {
25         throw '';
26     }
27 } catch (e) {
28     throw 'MochiKit.Signal depends on MochiKit.Base!';
31 try {
32     if (typeof(MochiKit.DOM) == 'undefined') {
33         throw '';
34     }
35 } catch (e) {
36     throw 'MochiKit.Signal depends on MochiKit.DOM!';
39 try {
40     if (typeof(MochiKit.Style) == 'undefined') {
41         throw '';
42     }
43 } catch (e) {
44     throw 'MochiKit.Signal depends on MochiKit.Style!';
47 if (typeof(MochiKit.Signal) == 'undefined') {
48     MochiKit.Signal = {};
51 MochiKit.Signal.NAME = 'MochiKit.Signal';
52 MochiKit.Signal.VERSION = '1.4';
54 MochiKit.Signal._observers = [];
56 /** @id MochiKit.Signal.Event */
57 MochiKit.Signal.Event = function (src, e) {
58     this._event = e || window.event;
59     this._src = src;
62 MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
64     __repr__: function () {
65         var repr = MochiKit.Base.repr;
66         var str = '{event(): ' + repr(this.event()) +
67             ', src(): ' + repr(this.src()) +
68             ', type(): ' + repr(this.type()) +
69             ', target(): ' + repr(this.target());
71         if (this.type() && 
72             this.type().indexOf('key') === 0 ||
73             this.type().indexOf('mouse') === 0 ||
74             this.type().indexOf('click') != -1 ||
75             this.type() == 'contextmenu') {
76             str += ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) +
77             ', ctrl: ' + repr(this.modifier().ctrl) +
78             ', meta: ' + repr(this.modifier().meta) +
79             ', shift: ' + repr(this.modifier().shift) +
80             ', any: ' + repr(this.modifier().any) + '}';
81         }
83         if (this.type() && this.type().indexOf('key') === 0) {
84             str += ', key(): {code: ' + repr(this.key().code) +
85                 ', string: ' + repr(this.key().string) + '}';
86         }
88         if (this.type() && (
89             this.type().indexOf('mouse') === 0 ||
90             this.type().indexOf('click') != -1 ||
91             this.type() == 'contextmenu')) {
93             str += ', mouse(): {page: ' + repr(this.mouse().page) +
94                 ', client: ' + repr(this.mouse().client);
96             if (this.type() != 'mousemove') {
97                 str += ', button: {left: ' + repr(this.mouse().button.left) +
98                     ', middle: ' + repr(this.mouse().button.middle) +
99                     ', right: ' + repr(this.mouse().button.right) + '}}';
100             } else {
101                 str += '}';
102             }
103         }
104         if (this.type() == 'mouseover' || this.type() == 'mouseout') {
105             str += ', relatedTarget(): ' + repr(this.relatedTarget());
106         }
107         str += '}';
108         return str;
109     },
111      /** @id MochiKit.Signal.Event.prototype.toString */
112     toString: function () {
113         return this.__repr__();
114     },
116     /** @id MochiKit.Signal.Event.prototype.src */
117     src: function () {
118         return this._src;
119     },
121     /** @id MochiKit.Signal.Event.prototype.event  */
122     event: function () {
123         return this._event;
124     },
126     /** @id MochiKit.Signal.Event.prototype.type */
127     type: function () {
128         return this._event.type || undefined;
129     },
131     /** @id MochiKit.Signal.Event.prototype.target */
132     target: function () {
133         return this._event.target || this._event.srcElement;
134     },
136     _relatedTarget: null,
137     /** @id MochiKit.Signal.Event.prototype.relatedTarget */
138     relatedTarget: function () {
139         if (this._relatedTarget !== null) {
140             return this._relatedTarget;
141         }
143         var elem = null;
144         if (this.type() == 'mouseover') {
145             elem = (this._event.relatedTarget ||
146                 this._event.fromElement);
147         } else if (this.type() == 'mouseout') {
148             elem = (this._event.relatedTarget ||
149                 this._event.toElement);
150         }
151         if (elem !== null) {
152             this._relatedTarget = elem;
153             return elem;
154         }
156         return undefined;
157     },
159     _modifier: null,
160     /** @id MochiKit.Signal.Event.prototype.modifier */
161     modifier: function () {
162         if (this._modifier !== null) {
163             return this._modifier;
164         }
165         var m = {};
166         m.alt = this._event.altKey;
167         m.ctrl = this._event.ctrlKey;
168         m.meta = this._event.metaKey || false; // IE and Opera punt here
169         m.shift = this._event.shiftKey;
170         m.any = m.alt || m.ctrl || m.shift || m.meta;
171         this._modifier = m;
172         return m;
173     },
175     _key: null,
176     /** @id MochiKit.Signal.Event.prototype.key */
177     key: function () {
178         if (this._key !== null) {
179             return this._key;
180         }
181         var k = {};
182         if (this.type() && this.type().indexOf('key') === 0) {
184             /*
186                 If you're looking for a special key, look for it in keydown or
187                 keyup, but never keypress. If you're looking for a Unicode
188                 chracter, look for it with keypress, but never keyup or
189                 keydown.
191                 Notes:
193                 FF key event behavior:
194                 key     event   charCode    keyCode
195                 DOWN    ku,kd   0           40
196                 DOWN    kp      0           40
197                 ESC     ku,kd   0           27
198                 ESC     kp      0           27
199                 a       ku,kd   0           65
200                 a       kp      97          0
201                 shift+a ku,kd   0           65
202                 shift+a kp      65          0
203                 1       ku,kd   0           49
204                 1       kp      49          0
205                 shift+1 ku,kd   0           0
206                 shift+1 kp      33          0
208                 IE key event behavior:
209                 (IE doesn't fire keypress events for special keys.)
210                 key     event   keyCode
211                 DOWN    ku,kd   40
212                 DOWN    kp      undefined
213                 ESC     ku,kd   27
214                 ESC     kp      27
215                 a       ku,kd   65
216                 a       kp      97
217                 shift+a ku,kd   65
218                 shift+a kp      65
219                 1       ku,kd   49
220                 1       kp      49
221                 shift+1 ku,kd   49
222                 shift+1 kp      33
224                 Safari key event behavior:
225                 (Safari sets charCode and keyCode to something crazy for
226                 special keys.)
227                 key     event   charCode    keyCode
228                 DOWN    ku,kd   63233       40
229                 DOWN    kp      63233       63233
230                 ESC     ku,kd   27          27
231                 ESC     kp      27          27
232                 a       ku,kd   97          65
233                 a       kp      97          97
234                 shift+a ku,kd   65          65
235                 shift+a kp      65          65
236                 1       ku,kd   49          49
237                 1       kp      49          49
238                 shift+1 ku,kd   33          49
239                 shift+1 kp      33          33
241             */
243             /* look for special keys here */
244             if (this.type() == 'keydown' || this.type() == 'keyup') {
245                 k.code = this._event.keyCode;
246                 k.string = (MochiKit.Signal._specialKeys[k.code] ||
247                     'KEY_UNKNOWN');
248                 this._key = k;
249                 return k;
251             /* look for characters here */
252             } else if (this.type() == 'keypress') {
254                 /*
256                     Special key behavior:
258                     IE: does not fire keypress events for special keys
259                     FF: sets charCode to 0, and sets the correct keyCode
260                     Safari: sets keyCode and charCode to something stupid
262                 */
264                 k.code = 0;
265                 k.string = '';
267                 if (typeof(this._event.charCode) != 'undefined' &&
268                     this._event.charCode !== 0 &&
269                     !MochiKit.Signal._specialMacKeys[this._event.charCode]) {
270                     k.code = this._event.charCode;
271                     k.string = String.fromCharCode(k.code);
272                 } else if (this._event.keyCode &&
273                     typeof(this._event.charCode) == 'undefined') { // IE
274                     k.code = this._event.keyCode;
275                     k.string = String.fromCharCode(k.code);
276                 }
278                 this._key = k;
279                 return k;
280             }
281         }
282         return undefined;
283     },
285     _mouse: null,
286     /** @id MochiKit.Signal.Event.prototype.mouse */
287     mouse: function () {
288         if (this._mouse !== null) {
289             return this._mouse;
290         }
292         var m = {};
293         var e = this._event;
295         if (this.type() && (
296             this.type().indexOf('mouse') === 0 ||
297             this.type().indexOf('click') != -1 ||
298             this.type() == 'contextmenu')) {
300             m.client = new MochiKit.Style.Coordinates(0, 0);
301             if (e.clientX || e.clientY) {
302                 m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX;
303                 m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY;
304             }
306             m.page = new MochiKit.Style.Coordinates(0, 0);
307             if (e.pageX || e.pageY) {
308                 m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
309                 m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
310             } else {
311                 /*
313                     The IE shortcut can be off by two. We fix it. See:
314                     http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
316                     This is similar to the method used in
317                     MochiKit.Style.getElementPosition().
319                 */
320                 var de = MochiKit.DOM._document.documentElement;
321                 var b = MochiKit.DOM._document.body;
323                 m.page.x = e.clientX +
324                     (de.scrollLeft || b.scrollLeft) -
325                     (de.clientLeft || 0);
327                 m.page.y = e.clientY +
328                     (de.scrollTop || b.scrollTop) -
329                     (de.clientTop || 0);
331             }
332             if (this.type() != 'mousemove') {
333                 m.button = {};
334                 m.button.left = false;
335                 m.button.right = false;
336                 m.button.middle = false;
338                 /* we could check e.button, but which is more consistent */
339                 if (e.which) {
340                     m.button.left = (e.which == 1);
341                     m.button.middle = (e.which == 2);
342                     m.button.right = (e.which == 3);
344                     /*
346                         Mac browsers and right click:
348                             - Safari doesn't fire any click events on a right
349                               click:
350                               http://bugs.webkit.org/show_bug.cgi?id=6595
352                             - Firefox fires the event, and sets ctrlKey = true
354                             - Opera fires the event, and sets metaKey = true
356                         oncontextmenu is fired on right clicks between
357                         browsers and across platforms.
359                     */
361                 } else {
362                     m.button.left = !!(e.button & 1);
363                     m.button.right = !!(e.button & 2);
364                     m.button.middle = !!(e.button & 4);
365                 }
366             }
367             this._mouse = m;
368             return m;
369         }
370         return undefined;
371     },
373     /** @id MochiKit.Signal.Event.prototype.stop */
374     stop: function () {
375         this.stopPropagation();
376         this.preventDefault();
377     },
379     /** @id MochiKit.Signal.Event.prototype.stopPropagation */
380     stopPropagation: function () {
381         if (this._event.stopPropagation) {
382             this._event.stopPropagation();
383         } else {
384             this._event.cancelBubble = true;
385         }
386     },
388     /** @id MochiKit.Signal.Event.prototype.preventDefault */
389     preventDefault: function () {
390         if (this._event.preventDefault) {
391             this._event.preventDefault();
392         } else if (this._confirmUnload === null) {
393             this._event.returnValue = false;
394         }
395     },
397     _confirmUnload: null,
399     /** @id MochiKit.Signal.Event.prototype.confirmUnload */
400     confirmUnload: function (msg) {
401         if (this.type() == 'beforeunload') {
402             this._confirmUnload = msg;
403             this._event.returnValue = msg;
404         }
405     }
408 /* Safari sets keyCode to these special values onkeypress. */
409 MochiKit.Signal._specialMacKeys = {
410     3: 'KEY_ENTER',
411     63289: 'KEY_NUM_PAD_CLEAR',
412     63276: 'KEY_PAGE_UP',
413     63277: 'KEY_PAGE_DOWN',
414     63275: 'KEY_END',
415     63273: 'KEY_HOME',
416     63234: 'KEY_ARROW_LEFT',
417     63232: 'KEY_ARROW_UP',
418     63235: 'KEY_ARROW_RIGHT',
419     63233: 'KEY_ARROW_DOWN',
420     63302: 'KEY_INSERT',
421     63272: 'KEY_DELETE'
424 /* for KEY_F1 - KEY_F12 */
425 (function () {
426     var _specialMacKeys = MochiKit.Signal._specialMacKeys;
427     for (i = 63236; i <= 63242; i++) {
428         // no F0
429         _specialMacKeys[i] = 'KEY_F' + (i - 63236 + 1);
430     }
431 })();
433 /* Standard keyboard key codes. */
434 MochiKit.Signal._specialKeys = {
435     8: 'KEY_BACKSPACE',
436     9: 'KEY_TAB',
437     12: 'KEY_NUM_PAD_CLEAR', // weird, for Safari and Mac FF only
438     13: 'KEY_ENTER',
439     16: 'KEY_SHIFT',
440     17: 'KEY_CTRL',
441     18: 'KEY_ALT',
442     19: 'KEY_PAUSE',
443     20: 'KEY_CAPS_LOCK',
444     27: 'KEY_ESCAPE',
445     32: 'KEY_SPACEBAR',
446     33: 'KEY_PAGE_UP',
447     34: 'KEY_PAGE_DOWN',
448     35: 'KEY_END',
449     36: 'KEY_HOME',
450     37: 'KEY_ARROW_LEFT',
451     38: 'KEY_ARROW_UP',
452     39: 'KEY_ARROW_RIGHT',
453     40: 'KEY_ARROW_DOWN',
454     44: 'KEY_PRINT_SCREEN',
455     45: 'KEY_INSERT',
456     46: 'KEY_DELETE',
457     59: 'KEY_SEMICOLON', // weird, for Safari and IE only
458     91: 'KEY_WINDOWS_LEFT',
459     92: 'KEY_WINDOWS_RIGHT',
460     93: 'KEY_SELECT',
461     106: 'KEY_NUM_PAD_ASTERISK',
462     107: 'KEY_NUM_PAD_PLUS_SIGN',
463     109: 'KEY_NUM_PAD_HYPHEN-MINUS',
464     110: 'KEY_NUM_PAD_FULL_STOP',
465     111: 'KEY_NUM_PAD_SOLIDUS',
466     144: 'KEY_NUM_LOCK',
467     145: 'KEY_SCROLL_LOCK',
468     186: 'KEY_SEMICOLON',
469     187: 'KEY_EQUALS_SIGN',
470     188: 'KEY_COMMA',
471     189: 'KEY_HYPHEN-MINUS',
472     190: 'KEY_FULL_STOP',
473     191: 'KEY_SOLIDUS',
474     192: 'KEY_GRAVE_ACCENT',
475     219: 'KEY_LEFT_SQUARE_BRACKET',
476     220: 'KEY_REVERSE_SOLIDUS',
477     221: 'KEY_RIGHT_SQUARE_BRACKET',
478     222: 'KEY_APOSTROPHE'
479     // undefined: 'KEY_UNKNOWN'
482 (function () {
483     /* for KEY_0 - KEY_9 */
484     var _specialKeys = MochiKit.Signal._specialKeys;
485     for (var i = 48; i <= 57; i++) {
486         _specialKeys[i] = 'KEY_' + (i - 48);
487     }
489     /* for KEY_A - KEY_Z */
490     for (i = 65; i <= 90; i++) {
491         _specialKeys[i] = 'KEY_' + String.fromCharCode(i);
492     }
494     /* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */
495     for (i = 96; i <= 105; i++) {
496         _specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96);
497     }
499     /* for KEY_F1 - KEY_F12 */
500     for (i = 112; i <= 123; i++) {
501         // no F0
502         _specialKeys[i] = 'KEY_F' + (i - 112 + 1);
503     }
504 })();
506 MochiKit.Base.update(MochiKit.Signal, {
508     __repr__: function () {
509         return '[' + this.NAME + ' ' + this.VERSION + ']';
510     },
512     toString: function () {
513         return this.__repr__();
514     },
516     _unloadCache: function () {
517         var self = MochiKit.Signal;
518         var observers = self._observers;
520         for (var i = 0; i < observers.length; i++) {
521             if (observers[i][1] !== 'onload' && observers[i][1] !== 'onunload') {
522                 self._disconnect(observers[i]);
523             }
524         }
525     },
527     _listener: function (src, sig, func, obj, isDOM) {
528         var self = MochiKit.Signal;
529         var E = self.Event;
530         if (!isDOM) {
531             return MochiKit.Base.bind(func, obj);
532         }
533         obj = obj || src;
534         if (typeof(func) == "string") {
535             if (sig === 'onload' || sig === 'onunload') {
536                 return function (nativeEvent) {
537                     obj[func].apply(obj, [new E(src, nativeEvent)]);
538                     MochiKit.Signal.disconnect(src, sig, obj, func);
539                 };                
540             } else {
541                 return function (nativeEvent) {
542                     obj[func].apply(obj, [new E(src, nativeEvent)]);
543                 };
544             }
545         } else {
546             if (sig === 'onload' || sig === 'onunload') {
547                 return function (nativeEvent) {
548                     func.apply(obj, [new E(src, nativeEvent)]);
549                     MochiKit.Signal.disconnect(src, sig, func);
550                 };                
551             } else {
552                 return function (nativeEvent) {
553                     func.apply(obj, [new E(src, nativeEvent)]);
554                 };
555             }
556         }
557     },
559     _browserAlreadyHasMouseEnterAndLeave: function () {
560         return /MSIE/.test(navigator.userAgent);
561     },
563     _mouseEnterListener: function (src, sig, func, obj) {
564         var E = MochiKit.Signal.Event;
565         return function (nativeEvent) {
566             var e = new E(src, nativeEvent);
567             try {
568                 e.relatedTarget().nodeName;
569             } catch (err) {
570                 /* probably hit a permission denied error; possibly one of
571                  * firefox's screwy anonymous DIVs inside an input element.
572                  * Allow this event to propogate up.
573                  */
574                 return;
575             }
576             e.stop();
577             if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) {
578                 /* We've moved between our node and a child. Ignore. */
579                 return;
580             }
581             e.type = function () { return sig; };
582             if (typeof(func) == "string") {
583                 return obj[func].apply(obj, [e]);
584             } else {
585                 return func.apply(obj, [e]);
586             }
587         };
588     },
590     _getDestPair: function (objOrFunc, funcOrStr) {
591         var obj = null;
592         var func = null;
593         if (typeof(funcOrStr) != 'undefined') {
594             obj = objOrFunc;
595             func = funcOrStr;
596             if (typeof(funcOrStr) == 'string') {
597                 if (typeof(objOrFunc[funcOrStr]) != "function") {
598                     throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
599                 }
600             } else if (typeof(funcOrStr) != 'function') {
601                 throw new Error("'funcOrStr' must be a function or string");
602             }
603         } else if (typeof(objOrFunc) != "function") {
604             throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
605         } else {
606             func = objOrFunc;
607         }
608         return [obj, func];
610     },
612     /** @id MochiKit.Signal.connect */
613     connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
614         src = MochiKit.DOM.getElement(src);
615         var self = MochiKit.Signal;
617         if (typeof(sig) != 'string') {
618             throw new Error("'sig' must be a string");
619         }
621         var destPair = self._getDestPair(objOrFunc, funcOrStr);
622         var obj = destPair[0];
623         var func = destPair[1];
624         if (typeof(obj) == 'undefined' || obj === null) {
625             obj = src;
626         }
628         var isDOM = !!(src.addEventListener || src.attachEvent);
629         if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave")
630                   && !self._browserAlreadyHasMouseEnterAndLeave()) {
631             var listener = self._mouseEnterListener(src, sig.substr(2), func, obj);
632             if (sig === "onmouseenter") {
633                 sig = "onmouseover";
634             } else {
635                 sig = "onmouseout";
636             }
637         } else {
638             var listener = self._listener(src, sig, func, obj, isDOM);
639         }
641         if (src.addEventListener) {
642             src.addEventListener(sig.substr(2), listener, false);
643         } else if (src.attachEvent) {
644             src.attachEvent(sig, listener); // useCapture unsupported
645         }
647         var ident = [src, sig, listener, isDOM, objOrFunc, funcOrStr, true];
648         self._observers.push(ident);
651         if (!isDOM && typeof(src.__connect__) == 'function') {
652             var args = MochiKit.Base.extend([ident], arguments, 1);
653             src.__connect__.apply(src, args);
654         }
657         return ident;
658     },
660     _disconnect: function (ident) {
661         // already disconnected
662         if (!ident[6]) { return; }
663         ident[6] = false;
664         // check isDOM
665         if (!ident[3]) { return; }
666         var src = ident[0];
667         var sig = ident[1];
668         var listener = ident[2];
669         
670         if (src.removeEventListener) {
671             src.removeEventListener(sig.substr(2), listener, false);
672         } else if (src.detachEvent) {
673             src.detachEvent(sig, listener); // useCapture unsupported
674         } else {
675             throw new Error("'src' must be a DOM element");
676         }
677     },
679      /** @id MochiKit.Signal.disconnect */
680     disconnect: function (ident) {
681         var self = MochiKit.Signal;
682         var observers = self._observers;
683         var m = MochiKit.Base;
684         if (arguments.length > 1) {
685             // compatibility API
686             var src = MochiKit.DOM.getElement(arguments[0]);
687             var sig = arguments[1];
688             var obj = arguments[2];
689             var func = arguments[3];
690             for (var i = observers.length - 1; i >= 0; i--) {
691                 var o = observers[i];
692                 if (o[0] === src && o[1] === sig && o[4] === obj && o[5] === func) {
693                     self._disconnect(o);
694                     if (!self._lock) {
695                         observers.splice(i, 1);
696                     } else {
697                         self._dirty = true;
698                     }
699                     return true;
700                 }
701             }
702         } else {
703             var idx = m.findIdentical(observers, ident);
704             if (idx >= 0) {
705                 self._disconnect(ident);
706                 if (!self._lock) {
707                     observers.splice(idx, 1);
708                 } else {
709                     self._dirty = true;
710                 }
711                 return true;
712             }
713         }
714         return false;
715     },
717     /** @id MochiKit.Signal.disconnectAllTo */
718     disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) {
719         var self = MochiKit.Signal;
720         var observers = self._observers;
721         var disconnect = self._disconnect;
722         var locked = self._lock;
723         var dirty = self._dirty;
724         if (typeof(funcOrStr) === 'undefined') {
725             funcOrStr = null;
726         }
727         for (var i = observers.length - 1; i >= 0; i--) {
728             var ident = observers[i];
729             if (ident[4] === objOrFunc &&
730                     (funcOrStr === null || ident[5] === funcOrStr)) {
731                 disconnect(ident);
732                 if (locked) {
733                     dirty = true;
734                 } else {
735                     observers.splice(i, 1);
736                 }
737             }
738         }
739         self._dirty = dirty;
740     },
742     /** @id MochiKit.Signal.disconnectAll */
743     disconnectAll: function (src/* optional */, sig) {
744         src = MochiKit.DOM.getElement(src);
745         var m = MochiKit.Base;
746         var signals = m.flattenArguments(m.extend(null, arguments, 1));
747         var self = MochiKit.Signal;
748         var disconnect = self._disconnect;
749         var observers = self._observers;
750         var i, ident;
751         var locked = self._lock;
752         var dirty = self._dirty;
753         if (signals.length === 0) {
754             // disconnect all
755             for (i = observers.length - 1; i >= 0; i--) {
756                 ident = observers[i];
757                 if (ident[0] === src) {
758                     disconnect(ident);
759                     if (!locked) {
760                         observers.splice(i, 1);
761                     } else {
762                         dirty = true;
763                     }
764                 }
765             }
766         } else {
767             var sigs = {};
768             for (i = 0; i < signals.length; i++) {
769                 sigs[signals[i]] = true;
770             }
771             for (i = observers.length - 1; i >= 0; i--) {
772                 ident = observers[i];
773                 if (ident[0] === src && ident[1] in sigs) {
774                     disconnect(ident);
775                     if (!locked) {
776                         observers.splice(i, 1);
777                     } else {
778                         dirty = true;
779                     }
780                 }
781             }
782         }
783         self._dirty = dirty;
784     },
786     /** @id MochiKit.Signal.signal */
787     signal: function (src, sig) {
788         var self = MochiKit.Signal;
789         var observers = self._observers;
790         src = MochiKit.DOM.getElement(src);
791         var args = MochiKit.Base.extend(null, arguments, 2);
792         var errors = [];
793         self._lock = true;
794         for (var i = 0; i < observers.length; i++) {
795             var ident = observers[i];
796             if (ident[0] === src && ident[1] === sig) {
797                 try {
798                     ident[2].apply(src, args);
799                 } catch (e) {
800                     errors.push(e);
801                 }
802             }
803         }
804         self._lock = false;
805         if (self._dirty) {
806             self._dirty = false;
807             for (var i = observers.length - 1; i >= 0; i--) {
808                 if (!observers[i][6]) {
809                     observers.splice(i, 1);
810                 }
811             }
812         }
813         if (errors.length == 1) {
814             throw errors[0];
815         } else if (errors.length > 1) {
816             var e = new Error("Multiple errors thrown in handling 'sig', see errors property");
817             e.errors = errors;
818             throw e;
819         }
820     }
824 MochiKit.Signal.EXPORT_OK = [];
826 MochiKit.Signal.EXPORT = [
827     'connect',
828     'disconnect',
829     'signal',
830     'disconnectAll',
831     'disconnectAllTo'
834 MochiKit.Signal.__new__ = function (win) {
835     var m = MochiKit.Base;
836     this._document = document;
837     this._window = win;
838     this._lock = false;
839     this._dirty = false;
841     try {
842         this.connect(window, 'onunload', this._unloadCache);
843     } catch (e) {
844         // pass: might not be a browser
845     }
847     this.EXPORT_TAGS = {
848         ':common': this.EXPORT,
849         ':all': m.concat(this.EXPORT, this.EXPORT_OK)
850     };
852     m.nameFunctions(this);
855 MochiKit.Signal.__new__(this);
858 // XXX: Internet Explorer blows
860 if (MochiKit.__export__) {
861     connect = MochiKit.Signal.connect;
862     disconnect = MochiKit.Signal.disconnect;
863     disconnectAll = MochiKit.Signal.disconnectAll;
864     signal = MochiKit.Signal.signal;
867 MochiKit.Base._exportSymbols(this, MochiKit.Signal);