Signal.js 26 KB

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