source: Dev/branches/rest-dojo-ui/client/dojo/_base/connect.js @ 263

Last change on this file since 263 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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