11
if (typeof(dojo) != 'undefined') {
12
dojo.provide('MochiKit.Signal');
13
dojo.require('MochiKit.Base');
14
dojo.require('MochiKit.DOM');
16
if (typeof(JSAN) != 'undefined') {
17
JSAN.use('MochiKit.Base', []);
18
JSAN.use('MochiKit.DOM', []);
22
if (typeof(MochiKit.Base) == 'undefined') {
26
throw 'MochiKit.Signal depends on MochiKit.Base!';
30
if (typeof(MochiKit.DOM) == 'undefined') {
34
throw 'MochiKit.Signal depends on MochiKit.DOM!';
37
if (typeof(MochiKit.Signal) == 'undefined') {
11
MochiKit.Base._deps('Signal', ['Base', 'DOM', 'Style']);
41
13
MochiKit.Signal.NAME = 'MochiKit.Signal';
42
MochiKit.Signal.VERSION = '1.3.1';
14
MochiKit.Signal.VERSION = '1.4.2';
44
16
MochiKit.Signal._observers = [];
18
/** @id MochiKit.Signal.Event */
46
19
MochiKit.Signal.Event = function (src, e) {
47
20
this._event = e || window.event;
51
24
MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
53
__repr__: function() {
26
__repr__: function () {
54
27
var repr = MochiKit.Base.repr;
55
28
var str = '{event(): ' + repr(this.event()) +
56
', src(): ' + repr(this.src()) +
29
', src(): ' + repr(this.src()) +
57
30
', type(): ' + repr(this.type()) +
58
', target(): ' + repr(this.target()) +
59
', modifier(): ' + '{alt: ' + repr(this.modifier().alt) +
31
', target(): ' + repr(this.target());
34
this.type().indexOf('key') === 0 ||
35
this.type().indexOf('mouse') === 0 ||
36
this.type().indexOf('click') != -1 ||
37
this.type() == 'contextmenu') {
38
str += ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) +
60
39
', ctrl: ' + repr(this.modifier().ctrl) +
61
40
', meta: ' + repr(this.modifier().meta) +
62
', shift: ' + repr(this.modifier().shift) +
41
', shift: ' + repr(this.modifier().shift) +
63
42
', any: ' + repr(this.modifier().any) + '}';
65
45
if (this.type() && this.type().indexOf('key') === 0) {
66
46
str += ', key(): {code: ' + repr(this.key().code) +
67
47
', string: ' + repr(this.key().string) + '}';
75
55
str += ', mouse(): {page: ' + repr(this.mouse().page) +
76
56
', client: ' + repr(this.mouse().client);
78
if (this.type() != 'mousemove') {
58
if (this.type() != 'mousemove' && this.type() != 'mousewheel') {
79
59
str += ', button: {left: ' + repr(this.mouse().button.left) +
80
60
', middle: ' + repr(this.mouse().button.middle) +
81
', right: ' + repr(this.mouse().button.right) + '}}';
61
', right: ' + repr(this.mouse().button.right) + '}';
63
if (this.type() == 'mousewheel') {
64
str += ', wheel: ' + repr(this.mouse().wheel);
86
if (this.type() == 'mouseover' || this.type() == 'mouseout') {
68
if (this.type() == 'mouseover' || this.type() == 'mouseout' ||
69
this.type() == 'mouseenter' || this.type() == 'mouseleave') {
87
70
str += ', relatedTarget(): ' + repr(this.relatedTarget());
76
/** @id MochiKit.Signal.Event.prototype.toString */
93
77
toString: function () {
94
78
return this.__repr__();
81
/** @id MochiKit.Signal.Event.prototype.src */
86
/** @id MochiKit.Signal.Event.prototype.event */
101
87
event: function () {
102
88
return this._event;
91
/** @id MochiKit.Signal.Event.prototype.type */
105
92
type: function () {
106
return this._event.type || undefined;
93
if (this._event.type === "DOMMouseScroll") {
96
return this._event.type || undefined;
100
/** @id MochiKit.Signal.Event.prototype.target */
109
101
target: function () {
110
102
return this._event.target || this._event.srcElement;
105
_relatedTarget: null,
106
/** @id MochiKit.Signal.Event.prototype.relatedTarget */
113
107
relatedTarget: function () {
114
if (this.type() == 'mouseover') {
115
return (this._event.relatedTarget ||
108
if (this._relatedTarget !== null) {
109
return this._relatedTarget;
113
if (this.type() == 'mouseover' || this.type() == 'mouseenter') {
114
elem = (this._event.relatedTarget ||
116
115
this._event.fromElement);
117
} else if (this.type() == 'mouseout') {
118
return (this._event.relatedTarget ||
116
} else if (this.type() == 'mouseout' || this.type() == 'mouseleave') {
117
elem = (this._event.relatedTarget ||
119
118
this._event.toElement);
121
// throw new Error("relatedTarget only available for 'mouseover' and 'mouseout'");
121
if (elem !== null && elem.nodeType !== null) {
122
this._relatedTarget = elem;
126
// Firefox 3 throws a permission denied error when accessing
127
// any property on XUL elements (e.g. scrollbars)...
122
130
return undefined;
134
/** @id MochiKit.Signal.Event.prototype.modifier */
125
135
modifier: function () {
136
if (this._modifier !== null) {
137
return this._modifier;
127
140
m.alt = this._event.altKey;
128
141
m.ctrl = this._event.ctrlKey;
129
142
m.meta = this._event.metaKey || false; // IE and Opera punt here
130
143
m.shift = this._event.shiftKey;
131
144
m.any = m.alt || m.ctrl || m.shift || m.meta;
150
/** @id MochiKit.Signal.Event.prototype.key */
135
151
key: function () {
152
if (this._key !== null) {
137
156
if (this.type() && this.type().indexOf('key') === 0) {
141
If you're looking for a special key, look for it in keydown or
160
If you're looking for a special key, look for it in keydown or
142
161
keyup, but never keypress. If you're looking for a Unicode
143
162
chracter, look for it with keypress, but never keyup or
148
FF key event behavior:
149
key event charCode keyCode
163
IE key event behavior:
164
(IE doesn't fire keypress events for special keys.)
179
Safari key event behavior:
180
(Safari sets charCode and keyCode to something crazy for
182
key event charCode keyCode
167
FF key event behavior:
168
key event charCode keyCode
182
IE key event behavior:
183
(IE doesn't fire keypress events for special keys.)
198
Safari key event behavior:
199
(Safari sets charCode and keyCode to something crazy for
201
key event charCode keyCode
200
219
k.code = this._event.keyCode;
201
220
k.string = (MochiKit.Signal._specialKeys[k.code] ||
205
225
/* look for characters here */
206
226
} else if (this.type() == 'keypress') {
210
230
Special key behavior:
212
232
IE: does not fire keypress events for special keys
213
233
FF: sets charCode to 0, and sets the correct keyCode
214
234
Safari: sets keyCode and charCode to something stupid
221
if (typeof(this._event.charCode) != 'undefined' &&
241
if (typeof(this._event.charCode) != 'undefined' &&
222
242
this._event.charCode !== 0 &&
223
243
!MochiKit.Signal._specialMacKeys[this._event.charCode]) {
224
244
k.code = this._event.charCode;
225
245
k.string = String.fromCharCode(k.code);
226
} else if (this._event.keyCode &&
246
} else if (this._event.keyCode &&
227
247
typeof(this._event.charCode) == 'undefined') { // IE
228
248
k.code = this._event.keyCode;
229
249
k.string = String.fromCharCode(k.code);
235
// throw new Error('This is not a key event');
236
256
return undefined;
260
/** @id MochiKit.Signal.Event.prototype.mouse */
239
261
mouse: function () {
262
if (this._mouse !== null) {
241
267
var e = this._event;
243
269
if (this.type() && (
244
270
this.type().indexOf('mouse') === 0 ||
245
271
this.type().indexOf('click') != -1 ||
246
272
this.type() == 'contextmenu')) {
248
m.client = new MochiKit.DOM.Coordinates(0, 0);
274
m.client = new MochiKit.Style.Coordinates(0, 0);
249
275
if (e.clientX || e.clientY) {
250
276
m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX;
251
277
m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY;
254
m.page = new MochiKit.DOM.Coordinates(0, 0);
280
m.page = new MochiKit.Style.Coordinates(0, 0);
255
281
if (e.pageX || e.pageY) {
256
282
m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
257
283
m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
261
IE keeps the document offset in:
262
document.documentElement.clientTop ||
263
document.body.clientTop
266
document.documentElement.clientLeft ||
267
document.body.clientLeft
270
http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
272
The offset is (2,2) in standards mode and (0,0) in quirks
287
The IE shortcut can be off by two. We fix it. See:
288
http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
290
This is similar to the method used in
291
MochiKit.Style.getElementPosition().
277
294
var de = MochiKit.DOM._document.documentElement;
278
295
var b = MochiKit.DOM._document.body;
280
297
m.page.x = e.clientX +
281
(de.scrollLeft || b.scrollLeft) -
282
(de.clientLeft || b.clientLeft);
298
(de.scrollLeft || b.scrollLeft) -
299
(de.clientLeft || 0);
284
301
m.page.y = e.clientY +
285
(de.scrollTop || b.scrollTop) -
286
(de.clientTop || b.clientTop);
302
(de.scrollTop || b.scrollTop) -
289
if (this.type() != 'mousemove') {
306
if (this.type() != 'mousemove' && this.type() != 'mousewheel') {
291
308
m.button.left = false;
292
309
m.button.right = false;
299
316
m.button.right = (e.which == 3);
303
Mac browsers and right click:
305
- Safari doesn't fire any click events on a right
307
http://bugzilla.opendarwin.org/show_bug.cgi?id=6595
309
- Firefox fires the event, and sets ctrlKey = true
311
- Opera fires the event, and sets metaKey = true
313
oncontextmenu is fired on right clicks between
314
browsers and across platforms.
320
Mac browsers and right click:
322
- Safari doesn't fire any click events on a right
324
http://bugs.webkit.org/show_bug.cgi?id=6595
326
- Firefox fires the event, and sets ctrlKey = true
328
- Opera fires the event, and sets metaKey = true
330
oncontextmenu is fired on right clicks between
331
browsers and across platforms.
319
336
m.button.left = !!(e.button & 1);
320
337
m.button.right = !!(e.button & 2);
321
338
m.button.middle = !!(e.button & 4);
341
if (this.type() == 'mousewheel') {
342
m.wheel = new MochiKit.Style.Coordinates(0, 0);
343
if (e.wheelDeltaX || e.wheelDeltaY) {
344
m.wheel.x = e.wheelDeltaX / -40 || 0;
345
m.wheel.y = e.wheelDeltaY / -40 || 0;
346
} else if (e.wheelDelta) {
347
m.wheel.y = e.wheelDelta / -40;
349
m.wheel.y = e.detail || 0;
326
// throw new Error('This is not a mouse event');
327
355
return undefined;
358
/** @id MochiKit.Signal.Event.prototype.stop */
330
359
stop: function () {
331
360
this.stopPropagation();
332
361
this.preventDefault();
364
/** @id MochiKit.Signal.Event.prototype.stopPropagation */
335
365
stopPropagation: function () {
336
366
if (this._event.stopPropagation) {
337
367
this._event.stopPropagation();
420
464
// undefined: 'KEY_UNKNOWN'
423
/* for KEY_0 - KEY_9 */
424
for (var i = 48; i <= 57; i++) {
425
MochiKit.Signal._specialKeys[i] = 'KEY_' + (i - 48);
428
/* for KEY_A - KEY_Z */
429
for (i = 65; i <= 90; i++) {
430
MochiKit.Signal._specialKeys[i] = 'KEY_' + String.fromCharCode(i);
433
/* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */
434
for (i = 96; i <= 105; i++) {
435
MochiKit.Signal._specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96);
438
/* for KEY_F1 - KEY_F12 */
439
for (i = 112; i <= 123; i++) {
440
MochiKit.Signal._specialKeys[i] = 'KEY_F' + (i - 112 + 1); // no F0
468
/* for KEY_0 - KEY_9 */
469
var _specialKeys = MochiKit.Signal._specialKeys;
470
for (var i = 48; i <= 57; i++) {
471
_specialKeys[i] = 'KEY_' + (i - 48);
474
/* for KEY_A - KEY_Z */
475
for (i = 65; i <= 90; i++) {
476
_specialKeys[i] = 'KEY_' + String.fromCharCode(i);
479
/* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */
480
for (i = 96; i <= 105; i++) {
481
_specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96);
484
/* for KEY_F1 - KEY_F12 */
485
for (i = 112; i <= 123; i++) {
487
_specialKeys[i] = 'KEY_F' + (i - 112 + 1);
491
/* Internal object to keep track of created signals. */
492
MochiKit.Signal.Ident = function (ident) {
493
this.source = ident.source;
494
this.signal = ident.signal;
495
this.listener = ident.listener;
496
this.isDOM = ident.isDOM;
497
this.objOrFunc = ident.objOrFunc;
498
this.funcOrStr = ident.funcOrStr;
499
this.connected = ident.connected;
502
MochiKit.Signal.Ident.prototype = {};
443
504
MochiKit.Base.update(MochiKit.Signal, {
453
514
_unloadCache: function () {
454
515
var self = MochiKit.Signal;
455
516
var observers = self._observers;
457
518
for (var i = 0; i < observers.length; i++) {
458
self._disconnect(observers[i]);
461
delete self._observers;
464
window.onload = undefined;
470
window.onunload = undefined;
519
if (observers[i].signal !== 'onload' && observers[i].signal !== 'onunload') {
520
self._disconnect(observers[i]);
476
_listener: function (src, func, obj, isDOM) {
477
var E = MochiKit.Signal.Event;
525
_listener: function (src, sig, func, obj, isDOM) {
526
var self = MochiKit.Signal;
479
return MochiKit.Base.bind(func, obj);
529
/* We don't want to re-bind already bound methods */
530
if (typeof(func.im_self) == 'undefined') {
531
return MochiKit.Base.bindLate(func, obj);
481
536
obj = obj || src;
482
537
if (typeof(func) == "string") {
483
return function (nativeEvent) {
484
obj[func].apply(obj, [new E(src, nativeEvent)]);
538
if (sig === 'onload' || sig === 'onunload') {
539
return function (nativeEvent) {
540
obj[func].apply(obj, [new E(src, nativeEvent)]);
542
var ident = new MochiKit.Signal.Ident({
543
source: src, signal: sig, objOrFunc: obj, funcOrStr: func});
545
MochiKit.Signal._disconnect(ident);
548
return function (nativeEvent) {
549
obj[func].apply(obj, [new E(src, nativeEvent)]);
487
return function (nativeEvent) {
488
func.apply(obj, [new E(src, nativeEvent)]);
493
connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
494
src = MochiKit.DOM.getElement(src);
495
var self = MochiKit.Signal;
497
if (typeof(sig) != 'string') {
498
throw new Error("'sig' must be a string");
553
if (sig === 'onload' || sig === 'onunload') {
554
return function (nativeEvent) {
555
func.apply(obj, [new E(src, nativeEvent)]);
557
var ident = new MochiKit.Signal.Ident({
558
source: src, signal: sig, objOrFunc: func});
560
MochiKit.Signal._disconnect(ident);
563
return function (nativeEvent) {
564
func.apply(obj, [new E(src, nativeEvent)]);
570
_browserAlreadyHasMouseEnterAndLeave: function () {
571
return /MSIE/.test(navigator.userAgent);
574
_browserLacksMouseWheelEvent: function () {
575
return /Gecko\//.test(navigator.userAgent);
578
_mouseEnterListener: function (src, sig, func, obj) {
579
var E = MochiKit.Signal.Event;
580
return function (nativeEvent) {
581
var e = new E(src, nativeEvent);
583
e.relatedTarget().nodeName;
585
/* probably hit a permission denied error; possibly one of
586
* firefox's screwy anonymous DIVs inside an input element.
587
* Allow this event to propogate up.
592
if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) {
593
/* We've moved between our node and a child. Ignore. */
596
e.type = function () { return sig; };
597
if (typeof(func) == "string") {
598
return obj[func].apply(obj, [e]);
600
return func.apply(obj, [e]);
605
_getDestPair: function (objOrFunc, funcOrStr) {
503
608
if (typeof(funcOrStr) != 'undefined') {
516
621
func = objOrFunc;
626
/** @id MochiKit.Signal.connect */
627
connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
628
src = MochiKit.DOM.getElement(src);
629
var self = MochiKit.Signal;
631
if (typeof(sig) != 'string') {
632
throw new Error("'sig' must be a string");
635
var destPair = self._getDestPair(objOrFunc, funcOrStr);
636
var obj = destPair[0];
637
var func = destPair[1];
518
638
if (typeof(obj) == 'undefined' || obj === null) {
522
642
var isDOM = !!(src.addEventListener || src.attachEvent);
523
var listener = self._listener(src, func, obj, isDOM);
643
if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave")
644
&& !self._browserAlreadyHasMouseEnterAndLeave()) {
645
var listener = self._mouseEnterListener(src, sig.substr(2), func, obj);
646
if (sig === "onmouseenter") {
651
} else if (isDOM && sig == "onmousewheel" && self._browserLacksMouseWheelEvent()) {
652
var listener = self._listener(src, sig, func, obj, isDOM);
653
sig = "onDOMMouseScroll";
655
var listener = self._listener(src, sig, func, obj, isDOM);
525
658
if (src.addEventListener) {
526
659
src.addEventListener(sig.substr(2), listener, false);
527
660
} else if (src.attachEvent) {
528
661
src.attachEvent(sig, listener); // useCapture unsupported
531
var ident = [src, sig, listener, isDOM, objOrFunc, funcOrStr];
664
var ident = new MochiKit.Signal.Ident({
669
objOrFunc: objOrFunc,
670
funcOrStr: funcOrStr,
532
673
self._observers.push(ident);
675
if (!isDOM && typeof(src.__connect__) == 'function') {
676
var args = MochiKit.Base.extend([ident], arguments, 1);
677
src.__connect__.apply(src, args);
538
683
_disconnect: function (ident) {
684
// already disconnected
685
if (!ident.connected) {
688
ident.connected = false;
689
var src = ident.source;
690
var sig = ident.signal;
691
var listener = ident.listener;
540
if (!ident[3]) { return; }
543
var listener = ident[2];
694
if (typeof(src.__disconnect__) == 'function') {
695
src.__disconnect__(ident, sig, ident.objOrFunc, ident.funcOrStr);
544
699
if (src.removeEventListener) {
545
700
src.removeEventListener(sig.substr(2), listener, false);
546
701
} else if (src.detachEvent) {
572
732
var idx = m.findIdentical(observers, ident);
574
734
self._disconnect(ident);
575
observers.splice(idx, 1);
736
observers.splice(idx, 1);
582
disconnectAll: function(src/* optional */, sig) {
746
/** @id MochiKit.Signal.disconnectAllTo */
747
disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) {
748
var self = MochiKit.Signal;
749
var observers = self._observers;
750
var disconnect = self._disconnect;
751
var locked = self._lock;
752
var dirty = self._dirty;
753
if (typeof(funcOrStr) === 'undefined') {
756
for (var i = observers.length - 1; i >= 0; i--) {
757
var ident = observers[i];
758
if (ident.objOrFunc === objOrFunc &&
759
(funcOrStr === null || ident.funcOrStr === funcOrStr)) {
764
observers.splice(i, 1);
771
/** @id MochiKit.Signal.disconnectAll */
772
disconnectAll: function (src/* optional */, sig) {
583
773
src = MochiKit.DOM.getElement(src);
584
774
var m = MochiKit.Base;
585
775
var signals = m.flattenArguments(m.extend(null, arguments, 1));
586
776
var self = MochiKit.Signal;
587
777
var disconnect = self._disconnect;
588
778
var observers = self._observers;
780
var locked = self._lock;
781
var dirty = self._dirty;
589
782
if (signals.length === 0) {
590
783
// disconnect all
591
for (var i = observers.length - 1; i >= 0; i--) {
592
var ident = observers[i];
593
if (ident[0] === src) {
784
for (i = observers.length - 1; i >= 0; i--) {
785
ident = observers[i];
786
if (ident.source === src) {
594
787
disconnect(ident);
595
observers.splice(i, 1);
789
observers.splice(i, 1);
600
for (var i = 0; i < signals.length; i++) {
797
for (i = 0; i < signals.length; i++) {
601
798
sigs[signals[i]] = true;
603
for (var i = observers.length - 1; i >= 0; i--) {
604
var ident = observers[i];
605
if (ident[0] === src && ident[1] in sigs) {
800
for (i = observers.length - 1; i >= 0; i--) {
801
ident = observers[i];
802
if (ident.source === src && ident.signal in sigs) {
606
803
disconnect(ident);
607
observers.splice(i, 1);
805
observers.splice(i, 1);
815
/** @id MochiKit.Signal.signal */
614
816
signal: function (src, sig) {
615
var observers = MochiKit.Signal._observers;
817
var self = MochiKit.Signal;
818
var observers = self._observers;
616
819
src = MochiKit.DOM.getElement(src);
617
820
var args = MochiKit.Base.extend(null, arguments, 2);
619
823
for (var i = 0; i < observers.length; i++) {
620
824
var ident = observers[i];
621
if (ident[0] === src && ident[1] === sig) {
825
if (ident.source === src && ident.signal === sig &&
623
ident[2].apply(src, args);
828
ident.listener.apply(src, args);
837
for (var i = observers.length - 1; i >= 0; i--) {
838
if (!observers[i].connected) {
839
observers.splice(i, 1);
629
843
if (errors.length == 1) {
631
845
} else if (errors.length > 1) {