testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Signal.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:ed11564898f3
1 /***
2
3 MochiKit.Signal 1.4.2
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 MochiKit.Base._deps('Signal', ['Base', 'DOM', 'Style']);
12
13 MochiKit.Signal.NAME = 'MochiKit.Signal';
14 MochiKit.Signal.VERSION = '1.4.2';
15
16 MochiKit.Signal._observers = [];
17
18 /** @id MochiKit.Signal.Event */
19 MochiKit.Signal.Event = function (src, e) {
20 this._event = e || window.event;
21 this._src = src;
22 };
23
24 MochiKit.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. */
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 };
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. */
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 };
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. */
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 };
501
502 MochiKit.Signal.Ident.prototype = {};
503
504 MochiKit.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
854 MochiKit.Signal.EXPORT_OK = [];
855
856 MochiKit.Signal.EXPORT = [
857 'connect',
858 'disconnect',
859 'signal',
860 'disconnectAll',
861 'disconnectAllTo'
862 ];
863
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;
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
885 MochiKit.Signal.__new__(this);
886
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 }
896
897 MochiKit.Base._exportSymbols(this, MochiKit.Signal);

mercurial