diff options
Diffstat (limited to 'lib/dijit/_base')
-rw-r--r-- | lib/dijit/_base/focus.js | 803 | ||||
-rw-r--r-- | lib/dijit/_base/manager.js | 718 | ||||
-rw-r--r-- | lib/dijit/_base/place.js | 449 | ||||
-rw-r--r-- | lib/dijit/_base/popup.js | 533 | ||||
-rw-r--r-- | lib/dijit/_base/scroll.js | 17 | ||||
-rw-r--r-- | lib/dijit/_base/sniff.js | 15 | ||||
-rw-r--r-- | lib/dijit/_base/typematic.js | 262 | ||||
-rw-r--r-- | lib/dijit/_base/wai.js | 191 | ||||
-rw-r--r-- | lib/dijit/_base/window.js | 13 |
9 files changed, 2098 insertions, 903 deletions
diff --git a/lib/dijit/_base/focus.js b/lib/dijit/_base/focus.js index 32be06aa7..55c5b682d 100644 --- a/lib/dijit/_base/focus.js +++ b/lib/dijit/_base/focus.js @@ -1,299 +1,530 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.focus"]){ -dojo._hasResource["dijit._base.focus"]=true; +if(!dojo._hasResource["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.focus"] = true; dojo.provide("dijit._base.focus"); dojo.require("dojo.window"); dojo.require("dijit._base.manager"); -dojo.mixin(dijit,{_curFocus:null,_prevFocus:null,isCollapsed:function(){ -return dijit.getBookmark().isCollapsed; -},getBookmark:function(){ -var bm,rg,tg,_1=dojo.doc.selection,cf=dijit._curFocus; -if(dojo.global.getSelection){ -_1=dojo.global.getSelection(); -if(_1){ -if(_1.isCollapsed){ -tg=cf?cf.tagName:""; -if(tg){ -tg=tg.toLowerCase(); -if(tg=="textarea"||(tg=="input"&&(!cf.type||cf.type.toLowerCase()=="text"))){ -_1={start:cf.selectionStart,end:cf.selectionEnd,node:cf,pRange:true}; -return {isCollapsed:(_1.end<=_1.start),mark:_1}; -} -} -bm={isCollapsed:true}; -}else{ -rg=_1.getRangeAt(0); -bm={isCollapsed:false,mark:rg.cloneRange()}; -} -} -}else{ -if(_1){ -tg=cf?cf.tagName:""; -tg=tg.toLowerCase(); -if(cf&&tg&&(tg=="button"||tg=="textarea"||tg=="input")){ -if(_1.type&&_1.type.toLowerCase()=="none"){ -return {isCollapsed:true,mark:null}; -}else{ -rg=_1.createRange(); -return {isCollapsed:rg.text&&rg.text.length?false:true,mark:{range:rg,pRange:true}}; -} -} -bm={}; -try{ -rg=_1.createRange(); -bm.isCollapsed=!(_1.type=="Text"?rg.htmlText.length:rg.length); -} -catch(e){ -bm.isCollapsed=true; -return bm; -} -if(_1.type.toUpperCase()=="CONTROL"){ -if(rg.length){ -bm.mark=[]; -var i=0,_2=rg.length; -while(i<_2){ -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; -},moveToBookmark:function(_3){ -var _4=dojo.doc,_5=_3.mark; -if(_5){ -if(dojo.global.getSelection){ -var _6=dojo.global.getSelection(); -if(_6&&_6.removeAllRanges){ -if(_5.pRange){ -var r=_5; -var n=r.node; -n.selectionStart=r.start; -n.selectionEnd=r.end; -}else{ -_6.removeAllRanges(); -_6.addRange(_5); -} -}else{ -console.warn("No idea how to restore selection for this browser!"); -} -}else{ -if(_4.selection&&_5){ -var rg; -if(_5.pRange){ -rg=_5.range; -}else{ -if(dojo.isArray(_5)){ -rg=_4.body.createControlRange(); -dojo.forEach(_5,function(n){ -rg.addElement(n); + + +// summary: +// These functions are used to query or set the focus and selection. +// +// Also, they trace when widgets become activated/deactivated, +// so that the widget can fire _onFocus/_onBlur events. +// "Active" here means something similar to "focused", but +// "focus" isn't quite the right word because we keep track of +// a whole stack of "active" widgets. Example: ComboButton --> Menu --> +// MenuItem. The onBlur event for ComboButton doesn't fire due to focusing +// on the Menu or a MenuItem, since they are considered part of the +// ComboButton widget. It only happens when focus is shifted +// somewhere completely different. + +dojo.mixin(dijit, { + // _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 = dojo.doc.selection, cf = dijit._curFocus; + + if(dojo.global.getSelection){ + //W3C Range API for selections. + sel = dojo.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 = dojo.doc, + mark = bookmark.mark; + if(mark){ + if(dojo.global.getSelection){ + //W3C Rangi API (FF, WebKit, Opera, etc) + var sel = dojo.global.getSelection(); + if(sel && sel.removeAllRanges){ + if(mark.pRange){ + var r = mark; + var n = r.node; + n.selectionStart = r.start; + n.selectionEnd = r.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(dojo.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 + dojo.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._Widget or {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 = !dijit._curFocus || (menu && dojo.isDescendant(dijit._curFocus, menu.domNode)) ? dijit._prevFocus : dijit._curFocus; + return { + node: node, + bookmark: (node == dijit._curFocus) && dojo.withGlobal(openedForWindow || dojo.global, dijit.getBookmark), + openedForWindow: openedForWindow + }; // Object + }, + + 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*/} + } + dijit._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 && dojo.withGlobal(openedForWindow || dojo.global, dijit.isCollapsed) && !collapsed){ + if(openedForWindow){ + openedForWindow.focus(); + } + try{ + dojo.withGlobal(openedForWindow || dojo.global, dijit.moveToBookmark, null, [bookmark]); + }catch(e2){ + /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */ + } + } + }, + + // _activeStack: dijit._Widget[] + // 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 dijit.registerWin(iframe.contentWindow, 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() + + dijit.unregisterWin(handle); + }, + + 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() + + // TODO: make this function private in 2.0; Editor/users should call registerIframe(), + + var mousedownListener = function(evt){ + dijit._justMouseDowned = true; + setTimeout(function(){ dijit._justMouseDowned = false; }, 0); + + // workaround weird IE bug where the click is on an orphaned node + // (first time clicking a Select/DropDownButton inside a TooltipDialog) + if(dojo.isIE && evt && evt.srcElement && evt.srcElement.parentNode == null){ + return; + } + + dijit._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse"); + }; + //dojo.connect(targetWindow, "onscroll", ???); + + // Listen for blur and focus events on targetWindow's document. + // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble + // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers + // fire. + // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because + // (at least for FF) the focus event doesn't fire on <html> or <body>. + var doc = dojo.isIE ? targetWindow.document.documentElement : targetWindow.document; + if(doc){ + if(dojo.isIE){ + targetWindow.document.body.attachEvent('onmousedown', mousedownListener); + var activateListener = function(evt){ + // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1, + // Should consider those more like a mouse-click than a focus.... + if(evt.srcElement.tagName.toLowerCase() != "#document" && + dijit.isTabNavigable(evt.srcElement)){ + dijit._onFocusNode(effectiveNode || evt.srcElement); + }else{ + dijit._onTouchNode(effectiveNode || evt.srcElement); + } + }; + doc.attachEvent('onactivate', activateListener); + var deactivateListener = function(evt){ + dijit._onBlurNode(effectiveNode || evt.srcElement); + }; + doc.attachEvent('ondeactivate', deactivateListener); + + return function(){ + targetWindow.document.detachEvent('onmousedown', mousedownListener); + doc.detachEvent('onactivate', activateListener); + doc.detachEvent('ondeactivate', deactivateListener); + doc = null; // prevent memory leak (apparent circular reference via closure) + }; + }else{ + doc.body.addEventListener('mousedown', mousedownListener, true); + var focusListener = function(evt){ + dijit._onFocusNode(effectiveNode || evt.target); + }; + doc.addEventListener('focus', focusListener, true); + var blurListener = function(evt){ + dijit._onBlurNode(effectiveNode || evt.target); + }; + doc.addEventListener('blur', blurListener, true); + + return function(){ + doc.body.removeEventListener('mousedown', mousedownListener, true); + doc.removeEventListener('focus', focusListener, true); + doc.removeEventListener('blur', blurListener, true); + doc = null; // prevent memory leak (apparent circular reference via closure) + }; + } + } + }, + + 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. + + // Currently our handle is actually a function + handle && handle(); + }, + + _onBlurNode: function(/*DomNode*/ node){ + // summary: + // Called when focus leaves a node. + // Usually ignored, _unless_ it *isn't* follwed by touching another node, + // which indicates that we tabbed off the last field on the page, + // in which case every widget is marked inactive + dijit._prevFocus = dijit._curFocus; + dijit._curFocus = null; + + if(dijit._justMouseDowned){ + // the mouse down caused a new widget to be marked as active; this blur event + // is coming late, so ignore it. + return; + } + + // if the blur event isn't followed by a focus event then mark all widgets as inactive. + if(dijit._clearActiveWidgetsTimer){ + clearTimeout(dijit._clearActiveWidgetsTimer); + } + dijit._clearActiveWidgetsTimer = setTimeout(function(){ + delete dijit._clearActiveWidgetsTimer; + dijit._setStack([]); + dijit._prevFocus = null; + }, 100); + }, + + _onTouchNode: function(/*DomNode*/ node, /*String*/ by){ + // summary: + // Callback when node is focused or mouse-downed + // node: + // The node that was touched. + // by: + // "mouse" if the focus/touch was caused by a mouse down event + + // ignore the recent blurNode event + if(dijit._clearActiveWidgetsTimer){ + clearTimeout(dijit._clearActiveWidgetsTimer); + delete dijit._clearActiveWidgetsTimer; + } + + // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem) + var newStack=[]; + try{ + while(node){ + var popupParent = dojo.attr(node, "dijitPopupParent"); + if(popupParent){ + node=dijit.byId(popupParent).domNode; + }else if(node.tagName && node.tagName.toLowerCase() == "body"){ + // is this the root of the document or just the root of an iframe? + if(node === dojo.body()){ + // node is the root of the main document + break; + } + // otherwise, find the iframe this node refers to (can't access it via parentNode, + // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit + node=dojo.window.get(node.ownerDocument).frameElement; + }else{ + // if this node is the root node of a widget, then add widget id to stack, + // except ignore clicks on disabled widgets (actually focusing a disabled widget still works, + // to support MenuItem) + var id = node.getAttribute && node.getAttribute("widgetId"), + widget = id && dijit.byId(id); + if(widget && !(by == "mouse" && widget.get("disabled"))){ + newStack.unshift(id); + } + node=node.parentNode; + } + } + }catch(e){ /* squelch */ } + + dijit._setStack(newStack, by); + }, + + _onFocusNode: function(/*DomNode*/ node){ + // summary: + // Callback when node is focused + + if(!node){ + return; + } + + if(node.nodeType == 9){ + // Ignore focus events on the document itself. This is here so that + // (for example) clicking the up/down arrows of a spinner + // (which don't get focus) won't cause that widget to blur. (FF issue) + return; + } + + dijit._onTouchNode(node); + + if(node == dijit._curFocus){ return; } + if(dijit._curFocus){ + dijit._prevFocus = dijit._curFocus; + } + dijit._curFocus = node; + dojo.publish("focusNode", [node]); + }, + + _setStack: function(/*String[]*/ newStack, /*String*/ by){ + // summary: + // The stack of active widgets has changed. Send out appropriate events and records new stack. + // newStack: + // array of widget id's, starting from the top (outermost) widget + // by: + // "mouse" if the focus/touch was caused by a mouse down event + + var oldStack = dijit._activeStack; + dijit._activeStack = newStack; + + // compare old stack to new stack to see how many elements they have in common + for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){ + if(oldStack[nCommon] != newStack[nCommon]){ + break; + } + } + + var widget; + // for all elements that have gone out of focus, send blur event + for(var i=oldStack.length-1; i>=nCommon; i--){ + widget = dijit.byId(oldStack[i]); + if(widget){ + widget._focused = false; + widget.set("focused", false); + widget._hasBeenBlurred = true; + if(widget._onBlur){ + widget._onBlur(by); + } + dojo.publish("widgetBlur", [widget, by]); + } + } + + // for all element that have come into focus, send focus event + for(i=nCommon; i<newStack.length; i++){ + widget = dijit.byId(newStack[i]); + if(widget){ + widget._focused = true; + widget.set("focused", true); + if(widget._onFocus){ + widget._onFocus(by); + } + dojo.publish("widgetFocus", [widget, by]); + } + } + } }); -}else{ -rg=_4.body.createTextRange(); -rg.moveToBookmark(_5); -} -} -rg.select(); -} -} -} -},getFocus:function(_7,_8){ -var _9=!dijit._curFocus||(_7&&dojo.isDescendant(dijit._curFocus,_7.domNode))?dijit._prevFocus:dijit._curFocus; -return {node:_9,bookmark:(_9==dijit._curFocus)&&dojo.withGlobal(_8||dojo.global,dijit.getBookmark),openedForWindow:_8}; -},focus:function(_a){ -if(!_a){ -return; -} -var _b="node" in _a?_a.node:_a,_c=_a.bookmark,_d=_a.openedForWindow,_e=_c?_c.isCollapsed:false; -if(_b){ -var _f=(_b.tagName.toLowerCase()=="iframe")?_b.contentWindow:_b; -if(_f&&_f.focus){ -try{ -_f.focus(); -} -catch(e){ -} -} -dijit._onFocusNode(_b); -} -if(_c&&dojo.withGlobal(_d||dojo.global,dijit.isCollapsed)&&!_e){ -if(_d){ -_d.focus(); -} -try{ -dojo.withGlobal(_d||dojo.global,dijit.moveToBookmark,null,[_c]); -} -catch(e2){ -} -} -},_activeStack:[],registerIframe:function(_10){ -return dijit.registerWin(_10.contentWindow,_10); -},unregisterIframe:function(_11){ -dijit.unregisterWin(_11); -},registerWin:function(_12,_13){ -var _14=function(evt){ -dijit._justMouseDowned=true; -setTimeout(function(){ -dijit._justMouseDowned=false; -},0); -if(dojo.isIE&&evt&&evt.srcElement&&evt.srcElement.parentNode==null){ -return; -} -dijit._onTouchNode(_13||evt.target||evt.srcElement,"mouse"); -}; -var doc=dojo.isIE?_12.document.documentElement:_12.document; -if(doc){ -if(dojo.isIE){ -doc.attachEvent("onmousedown",_14); -var _15=function(evt){ -if(evt.srcElement.tagName.toLowerCase()!="#document"&&dijit.isTabNavigable(evt.srcElement)){ -dijit._onFocusNode(_13||evt.srcElement); -}else{ -dijit._onTouchNode(_13||evt.srcElement); -} -}; -doc.attachEvent("onactivate",_15); -var _16=function(evt){ -dijit._onBlurNode(_13||evt.srcElement); -}; -doc.attachEvent("ondeactivate",_16); -return function(){ -doc.detachEvent("onmousedown",_14); -doc.detachEvent("onactivate",_15); -doc.detachEvent("ondeactivate",_16); -doc=null; -}; -}else{ -doc.addEventListener("mousedown",_14,true); -var _17=function(evt){ -dijit._onFocusNode(_13||evt.target); -}; -doc.addEventListener("focus",_17,true); -var _18=function(evt){ -dijit._onBlurNode(_13||evt.target); -}; -doc.addEventListener("blur",_18,true); -return function(){ -doc.removeEventListener("mousedown",_14,true); -doc.removeEventListener("focus",_17,true); -doc.removeEventListener("blur",_18,true); -doc=null; -}; -} -} -},unregisterWin:function(_19){ -_19&&_19(); -},_onBlurNode:function(_1a){ -dijit._prevFocus=dijit._curFocus; -dijit._curFocus=null; -if(dijit._justMouseDowned){ -return; -} -if(dijit._clearActiveWidgetsTimer){ -clearTimeout(dijit._clearActiveWidgetsTimer); -} -dijit._clearActiveWidgetsTimer=setTimeout(function(){ -delete dijit._clearActiveWidgetsTimer; -dijit._setStack([]); -dijit._prevFocus=null; -},100); -},_onTouchNode:function(_1b,by){ -if(dijit._clearActiveWidgetsTimer){ -clearTimeout(dijit._clearActiveWidgetsTimer); -delete dijit._clearActiveWidgetsTimer; -} -var _1c=[]; -try{ -while(_1b){ -var _1d=dojo.attr(_1b,"dijitPopupParent"); -if(_1d){ -_1b=dijit.byId(_1d).domNode; -}else{ -if(_1b.tagName&&_1b.tagName.toLowerCase()=="body"){ -if(_1b===dojo.body()){ -break; -} -_1b=dojo.window.get(_1b.ownerDocument).frameElement; -}else{ -var id=_1b.getAttribute&&_1b.getAttribute("widgetId"),_1e=id&&dijit.byId(id); -if(_1e&&!(by=="mouse"&&_1e.get("disabled"))){ -_1c.unshift(id); -} -_1b=_1b.parentNode; -} -} -} -} -catch(e){ -} -dijit._setStack(_1c,by); -},_onFocusNode:function(_1f){ -if(!_1f){ -return; -} -if(_1f.nodeType==9){ -return; -} -dijit._onTouchNode(_1f); -if(_1f==dijit._curFocus){ -return; -} -if(dijit._curFocus){ -dijit._prevFocus=dijit._curFocus; -} -dijit._curFocus=_1f; -dojo.publish("focusNode",[_1f]); -},_setStack:function(_20,by){ -var _21=dijit._activeStack; -dijit._activeStack=_20; -for(var _22=0;_22<Math.min(_21.length,_20.length);_22++){ -if(_21[_22]!=_20[_22]){ -break; -} -} -var _23; -for(var i=_21.length-1;i>=_22;i--){ -_23=dijit.byId(_21[i]); -if(_23){ -_23._focused=false; -_23._hasBeenBlurred=true; -if(_23._onBlur){ -_23._onBlur(by); -} -dojo.publish("widgetBlur",[_23,by]); -} -} -for(i=_22;i<_20.length;i++){ -_23=dijit.byId(_20[i]); -if(_23){ -_23._focused=true; -if(_23._onFocus){ -_23._onFocus(by); -} -dojo.publish("widgetFocus",[_23,by]); -} -} -}}); + +// register top window and all the iframes it contains dojo.addOnLoad(function(){ -var _24=dijit.registerWin(window); -if(dojo.isIE){ -dojo.addOnWindowUnload(function(){ -dijit.unregisterWin(_24); -_24=null; -}); -} + var handle = dijit.registerWin(window); + if(dojo.isIE){ + dojo.addOnWindowUnload(function(){ + dijit.unregisterWin(handle); + handle = null; + }) + } }); + } diff --git a/lib/dijit/_base/manager.js b/lib/dijit/_base/manager.js index d8d1cf6d5..e5f745622 100644 --- a/lib/dijit/_base/manager.js +++ b/lib/dijit/_base/manager.js @@ -1,245 +1,493 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.manager"]){ -dojo._hasResource["dijit._base.manager"]=true; +if(!dojo._hasResource["dijit._base.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.manager"] = true; dojo.provide("dijit._base.manager"); -dojo.declare("dijit.WidgetSet",null,{constructor:function(){ -this._hash={}; -this.length=0; -},add:function(_1){ -if(this._hash[_1.id]){ -throw new Error("Tried to register widget with id=="+_1.id+" but that id is already registered"); -} -this._hash[_1.id]=_1; -this.length++; -},remove:function(id){ -if(this._hash[id]){ -delete this._hash[id]; -this.length--; -} -},forEach:function(_2,_3){ -_3=_3||dojo.global; -var i=0,id; -for(id in this._hash){ -_2.call(_3,this._hash[id],i++,this._hash); -} -return this; -},filter:function(_4,_5){ -_5=_5||dojo.global; -var _6=new dijit.WidgetSet(),i=0,id; -for(id in this._hash){ -var w=this._hash[id]; -if(_4.call(_5,w,i++,this._hash)){ -_6.add(w); -} -} -return _6; -},byId:function(id){ -return this._hash[id]; -},byClass:function(_7){ -var _8=new dijit.WidgetSet(),id,_9; -for(id in this._hash){ -_9=this._hash[id]; -if(_9.declaredClass==_7){ -_8.add(_9); -} -} -return _8; -},toArray:function(){ -var ar=[]; -for(var id in this._hash){ -ar.push(this._hash[id]); -} -return ar; -},map:function(_a,_b){ -return dojo.map(this.toArray(),_a,_b); -},every:function(_c,_d){ -_d=_d||dojo.global; -var x=0,i; -for(i in this._hash){ -if(!_c.call(_d,this._hash[i],x++,this._hash)){ -return false; -} -} -return true; -},some:function(_e,_f){ -_f=_f||dojo.global; -var x=0,i; -for(i in this._hash){ -if(_e.call(_f,this._hash[i],x++,this._hash)){ -return true; -} -} -return false; -}}); -(function(){ -dijit.registry=new dijit.WidgetSet(); -var _10=dijit.registry._hash,_11=dojo.attr,_12=dojo.hasAttr,_13=dojo.style; -dijit.byId=function(id){ -return typeof id=="string"?_10[id]:id; -}; -var _14={}; -dijit.getUniqueId=function(_15){ -var id; -do{ -id=_15+"_"+(_15 in _14?++_14[_15]:_14[_15]=0); -}while(_10[id]); -return dijit._scopeName=="dijit"?id:dijit._scopeName+"_"+id; -}; -dijit.findWidgets=function(_16){ -var _17=[]; -function _18(_19){ -for(var _1a=_19.firstChild;_1a;_1a=_1a.nextSibling){ -if(_1a.nodeType==1){ -var _1b=_1a.getAttribute("widgetId"); -if(_1b){ -_17.push(_10[_1b]); -}else{ -_18(_1a); -} -} -} -}; -_18(_16); -return _17; -}; -dijit._destroyAll=function(){ -dijit._curFocus=null; -dijit._prevFocus=null; -dijit._activeStack=[]; -dojo.forEach(dijit.findWidgets(dojo.body()),function(_1c){ -if(!_1c._destroyed){ -if(_1c.destroyRecursive){ -_1c.destroyRecursive(); -}else{ -if(_1c.destroy){ -_1c.destroy(); -} -} -} -}); -}; -if(dojo.isIE){ -dojo.addOnWindowUnload(function(){ -dijit._destroyAll(); -}); -} -dijit.byNode=function(_1d){ -return _10[_1d.getAttribute("widgetId")]; -}; -dijit.getEnclosingWidget=function(_1e){ -while(_1e){ -var id=_1e.getAttribute&&_1e.getAttribute("widgetId"); -if(id){ -return _10[id]; -} -_1e=_1e.parentNode; -} -return null; -}; -var _1f=(dijit._isElementShown=function(_20){ -var s=_13(_20); -return (s.visibility!="hidden")&&(s.visibility!="collapsed")&&(s.display!="none")&&(_11(_20,"type")!="hidden"); -}); -dijit.hasDefaultTabStop=function(_21){ -switch(_21.nodeName.toLowerCase()){ -case "a": -return _12(_21,"href"); -case "area": -case "button": -case "input": -case "object": -case "select": -case "textarea": -return true; -case "iframe": -if(dojo.isMoz){ -try{ -return _21.contentDocument.designMode=="on"; -} -catch(err){ -return false; -} -}else{ -if(dojo.isWebKit){ -var doc=_21.contentDocument,_22=doc&&doc.body; -return _22&&_22.contentEditable=="true"; -}else{ -try{ -doc=_21.contentWindow.document; -_22=doc&&doc.body; -return _22&&_22.firstChild&&_22.firstChild.contentEditable=="true"; -} -catch(e){ -return false; -} -} -} -default: -return _21.contentEditable=="true"; -} -}; -var _23=(dijit.isTabNavigable=function(_24){ -if(_11(_24,"disabled")){ -return false; -}else{ -if(_12(_24,"tabIndex")){ -return _11(_24,"tabIndex")>=0; -}else{ -return dijit.hasDefaultTabStop(_24); -} -} -}); -dijit._getTabNavigable=function(_25){ -var _26,_27,_28,_29,_2a,_2b; -var _2c=function(_2d){ -dojo.query("> *",_2d).forEach(function(_2e){ -if((dojo.isIE&&_2e.scopeName!=="HTML")||!_1f(_2e)){ -return; -} -if(_23(_2e)){ -var _2f=_11(_2e,"tabIndex"); -if(!_12(_2e,"tabIndex")||_2f==0){ -if(!_26){ -_26=_2e; -} -_27=_2e; -}else{ -if(_2f>0){ -if(!_28||_2f<_29){ -_29=_2f; -_28=_2e; -} -if(!_2a||_2f>=_2b){ -_2b=_2f; -_2a=_2e; -} -} -} -} -if(_2e.nodeName.toUpperCase()!="SELECT"){ -_2c(_2e); -} + + +dojo.declare("dijit.WidgetSet", null, { + // summary: + // A set of widgets indexed by id. A default instance of this class is + // available as `dijit.registry` + // + // example: + // Create a small list of widgets: + // | var ws = new dijit.WidgetSet(); + // | ws.add(dijit.byId("one")); + // | ws.add(dijit.byId("two")); + // | // destroy both: + // | ws.forEach(function(w){ w.destroy(); }); + // + // example: + // Using dijit.registry: + // | dijit.registry.forEach(function(w){ /* do something */ }); + + constructor: function(){ + this._hash = {}; + this.length = 0; + }, + + add: function(/*dijit._Widget*/ widget){ + // summary: + // Add a widget to this list. If a duplicate ID is detected, a error is thrown. + // + // widget: dijit._Widget + // Any dijit._Widget subclass. + if(this._hash[widget.id]){ + throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered"); + } + this._hash[widget.id] = widget; + this.length++; + }, + + remove: function(/*String*/ id){ + // summary: + // Remove a widget from this WidgetSet. Does not destroy the widget; simply + // removes the reference. + if(this._hash[id]){ + delete this._hash[id]; + this.length--; + } + }, + + forEach: function(/*Function*/ func, /* Object? */thisObj){ + // summary: + // Call specified function for each widget in this set. + // + // func: + // A callback function to run for each item. Is passed the widget, the index + // in the iteration, and the full hash, similar to `dojo.forEach`. + // + // thisObj: + // An optional scope parameter + // + // example: + // Using the default `dijit.registry` instance: + // | dijit.registry.forEach(function(widget){ + // | console.log(widget.declaredClass); + // | }); + // + // returns: + // Returns self, in order to allow for further chaining. + + thisObj = thisObj || dojo.global; + var i = 0, id; + for(id in this._hash){ + func.call(thisObj, this._hash[id], i++, this._hash); + } + return this; // dijit.WidgetSet + }, + + filter: function(/*Function*/ filter, /* Object? */thisObj){ + // summary: + // Filter down this WidgetSet to a smaller new WidgetSet + // Works the same as `dojo.filter` and `dojo.NodeList.filter` + // + // filter: + // Callback function to test truthiness. Is passed the widget + // reference and the pseudo-index in the object. + // + // thisObj: Object? + // Option scope to use for the filter function. + // + // example: + // Arbitrary: select the odd widgets in this list + // | dijit.registry.filter(function(w, i){ + // | return i % 2 == 0; + // | }).forEach(function(w){ /* odd ones */ }); + + thisObj = thisObj || dojo.global; + var res = new dijit.WidgetSet(), i = 0, id; + for(id in this._hash){ + var w = this._hash[id]; + if(filter.call(thisObj, w, i++, this._hash)){ + res.add(w); + } + } + return res; // dijit.WidgetSet + }, + + byId: function(/*String*/ id){ + // summary: + // Find a widget in this list by it's id. + // example: + // Test if an id is in a particular WidgetSet + // | var ws = new dijit.WidgetSet(); + // | ws.add(dijit.byId("bar")); + // | var t = ws.byId("bar") // returns a widget + // | var x = ws.byId("foo"); // returns undefined + + return this._hash[id]; // dijit._Widget + }, + + byClass: function(/*String*/ cls){ + // summary: + // Reduce this widgetset to a new WidgetSet of a particular `declaredClass` + // + // cls: String + // The Class to scan for. Full dot-notated string. + // + // example: + // Find all `dijit.TitlePane`s in a page: + // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); }); + + var res = new dijit.WidgetSet(), id, widget; + for(id in this._hash){ + widget = this._hash[id]; + if(widget.declaredClass == cls){ + res.add(widget); + } + } + return res; // dijit.WidgetSet +}, + + toArray: function(){ + // summary: + // Convert this WidgetSet into a true Array + // + // example: + // Work with the widget .domNodes in a real Array + // | dojo.map(dijit.registry.toArray(), function(w){ return w.domNode; }); + + var ar = []; + for(var id in this._hash){ + ar.push(this._hash[id]); + } + return ar; // dijit._Widget[] +}, + + map: function(/* Function */func, /* Object? */thisObj){ + // summary: + // Create a new Array from this WidgetSet, following the same rules as `dojo.map` + // example: + // | var nodes = dijit.registry.map(function(w){ return w.domNode; }); + // + // returns: + // A new array of the returned values. + return dojo.map(this.toArray(), func, thisObj); // Array + }, + + every: function(func, thisObj){ + // summary: + // A synthetic clone of `dojo.every` acting explicitly on this WidgetSet + // + // func: Function + // A callback function run for every widget in this list. Exits loop + // when the first false return is encountered. + // + // thisObj: Object? + // Optional scope parameter to use for the callback + + thisObj = thisObj || dojo.global; + var x = 0, i; + for(i in this._hash){ + if(!func.call(thisObj, this._hash[i], x++, this._hash)){ + return false; // Boolean + } + } + return true; // Boolean + }, + + some: function(func, thisObj){ + // summary: + // A synthetic clone of `dojo.some` acting explictly on this WidgetSet + // + // func: Function + // A callback function run for every widget in this list. Exits loop + // when the first true return is encountered. + // + // thisObj: Object? + // Optional scope parameter to use for the callback + + thisObj = thisObj || dojo.global; + var x = 0, i; + for(i in this._hash){ + if(func.call(thisObj, this._hash[i], x++, this._hash)){ + return true; // Boolean + } + } + return false; // Boolean + } + }); -}; -if(_1f(_25)){ -_2c(_25); -} -return {first:_26,last:_27,lowest:_28,highest:_2a}; -}; -dijit.getFirstInTabbingOrder=function(_30){ -var _31=dijit._getTabNavigable(dojo.byId(_30)); -return _31.lowest?_31.lowest:_31.first; -}; -dijit.getLastInTabbingOrder=function(_32){ -var _33=dijit._getTabNavigable(dojo.byId(_32)); -return _33.last?_33.last:_33.highest; -}; -dijit.defaultDuration=dojo.config["defaultDuration"]||200; + +(function(){ + + /*===== + dijit.registry = { + // summary: + // A list of widgets on a page. + // description: + // Is an instance of `dijit.WidgetSet` + }; + =====*/ + dijit.registry = new dijit.WidgetSet(); + + var hash = dijit.registry._hash, + attr = dojo.attr, + hasAttr = dojo.hasAttr, + style = dojo.style; + + dijit.byId = function(/*String|dijit._Widget*/ id){ + // summary: + // Returns a widget by it's id, or if passed a widget, no-op (like dojo.byId()) + return typeof id == "string" ? hash[id] : id; // dijit._Widget + }; + + var _widgetTypeCtr = {}; + dijit.getUniqueId = function(/*String*/widgetType){ + // summary: + // Generates a unique id for a given widgetType + + var id; + do{ + id = widgetType + "_" + + (widgetType in _widgetTypeCtr ? + ++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0); + }while(hash[id]); + return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String + }; + + dijit.findWidgets = function(/*DomNode*/ root){ + // summary: + // Search subtree under root returning widgets found. + // Doesn't search for nested widgets (ie, widgets inside other widgets). + + var outAry = []; + + function getChildrenHelper(root){ + for(var node = root.firstChild; node; node = node.nextSibling){ + if(node.nodeType == 1){ + var widgetId = node.getAttribute("widgetId"); + if(widgetId){ + var widget = hash[widgetId]; + if(widget){ // may be null on page w/multiple dojo's loaded + outAry.push(widget); + } + }else{ + getChildrenHelper(node); + } + } + } + } + + getChildrenHelper(root); + return outAry; + }; + + dijit._destroyAll = function(){ + // summary: + // Code to destroy all widgets and do other cleanup on page unload + + // Clean up focus manager lingering references to widgets and nodes + dijit._curFocus = null; + dijit._prevFocus = null; + dijit._activeStack = []; + + // Destroy all the widgets, top down + dojo.forEach(dijit.findWidgets(dojo.body()), function(widget){ + // Avoid double destroy of widgets like Menu that are attached to <body> + // even though they are logically children of other widgets. + if(!widget._destroyed){ + if(widget.destroyRecursive){ + widget.destroyRecursive(); + }else if(widget.destroy){ + widget.destroy(); + } + } + }); + }; + + if(dojo.isIE){ + // Only run _destroyAll() for IE because we think it's only necessary in that case, + // and because it causes problems on FF. See bug #3531 for details. + dojo.addOnWindowUnload(function(){ + dijit._destroyAll(); + }); + } + + dijit.byNode = function(/*DOMNode*/ node){ + // summary: + // Returns the widget corresponding to the given DOMNode + return hash[node.getAttribute("widgetId")]; // dijit._Widget + }; + + dijit.getEnclosingWidget = function(/*DOMNode*/ node){ + // summary: + // Returns the widget whose DOM tree contains the specified DOMNode, or null if + // the node is not contained within the DOM tree of any widget + while(node){ + var id = node.getAttribute && node.getAttribute("widgetId"); + if(id){ + return hash[id]; + } + node = node.parentNode; + } + return null; + }; + + var shown = (dijit._isElementShown = function(/*Element*/ elem){ + var s = style(elem); + return (s.visibility != "hidden") + && (s.visibility != "collapsed") + && (s.display != "none") + && (attr(elem, "type") != "hidden"); + }); + + dijit.hasDefaultTabStop = function(/*Element*/ elem){ + // summary: + // Tests if element is tab-navigable even without an explicit tabIndex setting + + // No explicit tabIndex setting, need to investigate node type + switch(elem.nodeName.toLowerCase()){ + case "a": + // An <a> w/out a tabindex is only navigable if it has an href + return hasAttr(elem, "href"); + case "area": + case "button": + case "input": + case "object": + case "select": + case "textarea": + // These are navigable by default + return true; + case "iframe": + // If it's an editor <iframe> then it's tab navigable. + var body; + try{ + // non-IE + var contentDocument = elem.contentDocument; + if("designMode" in contentDocument && contentDocument.designMode == "on"){ + return true; + } + body = contentDocument.body; + }catch(e1){ + // contentWindow.document isn't accessible within IE7/8 + // if the iframe.src points to a foreign url and this + // page contains an element, that could get focus + try{ + body = elem.contentWindow.document.body; + }catch(e2){ + return false; + } + } + return body.contentEditable == 'true' || (body.firstChild && body.firstChild.contentEditable == 'true'); + default: + return elem.contentEditable == 'true'; + } + }; + + var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){ + // summary: + // Tests if an element is tab-navigable + + // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable() + if(attr(elem, "disabled")){ + return false; + }else if(hasAttr(elem, "tabIndex")){ + // Explicit tab index setting + return attr(elem, "tabIndex") >= 0; // boolean + }else{ + // No explicit tabIndex setting, so depends on node type + return dijit.hasDefaultTabStop(elem); + } + }); + + dijit._getTabNavigable = function(/*DOMNode*/ root){ + // summary: + // Finds descendants of the specified root node. + // + // description: + // Finds the following descendants of the specified root node: + // * the first tab-navigable element in document order + // without a tabIndex or with tabIndex="0" + // * the last tab-navigable element in document order + // without a tabIndex or with tabIndex="0" + // * the first element in document order with the lowest + // positive tabIndex value + // * the last element in document order with the highest + // positive tabIndex value + var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {}; + function radioName(node) { + // If this element is part of a radio button group, return the name for that group. + return node && node.tagName.toLowerCase() == "input" && + node.type && node.type.toLowerCase() == "radio" && + node.name && node.name.toLowerCase(); + } + var walkTree = function(/*DOMNode*/parent){ + dojo.query("> *", parent).forEach(function(child){ + // Skip hidden elements, and also non-HTML elements (those in custom namespaces) in IE, + // since show() invokes getAttribute("type"), which crash on VML nodes in IE. + if((dojo.isIE && child.scopeName!=="HTML") || !shown(child)){ + return; + } + + if(isTabNavigable(child)){ + var tabindex = attr(child, "tabIndex"); + if(!hasAttr(child, "tabIndex") || tabindex == 0){ + if(!first){ first = child; } + last = child; + }else if(tabindex > 0){ + if(!lowest || tabindex < lowestTabindex){ + lowestTabindex = tabindex; + lowest = child; + } + if(!highest || tabindex >= highestTabindex){ + highestTabindex = tabindex; + highest = child; + } + } + var rn = radioName(child); + if(dojo.attr(child, "checked") && rn) { + radioSelected[rn] = child; + } + } + if(child.nodeName.toUpperCase() != 'SELECT'){ + walkTree(child); + } + }); + }; + if(shown(root)){ walkTree(root) } + function rs(node) { + // substitute checked radio button for unchecked one, if there is a checked one with the same name. + return radioSelected[radioName(node)] || node; + } + return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) }; + } + dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){ + // summary: + // Finds the descendant of the specified root node + // that is first in the tabbing order + var elems = dijit._getTabNavigable(dojo.byId(root)); + return elems.lowest ? elems.lowest : elems.first; // DomNode + }; + + dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root){ + // summary: + // Finds the descendant of the specified root node + // that is last in the tabbing order + var elems = dijit._getTabNavigable(dojo.byId(root)); + return elems.last ? elems.last : elems.highest; // DomNode + }; + + /*===== + dojo.mixin(dijit, { + // defaultDuration: Integer + // The default animation speed (in ms) to use for all Dijit + // transitional animations, unless otherwise specified + // on a per-instance basis. Defaults to 200, overrided by + // `djConfig.defaultDuration` + defaultDuration: 200 + }); + =====*/ + + dijit.defaultDuration = dojo.config["defaultDuration"] || 200; + })(); + } diff --git a/lib/dijit/_base/place.js b/lib/dijit/_base/place.js index ddc38fd08..a098f2fa4 100644 --- a/lib/dijit/_base/place.js +++ b/lib/dijit/_base/place.js @@ -1,111 +1,376 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.place"]){ -dojo._hasResource["dijit._base.place"]=true; +if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.place"] = true; dojo.provide("dijit._base.place"); dojo.require("dojo.window"); dojo.require("dojo.AdapterRegistry"); -dijit.getViewport=function(){ -return dojo.window.getBox(); -}; -dijit.placeOnScreen=function(_1,_2,_3,_4){ -var _5=dojo.map(_3,function(_6){ -var c={corner:_6,pos:{x:_2.x,y:_2.y}}; -if(_4){ -c.pos.x+=_6.charAt(1)=="L"?_4.x:-_4.x; -c.pos.y+=_6.charAt(0)=="T"?_4.y:-_4.y; -} -return c; -}); -return dijit._place(_1,_5); + + +dijit.getViewport = function(){ + // summary: + // Returns the dimensions and scroll position of the viewable area of a browser window + + return dojo.window.getBox(); }; -dijit._place=function(_7,_8,_9){ -var _a=dojo.window.getBox(); -if(!_7.parentNode||String(_7.parentNode.tagName).toLowerCase()!="body"){ -dojo.body().appendChild(_7); -} -var _b=null; -dojo.some(_8,function(_c){ -var _d=_c.corner; -var _e=_c.pos; -if(_9){ -_9(_7,_c.aroundCorner,_d); + +/*===== +dijit.__Position = function(){ + // x: Integer + // horizontal coordinate in pixels, relative to document body + // y: Integer + // vertical coordinate in pixels, relative to document body + + thix.x = x; + this.y = y; } -var _f=_7.style; -var _10=_f.display; -var _11=_f.visibility; -_f.visibility="hidden"; -_f.display=""; -var mb=dojo.marginBox(_7); -_f.display=_10; -_f.visibility=_11; -var _12=Math.max(_a.l,_d.charAt(1)=="L"?_e.x:(_e.x-mb.w)),_13=Math.max(_a.t,_d.charAt(0)=="T"?_e.y:(_e.y-mb.h)),_14=Math.min(_a.l+_a.w,_d.charAt(1)=="L"?(_12+mb.w):_e.x),_15=Math.min(_a.t+_a.h,_d.charAt(0)=="T"?(_13+mb.h):_e.y),_16=_14-_12,_17=_15-_13,_18=(mb.w-_16)+(mb.h-_17); -if(_b==null||_18<_b.overflow){ -_b={corner:_d,aroundCorner:_c.aroundCorner,x:_12,y:_13,w:_16,h:_17,overflow:_18}; +=====*/ + + +dijit.placeOnScreen = function( + /* DomNode */ node, + /* dijit.__Position */ pos, + /* String[] */ corners, + /* dijit.__Position? */ padding){ + // summary: + // Positions one of the node's corners at specified position + // such that node is fully visible in viewport. + // description: + // NOTE: node is assumed to be absolutely or relatively positioned. + // pos: + // Object like {x: 10, y: 20} + // corners: + // Array of Strings representing order to try corners in, like ["TR", "BL"]. + // Possible values are: + // * "BL" - bottom left + // * "BR" - bottom right + // * "TL" - top left + // * "TR" - top right + // padding: + // set padding to put some buffer around the element you want to position. + // example: + // Try to place node's top right corner at (10,20). + // If that makes node go (partially) off screen, then try placing + // bottom left corner at (10,20). + // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"]) + + var choices = dojo.map(corners, function(corner){ + var c = { corner: corner, pos: {x:pos.x,y:pos.y} }; + if(padding){ + c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x; + c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y; + } + return c; + }); + + return dijit._place(node, choices); } -return !_18; -}); -_7.style.left=_b.x+"px"; -_7.style.top=_b.y+"px"; -if(_b.overflow&&_9){ -_9(_7,_b.aroundCorner,_b.corner); + +dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ aroundNodeCoords){ + // summary: + // Given a list of spots to put node, put it at the first spot where it fits, + // of if it doesn't fit anywhere then the place with the least overflow + // choices: Array + // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} } + // Above example says to put the top-left corner of the node at (10,20) + // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size) + // for things like tooltip, they are displayed differently (and have different dimensions) + // based on their orientation relative to the parent. This adjusts the popup based on orientation. + // It also passes in the available size for the popup, which is useful for tooltips to + // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing + // how much the popup had to be modified to fit into the available space. This is used to determine + // what the best placement is. + // aroundNodeCoords: Object + // Size of aroundNode, ex: {w: 200, h: 50} + + // get {x: 10, y: 10, w: 100, h:100} type obj representing position of + // viewport over document + var view = dojo.window.getBox(); + + // This won't work if the node is inside a <div style="position: relative">, + // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong + // and also it might get cutoff) + if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ + dojo.body().appendChild(node); + } + + var best = null; + dojo.some(choices, function(choice){ + var corner = choice.corner; + var pos = choice.pos; + var overflow = 0; + + // calculate amount of space available given specified position of node + var spaceAvailable = { + w: corner.charAt(1) == 'L' ? (view.l + view.w) - pos.x : pos.x - view.l, + h: corner.charAt(1) == 'T' ? (view.t + view.h) - pos.y : pos.y - view.t + }; + + // configure node to be displayed in given position relative to button + // (need to do this in order to get an accurate size for the node, because + // a tooltip's size changes based on position, due to triangle) + if(layoutNode){ + var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords); + overflow = typeof res == "undefined" ? 0 : res; + } + + // get node's size + var style = node.style; + var oldDisplay = style.display; + var oldVis = style.visibility; + style.visibility = "hidden"; + style.display = ""; + var mb = dojo.marginBox(node); + style.display = oldDisplay; + style.visibility = oldVis; + + // coordinates and size of node with specified corner placed at pos, + // and clipped by viewport + var startX = Math.max(view.l, corner.charAt(1) == 'L' ? pos.x : (pos.x - mb.w)), + startY = Math.max(view.t, corner.charAt(0) == 'T' ? pos.y : (pos.y - mb.h)), + endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x), + endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y), + width = endX - startX, + height = endY - startY; + + overflow += (mb.w - width) + (mb.h - height); + + if(best == null || overflow < best.overflow){ + best = { + corner: corner, + aroundCorner: choice.aroundCorner, + x: startX, + y: startY, + w: width, + h: height, + overflow: overflow, + spaceAvailable: spaceAvailable + }; + } + + return !overflow; + }); + + // In case the best position is not the last one we checked, need to call + // layoutNode() again. + if(best.overflow && layoutNode){ + layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords); + } + + // And then position the node. Do this last, after the layoutNode() above + // has sized the node, due to browser quirks when the viewport is scrolled + // (specifically that a Tooltip will shrink to fit as though the window was + // scrolled to the left). + // + // In RTL mode, set style.right rather than style.left so in the common case, + // window resizes move the popup along with the aroundNode. + var l = dojo._isBodyLtr(), + s = node.style; + s.top = best.y + "px"; + s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px"; + + return best; } -return _b; -}; -dijit.placeOnScreenAroundNode=function(_19,_1a,_1b,_1c){ -_1a=dojo.byId(_1a); -var _1d=_1a.style.display; -_1a.style.display=""; -var _1e=dojo.position(_1a,true); -_1a.style.display=_1d; -return dijit._placeOnScreenAroundRect(_19,_1e.x,_1e.y,_1e.w,_1e.h,_1b,_1c); -}; -dijit.placeOnScreenAroundRectangle=function(_1f,_20,_21,_22){ -return dijit._placeOnScreenAroundRect(_1f,_20.x,_20.y,_20.width,_20.height,_21,_22); + +dijit.placeOnScreenAroundNode = function( + /* DomNode */ node, + /* DomNode */ aroundNode, + /* Object */ aroundCorners, + /* Function? */ layoutNode){ + + // summary: + // Position node adjacent or kitty-corner to aroundNode + // such that it's fully visible in viewport. + // + // description: + // Place node such that corner of node touches a corner of + // aroundNode, and that node is fully visible. + // + // aroundCorners: + // Ordered list of pairs of corners to try matching up. + // Each pair of corners is represented as a key/value in the hash, + // where the key corresponds to the aroundNode's corner, and + // the value corresponds to the node's corner: + // + // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...} + // + // The following strings are used to represent the four corners: + // * "BL" - bottom left + // * "BR" - bottom right + // * "TL" - top left + // * "TR" - top right + // + // layoutNode: Function(node, aroundNodeCorner, nodeCorner) + // For things like tooltip, they are displayed differently (and have different dimensions) + // based on their orientation relative to the parent. This adjusts the popup based on orientation. + // + // example: + // | dijit.placeOnScreenAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'}); + // This will try to position node such that node's top-left corner is at the same position + // as the bottom left corner of the aroundNode (ie, put node below + // aroundNode, with left edges aligned). If that fails it will try to put + // the bottom-right corner of node where the top right corner of aroundNode is + // (ie, put node above aroundNode, with right edges aligned) + // + + // get coordinates of aroundNode + aroundNode = dojo.byId(aroundNode); + var aroundNodePos = dojo.position(aroundNode, true); + + // place the node around the calculated rectangle + return dijit._placeOnScreenAroundRect(node, + aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle + aroundCorners, layoutNode); }; -dijit._placeOnScreenAroundRect=function(_23,x,y,_24,_25,_26,_27){ -var _28=[]; -for(var _29 in _26){ -_28.push({aroundCorner:_29,corner:_26[_29],pos:{x:x+(_29.charAt(1)=="L"?0:_24),y:y+(_29.charAt(0)=="T"?0:_25)}}); + +/*===== +dijit.__Rectangle = function(){ + // x: Integer + // horizontal offset in pixels, relative to document body + // y: Integer + // vertical offset in pixels, relative to document body + // width: Integer + // width in pixels + // height: Integer + // height in pixels + + this.x = x; + this.y = y; + this.width = width; + this.height = height; } -return dijit._place(_23,_28,_27); +=====*/ + + +dijit.placeOnScreenAroundRectangle = function( + /* DomNode */ node, + /* dijit.__Rectangle */ aroundRect, + /* Object */ aroundCorners, + /* Function */ layoutNode){ + + // summary: + // Like dijit.placeOnScreenAroundNode(), except that the "around" + // parameter is an arbitrary rectangle on the screen (x, y, width, height) + // instead of a dom node. + + return dijit._placeOnScreenAroundRect(node, + aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle + aroundCorners, layoutNode); +}; + +dijit._placeOnScreenAroundRect = function( + /* DomNode */ node, + /* Number */ x, + /* Number */ y, + /* Number */ width, + /* Number */ height, + /* Object */ aroundCorners, + /* Function */ layoutNode){ + + // summary: + // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates + // of a rectangle to place node adjacent to. + + // TODO: combine with placeOnScreenAroundRectangle() + + // Generate list of possible positions for node + var choices = []; + for(var nodeCorner in aroundCorners){ + choices.push( { + aroundCorner: nodeCorner, + corner: aroundCorners[nodeCorner], + pos: { + x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width), + y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height) + } + }); + } + + return dijit._place(node, choices, layoutNode, {w: width, h: height}); }; -dijit.placementRegistry=new dojo.AdapterRegistry(); -dijit.placementRegistry.register("node",function(n,x){ -return typeof x=="object"&&typeof x.offsetWidth!="undefined"&&typeof x.offsetHeight!="undefined"; -},dijit.placeOnScreenAroundNode); -dijit.placementRegistry.register("rect",function(n,x){ -return typeof x=="object"&&"x" in x&&"y" in x&&"width" in x&&"height" in x; -},dijit.placeOnScreenAroundRectangle); -dijit.placeOnScreenAroundElement=function(_2a,_2b,_2c,_2d){ -return dijit.placementRegistry.match.apply(dijit.placementRegistry,arguments); + +dijit.placementRegistry= new dojo.AdapterRegistry(); +dijit.placementRegistry.register("node", + function(n, x){ + return typeof x == "object" && + typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined"; + }, + dijit.placeOnScreenAroundNode); +dijit.placementRegistry.register("rect", + function(n, x){ + return typeof x == "object" && + "x" in x && "y" in x && "width" in x && "height" in x; + }, + dijit.placeOnScreenAroundRectangle); + +dijit.placeOnScreenAroundElement = function( + /* DomNode */ node, + /* Object */ aroundElement, + /* Object */ aroundCorners, + /* Function */ layoutNode){ + + // summary: + // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object + // for the "around" argument and finds a proper processor to place a node. + + return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments); }; -dijit.getPopupAroundAlignment=function(_2e,_2f){ -var _30={}; -dojo.forEach(_2e,function(pos){ -switch(pos){ -case "after": -_30[_2f?"BR":"BL"]=_2f?"BL":"BR"; -break; -case "before": -_30[_2f?"BL":"BR"]=_2f?"BR":"BL"; -break; -case "below": -_30[_2f?"BL":"BR"]=_2f?"TL":"TR"; -_30[_2f?"BR":"BL"]=_2f?"TR":"TL"; -break; -case "above": -default: -_30[_2f?"TL":"TR"]=_2f?"BL":"BR"; -_30[_2f?"TR":"TL"]=_2f?"BR":"BL"; -break; -} -}); -return _30; + +dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){ + // summary: + // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement. + // + // position: String[] + // This variable controls the position of the drop down. + // It's an array of strings with the following values: + // + // * before: places drop down to the left of the target node/widget, or to the right in + // the case of RTL scripts like Hebrew and Arabic + // * after: places drop down to the right of the target node/widget, or to the left in + // the case of RTL scripts like Hebrew and Arabic + // * above: drop down goes above target node + // * below: drop down goes below target node + // + // The list is positions is tried, in order, until a position is found where the drop down fits + // within the viewport. + // + // leftToRight: Boolean + // Whether the popup will be displaying in leftToRight mode. + // + var align = {}; + dojo.forEach(position, function(pos){ + switch(pos){ + case "after": + align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR"; + break; + case "before": + align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL"; + break; + case "below-alt": + leftToRight = !leftToRight; + // fall through + case "below": + // first try to align left borders, next try to align right borders (or reverse for RTL mode) + align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR"; + align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL"; + break; + case "above-alt": + leftToRight = !leftToRight; + // fall through + case "above": + default: + // first try to align left borders, next try to align right borders (or reverse for RTL mode) + align[leftToRight ? "TL" : "TR"] = leftToRight ? "BL" : "BR"; + align[leftToRight ? "TR" : "TL"] = leftToRight ? "BR" : "BL"; + break; + } + }); + return align; }; + } diff --git a/lib/dijit/_base/popup.js b/lib/dijit/_base/popup.js index aed046489..e6fa6a5ed 100644 --- a/lib/dijit/_base/popup.js +++ b/lib/dijit/_base/popup.js @@ -1,158 +1,405 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.popup"]){ -dojo._hasResource["dijit._base.popup"]=true; +if(!dojo._hasResource["dijit._base.popup"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.popup"] = true; dojo.provide("dijit._base.popup"); dojo.require("dijit._base.focus"); dojo.require("dijit._base.place"); dojo.require("dijit._base.window"); -dijit.popup={_stack:[],_beginZIndex:1000,_idGen:1,moveOffScreen:function(_1){ -var _2=_1.parentNode; -if(!_2||!dojo.hasClass(_2,"dijitPopup")){ -_2=dojo.create("div",{"class":"dijitPopup",style:{visibility:"hidden",top:"-9999px"}},dojo.body()); -dijit.setWaiRole(_2,"presentation"); -_2.appendChild(_1); -} -var s=_1.style; -s.display=""; -s.visibility=""; -s.position=""; -s.top="0px"; -dojo.style(_2,{visibility:"hidden",top:"-9999px"}); -},getTopPopup:function(){ -var _3=this._stack; -for(var pi=_3.length-1;pi>0&&_3[pi].parent===_3[pi-1].widget;pi--){ -} -return _3[pi]; -},open:function(_4){ -var _5=this._stack,_6=_4.popup,_7=_4.orient||((_4.parent?_4.parent.isLeftToRight():dojo._isBodyLtr())?{"BL":"TL","BR":"TR","TL":"BL","TR":"BR"}:{"BR":"TR","BL":"TL","TR":"BR","TL":"BL"}),_8=_4.around,id=(_4.around&&_4.around.id)?(_4.around.id+"_dropdown"):("popup_"+this._idGen++); -var _9=_6.domNode.parentNode; -if(!_9||!dojo.hasClass(_9,"dijitPopup")){ -this.moveOffScreen(_6.domNode); -_9=_6.domNode.parentNode; -} -dojo.attr(_9,{id:id,style:{zIndex:this._beginZIndex+_5.length},"class":"dijitPopup "+(_6.baseClass||_6["class"]||"").split(" ")[0]+"Popup",dijitPopupParent:_4.parent?_4.parent.id:""}); -if(dojo.isIE||dojo.isMoz){ -var _a=_9.childNodes[1]; -if(!_a){ -_a=new dijit.BackgroundIframe(_9); -} -} -var _b=_8?dijit.placeOnScreenAroundElement(_9,_8,_7,_6.orient?dojo.hitch(_6,"orient"):null):dijit.placeOnScreen(_9,_4,_7=="R"?["TR","BR","TL","BL"]:["TL","BL","TR","BR"],_4.padding); -_9.style.visibility="visible"; -_6.domNode.style.visibility="visible"; -var _c=[]; -_c.push(dojo.connect(_9,"onkeypress",this,function(_d){ -if(_d.charOrCode==dojo.keys.ESCAPE&&_4.onCancel){ -dojo.stopEvent(_d); -_4.onCancel(); -}else{ -if(_d.charOrCode===dojo.keys.TAB){ -dojo.stopEvent(_d); -var _e=this.getTopPopup(); -if(_e&&_e.onCancel){ -_e.onCancel(); -} -} -} -})); -if(_6.onCancel){ -_c.push(dojo.connect(_6,"onCancel",_4.onCancel)); -} -_c.push(dojo.connect(_6,_6.onExecute?"onExecute":"onChange",this,function(){ -var _f=this.getTopPopup(); -if(_f&&_f.onExecute){ -_f.onExecute(); -} -})); -_5.push({wrapper:_9,iframe:_a,widget:_6,parent:_4.parent,onExecute:_4.onExecute,onCancel:_4.onCancel,onClose:_4.onClose,handlers:_c}); -if(_6.onOpen){ -_6.onOpen(_b); -} -return _b; -},close:function(_10){ -var _11=this._stack; -while(dojo.some(_11,function(_12){ -return _12.widget==_10; -})){ -var top=_11.pop(),_13=top.wrapper,_14=top.iframe,_15=top.widget,_16=top.onClose; -if(_15.onClose){ -_15.onClose(); -} -dojo.forEach(top.handlers,dojo.disconnect); -if(_15&&_15.domNode){ -this.moveOffScreen(_15.domNode); -}else{ -dojo.destroy(_13); -} -if(_16){ -_16(); -} -} -}}; -dijit._frames=new function(){ -var _17=[]; -this.pop=function(){ -var _18; -if(_17.length){ -_18=_17.pop(); -_18.style.display=""; -}else{ -if(dojo.isIE){ -var _19=dojo.config["dojoBlankHtmlUrl"]||(dojo.moduleUrl("dojo","resources/blank.html")+"")||"javascript:\"\""; -var _1a="<iframe src='"+_19+"'"+" style='position: absolute; left: 0px; top: 0px;"+"z-index: -1; filter:Alpha(Opacity=\"0\");'>"; -_18=dojo.doc.createElement(_1a); -}else{ -_18=dojo.create("iframe"); -_18.src="javascript:\"\""; -_18.className="dijitBackgroundIframe"; -dojo.style(_18,"opacity",0.1); -} -_18.tabIndex=-1; -dijit.setWaiRole(_18,"presentation"); + + +/*===== +dijit.popup.__OpenArgs = function(){ + // popup: Widget + // widget to display + // parent: Widget + // the button etc. that is displaying this popup + // around: DomNode + // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.) + // x: Integer + // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.) + // y: Integer + // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.) + // orient: Object|String + // When the around parameter is specified, orient should be an + // ordered list of tuples of the form (around-node-corner, popup-node-corner). + // dijit.popup.open() tries to position the popup according to each tuple in the list, in order, + // until the popup appears fully within the viewport. + // + // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples: + // 1. (BL, TL) + // 2. (TL, BL) + // where BL means "bottom left" and "TL" means "top left". + // So by default, it first tries putting the popup below the around node, left-aligning them, + // and then tries to put it above the around node, still left-aligning them. Note that the + // default is horizontally reversed when in RTL mode. + // + // When an (x,y) position is specified rather than an around node, orient is either + // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse, + // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't + // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner, + // and the top-right corner. + // onCancel: Function + // callback when user has canceled the popup by + // 1. hitting ESC or + // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog); + // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called + // onClose: Function + // callback whenever this popup is closed + // onExecute: Function + // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only) + // padding: dijit.__Position + // adding a buffer around the opening position. This is only useful when around is not set. + this.popup = popup; + this.parent = parent; + this.around = around; + this.x = x; + this.y = y; + this.orient = orient; + this.onCancel = onCancel; + this.onClose = onClose; + this.onExecute = onExecute; + this.padding = padding; } -return _18; -}; -this.push=function(_1b){ -_1b.style.display="none"; -_17.push(_1b); +=====*/ + +dijit.popup = { + // summary: + // This singleton is used to show/hide widgets as popups. + + // _stack: dijit._Widget[] + // Stack of currently popped up widgets. + // (someone opened _stack[0], and then it opened _stack[1], etc.) + _stack: [], + + // _beginZIndex: Number + // Z-index of the first popup. (If first popup opens other + // popups they get a higher z-index.) + _beginZIndex: 1000, + + _idGen: 1, + + _createWrapper: function(/*Widget || DomNode*/ widget){ + // summary: + // Initialization for widgets that will be used as popups. + // Puts widget inside a wrapper DIV (if not already in one), + // and returns pointer to that wrapper DIV. + + var wrapper = widget.declaredClass ? widget._popupWrapper : (widget.parentNode && dojo.hasClass(widget.parentNode, "dijitPopup")), + node = widget.domNode || widget; + + if(!wrapper){ + // Create wrapper <div> for when this widget [in the future] will be used as a popup. + // This is done early because of IE bugs where creating/moving DOM nodes causes focus + // to go wonky, see tests/robot/Toolbar.html to reproduce + wrapper = dojo.create("div",{ + "class":"dijitPopup", + style:{ display: "none"}, + role: "presentation" + }, dojo.body()); + wrapper.appendChild(node); + + var s = node.style; + s.display = ""; + s.visibility = ""; + s.position = ""; + s.top = "0px"; + + if(widget.declaredClass){ // TODO: in 2.0 change signature to always take widget, then remove if() + widget._popupWrapper = wrapper; + dojo.connect(widget, "destroy", function(){ + dojo.destroy(wrapper); + delete widget._popupWrapper; + }); + } + } + + return wrapper; + }, + + moveOffScreen: function(/*Widget || DomNode*/ widget){ + // summary: + // Moves the popup widget off-screen. + // Do not use this method to hide popups when not in use, because + // that will create an accessibility issue: the offscreen popup is + // still in the tabbing order. + + // Create wrapper if not already there + var wrapper = this._createWrapper(widget); + + dojo.style(wrapper, { + visibility: "hidden", + top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111) + display: "" + }); + }, + + hide: function(/*dijit._Widget*/ widget){ + // summary: + // Hide this popup widget (until it is ready to be shown). + // Initialization for widgets that will be used as popups + // + // Also puts widget inside a wrapper DIV (if not already in one) + // + // If popup widget needs to layout it should + // do so when it is made visible, and popup._onShow() is called. + + // Create wrapper if not already there + var wrapper = this._createWrapper(widget); + + dojo.style(wrapper, "display", "none"); + }, + + getTopPopup: function(){ + // summary: + // Compute the closest ancestor popup that's *not* a child of another popup. + // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button. + var stack = this._stack; + for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){ + /* do nothing, just trying to get right value for pi */ + } + return stack[pi]; + }, + + open: function(/*dijit.popup.__OpenArgs*/ args){ + // summary: + // Popup the widget at the specified position + // + // example: + // opening at the mouse position + // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); + // + // example: + // opening the widget as a dropdown + // | dijit.popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}}); + // + // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback + // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed. + + var stack = this._stack, + widget = args.popup, + orient = args.orient || ( + (args.parent ? args.parent.isLeftToRight() : dojo._isBodyLtr()) ? + {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'} : + {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'} + ), + around = args.around, + id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++); + + // If we are opening a new popup that isn't a child of a currently opened popup, then + // close currently opened popup(s). This should happen automatically when the old popups + // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198]. + while(stack.length && (!args.parent || !dojo.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){ + dijit.popup.close(stack[stack.length-1].widget); + } + + // Get pointer to popup wrapper, and create wrapper if it doesn't exist + var wrapper = this._createWrapper(widget); + + + dojo.attr(wrapper, { + id: id, + style: { + zIndex: this._beginZIndex + stack.length + }, + "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup", + dijitPopupParent: args.parent ? args.parent.id : "" + }); + + if(dojo.isIE || dojo.isMoz){ + if(!widget.bgIframe){ + // setting widget.bgIframe triggers cleanup in _Widget.destroy() + widget.bgIframe = new dijit.BackgroundIframe(wrapper); + } + } + + // position the wrapper node and make it visible + var best = around ? + dijit.placeOnScreenAroundElement(wrapper, around, orient, widget.orient ? dojo.hitch(widget, "orient") : null) : + dijit.placeOnScreen(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding); + + wrapper.style.display = ""; + wrapper.style.visibility = "visible"; + widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown + + var handlers = []; + + // provide default escape and tab key handling + // (this will work for any widget, not just menu) + handlers.push(dojo.connect(wrapper, "onkeypress", this, function(evt){ + if(evt.charOrCode == dojo.keys.ESCAPE && args.onCancel){ + dojo.stopEvent(evt); + args.onCancel(); + }else if(evt.charOrCode === dojo.keys.TAB){ + dojo.stopEvent(evt); + var topPopup = this.getTopPopup(); + if(topPopup && topPopup.onCancel){ + topPopup.onCancel(); + } + } + })); + + // watch for cancel/execute events on the popup and notify the caller + // (for a menu, "execute" means clicking an item) + if(widget.onCancel){ + handlers.push(dojo.connect(widget, "onCancel", args.onCancel)); + } + + handlers.push(dojo.connect(widget, widget.onExecute ? "onExecute" : "onChange", this, function(){ + var topPopup = this.getTopPopup(); + if(topPopup && topPopup.onExecute){ + topPopup.onExecute(); + } + })); + + stack.push({ + widget: widget, + parent: args.parent, + onExecute: args.onExecute, + onCancel: args.onCancel, + onClose: args.onClose, + handlers: handlers + }); + + if(widget.onOpen){ + // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here) + widget.onOpen(best); + } + + return best; + }, + + close: function(/*dijit._Widget?*/ popup){ + // summary: + // Close specified popup and any popups that it parented. + // If no popup is specified, closes all popups. + + var stack = this._stack; + + // Basically work backwards from the top of the stack closing popups + // until we hit the specified popup, but IIRC there was some issue where closing + // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C] + // closing C might close B indirectly and then the while() condition will run where stack==[A]... + // so the while condition is constructed defensively. + while((popup && dojo.some(stack, function(elem){return elem.widget == popup;})) || + (!popup && stack.length)){ + var top = stack.pop(), + widget = top.widget, + onClose = top.onClose; + + if(widget.onClose){ + // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here) + widget.onClose(); + } + dojo.forEach(top.handlers, dojo.disconnect); + + // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc. + if(widget && widget.domNode){ + this.hide(widget); + } + + if(onClose){ + onClose(); + } + } + } }; + +// TODO: remove dijit._frames, it isn't being used much, since popups never release their +// iframes (see [22236]) +dijit._frames = new function(){ + // summary: + // cache of iframes + + var queue = []; + + this.pop = function(){ + var iframe; + if(queue.length){ + iframe = queue.pop(); + iframe.style.display=""; + }else{ + if(dojo.isIE < 9){ + var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\""; + var html="<iframe src='" + burl + "'" + + " style='position: absolute; left: 0px; top: 0px;" + + "z-index: -1; filter:Alpha(Opacity=\"0\");'>"; + iframe = dojo.doc.createElement(html); + }else{ + iframe = dojo.create("iframe"); + iframe.src = 'javascript:""'; + iframe.className = "dijitBackgroundIframe"; + dojo.style(iframe, "opacity", 0.1); + } + iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work. + dijit.setWaiRole(iframe,"presentation"); + } + return iframe; + }; + + this.push = function(iframe){ + iframe.style.display="none"; + queue.push(iframe); + } }(); -dijit.BackgroundIframe=function(_1c){ -if(!_1c.id){ -throw new Error("no id"); -} -if(dojo.isIE||dojo.isMoz){ -var _1d=dijit._frames.pop(); -_1c.appendChild(_1d); -if(dojo.isIE<7){ -this.resize(_1c); -this._conn=dojo.connect(_1c,"onresize",this,function(){ -this.resize(_1c); -}); -}else{ -dojo.style(_1d,{width:"100%",height:"100%"}); -} -this.iframe=_1d; -} + + +dijit.BackgroundIframe = function(/*DomNode*/ node){ + // summary: + // For IE/FF z-index schenanigans. id attribute is required. + // + // description: + // new dijit.BackgroundIframe(node) + // Makes a background iframe as a child of node, that fills + // area (and position) of node + + if(!node.id){ throw new Error("no id"); } + if(dojo.isIE || dojo.isMoz){ + var iframe = (this.iframe = dijit._frames.pop()); + node.appendChild(iframe); + if(dojo.isIE<7 || dojo.isQuirks){ + this.resize(node); + this._conn = dojo.connect(node, 'onresize', this, function(){ + this.resize(node); + }); + }else{ + dojo.style(iframe, { + width: '100%', + height: '100%' + }); + } + } }; -dojo.extend(dijit.BackgroundIframe,{resize:function(_1e){ -if(this.iframe&&dojo.isIE<7){ -dojo.style(this.iframe,{width:_1e.offsetWidth+"px",height:_1e.offsetHeight+"px"}); -} -},destroy:function(){ -if(this._conn){ -dojo.disconnect(this._conn); -this._conn=null; -} -if(this.iframe){ -dijit._frames.push(this.iframe); -delete this.iframe; -} -}}); + +dojo.extend(dijit.BackgroundIframe, { + resize: function(node){ + // summary: + // Resize the iframe so it's the same size as node. + // Needed on IE6 and IE/quirks because height:100% doesn't work right. + if(this.iframe){ + dojo.style(this.iframe, { + width: node.offsetWidth + 'px', + height: node.offsetHeight + 'px' + }); + } + }, + destroy: function(){ + // summary: + // destroy the iframe + if(this._conn){ + dojo.disconnect(this._conn); + this._conn = null; + } + if(this.iframe){ + dijit._frames.push(this.iframe); + delete this.iframe; + } + } +}); + } diff --git a/lib/dijit/_base/scroll.js b/lib/dijit/_base/scroll.js index 623cea392..1010a4fca 100644 --- a/lib/dijit/_base/scroll.js +++ b/lib/dijit/_base/scroll.js @@ -1,15 +1,22 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.scroll"]){ -dojo._hasResource["dijit._base.scroll"]=true; +if(!dojo._hasResource["dijit._base.scroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.scroll"] = true; dojo.provide("dijit._base.scroll"); dojo.require("dojo.window"); -dijit.scrollIntoView=function(_1,_2){ -dojo.window.scrollIntoView(_1,_2); + + +dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ + // summary: + // Scroll the passed node into view, if it is not already. + // Deprecated, use `dojo.window.scrollIntoView` instead. + + dojo.window.scrollIntoView(node, pos); }; + } diff --git a/lib/dijit/_base/sniff.js b/lib/dijit/_base/sniff.js index bf5386cfb..c7c6b94d8 100644 --- a/lib/dijit/_base/sniff.js +++ b/lib/dijit/_base/sniff.js @@ -1,12 +1,21 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.sniff"]){ -dojo._hasResource["dijit._base.sniff"]=true; +if(!dojo._hasResource["dijit._base.sniff"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.sniff"] = true; dojo.provide("dijit._base.sniff"); dojo.require("dojo.uacss"); + + +// summary: +// Applies pre-set CSS classes to the top-level HTML node, see +// `dojo.uacss` for details. +// +// Simply doing a require on this module will +// establish this CSS. Modified version of Morris' CSS hack. + } diff --git a/lib/dijit/_base/typematic.js b/lib/dijit/_base/typematic.js index 460e9db1a..f424b71d5 100644 --- a/lib/dijit/_base/typematic.js +++ b/lib/dijit/_base/typematic.js @@ -1,87 +1,191 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.typematic"]){ -dojo._hasResource["dijit._base.typematic"]=true; +if(!dojo._hasResource["dijit._base.typematic"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.typematic"] = true; dojo.provide("dijit._base.typematic"); -dijit.typematic={_fireEventAndReload:function(){ -this._timer=null; -this._callback(++this._count,this._node,this._evt); -this._currentTimeout=Math.max(this._currentTimeout<0?this._initialDelay:(this._subsequentDelay>1?this._subsequentDelay:Math.round(this._currentTimeout*this._subsequentDelay)),this._minDelay); -this._timer=setTimeout(dojo.hitch(this,"_fireEventAndReload"),this._currentTimeout); -},trigger:function(_1,_2,_3,_4,_5,_6,_7,_8){ -if(_5!=this._obj){ -this.stop(); -this._initialDelay=_7||500; -this._subsequentDelay=_6||0.9; -this._minDelay=_8||10; -this._obj=_5; -this._evt=_1; -this._node=_3; -this._currentTimeout=-1; -this._count=-1; -this._callback=dojo.hitch(_2,_4); -this._fireEventAndReload(); -this._evt=dojo.mixin({faux:true},_1); -} -},stop:function(){ -if(this._timer){ -clearTimeout(this._timer); -this._timer=null; -} -if(this._obj){ -this._callback(-1,this._node,this._evt); -this._obj=null; -} -},addKeyListener:function(_9,_a,_b,_c,_d,_e,_f){ -if(_a.keyCode){ -_a.charOrCode=_a.keyCode; -dojo.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.","","2.0"); -}else{ -if(_a.charCode){ -_a.charOrCode=String.fromCharCode(_a.charCode); -dojo.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.","","2.0"); -} -} -return [dojo.connect(_9,"onkeypress",this,function(evt){ -if(evt.charOrCode==_a.charOrCode&&(_a.ctrlKey===undefined||_a.ctrlKey==evt.ctrlKey)&&(_a.altKey===undefined||_a.altKey==evt.altKey)&&(_a.metaKey===undefined||_a.metaKey==(evt.metaKey||false))&&(_a.shiftKey===undefined||_a.shiftKey==evt.shiftKey)){ -dojo.stopEvent(evt); -dijit.typematic.trigger(evt,_b,_9,_c,_a,_d,_e,_f); -}else{ -if(dijit.typematic._obj==_a){ -dijit.typematic.stop(); -} -} -}),dojo.connect(_9,"onkeyup",this,function(evt){ -if(dijit.typematic._obj==_a){ -dijit.typematic.stop(); -} -})]; -},addMouseListener:function(_10,_11,_12,_13,_14,_15){ -var dc=dojo.connect; -return [dc(_10,"mousedown",this,function(evt){ -dojo.stopEvent(evt); -dijit.typematic.trigger(evt,_11,_10,_12,_10,_13,_14,_15); -}),dc(_10,"mouseup",this,function(evt){ -dojo.stopEvent(evt); -dijit.typematic.stop(); -}),dc(_10,"mouseout",this,function(evt){ -dojo.stopEvent(evt); -dijit.typematic.stop(); -}),dc(_10,"mousemove",this,function(evt){ -evt.preventDefault(); -}),dc(_10,"dblclick",this,function(evt){ -dojo.stopEvent(evt); -if(dojo.isIE){ -dijit.typematic.trigger(evt,_11,_10,_12,_10,_13,_14,_15); -setTimeout(dojo.hitch(this,dijit.typematic.stop),50); -} -})]; -},addListener:function(_16,_17,_18,_19,_1a,_1b,_1c,_1d){ -return this.addKeyListener(_17,_18,_19,_1a,_1b,_1c,_1d).concat(this.addMouseListener(_16,_19,_1a,_1b,_1c,_1d)); -}}; + + +dijit.typematic = { + // summary: + // These functions are used to repetitively call a user specified callback + // method when a specific key or mouse click over a specific DOM node is + // held down for a specific amount of time. + // Only 1 such event is allowed to occur on the browser page at 1 time. + + _fireEventAndReload: function(){ + this._timer = null; + this._callback(++this._count, this._node, this._evt); + + // Schedule next event, timer is at most minDelay (default 10ms) to avoid + // browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup) + this._currentTimeout = Math.max( + this._currentTimeout < 0 ? this._initialDelay : + (this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)), + this._minDelay); + this._timer = setTimeout(dojo.hitch(this, "_fireEventAndReload"), this._currentTimeout); + }, + + trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start a timed, repeating callback sequence. + // If already started, the function call is ignored. + // This method is not normally called by the user but can be + // when the normal listener code is insufficient. + // evt: + // key or mouse event object to pass to the user callback + // _this: + // pointer to the user's widget space. + // node: + // the DOM node object to pass the the callback function + // callback: + // function to call until the sequence is stopped called with 3 parameters: + // count: + // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped + // node: + // the DOM node object passed in + // evt: + // key or mouse event object + // obj: + // user space object used to uniquely identify each typematic sequence + // subsequentDelay (optional): + // if > 1, the number of milliseconds until the 3->n events occur + // or else the fractional time multiplier for the next event's delay, default=0.9 + // initialDelay (optional): + // the number of milliseconds until the 2nd event occurs, default=500ms + // minDelay (optional): + // the maximum delay in milliseconds for event to fire, default=10ms + if(obj != this._obj){ + this.stop(); + this._initialDelay = initialDelay || 500; + this._subsequentDelay = subsequentDelay || 0.90; + this._minDelay = minDelay || 10; + this._obj = obj; + this._evt = evt; + this._node = node; + this._currentTimeout = -1; + this._count = -1; + this._callback = dojo.hitch(_this, callback); + this._fireEventAndReload(); + this._evt = dojo.mixin({faux: true}, evt); + } + }, + + stop: function(){ + // summary: + // Stop an ongoing timed, repeating callback sequence. + if(this._timer){ + clearTimeout(this._timer); + this._timer = null; + } + if(this._obj){ + this._callback(-1, this._node, this._evt); + this._obj = null; + } + }, + + addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start listening for a specific typematic key. + // See also the trigger method for other parameters. + // keyObject: + // an object defining the key to listen for: + // charOrCode: + // the printable character (string) or keyCode (number) to listen for. + // keyCode: + // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0). + // charCode: + // (deprecated - use charOrCode) the charCode (number) to listen for. + // ctrlKey: + // desired ctrl key state to initiate the callback sequence: + // - pressed (true) + // - released (false) + // - either (unspecified) + // altKey: + // same as ctrlKey but for the alt key + // shiftKey: + // same as ctrlKey but for the shift key + // returns: + // an array of dojo.connect handles + if(keyObject.keyCode){ + keyObject.charOrCode = keyObject.keyCode; + dojo.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0"); + }else if(keyObject.charCode){ + keyObject.charOrCode = String.fromCharCode(keyObject.charCode); + dojo.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0"); + } + return [ + dojo.connect(node, "onkeypress", this, function(evt){ + if(evt.charOrCode == keyObject.charOrCode && + (keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) && + (keyObject.altKey === undefined || keyObject.altKey == evt.altKey) && + (keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey + (keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){ + dojo.stopEvent(evt); + dijit.typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay); + }else if(dijit.typematic._obj == keyObject){ + dijit.typematic.stop(); + } + }), + dojo.connect(node, "onkeyup", this, function(evt){ + if(dijit.typematic._obj == keyObject){ + dijit.typematic.stop(); + } + }) + ]; + }, + + addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start listening for a typematic mouse click. + // See the trigger method for other parameters. + // returns: + // an array of dojo.connect handles + var dc = dojo.connect; + return [ + dc(node, "mousedown", this, function(evt){ + dojo.stopEvent(evt); + dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay); + }), + dc(node, "mouseup", this, function(evt){ + dojo.stopEvent(evt); + dijit.typematic.stop(); + }), + dc(node, "mouseout", this, function(evt){ + dojo.stopEvent(evt); + dijit.typematic.stop(); + }), + dc(node, "mousemove", this, function(evt){ + evt.preventDefault(); + }), + dc(node, "dblclick", this, function(evt){ + dojo.stopEvent(evt); + if(dojo.isIE){ + dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay); + setTimeout(dojo.hitch(this, dijit.typematic.stop), 50); + } + }) + ]; + }, + + addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start listening for a specific typematic key and mouseclick. + // This is a thin wrapper to addKeyListener and addMouseListener. + // See the addMouseListener and addKeyListener methods for other parameters. + // mouseNode: + // the DOM node object to listen on for mouse events. + // keyNode: + // the DOM node object to listen on for key events. + // returns: + // an array of dojo.connect handles + return this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay).concat( + this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay)); + } +}; + } diff --git a/lib/dijit/_base/wai.js b/lib/dijit/_base/wai.js index de67ad17a..58c9cdb8f 100644 --- a/lib/dijit/_base/wai.js +++ b/lib/dijit/_base/wai.js @@ -1,64 +1,145 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.wai"]){ -dojo._hasResource["dijit._base.wai"]=true; +if(!dojo._hasResource["dijit._base.wai"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.wai"] = true; dojo.provide("dijit._base.wai"); -dijit.wai={onload:function(){ -var _1=dojo.create("div",{id:"a11yTestNode",style:{cssText:"border: 1px solid;"+"border-color:red green;"+"position: absolute;"+"height: 5px;"+"top: -999px;"+"background-image: url(\""+(dojo.config.blankGif||dojo.moduleUrl("dojo","resources/blank.gif"))+"\");"}},dojo.body()); -var cs=dojo.getComputedStyle(_1); -if(cs){ -var _2=cs.backgroundImage; -var _3=(cs.borderTopColor==cs.borderRightColor)||(_2!=null&&(_2=="none"||_2=="url(invalid-url:)")); -dojo[_3?"addClass":"removeClass"](dojo.body(),"dijit_a11y"); -if(dojo.isIE){ -_1.outerHTML=""; -}else{ -dojo.body().removeChild(_1); -} -} -}}; -if(dojo.isIE||dojo.isMoz){ -dojo._loaders.unshift(dijit.wai.onload); -} -dojo.mixin(dijit,{_XhtmlRoles:/banner|contentinfo|definition|main|navigation|search|note|secondary|seealso/,hasWaiRole:function(_4,_5){ -var _6=this.getWaiRole(_4); -return _5?(_6.indexOf(_5)>-1):(_6.length>0); -},getWaiRole:function(_7){ -return dojo.trim((dojo.attr(_7,"role")||"").replace(this._XhtmlRoles,"").replace("wairole:","")); -},setWaiRole:function(_8,_9){ -var _a=dojo.attr(_8,"role")||""; -if(!this._XhtmlRoles.test(_a)){ -dojo.attr(_8,"role",_9); -}else{ -if((" "+_a+" ").indexOf(" "+_9+" ")<0){ -var _b=dojo.trim(_a.replace(this._XhtmlRoles,"")); -var _c=dojo.trim(_a.replace(_b,"")); -dojo.attr(_8,"role",_c+(_c?" ":"")+_9); -} -} -},removeWaiRole:function(_d,_e){ -var _f=dojo.attr(_d,"role"); -if(!_f){ -return; -} -if(_e){ -var t=dojo.trim((" "+_f+" ").replace(" "+_e+" "," ")); -dojo.attr(_d,"role",t); -}else{ -_d.removeAttribute("role"); + + +dijit.wai = { + onload: function(){ + // summary: + // Detects if we are in high-contrast mode or not + + // This must be a named function and not an anonymous + // function, so that the widget parsing code can make sure it + // registers its onload function after this function. + // DO NOT USE "this" within this function. + + // create div for testing if high contrast mode is on or images are turned off + var div = dojo.create("div",{ + id: "a11yTestNode", + style:{ + cssText:'border: 1px solid;' + + 'border-color:red green;' + + 'position: absolute;' + + 'height: 5px;' + + 'top: -999px;' + + 'background-image: url("' + (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")) + '");' + } + }, dojo.body()); + + // test it + var cs = dojo.getComputedStyle(div); + if(cs){ + var bkImg = cs.backgroundImage; + var needsA11y = (cs.borderTopColor == cs.borderRightColor) || (bkImg != null && (bkImg == "none" || bkImg == "url(invalid-url:)" )); + dojo[needsA11y ? "addClass" : "removeClass"](dojo.body(), "dijit_a11y"); + if(dojo.isIE){ + div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014 + }else{ + dojo.body().removeChild(div); + } + } + } +}; + +// Test if computer is in high contrast mode. +// Make sure the a11y test runs first, before widgets are instantiated. +if(dojo.isIE || dojo.isMoz){ // NOTE: checking in Safari messes things up + dojo._loaders.unshift(dijit.wai.onload); } -},hasWaiState:function(_10,_11){ -return _10.hasAttribute?_10.hasAttribute("aria-"+_11):!!_10.getAttribute("aria-"+_11); -},getWaiState:function(_12,_13){ -return _12.getAttribute("aria-"+_13)||""; -},setWaiState:function(_14,_15,_16){ -_14.setAttribute("aria-"+_15,_16); -},removeWaiState:function(_17,_18){ -_17.removeAttribute("aria-"+_18); -}}); + +dojo.mixin(dijit, { + hasWaiRole: function(/*Element*/ elem, /*String?*/ role){ + // summary: + // Determines if an element has a particular role. + // returns: + // True if elem has the specific role attribute and false if not. + // For backwards compatibility if role parameter not provided, + // returns true if has a role + var waiRole = this.getWaiRole(elem); + return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0); + }, + + getWaiRole: function(/*Element*/ elem){ + // summary: + // Gets the role for an element (which should be a wai role). + // returns: + // The role of elem or an empty string if elem + // does not have a role. + return dojo.trim((dojo.attr(elem, "role") || "").replace("wairole:","")); + }, + + setWaiRole: function(/*Element*/ elem, /*String*/ role){ + // summary: + // Sets the role on an element. + // description: + // Replace existing role attribute with new role. + + dojo.attr(elem, "role", role); + }, + + removeWaiRole: function(/*Element*/ elem, /*String*/ role){ + // summary: + // Removes the specified role from an element. + // Removes role attribute if no specific role provided (for backwards compat.) + + var roleValue = dojo.attr(elem, "role"); + if(!roleValue){ return; } + if(role){ + var t = dojo.trim((" " + roleValue + " ").replace(" " + role + " ", " ")); + dojo.attr(elem, "role", t); + }else{ + elem.removeAttribute("role"); + } + }, + + hasWaiState: function(/*Element*/ elem, /*String*/ state){ + // summary: + // Determines if an element has a given state. + // description: + // Checks for an attribute called "aria-"+state. + // returns: + // true if elem has a value for the given state and + // false if it does not. + + return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state); + }, + + getWaiState: function(/*Element*/ elem, /*String*/ state){ + // summary: + // Gets the value of a state on an element. + // description: + // Checks for an attribute called "aria-"+state. + // returns: + // The value of the requested state on elem + // or an empty string if elem has no value for state. + + return elem.getAttribute("aria-"+state) || ""; + }, + + setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){ + // summary: + // Sets a state on an element. + // description: + // Sets an attribute called "aria-"+state. + + elem.setAttribute("aria-"+state, value); + }, + + removeWaiState: function(/*Element*/ elem, /*String*/ state){ + // summary: + // Removes a state from an element. + // description: + // Sets an attribute called "aria-"+state. + + elem.removeAttribute("aria-"+state); + } +}); + } diff --git a/lib/dijit/_base/window.js b/lib/dijit/_base/window.js index 57a691436..713455b90 100644 --- a/lib/dijit/_base/window.js +++ b/lib/dijit/_base/window.js @@ -1,15 +1,18 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit._base.window"]){ -dojo._hasResource["dijit._base.window"]=true; +if(!dojo._hasResource["dijit._base.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._base.window"] = true; dojo.provide("dijit._base.window"); dojo.require("dojo.window"); -dijit.getDocumentWindow=function(_1){ -return dojo.window.get(_1); + + +dijit.getDocumentWindow = function(doc){ + return dojo.window.get(doc); }; + } |