michael@0: /*** michael@0: michael@0: MochiKit.Signal 1.4.2 michael@0: michael@0: See for documentation, downloads, license, etc. michael@0: michael@0: (c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito. All rights Reserved. michael@0: michael@0: ***/ michael@0: michael@0: MochiKit.Base._deps('Signal', ['Base', 'DOM', 'Style']); michael@0: michael@0: MochiKit.Signal.NAME = 'MochiKit.Signal'; michael@0: MochiKit.Signal.VERSION = '1.4.2'; michael@0: michael@0: MochiKit.Signal._observers = []; michael@0: michael@0: /** @id MochiKit.Signal.Event */ michael@0: MochiKit.Signal.Event = function (src, e) { michael@0: this._event = e || window.event; michael@0: this._src = src; michael@0: }; michael@0: michael@0: MochiKit.Base.update(MochiKit.Signal.Event.prototype, { michael@0: michael@0: __repr__: function () { michael@0: var repr = MochiKit.Base.repr; michael@0: var str = '{event(): ' + repr(this.event()) + michael@0: ', src(): ' + repr(this.src()) + michael@0: ', type(): ' + repr(this.type()) + michael@0: ', target(): ' + repr(this.target()); michael@0: michael@0: if (this.type() && michael@0: this.type().indexOf('key') === 0 || michael@0: this.type().indexOf('mouse') === 0 || michael@0: this.type().indexOf('click') != -1 || michael@0: this.type() == 'contextmenu') { michael@0: str += ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) + michael@0: ', ctrl: ' + repr(this.modifier().ctrl) + michael@0: ', meta: ' + repr(this.modifier().meta) + michael@0: ', shift: ' + repr(this.modifier().shift) + michael@0: ', any: ' + repr(this.modifier().any) + '}'; michael@0: } michael@0: michael@0: if (this.type() && this.type().indexOf('key') === 0) { michael@0: str += ', key(): {code: ' + repr(this.key().code) + michael@0: ', string: ' + repr(this.key().string) + '}'; michael@0: } michael@0: michael@0: if (this.type() && ( michael@0: this.type().indexOf('mouse') === 0 || michael@0: this.type().indexOf('click') != -1 || michael@0: this.type() == 'contextmenu')) { michael@0: michael@0: str += ', mouse(): {page: ' + repr(this.mouse().page) + michael@0: ', client: ' + repr(this.mouse().client); michael@0: michael@0: if (this.type() != 'mousemove' && this.type() != 'mousewheel') { michael@0: str += ', button: {left: ' + repr(this.mouse().button.left) + michael@0: ', middle: ' + repr(this.mouse().button.middle) + michael@0: ', right: ' + repr(this.mouse().button.right) + '}'; michael@0: } michael@0: if (this.type() == 'mousewheel') { michael@0: str += ', wheel: ' + repr(this.mouse().wheel); michael@0: } michael@0: str += '}'; michael@0: } michael@0: if (this.type() == 'mouseover' || this.type() == 'mouseout' || michael@0: this.type() == 'mouseenter' || this.type() == 'mouseleave') { michael@0: str += ', relatedTarget(): ' + repr(this.relatedTarget()); michael@0: } michael@0: str += '}'; michael@0: return str; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.toString */ michael@0: toString: function () { michael@0: return this.__repr__(); michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.src */ michael@0: src: function () { michael@0: return this._src; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.event */ michael@0: event: function () { michael@0: return this._event; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.type */ michael@0: type: function () { michael@0: if (this._event.type === "DOMMouseScroll") { michael@0: return "mousewheel"; michael@0: } else { michael@0: return this._event.type || undefined; michael@0: } michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.target */ michael@0: target: function () { michael@0: return this._event.target || this._event.srcElement; michael@0: }, michael@0: michael@0: _relatedTarget: null, michael@0: /** @id MochiKit.Signal.Event.prototype.relatedTarget */ michael@0: relatedTarget: function () { michael@0: if (this._relatedTarget !== null) { michael@0: return this._relatedTarget; michael@0: } michael@0: michael@0: var elem = null; michael@0: if (this.type() == 'mouseover' || this.type() == 'mouseenter') { michael@0: elem = (this._event.relatedTarget || michael@0: this._event.fromElement); michael@0: } else if (this.type() == 'mouseout' || this.type() == 'mouseleave') { michael@0: elem = (this._event.relatedTarget || michael@0: this._event.toElement); michael@0: } michael@0: try { michael@0: if (elem !== null && elem.nodeType !== null) { michael@0: this._relatedTarget = elem; michael@0: return elem; michael@0: } michael@0: } catch (ignore) { michael@0: // Firefox 3 throws a permission denied error when accessing michael@0: // any property on XUL elements (e.g. scrollbars)... michael@0: } michael@0: michael@0: return undefined; michael@0: }, michael@0: michael@0: _modifier: null, michael@0: /** @id MochiKit.Signal.Event.prototype.modifier */ michael@0: modifier: function () { michael@0: if (this._modifier !== null) { michael@0: return this._modifier; michael@0: } michael@0: var m = {}; michael@0: m.alt = this._event.altKey; michael@0: m.ctrl = this._event.ctrlKey; michael@0: m.meta = this._event.metaKey || false; // IE and Opera punt here michael@0: m.shift = this._event.shiftKey; michael@0: m.any = m.alt || m.ctrl || m.shift || m.meta; michael@0: this._modifier = m; michael@0: return m; michael@0: }, michael@0: michael@0: _key: null, michael@0: /** @id MochiKit.Signal.Event.prototype.key */ michael@0: key: function () { michael@0: if (this._key !== null) { michael@0: return this._key; michael@0: } michael@0: var k = {}; michael@0: if (this.type() && this.type().indexOf('key') === 0) { michael@0: michael@0: /* michael@0: michael@0: If you're looking for a special key, look for it in keydown or michael@0: keyup, but never keypress. If you're looking for a Unicode michael@0: chracter, look for it with keypress, but never keyup or michael@0: keydown. michael@0: michael@0: Notes: michael@0: michael@0: FF key event behavior: michael@0: key event charCode keyCode michael@0: DOWN ku,kd 0 40 michael@0: DOWN kp 0 40 michael@0: ESC ku,kd 0 27 michael@0: ESC kp 0 27 michael@0: a ku,kd 0 65 michael@0: a kp 97 0 michael@0: shift+a ku,kd 0 65 michael@0: shift+a kp 65 0 michael@0: 1 ku,kd 0 49 michael@0: 1 kp 49 0 michael@0: shift+1 ku,kd 0 0 michael@0: shift+1 kp 33 0 michael@0: michael@0: IE key event behavior: michael@0: (IE doesn't fire keypress events for special keys.) michael@0: key event keyCode michael@0: DOWN ku,kd 40 michael@0: DOWN kp undefined michael@0: ESC ku,kd 27 michael@0: ESC kp 27 michael@0: a ku,kd 65 michael@0: a kp 97 michael@0: shift+a ku,kd 65 michael@0: shift+a kp 65 michael@0: 1 ku,kd 49 michael@0: 1 kp 49 michael@0: shift+1 ku,kd 49 michael@0: shift+1 kp 33 michael@0: michael@0: Safari key event behavior: michael@0: (Safari sets charCode and keyCode to something crazy for michael@0: special keys.) michael@0: key event charCode keyCode michael@0: DOWN ku,kd 63233 40 michael@0: DOWN kp 63233 63233 michael@0: ESC ku,kd 27 27 michael@0: ESC kp 27 27 michael@0: a ku,kd 97 65 michael@0: a kp 97 97 michael@0: shift+a ku,kd 65 65 michael@0: shift+a kp 65 65 michael@0: 1 ku,kd 49 49 michael@0: 1 kp 49 49 michael@0: shift+1 ku,kd 33 49 michael@0: shift+1 kp 33 33 michael@0: michael@0: */ michael@0: michael@0: /* look for special keys here */ michael@0: if (this.type() == 'keydown' || this.type() == 'keyup') { michael@0: k.code = this._event.keyCode; michael@0: k.string = (MochiKit.Signal._specialKeys[k.code] || michael@0: 'KEY_UNKNOWN'); michael@0: this._key = k; michael@0: return k; michael@0: michael@0: /* look for characters here */ michael@0: } else if (this.type() == 'keypress') { michael@0: michael@0: /* michael@0: michael@0: Special key behavior: michael@0: michael@0: IE: does not fire keypress events for special keys michael@0: FF: sets charCode to 0, and sets the correct keyCode michael@0: Safari: sets keyCode and charCode to something stupid michael@0: michael@0: */ michael@0: michael@0: k.code = 0; michael@0: k.string = ''; michael@0: michael@0: if (typeof(this._event.charCode) != 'undefined' && michael@0: this._event.charCode !== 0 && michael@0: !MochiKit.Signal._specialMacKeys[this._event.charCode]) { michael@0: k.code = this._event.charCode; michael@0: k.string = String.fromCharCode(k.code); michael@0: } else if (this._event.keyCode && michael@0: typeof(this._event.charCode) == 'undefined') { // IE michael@0: k.code = this._event.keyCode; michael@0: k.string = String.fromCharCode(k.code); michael@0: } michael@0: michael@0: this._key = k; michael@0: return k; michael@0: } michael@0: } michael@0: return undefined; michael@0: }, michael@0: michael@0: _mouse: null, michael@0: /** @id MochiKit.Signal.Event.prototype.mouse */ michael@0: mouse: function () { michael@0: if (this._mouse !== null) { michael@0: return this._mouse; michael@0: } michael@0: michael@0: var m = {}; michael@0: var e = this._event; michael@0: michael@0: if (this.type() && ( michael@0: this.type().indexOf('mouse') === 0 || michael@0: this.type().indexOf('click') != -1 || michael@0: this.type() == 'contextmenu')) { michael@0: michael@0: m.client = new MochiKit.Style.Coordinates(0, 0); michael@0: if (e.clientX || e.clientY) { michael@0: m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX; michael@0: m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY; michael@0: } michael@0: michael@0: m.page = new MochiKit.Style.Coordinates(0, 0); michael@0: if (e.pageX || e.pageY) { michael@0: m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX; michael@0: m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY; michael@0: } else { michael@0: /* michael@0: michael@0: The IE shortcut can be off by two. We fix it. See: michael@0: http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp michael@0: michael@0: This is similar to the method used in michael@0: MochiKit.Style.getElementPosition(). michael@0: michael@0: */ michael@0: var de = MochiKit.DOM._document.documentElement; michael@0: var b = MochiKit.DOM._document.body; michael@0: michael@0: m.page.x = e.clientX + michael@0: (de.scrollLeft || b.scrollLeft) - michael@0: (de.clientLeft || 0); michael@0: michael@0: m.page.y = e.clientY + michael@0: (de.scrollTop || b.scrollTop) - michael@0: (de.clientTop || 0); michael@0: michael@0: } michael@0: if (this.type() != 'mousemove' && this.type() != 'mousewheel') { michael@0: m.button = {}; michael@0: m.button.left = false; michael@0: m.button.right = false; michael@0: m.button.middle = false; michael@0: michael@0: /* we could check e.button, but which is more consistent */ michael@0: if (e.which) { michael@0: m.button.left = (e.which == 1); michael@0: m.button.middle = (e.which == 2); michael@0: m.button.right = (e.which == 3); michael@0: michael@0: /* michael@0: michael@0: Mac browsers and right click: michael@0: michael@0: - Safari doesn't fire any click events on a right michael@0: click: michael@0: http://bugs.webkit.org/show_bug.cgi?id=6595 michael@0: michael@0: - Firefox fires the event, and sets ctrlKey = true michael@0: michael@0: - Opera fires the event, and sets metaKey = true michael@0: michael@0: oncontextmenu is fired on right clicks between michael@0: browsers and across platforms. michael@0: michael@0: */ michael@0: michael@0: } else { michael@0: m.button.left = !!(e.button & 1); michael@0: m.button.right = !!(e.button & 2); michael@0: m.button.middle = !!(e.button & 4); michael@0: } michael@0: } michael@0: if (this.type() == 'mousewheel') { michael@0: m.wheel = new MochiKit.Style.Coordinates(0, 0); michael@0: if (e.wheelDeltaX || e.wheelDeltaY) { michael@0: m.wheel.x = e.wheelDeltaX / -40 || 0; michael@0: m.wheel.y = e.wheelDeltaY / -40 || 0; michael@0: } else if (e.wheelDelta) { michael@0: m.wheel.y = e.wheelDelta / -40; michael@0: } else { michael@0: m.wheel.y = e.detail || 0; michael@0: } michael@0: } michael@0: this._mouse = m; michael@0: return m; michael@0: } michael@0: return undefined; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.stop */ michael@0: stop: function () { michael@0: this.stopPropagation(); michael@0: this.preventDefault(); michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.stopPropagation */ michael@0: stopPropagation: function () { michael@0: if (this._event.stopPropagation) { michael@0: this._event.stopPropagation(); michael@0: } else { michael@0: this._event.cancelBubble = true; michael@0: } michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.preventDefault */ michael@0: preventDefault: function () { michael@0: if (this._event.preventDefault) { michael@0: this._event.preventDefault(); michael@0: } else if (this._confirmUnload === null) { michael@0: this._event.returnValue = false; michael@0: } michael@0: }, michael@0: michael@0: _confirmUnload: null, michael@0: michael@0: /** @id MochiKit.Signal.Event.prototype.confirmUnload */ michael@0: confirmUnload: function (msg) { michael@0: if (this.type() == 'beforeunload') { michael@0: this._confirmUnload = msg; michael@0: this._event.returnValue = msg; michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /* Safari sets keyCode to these special values onkeypress. */ michael@0: MochiKit.Signal._specialMacKeys = { michael@0: 3: 'KEY_ENTER', michael@0: 63289: 'KEY_NUM_PAD_CLEAR', michael@0: 63276: 'KEY_PAGE_UP', michael@0: 63277: 'KEY_PAGE_DOWN', michael@0: 63275: 'KEY_END', michael@0: 63273: 'KEY_HOME', michael@0: 63234: 'KEY_ARROW_LEFT', michael@0: 63232: 'KEY_ARROW_UP', michael@0: 63235: 'KEY_ARROW_RIGHT', michael@0: 63233: 'KEY_ARROW_DOWN', michael@0: 63302: 'KEY_INSERT', michael@0: 63272: 'KEY_DELETE' michael@0: }; michael@0: michael@0: /* for KEY_F1 - KEY_F12 */ michael@0: (function () { michael@0: var _specialMacKeys = MochiKit.Signal._specialMacKeys; michael@0: for (i = 63236; i <= 63242; i++) { michael@0: // no F0 michael@0: _specialMacKeys[i] = 'KEY_F' + (i - 63236 + 1); michael@0: } michael@0: })(); michael@0: michael@0: /* Standard keyboard key codes. */ michael@0: MochiKit.Signal._specialKeys = { michael@0: 8: 'KEY_BACKSPACE', michael@0: 9: 'KEY_TAB', michael@0: 12: 'KEY_NUM_PAD_CLEAR', // weird, for Safari and Mac FF only michael@0: 13: 'KEY_ENTER', michael@0: 16: 'KEY_SHIFT', michael@0: 17: 'KEY_CTRL', michael@0: 18: 'KEY_ALT', michael@0: 19: 'KEY_PAUSE', michael@0: 20: 'KEY_CAPS_LOCK', michael@0: 27: 'KEY_ESCAPE', michael@0: 32: 'KEY_SPACEBAR', michael@0: 33: 'KEY_PAGE_UP', michael@0: 34: 'KEY_PAGE_DOWN', michael@0: 35: 'KEY_END', michael@0: 36: 'KEY_HOME', michael@0: 37: 'KEY_ARROW_LEFT', michael@0: 38: 'KEY_ARROW_UP', michael@0: 39: 'KEY_ARROW_RIGHT', michael@0: 40: 'KEY_ARROW_DOWN', michael@0: 44: 'KEY_PRINT_SCREEN', michael@0: 45: 'KEY_INSERT', michael@0: 46: 'KEY_DELETE', michael@0: 59: 'KEY_SEMICOLON', // weird, for Safari and IE only michael@0: 91: 'KEY_WINDOWS_LEFT', michael@0: 92: 'KEY_WINDOWS_RIGHT', michael@0: 93: 'KEY_SELECT', michael@0: 106: 'KEY_NUM_PAD_ASTERISK', michael@0: 107: 'KEY_NUM_PAD_PLUS_SIGN', michael@0: 109: 'KEY_NUM_PAD_HYPHEN-MINUS', michael@0: 110: 'KEY_NUM_PAD_FULL_STOP', michael@0: 111: 'KEY_NUM_PAD_SOLIDUS', michael@0: 144: 'KEY_NUM_LOCK', michael@0: 145: 'KEY_SCROLL_LOCK', michael@0: 186: 'KEY_SEMICOLON', michael@0: 187: 'KEY_EQUALS_SIGN', michael@0: 188: 'KEY_COMMA', michael@0: 189: 'KEY_HYPHEN-MINUS', michael@0: 190: 'KEY_FULL_STOP', michael@0: 191: 'KEY_SOLIDUS', michael@0: 192: 'KEY_GRAVE_ACCENT', michael@0: 219: 'KEY_LEFT_SQUARE_BRACKET', michael@0: 220: 'KEY_REVERSE_SOLIDUS', michael@0: 221: 'KEY_RIGHT_SQUARE_BRACKET', michael@0: 222: 'KEY_APOSTROPHE' michael@0: // undefined: 'KEY_UNKNOWN' michael@0: }; michael@0: michael@0: (function () { michael@0: /* for KEY_0 - KEY_9 */ michael@0: var _specialKeys = MochiKit.Signal._specialKeys; michael@0: for (var i = 48; i <= 57; i++) { michael@0: _specialKeys[i] = 'KEY_' + (i - 48); michael@0: } michael@0: michael@0: /* for KEY_A - KEY_Z */ michael@0: for (i = 65; i <= 90; i++) { michael@0: _specialKeys[i] = 'KEY_' + String.fromCharCode(i); michael@0: } michael@0: michael@0: /* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */ michael@0: for (i = 96; i <= 105; i++) { michael@0: _specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96); michael@0: } michael@0: michael@0: /* for KEY_F1 - KEY_F12 */ michael@0: for (i = 112; i <= 123; i++) { michael@0: // no F0 michael@0: _specialKeys[i] = 'KEY_F' + (i - 112 + 1); michael@0: } michael@0: })(); michael@0: michael@0: /* Internal object to keep track of created signals. */ michael@0: MochiKit.Signal.Ident = function (ident) { michael@0: this.source = ident.source; michael@0: this.signal = ident.signal; michael@0: this.listener = ident.listener; michael@0: this.isDOM = ident.isDOM; michael@0: this.objOrFunc = ident.objOrFunc; michael@0: this.funcOrStr = ident.funcOrStr; michael@0: this.connected = ident.connected; michael@0: }; michael@0: michael@0: MochiKit.Signal.Ident.prototype = {}; michael@0: michael@0: MochiKit.Base.update(MochiKit.Signal, { michael@0: michael@0: __repr__: function () { michael@0: return '[' + this.NAME + ' ' + this.VERSION + ']'; michael@0: }, michael@0: michael@0: toString: function () { michael@0: return this.__repr__(); michael@0: }, michael@0: michael@0: _unloadCache: function () { michael@0: var self = MochiKit.Signal; michael@0: var observers = self._observers; michael@0: michael@0: for (var i = 0; i < observers.length; i++) { michael@0: if (observers[i].signal !== 'onload' && observers[i].signal !== 'onunload') { michael@0: self._disconnect(observers[i]); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _listener: function (src, sig, func, obj, isDOM) { michael@0: var self = MochiKit.Signal; michael@0: var E = self.Event; michael@0: if (!isDOM) { michael@0: /* We don't want to re-bind already bound methods */ michael@0: if (typeof(func.im_self) == 'undefined') { michael@0: return MochiKit.Base.bindLate(func, obj); michael@0: } else { michael@0: return func; michael@0: } michael@0: } michael@0: obj = obj || src; michael@0: if (typeof(func) == "string") { michael@0: if (sig === 'onload' || sig === 'onunload') { michael@0: return function (nativeEvent) { michael@0: obj[func].apply(obj, [new E(src, nativeEvent)]); michael@0: michael@0: var ident = new MochiKit.Signal.Ident({ michael@0: source: src, signal: sig, objOrFunc: obj, funcOrStr: func}); michael@0: michael@0: MochiKit.Signal._disconnect(ident); michael@0: }; michael@0: } else { michael@0: return function (nativeEvent) { michael@0: obj[func].apply(obj, [new E(src, nativeEvent)]); michael@0: }; michael@0: } michael@0: } else { michael@0: if (sig === 'onload' || sig === 'onunload') { michael@0: return function (nativeEvent) { michael@0: func.apply(obj, [new E(src, nativeEvent)]); michael@0: michael@0: var ident = new MochiKit.Signal.Ident({ michael@0: source: src, signal: sig, objOrFunc: func}); michael@0: michael@0: MochiKit.Signal._disconnect(ident); michael@0: }; michael@0: } else { michael@0: return function (nativeEvent) { michael@0: func.apply(obj, [new E(src, nativeEvent)]); michael@0: }; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _browserAlreadyHasMouseEnterAndLeave: function () { michael@0: return /MSIE/.test(navigator.userAgent); michael@0: }, michael@0: michael@0: _browserLacksMouseWheelEvent: function () { michael@0: return /Gecko\//.test(navigator.userAgent); michael@0: }, michael@0: michael@0: _mouseEnterListener: function (src, sig, func, obj) { michael@0: var E = MochiKit.Signal.Event; michael@0: return function (nativeEvent) { michael@0: var e = new E(src, nativeEvent); michael@0: try { michael@0: e.relatedTarget().nodeName; michael@0: } catch (err) { michael@0: /* probably hit a permission denied error; possibly one of michael@0: * firefox's screwy anonymous DIVs inside an input element. michael@0: * Allow this event to propogate up. michael@0: */ michael@0: return; michael@0: } michael@0: e.stop(); michael@0: if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) { michael@0: /* We've moved between our node and a child. Ignore. */ michael@0: return; michael@0: } michael@0: e.type = function () { return sig; }; michael@0: if (typeof(func) == "string") { michael@0: return obj[func].apply(obj, [e]); michael@0: } else { michael@0: return func.apply(obj, [e]); michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: _getDestPair: function (objOrFunc, funcOrStr) { michael@0: var obj = null; michael@0: var func = null; michael@0: if (typeof(funcOrStr) != 'undefined') { michael@0: obj = objOrFunc; michael@0: func = funcOrStr; michael@0: if (typeof(funcOrStr) == 'string') { michael@0: if (typeof(objOrFunc[funcOrStr]) != "function") { michael@0: throw new Error("'funcOrStr' must be a function on 'objOrFunc'"); michael@0: } michael@0: } else if (typeof(funcOrStr) != 'function') { michael@0: throw new Error("'funcOrStr' must be a function or string"); michael@0: } michael@0: } else if (typeof(objOrFunc) != "function") { michael@0: throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given"); michael@0: } else { michael@0: func = objOrFunc; michael@0: } michael@0: return [obj, func]; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.connect */ michael@0: connect: function (src, sig, objOrFunc/* optional */, funcOrStr) { michael@0: src = MochiKit.DOM.getElement(src); michael@0: var self = MochiKit.Signal; michael@0: michael@0: if (typeof(sig) != 'string') { michael@0: throw new Error("'sig' must be a string"); michael@0: } michael@0: michael@0: var destPair = self._getDestPair(objOrFunc, funcOrStr); michael@0: var obj = destPair[0]; michael@0: var func = destPair[1]; michael@0: if (typeof(obj) == 'undefined' || obj === null) { michael@0: obj = src; michael@0: } michael@0: michael@0: var isDOM = !!(src.addEventListener || src.attachEvent); michael@0: if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave") michael@0: && !self._browserAlreadyHasMouseEnterAndLeave()) { michael@0: var listener = self._mouseEnterListener(src, sig.substr(2), func, obj); michael@0: if (sig === "onmouseenter") { michael@0: sig = "onmouseover"; michael@0: } else { michael@0: sig = "onmouseout"; michael@0: } michael@0: } else if (isDOM && sig == "onmousewheel" && self._browserLacksMouseWheelEvent()) { michael@0: var listener = self._listener(src, sig, func, obj, isDOM); michael@0: sig = "onDOMMouseScroll"; michael@0: } else { michael@0: var listener = self._listener(src, sig, func, obj, isDOM); michael@0: } michael@0: michael@0: if (src.addEventListener) { michael@0: src.addEventListener(sig.substr(2), listener, false); michael@0: } else if (src.attachEvent) { michael@0: src.attachEvent(sig, listener); // useCapture unsupported michael@0: } michael@0: michael@0: var ident = new MochiKit.Signal.Ident({ michael@0: source: src, michael@0: signal: sig, michael@0: listener: listener, michael@0: isDOM: isDOM, michael@0: objOrFunc: objOrFunc, michael@0: funcOrStr: funcOrStr, michael@0: connected: true michael@0: }); michael@0: self._observers.push(ident); michael@0: michael@0: if (!isDOM && typeof(src.__connect__) == 'function') { michael@0: var args = MochiKit.Base.extend([ident], arguments, 1); michael@0: src.__connect__.apply(src, args); michael@0: } michael@0: michael@0: return ident; michael@0: }, michael@0: michael@0: _disconnect: function (ident) { michael@0: // already disconnected michael@0: if (!ident.connected) { michael@0: return; michael@0: } michael@0: ident.connected = false; michael@0: var src = ident.source; michael@0: var sig = ident.signal; michael@0: var listener = ident.listener; michael@0: // check isDOM michael@0: if (!ident.isDOM) { michael@0: if (typeof(src.__disconnect__) == 'function') { michael@0: src.__disconnect__(ident, sig, ident.objOrFunc, ident.funcOrStr); michael@0: } michael@0: return; michael@0: } michael@0: if (src.removeEventListener) { michael@0: src.removeEventListener(sig.substr(2), listener, false); michael@0: } else if (src.detachEvent) { michael@0: src.detachEvent(sig, listener); // useCapture unsupported michael@0: } else { michael@0: throw new Error("'src' must be a DOM element"); michael@0: } michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.disconnect */ michael@0: disconnect: function (ident) { michael@0: var self = MochiKit.Signal; michael@0: var observers = self._observers; michael@0: var m = MochiKit.Base; michael@0: if (arguments.length > 1) { michael@0: // compatibility API michael@0: var src = MochiKit.DOM.getElement(arguments[0]); michael@0: var sig = arguments[1]; michael@0: var obj = arguments[2]; michael@0: var func = arguments[3]; michael@0: for (var i = observers.length - 1; i >= 0; i--) { michael@0: var o = observers[i]; michael@0: if (o.source === src && o.signal === sig && o.objOrFunc === obj && o.funcOrStr === func) { michael@0: self._disconnect(o); michael@0: if (!self._lock) { michael@0: observers.splice(i, 1); michael@0: } else { michael@0: self._dirty = true; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: } else { michael@0: var idx = m.findIdentical(observers, ident); michael@0: if (idx >= 0) { michael@0: self._disconnect(ident); michael@0: if (!self._lock) { michael@0: observers.splice(idx, 1); michael@0: } else { michael@0: self._dirty = true; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.disconnectAllTo */ michael@0: disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) { michael@0: var self = MochiKit.Signal; michael@0: var observers = self._observers; michael@0: var disconnect = self._disconnect; michael@0: var locked = self._lock; michael@0: var dirty = self._dirty; michael@0: if (typeof(funcOrStr) === 'undefined') { michael@0: funcOrStr = null; michael@0: } michael@0: for (var i = observers.length - 1; i >= 0; i--) { michael@0: var ident = observers[i]; michael@0: if (ident.objOrFunc === objOrFunc && michael@0: (funcOrStr === null || ident.funcOrStr === funcOrStr)) { michael@0: disconnect(ident); michael@0: if (locked) { michael@0: dirty = true; michael@0: } else { michael@0: observers.splice(i, 1); michael@0: } michael@0: } michael@0: } michael@0: self._dirty = dirty; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.disconnectAll */ michael@0: disconnectAll: function (src/* optional */, sig) { michael@0: src = MochiKit.DOM.getElement(src); michael@0: var m = MochiKit.Base; michael@0: var signals = m.flattenArguments(m.extend(null, arguments, 1)); michael@0: var self = MochiKit.Signal; michael@0: var disconnect = self._disconnect; michael@0: var observers = self._observers; michael@0: var i, ident; michael@0: var locked = self._lock; michael@0: var dirty = self._dirty; michael@0: if (signals.length === 0) { michael@0: // disconnect all michael@0: for (i = observers.length - 1; i >= 0; i--) { michael@0: ident = observers[i]; michael@0: if (ident.source === src) { michael@0: disconnect(ident); michael@0: if (!locked) { michael@0: observers.splice(i, 1); michael@0: } else { michael@0: dirty = true; michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: var sigs = {}; michael@0: for (i = 0; i < signals.length; i++) { michael@0: sigs[signals[i]] = true; michael@0: } michael@0: for (i = observers.length - 1; i >= 0; i--) { michael@0: ident = observers[i]; michael@0: if (ident.source === src && ident.signal in sigs) { michael@0: disconnect(ident); michael@0: if (!locked) { michael@0: observers.splice(i, 1); michael@0: } else { michael@0: dirty = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: self._dirty = dirty; michael@0: }, michael@0: michael@0: /** @id MochiKit.Signal.signal */ michael@0: signal: function (src, sig) { michael@0: var self = MochiKit.Signal; michael@0: var observers = self._observers; michael@0: src = MochiKit.DOM.getElement(src); michael@0: var args = MochiKit.Base.extend(null, arguments, 2); michael@0: var errors = []; michael@0: self._lock = true; michael@0: for (var i = 0; i < observers.length; i++) { michael@0: var ident = observers[i]; michael@0: if (ident.source === src && ident.signal === sig && michael@0: ident.connected) { michael@0: try { michael@0: ident.listener.apply(src, args); michael@0: } catch (e) { michael@0: errors.push(e); michael@0: } michael@0: } michael@0: } michael@0: self._lock = false; michael@0: if (self._dirty) { michael@0: self._dirty = false; michael@0: for (var i = observers.length - 1; i >= 0; i--) { michael@0: if (!observers[i].connected) { michael@0: observers.splice(i, 1); michael@0: } michael@0: } michael@0: } michael@0: if (errors.length == 1) { michael@0: throw errors[0]; michael@0: } else if (errors.length > 1) { michael@0: var e = new Error("Multiple errors thrown in handling 'sig', see errors property"); michael@0: e.errors = errors; michael@0: throw e; michael@0: } michael@0: } michael@0: michael@0: }); michael@0: michael@0: MochiKit.Signal.EXPORT_OK = []; michael@0: michael@0: MochiKit.Signal.EXPORT = [ michael@0: 'connect', michael@0: 'disconnect', michael@0: 'signal', michael@0: 'disconnectAll', michael@0: 'disconnectAllTo' michael@0: ]; michael@0: michael@0: MochiKit.Signal.__new__ = function (win) { michael@0: var m = MochiKit.Base; michael@0: this._document = document; michael@0: this._window = win; michael@0: this._lock = false; michael@0: this._dirty = false; michael@0: michael@0: try { michael@0: this.connect(window, 'onunload', this._unloadCache); michael@0: } catch (e) { michael@0: // pass: might not be a browser michael@0: } michael@0: michael@0: this.EXPORT_TAGS = { michael@0: ':common': this.EXPORT, michael@0: ':all': m.concat(this.EXPORT, this.EXPORT_OK) michael@0: }; michael@0: michael@0: m.nameFunctions(this); michael@0: }; michael@0: michael@0: MochiKit.Signal.__new__(this); michael@0: michael@0: // michael@0: // XXX: Internet Explorer blows michael@0: // michael@0: if (MochiKit.__export__) { michael@0: connect = MochiKit.Signal.connect; michael@0: disconnect = MochiKit.Signal.disconnect; michael@0: disconnectAll = MochiKit.Signal.disconnectAll; michael@0: signal = MochiKit.Signal.signal; michael@0: } michael@0: michael@0: MochiKit.Base._exportSymbols(this, MochiKit.Signal);