Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /***
3 MochiKit.Signal 1.4.2
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito. All rights Reserved.
9 ***/
11 MochiKit.Base._deps('Signal', ['Base', 'DOM', 'Style']);
13 MochiKit.Signal.NAME = 'MochiKit.Signal';
14 MochiKit.Signal.VERSION = '1.4.2';
16 MochiKit.Signal._observers = [];
18 /** @id MochiKit.Signal.Event */
19 MochiKit.Signal.Event = function (src, e) {
20 this._event = e || window.event;
21 this._src = src;
22 };
24 MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
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());
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 }
45 if (this.type() && this.type().indexOf('key') === 0) {
46 str += ', key(): {code: ' + repr(this.key().code) +
47 ', string: ' + repr(this.key().string) + '}';
48 }
50 if (this.type() && (
51 this.type().indexOf('mouse') === 0 ||
52 this.type().indexOf('click') != -1 ||
53 this.type() == 'contextmenu')) {
55 str += ', mouse(): {page: ' + repr(this.mouse().page) +
56 ', client: ' + repr(this.mouse().client);
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 },
76 /** @id MochiKit.Signal.Event.prototype.toString */
77 toString: function () {
78 return this.__repr__();
79 },
81 /** @id MochiKit.Signal.Event.prototype.src */
82 src: function () {
83 return this._src;
84 },
86 /** @id MochiKit.Signal.Event.prototype.event */
87 event: function () {
88 return this._event;
89 },
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 },
100 /** @id MochiKit.Signal.Event.prototype.target */
101 target: function () {
102 return this._event.target || this._event.srcElement;
103 },
105 _relatedTarget: null,
106 /** @id MochiKit.Signal.Event.prototype.relatedTarget */
107 relatedTarget: function () {
108 if (this._relatedTarget !== null) {
109 return this._relatedTarget;
110 }
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 }
130 return undefined;
131 },
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 },
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) {
158 /*
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.
165 Notes:
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
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
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
215 */
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;
225 /* look for characters here */
226 } else if (this.type() == 'keypress') {
228 /*
230 Special key behavior:
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
236 */
238 k.code = 0;
239 k.string = '';
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 }
252 this._key = k;
253 return k;
254 }
255 }
256 return undefined;
257 },
259 _mouse: null,
260 /** @id MochiKit.Signal.Event.prototype.mouse */
261 mouse: function () {
262 if (this._mouse !== null) {
263 return this._mouse;
264 }
266 var m = {};
267 var e = this._event;
269 if (this.type() && (
270 this.type().indexOf('mouse') === 0 ||
271 this.type().indexOf('click') != -1 ||
272 this.type() == 'contextmenu')) {
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 }
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 /*
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
290 This is similar to the method used in
291 MochiKit.Style.getElementPosition().
293 */
294 var de = MochiKit.DOM._document.documentElement;
295 var b = MochiKit.DOM._document.body;
297 m.page.x = e.clientX +
298 (de.scrollLeft || b.scrollLeft) -
299 (de.clientLeft || 0);
301 m.page.y = e.clientY +
302 (de.scrollTop || b.scrollTop) -
303 (de.clientTop || 0);
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;
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);
318 /*
320 Mac browsers and right click:
322 - Safari doesn't fire any click events on a right
323 click:
324 http://bugs.webkit.org/show_bug.cgi?id=6595
326 - Firefox fires the event, and sets ctrlKey = true
328 - Opera fires the event, and sets metaKey = true
330 oncontextmenu is fired on right clicks between
331 browsers and across platforms.
333 */
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 },
358 /** @id MochiKit.Signal.Event.prototype.stop */
359 stop: function () {
360 this.stopPropagation();
361 this.preventDefault();
362 },
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 },
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 },
382 _confirmUnload: null,
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 });
393 /* Safari sets keyCode to these special values onkeypress. */
394 MochiKit.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 };
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 })();
418 /* Standard keyboard key codes. */
419 MochiKit.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 };
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 }
474 /* for KEY_A - KEY_Z */
475 for (i = 65; i <= 90; i++) {
476 _specialKeys[i] = 'KEY_' + String.fromCharCode(i);
477 }
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 }
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 })();
491 /* Internal object to keep track of created signals. */
492 MochiKit.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 };
502 MochiKit.Signal.Ident.prototype = {};
504 MochiKit.Base.update(MochiKit.Signal, {
506 __repr__: function () {
507 return '[' + this.NAME + ' ' + this.VERSION + ']';
508 },
510 toString: function () {
511 return this.__repr__();
512 },
514 _unloadCache: function () {
515 var self = MochiKit.Signal;
516 var observers = self._observers;
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 },
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)]);
542 var ident = new MochiKit.Signal.Ident({
543 source: src, signal: sig, objOrFunc: obj, funcOrStr: func});
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)]);
557 var ident = new MochiKit.Signal.Ident({
558 source: src, signal: sig, objOrFunc: func});
560 MochiKit.Signal._disconnect(ident);
561 };
562 } else {
563 return function (nativeEvent) {
564 func.apply(obj, [new E(src, nativeEvent)]);
565 };
566 }
567 }
568 },
570 _browserAlreadyHasMouseEnterAndLeave: function () {
571 return /MSIE/.test(navigator.userAgent);
572 },
574 _browserLacksMouseWheelEvent: function () {
575 return /Gecko\//.test(navigator.userAgent);
576 },
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 },
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 },
626 /** @id MochiKit.Signal.connect */
627 connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
628 src = MochiKit.DOM.getElement(src);
629 var self = MochiKit.Signal;
631 if (typeof(sig) != 'string') {
632 throw new Error("'sig' must be a string");
633 }
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 }
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 }
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 }
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);
675 if (!isDOM && typeof(src.__connect__) == 'function') {
676 var args = MochiKit.Base.extend([ident], arguments, 1);
677 src.__connect__.apply(src, args);
678 }
680 return ident;
681 },
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 },
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 },
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 },
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 },
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 }
852 });
854 MochiKit.Signal.EXPORT_OK = [];
856 MochiKit.Signal.EXPORT = [
857 'connect',
858 'disconnect',
859 'signal',
860 'disconnectAll',
861 'disconnectAllTo'
862 ];
864 MochiKit.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;
871 try {
872 this.connect(window, 'onunload', this._unloadCache);
873 } catch (e) {
874 // pass: might not be a browser
875 }
877 this.EXPORT_TAGS = {
878 ':common': this.EXPORT,
879 ':all': m.concat(this.EXPORT, this.EXPORT_OK)
880 };
882 m.nameFunctions(this);
883 };
885 MochiKit.Signal.__new__(this);
887 //
888 // XXX: Internet Explorer blows
889 //
890 if (MochiKit.__export__) {
891 connect = MochiKit.Signal.connect;
892 disconnect = MochiKit.Signal.disconnect;
893 disconnectAll = MochiKit.Signal.disconnectAll;
894 signal = MochiKit.Signal.signal;
895 }
897 MochiKit.Base._exportSymbols(this, MochiKit.Signal);