400 lines
13 KiB
JavaScript
400 lines
13 KiB
JavaScript
define("dojo/_base/connect", ["./kernel", "../on", "../topic", "../aspect", "./event", "../mouse", "./sniff", "./lang", "../keys"], function(kernel, on, hub, aspect, eventModule, mouse, has, lang){
|
|
// module:
|
|
// dojo/_base/connect
|
|
// summary:
|
|
// This module defines the dojo.connect API.
|
|
// This modules also provides keyboard event handling helpers.
|
|
// This module exports an extension event for emulating Firefox's keypress handling.
|
|
// However, this extension event exists primarily for backwards compatibility and
|
|
// is not recommended. WebKit and IE uses an alternate keypress handling (only
|
|
// firing for printable characters, to distinguish from keydown events), and most
|
|
// consider the WebKit/IE behavior more desirable.
|
|
has.add("events-keypress-typed", function(){ // keypresses should only occur a printable character is hit
|
|
var testKeyEvent = {charCode: 0};
|
|
try{
|
|
testKeyEvent = document.createEvent("KeyboardEvent");
|
|
(testKeyEvent.initKeyboardEvent || testKeyEvent.initKeyEvent).call(testKeyEvent, "keypress", true, true, null, false, false, false, false, 9, 3);
|
|
}catch(e){}
|
|
return testKeyEvent.charCode == 0 && !has("opera");
|
|
});
|
|
|
|
function connect_(obj, event, context, method, dontFix){
|
|
method = lang.hitch(context, method);
|
|
if(!obj || !(obj.addEventListener || obj.attachEvent)){
|
|
// it is a not a DOM node and we are using the dojo.connect style of treating a
|
|
// method like an event, must go right to aspect
|
|
return aspect.after(obj || kernel.global, event, method, true);
|
|
}
|
|
if(typeof event == "string" && event.substring(0, 2) == "on"){
|
|
event = event.substring(2);
|
|
}
|
|
if(!obj){
|
|
obj = kernel.global;
|
|
}
|
|
if(!dontFix){
|
|
switch(event){
|
|
// dojo.connect has special handling for these event types
|
|
case "keypress":
|
|
event = keypress;
|
|
break;
|
|
case "mouseenter":
|
|
event = mouse.enter;
|
|
break;
|
|
case "mouseleave":
|
|
event = mouse.leave;
|
|
break;
|
|
}
|
|
}
|
|
return on(obj, event, method, dontFix);
|
|
}
|
|
|
|
var _punctMap = {
|
|
106:42,
|
|
111:47,
|
|
186:59,
|
|
187:43,
|
|
188:44,
|
|
189:45,
|
|
190:46,
|
|
191:47,
|
|
192:96,
|
|
219:91,
|
|
220:92,
|
|
221:93,
|
|
222:39,
|
|
229:113
|
|
};
|
|
var evtCopyKey = has("mac") ? "metaKey" : "ctrlKey";
|
|
|
|
|
|
var _synthesizeEvent = function(evt, props){
|
|
var faux = lang.mixin({}, evt, props);
|
|
setKeyChar(faux);
|
|
// FIXME: would prefer to use lang.hitch: lang.hitch(evt, evt.preventDefault);
|
|
// but it throws an error when preventDefault is invoked on Safari
|
|
// does Event.preventDefault not support "apply" on Safari?
|
|
faux.preventDefault = function(){ evt.preventDefault(); };
|
|
faux.stopPropagation = function(){ evt.stopPropagation(); };
|
|
return faux;
|
|
};
|
|
function setKeyChar(evt){
|
|
evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : '';
|
|
evt.charOrCode = evt.keyChar || evt.keyCode;
|
|
}
|
|
var keypress;
|
|
if(has("events-keypress-typed")){
|
|
// this emulates Firefox's keypress behavior where every keydown can correspond to a keypress
|
|
var _trySetKeyCode = function(e, code){
|
|
try{
|
|
// squelch errors when keyCode is read-only
|
|
// (e.g. if keyCode is ctrl or shift)
|
|
return (e.keyCode = code);
|
|
}catch(e){
|
|
return 0;
|
|
}
|
|
};
|
|
keypress = function(object, listener){
|
|
var keydownSignal = on(object, "keydown", function(evt){
|
|
// munge key/charCode
|
|
var k=evt.keyCode;
|
|
// These are Windows Virtual Key Codes
|
|
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/UserInput/VirtualKeyCodes.asp
|
|
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;
|
|
// synthesize keypress for most unprintables and CTRL-keys
|
|
if(unprintable||evt.ctrlKey){
|
|
var c = unprintable ? 0 : k;
|
|
if(evt.ctrlKey){
|
|
if(k==3 || k==13){
|
|
return listener.call(evt.currentTarget, evt); // IE will post CTRL-BREAK, CTRL-ENTER as keypress natively
|
|
}else if(c>95 && c<106){
|
|
c -= 48; // map CTRL-[numpad 0-9] to ASCII
|
|
}else if((!evt.shiftKey)&&(c>=65&&c<=90)){
|
|
c += 32; // map CTRL-[A-Z] to lowercase
|
|
}else{
|
|
c = _punctMap[c] || c; // map other problematic CTRL combinations to ASCII
|
|
}
|
|
}
|
|
// simulate a keypress event
|
|
var faux = _synthesizeEvent(evt, {type: 'keypress', faux: true, charCode: c});
|
|
listener.call(evt.currentTarget, faux);
|
|
if(has("ie")){
|
|
_trySetKeyCode(evt, faux.keyCode);
|
|
}
|
|
}
|
|
});
|
|
var keypressSignal = on(object, "keypress", function(evt){
|
|
var c = evt.charCode;
|
|
c = c>=32 ? c : 0;
|
|
evt = _synthesizeEvent(evt, {charCode: c, faux: true});
|
|
return listener.call(this, evt);
|
|
});
|
|
return {
|
|
remove: function(){
|
|
keydownSignal.remove();
|
|
keypressSignal.remove();
|
|
}
|
|
};
|
|
};
|
|
}else{
|
|
if(has("opera")){
|
|
keypress = function(object, listener){
|
|
return on(object, "keypress", function(evt){
|
|
var c = evt.which;
|
|
if(c==3){
|
|
c=99; // Mozilla maps CTRL-BREAK to CTRL-c
|
|
}
|
|
// can't trap some keys at all, like INSERT and DELETE
|
|
// there is no differentiating info between DELETE and ".", or INSERT and "-"
|
|
c = c<32 && !evt.shiftKey ? 0 : c;
|
|
if(evt.ctrlKey && !evt.shiftKey && c>=65 && c<=90){
|
|
// lowercase CTRL-[A-Z] keys
|
|
c += 32;
|
|
}
|
|
return listener.call(this, _synthesizeEvent(evt, { charCode: c }));
|
|
});
|
|
};
|
|
}else{
|
|
keypress = function(object, listener){
|
|
return on(object, "keypress", function(evt){
|
|
setKeyChar(evt);
|
|
return listener.call(this, evt);
|
|
});
|
|
};
|
|
}
|
|
}
|
|
|
|
var connect = {
|
|
_keypress:keypress,
|
|
|
|
connect:function(obj, event, context, method, dontFix){
|
|
// normalize arguments
|
|
var a=arguments, args=[], i=0;
|
|
// if a[0] is a String, obj was omitted
|
|
args.push(typeof a[0] == "string" ? null : a[i++], a[i++]);
|
|
// if the arg-after-next is a String or Function, context was NOT omitted
|
|
var a1 = a[i+1];
|
|
args.push(typeof a1 == "string" || typeof a1 == "function" ? a[i++] : null, a[i++]);
|
|
// absorb any additional arguments
|
|
for(var l=a.length; i<l; i++){ args.push(a[i]); }
|
|
return connect_.apply(this, args);
|
|
},
|
|
|
|
disconnect:function(handle){
|
|
if(handle){
|
|
handle.remove();
|
|
}
|
|
},
|
|
|
|
subscribe:function(topic, context, method){
|
|
return hub.subscribe(topic, lang.hitch(context, method));
|
|
},
|
|
|
|
publish:function(topic, args){
|
|
return hub.publish.apply(hub, [topic].concat(args));
|
|
},
|
|
|
|
connectPublisher:function(topic, obj, event){
|
|
var pf = function(){ connect.publish(topic, arguments); };
|
|
return event ? connect.connect(obj, event, pf) : connect.connect(obj, pf); //Handle
|
|
},
|
|
|
|
isCopyKey: function(e){
|
|
return e[evtCopyKey]; // Boolean
|
|
}
|
|
};
|
|
connect.unsubscribe = connect.disconnect;
|
|
|
|
1 && lang.mixin(kernel, connect);
|
|
return connect;
|
|
|
|
/*=====
|
|
dojo.connect = function(obj, event, context, method, dontFix){
|
|
// summary:
|
|
// `dojo.connect` is the core event handling and delegation method in
|
|
// Dojo. It allows one function to "listen in" on the execution of
|
|
// any other, triggering the second whenever the first is called. Many
|
|
// listeners may be attached to a function, and source functions may
|
|
// be either regular function calls or DOM events.
|
|
//
|
|
// description:
|
|
// Connects listeners to actions, so that after event fires, a
|
|
// listener is called with the same arguments passed to the original
|
|
// function.
|
|
//
|
|
// Since `dojo.connect` allows the source of events to be either a
|
|
// "regular" JavaScript function or a DOM event, it provides a uniform
|
|
// interface for listening to all the types of events that an
|
|
// application is likely to deal with though a single, unified
|
|
// interface. DOM programmers may want to think of it as
|
|
// "addEventListener for everything and anything".
|
|
//
|
|
// When setting up a connection, the `event` parameter must be a
|
|
// string that is the name of the method/event to be listened for. If
|
|
// `obj` is null, `kernel.global` is assumed, meaning that connections
|
|
// to global methods are supported but also that you may inadvertently
|
|
// connect to a global by passing an incorrect object name or invalid
|
|
// reference.
|
|
//
|
|
// `dojo.connect` generally is forgiving. If you pass the name of a
|
|
// function or method that does not yet exist on `obj`, connect will
|
|
// not fail, but will instead set up a stub method. Similarly, null
|
|
// arguments may simply be omitted such that fewer than 4 arguments
|
|
// may be required to set up a connection See the examples for details.
|
|
//
|
|
// The return value is a handle that is needed to
|
|
// remove this connection with `dojo.disconnect`.
|
|
//
|
|
// obj: Object|null:
|
|
// The source object for the event function.
|
|
// Defaults to `kernel.global` if null.
|
|
// If obj is a DOM node, the connection is delegated
|
|
// to the DOM event manager (unless dontFix is true).
|
|
//
|
|
// event: String:
|
|
// String name of the event function in obj.
|
|
// I.e. identifies a property `obj[event]`.
|
|
//
|
|
// context: Object|null
|
|
// The object that method will receive as "this".
|
|
//
|
|
// If context is null and method is a function, then method
|
|
// inherits the context of event.
|
|
//
|
|
// If method is a string then context must be the source
|
|
// object object for method (context[method]). If context is null,
|
|
// kernel.global is used.
|
|
//
|
|
// method: String|Function:
|
|
// A function reference, or name of a function in context.
|
|
// The function identified by method fires after event does.
|
|
// method receives the same arguments as the event.
|
|
// See context argument comments for information on method's scope.
|
|
//
|
|
// dontFix: Boolean?
|
|
// If obj is a DOM node, set dontFix to true to prevent delegation
|
|
// of this connection to the DOM event manager.
|
|
//
|
|
// example:
|
|
// When obj.onchange(), do ui.update():
|
|
// | dojo.connect(obj, "onchange", ui, "update");
|
|
// | dojo.connect(obj, "onchange", ui, ui.update); // same
|
|
//
|
|
// example:
|
|
// Using return value for disconnect:
|
|
// | var link = dojo.connect(obj, "onchange", ui, "update");
|
|
// | ...
|
|
// | dojo.disconnect(link);
|
|
//
|
|
// example:
|
|
// When onglobalevent executes, watcher.handler is invoked:
|
|
// | dojo.connect(null, "onglobalevent", watcher, "handler");
|
|
//
|
|
// example:
|
|
// When ob.onCustomEvent executes, customEventHandler is invoked:
|
|
// | dojo.connect(ob, "onCustomEvent", null, "customEventHandler");
|
|
// | dojo.connect(ob, "onCustomEvent", "customEventHandler"); // same
|
|
//
|
|
// example:
|
|
// When ob.onCustomEvent executes, customEventHandler is invoked
|
|
// with the same scope (this):
|
|
// | dojo.connect(ob, "onCustomEvent", null, customEventHandler);
|
|
// | dojo.connect(ob, "onCustomEvent", customEventHandler); // same
|
|
//
|
|
// example:
|
|
// When globalEvent executes, globalHandler is invoked
|
|
// with the same scope (this):
|
|
// | dojo.connect(null, "globalEvent", null, globalHandler);
|
|
// | dojo.connect("globalEvent", globalHandler); // same
|
|
}
|
|
=====*/
|
|
|
|
/*=====
|
|
dojo.disconnect = function(handle){
|
|
// summary:
|
|
// Remove a link created by dojo.connect.
|
|
// description:
|
|
// Removes the connection between event and the method referenced by handle.
|
|
// handle: Handle:
|
|
// the return value of the dojo.connect call that created the connection.
|
|
}
|
|
=====*/
|
|
|
|
/*=====
|
|
dojo.subscribe = function(topic, context, method){
|
|
// summary:
|
|
// Attach a listener to a named topic. The listener function is invoked whenever the
|
|
// named topic is published (see: dojo.publish).
|
|
// Returns a handle which is needed to unsubscribe this listener.
|
|
// topic: String:
|
|
// The topic to which to subscribe.
|
|
// context: Object|null:
|
|
// Scope in which method will be invoked, or null for default scope.
|
|
// method: String|Function:
|
|
// The name of a function in context, or a function reference. This is the function that
|
|
// is invoked when topic is published.
|
|
// example:
|
|
// | dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); });
|
|
// | dojo.publish("alerts", [ "read this", "hello world" ]);
|
|
}
|
|
=====*/
|
|
|
|
/*=====
|
|
dojo.unsubscribe = function(handle){
|
|
// summary:
|
|
// Remove a topic listener.
|
|
// handle: Handle
|
|
// The handle returned from a call to subscribe.
|
|
// example:
|
|
// | var alerter = dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); };
|
|
// | ...
|
|
// | dojo.unsubscribe(alerter);
|
|
}
|
|
=====*/
|
|
|
|
/*=====
|
|
dojo.publish = function(topic, args){
|
|
// summary:
|
|
// Invoke all listener method subscribed to topic.
|
|
// topic: String:
|
|
// The name of the topic to publish.
|
|
// args: Array?
|
|
// An array of arguments. The arguments will be applied
|
|
// to each topic subscriber (as first class parameters, via apply).
|
|
// example:
|
|
// | dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); };
|
|
// | dojo.publish("alerts", [ "read this", "hello world" ]);
|
|
}
|
|
=====*/
|
|
|
|
/*=====
|
|
dojo.connectPublisher = function(topic, obj, event){
|
|
// summary:
|
|
// Ensure that every time obj.event() is called, a message is published
|
|
// on the topic. Returns a handle which can be passed to
|
|
// dojo.disconnect() to disable subsequent automatic publication on
|
|
// the topic.
|
|
// topic: String:
|
|
// The name of the topic to publish.
|
|
// obj: Object|null:
|
|
// The source object for the event function. Defaults to kernel.global
|
|
// if null.
|
|
// event: String:
|
|
// The name of the event function in obj.
|
|
// I.e. identifies a property obj[event].
|
|
// example:
|
|
// | dojo.connectPublisher("/ajax/start", dojo, "xhrGet");
|
|
}
|
|
=====*/
|
|
|
|
/*=====
|
|
dojo.isCopyKey = function(e){
|
|
// summary:
|
|
// Checks an event for the copy key (meta on Mac, and ctrl anywhere else)
|
|
// e: Event
|
|
// Event object to examine
|
|
}
|
|
=====*/
|
|
|
|
});
|
|
|
|
|