connect.js 13 KB

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