connect.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. define(["./kernel", "../on", "../topic", "../aspect", "./event", "../mouse", "./sniff", "./lang", "../keys"], function(dojo, on, hub, aspect, eventModule, mouse, has, lang){
  2. // module:
  3. // dojo/_base/connect
  4. has.add("events-keypress-typed", function(){ // keypresses should only occur a printable character is hit
  5. var testKeyEvent = {charCode: 0};
  6. try{
  7. testKeyEvent = document.createEvent("KeyboardEvent");
  8. (testKeyEvent.initKeyboardEvent || testKeyEvent.initKeyEvent).call(testKeyEvent, "keypress", true, true, null, false, false, false, false, 9, 3);
  9. }catch(e){}
  10. return testKeyEvent.charCode == 0 && !has("opera");
  11. });
  12. function connect_(obj, event, context, method, dontFix){
  13. method = lang.hitch(context, method);
  14. if(!obj || !(obj.addEventListener || obj.attachEvent)){
  15. // it is a not a DOM node and we are using the dojo.connect style of treating a
  16. // method like an event, must go right to aspect
  17. return aspect.after(obj || dojo.global, event, method, true);
  18. }
  19. if(typeof event == "string" && event.substring(0, 2) == "on"){
  20. event = event.substring(2);
  21. }
  22. if(!obj){
  23. obj = dojo.global;
  24. }
  25. if(!dontFix){
  26. switch(event){
  27. // dojo.connect has special handling for these event types
  28. case "keypress":
  29. event = keypress;
  30. break;
  31. case "mouseenter":
  32. event = mouse.enter;
  33. break;
  34. case "mouseleave":
  35. event = mouse.leave;
  36. break;
  37. }
  38. }
  39. return on(obj, event, method, dontFix);
  40. }
  41. var _punctMap = {
  42. 106:42,
  43. 111:47,
  44. 186:59,
  45. 187:43,
  46. 188:44,
  47. 189:45,
  48. 190:46,
  49. 191:47,
  50. 192:96,
  51. 219:91,
  52. 220:92,
  53. 221:93,
  54. 222:39,
  55. 229:113
  56. };
  57. var evtCopyKey = has("mac") ? "metaKey" : "ctrlKey";
  58. var _synthesizeEvent = function(evt, props){
  59. var faux = lang.mixin({}, evt, props);
  60. setKeyChar(faux);
  61. // FIXME: would prefer to use lang.hitch: lang.hitch(evt, evt.preventDefault);
  62. // but it throws an error when preventDefault is invoked on Safari
  63. // does Event.preventDefault not support "apply" on Safari?
  64. faux.preventDefault = function(){ evt.preventDefault(); };
  65. faux.stopPropagation = function(){ evt.stopPropagation(); };
  66. return faux;
  67. };
  68. function setKeyChar(evt){
  69. evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : '';
  70. evt.charOrCode = evt.keyChar || evt.keyCode;
  71. }
  72. var keypress;
  73. if(has("events-keypress-typed")){
  74. // this emulates Firefox's keypress behavior where every keydown can correspond to a keypress
  75. var _trySetKeyCode = function(e, code){
  76. try{
  77. // squelch errors when keyCode is read-only
  78. // (e.g. if keyCode is ctrl or shift)
  79. return (e.keyCode = code);
  80. }catch(e){
  81. return 0;
  82. }
  83. };
  84. keypress = function(object, listener){
  85. var keydownSignal = on(object, "keydown", function(evt){
  86. // munge key/charCode
  87. var k=evt.keyCode;
  88. // These are Windows Virtual Key Codes
  89. // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/UserInput/VirtualKeyCodes.asp
  90. var unprintable = (k!=13) && k!=32 && (k!=27||!has("ie")) && (k<48||k>90) && (k<96||k>111) && (k<186||k>192) && (k<219||k>222) && k!=229;
  91. // synthesize keypress for most unprintables and CTRL-keys
  92. if(unprintable||evt.ctrlKey){
  93. var c = unprintable ? 0 : k;
  94. if(evt.ctrlKey){
  95. if(k==3 || k==13){
  96. return listener.call(evt.currentTarget, evt); // IE will post CTRL-BREAK, CTRL-ENTER as keypress natively
  97. }else if(c>95 && c<106){
  98. c -= 48; // map CTRL-[numpad 0-9] to ASCII
  99. }else if((!evt.shiftKey)&&(c>=65&&c<=90)){
  100. c += 32; // map CTRL-[A-Z] to lowercase
  101. }else{
  102. c = _punctMap[c] || c; // map other problematic CTRL combinations to ASCII
  103. }
  104. }
  105. // simulate a keypress event
  106. var faux = _synthesizeEvent(evt, {type: 'keypress', faux: true, charCode: c});
  107. listener.call(evt.currentTarget, faux);
  108. if(has("ie")){
  109. _trySetKeyCode(evt, faux.keyCode);
  110. }
  111. }
  112. });
  113. var keypressSignal = on(object, "keypress", function(evt){
  114. var c = evt.charCode;
  115. c = c>=32 ? c : 0;
  116. evt = _synthesizeEvent(evt, {charCode: c, faux: true});
  117. return listener.call(this, evt);
  118. });
  119. return {
  120. remove: function(){
  121. keydownSignal.remove();
  122. keypressSignal.remove();
  123. }
  124. };
  125. };
  126. }else{
  127. if(has("opera")){
  128. keypress = function(object, listener){
  129. return on(object, "keypress", function(evt){
  130. var c = evt.which;
  131. if(c==3){
  132. c=99; // Mozilla maps CTRL-BREAK to CTRL-c
  133. }
  134. // can't trap some keys at all, like INSERT and DELETE
  135. // there is no differentiating info between DELETE and ".", or INSERT and "-"
  136. c = c<32 && !evt.shiftKey ? 0 : c;
  137. if(evt.ctrlKey && !evt.shiftKey && c>=65 && c<=90){
  138. // lowercase CTRL-[A-Z] keys
  139. c += 32;
  140. }
  141. return listener.call(this, _synthesizeEvent(evt, { charCode: c }));
  142. });
  143. };
  144. }else{
  145. keypress = function(object, listener){
  146. return on(object, "keypress", function(evt){
  147. setKeyChar(evt);
  148. return listener.call(this, evt);
  149. });
  150. };
  151. }
  152. }
  153. var connect = {
  154. // summary:
  155. // This module defines the dojo.connect API.
  156. // This modules also provides keyboard event handling helpers.
  157. // This module exports an extension event for emulating Firefox's keypress handling.
  158. // However, this extension event exists primarily for backwards compatibility and
  159. // is not recommended. WebKit and IE uses an alternate keypress handling (only
  160. // firing for printable characters, to distinguish from keydown events), and most
  161. // consider the WebKit/IE behavior more desirable.
  162. _keypress:keypress,
  163. connect:function(obj, event, context, method, dontFix){
  164. // summary:
  165. // `dojo.connect` is a deprecated event handling and delegation method in
  166. // Dojo. It allows one function to "listen in" on the execution of
  167. // any other, triggering the second whenever the first is called. Many
  168. // listeners may be attached to a function, and source functions may
  169. // be either regular function calls or DOM events.
  170. //
  171. // description:
  172. // Connects listeners to actions, so that after event fires, a
  173. // listener is called with the same arguments passed to the original
  174. // function.
  175. //
  176. // Since `dojo.connect` allows the source of events to be either a
  177. // "regular" JavaScript function or a DOM event, it provides a uniform
  178. // interface for listening to all the types of events that an
  179. // application is likely to deal with though a single, unified
  180. // interface. DOM programmers may want to think of it as
  181. // "addEventListener for everything and anything".
  182. //
  183. // When setting up a connection, the `event` parameter must be a
  184. // string that is the name of the method/event to be listened for. If
  185. // `obj` is null, `kernel.global` is assumed, meaning that connections
  186. // to global methods are supported but also that you may inadvertently
  187. // connect to a global by passing an incorrect object name or invalid
  188. // reference.
  189. //
  190. // `dojo.connect` generally is forgiving. If you pass the name of a
  191. // function or method that does not yet exist on `obj`, connect will
  192. // not fail, but will instead set up a stub method. Similarly, null
  193. // arguments may simply be omitted such that fewer than 4 arguments
  194. // may be required to set up a connection See the examples for details.
  195. //
  196. // The return value is a handle that is needed to
  197. // remove this connection with `dojo.disconnect`.
  198. //
  199. // obj: Object?
  200. // The source object for the event function.
  201. // Defaults to `kernel.global` if null.
  202. // If obj is a DOM node, the connection is delegated
  203. // to the DOM event manager (unless dontFix is true).
  204. //
  205. // event: String
  206. // String name of the event function in obj.
  207. // I.e. identifies a property `obj[event]`.
  208. //
  209. // context: Object|null
  210. // The object that method will receive as "this".
  211. //
  212. // If context is null and method is a function, then method
  213. // inherits the context of event.
  214. //
  215. // If method is a string then context must be the source
  216. // object object for method (context[method]). If context is null,
  217. // kernel.global is used.
  218. //
  219. // method: String|Function
  220. // A function reference, or name of a function in context.
  221. // The function identified by method fires after event does.
  222. // method receives the same arguments as the event.
  223. // See context argument comments for information on method's scope.
  224. //
  225. // dontFix: Boolean?
  226. // If obj is a DOM node, set dontFix to true to prevent delegation
  227. // of this connection to the DOM event manager.
  228. //
  229. // example:
  230. // When obj.onchange(), do ui.update():
  231. // | dojo.connect(obj, "onchange", ui, "update");
  232. // | dojo.connect(obj, "onchange", ui, ui.update); // same
  233. //
  234. // example:
  235. // Using return value for disconnect:
  236. // | var link = dojo.connect(obj, "onchange", ui, "update");
  237. // | ...
  238. // | dojo.disconnect(link);
  239. //
  240. // example:
  241. // When onglobalevent executes, watcher.handler is invoked:
  242. // | dojo.connect(null, "onglobalevent", watcher, "handler");
  243. //
  244. // example:
  245. // When ob.onCustomEvent executes, customEventHandler is invoked:
  246. // | dojo.connect(ob, "onCustomEvent", null, "customEventHandler");
  247. // | dojo.connect(ob, "onCustomEvent", "customEventHandler"); // same
  248. //
  249. // example:
  250. // When ob.onCustomEvent executes, customEventHandler is invoked
  251. // with the same scope (this):
  252. // | dojo.connect(ob, "onCustomEvent", null, customEventHandler);
  253. // | dojo.connect(ob, "onCustomEvent", customEventHandler); // same
  254. //
  255. // example:
  256. // When globalEvent executes, globalHandler is invoked
  257. // with the same scope (this):
  258. // | dojo.connect(null, "globalEvent", null, globalHandler);
  259. // | dojo.connect("globalEvent", globalHandler); // same
  260. // normalize arguments
  261. var a=arguments, args=[], i=0;
  262. // if a[0] is a String, obj was omitted
  263. args.push(typeof a[0] == "string" ? null : a[i++], a[i++]);
  264. // if the arg-after-next is a String or Function, context was NOT omitted
  265. var a1 = a[i+1];
  266. args.push(typeof a1 == "string" || typeof a1 == "function" ? a[i++] : null, a[i++]);
  267. // absorb any additional arguments
  268. for(var l=a.length; i<l; i++){ args.push(a[i]); }
  269. return connect_.apply(this, args);
  270. },
  271. disconnect:function(handle){
  272. // summary:
  273. // Remove a link created by dojo.connect.
  274. // description:
  275. // Removes the connection between event and the method referenced by handle.
  276. // handle: Handle
  277. // the return value of the dojo.connect call that created the connection.
  278. if(handle){
  279. handle.remove();
  280. }
  281. },
  282. subscribe:function(topic, context, method){
  283. // summary:
  284. // Attach a listener to a named topic. The listener function is invoked whenever the
  285. // named topic is published (see: dojo.publish).
  286. // Returns a handle which is needed to unsubscribe this listener.
  287. // topic: String
  288. // The topic to which to subscribe.
  289. // context: Object?
  290. // Scope in which method will be invoked, or null for default scope.
  291. // method: String|Function
  292. // The name of a function in context, or a function reference. This is the function that
  293. // is invoked when topic is published.
  294. // example:
  295. // | dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); });
  296. // | dojo.publish("alerts", [ "read this", "hello world" ]);
  297. return hub.subscribe(topic, lang.hitch(context, method));
  298. },
  299. publish:function(topic, args){
  300. // summary:
  301. // Invoke all listener method subscribed to topic.
  302. // topic: String
  303. // The name of the topic to publish.
  304. // args: Array?
  305. // An array of arguments. The arguments will be applied
  306. // to each topic subscriber (as first class parameters, via apply).
  307. // example:
  308. // | dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); };
  309. // | dojo.publish("alerts", [ "read this", "hello world" ]);
  310. return hub.publish.apply(hub, [topic].concat(args));
  311. },
  312. connectPublisher:function(topic, obj, event){
  313. // summary:
  314. // Ensure that every time obj.event() is called, a message is published
  315. // on the topic. Returns a handle which can be passed to
  316. // dojo.disconnect() to disable subsequent automatic publication on
  317. // the topic.
  318. // topic: String
  319. // The name of the topic to publish.
  320. // obj: Object?
  321. // The source object for the event function. Defaults to kernel.global
  322. // if null.
  323. // event: String
  324. // The name of the event function in obj.
  325. // I.e. identifies a property obj[event].
  326. // example:
  327. // | dojo.connectPublisher("/ajax/start", dojo, "xhrGet");
  328. var pf = function(){ connect.publish(topic, arguments); };
  329. return event ? connect.connect(obj, event, pf) : connect.connect(obj, pf); //Handle
  330. },
  331. isCopyKey: function(e){
  332. // summary:
  333. // Checks an event for the copy key (meta on Mac, and ctrl anywhere else)
  334. // e: Event
  335. // Event object to examine
  336. return e[evtCopyKey]; // Boolean
  337. }
  338. };
  339. connect.unsubscribe = connect.disconnect;
  340. /*=====
  341. connect.unsubscribe = function(handle){
  342. // summary:
  343. // Remove a topic listener.
  344. // handle: Handle
  345. // The handle returned from a call to subscribe.
  346. // example:
  347. // | var alerter = dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); };
  348. // | ...
  349. // | dojo.unsubscribe(alerter);
  350. };
  351. =====*/
  352. has("extend-dojo") && lang.mixin(dojo, connect);
  353. return connect;
  354. });