keep removing MochiKit dependencies
[dygraphs.git] / mochikit_v14 / MochiKit / Signal.js
1 /***
2
3 MochiKit.Signal 1.4
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 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Signal');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Style');
16 }
17 if (typeof(JSAN) != 'undefined') {
18 JSAN.use('MochiKit.Base', []);
19 JSAN.use('MochiKit.DOM', []);
20 JSAN.use('MochiKit.Style', []);
21 }
22
23 try {
24 if (typeof(MochiKit.Base) == 'undefined') {
25 throw '';
26 }
27 } catch (e) {
28 throw 'MochiKit.Signal depends on MochiKit.Base!';
29 }
30
31 try {
32 if (typeof(MochiKit.DOM) == 'undefined') {
33 throw '';
34 }
35 } catch (e) {
36 throw 'MochiKit.Signal depends on MochiKit.DOM!';
37 }
38
39 try {
40 if (typeof(MochiKit.Style) == 'undefined') {
41 throw '';
42 }
43 } catch (e) {
44 throw 'MochiKit.Signal depends on MochiKit.Style!';
45 }
46
47 if (typeof(MochiKit.Signal) == 'undefined') {
48 MochiKit.Signal = {};
49 }
50
51 MochiKit.Signal.NAME = 'MochiKit.Signal';
52 MochiKit.Signal.VERSION = '1.4';
53
54 MochiKit.Signal._observers = [];
55
56 /** @id MochiKit.Signal.Event */
57 MochiKit.Signal.Event = function (src, e) {
58 this._event = e || window.event;
59 this._src = src;
60 };
61
62 MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
63
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());
70
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 }
82
83 if (this.type() && this.type().indexOf('key') === 0) {
84 str += ', key(): {code: ' + repr(this.key().code) +
85 ', string: ' + repr(this.key().string) + '}';
86 }
87
88 if (this.type() && (
89 this.type().indexOf('mouse') === 0 ||
90 this.type().indexOf('click') != -1 ||
91 this.type() == 'contextmenu')) {
92
93 str += ', mouse(): {page: ' + repr(this.mouse().page) +
94 ', client: ' + repr(this.mouse().client);
95
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 },
110
111 /** @id MochiKit.Signal.Event.prototype.toString */
112 toString: function () {
113 return this.__repr__();
114 },
115
116 /** @id MochiKit.Signal.Event.prototype.src */
117 src: function () {
118 return this._src;
119 },
120
121 /** @id MochiKit.Signal.Event.prototype.event */
122 event: function () {
123 return this._event;
124 },
125
126 /** @id MochiKit.Signal.Event.prototype.type */
127 type: function () {
128 return this._event.type || undefined;
129 },
130
131 /** @id MochiKit.Signal.Event.prototype.target */
132 target: function () {
133 return this._event.target || this._event.srcElement;
134 },
135
136 _relatedTarget: null,
137 /** @id MochiKit.Signal.Event.prototype.relatedTarget */
138 relatedTarget: function () {
139 if (this._relatedTarget !== null) {
140 return this._relatedTarget;
141 }
142
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 }
155
156 return undefined;
157 },
158
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 },
174
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) {
183
184 /*
185
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.
190
191 Notes:
192
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
207
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
223
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
240
241 */
242
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;
250
251 /* look for characters here */
252 } else if (this.type() == 'keypress') {
253
254 /*
255
256 Special key behavior:
257
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
261
262 */
263
264 k.code = 0;
265 k.string = '';
266
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 }
277
278 this._key = k;
279 return k;
280 }
281 }
282 return undefined;
283 },
284
285 _mouse: null,
286 /** @id MochiKit.Signal.Event.prototype.mouse */
287 mouse: function () {
288 if (this._mouse !== null) {
289 return this._mouse;
290 }
291
292 var m = {};
293 var e = this._event;
294
295 if (this.type() && (
296 this.type().indexOf('mouse') === 0 ||
297 this.type().indexOf('click') != -1 ||
298 this.type() == 'contextmenu')) {
299
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 }
305
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 /*
312
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
315
316 This is similar to the method used in
317 MochiKit.Style.getElementPosition().
318
319 */
320 var de = MochiKit.DOM._document.documentElement;
321 var b = MochiKit.DOM._document.body;
322
323 m.page.x = e.clientX +
324 (de.scrollLeft || b.scrollLeft) -
325 (de.clientLeft || 0);
326
327 m.page.y = e.clientY +
328 (de.scrollTop || b.scrollTop) -
329 (de.clientTop || 0);
330
331 }
332 if (this.type() != 'mousemove') {
333 m.button = {};
334 m.button.left = false;
335 m.button.right = false;
336 m.button.middle = false;
337
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);
343
344 /*
345
346 Mac browsers and right click:
347
348 - Safari doesn't fire any click events on a right
349 click:
350 http://bugs.webkit.org/show_bug.cgi?id=6595
351
352 - Firefox fires the event, and sets ctrlKey = true
353
354 - Opera fires the event, and sets metaKey = true
355
356 oncontextmenu is fired on right clicks between
357 browsers and across platforms.
358
359 */
360
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 },
372
373 /** @id MochiKit.Signal.Event.prototype.stop */
374 stop: function () {
375 this.stopPropagation();
376 this.preventDefault();
377 },
378
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 },
387
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 },
396
397 _confirmUnload: null,
398
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 }
406 });
407
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'
422 };
423
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 })();
432
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'
480 };
481
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 }
488
489 /* for KEY_A - KEY_Z */
490 for (i = 65; i <= 90; i++) {
491 _specialKeys[i] = 'KEY_' + String.fromCharCode(i);
492 }
493
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 }
498
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 })();
505
506 /* Internal object to keep track of created signals. */
507 MochiKit.Signal.Ident = function (ident) {
508 this.source = ident.source;
509 this.signal = ident.signal;
510 this.listener = ident.listener;
511 this.isDOM = ident.isDOM;
512 this.objOrFunc = ident.objOrFunc;
513 this.funcOrStr = ident.funcOrStr;
514 this.connected = ident.connected;
515 };
516
517 MochiKit.Signal.Ident.prototype = {};
518
519 MochiKit.Base.update(MochiKit.Signal, {
520
521 __repr__: function () {
522 return '[' + this.NAME + ' ' + this.VERSION + ']';
523 },
524
525 toString: function () {
526 return this.__repr__();
527 },
528
529 _unloadCache: function () {
530 var self = MochiKit.Signal;
531 var observers = self._observers;
532
533 for (var i = 0; i < observers.length; i++) {
534 if (observers[i].signal !== 'onload' && observers[i].signal !== 'onunload') {
535 self._disconnect(observers[i]);
536 }
537 }
538 },
539
540 _listener: function (src, sig, func, obj, isDOM) {
541 var self = MochiKit.Signal;
542 var E = self.Event;
543 if (!isDOM) {
544 return MochiKit.Base.bind(func, obj);
545 }
546 obj = obj || src;
547 if (typeof(func) == "string") {
548 if (sig === 'onload' || sig === 'onunload') {
549 return function (nativeEvent) {
550 obj[func].apply(obj, [new E(src, nativeEvent)]);
551
552 var ident = new MochiKit.Signal.Ident({
553 source: src, signal: sig, objOrFunc: obj, funcOrStr: func});
554
555 MochiKit.Signal._disconnect(ident);
556 };
557 } else {
558 return function (nativeEvent) {
559 obj[func].apply(obj, [new E(src, nativeEvent)]);
560 };
561 }
562 } else {
563 if (sig === 'onload' || sig === 'onunload') {
564 return function (nativeEvent) {
565 func.apply(obj, [new E(src, nativeEvent)]);
566 MochiKit.Signal.disconnect(src, sig, func);
567
568 var ident = new MochiKit.Signal.Ident({
569 source: src, signal: sig, objOrFunc: func});
570
571 MochiKit.Signal._disconnect(ident);
572 };
573 } else {
574 return function (nativeEvent) {
575 func.apply(obj, [new E(src, nativeEvent)]);
576 };
577 }
578 }
579 },
580
581 _browserAlreadyHasMouseEnterAndLeave: function () {
582 return /MSIE/.test(navigator.userAgent);
583 },
584
585 _mouseEnterListener: function (src, sig, func, obj) {
586 var E = MochiKit.Signal.Event;
587 return function (nativeEvent) {
588 var e = new E(src, nativeEvent);
589 try {
590 e.relatedTarget().nodeName;
591 } catch (err) {
592 /* probably hit a permission denied error; possibly one of
593 * firefox's screwy anonymous DIVs inside an input element.
594 * Allow this event to propogate up.
595 */
596 return;
597 }
598 e.stop();
599 if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) {
600 /* We've moved between our node and a child. Ignore. */
601 return;
602 }
603 e.type = function () { return sig; };
604 if (typeof(func) == "string") {
605 return obj[func].apply(obj, [e]);
606 } else {
607 return func.apply(obj, [e]);
608 }
609 };
610 },
611
612 _getDestPair: function (objOrFunc, funcOrStr) {
613 var obj = null;
614 var func = null;
615 if (typeof(funcOrStr) != 'undefined') {
616 obj = objOrFunc;
617 func = funcOrStr;
618 if (typeof(funcOrStr) == 'string') {
619 if (typeof(objOrFunc[funcOrStr]) != "function") {
620 throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
621 }
622 } else if (typeof(funcOrStr) != 'function') {
623 throw new Error("'funcOrStr' must be a function or string");
624 }
625 } else if (typeof(objOrFunc) != "function") {
626 throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
627 } else {
628 func = objOrFunc;
629 }
630 return [obj, func];
631 },
632
633 /** @id MochiKit.Signal.connect */
634 connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
635 // src = MochiKit.DOM.getElement(src);
636 var self = MochiKit.Signal;
637
638 if (typeof(sig) != 'string') {
639 throw new Error("'sig' must be a string");
640 }
641
642 var destPair = self._getDestPair(objOrFunc, funcOrStr);
643 var obj = destPair[0];
644 var func = destPair[1];
645 if (typeof(obj) == 'undefined' || obj === null) {
646 obj = src;
647 }
648
649 var isDOM = !!(src.addEventListener || src.attachEvent);
650 if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave")
651 && !self._browserAlreadyHasMouseEnterAndLeave()) {
652 var listener = self._mouseEnterListener(src, sig.substr(2), func, obj);
653 if (sig === "onmouseenter") {
654 sig = "onmouseover";
655 } else {
656 sig = "onmouseout";
657 }
658 } else {
659 var listener = self._listener(src, sig, func, obj, isDOM);
660 }
661
662 if (src.addEventListener) {
663 src.addEventListener(sig.substr(2), listener, false);
664 } else if (src.attachEvent) {
665 src.attachEvent(sig, listener); // useCapture unsupported
666 }
667
668 var ident = new MochiKit.Signal.Ident({
669 source: src,
670 signal: sig,
671 listener: listener,
672 isDOM: isDOM,
673 objOrFunc: objOrFunc,
674 funcOrStr: funcOrStr,
675 connected: true
676 });
677 self._observers.push(ident);
678
679 if (!isDOM && typeof(src.__connect__) == 'function') {
680 var args = MochiKit.Base.extend([ident], arguments, 1);
681 src.__connect__.apply(src, args);
682 }
683
684 return ident;
685 },
686
687 _disconnect: function (ident) {
688 // already disconnected
689 if (!ident.connected) {
690 return;
691 }
692 ident.connected = false;
693 // check isDOM
694 if (!ident.isDOM) {
695 return;
696 }
697 var src = ident.source;
698 var sig = ident.signal;
699 var listener = ident.listener;
700
701 if (src.removeEventListener) {
702 src.removeEventListener(sig.substr(2), listener, false);
703 } else if (src.detachEvent) {
704 src.detachEvent(sig, listener); // useCapture unsupported
705 } else {
706 throw new Error("'src' must be a DOM element");
707 }
708 },
709
710 /** @id MochiKit.Signal.disconnect */
711 disconnect: function (ident) {
712 var self = MochiKit.Signal;
713 var observers = self._observers;
714 var m = MochiKit.Base;
715 if (arguments.length > 1) {
716 // compatibility API
717 var src = MochiKit.DOM.getElement(arguments[0]);
718 var sig = arguments[1];
719 var obj = arguments[2];
720 var func = arguments[3];
721 for (var i = observers.length - 1; i >= 0; i--) {
722 var o = observers[i];
723 if (o.source === src && o.signal === sig && o.objOrFunc === obj && o.funcOrStr === func) {
724 self._disconnect(o);
725 if (!self._lock) {
726 observers.splice(i, 1);
727 } else {
728 self._dirty = true;
729 }
730 return true;
731 }
732 }
733 } else {
734 var idx = m.findIdentical(observers, ident);
735 if (idx >= 0) {
736 self._disconnect(ident);
737 if (!self._lock) {
738 observers.splice(idx, 1);
739 } else {
740 self._dirty = true;
741 }
742 return true;
743 }
744 }
745 return false;
746 },
747
748 /** @id MochiKit.Signal.disconnectAllTo */
749 disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) {
750 var self = MochiKit.Signal;
751 var observers = self._observers;
752 var disconnect = self._disconnect;
753 var locked = self._lock;
754 var dirty = self._dirty;
755 if (typeof(funcOrStr) === 'undefined') {
756 funcOrStr = null;
757 }
758 for (var i = observers.length - 1; i >= 0; i--) {
759 var ident = observers[i];
760 if (ident.objOrFunc === objOrFunc &&
761 (funcOrStr === null || ident.funcOrStr === funcOrStr)) {
762 disconnect(ident);
763 if (locked) {
764 dirty = true;
765 } else {
766 observers.splice(i, 1);
767 }
768 }
769 }
770 self._dirty = dirty;
771 },
772
773 /** @id MochiKit.Signal.disconnectAll */
774 disconnectAll: function (src/* optional */, sig) {
775 src = MochiKit.DOM.getElement(src);
776 var m = MochiKit.Base;
777 var signals = m.flattenArguments(m.extend(null, arguments, 1));
778 var self = MochiKit.Signal;
779 var disconnect = self._disconnect;
780 var observers = self._observers;
781 var i, ident;
782 var locked = self._lock;
783 var dirty = self._dirty;
784 if (signals.length === 0) {
785 // disconnect all
786 for (i = observers.length - 1; i >= 0; i--) {
787 ident = observers[i];
788 if (ident.source === src) {
789 disconnect(ident);
790 if (!locked) {
791 observers.splice(i, 1);
792 } else {
793 dirty = true;
794 }
795 }
796 }
797 } else {
798 var sigs = {};
799 for (i = 0; i < signals.length; i++) {
800 sigs[signals[i]] = true;
801 }
802 for (i = observers.length - 1; i >= 0; i--) {
803 ident = observers[i];
804 if (ident.source === src && ident.signal in sigs) {
805 disconnect(ident);
806 if (!locked) {
807 observers.splice(i, 1);
808 } else {
809 dirty = true;
810 }
811 }
812 }
813 }
814 self._dirty = dirty;
815 },
816
817 /** @id MochiKit.Signal.signal */
818 signal: function (src, sig) {
819 var self = MochiKit.Signal;
820 var observers = self._observers;
821 src = MochiKit.DOM.getElement(src);
822 var args = MochiKit.Base.extend(null, arguments, 2);
823 var errors = [];
824 self._lock = true;
825 for (var i = 0; i < observers.length; i++) {
826 var ident = observers[i];
827 if (ident.source === src && ident.signal === sig) {
828 try {
829 ident.listener.apply(src, args);
830 } catch (e) {
831 errors.push(e);
832 }
833 }
834 }
835 self._lock = false;
836 if (self._dirty) {
837 self._dirty = false;
838 for (var i = observers.length - 1; i >= 0; i--) {
839 if (!observers[i].connected) {
840 observers.splice(i, 1);
841 }
842 }
843 }
844 if (errors.length == 1) {
845 throw errors[0];
846 } else if (errors.length > 1) {
847 var e = new Error("Multiple errors thrown in handling 'sig', see errors property");
848 e.errors = errors;
849 throw e;
850 }
851 }
852
853 });
854
855 MochiKit.Signal.EXPORT_OK = [];
856
857 MochiKit.Signal.EXPORT = [
858 'connect',
859 'disconnect',
860 'signal',
861 'disconnectAll',
862 'disconnectAllTo'
863 ];
864
865 MochiKit.Signal.__new__ = function (win) {
866 var m = MochiKit.Base;
867 this._document = document;
868 this._window = win;
869 this._lock = false;
870 this._dirty = false;
871
872 try {
873 this.connect(window, 'onunload', this._unloadCache);
874 } catch (e) {
875 // pass: might not be a browser
876 }
877
878 this.EXPORT_TAGS = {
879 ':common': this.EXPORT,
880 ':all': m.concat(this.EXPORT, this.EXPORT_OK)
881 };
882
883 m.nameFunctions(this);
884 };
885
886 MochiKit.Signal.__new__(this);
887
888 //
889 // XXX: Internet Explorer blows
890 //
891 if (MochiKit.__export__) {
892 connect = MochiKit.Signal.connect;
893 disconnect = MochiKit.Signal.disconnect;
894 disconnectAll = MochiKit.Signal.disconnectAll;
895 signal = MochiKit.Signal.signal;
896 }
897
898 MochiKit.Base._exportSymbols(this, MochiKit.Signal);