root/mochikit/tags/MochiKit-1.4.2/MochiKit/Signal.js

Revision 1473, 28.5 KB (checked in by cederberg@…, 22 months ago)

Updated 1.4 branch version number for upcoming 1.4.2 release.

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