You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
12 years ago
|
define("dijit/_base/focus", [
|
||
|
"dojo/_base/array", // array.forEach
|
||
|
"dojo/dom", // dom.isDescendant
|
||
|
"dojo/_base/lang", // lang.isArray
|
||
|
"dojo/topic", // publish
|
||
|
"dojo/_base/window", // win.doc win.doc.selection win.global win.global.getSelection win.withGlobal
|
||
|
"../focus",
|
||
|
"../main" // for exporting symbols to dijit
|
||
|
], function(array, dom, lang, topic, win, focus, dijit){
|
||
|
|
||
|
// module:
|
||
|
// dijit/_base/focus
|
||
|
|
||
|
var exports = {
|
||
|
// summary:
|
||
|
// Deprecated module to monitor currently focused node and stack of currently focused widgets.
|
||
|
// New code should access dijit/focus directly.
|
||
|
|
||
|
// _curFocus: DomNode
|
||
|
// Currently focused item on screen
|
||
|
_curFocus: null,
|
||
|
|
||
|
// _prevFocus: DomNode
|
||
|
// Previously focused item on screen
|
||
|
_prevFocus: null,
|
||
|
|
||
|
isCollapsed: function(){
|
||
|
// summary:
|
||
|
// Returns true if there is no text selected
|
||
|
return dijit.getBookmark().isCollapsed;
|
||
|
},
|
||
|
|
||
|
getBookmark: function(){
|
||
|
// summary:
|
||
|
// Retrieves a bookmark that can be used with moveToBookmark to return to the same range
|
||
|
var bm, rg, tg, sel = win.doc.selection, cf = focus.curNode;
|
||
|
|
||
|
if(win.global.getSelection){
|
||
|
//W3C Range API for selections.
|
||
|
sel = win.global.getSelection();
|
||
|
if(sel){
|
||
|
if(sel.isCollapsed){
|
||
|
tg = cf? cf.tagName : "";
|
||
|
if(tg){
|
||
|
//Create a fake rangelike item to restore selections.
|
||
|
tg = tg.toLowerCase();
|
||
|
if(tg == "textarea" ||
|
||
|
(tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
|
||
|
sel = {
|
||
|
start: cf.selectionStart,
|
||
|
end: cf.selectionEnd,
|
||
|
node: cf,
|
||
|
pRange: true
|
||
|
};
|
||
|
return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
|
||
|
}
|
||
|
}
|
||
|
bm = {isCollapsed:true};
|
||
|
if(sel.rangeCount){
|
||
|
bm.mark = sel.getRangeAt(0).cloneRange();
|
||
|
}
|
||
|
}else{
|
||
|
rg = sel.getRangeAt(0);
|
||
|
bm = {isCollapsed: false, mark: rg.cloneRange()};
|
||
|
}
|
||
|
}
|
||
|
}else if(sel){
|
||
|
// If the current focus was a input of some sort and no selection, don't bother saving
|
||
|
// a native bookmark. This is because it causes issues with dialog/page selection restore.
|
||
|
// So, we need to create psuedo bookmarks to work with.
|
||
|
tg = cf ? cf.tagName : "";
|
||
|
tg = tg.toLowerCase();
|
||
|
if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
|
||
|
if(sel.type && sel.type.toLowerCase() == "none"){
|
||
|
return {
|
||
|
isCollapsed: true,
|
||
|
mark: null
|
||
|
}
|
||
|
}else{
|
||
|
rg = sel.createRange();
|
||
|
return {
|
||
|
isCollapsed: rg.text && rg.text.length?false:true,
|
||
|
mark: {
|
||
|
range: rg,
|
||
|
pRange: true
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
bm = {};
|
||
|
|
||
|
//'IE' way for selections.
|
||
|
try{
|
||
|
// createRange() throws exception when dojo in iframe
|
||
|
//and nothing selected, see #9632
|
||
|
rg = sel.createRange();
|
||
|
bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
|
||
|
}catch(e){
|
||
|
bm.isCollapsed = true;
|
||
|
return bm;
|
||
|
}
|
||
|
if(sel.type.toUpperCase() == 'CONTROL'){
|
||
|
if(rg.length){
|
||
|
bm.mark=[];
|
||
|
var i=0,len=rg.length;
|
||
|
while(i<len){
|
||
|
bm.mark.push(rg.item(i++));
|
||
|
}
|
||
|
}else{
|
||
|
bm.isCollapsed = true;
|
||
|
bm.mark = null;
|
||
|
}
|
||
|
}else{
|
||
|
bm.mark = rg.getBookmark();
|
||
|
}
|
||
|
}else{
|
||
|
console.warn("No idea how to store the current selection for this browser!");
|
||
|
}
|
||
|
return bm; // Object
|
||
|
},
|
||
|
|
||
|
moveToBookmark: function(/*Object*/ bookmark){
|
||
|
// summary:
|
||
|
// Moves current selection to a bookmark
|
||
|
// bookmark:
|
||
|
// This should be a returned object from dijit.getBookmark()
|
||
|
|
||
|
var _doc = win.doc,
|
||
|
mark = bookmark.mark;
|
||
|
if(mark){
|
||
|
if(win.global.getSelection){
|
||
|
//W3C Rangi API (FF, WebKit, Opera, etc)
|
||
|
var sel = win.global.getSelection();
|
||
|
if(sel && sel.removeAllRanges){
|
||
|
if(mark.pRange){
|
||
|
var n = mark.node;
|
||
|
n.selectionStart = mark.start;
|
||
|
n.selectionEnd = mark.end;
|
||
|
}else{
|
||
|
sel.removeAllRanges();
|
||
|
sel.addRange(mark);
|
||
|
}
|
||
|
}else{
|
||
|
console.warn("No idea how to restore selection for this browser!");
|
||
|
}
|
||
|
}else if(_doc.selection && mark){
|
||
|
//'IE' way.
|
||
|
var rg;
|
||
|
if(mark.pRange){
|
||
|
rg = mark.range;
|
||
|
}else if(lang.isArray(mark)){
|
||
|
rg = _doc.body.createControlRange();
|
||
|
//rg.addElement does not have call/apply method, so can not call it directly
|
||
|
//rg is not available in "range.addElement(item)", so can't use that either
|
||
|
array.forEach(mark, function(n){
|
||
|
rg.addElement(n);
|
||
|
});
|
||
|
}else{
|
||
|
rg = _doc.body.createTextRange();
|
||
|
rg.moveToBookmark(mark);
|
||
|
}
|
||
|
rg.select();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
|
||
|
// summary:
|
||
|
// Called as getFocus(), this returns an Object showing the current focus
|
||
|
// and selected text.
|
||
|
//
|
||
|
// Called as getFocus(widget), where widget is a (widget representing) a button
|
||
|
// that was just pressed, it returns where focus was before that button
|
||
|
// was pressed. (Pressing the button may have either shifted focus to the button,
|
||
|
// or removed focus altogether.) In this case the selected text is not returned,
|
||
|
// since it can't be accurately determined.
|
||
|
//
|
||
|
// menu: dijit/_WidgetBase|{domNode: DomNode} structure
|
||
|
// The button that was just pressed. If focus has disappeared or moved
|
||
|
// to this button, returns the previous focus. In this case the bookmark
|
||
|
// information is already lost, and null is returned.
|
||
|
//
|
||
|
// openedForWindow:
|
||
|
// iframe in which menu was opened
|
||
|
//
|
||
|
// returns:
|
||
|
// A handle to restore focus/selection, to be passed to `dijit.focus`
|
||
|
var node = !focus.curNode || (menu && dom.isDescendant(focus.curNode, menu.domNode)) ? dijit._prevFocus : focus.curNode;
|
||
|
return {
|
||
|
node: node,
|
||
|
bookmark: node && (node == focus.curNode) && win.withGlobal(openedForWindow || win.global, dijit.getBookmark),
|
||
|
openedForWindow: openedForWindow
|
||
|
}; // Object
|
||
|
},
|
||
|
|
||
|
// _activeStack: dijit/_WidgetBase[]
|
||
|
// List of currently active widgets (focused widget and it's ancestors)
|
||
|
_activeStack: [],
|
||
|
|
||
|
registerIframe: function(/*DomNode*/ iframe){
|
||
|
// summary:
|
||
|
// Registers listeners on the specified iframe so that any click
|
||
|
// or focus event on that iframe (or anything in it) is reported
|
||
|
// as a focus/click event on the `<iframe>` itself.
|
||
|
// description:
|
||
|
// Currently only used by editor.
|
||
|
// returns:
|
||
|
// Handle to pass to unregisterIframe()
|
||
|
return focus.registerIframe(iframe);
|
||
|
},
|
||
|
|
||
|
unregisterIframe: function(/*Object*/ handle){
|
||
|
// summary:
|
||
|
// Unregisters listeners on the specified iframe created by registerIframe.
|
||
|
// After calling be sure to delete or null out the handle itself.
|
||
|
// handle:
|
||
|
// Handle returned by registerIframe()
|
||
|
|
||
|
handle && handle.remove();
|
||
|
},
|
||
|
|
||
|
registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
|
||
|
// summary:
|
||
|
// Registers listeners on the specified window (either the main
|
||
|
// window or an iframe's window) to detect when the user has clicked somewhere
|
||
|
// or focused somewhere.
|
||
|
// description:
|
||
|
// Users should call registerIframe() instead of this method.
|
||
|
// targetWindow:
|
||
|
// If specified this is the window associated with the iframe,
|
||
|
// i.e. iframe.contentWindow.
|
||
|
// effectiveNode:
|
||
|
// If specified, report any focus events inside targetWindow as
|
||
|
// an event on effectiveNode, rather than on evt.target.
|
||
|
// returns:
|
||
|
// Handle to pass to unregisterWin()
|
||
|
|
||
|
return focus.registerWin(targetWindow, effectiveNode);
|
||
|
},
|
||
|
|
||
|
unregisterWin: function(/*Handle*/ handle){
|
||
|
// summary:
|
||
|
// Unregisters listeners on the specified window (either the main
|
||
|
// window or an iframe's window) according to handle returned from registerWin().
|
||
|
// After calling be sure to delete or null out the handle itself.
|
||
|
|
||
|
handle && handle.remove();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Override focus singleton's focus function so that dijit.focus()
|
||
|
// has backwards compatible behavior of restoring selection (although
|
||
|
// probably no one is using that).
|
||
|
focus.focus = function(/*Object|DomNode */ handle){
|
||
|
// summary:
|
||
|
// Sets the focused node and the selection according to argument.
|
||
|
// To set focus to an iframe's content, pass in the iframe itself.
|
||
|
// handle:
|
||
|
// object returned by get(), or a DomNode
|
||
|
|
||
|
if(!handle){ return; }
|
||
|
|
||
|
var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
|
||
|
bookmark = handle.bookmark,
|
||
|
openedForWindow = handle.openedForWindow,
|
||
|
collapsed = bookmark ? bookmark.isCollapsed : false;
|
||
|
|
||
|
// Set the focus
|
||
|
// Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
|
||
|
// but we need to set focus to iframe.contentWindow
|
||
|
if(node){
|
||
|
var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node;
|
||
|
if(focusNode && focusNode.focus){
|
||
|
try{
|
||
|
// Gecko throws sometimes if setting focus is impossible,
|
||
|
// node not displayed or something like that
|
||
|
focusNode.focus();
|
||
|
}catch(e){/*quiet*/}
|
||
|
}
|
||
|
focus._onFocusNode(node);
|
||
|
}
|
||
|
|
||
|
// set the selection
|
||
|
// do not need to restore if current selection is not empty
|
||
|
// (use keyboard to select a menu item) or if previous selection was collapsed
|
||
|
// as it may cause focus shift (Esp in IE).
|
||
|
if(bookmark && win.withGlobal(openedForWindow || win.global, dijit.isCollapsed) && !collapsed){
|
||
|
if(openedForWindow){
|
||
|
openedForWindow.focus();
|
||
|
}
|
||
|
try{
|
||
|
win.withGlobal(openedForWindow || win.global, dijit.moveToBookmark, null, [bookmark]);
|
||
|
}catch(e2){
|
||
|
/*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// For back compatibility, monitor changes to focused node and active widget stack,
|
||
|
// publishing events and copying changes from focus manager variables into dijit (top level) variables
|
||
|
focus.watch("curNode", function(name, oldVal, newVal){
|
||
|
dijit._curFocus = newVal;
|
||
|
dijit._prevFocus = oldVal;
|
||
|
if(newVal){
|
||
|
topic.publish("focusNode", newVal); // publish
|
||
|
}
|
||
|
});
|
||
|
focus.watch("activeStack", function(name, oldVal, newVal){
|
||
|
dijit._activeStack = newVal;
|
||
|
});
|
||
|
|
||
|
focus.on("widget-blur", function(widget, by){
|
||
|
topic.publish("widgetBlur", widget, by); // publish
|
||
|
});
|
||
|
focus.on("widget-focus", function(widget, by){
|
||
|
topic.publish("widgetFocus", widget, by); // publish
|
||
|
});
|
||
|
|
||
|
lang.mixin(dijit, exports);
|
||
|
|
||
|
/*===== return exports; =====*/
|
||
|
return dijit; // for back compat :-(
|
||
|
});
|