Signal.js (28231B)
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 weird 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 ident.connected) { 829 try { 830 ident.listener.apply(src, args); 831 } catch (e) { 832 errors.push(e); 833 } 834 } 835 } 836 self._lock = false; 837 if (self._dirty) { 838 self._dirty = false; 839 for (var i = observers.length - 1; i >= 0; i--) { 840 if (!observers[i].connected) { 841 observers.splice(i, 1); 842 } 843 } 844 } 845 if (errors.length == 1) { 846 throw errors[0]; 847 } else if (errors.length > 1) { 848 var e = new Error("Multiple errors thrown in handling 'sig', see errors property"); 849 e.errors = errors; 850 throw e; 851 } 852 } 853 854 }); 855 856 MochiKit.Signal.EXPORT_OK = []; 857 858 MochiKit.Signal.EXPORT = [ 859 'connect', 860 'disconnect', 861 'signal', 862 'disconnectAll', 863 'disconnectAllTo' 864 ]; 865 866 MochiKit.Signal.__new__ = function (win) { 867 var m = MochiKit.Base; 868 this._document = document; 869 this._window = win; 870 this._lock = false; 871 this._dirty = false; 872 873 try { 874 this.connect(window, 'onunload', this._unloadCache); 875 } catch (e) { 876 // pass: might not be a browser 877 } 878 879 this.EXPORT_TAGS = { 880 ':common': this.EXPORT, 881 ':all': m.concat(this.EXPORT, this.EXPORT_OK) 882 }; 883 884 m.nameFunctions(this); 885 }; 886 887 MochiKit.Signal.__new__(this); 888 889 // 890 // XXX: Internet Explorer blows 891 // 892 if (MochiKit.__export__) { 893 connect = MochiKit.Signal.connect; 894 disconnect = MochiKit.Signal.disconnect; 895 disconnectAll = MochiKit.Signal.disconnectAll; 896 signal = MochiKit.Signal.signal; 897 } 898 899 MochiKit.Base._exportSymbols(this, MochiKit.Signal);