testing/mochitest/MochiKit/Signal.js

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

mercurial