blob: 39a1af39d0d051c68d3a791a2c23c86c68b26455 [file] [log] [blame]
/*
Copyright (c) 2004-2009, 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
*/
/*
This is a compiled version of Dojo, built for deployment and not for
development. To get an editable version, please visit:
http://dojotoolkit.org
for documentation and information on getting the source.
*/
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, {
// 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 explictly 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
}
});
/*=====
dijit.registry = {
// summary:
// A list of widgets on a page.
// description:
// Is an instance of `dijit.WidgetSet`
};
=====*/
dijit.registry= new dijit.WidgetSet();
dijit._widgetTypeCtr = {};
dijit.getUniqueId = function(/*String*/widgetType){
// summary:
// Generates a unique id for a given widgetType
var id;
do{
id = widgetType + "_" +
(widgetType in dijit._widgetTypeCtr ?
++dijit._widgetTypeCtr[widgetType] : dijit._widgetTypeCtr[widgetType] = 0);
}while(dijit.byId(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 = dijit.byId(widgetId);
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.byId = function(/*String|Widget*/id){
// summary:
// Returns a widget by it's id, or if passed a widget, no-op (like dojo.byId())
return typeof id == "string" ? dijit.registry._hash[id] : id; // dijit._Widget
};
dijit.byNode = function(/* DOMNode */ node){
// summary:
// Returns the widget corresponding to the given DOMNode
return dijit.registry.byId(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 dijit.byId(id);
}
node = node.parentNode;
}
return null;
};
dijit._isElementShown = function(/*Element*/elem){
var style = dojo.style(elem);
return (style.visibility != "hidden")
&& (style.visibility != "collapsed")
&& (style.display != "none")
&& (dojo.attr(elem, "type") != "hidden");
}
dijit.isTabNavigable = function(/*Element*/elem){
// summary:
// Tests if an element is tab-navigable
// TODO: convert (and rename method) to return effectivite tabIndex; will save time in _getTabNavigable()
if(dojo.attr(elem, "disabled")){
return false;
}else if(dojo.hasAttr(elem, "tabIndex")){
// Explicit tab index setting
return dojo.attr(elem, "tabIndex") >= 0; // boolean
}else{
// 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 dojo.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.
if(dojo.isMoz){
return elem.contentDocument.designMode == "on";
}else if(dojo.isWebKit){
var doc = elem.contentDocument,
body = doc && doc.body;
return body && body.contentEditable == 'true';
}else{
// 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{
doc = elem.contentWindow.document;
body = doc && doc.body;
return body && body.firstChild && body.firstChild.contentEditable == 'true';
}catch(e){
return false;
}
}
default:
return elem.contentEditable == 'true';
}
}
};
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;
var walkTree = function(/*DOMNode*/parent){
dojo.query("> *", parent).forEach(function(child){
var isShown = dijit._isElementShown(child);
if(isShown && dijit.isTabNavigable(child)){
var tabindex = dojo.attr(child, "tabIndex");
if(!dojo.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;
}
}
}
if(isShown && child.nodeName.toUpperCase() != 'SELECT'){ walkTree(child) }
});
};
if(dijit._isElementShown(root)){ walkTree(root) }
return { first: first, last: last, lowest: lowest, highest: 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: 300
});
=====*/
dijit.defaultDuration = dojo.config["defaultDuration"] || 200;
}
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");
// for dijit.isTabNavigable()
// 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};
}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);
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){
doc.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(){
doc.detachEvent('onmousedown', mousedownListener);
doc.detachEvent('onactivate', activateListener);
doc.detachEvent('ondeactivate', deactivateListener);
doc = null; // prevent memory leak (apparent circular reference via closure)
};
}else{
doc.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.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=dijit.getDocumentWindow(node.ownerDocument).frameElement;
}else{
var id = node.getAttribute && node.getAttribute("widgetId");
if(id){
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._hasBeenBlurred = true;
if(widget._onBlur){
widget._onBlur(by);
}
if(widget._setStateClass){
widget._setStateClass();
}
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;
if(widget._onFocus){
widget._onFocus(by);
}
if(widget._setStateClass){
widget._setStateClass();
}
dojo.publish("widgetFocus", [widget, by]);
}
}
}
});
// register top window and all the iframes it contains
dojo.addOnLoad(function(){
var handle = dijit.registerWin(window);
if(dojo.isIE){
dojo.addOnWindowUnload(function(){
dijit.unregisterWin(handle);
handle = null;
})
}
});
}
if(!dojo._hasResource["dojo.AdapterRegistry"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.AdapterRegistry"] = true;
dojo.provide("dojo.AdapterRegistry");
dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){
// summary:
// A registry to make contextual calling/searching easier.
// description:
// Objects of this class keep list of arrays in the form [name, check,
// wrap, directReturn] that are used to determine what the contextual
// result of a set of checked arguments is. All check/wrap functions
// in this registry should be of the same arity.
// example:
// | // create a new registry
// | var reg = new dojo.AdapterRegistry();
// | reg.register("handleString",
// | dojo.isString,
// | function(str){
// | // do something with the string here
// | }
// | );
// | reg.register("handleArr",
// | dojo.isArray,
// | function(arr){
// | // do something with the array here
// | }
// | );
// |
// | // now we can pass reg.match() *either* an array or a string and
// | // the value we pass will get handled by the right function
// | reg.match("someValue"); // will call the first function
// | reg.match(["someValue"]); // will call the second
this.pairs = [];
this.returnWrappers = returnWrappers || false; // Boolean
}
dojo.extend(dojo.AdapterRegistry, {
register: function(/*String*/ name, /*Function*/ check, /*Function*/ wrap, /*Boolean?*/ directReturn, /*Boolean?*/ override){
// summary:
// register a check function to determine if the wrap function or
// object gets selected
// name:
// a way to identify this matcher.
// check:
// a function that arguments are passed to from the adapter's
// match() function. The check function should return true if the
// given arguments are appropriate for the wrap function.
// directReturn:
// If directReturn is true, the value passed in for wrap will be
// returned instead of being called. Alternately, the
// AdapterRegistry can be set globally to "return not call" using
// the returnWrappers property. Either way, this behavior allows
// the registry to act as a "search" function instead of a
// function interception library.
// override:
// If override is given and true, the check function will be given
// highest priority. Otherwise, it will be the lowest priority
// adapter.
this.pairs[((override) ? "unshift" : "push")]([name, check, wrap, directReturn]);
},
match: function(/* ... */){
// summary:
// Find an adapter for the given arguments. If no suitable adapter
// is found, throws an exception. match() accepts any number of
// arguments, all of which are passed to all matching functions
// from the registered pairs.
for(var i = 0; i < this.pairs.length; i++){
var pair = this.pairs[i];
if(pair[1].apply(this, arguments)){
if((pair[3])||(this.returnWrappers)){
return pair[2];
}else{
return pair[2].apply(this, arguments);
}
}
}
throw new Error("No match found");
},
unregister: function(name){
// summary: Remove a named adapter from the registry
// FIXME: this is kind of a dumb way to handle this. On a large
// registry this will be slow-ish and we can use the name as a lookup
// should we choose to trade memory for speed.
for(var i = 0; i < this.pairs.length; i++){
var pair = this.pairs[i];
if(pair[0] == name){
this.pairs.splice(i, 1);
return true;
}
}
return false;
}
});
}
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");
// ported from dojo.html.util
dijit.getViewport = function(){
// summary:
// Returns the dimensions and scroll position of the viewable area of a browser window
var scrollRoot = (dojo.doc.compatMode == 'BackCompat')? dojo.body() : dojo.doc.documentElement;
// get scroll position
var scroll = dojo._docScroll(); // scrollRoot.scrollTop/Left should work
return { w: scrollRoot.clientWidth, h: scrollRoot.clientHeight, l: scroll.x, t: scroll.y };
};
/*=====
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;
}
=====*/
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);
}
dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ layoutNode){
// 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)
// 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.
// get {x: 10, y: 10, w: 100, h:100} type obj representing position of
// viewport over document
var view = dijit.getViewport();
// 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;
// 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 tooltips size changes based on position, due to triangle)
if(layoutNode){
layoutNode(node, choice.aroundCorner, corner);
}
// 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
};
}
return !overflow;
});
node.style.left = best.x + "px";
node.style.top = best.y + "px";
if(best.overflow && layoutNode){
layoutNode(node, best.aroundCorner, best.corner);
}
return best;
}
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 oldDisplay = aroundNode.style.display;
aroundNode.style.display="";
// #3172: use the slightly tighter border box instead of marginBox
var aroundNodePos = dojo.position(aroundNode, true);
aroundNode.style.display=oldDisplay;
// place the node around the calculated rectangle
return dijit._placeOnScreenAroundRect(node,
aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle
aroundCorners, layoutNode);
};
/*=====
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;
}
=====*/
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);
};
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.getPopupAlignment = 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":
// 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":
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;
};
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":
// 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":
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;
};
}
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");
// TODO: remove this in 2.0, it's not used anymore, or at least not internally
dijit.getDocumentWindow = function(doc){
// summary:
// Get window object associated with document doc
// In some IE versions (at least 6.0), document.parentWindow does not return a
// reference to the real window object (maybe a copy), so we must fix it as well
// We use IE specific execScript to attach the real window reference to
// document._parentWindow for later use
if(dojo.isIE && window !== document.parentWindow && !doc._parentWindow){
/*
In IE 6, only the variable "window" can be used to connect events (others
may be only copies).
*/
doc.parentWindow.execScript("document._parentWindow = window;", "Javascript");
//to prevent memory leak, unset it after use
//another possibility is to add an onUnload handler which seems overkill to me (liucougar)
var win = doc._parentWindow;
doc._parentWindow = null;
return win; // Window
}
return doc._parentWindow || doc.parentWindow || doc.defaultView; // Window
}
}
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");
dijit.popup = new function(){
// summary:
// This class is used to show/hide widgets as popups.
var stack = [],
beginZIndex=1000,
idGen = 1;
this.moveOffScreen = function(/*DomNode*/ node){
// summary:
// Moves node offscreen without hiding it (so that all layout widgets included
// in this node can still layout properly)
//
// description:
// Attaches node to dojo.doc.body, and
// positions it off screen, but not display:none, so that
// the widget doesn't appear in the page flow and/or cause a blank
// area at the bottom of the viewport (making scrollbar longer), but
// initialization of contained widgets works correctly
var s = node.style;
s.visibility = "hidden"; // so TAB key doesn't navigate to hidden popup
s.position = "absolute";
s.top = "-9999px";
if(s.display == "none"){
s.display="";
}
dojo.body().appendChild(node);
};
/*=====
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. (Specity 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;
}
=====*/
// 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 getTopPopup = function(){
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];
};
var wrappers=[];
this.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 widget = args.popup,
orient = args.orient || (
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_"+idGen++);
// make wrapper div to hold widget and possibly hold iframe behind it.
// we can't attach the iframe as a child of the widget.domNode because
// widget.domNode might be a <table>, <ul>, etc.
var wrapperobj = wrappers.pop(), wrapper, iframe;
if(!wrapperobj){
wrapper = dojo.create("div",{
"class":"dijitPopup"
}, dojo.body());
dijit.setWaiRole(wrapper, "presentation");
}else{
// recycled a old wrapper, so that we don't need to reattach the iframe
// which is slow even if the iframe is empty, see #10167
wrapper = wrapperobj[0];
iframe = wrapperobj[1];
}
dojo.attr(wrapper,{
id: id,
style:{
zIndex: beginZIndex + stack.length,
visibility:"hidden",
// prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
top: "-9999px"
},
dijitPopupParent: args.parent ? args.parent.id : ""
});
var s = widget.domNode.style;
s.display = "";
s.visibility = "";
s.position = "";
s.top = "0px";
wrapper.appendChild(widget.domNode);
if(!iframe){
iframe = new dijit.BackgroundIframe(wrapper);
}else{
iframe.resize(wrapper)
}
// position the wrapper node
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.visibility = "visible";
// TODO: use effects to fade in wrapper
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 = 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", function(){
var topPopup = getTopPopup();
if(topPopup && topPopup.onExecute){
topPopup.onExecute();
}
}));
stack.push({
wrapper: wrapper,
iframe: iframe,
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;
};
this.close = function(/*dijit._Widget*/ popup){
// summary:
// Close specified popup and any popups that it parented
// 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(dojo.some(stack, function(elem){return elem.widget == popup;})){
var top = stack.pop(),
wrapper = top.wrapper,
iframe = top.iframe,
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);
// Move the widget offscreen, unless it has already been destroyed in above onClose() etc.
if(widget && widget.domNode){
this.moveOffScreen(widget.domNode);
}
// recycle the wrapper plus iframe, so we prevent reattaching iframe everytime an popup opens
// don't use moveOffScreen which would also reattach the wrapper to body, which causes reloading of iframe
wrapper.style.top = "-9999px";
wrapper.style.visibility = "hidden";
wrappers.push([wrapper,iframe]);
if(onClose){
onClose();
}
}
};
}();
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){
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 didnt work.
}
return iframe;
};
this.push = function(iframe){
iframe.style.display="none";
queue.push(iframe);
}
}();
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 = dijit._frames.pop();
node.appendChild(iframe);
if(dojo.isIE<7){
this.resize(node);
this._conn = dojo.connect(node, 'onresize', this, function(){
this.resize(node);
});
}else{
dojo.style(iframe, {
width: '100%',
height: '100%'
});
}
this.iframe = iframe;
}
};
dojo.extend(dijit.BackgroundIframe, {
resize: function(node){
// summary:
// resize the iframe so its the same size as node
// description:
// this function is a no-op in all browsers except
// IE6, which does not support 100% width/height
// of absolute positioned iframes
if(this.iframe && dojo.isIE<7){
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;
}
}
});
}
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");
dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
// summary:
// Scroll the passed node into view, if it is not already.
// don't rely on that node.scrollIntoView works just because the function is there
try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method
node = dojo.byId(node);
var doc = node.ownerDocument || dojo.doc,
body = doc.body || dojo.body(),
html = doc.documentElement || body.parentNode,
isIE = dojo.isIE, isWK = dojo.isWebKit;
// if an untested browser, then use the native method
if((!(dojo.isMoz || isIE || isWK) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){
node.scrollIntoView(false); // short-circuit to native if possible
return;
}
var backCompat = doc.compatMode == 'BackCompat',
clientAreaRoot = backCompat? body : html,
scrollRoot = isWK ? body : clientAreaRoot,
rootWidth = clientAreaRoot.clientWidth,
rootHeight = clientAreaRoot.clientHeight,
rtl = !dojo._isBodyLtr(),
nodePos = pos || dojo.position(node),
el = node.parentNode,
isFixed = function(el){
return ((isIE <= 6 || (isIE && backCompat))? false : (dojo.style(el, 'position').toLowerCase() == "fixed"));
};
if(isFixed(node)){ return; } // nothing to do
while(el){
if(el == body){ el = scrollRoot; }
var elPos = dojo.position(el),
fixedPos = isFixed(el);
with(elPos){
if(el == scrollRoot){
w = rootWidth, h = rootHeight;
if(scrollRoot == html && isIE && rtl){ x += scrollRoot.offsetWidth-w; } // IE workaround where scrollbar causes negative x
if(x < 0 || !isIE){ x = 0; } // IE can have values > 0
if(y < 0 || !isIE){ y = 0; }
}else{
var pb = dojo._getPadBorderExtents(el);
w -= pb.w; h -= pb.h; x += pb.l; y += pb.t;
}
with(el){
if(el != scrollRoot){ // body, html sizes already have the scrollbar removed
var clientSize = clientWidth,
scrollBarSize = w - clientSize;
if(clientSize > 0 && scrollBarSize > 0){
w = clientSize;
if(isIE && rtl){ x += scrollBarSize; }
}
clientSize = clientHeight;
scrollBarSize = h - clientSize;
if(clientSize > 0 && scrollBarSize > 0){
h = clientSize;
}
}
if(fixedPos){ // bounded by viewport, not parents
if(y < 0){
h += y, y = 0;
}
if(x < 0){
w += x, x = 0;
}
if(y + h > rootHeight){
h = rootHeight - y;
}
if(x + w > rootWidth){
w = rootWidth - x;
}
}
// calculate overflow in all 4 directions
var l = nodePos.x - x, // beyond left: < 0
t = nodePos.y - Math.max(y, 0), // beyond top: < 0
r = l + nodePos.w - w, // beyond right: > 0
bot = t + nodePos.h - h; // beyond bottom: > 0
if(r * l > 0){
var s = Math[l < 0? "max" : "min"](l, r);
nodePos.x += scrollLeft;
scrollLeft += (isIE >= 8 && !backCompat && rtl)? -s : s;
nodePos.x -= scrollLeft;
}
if(bot * t > 0){
nodePos.y += scrollTop;
scrollTop += Math[t < 0? "max" : "min"](t, bot);
nodePos.y -= scrollTop;
}
}
}
el = (el != scrollRoot) && !fixedPos && el.parentNode;
}
}catch(error){
console.error('scrollIntoView: ' + error);
node.scrollIntoView(false);
}
};
}
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;
// summary:
// Applies pre-set CSS classes to the top-level HTML node, based on:
// - browser (ex: dj_ie)
// - browser version (ex: dj_ie6)
// - box model (ex: dj_contentBox)
// - text direction (ex: dijitRtl)
//
// In addition, browser, browser version, and box model are
// combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl.
//
// Simply doing a require on this module will
// establish this CSS. Modified version of Morris' CSS hack.
dojo.provide("dijit._base.sniff");
(function(){
var d = dojo,
html = d.doc.documentElement,
ie = d.isIE,
opera = d.isOpera,
maj = Math.floor,
ff = d.isFF,
boxModel = d.boxModel.replace(/-/,''),
classes = {
dj_ie: ie,
dj_ie6: maj(ie) == 6,
dj_ie7: maj(ie) == 7,
dj_ie8: maj(ie) == 8,
dj_iequirks: ie && d.isQuirks,
// NOTE: Opera not supported by dijit
dj_opera: opera,
dj_khtml: d.isKhtml,
dj_webkit: d.isWebKit,
dj_safari: d.isSafari,
dj_chrome: d.isChrome,
dj_gecko: d.isMozilla,
dj_ff3: maj(ff) == 3
}; // no dojo unsupported browsers
classes["dj_" + boxModel] = true;
// apply browser, browser version, and box model class names
for(var p in classes){
if(classes[p]){
if(html.className){
html.className += " " + p;
}else{
html.className = p;
}
}
}
// If RTL mode then add dijitRtl flag plus repeat existing classes
// with -rtl extension
// (unshift is to make this code run after <body> node is loaded but before parser runs)
dojo._loaders.unshift(function(){
if(!dojo._isBodyLtr()){
html.className += " dijitRtl";
for(var p in classes){
if(classes[p]){
html.className += " " + p + "-rtl";
}
}
}
});
})();
}
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 = {
// 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, reducing the timer a little bit each iteration, bottoming-out at 10 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)),
10);
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){
// 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:
// 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:
// the number of milliseconds until the 2nd event occurs, default=500ms
if(obj != this._obj){
this.stop();
this._initialDelay = initialDelay || 500;
this._subsequentDelay = subsequentDelay || 0.90;
this._obj = obj;
this._evt = evt;
this._node = node;
this._currentTimeout = -1;
this._count = -1;
this._callback = dojo.hitch(_this, callback);
this._fireEventAndReload();
}
},
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){
// 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 calback 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(keyObject, _this, node, callback, keyObject, subsequentDelay, initialDelay);
}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){
// 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);
}),
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){
dojo.stopEvent(evt);
}),
dc(node, "dblclick", this, function(evt){
dojo.stopEvent(evt);
if(dojo.isIE){
dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay);
setTimeout(dojo.hitch(this, dijit.typematic.stop), 50);
}
})
];
},
addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay){
// 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).concat(
this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay));
}
};
}
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(){
// 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);
}
dojo.mixin(dijit, {
_XhtmlRoles: /banner|contentinfo|definition|main|navigation|search|note|secondary|seealso/,
hasWaiRole: function(/*Element*/ elem, /*String*/ role){
// summary:
// Determines if an element has a particular non-XHTML role.
// returns:
// True if elem has the specific non-XHTML role attribute and false if not.
// For backwards compatibility if role parameter not provided,
// returns true if has non XHTML role
var waiRole = this.getWaiRole(elem);
return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0);
},
getWaiRole: function(/*Element*/ elem){
// summary:
// Gets the non-XHTML role for an element (which should be a wai role).
// returns:
// The non-XHTML role of elem or an empty string if elem
// does not have a role.
return dojo.trim((dojo.attr(elem, "role") || "").replace(this._XhtmlRoles,"").replace("wairole:",""));
},
setWaiRole: function(/*Element*/ elem, /*String*/ role){
// summary:
// Sets the role on an element.
// description:
// Replace existing role attribute with new role.
// If elem already has an XHTML role, append this role to XHTML role
// and remove other ARIA roles.
var curRole = dojo.attr(elem, "role") || "";
if(!this._XhtmlRoles.test(curRole)){
dojo.attr(elem, "role", role);
}else{
if((" "+ curRole +" ").indexOf(" " + role + " ") < 0){
var clearXhtml = dojo.trim(curRole.replace(this._XhtmlRoles, ""));
var cleanRole = dojo.trim(curRole.replace(clearXhtml, ""));
dojo.attr(elem, "role", cleanRole + (cleanRole ? ' ' : '') + role);
}
}
},
removeWaiRole: function(/*Element*/ elem, /*String*/ role){
// summary:
// Removes the specified non-XHTML 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);
}
});
}
if(!dojo._hasResource["dijit._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._base"] = true;
dojo.provide("dijit._base");
}
if(!dojo._hasResource["dojo.date.stamp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.date.stamp"] = true;
dojo.provide("dojo.date.stamp");
// Methods to convert dates to or from a wire (string) format using well-known conventions
dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){
// summary:
// Returns a Date object given a string formatted according to a subset of the ISO-8601 standard.
//
// description:
// Accepts a string formatted according to a profile of ISO8601 as defined by
// [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed.
// Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime)
// The following combinations are valid:
//
// * dates only
// | * yyyy
// | * yyyy-MM
// | * yyyy-MM-dd
// * times only, with an optional time zone appended
// | * THH:mm
// | * THH:mm:ss
// | * THH:mm:ss.SSS
// * and "datetimes" which could be any combination of the above
//
// timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm
// Assumes the local time zone if not specified. Does not validate. Improperly formatted
// input may return null. Arguments which are out of bounds will be handled
// by the Date constructor (e.g. January 32nd typically gets resolved to February 1st)
// Only years between 100 and 9999 are supported.
//
// formattedString:
// A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00
//
// defaultTime:
// Used for defaults for fields omitted in the formattedString.
// Uses 1970-01-01T00:00:00.0Z by default.
if(!dojo.date.stamp._isoRegExp){
dojo.date.stamp._isoRegExp =
//TODO: could be more restrictive and check for 00-59, etc.
/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/;
}
var match = dojo.date.stamp._isoRegExp.exec(formattedString),
result = null;
if(match){
match.shift();
if(match[1]){match[1]--;} // Javascript Date months are 0-based
if(match[6]){match[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds
if(defaultTime){
// mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0
defaultTime = new Date(defaultTime);
dojo.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop){
return defaultTime["get" + prop]();
}).forEach(function(value, index){
if(match[index] === undefined){
match[index] = value;
}
});
}
result = new Date(match[0]||1970, match[1]||0, match[2]||1, match[3]||0, match[4]||0, match[5]||0, match[6]||0); //TODO: UTC defaults
if(match[0] < 100){
result.setFullYear(match[0] || 1970);
}
var offset = 0,
zoneSign = match[7] && match[7].charAt(0);
if(zoneSign != 'Z'){
offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0);
if(zoneSign != '-'){ offset *= -1; }
}
if(zoneSign){
offset -= result.getTimezoneOffset();
}
if(offset){
result.setTime(result.getTime() + offset * 60000);
}
}
return result; // Date or null
}
/*=====
dojo.date.stamp.__Options = function(){
// selector: String
// "date" or "time" for partial formatting of the Date object.
// Both date and time will be formatted by default.
// zulu: Boolean
// if true, UTC/GMT is used for a timezone
// milliseconds: Boolean
// if true, output milliseconds
this.selector = selector;
this.zulu = zulu;
this.milliseconds = milliseconds;
}
=====*/
dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__Options?*/options){
// summary:
// Format a Date object as a string according a subset of the ISO-8601 standard
//
// description:
// When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt)
// The local time zone is included as an offset from GMT, except when selector=='time' (time without a date)
// Does not check bounds. Only years between 100 and 9999 are supported.
//
// dateObject:
// A Date object
var _ = function(n){ return (n < 10) ? "0" + n : n; };
options = options || {};
var formattedDate = [],
getter = options.zulu ? "getUTC" : "get",
date = "";
if(options.selector != "time"){
var year = dateObject[getter+"FullYear"]();
date = ["0000".substr((year+"").length)+year, _(dateObject[getter+"Month"]()+1), _(dateObject[getter+"Date"]())].join('-');
}
formattedDate.push(date);
if(options.selector != "date"){
var time = [_(dateObject[getter+"Hours"]()), _(dateObject[getter+"Minutes"]()), _(dateObject[getter+"Seconds"]())].join(':');
var millis = dateObject[getter+"Milliseconds"]();
if(options.milliseconds){
time += "."+ (millis < 100 ? "0" : "") + _(millis);
}
if(options.zulu){
time += "Z";
}else if(options.selector != "time"){
var timezoneOffset = dateObject.getTimezoneOffset();
var absOffset = Math.abs(timezoneOffset);
time += (timezoneOffset > 0 ? "-" : "+") +
_(Math.floor(absOffset/60)) + ":" + _(absOffset%60);
}
formattedDate.push(time);
}
return formattedDate.join('T'); // String
}
}
if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.parser"] = true;
dojo.provide("dojo.parser");
dojo.parser = new function(){
// summary: The Dom/Widget parsing package
var d = dojo;
this._attrName = d._scopeName + "Type";
this._query = "[" + this._attrName + "]";
function val2type(/*Object*/ value){
// summary:
// Returns name of type of given value.
if(d.isString(value)){ return "string"; }
if(typeof value == "number"){ return "number"; }
if(typeof value == "boolean"){ return "boolean"; }
if(d.isFunction(value)){ return "function"; }
if(d.isArray(value)){ return "array"; } // typeof [] == "object"
if(value instanceof Date) { return "date"; } // assume timestamp
if(value instanceof d._Url){ return "url"; }
return "object";
}
function str2obj(/*String*/ value, /*String*/ type){
// summary:
// Convert given string value to given type
switch(type){
case "string":
return value;
case "number":
return value.length ? Number(value) : NaN;
case "boolean":
// for checked/disabled value might be "" or "checked". interpret as true.
return typeof value == "boolean" ? value : !(value.toLowerCase()=="false");
case "function":
if(d.isFunction(value)){
// IE gives us a function, even when we say something like onClick="foo"
// (in which case it gives us an invalid function "function(){ foo }").
// Therefore, convert to string
value=value.toString();
value=d.trim(value.substring(value.indexOf('{')+1, value.length-1));
}
try{
if(value.search(/[^\w\.]+/i) != -1){
// The user has specified some text for a function like "return x+5"
return new Function(value);
}else{
// The user has specified the name of a function like "myOnClick"
return d.getObject(value, false);
}
}catch(e){ return new Function(); }
case "array":
return value ? value.split(/\s*,\s*/) : [];
case "date":
switch(value){
case "": return new Date(""); // the NaN of dates
case "now": return new Date(); // current date
default: return d.date.stamp.fromISOString(value);
}
case "url":
return d.baseUrl + value;
default:
return d.fromJson(value);
}
}
var instanceClasses = {
// map from fully qualified name (like "dijit.Button") to structure like
// { cls: dijit.Button, params: {label: "string", disabled: "boolean"} }
};
// Widgets like BorderContainer add properties to _Widget via dojo.extend().
// If BorderContainer is loaded after _Widget's parameter list has been cached,
// we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
dojo.connect(dojo, "extend", function(){
instanceClasses = {};
});
function getClassInfo(/*String*/ className){
// className:
// fully qualified name (like "dijit.form.Button")
// returns:
// structure like
// {
// cls: dijit.Button,
// params: { label: "string", disabled: "boolean"}
// }
if(!instanceClasses[className]){
// get pointer to widget class
var cls = d.getObject(className);
if(!d.isFunction(cls)){
throw new Error("Could not load class '" + className +
"'. Did you spell the name correctly and use a full path, like 'dijit.form.Button'?");
}
var proto = cls.prototype;
// get table of parameter names & types
var params = {}, dummyClass = {};
for(var name in proto){
if(name.charAt(0)=="_"){ continue; } // skip internal properties
if(name in dummyClass){ continue; } // skip "constructor" and "toString"
var defVal = proto[name];
params[name]=val2type(defVal);
}
instanceClasses[className] = { cls: cls, params: params };
}
return instanceClasses[className];
}
this._functionFromScript = function(script){
var preamble = "";
var suffix = "";
var argsStr = script.getAttribute("args");
if(argsStr){
d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
preamble += "var "+part+" = arguments["+idx+"]; ";
});
}
var withStr = script.getAttribute("with");
if(withStr && withStr.length){
d.forEach(withStr.split(/\s*,\s*/), function(part){
preamble += "with("+part+"){";
suffix += "}";
});
}
return new Function(preamble+script.innerHTML+suffix);
}
this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){
// summary:
// Takes array of nodes, and turns them into class instances and
// potentially calls a layout method to allow them to connect with
// any children
// mixin: Object?
// An object that will be mixed in with each node in the array.
// Values in the mixin will override values in the node, if they
// exist.
// args: Object?
// An object used to hold kwArgs for instantiation.
// Only supports 'noStart' currently.
var thelist = [], dp = dojo.parser;
mixin = mixin||{};
args = args||{};
d.forEach(nodes, function(node){
if(!node){ return; }
var type = dp._attrName in mixin?mixin[dp._attrName]:node.getAttribute(dp._attrName);
if(!type || !type.length){ return; }
var clsInfo = getClassInfo(type),
clazz = clsInfo.cls,
ps = clazz._noScript || clazz.prototype._noScript;
// read parameters (ie, attributes).
// clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
var params = {},
attributes = node.attributes;
for(var name in clsInfo.params){
var item = name in mixin?{value:mixin[name],specified:true}:attributes.getNamedItem(name);
if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; }
var value = item.value;
// Deal with IE quirks for 'class' and 'style'
switch(name){
case "class":
value = "className" in mixin?mixin.className:node.className;
break;
case "style":
value = "style" in mixin?mixin.style:(node.style && node.style.cssText); // FIXME: Opera?
}
var _type = clsInfo.params[name];
if(typeof value == "string"){
params[name] = str2obj(value, _type);
}else{
params[name] = value;
}
}
// Process <script type="dojo/*"> script tags
// <script type="dojo/method" event="foo"> tags are added to params, and passed to
// the widget on instantiation.
// <script type="dojo/method"> tags (with no event) are executed after instantiation
// <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation
// note: dojo/* script tags cannot exist in self closing widgets, like <input />
if(!ps){
var connects = [], // functions to connect after instantiation
calls = []; // functions to call after instantiation
d.query("> script[type^='dojo/']", node).orphan().forEach(function(script){
var event = script.getAttribute("event"),
type = script.getAttribute("type"),
nf = d.parser._functionFromScript(script);
if(event){
if(type == "dojo/connect"){
connects.push({event: event, func: nf});
}else{
params[event] = nf;
}
}else{
calls.push(nf);
}
});
}
var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory;
// create the instance
var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node);
thelist.push(instance);
// map it to the JS namespace if that makes sense
var jsname = node.getAttribute("jsId");
if(jsname){
d.setObject(jsname, instance);
}
// process connections and startup functions
if(!ps){
d.forEach(connects, function(connect){
d.connect(instance, connect.event, null, connect.func);
});
d.forEach(calls, function(func){
func.call(instance);
});
}
});
// Call startup on each top level instance if it makes sense (as for
// widgets). Parent widgets will recursively call startup on their
// (non-top level) children
if(!mixin._started){
d.forEach(thelist, function(instance){
if( !args.noStart && instance &&
instance.startup &&
!instance._started &&
(!instance.getParent || !instance.getParent())
){
instance.startup();
}
});
}
return thelist;
};
this.parse = function(/*DomNode?*/ rootNode, /* Object? */ args){
// summary:
// Scan the DOM for class instances, and instantiate them.
//
// description:
// Search specified node (or root node) recursively for class instances,
// and instantiate them Searches for
// dojoType="qualified.class.name"
//
// rootNode: DomNode?
// A default starting root node from which to start the parsing. Can be
// omitted, defaulting to the entire document. If omitted, the `args`
// object can be passed in this place. If the `args` object has a
// `rootNode` member, that is used.
//
// args:
// a kwArgs object passed along to instantiate()
//
// * noStart: Boolean?
// when set will prevent the parser from calling .startup()
// when locating the nodes.
// * rootNode: DomNode?
// identical to the function's `rootNode` argument, though
// allowed to be passed in via this `args object.
//
// example:
// Parse all widgets on a page:
// | dojo.parser.parse();
//
// example:
// Parse all classes within the node with id="foo"
// | dojo.parser.parse(dojo.byId(foo));
//
// example:
// Parse all classes in a page, but do not call .startup() on any
// child
// | dojo.parser.parse({ noStart: true })
//
// example:
// Parse all classes in a node, but do not call .startup()
// | dojo.parser.parse(someNode, { noStart:true });
// | // or
// | dojo.parser.parse({ noStart:true, rootNode: someNode });
// determine the root node based on the passed arguments.
var root;
if(!args && rootNode && rootNode.rootNode){
args = rootNode;
root = args.rootNode;
}else{
root = rootNode;
}
var list = d.query(this._query, root);
// go build the object instances
return this.instantiate(list, null, args); // Array
};
}();
//Register the parser callback. It should be the first callback
//after the a11y test.
(function(){
var parseRunner = function(){
if(dojo.config.parseOnLoad){
dojo.parser.parse();
}
};
// FIXME: need to clobber cross-dependency!!
if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){
dojo._loaders.splice(1, 0, parseRunner);
}else{
dojo._loaders.unshift(parseRunner);
}
})();
}
if(!dojo._hasResource["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._Widget"] = true;
dojo.provide("dijit._Widget");
dojo.require( "dijit._base" );
// This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets'
// DOM nodes) until someone actually needs to monitor that event.
dojo.connect(dojo, "_connect",
function(/*dijit._Widget*/ widget, /*String*/ event){
if(widget && dojo.isFunction(widget._onConnect)){
widget._onConnect(event);
}
});
dijit._connectOnUseEventHandler = function(/*Event*/ event){};
// Keep track of where the last keydown event was, to help avoid generating
// spurious ondijitclick events when:
// 1. focus is on a <button> or <a>
// 2. user presses then releases the ENTER key
// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
// 4. onkeyup event fires, causing the ondijitclick handler to fire
dijit._lastKeyDownNode = null;
if(dojo.isIE){
(function(){
var keydownCallback = function(evt){
dijit._lastKeyDownNode = evt.srcElement;
};
dojo.doc.attachEvent('onkeydown', keydownCallback);
dojo.addOnWindowUnload(function(){
dojo.doc.detachEvent('onkeydown', keydownCallback);
});
})();
}else{
dojo.doc.addEventListener('keydown', function(evt){
dijit._lastKeyDownNode = evt.target;
}, true);
}
(function(){
var _attrReg = {}, // cached results from getSetterAttributes
getSetterAttributes = function(widget){
// summary:
// Returns list of attributes with custom setters for specified widget
var dc = widget.declaredClass;
if(!_attrReg[dc]){
var r = [],
attrs,
proto = widget.constructor.prototype;
for(var fxName in proto){
if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){
r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1));
}
}
_attrReg[dc] = r;
}
return _attrReg[dc] || []; // String[]
};
dojo.declare("dijit._Widget", null, {
// summary:
// Base class for all Dijit widgets.
// id: [const] String
// A unique, opaque ID string that can be assigned by users or by the
// system. If the developer passes an ID which is known not to be
// unique, the specified ID is ignored and the system-generated ID is
// used instead.
id: "",
// lang: [const] String
// Rarely used. Overrides the default Dojo locale used to render this widget,
// as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
// Value must be among the list of locales specified during by the Dojo bootstrap,
// formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
lang: "",
// dir: [const] String
// Unsupported by Dijit, but here for completeness. Dijit only supports setting text direction on the
// entire document.
// Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
// attribute. Either left-to-right "ltr" or right-to-left "rtl".
dir: "",
// class: String
// HTML class attribute
"class": "",
// style: String||Object
// HTML style attributes as cssText string or name/value hash
style: "",
// title: String
// HTML title attribute.
//
// For form widgets this specifies a tooltip to display when hovering over
// the widget (just like the native HTML title attribute).
//
// For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
// etc., it's used to specify the tab label, accordion pane title, etc.
title: "",
// tooltip: String
// When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
// this specifies the tooltip to appear when the mouse is hovered over that text.
tooltip: "",
// srcNodeRef: [readonly] DomNode
// pointer to original DOM node
srcNodeRef: null,
// domNode: [readonly] DomNode
// This is our visible representation of the widget! Other DOM
// Nodes may by assigned to other properties, usually through the
// template system's dojoAttachPoint syntax, but the domNode
// property is the canonical "top level" node in widget UI.
domNode: null,
// containerNode: [readonly] DomNode
// Designates where children of the source DOM node will be placed.
// "Children" in this case refers to both DOM nodes and widgets.
// For example, for myWidget:
//
// | <div dojoType=myWidget>
// | <b> here's a plain DOM node
// | <span dojoType=subWidget>and a widget</span>
// | <i> and another plain DOM node </i>
// | </div>
//
// containerNode would point to:
//
// | <b> here's a plain DOM node
// | <span dojoType=subWidget>and a widget</span>
// | <i> and another plain DOM node </i>
//
// In templated widgets, "containerNode" is set via a
// dojoAttachPoint assignment.
//
// containerNode must be defined for any widget that accepts innerHTML
// (like ContentPane or BorderContainer or even Button), and conversely
// is null for widgets that don't, like TextBox.
containerNode: null,
/*=====
// _started: Boolean
// startup() has completed.
_started: false,
=====*/
// attributeMap: [protected] Object
// attributeMap sets up a "binding" between attributes (aka properties)
// of the widget and the widget's DOM.
// Changes to widget attributes listed in attributeMap will be
// reflected into the DOM.
//
// For example, calling attr('title', 'hello')
// on a TitlePane will automatically cause the TitlePane's DOM to update
// with the new title.
//
// attributeMap is a hash where the key is an attribute of the widget,
// and the value reflects a binding to a:
//
// - DOM node attribute
// | focus: {node: "focusNode", type: "attribute"}
// Maps this.focus to this.focusNode.focus
//
// - DOM node innerHTML
// | title: { node: "titleNode", type: "innerHTML" }
// Maps this.title to this.titleNode.innerHTML
//
// - DOM node innerText
// | title: { node: "titleNode", type: "innerText" }
// Maps this.title to this.titleNode.innerText
//
// - DOM node CSS class
// | myClass: { node: "domNode", type: "class" }
// Maps this.myClass to this.domNode.className
//
// If the value is an array, then each element in the array matches one of the
// formats of the above list.
//
// There are also some shorthands for backwards compatibility:
// - string --> { node: string, type: "attribute" }, for example:
// | "focusNode" ---> { node: "focusNode", type: "attribute" }
// - "" --> { node: "domNode", type: "attribute" }
attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},
// _deferredConnects: [protected] Object
// attributeMap addendum for event handlers that should be connected only on first use
_deferredConnects: {
onClick: "",
onDblClick: "",
onKeyDown: "",
onKeyPress: "",
onKeyUp: "",
onMouseMove: "",
onMouseDown: "",
onMouseOut: "",
onMouseOver: "",
onMouseLeave: "",
onMouseEnter: "",
onMouseUp: ""
},
onClick: dijit._connectOnUseEventHandler,
/*=====
onClick: function(event){
// summary:
// Connect to this function to receive notifications of mouse click events.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onDblClick: dijit._connectOnUseEventHandler,
/*=====
onDblClick: function(event){
// summary:
// Connect to this function to receive notifications of mouse double click events.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onKeyDown: dijit._connectOnUseEventHandler,
/*=====
onKeyDown: function(event){
// summary:
// Connect to this function to receive notifications of keys being pressed down.
// event:
// key Event
// tags:
// callback
},
=====*/
onKeyPress: dijit._connectOnUseEventHandler,
/*=====
onKeyPress: function(event){
// summary:
// Connect to this function to receive notifications of printable keys being typed.
// event:
// key Event
// tags:
// callback
},
=====*/
onKeyUp: dijit._connectOnUseEventHandler,
/*=====
onKeyUp: function(event){
// summary:
// Connect to this function to receive notifications of keys being released.
// event:
// key Event
// tags:
// callback
},
=====*/
onMouseDown: dijit._connectOnUseEventHandler,
/*=====
onMouseDown: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse button is pressed down.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onMouseMove: dijit._connectOnUseEventHandler,
/*=====
onMouseMove: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onMouseOut: dijit._connectOnUseEventHandler,
/*=====
onMouseOut: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onMouseOver: dijit._connectOnUseEventHandler,
/*=====
onMouseOver: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onMouseLeave: dijit._connectOnUseEventHandler,
/*=====
onMouseLeave: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse moves off of this widget.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onMouseEnter: dijit._connectOnUseEventHandler,
/*=====
onMouseEnter: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse moves onto this widget.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onMouseUp: dijit._connectOnUseEventHandler,
/*=====
onMouseUp: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse button is released.
// event:
// mouse Event
// tags:
// callback
},
=====*/
// Constants used in templates
// _blankGif: [protected] String
// Path to a blank 1x1 image.
// Used by <img> nodes in templates that really get their image via CSS background-image.
_blankGif: (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")).toString(),
//////////// INITIALIZATION METHODS ///////////////////////////////////////
postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
// summary:
// Kicks off widget instantiation. See create() for details.
// tags:
// private
this.create(params, srcNodeRef);
},
create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
// summary:
// Kick off the life-cycle of a widget
// params:
// Hash of initialization parameters for widget, including
// scalar values (like title, duration etc.) and functions,
// typically callbacks like onClick.
// srcNodeRef:
// If a srcNodeRef (DOM node) is specified:
// - use srcNodeRef.innerHTML as my contents
// - if this is a behavioral widget then apply behavior
// to that srcNodeRef
// - otherwise, replace srcNodeRef with my generated DOM
// tree
// description:
// Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
// etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget
// for a discussion of the widget creation lifecycle.
//
// Of course, adventurous developers could override create entirely, but this should
// only be done as a last resort.
// tags:
// private
// store pointer to original DOM tree
this.srcNodeRef = dojo.byId(srcNodeRef);
// For garbage collection. An array of handles returned by Widget.connect()
// Each handle returned from Widget.connect() is an array of handles from dojo.connect()
this._connects = [];
// For garbage collection. An array of handles returned by Widget.subscribe()
// The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe()
this._subscribes = [];
// To avoid double-connects, remove entries from _deferredConnects
// that have been setup manually by a subclass (ex, by dojoAttachEvent).
// If a subclass has redefined a callback (ex: onClick) then assume it's being
// connected to manually.
this._deferredConnects = dojo.clone(this._deferredConnects);
for(var attr in this.attributeMap){
delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects
}
for(attr in this._deferredConnects){
if(this[attr] !== dijit._connectOnUseEventHandler){
delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists
}
}
//mixin our passed parameters
if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
if(params){
this.params = params;
dojo.mixin(this,params);
}
this.postMixInProperties();
// generate an id for the widget if one wasn't specified
// (be sure to do this before buildRendering() because that function might
// expect the id to be there.)
if(!this.id){
this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
}
dijit.registry.add(this);
this.buildRendering();
if(this.domNode){
// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
this._applyAttributes();
var source = this.srcNodeRef;
if(source && source.parentNode){
source.parentNode.replaceChild(this.domNode, source);
}
// If the developer has specified a handler as a widget parameter
// (ex: new Button({onClick: ...})
// then naturally need to connect from DOM node to that handler immediately,
for(attr in this.params){
this._onConnect(attr);
}
}
if(this.domNode){
this.domNode.setAttribute("widgetId", this.id);
}
this.postCreate();
// If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
if(this.srcNodeRef && !this.srcNodeRef.parentNode){
delete this.srcNodeRef;
}
this._created = true;
},
_applyAttributes: function(){
// summary:
// Step during widget creation to copy all widget attributes to the
// DOM as per attributeMap and _setXXXAttr functions.
// description:
// Skips over blank/false attribute values, unless they were explicitly specified
// as parameters to the widget, since those are the default anyway,
// and setting tabIndex="" is different than not setting tabIndex at all.
//
// It processes the attributes in the attribute map first, and then
// it goes through and processes the attributes for the _setXXXAttr
// functions that have been specified
// tags:
// private
var condAttrApply = function(attr, scope){
if((scope.params && attr in scope.params) || scope[attr]){
scope.attr(attr, scope[attr]);
}
};
// Do the attributes in attributeMap
for(var attr in this.attributeMap){
condAttrApply(attr, this);
}
// And also any attributes with custom setters
dojo.forEach(getSetterAttributes(this), function(a){
if(!(a in this.attributeMap)){
condAttrApply(a, this);
}
}, this);
},
postMixInProperties: function(){
// summary:
// Called after the parameters to the widget have been read-in,
// but before the widget template is instantiated. Especially
// useful to set properties that are referenced in the widget
// template.
// tags:
// protected
},
buildRendering: function(){
// summary:
// Construct the UI for this widget, setting this.domNode
// description:
// Most widgets will mixin `dijit._Templated`, which implements this
// method.
// tags:
// protected
this.domNode = this.srcNodeRef || dojo.create('div');
},
postCreate: function(){
// summary:
// Processing after the DOM fragment is created
// description:
// Called after the DOM fragment has been created, but not necessarily
// added to the document. Do not include any operations which rely on
// node dimensions or placement.
// tags:
// protected
},
startup: function(){
// summary:
// Processing after the DOM fragment is added to the document
// description:
// Called after a widget and its children have been created and added to the page,
// and all related widgets have finished their create() cycle, up through postCreate().
// This is useful for composite widgets that need to control or layout sub-widgets.
// Many layout widgets can use this as a wiring phase.
this._started = true;
},
//////////// DESTROY FUNCTIONS ////////////////////////////////
destroyRecursive: function(/*Boolean?*/ preserveDom){
// summary:
// Destroy this widget and its descendants
// description:
// This is the generic "destructor" function that all widget users
// should call to cleanly discard with a widget. Once a widget is
// destroyed, it is removed from the manager object.
// preserveDom:
// If true, this method will leave the original DOM structure
// alone of descendant Widgets. Note: This will NOT work with
// dijit._Templated widgets.
this._beingDestroyed = true;
this.destroyDescendants(preserveDom);
this.destroy(preserveDom);
},
destroy: function(/*Boolean*/ preserveDom){
// summary:
// Destroy this widget, but not its descendants.
// This method will, however, destroy internal widgets such as those used within a template.
// preserveDom: Boolean
// If true, this method will leave the original DOM structure alone.
// Note: This will not yet work with _Templated widgets
this._beingDestroyed = true;
this.uninitialize();
var d = dojo,
dfe = d.forEach,
dun = d.unsubscribe;
dfe(this._connects, function(array){
dfe(array, d.disconnect);
});
dfe(this._subscribes, function(handle){
dun(handle);
});
// destroy widgets created as part of template, etc.
dfe(this._supportingWidgets || [], function(w){
if(w.destroyRecursive){
w.destroyRecursive();
}else if(w.destroy){
w.destroy();
}
});
this.destroyRendering(preserveDom);
dijit.registry.remove(this.id);
this._destroyed = true;
},
destroyRendering: function(/*Boolean?*/ preserveDom){
// summary:
// Destroys the DOM nodes associated with this widget
// preserveDom:
// If true, this method will leave the original DOM structure alone
// during tear-down. Note: this will not work with _Templated
// widgets yet.
// tags:
// protected
if(this.bgIframe){
this.bgIframe.destroy(preserveDom);
delete this.bgIframe;
}
if(this.domNode){
if(preserveDom){
dojo.removeAttr(this.domNode, "widgetId");
}else{
dojo.destroy(this.domNode);
}
delete this.domNode;
}
if(this.srcNodeRef){
if(!preserveDom){
dojo.destroy(this.srcNodeRef);
}
delete this.srcNodeRef;
}
},
destroyDescendants: function(/*Boolean?*/ preserveDom){
// summary:
// Recursively destroy the children of this widget and their
// descendants.
// preserveDom:
// If true, the preserveDom attribute is passed to all descendant
// widget's .destroy() method. Not for use with _Templated
// widgets.
// get all direct descendants and destroy them recursively
dojo.forEach(this.getChildren(), function(widget){
if(widget.destroyRecursive){
widget.destroyRecursive(preserveDom);
}
});
},
uninitialize: function(){
// summary:
// Stub function. Override to implement custom widget tear-down
// behavior.
// tags:
// protected
return false;
},
////////////////// MISCELLANEOUS METHODS ///////////////////
onFocus: function(){
// summary:
// Called when the widget becomes "active" because
// it or a widget inside of it either has focus, or has recently
// been clicked.
// tags:
// callback
},
onBlur: function(){
// summary:
// Called when the widget stops being "active" because
// focus moved to something outside of it, or the user
// clicked somewhere outside of it, or the widget was
// hidden.
// tags:
// callback
},
_onFocus: function(e){
// summary:
// This is where widgets do processing for when they are active,
// such as changing CSS classes. See onFocus() for more details.
// tags:
// protected
this.onFocus();
},
_onBlur: function(){
// summary:
// This is where widgets do processing for when they stop being active,
// such as changing CSS classes. See onBlur() for more details.
// tags:
// protected
this.onBlur();
},
_onConnect: function(/*String*/ event){
// summary:
// Called when someone connects to one of my handlers.
// "Turn on" that handler if it isn't active yet.
//
// This is also called for every single initialization parameter
// so need to do nothing for parameters like "id".
// tags:
// private
if(event in this._deferredConnects){
var mapNode = this[this._deferredConnects[event] || 'domNode'];
this.connect(mapNode, event.toLowerCase(), event);
delete this._deferredConnects[event];
}
},
_setClassAttr: function(/*String*/ value){
// summary:
// Custom setter for the CSS "class" attribute
// tags:
// protected
var mapNode = this[this.attributeMap["class"] || 'domNode'];
dojo.removeClass(mapNode, this["class"])
this["class"] = value;
dojo.addClass(mapNode, value);
},
_setStyleAttr: function(/*String||Object*/ value){
// summary:
// Sets the style attribut of the widget according to value,
// which is either a hash like {height: "5px", width: "3px"}
// or a plain string
// description:
// Determines which node to set the style on based on style setting
// in attributeMap.
// tags:
// protected
var mapNode = this[this.attributeMap.style || 'domNode'];
// Note: technically we should revert any style setting made in a previous call
// to his method, but that's difficult to keep track of.
if(dojo.isObject(value)){
dojo.style(mapNode, value);
}else{
if(mapNode.style.cssText){
mapNode.style.cssText += "; " + value;
}else{
mapNode.style.cssText = value;
}
}
this.style = value;
},
setAttribute: function(/*String*/ attr, /*anything*/ value){
// summary:
// Deprecated. Use attr() instead.
// tags:
// deprecated
dojo.deprecated(this.declaredClass+"::setAttribute() is deprecated. Use attr() instead.", "", "2.0");
this.attr(attr, value);
},
_attrToDom: function(/*String*/ attr, /*String*/ value){
// summary:
// Reflect a widget attribute (title, tabIndex, duration etc.) to
// the widget DOM, as specified in attributeMap.
//
// description:
// Also sets this["attr"] to the new value.
// Note some attributes like "type"
// cannot be processed this way as they are not mutable.
//
// tags:
// private
var commands = this.attributeMap[attr];
dojo.forEach(dojo.isArray(commands) ? commands : [commands], function(command){
// Get target node and what we are doing to that node
var mapNode = this[command.node || command || "domNode"]; // DOM node
var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute
switch(type){
case "attribute":
if(dojo.isFunction(value)){ // functions execute in the context of the widget
value = dojo.hitch(this, value);
}
// Get the name of the DOM node attribute; usually it's the same
// as the name of the attribute in the widget (attr), but can be overridden.
// Also maps handler names to lowercase, like onSubmit --> onsubmit
var attrName = command.attribute ? command.attribute :
(/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr);
dojo.attr(mapNode, attrName, value);
break;
case "innerText":
mapNode.innerHTML = "";
mapNode.appendChild(dojo.doc.createTextNode(value));
break;
case "innerHTML":
mapNode.innerHTML = value;
break;
case "class":
dojo.removeClass(mapNode, this[attr]);
dojo.addClass(mapNode, value);
break;
}
}, this);
this[attr] = value;
},
attr: function(/*String|Object*/name, /*Object?*/value){
// summary:
// Set or get properties on a widget instance.
// name:
// The property to get or set. If an object is passed here and not
// a string, its keys are used as names of attributes to be set
// and the value of the object as values to set in the widget.
// value:
// Optional. If provided, attr() operates as a setter. If omitted,
// the current value of the named property is returned.
// description:
// Get or set named properties on a widget. If no value is
// provided, the current value of the attribute is returned,
// potentially via a getter method. If a value is provided, then
// the method acts as a setter, assigning the value to the name,
// potentially calling any explicitly provided setters to handle
// the operation. For instance, if the widget has properties "foo"
// and "bar" and a method named "_setFooAttr", calling:
// | myWidget.attr("foo", "Howdy!");
// would be equivalent to calling:
// | widget._setFooAttr("Howdy!");
// while calling:
// | myWidget.attr("bar", "Howdy!");
// would be the same as writing:
// | widget.bar = "Howdy!";
// It also tries to copy the changes to the widget's DOM according
// to settings in attributeMap (see description of `dijit._Widget.attributeMap`
// for details)
// For example, calling:
// | myTitlePane.attr("title", "Howdy!");
// will do
// | myTitlePane.title = "Howdy!";
// | myTitlePane.title.innerHTML = "Howdy!";
// It works for DOM node attributes too. Calling
// | widget.attr("disabled", true)
// will set the disabled attribute on the widget's focusNode,
// among other housekeeping for a change in disabled state.
// open questions:
// - how to handle build shortcut for attributes which want to map
// into DOM attributes?
// - what relationship should setAttribute()/attr() have to
// layout() calls?
var args = arguments.length;
if(args == 1 && !dojo.isString(name)){
for(var x in name){ this.attr(x, name[x]); }
return this;
}
var names = this._getAttrNames(name);
if(args >= 2){ // setter
if(this[names.s]){
// use the explicit setter
args = dojo._toArray(arguments, 1);
return this[names.s].apply(this, args) || this;
}else{
// if param is specified as DOM node attribute, copy it
if(name in this.attributeMap){
this._attrToDom(name, value);
}
// FIXME: what about function assignments? Any way to connect() here?
this[name] = value;
}
return this;
}else{ // getter
return this[names.g] ? this[names.g]() : this[name];
}
},
_attrPairNames: {}, // shared between all widgets
_getAttrNames: function(name){
// summary:
// Helper function for Widget.attr().
// Caches attribute name values so we don't do the string ops every time.
// tags:
// private
var apn = this._attrPairNames;
if(apn[name]){ return apn[name]; }
var uc = name.charAt(0).toUpperCase() + name.substr(1);
return (apn[name] = {
n: name+"Node",
s: "_set"+uc+"Attr",
g: "_get"+uc+"Attr"
});
},
toString: function(){
// summary:
// Returns a string that represents the widget
// description:
// When a widget is cast to a string, this method will be used to generate the
// output. Currently, it does not implement any sort of reversible
// serialization.
return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
},
getDescendants: function(){
// summary:
// Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
// This method should generally be avoided as it returns widgets declared in templates, which are
// supposed to be internal/hidden, but it's left here for back-compat reasons.
return this.containerNode ? dojo.query('[widgetId]', this.containerNode).map(dijit.byNode) : []; // dijit._Widget[]
},
getChildren: function(){
// summary:
// Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
// Does not return nested widgets, nor widgets that are part of this widget's template.
return this.containerNode ? dijit.findWidgets(this.containerNode) : []; // dijit._Widget[]
},
// nodesWithKeyClick: [private] String[]
// List of nodes that correctly handle click events via native browser support,
// and don't need dijit's help
nodesWithKeyClick: ["input", "button"],
connect: function(
/*Object|null*/ obj,
/*String|Function*/ event,
/*String|Function*/ method){
// summary:
// Connects specified obj/event to specified method of this object
// and registers for disconnect() on widget destroy.
// description:
// Provide widget-specific analog to dojo.connect, except with the
// implicit use of this widget as the target object.
// This version of connect also provides a special "ondijitclick"
// event which triggers on a click or space or enter keyup
// returns:
// A handle that can be passed to `disconnect` in order to disconnect before
// the widget is destroyed.
// example:
// | var btn = new dijit.form.Button();
// | // when foo.bar() is called, call the listener we're going to
// | // provide in the scope of btn
// | btn.connect(foo, "bar", function(){
// | console.debug(this.toString());
// | });
// tags:
// protected
var d = dojo,
dc = d._connect,
handles = [];
if(event == "ondijitclick"){
// add key based click activation for unsupported nodes.
// do all processing onkey up to prevent spurious clicks
// for details see comments at top of this file where _lastKeyDownNode is defined
if(!this.nodesWithKeyClick[obj.tagName.toLowerCase()]){
var m = d.hitch(this, method);
handles.push(
dc(obj, "onkeydown", this, function(e){
//console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
if((e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
// needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
dijit._lastKeyDownNode = e.target;
d.stopEvent(e); // stop event to prevent scrolling on space key in IE
}
}),
dc(obj, "onkeyup", this, function(e){
//console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
if( (e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
e.target === dijit._lastKeyDownNode &&
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
//need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
dijit._lastKeyDownNode = null;
return m(e);
}
})
);
}
event = "onclick";
}
handles.push(dc(obj, event, this, method));
this._connects.push(handles);
return handles; // _Widget.Handle
},
disconnect: function(/* _Widget.Handle */ handles){
// summary:
// Disconnects handle created by `connect`.
// Also removes handle from this widget's list of connects.
// tags:
// protected
for(var i=0; i<this._connects.length; i++){
if(this._connects[i] == handles){
dojo.forEach(handles, dojo.disconnect);
this._connects.splice(i, 1);
return;
}
}
},
subscribe: function(
/*String*/ topic,
/*String|Function*/ method){
// summary:
// Subscribes to the specified topic and calls the specified method
// of this object and registers for unsubscribe() on widget destroy.
// description:
// Provide widget-specific analog to dojo.subscribe, except with the
// implicit use of this widget as the target object.
// example:
// | var btn = new dijit.form.Button();
// | // when /my/topic is published, this button changes its label to
// | // be the parameter of the topic.
// | btn.subscribe("/my/topic", function(v){
// | this.attr("label", v);
// | });
var d = dojo,
handle = d.subscribe(topic, this, method);
// return handles for Any widget that may need them
this._subscribes.push(handle);
return handle;
},
unsubscribe: function(/*Object*/ handle){
// summary:
// Unsubscribes handle created by this.subscribe.
// Also removes handle from this widget's list of subscriptions
for(var i=0; i<this._subscribes.length; i++){
if(this._subscribes[i] == handle){
dojo.unsubscribe(handle);
this._subscribes.splice(i, 1);
return;
}
}
},
isLeftToRight: function(){
// summary:
// Checks the page for text direction
// tags:
// protected
return dojo._isBodyLtr(); //Boolean
},
isFocusable: function(){
// summary:
// Return true if this widget can currently be focused
// and false if not
return this.focus && (dojo.style(this.domNode, "display") != "none");
},
placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
// summary:
// Place this widget's domNode reference somewhere in the DOM based
// on standard dojo.place conventions, or passing a Widget reference that
// contains and addChild member.
//
// description:
// A convenience function provided in all _Widgets, providing a simple
// shorthand mechanism to put an existing (or newly created) Widget
// somewhere in the dom, and allow chaining.
//
// reference:
// The String id of a domNode, a domNode reference, or a reference to a Widget posessing
// an addChild method.
//
// position:
// If passed a string or domNode reference, the position argument
// accepts a string just as dojo.place does, one of: "first", "last",
// "before", or "after".
//
// If passed a _Widget reference, and that widget reference has an ".addChild" method,
// it will be called passing this widget instance into that method, supplying the optional
// position index passed.
//
// returns:
// dijit._Widget
// Provides a useful return of the newly created dijit._Widget instance so you
// can "chain" this function by instantiating, placing, then saving the return value
// to a variable.
//
// example:
// | // create a Button with no srcNodeRef, and place it in the body:
// | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body());
// | // now, 'button' is still the widget reference to the newly created button
// | dojo.connect(button, "onClick", function(e){ console.log('click'); });
//
// example:
// | // create a button out of a node with id="src" and append it to id="wrapper":
// | var button = new dijit.form.Button({},"src").placeAt("wrapper");
//
// example:
// | // place a new button as the first element of some div
// | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
//
// example:
// | // create a contentpane and add it to a TabContainer
// | var tc = dijit.byId("myTabs");
// | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
if(reference.declaredClass && reference.addChild){
reference.addChild(this, position);
}else{
dojo.place(this.domNode, reference, position);
}
return this;
},
_onShow: function(){
// summary:
// Internal method called when this widget is made visible.
// See `onShow` for details.
this.onShow();
},
onShow: function(){
// summary:
// Called when this widget becomes the selected pane in a
// `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
// `dijit.layout.AccordionContainer`, etc.
//
// Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
// tags:
// callback
},
onHide: function(){
// summary:
// Called when another widget becomes the selected pane in a
// `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
// `dijit.layout.AccordionContainer`, etc.
//
// Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
// tags:
// callback
}
});
})();
}
if(!dojo._hasResource["dojo.string"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.string"] = true;
dojo.provide("dojo.string");
/*=====
dojo.string = {
// summary: String utilities for Dojo
};
=====*/
dojo.string.rep = function(/*String*/str, /*Integer*/num){
// summary:
// Efficiently replicate a string `n` times.
// str:
// the string to replicate
// num:
// number of times to replicate the string
if(num <= 0 || !str){ return ""; }
var buf = [];
for(;;){
if(num & 1){
buf.push(str);
}
if(!(num >>= 1)){ break; }
str += str;
}
return buf.join(""); // String
};
dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){
// summary:
// Pad a string to guarantee that it is at least `size` length by
// filling with the character `ch` at either the start or end of the
// string. Pads at the start, by default.
// text:
// the string to pad
// size:
// length to provide padding
// ch:
// character to pad, defaults to '0'
// end:
// adds padding at the end if true, otherwise pads at start
// example:
// | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++".
// | dojo.string.pad("Dojo", 10, "+", true);
if(!ch){
ch = '0';
}
var out = String(text),
pad = dojo.string.rep(ch, Math.ceil((size - out.length) / ch.length));
return end ? out + pad : pad + out; // String
};
dojo.string.substitute = function( /*String*/ template,
/*Object|Array*/map,
/*Function?*/ transform,
/*Object?*/ thisObject){
// summary:
// Performs parameterized substitutions on a string. Throws an
// exception if any parameter is unmatched.
// template:
// a string with expressions in the form `${key}` to be replaced or
// `${key:format}` which specifies a format function. keys are case-sensitive.
// map:
// hash to search for substitutions
// transform:
// a function to process all parameters before substitution takes
// place, e.g. mylib.encodeXML
// thisObject:
// where to look for optional format function; default to the global
// namespace
// example:
// Substitutes two expressions in a string from an Array or Object
// | // returns "File 'foo.html' is not found in directory '/temp'."
// | // by providing substitution data in an Array
// | dojo.string.substitute(
// | "File '${0}' is not found in directory '${1}'.",
// | ["foo.html","/temp"]
// | );
// |
// | // also returns "File 'foo.html' is not found in directory '/temp'."
// | // but provides substitution data in an Object structure. Dotted
// | // notation may be used to traverse the structure.
// | dojo.string.substitute(
// | "File '${name}' is not found in directory '${info.dir}'.",
// | { name: "foo.html", info: { dir: "/temp" } }
// | );
// example:
// Use a transform function to modify the values:
// | // returns "file 'foo.html' is not found in directory '/temp'."
// | dojo.string.substitute(
// | "${0} is not found in ${1}.",
// | ["foo.html","/temp"],
// | function(str){
// | // try to figure out the type
// | var prefix = (str.charAt(0) == "/") ? "directory": "file";
// | return prefix + " '" + str + "'";
// | }
// | );
// example:
// Use a formatter
// | // returns "thinger -- howdy"
// | dojo.string.substitute(
// | "${0:postfix}", ["thinger"], null, {
// | postfix: function(value, key){
// | return value + " -- howdy";
// | }
// | }
// | );
thisObject = thisObject || dojo.global;
transform = transform ?
dojo.hitch(thisObject, transform) : function(v){ return v; };
return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
function(match, key, format){
var value = dojo.getObject(key, false, map);
if(format){
value = dojo.getObject(format, false, thisObject).call(thisObject, value, key);
}
return transform(value, key).toString();
}); // String
};
/*=====
dojo.string.trim = function(str){
// summary:
// Trims whitespace from both sides of the string
// str: String
// String to be trimmed
// returns: String
// Returns the trimmed string
// description:
// This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript).
// The short yet performant version of this function is dojo.trim(),
// which is part of Dojo base. Uses String.prototype.trim instead, if available.
return ""; // String
}
=====*/
dojo.string.trim = String.prototype.trim ?
dojo.trim : // aliasing to the native function
function(str){
str = str.replace(/^\s+/, '');
for(var i = str.length - 1; i >= 0; i--){
if(/\S/.test(str.charAt(i))){
str = str.substring(0, i + 1);
break;
}
}
return str;
};
}
if(!dojo._hasResource["dojo.cache"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.cache"] = true;
dojo.provide("dojo.cache");
/*=====
dojo.cache = {
// summary:
// A way to cache string content that is fetchable via `dojo.moduleUrl`.
};
=====*/
(function(){
var cache = {};
dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){
// summary:
// A getter and setter for storing the string content associated with the
// module and url arguments.
// description:
// module and url are used to call `dojo.moduleUrl()` to generate a module URL.
// If value is specified, the cache value for the moduleUrl will be set to
// that value. Otherwise, dojo.cache will fetch the moduleUrl and store it
// in its internal cache and return that cached value for the URL. To clear
// a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the
// the URL contents, only modules on the same domain of the page can use this capability.
// The build system can inline the cache values though, to allow for xdomain hosting.
// module: String||Object
// If a String, the module name to use for the base part of the URL, similar to module argument
// to `dojo.moduleUrl`. If an Object, something that has a .toString() method that
// generates a valid path for the cache item. For example, a dojo._Url object.
// url: String
// The rest of the path to append to the path derived from the module argument. If
// module is an object, then this second argument should be the "value" argument instead.
// value: String||Object?
// If a String, the value to use in the cache for the module/url combination.
// If an Object, it can have two properties: value and sanitize. The value property
// should be the value to use in the cache, and sanitize can be set to true or false,
// to indicate if XML declarations should be removed from the value and if the HTML
// inside a body tag in the value should be extracted as the real value. The value argument
// or the value property on the value argument are usually only used by the build system
// as it inlines cache content.
// example:
// To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style
// of call is used to avoid an issue with the build system erroneously trying to intern
// this example. To get the build system to intern your dojo.cache calls, use the
// "dojo.cache" style of call):
// | //If template.html contains "<h1>Hello</h1>" that will be
// | //the value for the text variable.
// | var text = dojo["cache"]("my.module", "template.html");
// example:
// To ask dojo.cache to fetch content and store it in the cache, and sanitize the input
// (the dojo["cache"] style of call is used to avoid an issue with the build system
// erroneously trying to intern this example. To get the build system to intern your
// dojo.cache calls, use the "dojo.cache" style of call):
// | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
// | //text variable will contain just "<h1>Hello</h1>".
// | var text = dojo["cache"]("my.module", "template.html", {sanitize: true});
// example:
// Same example as previous, but demostrates how an object can be passed in as
// the first argument, then the value argument can then be the second argument.
// | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
// | //text variable will contain just "<h1>Hello</h1>".
// | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true});
//Module could be a string, or an object that has a toString() method
//that will return a useful path. If it is an object, then the "url" argument
//will actually be the value argument.
if(typeof module == "string"){
var pathObj = dojo.moduleUrl(module, url);
}else{
pathObj = module;
value = url;
}
var key = pathObj.toString();
var val = value;
if(value !== undefined && !dojo.isString(value)){
val = ("value" in value ? value.value : undefined);
}
var sanitize = value && value.sanitize ? true : false;
if(val || val === null){
//We have a value, either clear or set the cache value.
if(val == null){
delete cache[key];
}else{
val = cache[key] = sanitize ? dojo.cache._sanitize(val) : val;
}
}else{
//Allow cache values to be empty strings. If key property does
//not exist, fetch it.
if(!(key in cache)){
val = dojo._getText(key);
cache[key] = sanitize ? dojo.cache._sanitize(val) : val;
}
val = cache[key];
}
return val; //String
};
dojo.cache._sanitize = function(/*String*/val){
// summary:
// Strips <?xml ...?> declarations so that external SVG and XML
// documents can be added to a document without worry. Also, if the string
// is an HTML document, only the part inside the body tag is returned.
// description:
// Copied from dijit._Templated._sanitizeTemplateString.
if(val){
val = val.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
var matches = val.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
if(matches){
val = matches[1];
}
}else{
val = "";
}
return val; //String
};
})();
}
if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._Templated"] = true;
dojo.provide("dijit._Templated");
dojo.declare("dijit._Templated",
null,
{
// summary:
// Mixin for widgets that are instantiated from a template
// templateString: [protected] String
// A string that represents the widget template. Pre-empts the
// templatePath. In builds that have their strings "interned", the
// templatePath is converted to an inline templateString, thereby
// preventing a synchronous network call.
//
// Use in conjunction with dojo.cache() to load from a file.
templateString: null,
// templatePath: [protected deprecated] String
// Path to template (HTML file) for this widget relative to dojo.baseUrl.
// Deprecated: use templateString with dojo.cache() instead.
templatePath: null,
// widgetsInTemplate: [protected] Boolean
// Should we parse the template to find widgets that might be
// declared in markup inside it? False by default.
widgetsInTemplate: false,
// skipNodeCache: [protected] Boolean
// If using a cached widget template node poses issues for a
// particular widget class, it can set this property to ensure
// that its template is always re-built from a string
_skipNodeCache: false,
// _earlyTemplatedStartup: Boolean
// A fallback to preserve the 1.0 - 1.3 behavior of children in
// templates having their startup called before the parent widget
// fires postCreate. Defaults to 'false', causing child widgets to
// have their .startup() called immediately before a parent widget
// .startup(), but always after the parent .postCreate(). Set to
// 'true' to re-enable to previous, arguably broken, behavior.
_earlyTemplatedStartup: false,
// _attachPoints: [private] String[]
// List of widget attribute names associated with dojoAttachPoint=... in the
// template, ex: ["containerNode", "labelNode"]
/*=====
_attachPoints: [],
=====*/
constructor: function(){
this._attachPoints = [];
},
_stringRepl: function(tmpl){
// summary:
// Does substitution of ${foo} type properties in template string
// tags:
// private
var className = this.declaredClass, _this = this;
// Cache contains a string because we need to do property replacement
// do the property replacement
return dojo.string.substitute(tmpl, this, function(value, key){
if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); }
if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
if(value == null){ return ""; }
// Substitution keys beginning with ! will skip the transform step,
// in case a user wishes to insert unescaped markup, e.g. ${!foo}
return key.charAt(0) == "!" ? value :
// Safer substitution, see heading "Attribute values" in
// http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
value.toString().replace(/"/g,"&quot;"); //TODO: add &amp? use encodeXML method?
}, this);
},
// method over-ride
buildRendering: function(){
// summary:
// Construct the UI for this widget from a template, setting this.domNode.
// tags:
// protected
// Lookup cached version of template, and download to cache if it
// isn't there already. Returns either a DomNode or a string, depending on
// whether or not the template contains ${foo} replacement parameters.
var cached = dijit._Templated.getCachedTemplate(this.templatePath, this.templateString, this._skipNodeCache);
var node;
if(dojo.isString(cached)){
node = dojo._toDom(this._stringRepl(cached));
if(node.nodeType != 1){
// Flag common problems such as templates with multiple top level nodes (nodeType == 11)
throw new Error("Invalid template: " + cached);
}
}else{
// if it's a node, all we have to do is clone it
node = cached.cloneNode(true);
}
this.domNode = node;
// recurse through the node, looking for, and attaching to, our
// attachment points and events, which should be defined on the template node.
this._attachTemplateNodes(node);
if(this.widgetsInTemplate){
// Make sure dojoType is used for parsing widgets in template.
// The dojo.parser.query could be changed from multiversion support.
var parser = dojo.parser, qry, attr;
if(parser._query != "[dojoType]"){
qry = parser._query;
attr = parser._attrName;
parser._query = "[dojoType]";
parser._attrName = "dojoType";
}
// Store widgets that we need to start at a later point in time
var cw = (this._startupWidgets = dojo.parser.parse(node, {
noStart: !this._earlyTemplatedStartup
}));
// Restore the query.
if(qry){
parser._query = qry;
parser._attrName = attr;
}
this._supportingWidgets = dijit.findWidgets(node);
this._attachTemplateNodes(cw, function(n,p){
return n[p];
});
}
this._fillContent(this.srcNodeRef);
},
_fillContent: function(/*DomNode*/ source){
// summary:
// Relocate source contents to templated container node.
// this.containerNode must be able to receive children, or exceptions will be thrown.
// tags:
// protected
var dest = this.containerNode;
if(source && dest){
while(source.hasChildNodes()){
dest.appendChild(source.firstChild);
}
}
},
_attachTemplateNodes: function(rootNode, getAttrFunc){
// summary:
// Iterate through the template and attach functions and nodes accordingly.
// description:
// Map widget properties and functions to the handlers specified in
// the dom node and it's descendants. This function iterates over all
// nodes and looks for these properties:
// * dojoAttachPoint
// * dojoAttachEvent
// * waiRole
// * waiState
// rootNode: DomNode|Array[Widgets]
// the node to search for properties. All children will be searched.
// getAttrFunc: Function?
// a function which will be used to obtain property for a given
// DomNode/Widget
// tags:
// private
getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); };
var nodes = dojo.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
var x = dojo.isArray(rootNode) ? 0 : -1;
for(; x<nodes.length; x++){
var baseNode = (x == -1) ? rootNode : nodes[x];
if(this.widgetsInTemplate && getAttrFunc(baseNode, "dojoType")){
continue;
}
// Process dojoAttachPoint
var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint");
if(attachPoint){
var point, points = attachPoint.split(/\s*,\s*/);
while((point = points.shift())){
if(dojo.isArray(this[point])){
this[point].push(baseNode);
}else{
this[point]=baseNode;
}
this._attachPoints.push(point);
}
}
// Process dojoAttachEvent
var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent");
if(attachEvent){
// NOTE: we want to support attributes that have the form
// "domEvent: nativeEvent; ..."
var event, events = attachEvent.split(/\s*,\s*/);
var trim = dojo.trim;
while((event = events.shift())){
if(event){
var thisFunc = null;
if(event.indexOf(":") != -1){
// oh, if only JS had tuple assignment
var funcNameArr = event.split(":");
event = trim(funcNameArr[0]);
thisFunc = trim(funcNameArr[1]);
}else{
event = trim(event);
}
if(!thisFunc){
thisFunc = event;
}
this.connect(baseNode, event, thisFunc);
}
}
}
// waiRole, waiState
var role = getAttrFunc(baseNode, "waiRole");
if(role){
dijit.setWaiRole(baseNode, role);
}
var values = getAttrFunc(baseNode, "waiState");
if(values){
dojo.forEach(values.split(/\s*,\s*/), function(stateValue){
if(stateValue.indexOf('-') != -1){
var pair = stateValue.split('-');
dijit.setWaiState(baseNode, pair[0], pair[1]);
}
});
}
}
},
startup: function(){
dojo.forEach(this._startupWidgets, function(w){
if(w && !w._started && w.startup){
w.startup();
}
});
this.inherited(arguments);
},
destroyRendering: function(){
// Delete all attach points to prevent IE6 memory leaks.
dojo.forEach(this._attachPoints, function(point){
delete this[point];
}, this);
this._attachPoints = [];
this.inherited(arguments);
}
}
);
// key is either templatePath or templateString; object is either string or DOM tree
dijit._Templated._templateCache = {};
dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){
// summary:
// Static method to get a template based on the templatePath or
// templateString key
// templatePath: String||dojo.uri.Uri
// The URL to get the template from.
// templateString: String?
// a string to use in lieu of fetching the template from a URL. Takes precedence
// over templatePath
// returns: Mixed
// Either string (if there are ${} variables that need to be replaced) or just
// a DOM tree (if the node can be cloned directly)
// is it already cached?
var tmplts = dijit._Templated._templateCache;
var key = templateString || templatePath;
var cached = tmplts[key];
if(cached){
try{
// if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the current document, then use the current cached value
if(!cached.ownerDocument || cached.ownerDocument == dojo.doc){
// string or node of the same document
return cached;
}
}catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
dojo.destroy(cached);
}
// If necessary, load template string from template path
if(!templateString){
templateString = dojo.cache(templatePath, {sanitize: true});
}
templateString = dojo.string.trim(templateString);
if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
// there are variables in the template so all we can do is cache the string
return (tmplts[key] = templateString); //String
}else{
// there are no variables in the template so we can cache the DOM tree
var node = dojo._toDom(templateString);
if(node.nodeType != 1){
throw new Error("Invalid template: " + templateString);
}
return (tmplts[key] = node); //Node
}
};
if(dojo.isIE){
dojo.addOnWindowUnload(function(){
var cache = dijit._Templated._templateCache;
for(var key in cache){
var value = cache[key];
if(typeof value == "object"){ // value is either a string or a DOM node template
dojo.destroy(value);
}
delete cache[key];
}
});
}
// These arguments can be specified for widgets which are used in templates.
// Since any widget can be specified as sub widgets in template, mix it
// into the base widget class. (This is a hack, but it's effective.)
dojo.extend(dijit._Widget,{
dojoAttachEvent: "",
dojoAttachPoint: "",
waiRole: "",
waiState:""
});
}
if(!dojo._hasResource["dijit._Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._Container"] = true;
dojo.provide("dijit._Container");
dojo.declare("dijit._Container",
null,
{
// summary:
// Mixin for widgets that contain a set of widget children.
// description:
// Use this mixin for widgets that needs to know about and
// keep track of their widget children. Suitable for widgets like BorderContainer
// and TabContainer which contain (only) a set of child widgets.
//
// It's not suitable for widgets like ContentPane
// which contains mixed HTML (plain DOM nodes in addition to widgets),
// and where contained widgets are not necessarily directly below
// this.containerNode. In that case calls like addChild(node, position)
// wouldn't make sense.
// isContainer: [protected] Boolean
// Indicates that this widget acts as a "parent" to the descendant widgets.
// When the parent is started it will call startup() on the child widgets.
// See also `isLayoutContainer`.
isContainer: true,
buildRendering: function(){
this.inherited(arguments);
if(!this.containerNode){
// all widgets with descendants must set containerNode
this.containerNode = this.domNode;
}
},
addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
// summary:
// Makes the given widget a child of this widget.
// description:
// Inserts specified child widget's dom node as a child of this widget's
// container node, and possibly does other processing (such as layout).
var refNode = this.containerNode;
if(insertIndex && typeof insertIndex == "number"){
var children = this.getChildren();
if(children && children.length >= insertIndex){
refNode = children[insertIndex-1].domNode;
insertIndex = "after";
}
}
dojo.place(widget.domNode, refNode, insertIndex);
// If I've been started but the child widget hasn't been started,
// start it now. Make sure to do this after widget has been
// inserted into the DOM tree, so it can see that it's being controlled by me,
// so it doesn't try to size itself.
if(this._started && !widget._started){
widget.startup();
}
},
removeChild: function(/*Widget or int*/ widget){
// summary:
// Removes the passed widget instance from this widget but does
// not destroy it. You can also pass in an integer indicating
// the index within the container to remove
if(typeof widget == "number" && widget > 0){
widget = this.getChildren()[widget];
}
if(widget && widget.domNode){
var node = widget.domNode;
node.parentNode.removeChild(node); // detach but don't destroy
}
},
getChildren: function(){
// summary:
// Returns array of children widgets.
// description:
// Returns the widgets that are directly under this.containerNode.
return dojo.query("> [widgetId]", this.containerNode).map(dijit.byNode); // Widget[]
},
hasChildren: function(){
// summary:
// Returns true if widget has children, i.e. if this.containerNode contains something.
return dojo.query("> [widgetId]", this.containerNode).length > 0; // Boolean
},
destroyDescendants: function(/*Boolean*/ preserveDom){
// summary:
// Destroys all the widgets inside this.containerNode,
// but not this widget itself
dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
},
_getSiblingOfChild: function(/*dijit._Widget*/ child, /*int*/ dir){
// summary:
// Get the next or previous widget sibling of child
// dir:
// if 1, get the next sibling
// if -1, get the previous sibling
// tags:
// private
var node = child.domNode,
which = (dir>0 ? "nextSibling" : "previousSibling");
do{
node = node[which];
}while(node && (node.nodeType != 1 || !dijit.byNode(node)));
return node && dijit.byNode(node); // dijit._Widget
},
getIndexOfChild: function(/*dijit._Widget*/ child){
// summary:
// Gets the index of the child in this container or -1 if not found
return dojo.indexOf(this.getChildren(), child); // int
},
startup: function(){
// summary:
// Called after all the widgets have been instantiated and their
// dom nodes have been inserted somewhere under dojo.doc.body.
//
// Widgets should override this method to do any initialization
// dependent on other widgets existing, and then call
// this superclass method to finish things off.
//
// startup() in subclasses shouldn't do anything
// size related because the size of the widget hasn't been set yet.
if(this._started){ return; }
// Startup all children of this widget
dojo.forEach(this.getChildren(), function(child){ child.startup(); });
this.inherited(arguments);
}
}
);
}
if(!dojo._hasResource["dijit._Contained"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._Contained"] = true;
dojo.provide("dijit._Contained");
dojo.declare("dijit._Contained",
null,
{
// summary:
// Mixin for widgets that are children of a container widget
//
// example:
// | // make a basic custom widget that knows about it's parents
// | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
getParent: function(){
// summary:
// Returns the parent widget of this widget, assuming the parent
// specifies isContainer
var parent = dijit.getEnclosingWidget(this.domNode.parentNode);
return parent && parent.isContainer ? parent : null;
},
_getSibling: function(/*String*/ which){
// summary:
// Returns next or previous sibling
// which:
// Either "next" or "previous"
// tags:
// private
var node = this.domNode;
do{
node = node[which+"Sibling"];
}while(node && node.nodeType != 1);
return node && dijit.byNode(node); // dijit._Widget
},
getPreviousSibling: function(){
// summary:
// Returns null if this is the first child of the parent,
// otherwise returns the next element sibling to the "left".
return this._getSibling("previous"); // dijit._Widget
},
getNextSibling: function(){
// summary:
// Returns null if this is the last child of the parent,
// otherwise returns the next element sibling to the "right".
return this._getSibling("next"); // dijit._Widget
},
getIndexInParent: function(){
// summary:
// Returns the index of this widget within its container parent.
// It returns -1 if the parent does not exist, or if the parent
// is not a dijit._Container
var p = this.getParent();
if(!p || !p.getIndexOfChild){
return -1; // int
}
return p.getIndexOfChild(this); // int
}
}
);
}
if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.layout._LayoutWidget"] = true;
dojo.provide("dijit.layout._LayoutWidget");
dojo.declare("dijit.layout._LayoutWidget",
[dijit._Widget, dijit._Container, dijit._Contained],
{
// summary:
// Base class for a _Container widget which is responsible for laying out its children.
// Widgets which mixin this code must define layout() to manage placement and sizing of the children.
// baseClass: [protected extension] String
// This class name is applied to the widget's domNode
// and also may be used to generate names for sub nodes,
// for example dijitTabContainer-content.
baseClass: "dijitLayoutContainer",
// isLayoutContainer: [protected] Boolean
// Indicates that this widget is going to call resize() on its
// children widgets, setting their size, when they become visible.
isLayoutContainer: true,
postCreate: function(){
dojo.addClass(this.domNode, "dijitContainer");
dojo.addClass(this.domNode, this.baseClass);
this.inherited(arguments);
},
startup: function(){
// summary:
// Called after all the widgets have been instantiated and their
// dom nodes have been inserted somewhere under dojo.doc.body.
//
// Widgets should override this method to do any initialization
// dependent on other widgets existing, and then call
// this superclass method to finish things off.
//
// startup() in subclasses shouldn't do anything
// size related because the size of the widget hasn't been set yet.
if(this._started){ return; }
// Need to call inherited first - so that child widgets get started
// up correctly
this.inherited(arguments);
// If I am a not being controlled by a parent layout widget...
var parent = this.getParent && this.getParent()
if(!(parent && parent.isLayoutContainer)){
// Do recursive sizing and layout of all my descendants
// (passing in no argument to resize means that it has to glean the size itself)
this.resize();
// Since my parent isn't a layout container, and my style *may be* width=height=100%
// or something similar (either set directly or via a CSS class),
// monitor when my size changes so that I can re-layout.
// For browsers where I can't directly monitor when my size changes,
// monitor when the viewport changes size, which *may* indicate a size change for me.
this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){
// Using function(){} closure to ensure no arguments to resize.
this.resize();
});
}
},
resize: function(changeSize, resultSize){
// summary:
// Call this to resize a widget, or after its size has changed.
// description:
// Change size mode:
// When changeSize is specified, changes the marginBox of this widget
// and forces it to relayout its contents accordingly.
// changeSize may specify height, width, or both.
//
// If resultSize is specified it indicates the size the widget will
// become after changeSize has been applied.
//
// Notification mode:
// When changeSize is null, indicates that the caller has already changed
// the size of the widget, or perhaps it changed because the browser
// window was resized. Tells widget to relayout its contents accordingly.
//
// If resultSize is also specified it indicates the size the widget has
// become.
//
// In either mode, this method also:
// 1. Sets this._borderBox and this._contentBox to the new size of
// the widget. Queries the current domNode size if necessary.
// 2. Calls layout() to resize contents (and maybe adjust child widgets).
//
// changeSize: Object?
// Sets the widget to this margin-box size and position.
// May include any/all of the following properties:
// | {w: int, h: int, l: int, t: int}
//
// resultSize: Object?
// The margin-box size of this widget after applying changeSize (if
// changeSize is specified). If caller knows this size and
// passes it in, we don't need to query the browser to get the size.
// | {w: int, h: int}
var node = this.domNode;
// set margin box size, unless it wasn't specified, in which case use current size
if(changeSize){
dojo.marginBox(node, changeSize);
// set offset of the node
if(changeSize.t){ node.style.top = changeSize.t + "px"; }
if(changeSize.l){ node.style.left = changeSize.l + "px"; }
}
// If either height or width wasn't specified by the user, then query node for it.
// But note that setting the margin box and then immediately querying dimensions may return
// inaccurate results, so try not to depend on it.
var mb = resultSize || {};
dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
if( !("h" in mb) || !("w" in mb) ){
mb = dojo.mixin(dojo.marginBox(node), mb); // just use dojo.marginBox() to fill in missing values
}
// Compute and save the size of my border box and content box
// (w/out calling dojo.contentBox() since that may fail if size was recently set)
var cs = dojo.getComputedStyle(node);
var me = dojo._getMarginExtents(node, cs);
var be = dojo._getBorderExtents(node, cs);
var bb = (this._borderBox = {
w: mb.w - (me.w + be.w),
h: mb.h - (me.h + be.h)
});
var pe = dojo._getPadExtents(node, cs);
this._contentBox = {
l: dojo._toPixelValue(node, cs.paddingLeft),
t: dojo._toPixelValue(node, cs.paddingTop),
w: bb.w - pe.w,
h: bb.h - pe.h
};
// Callback for widget to adjust size of its children
this.layout();
},
layout: function(){
// summary:
// Widgets override this method to size and position their contents/children.
// When this is called this._contentBox is guaranteed to be set (see resize()).
//
// This is called after startup(), and also when the widget's size has been
// changed.
// tags:
// protected extension
},
_setupChild: function(/*dijit._Widget*/child){
// summary:
// Common setup for initial children and children which are added after startup
// tags:
// protected extension
dojo.addClass(child.domNode, this.baseClass+"-child");
if(child.baseClass){
dojo.addClass(child.domNode, this.baseClass+"-"+child.baseClass);
}
},
addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
// Overrides _Container.addChild() to call _setupChild()
this.inherited(arguments);
if(this._started){
this._setupChild(child);
}
},
removeChild: function(/*dijit._Widget*/ child){
// Overrides _Container.removeChild() to remove class added by _setupChild()
dojo.removeClass(child.domNode, this.baseClass+"-child");
if(child.baseClass){
dojo.removeClass(child.domNode, this.baseClass+"-"+child.baseClass);
}
this.inherited(arguments);
}
}
);
dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
// summary:
// Given the margin-box size of a node, return its content box size.
// Functions like dojo.contentBox() but is more reliable since it doesn't have
// to wait for the browser to compute sizes.
var cs = dojo.getComputedStyle(node);
var me = dojo._getMarginExtents(node, cs);
var pb = dojo._getPadBorderExtents(node, cs);
return {
l: dojo._toPixelValue(node, cs.paddingLeft),
t: dojo._toPixelValue(node, cs.paddingTop),
w: mb.w - (me.w + pb.w),
h: mb.h - (me.h + pb.h)
};
};
(function(){
var capitalize = function(word){
return word.substring(0,1).toUpperCase() + word.substring(1);
};
var size = function(widget, dim){
// size the child
widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
// record child's size, but favor our own numbers when we have them.
// the browser lies sometimes
dojo.mixin(widget, dojo.marginBox(widget.domNode));
dojo.mixin(widget, dim);
};
dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){
// summary
// Layout a bunch of child dom nodes within a parent dom node
// container:
// parent node
// dim:
// {l, t, w, h} object specifying dimensions of container into which to place children
// children:
// an array like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ]
// copy dim because we are going to modify it
dim = dojo.mixin({}, dim);
dojo.addClass(container, "dijitLayoutContainer");
// Move "client" elements to the end of the array for layout. a11y dictates that the author
// needs to be able to put them in the document in tab-order, but this algorithm requires that
// client be last.
children = dojo.filter(children, function(item){ return item.layoutAlign != "client"; })
.concat(dojo.filter(children, function(item){ return item.layoutAlign == "client"; }));
// set positions/sizes
dojo.forEach(children, function(child){
var elm = child.domNode,
pos = child.layoutAlign;
// set elem to upper left corner of unused space; may move it later
var elmStyle = elm.style;
elmStyle.left = dim.l+"px";
elmStyle.top = dim.t+"px";
elmStyle.bottom = elmStyle.right = "auto";
dojo.addClass(elm, "dijitAlign" + capitalize(pos));
// set size && adjust record of remaining space.
// note that setting the width of a <div> may affect its height.
if(pos == "top" || pos == "bottom"){
size(child, { w: dim.w });
dim.h -= child.h;
if(pos == "top"){
dim.t += child.h;
}else{
elmStyle.top = dim.t + dim.h + "px";
}
}else if(pos == "left" || pos == "right"){
size(child, { h: dim.h });
dim.w -= child.w;
if(pos == "left"){
dim.l += child.w;
}else{
elmStyle.left = dim.l + dim.w + "px";
}
}else if(pos == "client"){
size(child, dim);
}
});
};
})();
}
if(!dojo._hasResource["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.form._FormWidget"] = true;
dojo.provide("dijit.form._FormWidget");
dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated],
{
// summary:
// Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>,
// which can be children of a <form> node or a `dijit.form.Form` widget.
//
// description:
// Represents a single HTML element.
// All these widgets should have these attributes just like native HTML input elements.
// You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
//
// They also share some common methods.
// baseClass: [protected] String
// Root CSS class of the widget (ex: dijitTextBox), used to add CSS classes of widget
// (ex: "dijitTextBox dijitTextBoxInvalid dijitTextBoxFocused dijitTextBoxInvalidFocused")
// See _setStateClass().
baseClass: "",
// name: String
// Name used when submitting form; same as "name" attribute or plain HTML elements
name: "",
// alt: String
// Corresponds to the native HTML <input> element's attribute.
alt: "",
// value: String
// Corresponds to the native HTML <input> element's attribute.
value: "",
// type: String
// Corresponds to the native HTML <input> element's attribute.
type: "text",
// tabIndex: Integer
// Order fields are traversed when user hits the tab key
tabIndex: "0",
// disabled: Boolean
// Should this widget respond to user input?
// In markup, this is specified as "disabled='disabled'", or just "disabled".
disabled: false,
// intermediateChanges: Boolean
// Fires onChange for each value change or only on demand
intermediateChanges: false,
// scrollOnFocus: Boolean
// On focus, should this widget scroll into view?
scrollOnFocus: true,
// These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are.
attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
value: "focusNode",
id: "focusNode",
tabIndex: "focusNode",
alt: "focusNode",
title: "focusNode"
}),
postMixInProperties: function(){
// Setup name=foo string to be referenced from the template (but only if a name has been specified)
// Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : "";
this.inherited(arguments);
},
_setDisabledAttr: function(/*Boolean*/ value){
this.disabled = value;
dojo.attr(this.focusNode, 'disabled', value);
if(this.valueNode){
dojo.attr(this.valueNode, 'disabled', value);
}
dijit.setWaiState(this.focusNode, "disabled", value);
if(value){
// reset those, because after the domNode is disabled, we can no longer receive
// mouse related events, see #4200
this._hovering = false;
this._active = false;
// remove the tabIndex, especially for FF
this.focusNode.setAttribute('tabIndex', "-1");
}else{
this.focusNode.setAttribute('tabIndex', this.tabIndex);
}
this._setStateClass();
},
setDisabled: function(/*Boolean*/ disabled){
// summary:
// Deprecated. Use attr('disabled', ...) instead.
dojo.deprecated("setDisabled("+disabled+") is deprecated. Use attr('disabled',"+disabled+") instead.", "", "2.0");
this.attr('disabled', disabled);
},
_onFocus: function(e){
if(this.scrollOnFocus){
dijit.scrollIntoView(this.domNode);
}
this.inherited(arguments);
},
_onMouse : function(/*Event*/ event){
// summary:
// Sets _hovering, _active, and stateModifier properties depending on mouse state,
// then calls setStateClass() to set appropriate CSS classes for this.domNode.
//
// To get a different CSS class for hover, send onmouseover and onmouseout events to this method.
// To get a different CSS class while mouse button is depressed, send onmousedown to this method.
var mouseNode = event.currentTarget;
if(mouseNode && mouseNode.getAttribute){
this.stateModifier = mouseNode.getAttribute("stateModifier") || "";
}
if(!this.disabled){
switch(event.type){
case "mouseenter":
case "mouseover":
this._hovering = true;
this._active = this._mouseDown;
break;
case "mouseout":
case "mouseleave":
this._hovering = false;
this._active = false;
break;
case "mousedown" :
this._active = true;
this._mouseDown = true;
// set a global event to handle mouseup, so it fires properly
// even if the cursor leaves the button
var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
// if user clicks on the button, even if the mouse is released outside of it,
// this button should get focus (which mimics native browser buttons)
if(this._mouseDown && this.isFocusable()){
this.focus();
}
this._active = false;
this._mouseDown = false;
this._setStateClass();
this.disconnect(mouseUpConnector);
});
break;
}
this._setStateClass();
}
},
isFocusable: function(){
// summary:
// Tells if this widget is focusable or not. Used internally by dijit.
// tags:
// protected
return !this.disabled && !this.readOnly && this.focusNode && (dojo.style(this.domNode, "display") != "none");
},
focus: function(){
// summary:
// Put focus on this widget
dijit.focus(this.focusNode);
},
_setStateClass: function(){
// summary:
// Update the visual state of the widget by setting the css classes on this.domNode
// (or this.stateNode if defined) by combining this.baseClass with
// various suffixes that represent the current widget state(s).
//
// description:
// In the case where a widget has multiple
// states, it sets the class based on all possible
// combinations. For example, an invalid form widget that is being hovered
// will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
//
// For complex widgets with multiple regions, there can be various hover/active states,
// such as "Hover" or "CloseButtonHover" (for tab buttons).
// This is controlled by a stateModifier="CloseButton" attribute on the close button node.
//
// The widget may have one or more of the following states, determined
// by this.state, this.checked, this.valid, and this.selected:
// - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
// - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
// - Selected - ex: currently selected tab will have this.selected==true
//
// In addition, it may have one or more of the following states,
// based on this.disabled and flags set in _onMouse (this._active, this._hovering, this._focused):
// - Disabled - if the widget is disabled
// - Active - if the mouse (or space/enter key?) is being pressed down
// - Focused - if the widget has focus
// - Hover - if the mouse is over the widget
// Compute new set of classes
var newStateClasses = this.baseClass.split(" ");
function multiply(modifier){
newStateClasses = newStateClasses.concat(dojo.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
}
if(this.checked){
multiply("Checked");
}
if(this.state){
multiply(this.state);
}
if(this.selected){
multiply("Selected");
}
if(this.disabled){
multiply("Disabled");
}else if(this.readOnly){
multiply("ReadOnly");
}else if(this._active){
multiply(this.stateModifier+"Active");
}else{
if(this._focused){
multiply("Focused");
}
if(this._hovering){
multiply(this.stateModifier+"Hover");
}
}
// Remove old state classes and add new ones.
// For performance concerns we only write into domNode.className once.
var tn = this.stateNode || this.domNode,
classHash = {}; // set of all classes (state and otherwise) for node
dojo.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
if("_stateClasses" in this){
dojo.forEach(this._stateClasses, function(c){ delete classHash[c]; });
}
dojo.forEach(newStateClasses, function(c){ classHash[c] = true; });
var newClasses = [];
for(var c in classHash){
newClasses.push(c);
}
tn.className = newClasses.join(" ");
this._stateClasses = newStateClasses;
},
compare: function(/*anything*/val1, /*anything*/val2){
// summary:
// Compare 2 values (as returned by attr('value') for this widget).
// tags:
// protected
if(typeof val1 == "number" && typeof val2 == "number"){
return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
}else if(val1 > val2){
return 1;
}else if(val1 < val2){
return -1;
}else{
return 0;
}
},
onChange: function(newValue){
// summary:
// Callback when this widget's value is changed.
// tags:
// callback
},
// _onChangeActive: [private] Boolean
// Indicates that changes to the value should call onChange() callback.
// This is false during widget initialization, to avoid calling onChange()
// when the initial value is set.
_onChangeActive: false,
_handleOnChange: function(/*anything*/ newValue, /* Boolean? */ priorityChange){
// summary:
// Called when the value of the widget is set. Calls onChange() if appropriate
// newValue:
// the new value
// priorityChange:
// For a slider, for example, dragging the slider is priorityChange==false,
// but on mouse up, it's priorityChange==true. If intermediateChanges==true,
// onChange is only called form priorityChange=true events.
// tags:
// private
this._lastValue = newValue;
if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
// this block executes not for a change, but during initialization,
// and is used to store away the original value (or for ToggleButton, the original checked state)
this._resetValue = this._lastValueReported = newValue;
}
if((this.intermediateChanges || priorityChange || priorityChange === undefined) &&
((typeof newValue != typeof this._lastValueReported) ||
this.compare(newValue, this._lastValueReported) != 0)){
this._lastValueReported = newValue;
if(this._onChangeActive){
if(this._onChangeHandle){
clearTimeout(this._onChangeHandle);
}
// setTimout allows hidden value processing to run and
// also the onChange handler can safely adjust focus, etc
this._onChangeHandle = setTimeout(dojo.hitch(this,
function(){
this._onChangeHandle = null;
this.onChange(newValue);
}), 0); // try to collapse multiple onChange's fired faster than can be processed
}
}
},
create: function(){
// Overrides _Widget.create()
this.inherited(arguments);
this._onChangeActive = true;
this._setStateClass();
},
destroy: function(){
if(this._onChangeHandle){ // destroy called before last onChange has fired
clearTimeout(this._onChangeHandle);
this.onChange(this._lastValueReported);
}
this.inherited(arguments);
},
setValue: function(/*String*/ value){
// summary:
// Deprecated. Use attr('value', ...) instead.
dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use attr('value',"+value+") instead.", "", "2.0");
this.attr('value', value);
},
getValue: function(){
// summary:
// Deprecated. Use attr('value') instead.
dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use attr('value') instead.", "", "2.0");
return this.attr('value');
}
});
dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
{
// summary:
// Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values.
// description:
// Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element,
// to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
// works as expected.
// Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
// directly in the template as read by the parser in order to function. IE is known to specifically
// require the 'name' attribute at element creation time. See #8484, #8660.
// TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode,
// so maybe {value: ""} is so the value *doesn't* get copied to focusNode?
// Seems like we really want value removed from attributeMap altogether
// (although there's no easy way to do that now)
// readOnly: Boolean
// Should this widget respond to user input?
// In markup, this is specified as "readOnly".
// Similar to disabled except readOnly form values are submitted.
readOnly: false,
attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
value: "",
readOnly: "focusNode"
}),
_setReadOnlyAttr: function(/*Boolean*/ value){
this.readOnly = value;
dojo.attr(this.focusNode, 'readOnly', value);
dijit.setWaiState(this.focusNode, "readonly", value);
this._setStateClass();
},
postCreate: function(){
if(dojo.isIE){ // IE won't stop the event with keypress
this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown);
}
// Update our reset value if it hasn't yet been set (because this.attr
// is only called when there *is* a value
if(this._resetValue === undefined){
this._resetValue = this.value;
}
},
_setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
// summary:
// Hook so attr('value', value) works.
// description:
// Sets the value of the widget.
// If the value has changed, then fire onChange event, unless priorityChange
// is specified as null (or false?)
this.value = newValue;
this._handleOnChange(newValue, priorityChange);
},
_getValueAttr: function(){
// summary:
// Hook so attr('value') works.
return this._lastValue;
},
undo: function(){
// summary:
// Restore the value to the last value passed to onChange
this._setValueAttr(this._lastValueReported, false);
},
reset: function(){
// summary:
// Reset the widget's value to what it was at initialization time
this._hasBeenBlurred = false;
this._setValueAttr(this._resetValue, true);
},
_onKeyDown: function(e){
if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
var te;
if(dojo.isIE){
e.preventDefault(); // default behavior needs to be stopped here since keypress is too late
te = document.createEventObject();
te.keyCode = dojo.keys.ESCAPE;
te.shiftKey = e.shiftKey;
e.srcElement.fireEvent('onkeypress', te);
}
}
},
_layoutHackIE7: function(){
// summary:
// Work around table sizing bugs on IE7 by forcing redraw
if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight
var domNode = this.domNode;
var parent = domNode.parentNode;
var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter
var origFilter = pingNode.style.filter; // save custom filter, most likely nothing
while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
parent._disconnectHandle = this.connect(parent, "onscroll", dojo.hitch(this, function(e){
this.disconnect(parent._disconnectHandle); // only call once
parent.removeAttribute("_disconnectHandle"); // clean up DOM node
pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique
setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any
}));
parent = parent.parentNode;
}
}
}
});
}
if(!dojo._hasResource["dijit.dijit"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.dijit"] = true;
dojo.provide("dijit.dijit");
/*=====
dijit.dijit = {
// summary:
// A roll-up for common dijit methods
// description:
// A rollup file for the build system including the core and common
// dijit files.
//
// example:
// | <script type="text/javascript" src="js/dojo/dijit/dijit.js"></script>
//
};
=====*/
// All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
// And some other stuff that we tend to pull in all the time anyway
}
if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._KeyNavContainer"] = true;
dojo.provide("dijit._KeyNavContainer");
dojo.declare("dijit._KeyNavContainer",
dijit._Container,
{
// summary:
// A _Container with keyboard navigation of its children.
// description:
// To use this mixin, call connectKeyNavHandlers() in
// postCreate() and call startupKeyNavChildren() in startup().
// It provides normalized keyboard and focusing code for Container
// widgets.
/*=====
// focusedChild: [protected] Widget
// The currently focused child widget, or null if there isn't one
focusedChild: null,
=====*/
// tabIndex: Integer
// Tab index of the container; same as HTML tabIndex attribute.
// Note then when user tabs into the container, focus is immediately
// moved to the first item in the container.
tabIndex: "0",
_keyNavCodes: {},
connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){
// summary:
// Call in postCreate() to attach the keyboard handlers
// to the container.
// preKeyCodes: dojo.keys[]
// Key codes for navigating to the previous child.
// nextKeyCodes: dojo.keys[]
// Key codes for navigating to the next child.
// tags:
// protected
var keyCodes = (this._keyNavCodes = {});
var prev = dojo.hitch(this, this.focusPrev);
var next = dojo.hitch(this, this.focusNext);
dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
this.connect(this.domNode, "onfocus", "_onContainerFocus");
},
startupKeyNavChildren: function(){
// summary:
// Call in startup() to set child tabindexes to -1
// tags:
// protected
dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
},
addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
// summary:
// Add a child to our _Container
dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
this._startupChild(widget);
},
focus: function(){
// summary:
// Default focus() implementation: focus the first child.
this.focusFirstChild();
},
focusFirstChild: function(){
// summary:
// Focus the first focusable child in the container.
// tags:
// protected
var child = this._getFirstFocusableChild();
if(child){ // edge case: Menu could be empty or hidden
this.focusChild(child);
}
},
focusNext: function(){
// summary:
// Focus the next widget
// tags:
// protected
var child = this._getNextFocusableChild(this.focusedChild, 1);
this.focusChild(child);
},
focusPrev: function(){
// summary:
// Focus the last focusable node in the previous widget
// (ex: go to the ComboButton icon section rather than button section)
// tags:
// protected
var child = this._getNextFocusableChild(this.focusedChild, -1);
this.focusChild(child, true);
},
focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
// summary:
// Focus widget.
// widget:
// Reference to container's child widget
// last:
// If true and if widget has multiple focusable nodes, focus the
// last one instead of the first one
// tags:
// protected
if(this.focusedChild && widget !== this.focusedChild){
this._onChildBlur(this.focusedChild);
}
widget.focus(last ? "end" : "start");
this.focusedChild = widget;
},
_startupChild: function(/*dijit._Widget*/ widget){
// summary:
// Setup for each child widget
// description:
// Sets tabIndex=-1 on each child, so that the tab key will
// leave the container rather than visiting each child.
// tags:
// private
widget.attr("tabIndex", "-1");
this.connect(widget, "_onFocus", function(){
// Set valid tabIndex so tabbing away from widget goes to right place, see #10272
widget.attr("tabIndex", this.tabIndex);
});
this.connect(widget, "_onBlur", function(){
widget.attr("tabIndex", "-1");
});
},
_onContainerFocus: function(evt){
// summary:
// Handler for when the container gets focus
// description:
// Initially the container itself has a tabIndex, but when it gets
// focus, switch focus to first child...
// tags:
// private
// Note that we can't use _onFocus() because switching focus from the
// _onFocus() handler confuses the focus.js code
// (because it causes _onFocusNode() to be called recursively)
// focus bubbles on Firefox,
// so just make sure that focus has really gone to the container
if(evt.target !== this.domNode){ return; }
this.focusFirstChild();
// and then set the container's tabIndex to -1,
// (don't remove as that breaks Safari 4)
// so that tab or shift-tab will go to the fields after/before
// the container, rather than the container itself
dojo.attr(this.domNode, "tabIndex", "-1");
},
_onBlur: function(evt){
// When focus is moved away the container, and it's descendant (popup) widgets,
// then restore the container's tabIndex so that user can tab to it again.
// Note that using _onBlur() so that this doesn't happen when focus is shifted
// to one of my child widgets (typically a popup)
if(this.tabIndex){
dojo.attr(this.domNode, "tabIndex", this.tabIndex);
}
this.inherited(arguments);
},
_onContainerKeypress: function(evt){
// summary:
// When a key is pressed, if it's an arrow key etc. then
// it's handled here.
// tags:
// private
if(evt.ctrlKey || evt.altKey){ return; }
var func = this._keyNavCodes[evt.charOrCode];
if(func){
func();
dojo.stopEvent(evt);
}
},
_onChildBlur: function(/*dijit._Widget*/ widget){
// summary:
// Called when focus leaves a child widget to go
// to a sibling widget.
// tags:
// protected
},
_getFirstFocusableChild: function(){
// summary:
// Returns first child that can be focused
return this._getNextFocusableChild(null, 1); // dijit._Widget
},
_getNextFocusableChild: function(child, dir){
// summary:
// Returns the next or previous focusable child, compared
// to "child"
// child: Widget
// The current widget
// dir: Integer
// * 1 = after
// * -1 = before
if(child){
child = this._getSiblingOfChild(child, dir);
}
var children = this.getChildren();
for(var i=0; i < children.length; i++){
if(!child){
child = children[(dir>0) ? 0 : (children.length-1)];
}
if(child.isFocusable()){
return child; // dijit._Widget
}
child = this._getSiblingOfChild(child, dir);
}
// no focusable child found
return null; // dijit._Widget
}
}
);
}
if(!dojo._hasResource["dijit.MenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.MenuItem"] = true;
dojo.provide("dijit.MenuItem");
dojo.declare("dijit.MenuItem",
[dijit._Widget, dijit._Templated, dijit._Contained],
{
// summary:
// A line item in a Menu Widget
// Make 3 columns
// icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\">\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\">\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"),
attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
label: { node: "containerNode", type: "innerHTML" },
iconClass: { node: "iconNode", type: "class" }
}),
// label: String
// Menu text
label: '',
// iconClass: String
// Class to apply to DOMNode to make it display an icon.
iconClass: "",
// accelKey: String
// Text for the accelerator (shortcut) key combination.
// Note that although Menu can display accelerator keys there
// is no infrastructure to actually catch and execute these
// accelerators.
accelKey: "",
// disabled: Boolean
// If true, the menu item is disabled.
// If false, the menu item is enabled.
disabled: false,
_fillContent: function(/*DomNode*/ source){
// If button label is specified as srcNodeRef.innerHTML rather than
// this.params.label, handle it here.
if(source && !("label" in this.params)){
this.attr('label', source.innerHTML);
}
},
postCreate: function(){
dojo.setSelectable(this.domNode, false);
var label = this.id+"_text";
dojo.attr(this.containerNode, "id", label);
if(this.accelKeyNode){
dojo.attr(this.accelKeyNode, "id", this.id + "_accel");
label += " " + this.id + "_accel";
}
dijit.setWaiState(this.domNode, "labelledby", label);
},
_onHover: function(){
// summary:
// Handler when mouse is moved onto menu item
// tags:
// protected
dojo.addClass(this.domNode, 'dijitMenuItemHover');
this.getParent().onItemHover(this);
},
_onUnhover: function(){
// summary:
// Handler when mouse is moved off of menu item,
// possibly to a child menu, or maybe to a sibling
// menuitem or somewhere else entirely.
// tags:
// protected
// if we are unhovering the currently selected item
// then unselect it
dojo.removeClass(this.domNode, 'dijitMenuItemHover');
this.getParent().onItemUnhover(this);
},
_onClick: function(evt){
// summary:
// Internal handler for click events on MenuItem.
// tags:
// private
this.getParent().onItemClick(this, evt);
dojo.stopEvent(evt);
},
onClick: function(/*Event*/ evt){
// summary:
// User defined function to handle clicks
// tags:
// callback
},
focus: function(){
// summary:
// Focus on this MenuItem
try{
if(dojo.isIE == 8){
// needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275)
this.containerNode.focus();
}
dijit.focus(this.focusNode);
}catch(e){
// this throws on IE (at least) in some scenarios
}
},
_onFocus: function(){
// summary:
// This is called by the focus manager when focus
// goes to this MenuItem or a child menu.
// tags:
// protected
this._setSelected(true);
this.getParent()._onItemFocus(this);
this.inherited(arguments);
},
_setSelected: function(selected){
// summary:
// Indicate that this node is the currently selected one
// tags:
// private
/***
* TODO: remove this method and calls to it, when _onBlur() is working for MenuItem.
* Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu.
* That's not supposed to happen, but the problem is:
* In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent
* points to the parent Menu, bypassing the parent MenuItem... thus the
* MenuItem is not in the chain of active widgets and gets a premature call to
* _onBlur()
*/
dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected);
},
setLabel: function(/*String*/ content){
// summary:
// Deprecated. Use attr('label', ...) instead.
// tags:
// deprecated
dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use attr('label', ...) instead.", "", "2.0");
this.attr("label", content);
},
setDisabled: function(/*Boolean*/ disabled){
// summary:
// Deprecated. Use attr('disabled', bool) instead.
// tags:
// deprecated
dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use attr('disabled', bool) instead.", "", "2.0");
this.attr('disabled', disabled);
},
_setDisabledAttr: function(/*Boolean*/ value){
// summary:
// Hook for attr('disabled', ...) to work.
// Enable or disable this menu item.
this.disabled = value;
dojo[value ? "addClass" : "removeClass"](this.domNode, 'dijitMenuItemDisabled');
dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false');
},
_setAccelKeyAttr: function(/*String*/ value){
// summary:
// Hook for attr('accelKey', ...) to work.
// Set accelKey on this menu item.
this.accelKey=value;
this.accelKeyNode.style.display=value?"":"none";
this.accelKeyNode.innerHTML=value;
//have to use colSpan to make it work in IE
dojo.attr(this.containerNode,'colSpan',value?"1":"2");
}
});
}
if(!dojo._hasResource["dijit.PopupMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.PopupMenuItem"] = true;
dojo.provide("dijit.PopupMenuItem");
dojo.declare("dijit.PopupMenuItem",
dijit.MenuItem,
{
_fillContent: function(){
// summary:
// When Menu is declared in markup, this code gets the menu label and
// the popup widget from the srcNodeRef.
// description:
// srcNodeRefinnerHTML contains both the menu item text and a popup widget
// The first part holds the menu item text and the second part is the popup
// example:
// | <div dojoType="dijit.PopupMenuItem">
// | <span>pick me</span>
// | <popup> ... </popup>
// | </div>
// tags:
// protected
if(this.srcNodeRef){
var nodes = dojo.query("*", this.srcNodeRef);
dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
// save pointer to srcNode so we can grab the drop down widget after it's instantiated
this.dropDownContainer = this.srcNodeRef;
}
},
startup: function(){
if(this._started){ return; }
this.inherited(arguments);
// we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's
// land now. move it to dojo.doc.body.
if(!this.popup){
var node = dojo.query("[widgetId]", this.dropDownContainer)[0];
this.popup = dijit.byNode(node);
}
dojo.body().appendChild(this.popup.domNode);
this.popup.domNode.style.display="none";
if(this.arrowWrapper){
dojo.style(this.arrowWrapper, "visibility", "");
}
dijit.setWaiState(this.focusNode, "haspopup", "true");
},
destroyDescendants: function(){
if(this.popup){
// Destroy the popup, unless it's already been destroyed. This can happen because
// the popup is a direct child of <body> even though it's logically my child.
if(!this.popup._destroyed){
this.popup.destroyRecursive();
}
delete this.popup;
}
this.inherited(arguments);
}
});
}
if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.CheckedMenuItem"] = true;
dojo.provide("dijit.CheckedMenuItem");
dojo.declare("dijit.CheckedMenuItem",
dijit.MenuItem,
{
// summary:
// A checkbox-like menu item for toggling on and off
templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\">\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">&#10003;</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t</td>\n</tr>\n"),
// checked: Boolean
// Our checked state
checked: false,
_setCheckedAttr: function(/*Boolean*/ checked){
// summary:
// Hook so attr('checked', bool) works.
// Sets the class and state for the check box.
dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked);
dijit.setWaiState(this.domNode, "checked", checked);
this.checked = checked;
},
onChange: function(/*Boolean*/ checked){
// summary:
// User defined function to handle check/uncheck events
// tags:
// callback
},
_onClick: function(/*Event*/ e){
// summary:
// Clicking this item just toggles its state
// tags:
// private
if(!this.disabled){
this.attr("checked", !this.checked);
this.onChange(this.checked);
}
this.inherited(arguments);
}
});
}
if(!dojo._hasResource["dijit.MenuSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.MenuSeparator"] = true;
dojo.provide("dijit.MenuSeparator");
dojo.declare("dijit.MenuSeparator",
[dijit._Widget, dijit._Templated, dijit._Contained],
{
// summary:
// A line between two menu items
templateString: dojo.cache("dijit", "templates/MenuSeparator.html", "<tr class=\"dijitMenuSeparator\">\n\t<td colspan=\"4\">\n\t\t<div class=\"dijitMenuSeparatorTop\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n</tr>\n"),
postCreate: function(){
dojo.setSelectable(this.domNode, false);
},
isFocusable: function(){
// summary:
// Override to always return false
// tags:
// protected
return false; // Boolean
}
});
}
if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.Menu"] = true;
dojo.provide("dijit.Menu");
dojo.declare("dijit._MenuBase",
[dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
{
// summary:
// Base class for Menu and MenuBar
// parentMenu: [readonly] Widget
// pointer to menu that displayed me
parentMenu: null,
// popupDelay: Integer
// number of milliseconds before hovering (without clicking) causes the popup to automatically open.
popupDelay: 500,
startup: function(){
if(this._started){ return; }
dojo.forEach(this.getChildren(), function(child){ child.startup(); });
this.startupKeyNavChildren();
this.inherited(arguments);
},
onExecute: function(){
// summary:
// Attach point for notification about when a menu item has been executed.
// This is an internal mechanism used for Menus to signal to their parent to
// close them, because they are about to execute the onClick handler. In
// general developers should not attach to or override this method.
// tags:
// protected
},
onCancel: function(/*Boolean*/ closeAll){
// summary:
// Attach point for notification about when the user cancels the current menu
// This is an internal mechanism used for Menus to signal to their parent to
// close them. In general developers should not attach to or override this method.
// tags:
// protected
},
_moveToPopup: function(/*Event*/ evt){
// summary:
// This handles the right arrow key (left arrow key on RTL systems),
// which will either open a submenu, or move to the next item in the
// ancestor MenuBar
// tags:
// private
if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
this.focusedChild._onClick(evt);
}else{
var topMenu = this._getTopMenu();
if(topMenu && topMenu._isMenuBar){
topMenu.focusNext();
}
}
},
_onPopupHover: function(/*Event*/ evt){
// summary:
// This handler is called when the mouse moves over the popup.
// tags:
// private
// if the mouse hovers over a menu popup that is in pending-close state,
// then stop the close operation.
// This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
if(this.currentPopup && this.currentPopup._pendingClose_timer){
var parentMenu = this.currentPopup.parentMenu;
// highlight the parent menu item pointing to this popup
if(parentMenu.focusedChild){
parentMenu.focusedChild._setSelected(false);
}
parentMenu.focusedChild = this.currentPopup.from_item;
parentMenu.focusedChild._setSelected(true);
// cancel the pending close
this._stopPendingCloseTimer(this.currentPopup);
}
},
onItemHover: function(/*MenuItem*/ item){
// summary:
// Called when cursor is over a MenuItem.
// tags:
// protected
// Don't do anything unless user has "activated" the menu by:
// 1) clicking it
// 2) opening it from a parent menu (which automatically focuses it)
if(this.isActive){
this.focusChild(item);
if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay);
}
}
// if the user is mixing mouse and keyboard navigation,
// then the menu may not be active but a menu item has focus,
// but it's not the item that the mouse just hovered over.
// To avoid both keyboard and mouse selections, use the latest.
if(this.focusedChild){
this.focusChild(item);
}
this._hoveredChild = item;
},
_onChildBlur: function(item){
// summary:
// Called when a child MenuItem becomes inactive because focus
// has been removed from the MenuItem *and* it's descendant menus.
// tags:
// private
this._stopPopupTimer();
item._setSelected(false);
// Close all popups that are open and descendants of this menu
var itemPopup = item.popup;
if(itemPopup){
this._stopPendingCloseTimer(itemPopup);
itemPopup._pendingClose_timer = setTimeout(function(){
itemPopup._pendingClose_timer = null;
if(itemPopup.parentMenu){
itemPopup.parentMenu.currentPopup = null;
}
dijit.popup.close(itemPopup); // this calls onClose
}, this.popupDelay);
}
},
onItemUnhover: function(/*MenuItem*/ item){
// summary:
// Callback fires when mouse exits a MenuItem
// tags:
// protected
if(this.isActive){
this._stopPopupTimer();
}
if(this._hoveredChild == item){ this._hoveredChild = null; }
},
_stopPopupTimer: function(){
// summary:
// Cancels the popup timer because the user has stop hovering
// on the MenuItem, etc.
// tags:
// private
if(this.hover_timer){
clearTimeout(this.hover_timer);
this.hover_timer = null;
}
},
_stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
// summary:
// Cancels the pending-close timer because the close has been preempted
// tags:
// private
if(popup._pendingClose_timer){
clearTimeout(popup._pendingClose_timer);
popup._pendingClose_timer = null;
}
},
_stopFocusTimer: function(){
// summary:
// Cancels the pending-focus timer because the menu was closed before focus occured
// tags:
// private
if(this._focus_timer){
clearTimeout(this._focus_timer);
this._focus_timer = null;
}
},
_getTopMenu: function(){
// summary:
// Returns the top menu in this chain of Menus
// tags:
// private
for(var top=this; top.parentMenu; top=top.parentMenu);
return top;
},
onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
// summary:
// Handle clicks on an item.
// tags:
// private
if(item.disabled){ return false; }
// this can't be done in _onFocus since the _onFocus events occurs asynchronously
if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
this._markActive();
}
this.focusChild(item);
if(item.popup){
this._openPopup();
}else{
// before calling user defined handler, close hierarchy of menus
// and restore focus to place it was when menu was opened
this.onExecute();
// user defined handler for click
item.onClick(evt);
}
},
_openPopup: function(){
// summary:
// Open the popup to the side of/underneath the current menu item
// tags:
// protected
this._stopPopupTimer();
var from_item = this.focusedChild;
if(!from_item){ return; } // the focused child lost focus since the timer was started
var popup = from_item.popup;
if(popup.isShowingNow){ return; }
if(this.currentPopup){
this._stopPendingCloseTimer(this.currentPopup);
dijit.popup.close(this.currentPopup);
}
popup.parentMenu = this;
popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
var self = this;
dijit.popup.open({
parent: this,
popup: popup,
around: from_item.domNode,
orient: this._orient || (this.isLeftToRight() ?
{'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} :
{'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}),
onCancel: function(){ // called when the child menu is canceled
// set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
// which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
self.focusChild(from_item); // put focus back on my node
self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
from_item._setSelected(true); // oops, _cleanUp() deselected the item
self.focusedChild = from_item; // and unset focusedChild
},
onExecute: dojo.hitch(this, "_cleanUp")
});
this.currentPopup = popup;
// detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close
if(popup.focus){
// If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar),
// if the cursor happens to collide with the popup, it will generate an onmouseover event
// even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that
// our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
popup._focus_timer = setTimeout(dojo.hitch(popup, function(){
this._focus_timer = null;
this.focus();
}), 0);
}
},
_markActive: function(){
// summary:
// Mark this menu's state as active.
// Called when this Menu gets focus from:
// 1) clicking it (mouse or via space/arrow key)
// 2) being opened by a parent menu.
// This is not called just from mouse hover.
// Focusing a menu via TAB does NOT automatically set isActive
// since TAB is a navigation operation and not a selection one.
// For Windows apps, pressing the ALT key focuses the menubar
// menus (similar to TAB navigation) but the menu is not active
// (ie no dropdown) until an item is clicked.
this.isActive = true;
dojo.addClass(this.domNode, "dijitMenuActive");
dojo.removeClass(this.domNode, "dijitMenuPassive");
},
onOpen: function(/*Event*/ e){
// summary:
// Callback when this menu is opened.
// This is called by the popup manager as notification that the menu
// was opened.
// tags:
// private
this.isShowingNow = true;
this._markActive();
},
_markInactive: function(){
// summary:
// Mark this menu's state as inactive.
this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
dojo.removeClass(this.domNode, "dijitMenuActive");
dojo.addClass(this.domNode, "dijitMenuPassive");
},
onClose: function(){
// summary:
// Callback when this menu is closed.
// This is called by the popup manager as notification that the menu
// was closed.
// tags:
// private
this._stopFocusTimer();
this._markInactive();
this.isShowingNow = false;
this.parentMenu = null;
},
_closeChild: function(){
// summary:
// Called when submenu is clicked or focus is lost. Close hierarchy of menus.
// tags:
// private
this._stopPopupTimer();
if(this.focusedChild){ // unhighlight the focused item
this.focusedChild._setSelected(false);
this.focusedChild._onUnhover();
this.focusedChild = null;
}
if(this.currentPopup){
// Close all popups that are open and descendants of this menu
dijit.popup.close(this.currentPopup);
this.currentPopup = null;
}
},
_onItemFocus: function(/*MenuItem*/ item){
// summary:
// Called when child of this Menu gets focus from:
// 1) clicking it
// 2) tabbing into it
// 3) being opened by a parent menu.
// This is not called just from mouse hover.
if(this._hoveredChild && this._hoveredChild != item){
this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection
}
},
_onBlur: function(){
// summary:
// Called when focus is moved away from this Menu and it's submenus.
// tags:
// protected
this._cleanUp();
this.inherited(arguments);
},
_cleanUp: function(){
// summary:
// Called when the user is done with this menu. Closes hierarchy of menus.
// tags:
// private
this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose
this._markInactive();
}
}
});
dojo.declare("dijit.Menu",
dijit._MenuBase,
{
// summary
// A context menu you can assign to multiple elements
// TODO: most of the code in here is just for context menu (right-click menu)
// support. In retrospect that should have been a separate class (dijit.ContextMenu).
// Split them for 2.0
constructor: function(){
this._bindings = [];
},
templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" waiRole=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),
// targetNodeIds: [const] String[]
// Array of dom node ids of nodes to attach to.
// Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
targetNodeIds: [],
// contextMenuForWindow: [const] Boolean
// If true, right clicking anywhere on the window will cause this context menu to open.
// If false, must specify targetNodeIds.
contextMenuForWindow: false,
// leftClickToOpen: [const] Boolean
// If true, menu will open on left click instead of right click, similiar to a file menu.
leftClickToOpen: false,
// refocus: Boolean
// When this menu closes, re-focus the element which had focus before it was opened.
refocus: true,
// _contextMenuWithMouse: [private] Boolean
// Used to record mouse and keyboard events to determine if a context
// menu is being opened with the keyboard or the mouse.
_contextMenuWithMouse: false,
postCreate: function(){
if(this.contextMenuForWindow){
this.bindDomNode(dojo.body());
}else{
// TODO: should have _setTargetNodeIds() method to handle initialization and a possible
// later attr('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
// gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
}
var k = dojo.keys, l = this.isLeftToRight();
this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW;
this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW;
this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]);
},
_onKeyPress: function(/*Event*/ evt){
// summary:
// Handle keyboard based menu navigation.
// tags:
// protected
if(evt.ctrlKey || evt.altKey){ return; }
switch(evt.charOrCode){
case this._openSubMenuKey:
this._moveToPopup(evt);
dojo.stopEvent(evt);
break;
case this._closeSubMenuKey:
if(this.parentMenu){
if(this.parentMenu._isMenuBar){
this.parentMenu.focusPrev();
}else{
this.onCancel(false);
}
}else{
dojo.stopEvent(evt);
}
break;
}
},
// thanks burstlib!
_iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
// summary:
// Returns the window reference of the passed iframe
// tags:
// private
var win = dijit.getDocumentWindow(this._iframeContentDocument(iframe_el)) ||
// Moz. TODO: is this available when defaultView isn't?
this._iframeContentDocument(iframe_el)['__parent__'] ||
(iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
return win; // Window
},
_iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
// summary:
// Returns a reference to the document object inside iframe_el
// tags:
// protected
var doc = iframe_el.contentDocument // W3
|| (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
|| (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
|| null;
return doc; // HTMLDocument
},
bindDomNode: function(/*String|DomNode*/ node){
// summary:
// Attach menu to given node
node = dojo.byId(node);
var cn; // Connect node
// Support context menus on iframes. Rather than binding to the iframe itself we need
// to bind to the <body> node inside the iframe.
if(node.tagName.toLowerCase() == "iframe"){
var iframe = node,
win = this._iframeContentWindow(iframe);
cn = dojo.withGlobal(win, dojo.body);
}else{
// To capture these events at the top level, attach to <html>, not <body>.
// Otherwise right-click context menu just doesn't work.
cn = (node == dojo.body() ? dojo.doc.documentElement : node);
}
// "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
var binding = {
node: node,
iframe: iframe
};
// Save info about binding in _bindings[], and make node itself record index(+1) into
// _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
// start with a number, which fails on FF/safari.
dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding));
// Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
// loading yet, in which case we need to wait for the onload event first, and then connect
var doConnects = dojo.hitch(this, function(cn){
return [
dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, function(evt){
this._openMyself(evt, cn, iframe);
}),
dojo.connect(cn, "onkeydown", this, "_contextKey"),
dojo.connect(cn, "onmousedown", this, "_contextMouse")
];
});
binding.connects = cn ? doConnects(cn) : [];
if(iframe){
// Setup handler to [re]bind to the iframe when the contents are initially loaded,
// and every time the contents change.
// Need to do this b/c we are actually binding to the iframe's <body> node.
// Note: can't use dojo.connect(), see #9609.
binding.onloadHandler = dojo.hitch(this, function(){
// want to remove old connections, but IE throws exceptions when trying to
// access the <body> node because it's already gone, or at least in a state of limbo
var win = this._iframeContentWindow(iframe);
cn = dojo.withGlobal(win, dojo.body);
binding.connects = doConnects(cn);
});
if(iframe.addEventListener){
iframe.addEventListener("load", binding.onloadHandler, false);
}else{
iframe.attachEvent("onload", binding.onloadHandler);
}
}
},
unBindDomNode: function(/*String|DomNode*/ nodeName){
// summary:
// Detach menu from given node
var node;
try{
node = dojo.byId(nodeName);
}catch(e){
// On IE the dojo.byId() call will get an exception if the attach point was
// the <body> node of an <iframe> that has since been reloaded (and thus the
// <body> node is in a limbo state of destruction.
return;
}
// node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array
var attrName = "_dijitMenu" + this.id;
if(node && dojo.hasAttr(node, attrName)){
var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid];
dojo.forEach(b.connects, dojo.disconnect);
// Remove listener for iframe onload events
var iframe = b.iframe;
if(iframe){
if(iframe.removeEventListener){
iframe.removeEventListener("load", b.onloadHandler, false);
}else{
iframe.detachEvent("onload", b.onloadHandler);
}
}
dojo.removeAttr(node, attrName);
delete this._bindings[bid];
}
},
_contextKey: function(e){
// summary:
// Code to handle popping up editor using F10 key rather than mouse
// tags:
// private
this._contextMenuWithMouse = false;
if(e.keyCode == dojo.keys.F10){
dojo.stopEvent(e);
if(e.shiftKey && e.type == "keydown"){
// FF: copying the wrong property from e will cause the system
// context menu to appear in spite of stopEvent. Don't know
// exactly which properties cause this effect.
var _e = { target: e.target, pageX: e.pageX, pageY: e.pageY };
_e.preventDefault = _e.stopPropagation = function(){};
// IE: without the delay, focus work in "open" causes the system
// context menu to appear in spite of stopEvent.
window.setTimeout(dojo.hitch(this, function(){ this._openMyself(_e); }), 1);
}
}
},
_contextMouse: function(e){
// summary:
// Helper to remember when we opened the context menu with the mouse instead
// of with the keyboard
// tags:
// private
this._contextMenuWithMouse = true;
},
_openMyself: function(/*Event*/ e, /*DomNode?*/ node, /*DomNode?*/ iframe){
// summary:
// Internal function for opening myself when the user
// does a right-click or something similar.
// node:
// The node that is being clicked
// iframe:
// If an <iframe> is being clicked, iframe points to that iframe and node
// points to the iframe's body.
// tags:
// private
if(this.leftClickToOpen && e.button>0){
return;
}
dojo.stopEvent(e);
// Get coordinates.
// If we are opening the menu with the mouse or on safari open
// the menu at the mouse cursor
// (Safari does not have a keyboard command to open the context menu
// and we don't currently have a reliable way to determine
// _contextMenuWithMouse on Safari)
var x,y;
if(dojo.isSafari || this._contextMenuWithMouse){
x=e.pageX;
y=e.pageY;
if(iframe){
// Event is on <body> node of an <iframe>, convert coordinates to match main document
var od = e.target.ownerDocument,
ifc = dojo.position(iframe, true),
win = this._iframeContentWindow(iframe),
scroll = dojo.withGlobal(win, "_docScroll", dojo);
var cs = dojo.getComputedStyle(iframe),
tp = dojo._toPixelValue,
left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0),
top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0);
x += ifc.x + left - scroll.x;
y += ifc.y + top - scroll.y;
}
}else{
// otherwise open near e.target
var coords = dojo.position(e.target, true);
x = coords.x + 10;
y = coords.y + 10;
}
var self=this;
var savedFocus = dijit.getFocus(this);
function closeAndRestoreFocus(){
// user has clicked on a menu or popup
if(self.refocus){
dijit.focus(savedFocus);
}
dijit.popup.close(self);
}
dijit.popup.open({
popup: this,
x: x,
y: y,
onExecute: closeAndRestoreFocus,
onCancel: closeAndRestoreFocus,
orient: this.isLeftToRight() ? 'L' : 'R'
});
this.focus();
this._onBlur = function(){
this.inherited('_onBlur', arguments);
// Usually the parent closes the child widget but if this is a context
// menu then there is no parent
dijit.popup.close(this);
// don't try to restore focus; user has clicked another part of the screen
// and set focus there
};
},
uninitialize: function(){
dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
this.inherited(arguments);
}
}
);
// Back-compat (TODO: remove in 2.0)
}
if(!dojo._hasResource["dojox.html.metrics"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.html.metrics"] = true;
dojo.provide("dojox.html.metrics");
(function(){
var dhm = dojox.html.metrics;
// derived from Morris John's emResized measurer
dhm.getFontMeasurements = function(){
// summary
// Returns an object that has pixel equivilents of standard font size values.
var heights = {
'1em':0, '1ex':0, '100%':0, '12pt':0, '16px':0, 'xx-small':0, 'x-small':0,
'small':0, 'medium':0, 'large':0, 'x-large':0, 'xx-large':0
};
if(dojo.isIE){
// we do a font-size fix if and only if one isn't applied already.
// NOTE: If someone set the fontSize on the HTML Element, this will kill it.
dojo.doc.documentElement.style.fontSize="100%";
}
// set up the measuring node.
var div=dojo.doc.createElement("div");
var ds = div.style;
ds.position="absolute";
ds.left="-100px";
ds.top="0";
ds.width="30px";
ds.height="1000em";
ds.border="0";
ds.margin="0";
ds.padding="0";
ds.outline="0";
ds.lineHeight="1";
ds.overflow="hidden";
dojo.body().appendChild(div);
// do the measurements.
for(var p in heights){
ds.fontSize = p;
heights[p] = Math.round(div.offsetHeight * 12/16) * 16/12 / 1000;
}
dojo.body().removeChild(div);
div = null;
return heights; // object
};
var fontMeasurements = null;
dhm.getCachedFontMeasurements = function(recalculate){
if(recalculate || !fontMeasurements){
fontMeasurements = dhm.getFontMeasurements();
}
return fontMeasurements;
};
var measuringNode = null, empty = {};
dhm.getTextBox = function(/* String */ text, /* Object */ style, /* String? */ className){
var m;
if(!measuringNode){
m = measuringNode = dojo.doc.createElement("div");
m.style.position = "absolute";
m.style.left = "0px";
m.style.top = "-10000px";
dojo.body().appendChild(m);
}else{
m = measuringNode;
}
// reset styles
m.className = "";
m.style.border = "0";
m.style.margin = "0";
m.style.padding = "0";
m.style.outline = "0";
// set new style
if(arguments.length > 1 && style){
for(var i in style){
if(i in empty){ continue; }
m.style[i] = style[i];
}
}
// set classes
if(arguments.length > 2 && className){
m.className = className;
}
// take a measure
m.innerHTML = text;
return dojo.marginBox(m);
};
// determine the scrollbar sizes on load.
var scroll={ w:16, h:16 };
dhm.getScrollbar=function(){ return { w:scroll.w, h:scroll.h }; };
dhm._fontResizeNode = null;
dhm.initOnFontResize = function(interval){
var f = dhm._fontResizeNode = dojo.doc.createElement("iframe");
var fs = f.style;
fs.position = "absolute";
fs.width = "5em";
fs.height = "10em";
fs.top = "-10000px";
if(dojo.isIE){
f.onreadystatechange = function(){
if(f.contentWindow.document.readyState == "complete"){
f.onresize = f.contentWindow.parent[dojox._scopeName].html.metrics._fontresize;
}
};
}else{
f.onload = function(){
f.contentWindow.onresize = f.contentWindow.parent[dojox._scopeName].html.metrics._fontresize;
};
}
//The script tag is to work around a known firebug race condition. See comments in bug #9046
f.setAttribute("src", "javascript:'<html><head><script>if(\"loadFirebugConsole\" in window){window.loadFirebugConsole();}</script></head><body></body></html>'");
dojo.body().appendChild(f);
dhm.initOnFontResize = function(){};
};
dhm.onFontResize = function(){};
dhm._fontresize = function(){
dhm.onFontResize();
}
dojo.addOnUnload(function(){
// destroy our font resize iframe if we have one
var f = dhm._fontResizeNode;
if(f){
if(dojo.isIE && f.onresize){
f.onresize = null;
}else if(f.contentWindow && f.contentWindow.onresize){
f.contentWindow.onresize = null;
}
dhm._fontResizeNode = null;
}
});
dojo.addOnLoad(function(){
// getScrollbar metrics node
try{
var n=dojo.doc.createElement("div");
n.style.cssText = "top:0;left:0;width:100px;height:100px;overflow:scroll;position:absolute;visibility:hidden;";
dojo.body().appendChild(n);
scroll.w = n.offsetWidth - n.clientWidth;
scroll.h = n.offsetHeight - n.clientHeight;
dojo.body().removeChild(n);
//console.log("Scroll bar dimensions: ", scroll);
delete n;
}catch(e){}
// text size poll setup
if("fontSizeWatch" in dojo.config && !!dojo.config.fontSizeWatch){
dhm.initOnFontResize();
}
});
})();
}
if(!dojo._hasResource["dojox.grid.util"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.util"] = true;
dojo.provide("dojox.grid.util");
// summary: grid utility library
(function(){
var dgu = dojox.grid.util;
dgu.na = '...';
dgu.rowIndexTag = "gridRowIndex";
dgu.gridViewTag = "gridView";
dgu.fire = function(ob, ev, args){
var fn = ob && ev && ob[ev];
return fn && (args ? fn.apply(ob, args) : ob[ev]());
};
dgu.setStyleHeightPx = function(inElement, inHeight){
if(inHeight >= 0){
var s = inElement.style;
var v = inHeight + 'px';
if(inElement && s['height'] != v){
s['height'] = v;
}
}
};
dgu.mouseEvents = [ 'mouseover', 'mouseout', /*'mousemove',*/ 'mousedown', 'mouseup', 'click', 'dblclick', 'contextmenu' ];
dgu.keyEvents = [ 'keyup', 'keydown', 'keypress' ];
dgu.funnelEvents = function(inNode, inObject, inMethod, inEvents){
var evts = (inEvents ? inEvents : dgu.mouseEvents.concat(dgu.keyEvents));
for (var i=0, l=evts.length; i<l; i++){
inObject.connect(inNode, 'on' + evts[i], inMethod);
}
};
dgu.removeNode = function(inNode){
inNode = dojo.byId(inNode);
inNode && inNode.parentNode && inNode.parentNode.removeChild(inNode);
return inNode;
};
dgu.arrayCompare = function(inA, inB){
for(var i=0,l=inA.length; i<l; i++){
if(inA[i] != inB[i]){return false;}
}
return (inA.length == inB.length);
};
dgu.arrayInsert = function(inArray, inIndex, inValue){
if(inArray.length <= inIndex){
inArray[inIndex] = inValue;
}else{
inArray.splice(inIndex, 0, inValue);
}
};
dgu.arrayRemove = function(inArray, inIndex){
inArray.splice(inIndex, 1);
};
dgu.arraySwap = function(inArray, inI, inJ){
var cache = inArray[inI];
inArray[inI] = inArray[inJ];
inArray[inJ] = cache;
};
})();
}
if(!dojo._hasResource["dojox.grid._Scroller"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._Scroller"] = true;
dojo.provide("dojox.grid._Scroller");
(function(){
var indexInParent = function(inNode){
var i=0, n, p=inNode.parentNode;
while((n = p.childNodes[i++])){
if(n == inNode){
return i - 1;
}
}
return -1;
};
var cleanNode = function(inNode){
if(!inNode){
return;
}
var filter = function(inW){
return inW.domNode && dojo.isDescendant(inW.domNode, inNode, true);
};
var ws = dijit.registry.filter(filter);
for(var i=0, w; (w=ws[i]); i++){
w.destroy();
}
delete ws;
};
var getTagName = function(inNodeOrId){
var node = dojo.byId(inNodeOrId);
return (node && node.tagName ? node.tagName.toLowerCase() : '');
};
var nodeKids = function(inNode, inTag){
var result = [];
var i=0, n;
while((n = inNode.childNodes[i])){
i++;
if(getTagName(n) == inTag){
result.push(n);
}
}
return result;
};
var divkids = function(inNode){
return nodeKids(inNode, 'div');
};
dojo.declare("dojox.grid._Scroller", null, {
constructor: function(inContentNodes){
this.setContentNodes(inContentNodes);
this.pageHeights = [];
this.pageNodes = [];
this.stack = [];
},
// specified
rowCount: 0, // total number of rows to manage
defaultRowHeight: 32, // default height of a row
keepRows: 100, // maximum number of rows that should exist at one time
contentNode: null, // node to contain pages
scrollboxNode: null, // node that controls scrolling
// calculated
defaultPageHeight: 0, // default height of a page
keepPages: 10, // maximum number of pages that should exists at one time
pageCount: 0,
windowHeight: 0,
firstVisibleRow: 0,
lastVisibleRow: 0,
averageRowHeight: 0, // the average height of a row
// private
page: 0,
pageTop: 0,
// init
init: function(inRowCount, inKeepRows, inRowsPerPage){
switch(arguments.length){
case 3: this.rowsPerPage = inRowsPerPage;
case 2: this.keepRows = inKeepRows;
case 1: this.rowCount = inRowCount;
default: break;
}
this.defaultPageHeight = this.defaultRowHeight * this.rowsPerPage;
this.pageCount = this._getPageCount(this.rowCount, this.rowsPerPage);
this.setKeepInfo(this.keepRows);
this.invalidate();
if(this.scrollboxNode){
this.scrollboxNode.scrollTop = 0;
this.scroll(0);
this.scrollboxNode.onscroll = dojo.hitch(this, 'onscroll');
}
},
_getPageCount: function(rowCount, rowsPerPage){
return rowCount ? (Math.ceil(rowCount / rowsPerPage) || 1) : 0;
},
destroy: function(){
this.invalidateNodes();
delete this.contentNodes;
delete this.contentNode;
delete this.scrollboxNode;
},
setKeepInfo: function(inKeepRows){
this.keepRows = inKeepRows;
this.keepPages = !this.keepRows ? this.keepPages : Math.max(Math.ceil(this.keepRows / this.rowsPerPage), 2);
},
// nodes
setContentNodes: function(inNodes){
this.contentNodes = inNodes;
this.colCount = (this.contentNodes ? this.contentNodes.length : 0);
this.pageNodes = [];
for(var i=0; i<this.colCount; i++){
this.pageNodes[i] = [];
}
},
getDefaultNodes: function(){
return this.pageNodes[0] || [];
},
// updating
invalidate: function(){
this._invalidating = true;
this.invalidateNodes();
this.pageHeights = [];
this.height = (this.pageCount ? (this.pageCount - 1)* this.defaultPageHeight + this.calcLastPageHeight() : 0);
this.resize();
this._invalidating = false;
},
updateRowCount: function(inRowCount){
this.invalidateNodes();
this.rowCount = inRowCount;
// update page count, adjust document height
var oldPageCount = this.pageCount;
if(oldPageCount === 0){
//We want to have at least 1px in height to keep scroller. Otherwise with an
//empty grid you can't scroll to see the header.
this.height = 1;
}
this.pageCount = this._getPageCount(this.rowCount, this.rowsPerPage);
if(this.pageCount < oldPageCount){
for(var i=oldPageCount-1; i>=this.pageCount; i--){
this.height -= this.getPageHeight(i);
delete this.pageHeights[i];
}
}else if(this.pageCount > oldPageCount){
this.height += this.defaultPageHeight * (this.pageCount - oldPageCount - 1) + this.calcLastPageHeight();
}
this.resize();
},
// implementation for page manager
pageExists: function(inPageIndex){
return Boolean(this.getDefaultPageNode(inPageIndex));
},
measurePage: function(inPageIndex){
if(this.grid.rowHeight){
var height = this.grid.rowHeight + 1;
return ((inPageIndex + 1) * this.rowsPerPage > this.rowCount ?
this.rowCount - inPageIndex * this.rowsPerPage :
this.rowsPerPage) * height;
}
var n = this.getDefaultPageNode(inPageIndex);
return (n && n.innerHTML) ? n.offsetHeight : undefined;
},
positionPage: function(inPageIndex, inPos){
for(var i=0; i<this.colCount; i++){
this.pageNodes[i][inPageIndex].style.top = inPos + 'px';
}
},
repositionPages: function(inPageIndex){
var nodes = this.getDefaultNodes();
var last = 0;
for(var i=0; i<this.stack.length; i++){
last = Math.max(this.stack[i], last);
}
//
var n = nodes[inPageIndex];
var y = (n ? this.getPageNodePosition(n) + this.getPageHeight(inPageIndex) : 0);
for(var p=inPageIndex+1; p<=last; p++){
n = nodes[p];
if(n){
if(this.getPageNodePosition(n) == y){
return;
}
this.positionPage(p, y);
}
y += this.getPageHeight(p);
}
},
installPage: function(inPageIndex){
for(var i=0; i<this.colCount; i++){
this.contentNodes[i].appendChild(this.pageNodes[i][inPageIndex]);
}
},
preparePage: function(inPageIndex, inReuseNode){
var p = (inReuseNode ? this.popPage() : null);
for(var i=0; i<this.colCount; i++){
var nodes = this.pageNodes[i];
var new_p = (p === null ? this.createPageNode() : this.invalidatePageNode(p, nodes));
new_p.pageIndex = inPageIndex;
nodes[inPageIndex] = new_p;
}
},
// rendering implementation
renderPage: function(inPageIndex){
var nodes = [];
var i, j;
for(i=0; i<this.colCount; i++){
nodes[i] = this.pageNodes[i][inPageIndex];
}
for(i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
this.renderRow(j, nodes);
}
},
removePage: function(inPageIndex){
for(var i=0, j=inPageIndex*this.rowsPerPage; i<this.rowsPerPage; i++, j++){
this.removeRow(j);
}
},
destroyPage: function(inPageIndex){
for(var i=0; i<this.colCount; i++){
var n = this.invalidatePageNode(inPageIndex, this.pageNodes[i]);
if(n){
dojo.destroy(n);
}
}
},
pacify: function(inShouldPacify){
},
// pacification
pacifying: false,
pacifyTicks: 200,
setPacifying: function(inPacifying){
if(this.pacifying != inPacifying){
this.pacifying = inPacifying;
this.pacify(this.pacifying);
}
},
startPacify: function(){
this.startPacifyTicks = new Date().getTime();
},
doPacify: function(){
var result = (new Date().getTime() - this.startPacifyTicks) > this.pacifyTicks;
this.setPacifying(true);
this.startPacify();
return result;
},
endPacify: function(){
this.setPacifying(false);
},
// default sizing implementation
resize: function(){
if(this.scrollboxNode){
this.windowHeight = this.scrollboxNode.clientHeight;
}
for(var i=0; i<this.colCount; i++){
//We want to have 1px in height min to keep scroller. Otherwise can't scroll
//and see header in empty grid.
dojox.grid.util.setStyleHeightPx(this.contentNodes[i], Math.max(1,this.height));
}
// Calculate the average row height and update the defaults (row and page).
var needPage = (!this._invalidating);
if(!needPage){
var ah = this.grid.attr("autoHeight");
if(typeof ah == "number" && ah <= Math.min(this.rowsPerPage, this.rowCount)){
needPage = true;
}
}
if(needPage){
this.needPage(this.page, this.pageTop);
}
var rowsOnPage = (this.page < this.pageCount - 1) ? this.rowsPerPage : ((this.rowCount % this.rowsPerPage) || this.rowsPerPage);
var pageHeight = this.getPageHeight(this.page);
this.averageRowHeight = (pageHeight > 0 && rowsOnPage > 0) ? (pageHeight / rowsOnPage) : 0;
},
calcLastPageHeight: function(){
if(!this.pageCount){
return 0;
}
var lastPage = this.pageCount - 1;
var lastPageHeight = ((this.rowCount % this.rowsPerPage)||(this.rowsPerPage)) * this.defaultRowHeight;
this.pageHeights[lastPage] = lastPageHeight;
return lastPageHeight;
},
updateContentHeight: function(inDh){
this.height += inDh;
this.resize();
},
updatePageHeight: function(inPageIndex, fromBuild){
if(this.pageExists(inPageIndex)){
var oh = this.getPageHeight(inPageIndex);
var h = (this.measurePage(inPageIndex));
if(h === undefined){
h = oh;
}
this.pageHeights[inPageIndex] = h;
if(oh != h){
this.updateContentHeight(h - oh);
var ah = this.grid.attr("autoHeight");
if((typeof ah == "number" && ah > this.rowCount)||(ah === true && !fromBuild)){
this.grid.sizeChange();
}else{
this.repositionPages(inPageIndex);
}
}
return h;
}
return 0;
},
rowHeightChanged: function(inRowIndex){
this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage), false);
},
// scroller core
invalidateNodes: function(){
while(this.stack.length){
this.destroyPage(this.popPage());
}
},
createPageNode: function(){
var p = document.createElement('div');
dojo.attr(p,"role","presentation");
p.style.position = 'absolute';
//p.style.width = '100%';
p.style[dojo._isBodyLtr() ? "left" : "right"] = '0';
return p;
},
getPageHeight: function(inPageIndex){
var ph = this.pageHeights[inPageIndex];
return (ph !== undefined ? ph : this.defaultPageHeight);
},
// FIXME: this is not a stack, it's a FIFO list
pushPage: function(inPageIndex){
return this.stack.push(inPageIndex);
},
popPage: function(){
return this.stack.shift();
},
findPage: function(inTop){
var i = 0, h = 0;
for(var ph = 0; i<this.pageCount; i++, h += ph){
ph = this.getPageHeight(i);
if(h + ph >= inTop){
break;
}
}
this.page = i;
this.pageTop = h;
},
buildPage: function(inPageIndex, inReuseNode, inPos){
this.preparePage(inPageIndex, inReuseNode);
this.positionPage(inPageIndex, inPos);
// order of operations is key below
this.installPage(inPageIndex);
this.renderPage(inPageIndex);
// order of operations is key above
this.pushPage(inPageIndex);
},
needPage: function(inPageIndex, inPos){
var h = this.getPageHeight(inPageIndex), oh = h;
if(!this.pageExists(inPageIndex)){
this.buildPage(inPageIndex, this.keepPages&&(this.stack.length >= this.keepPages), inPos);
h = this.updatePageHeight(inPageIndex, true);
}else{
this.positionPage(inPageIndex, inPos);
}
return h;
},
onscroll: function(){
this.scroll(this.scrollboxNode.scrollTop);
},
scroll: function(inTop){
this.grid.scrollTop = inTop;
if(this.colCount){
this.startPacify();
this.findPage(inTop);
var h = this.height;
var b = this.getScrollBottom(inTop);
for(var p=this.page, y=this.pageTop; (p<this.pageCount)&&((b<0)||(y<b)); p++){
y += this.needPage(p, y);
}
this.firstVisibleRow = this.getFirstVisibleRow(this.page, this.pageTop, inTop);
this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b);
// indicates some page size has been updated
if(h != this.height){
this.repositionPages(p-1);
}
this.endPacify();
}
},
getScrollBottom: function(inTop){
return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1);
},
// events
processNodeEvent: function(e, inNode){
var t = e.target;
while(t && (t != inNode) && t.parentNode && (t.parentNode.parentNode != inNode)){
t = t.parentNode;
}
if(!t || !t.parentNode || (t.parentNode.parentNode != inNode)){
return false;
}
var page = t.parentNode;
e.topRowIndex = page.pageIndex * this.rowsPerPage;
e.rowIndex = e.topRowIndex + indexInParent(t);
e.rowTarget = t;
return true;
},
processEvent: function(e){
return this.processNodeEvent(e, this.contentNode);
},
// virtual rendering interface
renderRow: function(inRowIndex, inPageNode){
},
removeRow: function(inRowIndex){
},
// page node operations
getDefaultPageNode: function(inPageIndex){
return this.getDefaultNodes()[inPageIndex];
},
positionPageNode: function(inNode, inPos){
},
getPageNodePosition: function(inNode){
return inNode.offsetTop;
},
invalidatePageNode: function(inPageIndex, inNodes){
var p = inNodes[inPageIndex];
if(p){
delete inNodes[inPageIndex];
this.removePage(inPageIndex, p);
cleanNode(p);
p.innerHTML = '';
}
return p;
},
// scroll control
getPageRow: function(inPage){
return inPage * this.rowsPerPage;
},
getLastPageRow: function(inPage){
return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1;
},
getFirstVisibleRow: function(inPage, inPageTop, inScrollTop){
if(!this.pageExists(inPage)){
return 0;
}
var row = this.getPageRow(inPage);
var nodes = this.getDefaultNodes();
var rows = divkids(nodes[inPage]);
for(var i=0,l=rows.length; i<l && inPageTop<inScrollTop; i++, row++){
inPageTop += rows[i].offsetHeight;
}
return (row ? row - 1 : row);
},
getLastVisibleRow: function(inPage, inBottom, inScrollBottom){
if(!this.pageExists(inPage)){
return 0;
}
var nodes = this.getDefaultNodes();
var row = this.getLastPageRow(inPage);
var rows = divkids(nodes[inPage]);
for(var i=rows.length-1; i>=0 && inBottom>inScrollBottom; i--, row--){
inBottom -= rows[i].offsetHeight;
}
return row + 1;
},
findTopRow: function(inScrollTop){
var nodes = this.getDefaultNodes();
var rows = divkids(nodes[this.page]);
for(var i=0,l=rows.length,t=this.pageTop,h; i<l; i++){
h = rows[i].offsetHeight;
t += h;
if(t >= inScrollTop){
this.offset = h - (t - inScrollTop);
return i + this.page * this.rowsPerPage;
}
}
return -1;
},
findScrollTop: function(inRow){
var rowPage = Math.floor(inRow / this.rowsPerPage);
var t = 0;
var i, l;
for(i=0; i<rowPage; i++){
t += this.getPageHeight(i);
}
this.pageTop = t;
this.needPage(rowPage, this.pageTop);
var nodes = this.getDefaultNodes();
var rows = divkids(nodes[rowPage]);
var r = inRow - this.rowsPerPage * rowPage;
for(i=0,l=rows.length; i<l && i<r; i++){
t += rows[i].offsetHeight;
}
return t;
},
dummy: 0
});
})();
}
if(!dojo._hasResource["dojox.grid.cells._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.cells._base"] = true;
dojo.provide("dojox.grid.cells._base");
dojo.declare("dojox.grid._DeferredTextWidget", dijit._Widget, {
deferred: null,
_destroyOnRemove: true,
postCreate: function(){
if(this.deferred){
this.deferred.addBoth(dojo.hitch(this, function(text){
if(this.domNode){
this.domNode.innerHTML = text;
}
}));
}
}
});
(function(){
var focusSelectNode = function(inNode){
try{
dojox.grid.util.fire(inNode, "focus");
dojox.grid.util.fire(inNode, "select");
}catch(e){// IE sux bad
}
};
var whenIdle = function(/*inContext, inMethod, args ...*/){
setTimeout(dojo.hitch.apply(dojo, arguments), 0);
};
var dgc = dojox.grid.cells;
dojo.declare("dojox.grid.cells._Base", null, {
// summary:
// Respresents a grid cell and contains information about column options and methods
// for retrieving cell related information.
// Each column in a grid layout has a cell object and most events and many methods
// provide access to these objects.
styles: '',
classes: '',
editable: false,
alwaysEditing: false,
formatter: null,
defaultValue: '...',
value: null,
hidden: false,
noresize: false,
draggable: true,
//private
_valueProp: "value",
_formatPending: false,
constructor: function(inProps){
this._props = inProps || {};
dojo.mixin(this, inProps);
if(this.draggable === undefined){
this.draggable = true;
}
},
_defaultFormat: function(inValue, callArgs){
var s = this.grid.formatterScope || this;
var f = this.formatter;
if(f && s && typeof f == "string"){
f = this.formatter = s[f];
}
var v = (inValue != this.defaultValue && f) ? f.apply(s, callArgs) : inValue;
if(typeof v == "undefined"){
return this.defaultValue;
}
if(v && v.addBoth){
// Check if it's a deferred
v = new dojox.grid._DeferredTextWidget({deferred: v},
dojo.create("span", {innerHTML: this.defaultValue}));
}
if(v && v.declaredClass && v.startup){
return "<div class='dojoxGridStubNode' linkWidget='" +
v.id +
"' cellIdx='" +
this.index +
"'>" +
this.defaultValue +
"</div>";
}
return v;
},
// data source
format: function(inRowIndex, inItem){
// summary:
// provides the html for a given grid cell.
// inRowIndex: int
// grid row index
// returns: html for a given grid cell
var f, i=this.grid.edit.info, d=this.get ? this.get(inRowIndex, inItem) : (this.value || this.defaultValue);
d = (d && d.replace && this.grid.escapeHTMLInData) ? d.replace(/&/g, '&amp;').replace(/</g, '&lt;') : d;
if(this.editable && (this.alwaysEditing || (i.rowIndex==inRowIndex && i.cell==this))){
return this.formatEditing(d, inRowIndex);
}else{
return this._defaultFormat(d, [d, inRowIndex, this]);
}
},
formatEditing: function(inDatum, inRowIndex){
// summary:
// formats the cell for editing
// inDatum: anything
// cell data to edit
// inRowIndex: int
// grid row index
// returns: string of html to place in grid cell
},
// utility
getNode: function(inRowIndex){
// summary:
// gets the dom node for a given grid cell.
// inRowIndex: int
// grid row index
// returns: dom node for a given grid cell
return this.view.getCellNode(inRowIndex, this.index);
},
getHeaderNode: function(){
return this.view.getHeaderCellNode(this.index);
},
getEditNode: function(inRowIndex){
return (this.getNode(inRowIndex) || 0).firstChild || 0;
},
canResize: function(){
var uw = this.unitWidth;
return uw && (uw!=='auto');
},
isFlex: function(){
var uw = this.unitWidth;
return uw && dojo.isString(uw) && (uw=='auto' || uw.slice(-1)=='%');
},
// edit support
applyEdit: function(inValue, inRowIndex){
this.grid.edit.applyCellEdit(inValue, this, inRowIndex);
},
cancelEdit: function(inRowIndex){
this.grid.doCancelEdit(inRowIndex);
},
_onEditBlur: function(inRowIndex){
if(this.grid.edit.isEditCell(inRowIndex, this.index)){
//console.log('editor onblur', e);
this.grid.edit.apply();
}
},
registerOnBlur: function(inNode, inRowIndex){
if(this.commitOnBlur){
dojo.connect(inNode, "onblur", function(e){
// hack: if editor still thinks this editor is current some ms after it blurs, assume we've focused away from grid
setTimeout(dojo.hitch(this, "_onEditBlur", inRowIndex), 250);
});
}
},
//protected
needFormatNode: function(inDatum, inRowIndex){
this._formatPending = true;
whenIdle(this, "_formatNode", inDatum, inRowIndex);
},
cancelFormatNode: function(){
this._formatPending = false;
},
//private
_formatNode: function(inDatum, inRowIndex){
if(this._formatPending){
this._formatPending = false;
// make cell selectable
dojo.setSelectable(this.grid.domNode, true);
this.formatNode(this.getEditNode(inRowIndex), inDatum, inRowIndex);
}
},
//protected
formatNode: function(inNode, inDatum, inRowIndex){
// summary:
// format the editing dom node. Use when editor is a widget.
// inNode: dom node
// dom node for the editor
// inDatum: anything
// cell data to edit
// inRowIndex: int
// grid row index
if(dojo.isIE){
// IE sux bad
whenIdle(this, "focus", inRowIndex, inNode);
}else{
this.focus(inRowIndex, inNode);
}
},
dispatchEvent: function(m, e){
if(m in this){
return this[m](e);
}
},
//public
getValue: function(inRowIndex){
// summary:
// returns value entered into editor
// inRowIndex: int
// grid row index
// returns:
// value of editor
return this.getEditNode(inRowIndex)[this._valueProp];
},
setValue: function(inRowIndex, inValue){
// summary:
// set the value of the grid editor
// inRowIndex: int
// grid row index
// inValue: anything
// value of editor
var n = this.getEditNode(inRowIndex);
if(n){
n[this._valueProp] = inValue;
}
},
focus: function(inRowIndex, inNode){
// summary:
// focus the grid editor
// inRowIndex: int
// grid row index
// inNode: dom node
// editor node
focusSelectNode(inNode || this.getEditNode(inRowIndex));
},
save: function(inRowIndex){
// summary:
// save editor state
// inRowIndex: int
// grid row index
this.value = this.value || this.getValue(inRowIndex);
//console.log("save", this.value, inCell.index, inRowIndex);
},
restore: function(inRowIndex){
// summary:
// restore editor state
// inRowIndex: int
// grid row index
this.setValue(inRowIndex, this.value);
//console.log("restore", this.value, inCell.index, inRowIndex);
},
//protected
_finish: function(inRowIndex){
// summary:
// called when editing is completed to clean up editor
// inRowIndex: int
// grid row index
dojo.setSelectable(this.grid.domNode, false);
this.cancelFormatNode();
},
//public
apply: function(inRowIndex){
// summary:
// apply edit from cell editor
// inRowIndex: int
// grid row index
this.applyEdit(this.getValue(inRowIndex), inRowIndex);
this._finish(inRowIndex);
},
cancel: function(inRowIndex){
// summary:
// cancel cell edit
// inRowIndex: int
// grid row index
this.cancelEdit(inRowIndex);
this._finish(inRowIndex);
}
});
dgc._Base.markupFactory = function(node, cellDef){
var d = dojo;
var formatter = d.trim(d.attr(node, "formatter")||"");
if(formatter){
cellDef.formatter = dojo.getObject(formatter)||formatter;
}
var get = d.trim(d.attr(node, "get")||"");
if(get){
cellDef.get = dojo.getObject(get);
}
var getBoolAttr = function(attr, cell, cellAttr){
var value = d.trim(d.attr(node, attr)||"");
if(value){ cell[cellAttr||attr] = !(value.toLowerCase()=="false"); }
};
getBoolAttr("sortDesc", cellDef);
getBoolAttr("editable", cellDef);
getBoolAttr("alwaysEditing", cellDef);
getBoolAttr("noresize", cellDef);
getBoolAttr("draggable", cellDef);
var value = d.trim(d.attr(node, "loadingText")||d.attr(node, "defaultValue")||"");
if(value){
cellDef.defaultValue = value;
}
var getStrAttr = function(attr, cell, cellAttr){
var value = d.trim(d.attr(node, attr)||"")||undefined;
if(value){ cell[cellAttr||attr] = value; }
};
getStrAttr("styles", cellDef);
getStrAttr("headerStyles", cellDef);
getStrAttr("cellStyles", cellDef);
getStrAttr("classes", cellDef);
getStrAttr("headerClasses", cellDef);
getStrAttr("cellClasses", cellDef);
};
dojo.declare("dojox.grid.cells.Cell", dgc._Base, {
// summary
// grid cell that provides a standard text input box upon editing
constructor: function(){
this.keyFilter = this.keyFilter;
},
// keyFilter: RegExp
// optional regex for disallowing keypresses
keyFilter: null,
formatEditing: function(inDatum, inRowIndex){
this.needFormatNode(inDatum, inRowIndex);
return '<input class="dojoxGridInput" type="text" value="' + inDatum + '">';
},
formatNode: function(inNode, inDatum, inRowIndex){
this.inherited(arguments);
// FIXME: feels too specific for this interface
this.registerOnBlur(inNode, inRowIndex);
},
doKey: function(e){
if(this.keyFilter){
var key = String.fromCharCode(e.charCode);
if(key.search(this.keyFilter) == -1){
dojo.stopEvent(e);
}
}
},
_finish: function(inRowIndex){
this.inherited(arguments);
var n = this.getEditNode(inRowIndex);
try{
dojox.grid.util.fire(n, "blur");
}catch(e){}
}
});
dgc.Cell.markupFactory = function(node, cellDef){
dgc._Base.markupFactory(node, cellDef);
var d = dojo;
var keyFilter = d.trim(d.attr(node, "keyFilter")||"");
if(keyFilter){
cellDef.keyFilter = new RegExp(keyFilter);
}
};
dojo.declare("dojox.grid.cells.RowIndex", dgc.Cell, {
name: 'Row',
postscript: function(){
this.editable = false;
},
get: function(inRowIndex){
return inRowIndex + 1;
}
});
dgc.RowIndex.markupFactory = function(node, cellDef){
dgc.Cell.markupFactory(node, cellDef);
};
dojo.declare("dojox.grid.cells.Select", dgc.Cell, {
// summary:
// grid cell that provides a standard select for editing
// options: Array
// text of each item
options: null,
// values: Array
// value for each item
values: null,
// returnIndex: Integer
// editor returns only the index of the selected option and not the value
returnIndex: -1,
constructor: function(inCell){
this.values = this.values || this.options;
},
formatEditing: function(inDatum, inRowIndex){
this.needFormatNode(inDatum, inRowIndex);
var h = [ '<select class="dojoxGridSelect">' ];
for (var i=0, o, v; ((o=this.options[i]) !== undefined)&&((v=this.values[i]) !== undefined); i++){
h.push("<option", (inDatum==v ? ' selected' : ''), ' value="' + v + '"', ">", o, "</option>");
}
h.push('</select>');
return h.join('');
},
getValue: function(inRowIndex){
var n = this.getEditNode(inRowIndex);
if(n){
var i = n.selectedIndex, o = n.options[i];
return this.returnIndex > -1 ? i : o.value || o.innerHTML;
}
}
});
dgc.Select.markupFactory = function(node, cell){
dgc.Cell.markupFactory(node, cell);
var d=dojo;
var options = d.trim(d.attr(node, "options")||"");
if(options){
var o = options.split(',');
if(o[0] != options){
cell.options = o;
}
}
var values = d.trim(d.attr(node, "values")||"");
if(values){
var v = values.split(',');
if(v[0] != values){
cell.values = v;
}
}
};
dojo.declare("dojox.grid.cells.AlwaysEdit", dgc.Cell, {
// summary:
// grid cell that is always in an editable state, regardless of grid editing state
alwaysEditing: true,
_formatNode: function(inDatum, inRowIndex){
this.formatNode(this.getEditNode(inRowIndex), inDatum, inRowIndex);
},
applyStaticValue: function(inRowIndex){
var e = this.grid.edit;
e.applyCellEdit(this.getValue(inRowIndex), this, inRowIndex);
e.start(this, inRowIndex, true);
}
});
dgc.AlwaysEdit.markupFactory = function(node, cell){
dgc.Cell.markupFactory(node, cell);
};
dojo.declare("dojox.grid.cells.Bool", dgc.AlwaysEdit, {
// summary:
// grid cell that provides a standard checkbox that is always on for editing
_valueProp: "checked",
formatEditing: function(inDatum, inRowIndex){
return '<input class="dojoxGridInput" type="checkbox"' + (inDatum ? ' checked="checked"' : '') + ' style="width: auto" />';
},
doclick: function(e){
if(e.target.tagName == 'INPUT'){
this.applyStaticValue(e.rowIndex);
}
}
});
dgc.Bool.markupFactory = function(node, cell){
dgc.AlwaysEdit.markupFactory(node, cell);
};
})();
}
if(!dojo._hasResource["dojox.grid.cells"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.cells"] = true;
dojo.provide("dojox.grid.cells");
}
if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.common"] = true;
dojo.provide("dojo.dnd.common");
dojo.dnd.getCopyKeyState = dojo.isCopyKeyPressed;
dojo.dnd._uniqueId = 0;
dojo.dnd.getUniqueId = function(){
// summary:
// returns a unique string for use with any DOM element
var id;
do{
id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId);
}while(dojo.byId(id));
return id;
};
dojo.dnd._empty = {};
dojo.dnd.isFormElement = function(/*Event*/ e){
// summary:
// returns true if user clicked on a form element
var t = e.target;
if(t.nodeType == 3 /*TEXT_NODE*/){
t = t.parentNode;
}
return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean
};
}
if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.autoscroll"] = true;
dojo.provide("dojo.dnd.autoscroll");
dojo.dnd.getViewport = function(){
// summary:
// Returns a viewport size (visible part of the window)
// TODO: remove this when getViewport() moved to dojo core, see #7028
// FIXME: need more docs!!
var d = dojo.doc, dd = d.documentElement, w = window, b = dojo.body();
if(dojo.isMozilla){
return {w: dd.clientWidth, h: w.innerHeight}; // Object
}else if(!dojo.isOpera && w.innerWidth){
return {w: w.innerWidth, h: w.innerHeight}; // Object
}else if (!dojo.isOpera && dd && dd.clientWidth){
return {w: dd.clientWidth, h: dd.clientHeight}; // Object
}else if (b.clientWidth){
return {w: b.clientWidth, h: b.clientHeight}; // Object
}
return null; // Object
};
dojo.dnd.V_TRIGGER_AUTOSCROLL = 32;
dojo.dnd.H_TRIGGER_AUTOSCROLL = 32;
dojo.dnd.V_AUTOSCROLL_VALUE = 16;
dojo.dnd.H_AUTOSCROLL_VALUE = 16;
dojo.dnd.autoScroll = function(e){
// summary:
// a handler for onmousemove event, which scrolls the window, if
// necesary
// e: Event
// onmousemove event
// FIXME: needs more docs!
var v = dojo.dnd.getViewport(), dx = 0, dy = 0;
if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){
dx = -dojo.dnd.H_AUTOSCROLL_VALUE;
}else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){
dx = dojo.dnd.H_AUTOSCROLL_VALUE;
}
if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){
dy = -dojo.dnd.V_AUTOSCROLL_VALUE;
}else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){
dy = dojo.dnd.V_AUTOSCROLL_VALUE;
}
window.scrollBy(dx, dy);
};
dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1};
dojo.dnd._validOverflow = {"auto": 1, "scroll": 1};
dojo.dnd.autoScrollNodes = function(e){
// summary:
// a handler for onmousemove event, which scrolls the first avaialble
// Dom element, it falls back to dojo.dnd.autoScroll()
// e: Event
// onmousemove event
// FIXME: needs more docs!
for(var n = e.target; n;){
if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){
var s = dojo.getComputedStyle(n);
if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){
var b = dojo._getContentBox(n, s), t = dojo.position(n, true);
//console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop);
var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2),
h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2),
rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0;
if(dojo.isWebKit || dojo.isOpera){
// FIXME: this code should not be here, it should be taken into account
// either by the event fixing code, or the dojo.position()
// FIXME: this code doesn't work on Opera 9.5 Beta
rx += dojo.body().scrollLeft, ry += dojo.body().scrollTop;
}
if(rx > 0 && rx < b.w){
if(rx < w){
dx = -w;
}else if(rx > b.w - w){
dx = w;
}
}
//console.log("ry =", ry, "b.h =", b.h, "h =", h);
if(ry > 0 && ry < b.h){
if(ry < h){
dy = -h;
}else if(ry > b.h - h){
dy = h;
}
}
var oldLeft = n.scrollLeft, oldTop = n.scrollTop;
n.scrollLeft = n.scrollLeft + dx;
n.scrollTop = n.scrollTop + dy;
if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; }
}
}
try{
n = n.parentNode;
}catch(x){
n = null;
}
}
dojo.dnd.autoScroll(e);
};
}
if(!dojo._hasResource["dojo.dnd.Mover"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Mover"] = true;
dojo.provide("dojo.dnd.Mover");
dojo.declare("dojo.dnd.Mover", null, {
constructor: function(node, e, host){
// summary:
// an object, which makes a node follow the mouse.
// Used as a default mover, and as a base class for custom movers.
// node: Node
// a node (or node's id) to be moved
// e: Event
// a mouse event, which started the move;
// only pageX and pageY properties are used
// host: Object?
// object which implements the functionality of the move,
// and defines proper events (onMoveStart and onMoveStop)
this.node = dojo.byId(node);
this.marginBox = {l: e.pageX, t: e.pageY};
this.mouseButton = e.button;
var h = this.host = host, d = node.ownerDocument,
firstEvent = dojo.connect(d, "onmousemove", this, "onFirstMove");
this.events = [
dojo.connect(d, "onmousemove", this, "onMouseMove"),
dojo.connect(d, "onmouseup", this, "onMouseUp"),
// cancel text selection and text dragging
dojo.connect(d, "ondragstart", dojo.stopEvent),
dojo.connect(d.body, "onselectstart", dojo.stopEvent),
firstEvent
];
// notify that the move has started
if(h && h.onMoveStart){
h.onMoveStart(this);
}
},
// mouse event processors
onMouseMove: function(e){
// summary:
// event processor for onmousemove
// e: Event
// mouse event
dojo.dnd.autoScroll(e);
var m = this.marginBox;
this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY});
dojo.stopEvent(e);
},
onMouseUp: function(e){
if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ?
e.button == 0 : this.mouseButton == e.button){
this.destroy();
}
dojo.stopEvent(e);
},
// utilities
onFirstMove: function(){
// summary:
// makes the node absolute; it is meant to be called only once.
// relative and absolutely positioned nodes are assumed to use pixel units
var s = this.node.style, l, t, h = this.host;
switch(s.position){
case "relative":
case "absolute":
// assume that left and top values are in pixels already
l = Math.round(parseFloat(s.left));
t = Math.round(parseFloat(s.top));
break;
default:
s.position = "absolute"; // enforcing the absolute mode
var m = dojo.marginBox(this.node);
// event.pageX/pageY (which we used to generate the initial
// margin box) includes padding and margin set on the body.
// However, setting the node's position to absolute and then
// doing dojo.marginBox on it *doesn't* take that additional
// space into account - so we need to subtract the combined
// padding and margin. We use getComputedStyle and
// _getMarginBox/_getContentBox to avoid the extra lookup of
// the computed style.
var b = dojo.doc.body;
var bs = dojo.getComputedStyle(b);
var bm = dojo._getMarginBox(b, bs);
var bc = dojo._getContentBox(b, bs);
l = m.l - (bc.l - bm.l);
t = m.t - (bc.t - bm.t);
break;
}
this.marginBox.l = l - this.marginBox.l;
this.marginBox.t = t - this.marginBox.t;
if(h && h.onFirstMove){
h.onFirstMove(this);
}
dojo.disconnect(this.events.pop());
},
destroy: function(){
// summary:
// stops the move, deletes all references, so the object can be garbage-collected
dojo.forEach(this.events, dojo.disconnect);
// undo global settings
var h = this.host;
if(h && h.onMoveStop){
h.onMoveStop(this);
}
// destroy objects
this.events = this.node = this.host = null;
}
});
}
if(!dojo._hasResource["dojo.dnd.Moveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Moveable"] = true;
dojo.provide("dojo.dnd.Moveable");
/*=====
dojo.declare("dojo.dnd.__MoveableArgs", [], {
// handle: Node||String
// A node (or node's id), which is used as a mouse handle.
// If omitted, the node itself is used as a handle.
handle: null,
// delay: Number
// delay move by this number of pixels
delay: 0,
// skip: Boolean
// skip move of form elements
skip: false,
// mover: Object
// a constructor of custom Mover
mover: dojo.dnd.Mover
});
=====*/
dojo.declare("dojo.dnd.Moveable", null, {
// object attributes (for markup)
handle: "",
delay: 0,
skip: false,
constructor: function(node, params){
// summary:
// an object, which makes a node moveable
// node: Node
// a node (or node's id) to be moved
// params: dojo.dnd.__MoveableArgs?
// optional parameters
this.node = dojo.byId(node);
if(!params){ params = {}; }
this.handle = params.handle ? dojo.byId(params.handle) : null;
if(!this.handle){ this.handle = this.node; }
this.delay = params.delay > 0 ? params.delay : 0;
this.skip = params.skip;
this.mover = params.mover ? params.mover : dojo.dnd.Mover;
this.events = [
dojo.connect(this.handle, "onmousedown", this, "onMouseDown"),
// cancel text selection and text dragging
dojo.connect(this.handle, "ondragstart", this, "onSelectStart"),
dojo.connect(this.handle, "onselectstart", this, "onSelectStart")
];
},
// markup methods
markupFactory: function(params, node){
return new dojo.dnd.Moveable(node, params);
},
// methods
destroy: function(){
// summary:
// stops watching for possible move, deletes all references, so the object can be garbage-collected
dojo.forEach(this.events, dojo.disconnect);
this.events = this.node = this.handle = null;
},
// mouse event processors
onMouseDown: function(e){
// summary:
// event processor for onmousedown, creates a Mover for the node
// e: Event
// mouse event
if(this.skip && dojo.dnd.isFormElement(e)){ return; }
if(this.delay){
this.events.push(
dojo.connect(this.handle, "onmousemove", this, "onMouseMove"),
dojo.connect(this.handle, "onmouseup", this, "onMouseUp")
);
this._lastX = e.pageX;
this._lastY = e.pageY;
}else{
this.onDragDetected(e);
}
dojo.stopEvent(e);
},
onMouseMove: function(e){
// summary:
// event processor for onmousemove, used only for delayed drags
// e: Event
// mouse event
if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){
this.onMouseUp(e);
this.onDragDetected(e);
}
dojo.stopEvent(e);
},
onMouseUp: function(e){
// summary:
// event processor for onmouseup, used only for delayed drags
// e: Event
// mouse event
for(var i = 0; i < 2; ++i){
dojo.disconnect(this.events.pop());
}
dojo.stopEvent(e);
},
onSelectStart: function(e){
// summary:
// event processor for onselectevent and ondragevent
// e: Event
// mouse event
if(!this.skip || !dojo.dnd.isFormElement(e)){
dojo.stopEvent(e);
}
},
// local events
onDragDetected: function(/* Event */ e){
// summary:
// called when the drag is detected;
// responsible for creation of the mover
new this.mover(this.node, e, this);
},
onMoveStart: function(/* dojo.dnd.Mover */ mover){
// summary:
// called before every move operation
dojo.publish("/dnd/move/start", [mover]);
dojo.addClass(dojo.body(), "dojoMove");
dojo.addClass(this.node, "dojoMoveItem");
},
onMoveStop: function(/* dojo.dnd.Mover */ mover){
// summary:
// called after every move operation
dojo.publish("/dnd/move/stop", [mover]);
dojo.removeClass(dojo.body(), "dojoMove");
dojo.removeClass(this.node, "dojoMoveItem");
},
onFirstMove: function(/* dojo.dnd.Mover */ mover){
// summary:
// called during the very first move notification;
// can be used to initialize coordinates, can be overwritten.
// default implementation does nothing
},
onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
// summary:
// called during every move notification;
// should actually move the node; can be overwritten.
this.onMoving(mover, leftTop);
var s = mover.node.style;
s.left = leftTop.l + "px";
s.top = leftTop.t + "px";
this.onMoved(mover, leftTop);
},
onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
// summary:
// called before every incremental move; can be overwritten.
// default implementation does nothing
},
onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
// summary:
// called after every incremental move; can be overwritten.
// default implementation does nothing
}
});
}
if(!dojo._hasResource["dojox.grid._Builder"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._Builder"] = true;
dojo.provide("dojox.grid._Builder");
(function(){
var dg = dojox.grid;
var getTdIndex = function(td){
return td.cellIndex >=0 ? td.cellIndex : dojo.indexOf(td.parentNode.cells, td);
};
var getTrIndex = function(tr){
return tr.rowIndex >=0 ? tr.rowIndex : dojo.indexOf(tr.parentNode.childNodes, tr);
};
var getTr = function(rowOwner, index){
return rowOwner && ((rowOwner.rows||0)[index] || rowOwner.childNodes[index]);
};
var findTable = function(node){
for(var n=node; n && n.tagName!='TABLE'; n=n.parentNode){}
return n;
};
var ascendDom = function(inNode, inWhile){
for(var n=inNode; n && inWhile(n); n=n.parentNode){}
return n;
};
var makeNotTagName = function(inTagName){
var name = inTagName.toUpperCase();
return function(node){ return node.tagName != name; };
};
var rowIndexTag = dojox.grid.util.rowIndexTag;
var gridViewTag = dojox.grid.util.gridViewTag;
// base class for generating markup for the views
dg._Builder = dojo.extend(function(view){
if(view){
this.view = view;
this.grid = view.grid;
}
},{
view: null,
// boilerplate HTML
_table: '<table class="dojoxGridRowTable" border="0" cellspacing="0" cellpadding="0" role="presentation"',
// Returns the table variable as an array - and with the view width, if specified
getTableArray: function(){
var html = [this._table];
if(this.view.viewWidth){
html.push([' style="width:', this.view.viewWidth, ';"'].join(''));
}
html.push('>');
return html;
},
// generate starting tags for a cell
generateCellMarkup: function(inCell, inMoreStyles, inMoreClasses, isHeader){
var result = [], html;
if(isHeader){
var sortInfo = inCell.index != inCell.grid.getSortIndex() ? "" : inCell.grid.sortInfo > 0 ? 'aria-sort="ascending"' : 'aria-sort="descending"';
if (!inCell.id){
inCell.id = this.grid.id + "Hdr" + inCell.index;
}
// column headers are not editable, mark as aria-readonly=true
html = ['<th tabIndex="-1" aria-readonly="true" role="columnheader"', sortInfo, 'id="', inCell.id, '"'];
}else{
// cells inherit grid aria-readonly property; default value for aria-readonly is false(grid is editable)
// if grid is editable (had any editable cells), mark non editable cells as aria-readonly=true
// if no editable cells, grid's aria-readonly value will have been set to true and cells will inherit
var editInfo = this.grid.editable && !inCell.editable ? 'aria-readonly="true"' : "";
html = ['<td tabIndex="-1" role="gridcell"', editInfo];
}
if(inCell.colSpan){
html.push(' colspan="', inCell.colSpan, '"');
}
if(inCell.rowSpan){
html.push(' rowspan="', inCell.rowSpan, '"');
}
html.push(' class="dojoxGridCell ');
if(inCell.classes){
html.push(inCell.classes, ' ');
}
if(inMoreClasses){
html.push(inMoreClasses, ' ');
}
// result[0] => td opener, style
result.push(html.join(''));
// SLOT: result[1] => td classes
result.push('');
html = ['" idx="', inCell.index, '" style="'];
if(inMoreStyles && inMoreStyles[inMoreStyles.length-1] != ';'){
inMoreStyles += ';';
}
html.push(inCell.styles, inMoreStyles||'', inCell.hidden?'display:none;':'');
if(inCell.unitWidth){
html.push('width:', inCell.unitWidth, ';');
}
// result[2] => markup
result.push(html.join(''));
// SLOT: result[3] => td style
result.push('');
html = [ '"' ];
if(inCell.attrs){
html.push(" ", inCell.attrs);
}
html.push('>');
// result[4] => td postfix
result.push(html.join(''));
// SLOT: result[5] => content
result.push('');
// result[6] => td closes
result.push(isHeader?'</th>':'</td>');
return result; // Array
},
// cell finding
isCellNode: function(inNode){
return Boolean(inNode && inNode!=dojo.doc && dojo.attr(inNode, "idx"));
},
getCellNodeIndex: function(inCellNode){
return inCellNode ? Number(dojo.attr(inCellNode, "idx")) : -1;
},
getCellNode: function(inRowNode, inCellIndex){
for(var i=0, row; (row=getTr(inRowNode.firstChild, i)); i++){
for(var j=0, cell; (cell=row.cells[j]); j++){
if(this.getCellNodeIndex(cell) == inCellIndex){
return cell;
}
}
}
return null;
},
findCellTarget: function(inSourceNode, inTopNode){
var n = inSourceNode;
while(n && (!this.isCellNode(n) || (n.offsetParent && gridViewTag in n.offsetParent.parentNode && n.offsetParent.parentNode[gridViewTag] != this.view.id)) && (n!=inTopNode)){
n = n.parentNode;
}
return n!=inTopNode ? n : null;
},
// event decoration
baseDecorateEvent: function(e){
e.dispatch = 'do' + e.type;
e.grid = this.grid;
e.sourceView = this.view;
e.cellNode = this.findCellTarget(e.target, e.rowNode);
e.cellIndex = this.getCellNodeIndex(e.cellNode);
e.cell = (e.cellIndex >= 0 ? this.grid.getCell(e.cellIndex) : null);
},
// event dispatch
findTarget: function(inSource, inTag){
var n = inSource;
while(n && (n!=this.domNode) && (!(inTag in n) || (gridViewTag in n && n[gridViewTag] != this.view.id))){
n = n.parentNode;
}
return (n != this.domNode) ? n : null;
},
findRowTarget: function(inSource){
return this.findTarget(inSource, rowIndexTag);
},
isIntraNodeEvent: function(e){
try{
return (e.cellNode && e.relatedTarget && dojo.isDescendant(e.relatedTarget, e.cellNode));
}catch(x){
// e.relatedTarget has permission problem in FF if it's an input: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
return false;
}
},
isIntraRowEvent: function(e){
try{
var row = e.relatedTarget && this.findRowTarget(e.relatedTarget);
return !row && (e.rowIndex==-1) || row && (e.rowIndex==row.gridRowIndex);
}catch(x){
// e.relatedTarget on INPUT has permission problem in FF: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
return false;
}
},
dispatchEvent: function(e){
if(e.dispatch in this){
return this[e.dispatch](e);
}
return false;
},
// dispatched event handlers
domouseover: function(e){
if(e.cellNode && (e.cellNode!=this.lastOverCellNode)){
this.lastOverCellNode = e.cellNode;
this.grid.onMouseOver(e);
}
this.grid.onMouseOverRow(e);
},
domouseout: function(e){
if(e.cellNode && (e.cellNode==this.lastOverCellNode) && !this.isIntraNodeEvent(e, this.lastOverCellNode)){
this.lastOverCellNode = null;
this.grid.onMouseOut(e);
if(!this.isIntraRowEvent(e)){
this.grid.onMouseOutRow(e);
}
}
},
domousedown: function(e){
if (e.cellNode)
this.grid.onMouseDown(e);
this.grid.onMouseDownRow(e);
}
});
// Produces html for grid data content. Owned by grid and used internally
// for rendering data. Override to implement custom rendering.
dg._ContentBuilder = dojo.extend(function(view){
dg._Builder.call(this, view);
},dg._Builder.prototype,{
update: function(){
this.prepareHtml();
},
// cache html for rendering data rows
prepareHtml: function(){
var defaultGet=this.grid.get, cells=this.view.structure.cells;
for(var j=0, row; (row=cells[j]); j++){
for(var i=0, cell; (cell=row[i]); i++){
cell.get = cell.get || (cell.value == undefined) && defaultGet;
cell.markup = this.generateCellMarkup(cell, cell.cellStyles, cell.cellClasses, false);
if (!this.grid.editable && cell.editable){
this.grid.editable = true;
}
}
}
},
// time critical: generate html using cache and data source
generateHtml: function(inDataIndex, inRowIndex){
var
html = this.getTableArray(),
v = this.view,
cells = v.structure.cells,
item = this.grid.getItem(inRowIndex);
dojox.grid.util.fire(this.view, "onBeforeRow", [inRowIndex, cells]);
for(var j=0, row; (row=cells[j]); j++){
if(row.hidden || row.header){
continue;
}
html.push(!row.invisible ? '<tr>' : '<tr class="dojoxGridInvisible">');
for(var i=0, cell, m, cc, cs; (cell=row[i]); i++){
m = cell.markup; cc = cell.customClasses = []; cs = cell.customStyles = [];
// content (format can fill in cc and cs as side-effects)
m[5] = cell.format(inRowIndex, item);
// classes
m[1] = cc.join(' ');
// styles
m[3] = cs.join(';');
// in-place concat
html.push.apply(html, m);
}
html.push('</tr>');
}
html.push('</table>');
return html.join(''); // String
},
decorateEvent: function(e){
e.rowNode = this.findRowTarget(e.target);
if(!e.rowNode){return false;}
e.rowIndex = e.rowNode[rowIndexTag];
this.baseDecorateEvent(e);
e.cell = this.grid.getCell(e.cellIndex);
return true; // Boolean
}
});
// Produces html for grid header content. Owned by grid and used internally
// for rendering data. Override to implement custom rendering.
dg._HeaderBuilder = dojo.extend(function(view){
this.moveable = null;
dg._Builder.call(this, view);
},dg._Builder.prototype,{
_skipBogusClicks: false,
overResizeWidth: 4,
minColWidth: 1,
update: function(){
if(this.tableMap){
this.tableMap.mapRows(this.view.structure.cells);
}else{
this.tableMap = new dg._TableMap(this.view.structure.cells);
}
},
generateHtml: function(inGetValue, inValue){
var html = this.getTableArray(), cells = this.view.structure.cells;
dojox.grid.util.fire(this.view, "onBeforeRow", [-1, cells]);
for(var j=0, row; (row=cells[j]); j++){
if(row.hidden){
continue;
}
html.push(!row.invisible ? '<tr>' : '<tr class="dojoxGridInvisible">');
for(var i=0, cell, markup; (cell=row[i]); i++){
cell.customClasses = [];
cell.customStyles = [];
if(this.view.simpleStructure){
if(cell.draggable){
if(cell.headerClasses){
if(cell.headerClasses.indexOf('dojoDndItem') == -1){
cell.headerClasses += ' dojoDndItem';
}
}else{
cell.headerClasses = 'dojoDndItem';
}
}
if(cell.attrs){
if(cell.attrs.indexOf("dndType='gridColumn_") == -1){
cell.attrs += " dndType='gridColumn_" + this.grid.id + "'";
}
}else{
cell.attrs = "dndType='gridColumn_" + this.grid.id + "'";
}
}
markup = this.generateCellMarkup(cell, cell.headerStyles, cell.headerClasses, true);
// content
markup[5] = (inValue != undefined ? inValue : inGetValue(cell));
// styles
markup[3] = cell.customStyles.join(';');
// classes
markup[1] = cell.customClasses.join(' '); //(cell.customClasses ? ' ' + cell.customClasses : '');
html.push(markup.join(''));
}
html.push('</tr>');
}
html.push('</table>');
return html.join('');
},
// event helpers
getCellX: function(e){
var n, x = e.layerX;
if(dojo.isMoz){
n = ascendDom(e.target, makeNotTagName("th"));
x -= (n && n.offsetLeft) || 0;
var t = e.sourceView.getScrollbarWidth();
if(!dojo._isBodyLtr() && e.sourceView.headerNode.scrollLeft < t)
x -= t;
//x -= getProp(ascendDom(e.target, mkNotTagName("td")), "offsetLeft") || 0;
}
n = ascendDom(e.target, function(){
if(!n || n == e.cellNode){
return false;
}
// Mozilla 1.8 (FF 1.5) has a bug that makes offsetLeft = -parent border width
// when parent has border, overflow: hidden, and is positioned
// handle this problem here ... not a general solution!
x += (n.offsetLeft < 0 ? 0 : n.offsetLeft);
return true;
});
return x;
},
// event decoration
decorateEvent: function(e){
this.baseDecorateEvent(e);
e.rowIndex = -1;
e.cellX = this.getCellX(e);
return true;
},
// event handlers
// resizing
prepareResize: function(e, mod){
do{
var i = getTdIndex(e.cellNode);
e.cellNode = (i ? e.cellNode.parentNode.cells[i+mod] : null);
e.cellIndex = (e.cellNode ? this.getCellNodeIndex(e.cellNode) : -1);
}while(e.cellNode && e.cellNode.style.display == "none");
return Boolean(e.cellNode);
},
canResize: function(e){
if(!e.cellNode || e.cellNode.colSpan > 1){
return false;
}
var cell = this.grid.getCell(e.cellIndex);
return !cell.noresize && cell.canResize();
},
overLeftResizeArea: function(e){
// We are never over a resize area if we are in the process of moving
if(dojo.hasClass(dojo.body(), "dojoDndMove")){
return false;
}
//Bugfix for crazy IE problem (#8807). IE returns position information for the icon and text arrow divs
//as if they were still on the left instead of returning the position they were 'float: right' to.
//So, the resize check ends up checking the wrong adjacent cell. This checks to see if the hover was over
//the image or text nodes, then just ignored them/treat them not in scale range.
if(dojo.isIE){
var tN = e.target;
if(dojo.hasClass(tN, "dojoxGridArrowButtonNode") ||
dojo.hasClass(tN, "dojoxGridArrowButtonChar")){
return false;
}
}
if(dojo._isBodyLtr()){
return (e.cellIndex>0) && (e.cellX < this.overResizeWidth) && this.prepareResize(e, -1);
}
var t = e.cellNode && (e.cellX < this.overResizeWidth);
return t;
},
overRightResizeArea: function(e){
// We are never over a resize area if we are in the process of moving
if(dojo.hasClass(dojo.body(), "dojoDndMove")){
return false;
}
//Bugfix for crazy IE problem (#8807). IE returns position information for the icon and text arrow divs
//as if they were still on the left instead of returning the position they were 'float: right' to.
//So, the resize check ends up checking the wrong adjacent cell. This checks to see if the hover was over
//the image or text nodes, then just ignored them/treat them not in scale range.
if(dojo.isIE){
var tN = e.target;
if(dojo.hasClass(tN, "dojoxGridArrowButtonNode") ||
dojo.hasClass(tN, "dojoxGridArrowButtonChar")){
return false;
}
}
if(dojo._isBodyLtr()){
return e.cellNode && (e.cellX >= e.cellNode.offsetWidth - this.overResizeWidth);
}
return (e.cellIndex>0) && (e.cellX >= e.cellNode.offsetWidth - this.overResizeWidth) && this.prepareResize(e, -1);
},
domousemove: function(e){
//console.log(e.cellIndex, e.cellX, e.cellNode.offsetWidth);
if(!this.moveable){
var c = (this.overRightResizeArea(e) ? 'dojoxGridColResize' : (this.overLeftResizeArea(e) ? 'dojoxGridColResize' : ''));
if(c && !this.canResize(e)){
c = 'dojoxGridColNoResize';
}
dojo.toggleClass(e.sourceView.headerNode, "dojoxGridColNoResize", (c == "dojoxGridColNoResize"));
dojo.toggleClass(e.sourceView.headerNode, "dojoxGridColResize", (c == "dojoxGridColResize"));
if(dojo.isIE){
var t = e.sourceView.headerNode.scrollLeft;
e.sourceView.headerNode.scrollLeft = t;
}
if(c){
dojo.stopEvent(e);
}
}
},
domousedown: function(e){
if(!this.moveable){
if((this.overRightResizeArea(e) || this.overLeftResizeArea(e)) && this.canResize(e)){
this.beginColumnResize(e);
}else{
this.grid.onMouseDown(e);
this.grid.onMouseOverRow(e);
}
//else{
// this.beginMoveColumn(e);
//}
}
},
doclick: function(e) {
if(this._skipBogusClicks){
dojo.stopEvent(e);
return true;
}
return false;
},
// column resizing
colResizeSetup: function(/*Event Object*/e, /*boolean*/ isMouse ){
//Set up the drag object for column resizing
// Called with mouse event in case of drag and drop,
// Also called from keyboard shift-arrow event when focus is on a header
var headContentBox = dojo.contentBox(e.sourceView.headerNode);
if(isMouse){ //IE draws line even with no mouse down so separate from keyboard
this.lineDiv = document.createElement('div');
// NOTE: this is for backwards compatibility with Dojo 1.3
var vw = (dojo.position||dojo._abs)(e.sourceView.headerNode, true);
var bodyContentBox = dojo.contentBox(e.sourceView.domNode);
dojo.style(this.lineDiv, {
top: vw.y + "px",
left: e.clientX + "px",
height: (bodyContentBox.h + headContentBox.h) + "px"
});
dojo.addClass(this.lineDiv, "dojoxGridResizeColLine");
this.lineDiv._origLeft = e.clientX;
dojo.body().appendChild(this.lineDiv);
}
var spanners = [], nodes = this.tableMap.findOverlappingNodes(e.cellNode);
for(var i=0, cell; (cell=nodes[i]); i++){
spanners.push({ node: cell, index: this.getCellNodeIndex(cell), width: cell.offsetWidth });
//console.log("spanner: " + this.getCellNodeIndex(cell));
}
var view = e.sourceView;
var adj = dojo._isBodyLtr() ? 1 : -1;
var views = e.grid.views.views;
var followers = [];
for(var j=view.idx+adj, cView; (cView=views[j]); j=j+adj){
followers.push({ node: cView.headerNode, left: window.parseInt(cView.headerNode.style.left) });
}
var table = view.headerContentNode.firstChild;
var drag = {
scrollLeft: e.sourceView.headerNode.scrollLeft,
view: view,
node: e.cellNode,
index: e.cellIndex,
w: dojo.contentBox(e.cellNode).w,
vw: headContentBox.w,
table: table,
tw: dojo.contentBox(table).w,
spanners: spanners,
followers: followers
};
return drag;
},
beginColumnResize: function(e){
this.moverDiv = document.createElement("div");
dojo.style(this.moverDiv,{position: "absolute", left:0}); // to make DnD work with dir=rtl
dojo.body().appendChild(this.moverDiv);
dojo.addClass(this.grid.domNode, "dojoxGridColumnResizing");
var m = (this.moveable = new dojo.dnd.Moveable(this.moverDiv));
var drag = this.colResizeSetup(e,true);
m.onMove = dojo.hitch(this, "doResizeColumn", drag);
dojo.connect(m, "onMoveStop", dojo.hitch(this, function(){
this.endResizeColumn(drag);
if(drag.node.releaseCapture){
drag.node.releaseCapture();
}
this.moveable.destroy();
delete this.moveable;
this.moveable = null;
dojo.removeClass(this.grid.domNode, "dojoxGridColumnResizing");
}));
if(e.cellNode.setCapture){
e.cellNode.setCapture();
}
m.onMouseDown(e);
},
doResizeColumn: function(inDrag, mover, leftTop){
var changeX = leftTop.l;
var data = {
deltaX: changeX,
w: inDrag.w + changeX,
vw: inDrag.vw + changeX,
tw: inDrag.tw + changeX
};
this.dragRecord = {inDrag: inDrag, mover: mover, leftTop:leftTop};
if(data.w >= this.minColWidth){
if (!mover) { // we are using keyboard do immediate resize
this.doResizeNow(inDrag, data);
}
else{
dojo.style(this.lineDiv, "left", (this.lineDiv._origLeft + data.deltaX) + "px");
}
}
},
endResizeColumn: function(inDrag){
if(this.dragRecord){
var leftTop = this.dragRecord.leftTop;
var changeX = dojo._isBodyLtr() ? leftTop.l : -leftTop.l;
// Make sure we are not under our minimum
// http://bugs.dojotoolkit.org/ticket/9390
changeX += Math.max(inDrag.w + changeX, this.minColWidth) - (inDrag.w + changeX);
if(dojo.isWebKit && inDrag.spanners.length){
// Webkit needs the pad border extents back in
changeX += dojo._getPadBorderExtents(inDrag.spanners[0].node).w;
}
var data = {
deltaX: changeX,
w: inDrag.w + changeX,
vw: inDrag.vw + changeX,
tw: inDrag.tw + changeX
};
// Only resize the columns when the drag has finished
this.doResizeNow(inDrag, data);
}
dojo.destroy(this.lineDiv);
dojo.destroy(this.moverDiv);
dojo.destroy(this.moverDiv);
delete this.moverDiv;
this._skipBogusClicks = true;
inDrag.view.update();
this._skipBogusClicks = false;
this.grid.onResizeColumn(inDrag.index);
},
doResizeNow: function(inDrag, data){
inDrag.view.convertColPctToFixed();
if(inDrag.view.flexCells && !inDrag.view.testFlexCells()){
var t = findTable(inDrag.node);
if(t){
(t.style.width = '');
}
}
var i, s, sw, f, fl;
for(i=0; (s=inDrag.spanners[i]); i++){
sw = s.width + data.deltaX;
s.node.style.width = sw + 'px';
inDrag.view.setColWidth(s.index, sw);
}
for(i=0; (f=inDrag.followers[i]); i++){
fl = f.left + data.deltaX;
f.node.style.left = fl + 'px';
}
inDrag.node.style.width = data.w + 'px';
inDrag.view.setColWidth(inDrag.index, data.w);
inDrag.view.headerNode.style.width = data.vw + 'px';
inDrag.view.setColumnsWidth(data.tw);
if(!dojo._isBodyLtr()){
inDrag.view.headerNode.scrollLeft = inDrag.scrollLeft + data.deltaX;
}
}
});
// Maps an html table into a structure parsable for information about cell row and col spanning.
// Used by HeaderBuilder.
dg._TableMap = dojo.extend(function(rows){
this.mapRows(rows);
},{
map: null,
mapRows: function(inRows){
// summary: Map table topography
//console.log('mapRows');
// # of rows
var rowCount = inRows.length;
if(!rowCount){
return;
}
// map which columns and rows fill which cells
this.map = [];
var row;
for(var k=0; (row=inRows[k]); k++){
this.map[k] = [];
}
for(var j=0; (row=inRows[j]); j++){
for(var i=0, x=0, cell, colSpan, rowSpan; (cell=row[i]); i++){
while(this.map[j][x]){x++;}
this.map[j][x] = { c: i, r: j };
rowSpan = cell.rowSpan || 1;
colSpan = cell.colSpan || 1;
for(var y=0; y<rowSpan; y++){
for(var s=0; s<colSpan; s++){
this.map[j+y][x+s] = this.map[j][x];
}
}
x += colSpan;
}
}
//this.dumMap();
},
dumpMap: function(){
for(var j=0, row, h=''; (row=this.map[j]); j++,h=''){
for(var i=0, cell; (cell=row[i]); i++){
h += cell.r + ',' + cell.c + ' ';
}
}
},
getMapCoords: function(inRow, inCol){
// summary: Find node's map coords by it's structure coords
for(var j=0, row; (row=this.map[j]); j++){
for(var i=0, cell; (cell=row[i]); i++){
if(cell.c==inCol && cell.r == inRow){
return { j: j, i: i };
}
//else{console.log(inRow, inCol, ' : ', i, j, " : ", cell.r, cell.c); };
}
}
return { j: -1, i: -1 };
},
getNode: function(inTable, inRow, inCol){
// summary: Find a node in inNode's table with the given structure coords
var row = inTable && inTable.rows[inRow];
return row && row.cells[inCol];
},
_findOverlappingNodes: function(inTable, inRow, inCol){
var nodes = [];
var m = this.getMapCoords(inRow, inCol);
//console.log("node j: %d, i: %d", m.j, m.i);
for(var j=0, row; (row=this.map[j]); j++){
if(j == m.j){ continue; }
var rw = row[m.i];
//console.log("overlaps: r: %d, c: %d", rw.r, rw.c);
var n = (rw?this.getNode(inTable, rw.r, rw.c):null);
if(n){ nodes.push(n); }
}
//console.log(nodes);
return nodes;
},
findOverlappingNodes: function(inNode){
return this._findOverlappingNodes(findTable(inNode), getTrIndex(inNode.parentNode), getTdIndex(inNode));
}
});
})();
}
if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Container"] = true;
dojo.provide("dojo.dnd.Container");
/*
Container states:
"" - normal state
"Over" - mouse over a container
Container item states:
"" - normal state
"Over" - mouse over a container item
*/
/*=====
dojo.declare("dojo.dnd.__ContainerArgs", [], {
creator: function(){
// summary:
// a creator function, which takes a data item, and returns an object like that:
// {node: newNode, data: usedData, type: arrayOfStrings}
},
// skipForm: Boolean
// don't start the drag operation, if clicked on form elements
skipForm: false,
// dropParent: Node||String
// node or node's id to use as the parent node for dropped items
// (must be underneath the 'node' parameter in the DOM)
dropParent: null,
// _skipStartup: Boolean
// skip startup(), which collects children, for deferred initialization
// (this is used in the markup mode)
_skipStartup: false
});
dojo.dnd.Item = function(){
// summary:
// Represents (one of) the source node(s) being dragged.
// Contains (at least) the "type" and "data" attributes.
// type: String[]
// Type(s) of this item, by default this is ["text"]
// data: Object
// Logical representation of the object being dragged.
// If the drag object's type is "text" then data is a String,
// if it's another type then data could be a different Object,
// perhaps a name/value hash.
this.type = type;
this.data = data;
}
=====*/
dojo.declare("dojo.dnd.Container", null, {
// summary:
// a Container object, which knows when mouse hovers over it,
// and over which element it hovers
// object attributes (for markup)
skipForm: false,
/*=====
// current: DomNode
// The DOM node the mouse is currently hovered over
current: null,
// map: Hash<String, dojo.dnd.Item>
// Map from an item's id (which is also the DOMNode's id) to
// the dojo.dnd.Item itself.
map: {},
=====*/
constructor: function(node, params){
// summary:
// a constructor of the Container
// node: Node
// node or node's id to build the container on
// params: dojo.dnd.__ContainerArgs
// a dictionary of parameters
this.node = dojo.byId(node);
if(!params){ params = {}; }
this.creator = params.creator || null;
this.skipForm = params.skipForm;
this.parent = params.dropParent && dojo.byId(params.dropParent);
// class-specific variables
this.map = {};
this.current = null;
// states
this.containerState = "";
dojo.addClass(this.node, "dojoDndContainer");
// mark up children
if(!(params && params._skipStartup)){
this.startup();
}
// set up events
this.events = [
dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
// cancel text selection and text dragging
dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
dojo.connect(this.node, "onselectstart", this, "onSelectStart")
];
},
// object attributes (for markup)
creator: function(){
// summary:
// creator function, dummy at the moment
},
// abstract access to the map
getItem: function(/*String*/ key){
// summary:
// returns a data item by its key (id)
return this.map[key]; // dojo.dnd.Item
},
setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
// summary:
// associates a data item with its key (id)
this.map[key] = data;
},
delItem: function(/*String*/ key){
// summary:
// removes a data item from the map by its key (id)
delete this.map[key];
},
forInItems: function(/*Function*/ f, /*Object?*/ o){
// summary:
// iterates over a data map skipping members that
// are present in the empty object (IE and/or 3rd-party libraries).
o = o || dojo.global;
var m = this.map, e = dojo.dnd._empty;
for(var i in m){
if(i in e){ continue; }
f.call(o, m[i], i, this);
}
return o; // Object
},
clearItems: function(){
// summary:
// removes all data items from the map
this.map = {};
},
// methods
getAllNodes: function(){
// summary:
// returns a list (an array) of all valid child nodes
return dojo.query("> .dojoDndItem", this.parent); // NodeList
},
sync: function(){
// summary:
// sync up the node list with the data map
var map = {};
this.getAllNodes().forEach(function(node){
if(node.id){
var item = this.getItem(node.id);
if(item){
map[node.id] = item;
return;
}
}else{
node.id = dojo.dnd.getUniqueId();
}
var type = node.getAttribute("dndType"),
data = node.getAttribute("dndData");
map[node.id] = {
data: data || node.innerHTML,
type: type ? type.split(/\s*,\s*/) : ["text"]
};
}, this);
this.map = map;
return this; // self
},
insertNodes: function(data, before, anchor){
// summary:
// inserts an array of new nodes before/after an anchor node
// data: Array
// a list of data items, which should be processed by the creator function
// before: Boolean
// insert before the anchor, if true, and after the anchor otherwise
// anchor: Node
// the anchor node to be used as a point of insertion
if(!this.parent.firstChild){
anchor = null;
}else if(before){
if(!anchor){
anchor = this.parent.firstChild;
}
}else{
if(anchor){
anchor = anchor.nextSibling;
}
}
if(anchor){
for(var i = 0; i < data.length; ++i){
var t = this._normalizedCreator(data[i]);
this.setItem(t.node.id, {data: t.data, type: t.type});
this.parent.insertBefore(t.node, anchor);
}
}else{
for(var i = 0; i < data.length; ++i){
var t = this._normalizedCreator(data[i]);
this.setItem(t.node.id, {data: t.data, type: t.type});
this.parent.appendChild(t.node);
}
}
return this; // self
},
destroy: function(){
// summary:
// prepares this object to be garbage-collected
dojo.forEach(this.events, dojo.disconnect);
this.clearItems();
this.node = this.parent = this.current = null;
},
// markup methods
markupFactory: function(params, node){
params._skipStartup = true;
return new dojo.dnd.Container(node, params);
},
startup: function(){
// summary:
// collects valid child items and populate the map
// set up the real parent node
if(!this.parent){
// use the standard algorithm, if not assigned
this.parent = this.node;
if(this.parent.tagName.toLowerCase() == "table"){
var c = this.parent.getElementsByTagName("tbody");
if(c && c.length){ this.parent = c[0]; }
}
}
this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
// process specially marked children
this.sync();
},
// mouse events
onMouseOver: function(e){
// summary:
// event processor for onmouseover
// e: Event
// mouse event
var n = e.relatedTarget;
while(n){
if(n == this.node){ break; }
try{
n = n.parentNode;
}catch(x){
n = null;
}
}
if(!n){
this._changeState("Container", "Over");
this.onOverEvent();
}
n = this._getChildByEvent(e);
if(this.current == n){ return; }
if(this.current){ this._removeItemClass(this.current, "Over"); }
if(n){ this._addItemClass(n, "Over"); }
this.current = n;
},
onMouseOut: function(e){
// summary:
// event processor for onmouseout
// e: Event
// mouse event
for(var n = e.relatedTarget; n;){
if(n == this.node){ return; }
try{
n = n.parentNode;
}catch(x){
n = null;
}
}
if(this.current){
this._removeItemClass(this.current, "Over");
this.current = null;
}
this._changeState("Container", "");
this.onOutEvent();
},
onSelectStart: function(e){
// summary:
// event processor for onselectevent and ondragevent
// e: Event
// mouse event
if(!this.skipForm || !dojo.dnd.isFormElement(e)){
dojo.stopEvent(e);
}
},
// utilities
onOverEvent: function(){
// summary:
// this function is called once, when mouse is over our container
},
onOutEvent: function(){
// summary:
// this function is called once, when mouse is out of our container
},
_changeState: function(type, newState){
// summary:
// changes a named state to new state value
// type: String
// a name of the state to change
// newState: String
// new state
var prefix = "dojoDnd" + type;
var state = type.toLowerCase() + "State";
//dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
dojo.removeClass(this.node, prefix + this[state]);
dojo.addClass(this.node, prefix + newState);
this[state] = newState;
},
_addItemClass: function(node, type){
// summary:
// adds a class with prefix "dojoDndItem"
// node: Node
// a node
// type: String
// a variable suffix for a class name
dojo.addClass(node, "dojoDndItem" + type);
},
_removeItemClass: function(node, type){
// summary:
// removes a class with prefix "dojoDndItem"
// node: Node
// a node
// type: String
// a variable suffix for a class name
dojo.removeClass(node, "dojoDndItem" + type);
},
_getChildByEvent: function(e){
// summary:
// gets a child, which is under the mouse at the moment, or null
// e: Event
// a mouse event
var node = e.target;
if(node){
for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
}
}
return null;
},
_normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
// summary:
// adds all necessary data to the output of the user-supplied creator function
var t = (this.creator || this.defaultCreator).call(this, item, hint);
if(!dojo.isArray(t.type)){ t.type = ["text"]; }
if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
dojo.addClass(t.node, "dojoDndItem");
return t;
}
});
dojo.dnd._createNode = function(tag){
// summary:
// returns a function, which creates an element of given tag
// (SPAN by default) and sets its innerHTML to given text
// tag: String
// a tag name or empty for SPAN
if(!tag){ return dojo.dnd._createSpan; }
return function(text){ // Function
return dojo.create(tag, {innerHTML: text}); // Node
};
};
dojo.dnd._createTrTd = function(text){
// summary:
// creates a TR/TD structure with given text as an innerHTML of TD
// text: String
// a text for TD
var tr = dojo.create("tr");
dojo.create("td", {innerHTML: text}, tr);
return tr; // Node
};
dojo.dnd._createSpan = function(text){
// summary:
// creates a SPAN element with given text as its innerHTML
// text: String
// a text for SPAN
return dojo.create("span", {innerHTML: text}); // Node
};
// dojo.dnd._defaultCreatorNodes: Object
// a dictionary that maps container tag names to child tag names
dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
dojo.dnd._defaultCreator = function(node){
// summary:
// takes a parent node, and returns an appropriate creator function
// node: Node
// a container node
var tag = node.tagName.toLowerCase();
var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
return function(item, hint){ // Function
var isObj = item && dojo.isObject(item), data, type, n;
if(isObj && item.tagName && item.nodeType && item.getAttribute){
// process a DOM node
data = item.getAttribute("dndData") || item.innerHTML;
type = item.getAttribute("dndType");
type = type ? type.split(/\s*,\s*/) : ["text"];
n = item; // this node is going to be moved rather than copied
}else{
// process a DnD item object or a string
data = (isObj && item.data) ? item.data : item;
type = (isObj && item.type) ? item.type : ["text"];
n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
}
n.id = dojo.dnd.getUniqueId();
return {node: n, data: data, type: type};
};
};
}
if(!dojo._hasResource["dojo.dnd.Selector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Selector"] = true;
dojo.provide("dojo.dnd.Selector");
/*
Container item states:
"" - an item is not selected
"Selected" - an item is selected
"Anchor" - an item is selected, and is an anchor for a "shift" selection
*/
/*=====
dojo.declare("dojo.dnd.__SelectorArgs", [dojo.dnd.__ContainerArgs], {
// singular: Boolean
// allows selection of only one element, if true
singular: false,
// autoSync: Boolean
// autosynchronizes the source with its list of DnD nodes,
autoSync: false
});
=====*/
dojo.declare("dojo.dnd.Selector", dojo.dnd.Container, {
// summary:
// a Selector object, which knows how to select its children
/*=====
// selection: Set<String>
// The set of id's that are currently selected, such that this.selection[id] == 1
// if the node w/that id is selected. Can iterate over selected node's id's like:
// | for(var id in this.selection)
selection: {},
=====*/
constructor: function(node, params){
// summary:
// constructor of the Selector
// node: Node||String
// node or node's id to build the selector on
// params: dojo.dnd.__SelectorArgs?
// a dictionary of parameters
if(!params){ params = {}; }
this.singular = params.singular;
this.autoSync = params.autoSync;
// class-specific variables
this.selection = {};
this.anchor = null;
this.simpleSelection = false;
// set up events
this.events.push(
dojo.connect(this.node, "onmousedown", this, "onMouseDown"),
dojo.connect(this.node, "onmouseup", this, "onMouseUp"));
},
// object attributes (for markup)
singular: false, // is singular property
// methods
getSelectedNodes: function(){
// summary:
// returns a list (an array) of selected nodes
var t = new dojo.NodeList();
var e = dojo.dnd._empty;
for(var i in this.selection){
if(i in e){ continue; }
t.push(dojo.byId(i));
}
return t; // NodeList
},
selectNone: function(){
// summary:
// unselects all items
return this._removeSelection()._removeAnchor(); // self
},
selectAll: function(){
// summary:
// selects all items
this.forInItems(function(data, id){
this._addItemClass(dojo.byId(id), "Selected");
this.selection[id] = 1;
}, this);
return this._removeAnchor(); // self
},
deleteSelectedNodes: function(){
// summary:
// deletes all selected items
var e = dojo.dnd._empty;
for(var i in this.selection){
if(i in e){ continue; }
var n = dojo.byId(i);
this.delItem(i);
dojo.destroy(n);
}
this.anchor = null;
this.selection = {};
return this; // self
},
forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
// summary:
// iterates over selected items;
// see `dojo.dnd.Container.forInItems()` for details
o = o || dojo.global;
var s = this.selection, e = dojo.dnd._empty;
for(var i in s){
if(i in e){ continue; }
f.call(o, this.getItem(i), i, this);
}
},
sync: function(){
// summary:
// sync up the node list with the data map
dojo.dnd.Selector.superclass.sync.call(this);
// fix the anchor
if(this.anchor){
if(!this.getItem(this.anchor.id)){
this.anchor = null;
}
}
// fix the selection
var t = [], e = dojo.dnd._empty;
for(var i in this.selection){
if(i in e){ continue; }
if(!this.getItem(i)){
t.push(i);
}
}
dojo.forEach(t, function(i){
delete this.selection[i];
}, this);
return this; // self
},
insertNodes: function(addSelected, data, before, anchor){
// summary:
// inserts new data items (see `dojo.dnd.Container.insertNodes()` method for details)
// addSelected: Boolean
// all new nodes will be added to selected items, if true, no selection change otherwise
// data: Array
// a list of data items, which should be processed by the creator function
// before: Boolean
// insert before the anchor, if true, and after the anchor otherwise
// anchor: Node
// the anchor node to be used as a point of insertion
var oldCreator = this._normalizedCreator;
this._normalizedCreator = function(item, hint){
var t = oldCreator.call(this, item, hint);
if(addSelected){
if(!this.anchor){
this.anchor = t.node;
this._removeItemClass(t.node, "Selected");
this._addItemClass(this.anchor, "Anchor");
}else if(this.anchor != t.node){
this._removeItemClass(t.node, "Anchor");
this._addItemClass(t.node, "Selected");
}
this.selection[t.node.id] = 1;
}else{
this._removeItemClass(t.node, "Selected");
this._removeItemClass(t.node, "Anchor");
}
return t;
};
dojo.dnd.Selector.superclass.insertNodes.call(this, data, before, anchor);
this._normalizedCreator = oldCreator;
return this; // self
},
destroy: function(){
// summary:
// prepares the object to be garbage-collected
dojo.dnd.Selector.superclass.destroy.call(this);
this.selection = this.anchor = null;
},
// markup methods
markupFactory: function(params, node){
params._skipStartup = true;
return new dojo.dnd.Selector(node, params);
},
// mouse events
onMouseDown: function(e){
// summary:
// event processor for onmousedown
// e: Event
// mouse event
if(this.autoSync){ this.sync(); }
if(!this.current){ return; }
if(!this.singular && !dojo.isCopyKey(e) && !e.shiftKey && (this.current.id in this.selection)){
this.simpleSelection = true;
if(e.button === dojo.mouseButtons.LEFT){
// accept the left button and stop the event
// for IE we don't stop event when multiple buttons are pressed
dojo.stopEvent(e);
}
return;
}
if(!this.singular && e.shiftKey){
if(!dojo.isCopyKey(e)){
this._removeSelection();
}
var c = this.getAllNodes();
if(c.length){
if(!this.anchor){
this.anchor = c[0];
this._addItemClass(this.anchor, "Anchor");
}
this.selection[this.anchor.id] = 1;
if(this.anchor != this.current){
var i = 0;
for(; i < c.length; ++i){
var node = c[i];
if(node == this.anchor || node == this.current){ break; }
}
for(++i; i < c.length; ++i){
var node = c[i];
if(node == this.anchor || node == this.current){ break; }
this._addItemClass(node, "Selected");
this.selection[node.id] = 1;
}
this._addItemClass(this.current, "Selected");
this.selection[this.current.id] = 1;
}
}
}else{
if(this.singular){
if(this.anchor == this.current){
if(dojo.isCopyKey(e)){
this.selectNone();
}
}else{
this.selectNone();
this.anchor = this.current;
this._addItemClass(this.anchor, "Anchor");
this.selection[this.current.id] = 1;
}
}else{
if(dojo.isCopyKey(e)){
if(this.anchor == this.current){
delete this.selection[this.anchor.id];
this._removeAnchor();
}else{
if(this.current.id in this.selection){
this._removeItemClass(this.current, "Selected");
delete this.selection[this.current.id];
}else{
if(this.anchor){
this._removeItemClass(this.anchor, "Anchor");
this._addItemClass(this.anchor, "Selected");
}
this.anchor = this.current;
this._addItemClass(this.current, "Anchor");
this.selection[this.current.id] = 1;
}
}
}else{
if(!(this.current.id in this.selection)){
this.selectNone();
this.anchor = this.current;
this._addItemClass(this.current, "Anchor");
this.selection[this.current.id] = 1;
}
}
}
}
dojo.stopEvent(e);
},
onMouseUp: function(e){
// summary:
// event processor for onmouseup
// e: Event
// mouse event
if(!this.simpleSelection){ return; }
this.simpleSelection = false;
this.selectNone();
if(this.current){
this.anchor = this.current;
this._addItemClass(this.anchor, "Anchor");
this.selection[this.current.id] = 1;
}
},
onMouseMove: function(e){
// summary
// event processor for onmousemove
// e: Event
// mouse event
this.simpleSelection = false;
},
// utilities
onOverEvent: function(){
// summary:
// this function is called once, when mouse is over our container
this.onmousemoveEvent = dojo.connect(this.node, "onmousemove", this, "onMouseMove");
},
onOutEvent: function(){
// summary:
// this function is called once, when mouse is out of our container
dojo.disconnect(this.onmousemoveEvent);
delete this.onmousemoveEvent;
},
_removeSelection: function(){
// summary:
// unselects all items
var e = dojo.dnd._empty;
for(var i in this.selection){
if(i in e){ continue; }
var node = dojo.byId(i);
if(node){ this._removeItemClass(node, "Selected"); }
}
this.selection = {};
return this; // self
},
_removeAnchor: function(){
if(this.anchor){
this._removeItemClass(this.anchor, "Anchor");
this.anchor = null;
}
return this; // self
}
});
}
if(!dojo._hasResource["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Avatar"] = true;
dojo.provide("dojo.dnd.Avatar");
dojo.declare("dojo.dnd.Avatar", null, {
// summary:
// Object that represents transferred DnD items visually
// manager: Object
// a DnD manager object
constructor: function(manager){
this.manager = manager;
this.construct();
},
// methods
construct: function(){
// summary:
// constructor function;
// it is separate so it can be (dynamically) overwritten in case of need
this.isA11y = dojo.hasClass(dojo.body(),"dijit_a11y");
var a = dojo.create("table", {
"class": "dojoDndAvatar",
style: {
position: "absolute",
zIndex: "1999",
margin: "0px"
}
}),
source = this.manager.source, node,
b = dojo.create("tbody", null, a),
tr = dojo.create("tr", null, b),
td = dojo.create("td", null, tr),
icon = this.isA11y ? dojo.create("span", {
id : "a11yIcon",
innerHTML : this.manager.copy ? '+' : "<"
}, td) : null,
span = dojo.create("span", {
innerHTML: source.generateText ? this._generateText() : ""
}, td),
k = Math.min(5, this.manager.nodes.length), i = 0;
// we have to set the opacity on IE only after the node is live
dojo.attr(tr, {
"class": "dojoDndAvatarHeader",
style: {opacity: 0.9}
});
for(; i < k; ++i){
if(source.creator){
// create an avatar representation of the node
node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node;
}else{
// or just clone the node and hope it works
node = this.manager.nodes[i].cloneNode(true);
if(node.tagName.toLowerCase() == "tr"){
// insert extra table nodes
var table = dojo.create("table"),
tbody = dojo.create("tbody", null, table);
tbody.appendChild(node);
node = table;
}
}
node.id = "";
tr = dojo.create("tr", null, b);
td = dojo.create("td", null, tr);
td.appendChild(node);
dojo.attr(tr, {
"class": "dojoDndAvatarItem",
style: {opacity: (9 - i) / 10}
});
}
this.node = a;
},
destroy: function(){
// summary:
// destructor for the avatar; called to remove all references so it can be garbage-collected
dojo.destroy(this.node);
this.node = false;
},
update: function(){
// summary:
// updates the avatar to reflect the current DnD state
dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop");
if (this.isA11y){
var icon = dojo.byId("a11yIcon");
var text = '+'; // assume canDrop && copy
if (this.manager.canDropFlag && !this.manager.copy) {
text = '< '; // canDrop && move
}else if (!this.manager.canDropFlag && !this.manager.copy) {
text = "o"; //!canDrop && move
}else if(!this.manager.canDropFlag){
text = 'x'; // !canDrop && copy
}
icon.innerHTML=text;
}
// replace text
dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach(
function(node){
node.innerHTML = this._generateText();
}, this);
},
_generateText: function(){
// summary: generates a proper text to reflect copying or moving of items
return this.manager.nodes.length.toString();
}
});
}
if(!dojo._hasResource["dojo.dnd.Manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Manager"] = true;
dojo.provide("dojo.dnd.Manager");
dojo.declare("dojo.dnd.Manager", null, {
// summary:
// the manager of DnD operations (usually a singleton)
constructor: function(){
this.avatar = null;
this.source = null;
this.nodes = [];
this.copy = true;
this.target = null;
this.canDropFlag = false;
this.events = [];
},
// avatar's offset from the mouse
OFFSET_X: 16,
OFFSET_Y: 16,
// methods
overSource: function(source){
// summary:
// called when a source detected a mouse-over condition
// source: Object
// the reporter
if(this.avatar){
this.target = (source && source.targetState != "Disabled") ? source : null;
this.canDropFlag = Boolean(this.target);
this.avatar.update();
}
dojo.publish("/dnd/source/over", [source]);
},
outSource: function(source){
// summary:
// called when a source detected a mouse-out condition
// source: Object
// the reporter
if(this.avatar){
if(this.target == source){
this.target = null;
this.canDropFlag = false;
this.avatar.update();
dojo.publish("/dnd/source/over", [null]);
}
}else{
dojo.publish("/dnd/source/over", [null]);
}
},
startDrag: function(source, nodes, copy){
// summary:
// called to initiate the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
this.source = source;
this.nodes = nodes;
this.copy = Boolean(copy); // normalizing to true boolean
this.avatar = this.makeAvatar();
dojo.body().appendChild(this.avatar.node);
dojo.publish("/dnd/start", [source, nodes, this.copy]);
this.events = [
dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"),
dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"),
dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"),
dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp"),
// cancel text selection and text dragging
dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent),
dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent)
];
var c = "dojoDnd" + (copy ? "Copy" : "Move");
dojo.addClass(dojo.body(), c);
},
canDrop: function(flag){
// summary:
// called to notify if the current target can accept items
var canDropFlag = Boolean(this.target && flag);
if(this.canDropFlag != canDropFlag){
this.canDropFlag = canDropFlag;
this.avatar.update();
}
},
stopDrag: function(){
// summary:
// stop the DnD in progress
dojo.removeClass(dojo.body(), "dojoDndCopy");
dojo.removeClass(dojo.body(), "dojoDndMove");
dojo.forEach(this.events, dojo.disconnect);
this.events = [];
this.avatar.destroy();
this.avatar = null;
this.source = this.target = null;
this.nodes = [];
},
makeAvatar: function(){
// summary:
// makes the avatar; it is separate to be overwritten dynamically, if needed
return new dojo.dnd.Avatar(this);
},
updateAvatar: function(){
// summary:
// updates the avatar; it is separate to be overwritten dynamically, if needed
this.avatar.update();
},
// mouse event processors
onMouseMove: function(e){
// summary:
// event processor for onmousemove
// e: Event
// mouse event
var a = this.avatar;
if(a){
dojo.dnd.autoScrollNodes(e);
//dojo.dnd.autoScroll(e);
var s = a.node.style;
s.left = (e.pageX + this.OFFSET_X) + "px";
s.top = (e.pageY + this.OFFSET_Y) + "px";
var copy = Boolean(this.source.copyState(dojo.isCopyKey(e)));
if(this.copy != copy){
this._setCopyStatus(copy);
}
}
},
onMouseUp: function(e){
// summary:
// event processor for onmouseup
// e: Event
// mouse event
if(this.avatar){
if(this.target && this.canDropFlag){
var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))),
params = [this.source, this.nodes, copy, this.target];
dojo.publish("/dnd/drop/before", params);
dojo.publish("/dnd/drop", params);
}else{
dojo.publish("/dnd/cancel");
}
this.stopDrag();
}
},
// keyboard event processors
onKeyDown: function(e){
// summary:
// event processor for onkeydown:
// watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
// e: Event
// keyboard event
if(this.avatar){
switch(e.keyCode){
case dojo.keys.CTRL:
var copy = Boolean(this.source.copyState(true));
if(this.copy != copy){
this._setCopyStatus(copy);
}
break;
case dojo.keys.ESCAPE:
dojo.publish("/dnd/cancel");
this.stopDrag();
break;
}
}
},
onKeyUp: function(e){
// summary:
// event processor for onkeyup, watching for CTRL for copy/move status
// e: Event
// keyboard event
if(this.avatar && e.keyCode == dojo.keys.CTRL){
var copy = Boolean(this.source.copyState(false));
if(this.copy != copy){
this._setCopyStatus(copy);
}
}
},
// utilities
_setCopyStatus: function(copy){
// summary:
// changes the copy status
// copy: Boolean
// the copy status
this.copy = copy;
this.source._markDndStatus(this.copy);
this.updateAvatar();
dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy"));
dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"));
}
});
// dojo.dnd._manager:
// The manager singleton variable. Can be overwritten if needed.
dojo.dnd._manager = null;
dojo.dnd.manager = function(){
// summary:
// Returns the current DnD manager. Creates one if it is not created yet.
if(!dojo.dnd._manager){
dojo.dnd._manager = new dojo.dnd.Manager();
}
return dojo.dnd._manager; // Object
};
}
if(!dojo._hasResource["dojo.dnd.Source"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.dnd.Source"] = true;
dojo.provide("dojo.dnd.Source");
/*
Container property:
"Horizontal"- if this is the horizontal container
Source states:
"" - normal state
"Moved" - this source is being moved
"Copied" - this source is being copied
Target states:
"" - normal state
"Disabled" - the target cannot accept an avatar
Target anchor state:
"" - item is not selected
"Before" - insert point is before the anchor
"After" - insert point is after the anchor
*/
/*=====
dojo.dnd.__SourceArgs = function(){
// summary:
// a dict of parameters for DnD Source configuration. Note that any
// property on Source elements may be configured, but this is the
// short-list
// isSource: Boolean?
// can be used as a DnD source. Defaults to true.
// accept: Array?
// list of accepted types (text strings) for a target; defaults to
// ["text"]
// autoSync: Boolean
// if true refreshes the node list on every operation; false by default
// copyOnly: Boolean?
// copy items, if true, use a state of Ctrl key otherwise,
// see selfCopy and selfAccept for more details
// delay: Number
// the move delay in pixels before detecting a drag; 0 by default
// horizontal: Boolean?
// a horizontal container, if true, vertical otherwise or when omitted
// selfCopy: Boolean?
// copy items by default when dropping on itself,
// false by default, works only if copyOnly is true
// selfAccept: Boolean?
// accept its own items when copyOnly is true,
// true by default, works only if copyOnly is true
// withHandles: Boolean?
// allows dragging only by handles, false by default
// generateText: Boolean?
// generate text node for drag and drop, true by default
this.isSource = isSource;
this.accept = accept;
this.autoSync = autoSync;
this.copyOnly = copyOnly;
this.delay = delay;
this.horizontal = horizontal;
this.selfCopy = selfCopy;
this.selfAccept = selfAccept;
this.withHandles = withHandles;
this.generateText = true;
}
=====*/
dojo.declare("dojo.dnd.Source", dojo.dnd.Selector, {
// summary:
// a Source object, which can be used as a DnD source, or a DnD target
// object attributes (for markup)
isSource: true,
horizontal: false,
copyOnly: false,
selfCopy: false,
selfAccept: true,
skipForm: false,
withHandles: false,
autoSync: false,
delay: 0, // pixels
accept: ["text"],
generateText: true,
constructor: function(/*DOMNode|String*/node, /*dojo.dnd.__SourceArgs?*/params){
// summary:
// a constructor of the Source
// node:
// node or node's id to build the source on
// params:
// any property of this class may be configured via the params
// object which is mixed-in to the `dojo.dnd.Source` instance
dojo.mixin(this, dojo.mixin({}, params));
var type = this.accept;
if(type.length){
this.accept = {};
for(var i = 0; i < type.length; ++i){
this.accept[type[i]] = 1;
}
}
// class-specific variables
this.isDragging = false;
this.mouseDown = false;
this.targetAnchor = null;
this.targetBox = null;
this.before = true;
this._lastX = 0;
this._lastY = 0;
// states
this.sourceState = "";
if(this.isSource){
dojo.addClass(this.node, "dojoDndSource");
}
this.targetState = "";
if(this.accept){
dojo.addClass(this.node, "dojoDndTarget");
}
if(this.horizontal){
dojo.addClass(this.node, "dojoDndHorizontal");
}
// set up events
this.topics = [
dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
dojo.subscribe("/dnd/start", this, "onDndStart"),
dojo.subscribe("/dnd/drop", this, "onDndDrop"),
dojo.subscribe("/dnd/cancel", this, "onDndCancel")
];
},
// methods
checkAcceptance: function(source, nodes){
// summary:
// checks if the target can accept nodes from this source
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
if(this == source){
return !this.copyOnly || this.selfAccept;
}
for(var i = 0; i < nodes.length; ++i){
var type = source.getItem(nodes[i].id).type;
// type instanceof Array
var flag = false;
for(var j = 0; j < type.length; ++j){
if(type[j] in this.accept){
flag = true;
break;
}
}
if(!flag){
return false; // Boolean
}
}
return true; // Boolean
},
copyState: function(keyPressed, self){
// summary:
// Returns true if we need to copy items, false to move.
// It is separated to be overwritten dynamically, if needed.
// keyPressed: Boolean
// the "copy" key was pressed
// self: Boolean?
// optional flag that means that we are about to drop on itself
if(keyPressed){ return true; }
if(arguments.length < 2){
self = this == dojo.dnd.manager().target;
}
if(self){
if(this.copyOnly){
return this.selfCopy;
}
}else{
return this.copyOnly;
}
return false; // Boolean
},
destroy: function(){
// summary:
// prepares the object to be garbage-collected
dojo.dnd.Source.superclass.destroy.call(this);
dojo.forEach(this.topics, dojo.unsubscribe);
this.targetAnchor = null;
},
// markup methods
markupFactory: function(params, node){
params._skipStartup = true;
return new dojo.dnd.Source(node, params);
},
// mouse event processors
onMouseMove: function(e){
// summary:
// event processor for onmousemove
// e: Event
// mouse event
if(this.isDragging && this.targetState == "Disabled"){ return; }
dojo.dnd.Source.superclass.onMouseMove.call(this, e);
var m = dojo.dnd.manager();
if(this.isDragging){
// calculate before/after
var before = false;
if(this.current){
if(!this.targetBox || this.targetAnchor != this.current){
this.targetBox = dojo.position(this.current, true);
}
if(this.horizontal){
before = (e.pageX - this.targetBox.x) < (this.targetBox.w / 2);
}else{
before = (e.pageY - this.targetBox.y) < (this.targetBox.h / 2);
}
}
if(this.current != this.targetAnchor || before != this.before){
this._markTargetAnchor(before);
m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection));
}
}else{
if(this.mouseDown && this.isSource &&
(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay)){
var nodes = this.getSelectedNodes();
if(nodes.length){
m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e), true));
}
}
}
},
onMouseDown: function(e){
// summary:
// event processor for onmousedown
// e: Event
// mouse event
if(!this.mouseDown && this._legalMouseDown(e) && (!this.skipForm || !dojo.dnd.isFormElement(e))){
this.mouseDown = true;
this._lastX = e.pageX;
this._lastY = e.pageY;
dojo.dnd.Source.superclass.onMouseDown.call(this, e);
}
},
onMouseUp: function(e){
// summary:
// event processor for onmouseup
// e: Event
// mouse event
if(this.mouseDown){
this.mouseDown = false;
dojo.dnd.Source.superclass.onMouseUp.call(this, e);
}
},
// topic event processors
onDndSourceOver: function(source){
// summary:
// topic event processor for /dnd/source/over, called when detected a current source
// source: Object
// the source which has the mouse over it
if(this != source){
this.mouseDown = false;
if(this.targetAnchor){
this._unmarkTargetAnchor();
}
}else if(this.isDragging){
var m = dojo.dnd.manager();
m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
}
},
onDndStart: function(source, nodes, copy){
// summary:
// topic event processor for /dnd/start, called to initiate the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
if(this.autoSync){ this.sync(); }
if(this.isSource){
this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
}
var accepted = this.accept && this.checkAcceptance(source, nodes);
this._changeState("Target", accepted ? "" : "Disabled");
if(this == source){
dojo.dnd.manager().overSource(this);
}
this.isDragging = true;
},
onDndDrop: function(source, nodes, copy, target){
// summary:
// topic event processor for /dnd/drop, called to finish the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
// target: Object
// the target which accepts items
if(this == target){
// this one is for us => move nodes!
this.onDrop(source, nodes, copy);
}
this.onDndCancel();
},
onDndCancel: function(){
// summary:
// topic event processor for /dnd/cancel, called to cancel the DnD operation
if(this.targetAnchor){
this._unmarkTargetAnchor();
this.targetAnchor = null;
}
this.before = true;
this.isDragging = false;
this.mouseDown = false;
this._changeState("Source", "");
this._changeState("Target", "");
},
// local events
onDrop: function(source, nodes, copy){
// summary:
// called only on the current target, when drop is performed
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
if(this != source){
this.onDropExternal(source, nodes, copy);
}else{
this.onDropInternal(nodes, copy);
}
},
onDropExternal: function(source, nodes, copy){
// summary:
// called only on the current target, when drop is performed
// from an external source
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
var oldCreator = this._normalizedCreator;
// transferring nodes from the source to the target
if(this.creator){
// use defined creator
this._normalizedCreator = function(node, hint){
return oldCreator.call(this, source.getItem(node.id).data, hint);
};
}else{
// we have no creator defined => move/clone nodes
if(copy){
// clone nodes
this._normalizedCreator = function(node, hint){
var t = source.getItem(node.id);
var n = node.cloneNode(true);
n.id = dojo.dnd.getUniqueId();
return {node: n, data: t.data, type: t.type};
};
}else{
// move nodes
this._normalizedCreator = function(node, hint){
var t = source.getItem(node.id);
source.delItem(node.id);
return {node: node, data: t.data, type: t.type};
};
}
}
this.selectNone();
if(!copy && !this.creator){
source.selectNone();
}
this.insertNodes(true, nodes, this.before, this.current);
if(!copy && this.creator){
source.deleteSelectedNodes();
}
this._normalizedCreator = oldCreator;
},
onDropInternal: function(nodes, copy){
// summary:
// called only on the current target, when drop is performed
// from the same target/source
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
var oldCreator = this._normalizedCreator;
// transferring nodes within the single source
if(this.current && this.current.id in this.selection){
// do nothing
return;
}
if(copy){
if(this.creator){
// create new copies of data items
this._normalizedCreator = function(node, hint){
return oldCreator.call(this, this.getItem(node.id).data, hint);
};
}else{
// clone nodes
this._normalizedCreator = function(node, hint){
var t = this.getItem(node.id);
var n = node.cloneNode(true);
n.id = dojo.dnd.getUniqueId();
return {node: n, data: t.data, type: t.type};
};
}
}else{
// move nodes
if(!this.current){
// do nothing
return;
}
this._normalizedCreator = function(node, hint){
var t = this.getItem(node.id);
return {node: node, data: t.data, type: t.type};
};
}
this._removeSelection();
this.insertNodes(true, nodes, this.before, this.current);
this._normalizedCreator = oldCreator;
},
onDraggingOver: function(){
// summary:
// called during the active DnD operation, when items
// are dragged over this target, and it is not disabled
},
onDraggingOut: function(){
// summary:
// called during the active DnD operation, when items
// are dragged away from this target, and it is not disabled
},
// utilities
onOverEvent: function(){
// summary:
// this function is called once, when mouse is over our container
dojo.dnd.Source.superclass.onOverEvent.call(this);
dojo.dnd.manager().overSource(this);
if(this.isDragging && this.targetState != "Disabled"){
this.onDraggingOver();
}
},
onOutEvent: function(){
// summary:
// this function is called once, when mouse is out of our container
dojo.dnd.Source.superclass.onOutEvent.call(this);
dojo.dnd.manager().outSource(this);
if(this.isDragging && this.targetState != "Disabled"){
this.onDraggingOut();
}
},
_markTargetAnchor: function(before){
// summary:
// assigns a class to the current target anchor based on "before" status
// before: Boolean
// insert before, if true, after otherwise
if(this.current == this.targetAnchor && this.before == before){ return; }
if(this.targetAnchor){
this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
}
this.targetAnchor = this.current;
this.targetBox = null;
this.before = before;
if(this.targetAnchor){
this._addItemClass(this.targetAnchor, this.before ? "Before" : "After");
}
},
_unmarkTargetAnchor: function(){
// summary:
// removes a class of the current target anchor based on "before" status
if(!this.targetAnchor){ return; }
this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
this.targetAnchor = null;
this.targetBox = null;
this.before = true;
},
_markDndStatus: function(copy){
// summary:
// changes source's state based on "copy" status
this._changeState("Source", copy ? "Copied" : "Moved");
},
_legalMouseDown: function(e){
// summary:
// checks if user clicked on "approved" items
// e: Event
// mouse event
// accept only the left mouse button
if(!dojo.mouseButtons.isLeft(e)){ return false; }
if(!this.withHandles){ return true; }
// check for handles
for(var node = e.target; node && node !== this.node; node = node.parentNode){
if(dojo.hasClass(node, "dojoDndHandle")){ return true; }
if(dojo.hasClass(node, "dojoDndItem") || dojo.hasClass(node, "dojoDndIgnore")){ break; }
}
return false; // Boolean
}
});
dojo.declare("dojo.dnd.Target", dojo.dnd.Source, {
// summary: a Target object, which can be used as a DnD target
constructor: function(node, params){
// summary:
// a constructor of the Target --- see the `dojo.dnd.Source.constructor` for details
this.isSource = false;
dojo.removeClass(this.node, "dojoDndSource");
},
// markup methods
markupFactory: function(params, node){
params._skipStartup = true;
return new dojo.dnd.Target(node, params);
}
});
dojo.declare("dojo.dnd.AutoSource", dojo.dnd.Source, {
// summary:
// a source that syncs its DnD nodes by default
constructor: function(node, params){
// summary:
// constructor of the AutoSource --- see the Source constructor for details
this.autoSync = true;
},
// markup methods
markupFactory: function(params, node){
params._skipStartup = true;
return new dojo.dnd.AutoSource(node, params);
}
});
}
if(!dojo._hasResource["dojox.grid._View"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._View"] = true;
dojo.provide("dojox.grid._View");
(function(){
// private
var getStyleText = function(inNode, inStyleText){
return inNode.style.cssText == undefined ? inNode.getAttribute("style") : inNode.style.cssText;
};
// public
dojo.declare('dojox.grid._View', [dijit._Widget, dijit._Templated], {
// summary:
// A collection of grid columns. A grid is comprised of a set of views that stack horizontally.
// Grid creates views automatically based on grid's layout structure.
// Users should typically not need to access individual views directly.
//
// defaultWidth: String
// Default width of the view
defaultWidth: "18em",
// viewWidth: String
// Width for the view, in valid css unit
viewWidth: "",
templateString:"<div class=\"dojoxGridView\" wairole=\"presentation\">\n\t<div class=\"dojoxGridHeader\" dojoAttachPoint=\"headerNode\" wairole=\"presentation\">\n\t\t<div dojoAttachPoint=\"headerNodeContainer\" style=\"width:9000em\" wairole=\"presentation\">\n\t\t\t<div dojoAttachPoint=\"headerContentNode\" wairole=\"row\"></div>\n\t\t</div>\n\t</div>\n\t<input type=\"checkbox\" class=\"dojoxGridHiddenFocus\" dojoAttachPoint=\"hiddenFocusNode\" wairole=\"presentation\" />\n\t<input type=\"checkbox\" class=\"dojoxGridHiddenFocus\" wairole=\"presentation\" />\n\t<div class=\"dojoxGridScrollbox\" dojoAttachPoint=\"scrollboxNode\" wairole=\"presentation\">\n\t\t<div class=\"dojoxGridContent\" dojoAttachPoint=\"contentNode\" hidefocus=\"hidefocus\" wairole=\"presentation\"></div>\n\t</div>\n</div>\n",
themeable: false,
classTag: 'dojoxGrid',
marginBottom: 0,
rowPad: 2,
// _togglingColumn: int
// Width of the column being toggled (-1 for none)
_togglingColumn: -1,
// _headerBuilderClass: Object
// The class to use for our header builder
_headerBuilderClass: dojox.grid._HeaderBuilder,
// _contentBuilderClass: Object
// The class to use for our content builder
_contentBuilderClass: dojox.grid._ContentBuilder,
postMixInProperties: function(){
this.rowNodes = {};
},
postCreate: function(){
this.connect(this.scrollboxNode,"onscroll","doscroll");
dojox.grid.util.funnelEvents(this.contentNode, this, "doContentEvent", [ 'mouseover', 'mouseout', 'click', 'dblclick', 'contextmenu', 'mousedown' ]);
dojox.grid.util.funnelEvents(this.headerNode, this, "doHeaderEvent", [ 'dblclick', 'mouseover', 'mouseout', 'mousemove', 'mousedown', 'click', 'contextmenu' ]);
this.content = new this._contentBuilderClass(this);
this.header = new this._headerBuilderClass(this);
//BiDi: in RTL case, style width='9000em' causes scrolling problem in head node
if(!dojo._isBodyLtr()){
this.headerNodeContainer.style.width = "";
}
},
destroy: function(){
dojo.destroy(this.headerNode);
delete this.headerNode;
for(var i in this.rowNodes){
dojo.destroy(this.rowNodes[i]);
}
this.rowNodes = {};
if(this.source){
this.source.destroy();
}
this.inherited(arguments);
},
// focus
focus: function(){
if(dojo.isIE || dojo.isWebKit || dojo.isOpera){
this.hiddenFocusNode.focus();
}else{
this.scrollboxNode.focus();
}
},
setStructure: function(inStructure){
var vs = (this.structure = inStructure);
// FIXME: similar logic is duplicated in layout
if(vs.width && !isNaN(vs.width)){
this.viewWidth = vs.width + 'em';
}else{
this.viewWidth = vs.width || (vs.noscroll ? 'auto' : this.viewWidth); //|| this.defaultWidth;
}
this._onBeforeRow = vs.onBeforeRow||function(){};
this._onAfterRow = vs.onAfterRow||function(){};
this.noscroll = vs.noscroll;
if(this.noscroll){
this.scrollboxNode.style.overflow = "hidden";
}
this.simpleStructure = Boolean(vs.cells.length == 1);
// bookkeeping
this.testFlexCells();
// accomodate new structure
this.updateStructure();
},
_cleanupRowWidgets: function(inRowNode){
// Summary:
// Cleans up the widgets for the given row node so that
// we can reattach them if needed
if(inRowNode){
dojo.forEach(dojo.query("[widgetId]", inRowNode).map(dijit.byNode), function(w){
if(w._destroyOnRemove){
w.destroy();
delete w;
}else if(w.domNode && w.domNode.parentNode){
w.domNode.parentNode.removeChild(w.domNode);
}
});
}
},
onBeforeRow: function(inRowIndex, cells){
this._onBeforeRow(inRowIndex, cells);
if(inRowIndex >= 0){
this._cleanupRowWidgets(this.getRowNode(inRowIndex));
}
},
onAfterRow: function(inRowIndex, cells, inRowNode){
this._onAfterRow(inRowIndex, cells, inRowNode);
var g = this.grid;
dojo.forEach(dojo.query(".dojoxGridStubNode", inRowNode), function(n){
if(n && n.parentNode){
var lw = n.getAttribute("linkWidget");
var cellIdx = window.parseInt(dojo.attr(n, "cellIdx"), 10);
var cellDef = g.getCell(cellIdx);
var w = dijit.byId(lw);
if(w){
n.parentNode.replaceChild(w.domNode, n);
if(!w._started){
w.startup();
}
}else{
n.innerHTML = "";
}
}
}, this);
},
testFlexCells: function(){
// FIXME: cheater, this function does double duty as initializer and tester
this.flexCells = false;
for(var j=0, row; (row=this.structure.cells[j]); j++){
for(var i=0, cell; (cell=row[i]); i++){
cell.view = this;
this.flexCells = this.flexCells || cell.isFlex();
}
}
return this.flexCells;
},
updateStructure: function(){
// header builder needs to update table map
this.header.update();
// content builder needs to update markup cache
this.content.update();
},
getScrollbarWidth: function(){
var hasScrollSpace = this.hasVScrollbar();
var overflow = dojo.style(this.scrollboxNode, "overflow");
if(this.noscroll || !overflow || overflow == "hidden"){
hasScrollSpace = false;
}else if(overflow == "scroll"){
hasScrollSpace = true;
}
return (hasScrollSpace ? dojox.html.metrics.getScrollbar().w : 0); // Integer
},
getColumnsWidth: function(){
var h = this.headerContentNode;
return h && h.firstChild ? h.firstChild.offsetWidth : 0; // Integer
},
setColumnsWidth: function(width){
this.headerContentNode.firstChild.style.width = width + 'px';
if(this.viewWidth){
this.viewWidth = width + 'px';
}
},
getWidth: function(){
return this.viewWidth || (this.getColumnsWidth()+this.getScrollbarWidth()) +'px'; // String
},
getContentWidth: function(){
return Math.max(0, dojo._getContentBox(this.domNode).w - this.getScrollbarWidth()) + 'px'; // String
},
render: function(){
this.scrollboxNode.style.height = '';
this.renderHeader();
if(this._togglingColumn >= 0){
this.setColumnsWidth(this.getColumnsWidth() - this._togglingColumn);
this._togglingColumn = -1;
}
var cells = this.grid.layout.cells;
var getSibling = dojo.hitch(this, function(node, before){
var inc = before?-1:1;
var idx = this.header.getCellNodeIndex(node) + inc;
var cell = cells[idx];
while(cell && cell.getHeaderNode() && cell.getHeaderNode().style.display == "none"){
idx += inc;
cell = cells[idx];
}
if(cell){
return cell.getHeaderNode();
}
return null;
});
if(this.grid.columnReordering && this.simpleStructure){
if(this.source){
this.source.destroy();
}
// Create the top and bottom markers
var bottomMarkerId = "dojoxGrid_bottomMarker";
var topMarkerId = "dojoxGrid_topMarker";
if(this.bottomMarker){
dojo.destroy(this.bottomMarker);
}
this.bottomMarker = dojo.byId(bottomMarkerId);
if(this.topMarker){
dojo.destroy(this.topMarker);
}
this.topMarker = dojo.byId(topMarkerId);
if (!this.bottomMarker) {
this.bottomMarker = dojo.create("div", {
"id": bottomMarkerId,
"class": "dojoxGridColPlaceBottom"
}, dojo.body());
this._hide(this.bottomMarker);
this.topMarker = dojo.create("div", {
"id": topMarkerId,
"class": "dojoxGridColPlaceTop"
}, dojo.body());
this._hide(this.topMarker);
}
this.arrowDim = dojo.contentBox(this.bottomMarker);
var headerHeight = dojo.contentBox(this.headerContentNode.firstChild.rows[0]).h;
this.source = new dojo.dnd.Source(this.headerContentNode.firstChild.rows[0], {
horizontal: true,
accept: [ "gridColumn_" + this.grid.id ],
viewIndex: this.index,
generateText: false,
onMouseDown: dojo.hitch(this, function(e){
this.header.decorateEvent(e);
if((this.header.overRightResizeArea(e) || this.header.overLeftResizeArea(e)) &&
this.header.canResize(e) && !this.header.moveable){
this.header.beginColumnResize(e);
}else{
if(this.grid.headerMenu){
this.grid.headerMenu.onCancel(true);
}
// IE reports a left click as 1, where everything else reports 0
if(e.button === (dojo.isIE ? 1 : 0)){
dojo.dnd.Source.prototype.onMouseDown.call(this.source, e);
}
}
}),
onMouseOver: dojo.hitch(this, function(e){
var src = this.source;
if(src._getChildByEvent(e)){
dojo.dnd.Source.prototype.onMouseOver.apply(src, arguments);
}
}),
_markTargetAnchor: dojo.hitch(this, function(before){
var src = this.source;
if(src.current == src.targetAnchor && src.before == before){ return; }
if(src.targetAnchor && getSibling(src.targetAnchor, src.before)){
src._removeItemClass(getSibling(src.targetAnchor, src.before), src.before ? "After" : "Before");
}
dojo.dnd.Source.prototype._markTargetAnchor.call(src, before);
var target = before ? src.targetAnchor : getSibling(src.targetAnchor, src.before);
var endAdd = 0;
if (!target) {
target = src.targetAnchor;
endAdd = dojo.contentBox(target).w + this.arrowDim.w/2 + 2;
}
// NOTE: this is for backwards compatibility with Dojo 1.3
var pos = (dojo.position||dojo._abs)(target, true);
var left = Math.floor(pos.x - this.arrowDim.w/2 + endAdd);
dojo.style(this.bottomMarker, "visibility", "visible");
dojo.style(this.topMarker, "visibility", "visible");
dojo.style(this.bottomMarker, {
"left": left + "px",
"top" : (headerHeight + pos.y) + "px"
});
dojo.style(this.topMarker, {
"left": left + "px",
"top" : (pos.y - this.arrowDim.h) + "px"
});
if(src.targetAnchor && getSibling(src.targetAnchor, src.before)){
src._addItemClass(getSibling(src.targetAnchor, src.before), src.before ? "After" : "Before");
}
}),
_unmarkTargetAnchor: dojo.hitch(this, function(){
var src = this.source;
if(!src.targetAnchor){ return; }
if(src.targetAnchor && getSibling(src.targetAnchor, src.before)){
src._removeItemClass(getSibling(src.targetAnchor, src.before), src.before ? "After" : "Before");
}
this._hide(this.bottomMarker);
this._hide(this.topMarker);
dojo.dnd.Source.prototype._unmarkTargetAnchor.call(src);
}),
destroy: dojo.hitch(this, function(){
dojo.disconnect(this._source_conn);
dojo.unsubscribe(this._source_sub);
dojo.dnd.Source.prototype.destroy.call(this.source);
if(this.bottomMarker){
dojo.destroy(this.bottomMarker);
delete this.bottomMarker;
}
if(this.topMarker){
dojo.destroy(this.topMarker);
delete this.topMarker;
}
}),
onDndCancel: dojo.hitch(this, function(){
dojo.dnd.Source.prototype.onDndCancel.call(this.source);
this._hide(this.bottomMarker);
this._hide(this.topMarker);
})
});
this._source_conn = dojo.connect(this.source, "onDndDrop", this, "_onDndDrop");
this._source_sub = dojo.subscribe("/dnd/drop/before", this, "_onDndDropBefore");
this.source.startup();
}
},
_hide: function(node){
dojo.style(node, {
left: "-10000px",
top: "-10000px",
"visibility": "hidden"
});
},
_onDndDropBefore: function(source, nodes, copy){
if(dojo.dnd.manager().target !== this.source){
return;
}
this.source._targetNode = this.source.targetAnchor;
this.source._beforeTarget = this.source.before;
var views = this.grid.views.views;
var srcView = views[source.viewIndex];
var tgtView = views[this.index];
if(tgtView != srcView){
srcView.convertColPctToFixed();
tgtView.convertColPctToFixed();
}
},
_onDndDrop: function(source, nodes, copy){
if(dojo.dnd.manager().target !== this.source){
if(dojo.dnd.manager().source === this.source){
this._removingColumn = true;
}
return;
}
this._hide(this.bottomMarker);
this._hide(this.topMarker);
var getIdx = function(n){
return n ? dojo.attr(n, "idx") : null;
};
var w = dojo.marginBox(nodes[0]).w;
if(source.viewIndex !== this.index){
var views = this.grid.views.views;
var srcView = views[source.viewIndex];
var tgtView = views[this.index];
if(srcView.viewWidth && srcView.viewWidth != "auto"){
srcView.setColumnsWidth(srcView.getColumnsWidth() - w);
}
if(tgtView.viewWidth && tgtView.viewWidth != "auto"){
tgtView.setColumnsWidth(tgtView.getColumnsWidth());
}
}
var stn = this.source._targetNode;
var stb = this.source._beforeTarget;
var layout = this.grid.layout;
var idx = this.index;
delete this.source._targetNode;
delete this.source._beforeTarget;
layout.moveColumn(
source.viewIndex,
idx,
getIdx(nodes[0]),
getIdx(stn),
stb);
},
renderHeader: function(){
this.headerContentNode.innerHTML = this.header.generateHtml(this._getHeaderContent);
if(this.flexCells){
this.contentWidth = this.getContentWidth();
this.headerContentNode.firstChild.style.width = this.contentWidth;
}
dojox.grid.util.fire(this, "onAfterRow", [-1, this.structure.cells, this.headerContentNode]);
},
// note: not called in 'view' context
_getHeaderContent: function(inCell){
var n = inCell.name || inCell.grid.getCellName(inCell);
var ret = [ '<div class="dojoxGridSortNode' ];
if(inCell.index != inCell.grid.getSortIndex()){
ret.push('">');
}else{
ret = ret.concat([ ' ',
inCell.grid.sortInfo > 0 ? 'dojoxGridSortUp' : 'dojoxGridSortDown',
'"><div class="dojoxGridArrowButtonChar">',
inCell.grid.sortInfo > 0 ? '&#9650;' : '&#9660;',
'</div><div class="dojoxGridArrowButtonNode" role="presentation"></div>']);
}
ret = ret.concat([n, '</div>']);
return ret.join('');
},
resize: function(){
this.adaptHeight();
this.adaptWidth();
},
hasHScrollbar: function(reset){
var hadScroll = this._hasHScroll||false;
if(this._hasHScroll == undefined || reset){
if(this.noscroll){
this._hasHScroll = false;
}else{
var style = dojo.style(this.scrollboxNode, "overflow");
if(style == "hidden"){
this._hasHScroll = false;
}else if(style == "scroll"){
this._hasHScroll = true;
}else{
this._hasHScroll = (this.scrollboxNode.offsetWidth - this.getScrollbarWidth() < this.contentNode.offsetWidth );
}
}
}
if(hadScroll !== this._hasHScroll){
this.grid.update();
}
return this._hasHScroll; // Boolean
},
hasVScrollbar: function(reset){
var hadScroll = this._hasVScroll||false;
if(this._hasVScroll == undefined || reset){
if(this.noscroll){
this._hasVScroll = false;
}else{
var style = dojo.style(this.scrollboxNode, "overflow");
if(style == "hidden"){
this._hasVScroll = false;
}else if(style == "scroll"){
this._hasVScroll = true;
}else{
this._hasVScroll = (this.scrollboxNode.scrollHeight > this.scrollboxNode.clientHeight);
}
}
}
if(hadScroll !== this._hasVScroll){
this.grid.update();
}
return this._hasVScroll; // Boolean
},
convertColPctToFixed: function(){
// Fix any percentage widths to be pixel values
var hasPct = false;
this.grid.initialWidth = "";
var cellNodes = dojo.query("th", this.headerContentNode);
var fixedWidths = dojo.map(cellNodes, function(c, vIdx){
var w = c.style.width;
dojo.attr(c, "vIdx", vIdx);
if(w && w.slice(-1) == "%"){
hasPct = true;
}else if(w && w.slice(-2) == "px"){
return window.parseInt(w, 10);
}
return dojo.contentBox(c).w;
});
if(hasPct){
dojo.forEach(this.grid.layout.cells, function(cell, idx){
if(cell.view == this){
var cellNode = cell.view.getHeaderCellNode(cell.index);
if(cellNode && dojo.hasAttr(cellNode, "vIdx")){
var vIdx = window.parseInt(dojo.attr(cellNode, "vIdx"));
this.setColWidth(idx, fixedWidths[vIdx]);
dojo.removeAttr(cellNode, "vIdx");
}
}
}, this);
return true;
}
return false;
},
adaptHeight: function(minusScroll){
if(!this.grid._autoHeight){
var h = (this.domNode.style.height && parseInt(this.domNode.style.height.replace(/px/,''), 10)) || this.domNode.clientHeight;
var self = this;
var checkOtherViewScrollers = function(){
var v;
for(var i in self.grid.views.views){
v = self.grid.views.views[i];
if(v !== self && v.hasHScrollbar()){
return true;
}
}
return false;
};
if(minusScroll || (this.noscroll && checkOtherViewScrollers())){
h -= dojox.html.metrics.getScrollbar().h;
}
dojox.grid.util.setStyleHeightPx(this.scrollboxNode, h);
}
this.hasVScrollbar(true);
},
adaptWidth: function(){
if(this.flexCells){
// the view content width
this.contentWidth = this.getContentWidth();
this.headerContentNode.firstChild.style.width = this.contentWidth;
}
// FIXME: it should be easier to get w from this.scrollboxNode.clientWidth,
// but clientWidth seemingly does not include scrollbar width in some cases
var w = this.scrollboxNode.offsetWidth - this.getScrollbarWidth();
if(!this._removingColumn){
w = Math.max(w, this.getColumnsWidth()) + 'px';
}else{
w = Math.min(w, this.getColumnsWidth()) + 'px';
this._removingColumn = false;
}
var cn = this.contentNode;
cn.style.width = w;
this.hasHScrollbar(true);
},
setSize: function(w, h){
var ds = this.domNode.style;
var hs = this.headerNode.style;
if(w){
ds.width = w;
hs.width = w;
}
ds.height = (h >= 0 ? h + 'px' : '');
},
renderRow: function(inRowIndex){
var rowNode = this.createRowNode(inRowIndex);
this.buildRow(inRowIndex, rowNode);
this.grid.edit.restore(this, inRowIndex);
return rowNode;
},
createRowNode: function(inRowIndex){
var node = document.createElement("div");
node.className = this.classTag + 'Row';
if (this instanceof dojox.grid._RowSelector){
dojo.attr(node,"role","presentation");
}else{
dojo.attr(node,"role","row");
if (this.grid.selectionMode != "none") {
dojo.attr(node, "aria-selected", "false"); //rows can be selected so add aria-selected prop
}
}
node[dojox.grid.util.gridViewTag] = this.id;
node[dojox.grid.util.rowIndexTag] = inRowIndex;
this.rowNodes[inRowIndex] = node;
return node;
},
buildRow: function(inRowIndex, inRowNode){
this.buildRowContent(inRowIndex, inRowNode);
this.styleRow(inRowIndex, inRowNode);
},
buildRowContent: function(inRowIndex, inRowNode){
inRowNode.innerHTML = this.content.generateHtml(inRowIndex, inRowIndex);
if(this.flexCells && this.contentWidth){
// FIXME: accessing firstChild here breaks encapsulation
inRowNode.firstChild.style.width = this.contentWidth;
}
dojox.grid.util.fire(this, "onAfterRow", [inRowIndex, this.structure.cells, inRowNode]);
},
rowRemoved:function(inRowIndex){
if(inRowIndex >= 0){
this._cleanupRowWidgets(this.getRowNode(inRowIndex));
}
this.grid.edit.save(this, inRowIndex);
delete this.rowNodes[inRowIndex];
},
getRowNode: function(inRowIndex){
return this.rowNodes[inRowIndex];
},
getCellNode: function(inRowIndex, inCellIndex){
var row = this.getRowNode(inRowIndex);
if(row){
return this.content.getCellNode(row, inCellIndex);
}
},
getHeaderCellNode: function(inCellIndex){
if(this.headerContentNode){
return this.header.getCellNode(this.headerContentNode, inCellIndex);
}
},
// styling
styleRow: function(inRowIndex, inRowNode){
inRowNode._style = getStyleText(inRowNode);
this.styleRowNode(inRowIndex, inRowNode);
},
styleRowNode: function(inRowIndex, inRowNode){
if(inRowNode){
this.doStyleRowNode(inRowIndex, inRowNode);
}
},
doStyleRowNode: function(inRowIndex, inRowNode){
this.grid.styleRowNode(inRowIndex, inRowNode);
},
// updating
updateRow: function(inRowIndex){
var rowNode = this.getRowNode(inRowIndex);
if(rowNode){
rowNode.style.height = '';
this.buildRow(inRowIndex, rowNode);
}
return rowNode;
},
updateRowStyles: function(inRowIndex){
this.styleRowNode(inRowIndex, this.getRowNode(inRowIndex));
},
// scrolling
lastTop: 0,
firstScroll:0,
doscroll: function(inEvent){
//var s = dojo.marginBox(this.headerContentNode.firstChild);
var isLtr = dojo._isBodyLtr();
if(this.firstScroll < 2){
if((!isLtr && this.firstScroll == 1) || (isLtr && this.firstScroll === 0)){
var s = dojo.marginBox(this.headerNodeContainer);
if(dojo.isIE){
this.headerNodeContainer.style.width = s.w + this.getScrollbarWidth() + 'px';
}else if(dojo.isMoz){
//TODO currently only for FF, not sure for safari and opera
this.headerNodeContainer.style.width = s.w - this.getScrollbarWidth() + 'px';
//this.headerNodeContainer.style.width = s.w + 'px';
//set scroll to right in FF
this.scrollboxNode.scrollLeft = isLtr ?
this.scrollboxNode.clientWidth - this.scrollboxNode.scrollWidth :
this.scrollboxNode.scrollWidth - this.scrollboxNode.clientWidth;
}
}
this.firstScroll++;
}
this.headerNode.scrollLeft = this.scrollboxNode.scrollLeft;
// 'lastTop' is a semaphore to prevent feedback-loop with setScrollTop below
var top = this.scrollboxNode.scrollTop;
if(top !== this.lastTop){
this.grid.scrollTo(top);
}
},
setScrollTop: function(inTop){
// 'lastTop' is a semaphore to prevent feedback-loop with doScroll above
this.lastTop = inTop;
this.scrollboxNode.scrollTop = inTop;
return this.scrollboxNode.scrollTop;
},
// event handlers (direct from DOM)
doContentEvent: function(e){
if(this.content.decorateEvent(e)){
this.grid.onContentEvent(e);
}
},
doHeaderEvent: function(e){
if(this.header.decorateEvent(e)){
this.grid.onHeaderEvent(e);
}
},
// event dispatch(from Grid)
dispatchContentEvent: function(e){
return this.content.dispatchEvent(e);
},
dispatchHeaderEvent: function(e){
return this.header.dispatchEvent(e);
},
// column resizing
setColWidth: function(inIndex, inWidth){
this.grid.setCellWidth(inIndex, inWidth + 'px');
},
update: function(){
if(!this.domNode){
return;
}
this.content.update();
this.grid.update();
//get scroll after update or scroll left setting goes wrong on IE.
//See trac: #8040
var left = this.scrollboxNode.scrollLeft;
this.scrollboxNode.scrollLeft = left;
this.headerNode.scrollLeft = left;
}
});
dojo.declare("dojox.grid._GridAvatar", dojo.dnd.Avatar, {
construct: function(){
var dd = dojo.doc;
var a = dd.createElement("table");
a.cellPadding = a.cellSpacing = "0";
a.className = "dojoxGridDndAvatar";
a.style.position = "absolute";
a.style.zIndex = 1999;
a.style.margin = "0px"; // to avoid dojo.marginBox() problems with table's margins
var b = dd.createElement("tbody");
var tr = dd.createElement("tr");
var td = dd.createElement("td");
var img = dd.createElement("td");
tr.className = "dojoxGridDndAvatarItem";
img.className = "dojoxGridDndAvatarItemImage";
img.style.width = "16px";
var source = this.manager.source, node;
if(source.creator){
// create an avatar representation of the node
node = source._normalizedCreator(source.getItem(this.manager.nodes[0].id).data, "avatar").node;
}else{
// or just clone the node and hope it works
node = this.manager.nodes[0].cloneNode(true);
var table, tbody;
if(node.tagName.toLowerCase() == "tr"){
// insert extra table nodes
table = dd.createElement("table");
tbody = dd.createElement("tbody");
tbody.appendChild(node);
table.appendChild(tbody);
node = table;
}else if(node.tagName.toLowerCase() == "th"){
// insert extra table nodes
table = dd.createElement("table");
tbody = dd.createElement("tbody");
var r = dd.createElement("tr");
table.cellPadding = table.cellSpacing = "0";
r.appendChild(node);
tbody.appendChild(r);
table.appendChild(tbody);
node = table;
}
}
node.id = "";
td.appendChild(node);
tr.appendChild(img);
tr.appendChild(td);
dojo.style(tr, "opacity", 0.9);
b.appendChild(tr);
a.appendChild(b);
this.node = a;
var m = dojo.dnd.manager();
this.oldOffsetY = m.OFFSET_Y;
m.OFFSET_Y = 1;
},
destroy: function(){
dojo.dnd.manager().OFFSET_Y = this.oldOffsetY;
this.inherited(arguments);
}
});
var oldMakeAvatar = dojo.dnd.manager().makeAvatar;
dojo.dnd.manager().makeAvatar = function(){
var src = this.source;
if(src.viewIndex !== undefined && !dojo.hasClass(dojo.body(),"dijit_a11y")){
return new dojox.grid._GridAvatar(this);
}
return oldMakeAvatar.call(dojo.dnd.manager());
};
})();
}
if(!dojo._hasResource["dojox.grid._RowSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._RowSelector"] = true;
dojo.provide("dojox.grid._RowSelector");
dojo.declare('dojox.grid._RowSelector', dojox.grid._View, {
// summary:
// Custom grid view. If used in a grid structure, provides a small selectable region for grid rows.
defaultWidth: "2em",
noscroll: true,
padBorderWidth: 2,
buildRendering: function(){
this.inherited('buildRendering', arguments);
this.scrollboxNode.style.overflow = "hidden";
this.headerNode.style.visibility = "hidden";
},
getWidth: function(){
return this.viewWidth || this.defaultWidth;
},
buildRowContent: function(inRowIndex, inRowNode){
var w = this.contentWidth || 0;
inRowNode.innerHTML = '<table class="dojoxGridRowbarTable" style="width:' + w + 'px;height:1px;" border="0" cellspacing="0" cellpadding="0" role="presentation"><tr><td class="dojoxGridRowbarInner">&nbsp;</td></tr></table>';
},
renderHeader: function(){
},
updateRow: function(){
},
resize: function(){
this.adaptHeight();
},
adaptWidth: function(){
// Only calculate this here - rather than every call to buildRowContent
if(!("contentWidth" in this) && this.contentNode){
this.contentWidth = this.contentNode.offsetWidth - this.padBorderWidth;
}
},
// styling
doStyleRowNode: function(inRowIndex, inRowNode){
var n = [ "dojoxGridRowbar dojoxGridNonNormalizedCell" ];
if(this.grid.rows.isOver(inRowIndex)){
n.push("dojoxGridRowbarOver");
}
if(this.grid.selection.isSelected(inRowIndex)){
n.push("dojoxGridRowbarSelected");
}
inRowNode.className = n.join(" ");
},
// event handlers
domouseover: function(e){
this.grid.onMouseOverRow(e);
},
domouseout: function(e){
if(!this.isIntraRowEvent(e)){
this.grid.onMouseOutRow(e);
}
}
});
}
if(!dojo._hasResource["dojox.grid._Layout"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._Layout"] = true;
dojo.provide("dojox.grid._Layout");
dojo.declare("dojox.grid._Layout", null, {
// summary:
// Controls grid cell layout. Owned by grid and used internally.
constructor: function(inGrid){
this.grid = inGrid;
},
// flat array of grid cells
cells: [],
// structured array of grid cells
structure: null,
// default cell width
defaultWidth: '6em',
// methods
moveColumn: function(sourceViewIndex, destViewIndex, cellIndex, targetIndex, before){
var source_cells = this.structure[sourceViewIndex].cells[0];
var dest_cells = this.structure[destViewIndex].cells[0];
var cell = null;
var cell_ri = 0;
var target_ri = 0;
for(var i=0, c; c=source_cells[i]; i++){
if(c.index == cellIndex){
cell_ri = i;
break;
}
}
cell = source_cells.splice(cell_ri, 1)[0];
cell.view = this.grid.views.views[destViewIndex];
for(i=0, c=null; c=dest_cells[i]; i++){
if(c.index == targetIndex){
target_ri = i;
break;
}
}
if(!before){
target_ri += 1;
}
dest_cells.splice(target_ri, 0, cell);
var sortedCell = this.grid.getCell(this.grid.getSortIndex());
if(sortedCell){
sortedCell._currentlySorted = this.grid.getSortAsc();
}
this.cells = [];
cellIndex = 0;
var v;
for(i=0; v=this.structure[i]; i++){
for(var j=0, cs; cs=v.cells[j]; j++){
for(var k=0; c=cs[k]; k++){
c.index = cellIndex;
this.cells.push(c);
if("_currentlySorted" in c){
var si = cellIndex + 1;
si *= c._currentlySorted ? 1 : -1;
this.grid.sortInfo = si;
delete c._currentlySorted;
}
cellIndex++;
}
}
}
this.grid.setupHeaderMenu();
//this.grid.renderOnIdle();
},
setColumnVisibility: function(columnIndex, visible){
var cell = this.cells[columnIndex];
if(cell.hidden == visible){
cell.hidden = !visible;
var v = cell.view, w = v.viewWidth;
if(w && w != "auto"){
v._togglingColumn = dojo.marginBox(cell.getHeaderNode()).w || 0;
}
v.update();
return true;
}else{
return false;
}
},
addCellDef: function(inRowIndex, inCellIndex, inDef){
var self = this;
var getCellWidth = function(inDef){
var w = 0;
if(inDef.colSpan > 1){
w = 0;
}else{
w = inDef.width || self._defaultCellProps.width || self.defaultWidth;
if(!isNaN(w)){
w = w + "em";
}
}
return w;
};
var props = {
grid: this.grid,
subrow: inRowIndex,
layoutIndex: inCellIndex,
index: this.cells.length
};
if(inDef && inDef instanceof dojox.grid.cells._Base){
var new_cell = dojo.clone(inDef);
props.unitWidth = getCellWidth(new_cell._props);
new_cell = dojo.mixin(new_cell, this._defaultCellProps, inDef._props, props);
return new_cell;
}
var cell_type = inDef.type || this._defaultCellProps.type || dojox.grid.cells.Cell;
props.unitWidth = getCellWidth(inDef);
return new cell_type(dojo.mixin({}, this._defaultCellProps, inDef, props));
},
addRowDef: function(inRowIndex, inDef){
var result = [];
var relSum = 0, pctSum = 0, doRel = true;
for(var i=0, def, cell; (def=inDef[i]); i++){
cell = this.addCellDef(inRowIndex, i, def);
result.push(cell);
this.cells.push(cell);
// Check and calculate the sum of all relative widths
if(doRel && cell.relWidth){
relSum += cell.relWidth;
}else if(cell.width){
var w = cell.width;
if(typeof w == "string" && w.slice(-1) == "%"){
pctSum += window.parseInt(w, 10);
}else if(w == "auto"){
// relative widths doesn't play nice with auto - since we
// don't have a way of knowing how much space the auto is
// supposed to take up.
doRel = false;
}
}
}
if(relSum && doRel){
// We have some kind of relWidths specified - so change them to %
dojo.forEach(result, function(cell){
if(cell.relWidth){
cell.width = cell.unitWidth = ((cell.relWidth / relSum) * (100 - pctSum)) + "%";
}
});
}
return result;
},
addRowsDef: function(inDef){
var result = [];
if(dojo.isArray(inDef)){
if(dojo.isArray(inDef[0])){
for(var i=0, row; inDef && (row=inDef[i]); i++){
result.push(this.addRowDef(i, row));
}
}else{
result.push(this.addRowDef(0, inDef));
}
}
return result;
},
addViewDef: function(inDef){
this._defaultCellProps = inDef.defaultCell || {};
if(inDef.width && inDef.width == "auto"){
delete inDef.width;
}
return dojo.mixin({}, inDef, {cells: this.addRowsDef(inDef.rows || inDef.cells)});
},
setStructure: function(inStructure){
this.fieldIndex = 0;
this.cells = [];
var s = this.structure = [];
if(this.grid.rowSelector){
var sel = { type: dojox._scopeName + ".grid._RowSelector" };
if(dojo.isString(this.grid.rowSelector)){
var width = this.grid.rowSelector;
if(width == "false"){
sel = null;
}else if(width != "true"){
sel['width'] = width;
}
}else{
if(!this.grid.rowSelector){
sel = null;
}
}
if(sel){
s.push(this.addViewDef(sel));
}
}
var isCell = function(def){
return ("name" in def || "field" in def || "get" in def);
};
var isRowDef = function(def){
if(dojo.isArray(def)){
if(dojo.isArray(def[0]) || isCell(def[0])){
return true;
}
}
return false;
};
var isView = function(def){
return (def !== null && dojo.isObject(def) &&
("cells" in def || "rows" in def || ("type" in def && !isCell(def))));
};
if(dojo.isArray(inStructure)){
var hasViews = false;
for(var i=0, st; (st=inStructure[i]); i++){
if(isView(st)){
hasViews = true;
break;
}
}
if(!hasViews){
s.push(this.addViewDef({ cells: inStructure }));
}else{
for(i=0; (st=inStructure[i]); i++){
if(isRowDef(st)){
s.push(this.addViewDef({ cells: st }));
}else if(isView(st)){
s.push(this.addViewDef(st));
}
}
}
}else if(isView(inStructure)){
// it's a view object
s.push(this.addViewDef(inStructure));
}
this.cellCount = this.cells.length;
this.grid.setupHeaderMenu();
}
});
}
if(!dojo._hasResource["dojox.grid._ViewManager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._ViewManager"] = true;
dojo.provide("dojox.grid._ViewManager");
dojo.declare('dojox.grid._ViewManager', null, {
// summary:
// A collection of grid views. Owned by grid and used internally for managing grid views.
// description:
// Grid creates views automatically based on grid's layout structure.
// Users should typically not need to access individual views or the views collection directly.
constructor: function(inGrid){
this.grid = inGrid;
},
defaultWidth: 200,
views: [],
// operations
resize: function(){
this.onEach("resize");
},
render: function(){
this.onEach("render");
},
// views
addView: function(inView){
inView.idx = this.views.length;
this.views.push(inView);
},
destroyViews: function(){
for(var i=0, v; v=this.views[i]; i++){
v.destroy();
}
this.views = [];
},
getContentNodes: function(){
var nodes = [];
for(var i=0, v; v=this.views[i]; i++){
nodes.push(v.contentNode);
}
return nodes;
},
forEach: function(inCallback){
for(var i=0, v; v=this.views[i]; i++){
inCallback(v, i);
}
},
onEach: function(inMethod, inArgs){
inArgs = inArgs || [];
for(var i=0, v; v=this.views[i]; i++){
if(inMethod in v){
v[inMethod].apply(v, inArgs);
}
}
},
// layout
normalizeHeaderNodeHeight: function(){
var rowNodes = [];
for(var i=0, v; (v=this.views[i]); i++){
if(v.headerContentNode.firstChild){
rowNodes.push(v.headerContentNode);
}
}
this.normalizeRowNodeHeights(rowNodes);
},
normalizeRowNodeHeights: function(inRowNodes){
var h = 0;
var currHeights = [];
if(this.grid.rowHeight){
h = this.grid.rowHeight;
}else{
if(inRowNodes.length <= 1){
// no need to normalize if we are the only one...
return;
}
for(var i=0, n; (n=inRowNodes[i]); i++){
// We only care about the height - so don't use marginBox. This
// depends on the container not having any margin (which it shouldn't)
// Also - we only look up the height if the cell doesn't have the
// dojoxGridNonNormalizedCell class (like for row selectors)
if(!dojo.hasClass(n, "dojoxGridNonNormalizedCell")){
currHeights[i] = n.firstChild.offsetHeight;
h = Math.max(h, currHeights[i]);
}
}
h = (h >= 0 ? h : 0);
//Work around odd FF3 rendering bug: #8864.
//A one px increase fixes FireFox 3's rounding bug for fractional font sizes.
if(dojo.isMoz && h){h++;}
}
for(i=0; (n=inRowNodes[i]); i++){
if(currHeights[i] != h){
n.firstChild.style.height = h + "px";
}
}
},
resetHeaderNodeHeight: function(){
for(var i=0, v, n; (v=this.views[i]); i++){
n = v.headerContentNode.firstChild;
if(n){
n.style.height = "";
}
}
},
renormalizeRow: function(inRowIndex){
var rowNodes = [];
for(var i=0, v, n; (v=this.views[i])&&(n=v.getRowNode(inRowIndex)); i++){
n.firstChild.style.height = '';
rowNodes.push(n);
}
this.normalizeRowNodeHeights(rowNodes);
},
getViewWidth: function(inIndex){
return this.views[inIndex].getWidth() || this.defaultWidth;
},
// must be called after view widths are properly set or height can be miscalculated
// if there are flex columns
measureHeader: function(){
// need to reset view header heights so they are properly measured.
this.resetHeaderNodeHeight();
this.forEach(function(inView){
inView.headerContentNode.style.height = '';
});
var h = 0;
// calculate maximum view header height
this.forEach(function(inView){
h = Math.max(inView.headerNode.offsetHeight, h);
});
return h;
},
measureContent: function(){
var h = 0;
this.forEach(function(inView){
h = Math.max(inView.domNode.offsetHeight, h);
});
return h;
},
findClient: function(inAutoWidth){
// try to use user defined client
var c = this.grid.elasticView || -1;
// attempt to find implicit client
if(c < 0){
for(var i=1, v; (v=this.views[i]); i++){
if(v.viewWidth){
for(i=1; (v=this.views[i]); i++){
if(!v.viewWidth){
c = i;
break;
}
}
break;
}
}
}
// client is in the middle by default
if(c < 0){
c = Math.floor(this.views.length / 2);
}
return c;
},
arrange: function(l, w){
var i, v, vw, len = this.views.length;
// find the client
var c = (w <= 0 ? len : this.findClient());
// layout views
var setPosition = function(v, l){
var ds = v.domNode.style;
var hs = v.headerNode.style;
if(!dojo._isBodyLtr()){
ds.right = l + 'px';
// fixed rtl, the scrollbar is on the right side in FF
if (dojo.isMoz) {
hs.right = l + v.getScrollbarWidth() + 'px';
hs.width = parseInt(hs.width, 10) - v.getScrollbarWidth() + 'px';
}else{
hs.right = l + 'px';
}
}else{
ds.left = l + 'px';
hs.left = l + 'px';
}
ds.top = 0 + 'px';
hs.top = 0;
};
// for views left of the client
//BiDi TODO: The left and right should not appear in BIDI environment. Should be replaced with
//leading and tailing concept.
for(i=0; (v=this.views[i])&&(i<c); i++){
// get width
vw = this.getViewWidth(i);
// process boxes
v.setSize(vw, 0);
setPosition(v, l);
if(v.headerContentNode && v.headerContentNode.firstChild){
vw = v.getColumnsWidth()+v.getScrollbarWidth();
}else{
vw = v.domNode.offsetWidth;
}
// update position
l += vw;
}
// next view (is the client, i++ == c)
i++;
// start from the right edge
var r = w;
// for views right of the client (iterated from the right)
for(var j=len-1; (v=this.views[j])&&(i<=j); j--){
// get width
vw = this.getViewWidth(j);
// set size
v.setSize(vw, 0);
// measure in pixels
vw = v.domNode.offsetWidth;
// update position
r -= vw;
// set position
setPosition(v, r);
}
if(c<len){
v = this.views[c];
// position the client box between left and right boxes
vw = Math.max(1, r-l);
// set size
v.setSize(vw + 'px', 0);
setPosition(v, l);
}
return l;
},
// rendering
renderRow: function(inRowIndex, inNodes, skipRenorm){
var rowNodes = [];
for(var i=0, v, n, rowNode; (v=this.views[i])&&(n=inNodes[i]); i++){
rowNode = v.renderRow(inRowIndex);
n.appendChild(rowNode);
rowNodes.push(rowNode);
}
if(!skipRenorm){
this.normalizeRowNodeHeights(rowNodes);
}
},
rowRemoved: function(inRowIndex){
this.onEach("rowRemoved", [ inRowIndex ]);
},
// updating
updateRow: function(inRowIndex, skipRenorm){
for(var i=0, v; v=this.views[i]; i++){
v.updateRow(inRowIndex);
}
if(!skipRenorm){
this.renormalizeRow(inRowIndex);
}
},
updateRowStyles: function(inRowIndex){
this.onEach("updateRowStyles", [ inRowIndex ]);
},
// scrolling
setScrollTop: function(inTop){
var top = inTop;
for(var i=0, v; v=this.views[i]; i++){
top = v.setScrollTop(inTop);
// Work around IE not firing scroll events that cause header offset
// issues to occur.
if(dojo.isIE && v.headerNode && v.scrollboxNode){
v.headerNode.scrollLeft = v.scrollboxNode.scrollLeft;
}
}
return top;
//this.onEach("setScrollTop", [ inTop ]);
},
getFirstScrollingView: function(){
// summary: Returns the first grid view with a scroll bar
for(var i=0, v; (v=this.views[i]); i++){
if(v.hasHScrollbar() || v.hasVScrollbar()){
return v;
}
}
return null;
}
});
}
if(!dojo._hasResource["dojox.grid._RowManager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._RowManager"] = true;
dojo.provide("dojox.grid._RowManager");
(function(){
var setStyleText = function(inNode, inStyleText){
if(inNode.style.cssText == undefined){
inNode.setAttribute("style", inStyleText);
}else{
inNode.style.cssText = inStyleText;
}
};
dojo.declare("dojox.grid._RowManager", null, {
// Stores information about grid rows. Owned by grid and used internally.
constructor: function(inGrid){
this.grid = inGrid;
},
linesToEms: 2,
overRow: -2,
// styles
prepareStylingRow: function(inRowIndex, inRowNode){
return {
index: inRowIndex,
node: inRowNode,
odd: Boolean(inRowIndex&1),
selected: !!this.grid.selection.isSelected(inRowIndex),
over: this.isOver(inRowIndex),
customStyles: "",
customClasses: "dojoxGridRow"
};
},
styleRowNode: function(inRowIndex, inRowNode){
var row = this.prepareStylingRow(inRowIndex, inRowNode);
this.grid.onStyleRow(row);
this.applyStyles(row);
},
applyStyles: function(inRow){
var i = inRow;
i.node.className = i.customClasses;
var h = i.node.style.height;
setStyleText(i.node, i.customStyles + ';' + (i.node._style||''));
i.node.style.height = h;
},
updateStyles: function(inRowIndex){
this.grid.updateRowStyles(inRowIndex);
},
// states and events
setOverRow: function(inRowIndex){
var last = this.overRow;
this.overRow = inRowIndex;
if((last!=this.overRow)&&(dojo.isString(last) || last >= 0)){
this.updateStyles(last);
}
this.updateStyles(this.overRow);
},
isOver: function(inRowIndex){
return (this.overRow == inRowIndex && !dojo.hasClass(this.grid.domNode, "dojoxGridColumnResizing"));
}
});
})();
}
if(!dojo._hasResource["dojox.grid._FocusManager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._FocusManager"] = true;
dojo.provide("dojox.grid._FocusManager");
// focus management
dojo.declare("dojox.grid._FocusManager", null, {
// summary:
// Controls grid cell focus. Owned by grid and used internally for focusing.
// Note: grid cell actually receives keyboard input only when cell is being edited.
constructor: function(inGrid){
this.grid = inGrid;
this.cell = null;
this.rowIndex = -1;
this._connects = [];
this.headerMenu = this.grid.headerMenu;
this._connects.push(dojo.connect(this.grid.domNode, "onfocus", this, "doFocus"));
this._connects.push(dojo.connect(this.grid.domNode, "onblur", this, "doBlur"));
this._connects.push(dojo.connect(this.grid.domNode, "oncontextmenu", this, "doContextMenu"));
this._connects.push(dojo.connect(this.grid.lastFocusNode, "onfocus", this, "doLastNodeFocus"));
this._connects.push(dojo.connect(this.grid.lastFocusNode, "onblur", this, "doLastNodeBlur"));
this._connects.push(dojo.connect(this.grid,"_onFetchComplete", this, "_delayedCellFocus"));
this._connects.push(dojo.connect(this.grid,"postrender", this, "_delayedHeaderFocus"));
},
destroy: function(){
dojo.forEach(this._connects, dojo.disconnect);
delete this.grid;
delete this.cell;
},
_colHeadNode: null,
_colHeadFocusIdx: null,
_contextMenuBindNode: null,
tabbingOut: false,
focusClass: "dojoxGridCellFocus",
focusView: null,
initFocusView: function(){
this.focusView = this.grid.views.getFirstScrollingView() || this.focusView;
this._initColumnHeaders();
},
isFocusCell: function(inCell, inRowIndex){
// summary:
// states if the given cell is focused
// inCell: object
// grid cell object
// inRowIndex: int
// grid row index
// returns:
// true of the given grid cell is focused
return (this.cell == inCell) && (this.rowIndex == inRowIndex);
},
isLastFocusCell: function(){
if(this.cell){
return (this.rowIndex == this.grid.rowCount-1) && (this.cell.index == this.grid.layout.cellCount-1);
}
return false;
},
isFirstFocusCell: function(){
if(this.cell){
return (this.rowIndex === 0) && (this.cell.index === 0);
}
return false;
},
isNoFocusCell: function(){
return (this.rowIndex < 0) || !this.cell;
},
isNavHeader: function(){
// summary:
// states whether currently navigating among column headers.
// returns:
// true if focus is on a column header; false otherwise.
return (!!this._colHeadNode);
},
getHeaderIndex: function(){
// summary:
// if one of the column headers currently has focus, return its index.
// returns:
// index of the focused column header, or -1 if none have focus.
if(this._colHeadNode){
return dojo.indexOf(this._findHeaderCells(), this._colHeadNode);
}else{
return -1;
}
},
_focusifyCellNode: function(inBork){
var n = this.cell && this.cell.getNode(this.rowIndex);
if(n){
dojo.toggleClass(n, this.focusClass, inBork);
if(inBork){
var sl = this.scrollIntoView();
try{
if(!this.grid.edit.isEditing()){
dojox.grid.util.fire(n, "focus");
if(sl){ this.cell.view.scrollboxNode.scrollLeft = sl; }
}
}catch(e){}
}
}
},
_delayedCellFocus: function(){
if(this.isNavHeader()||!this.grid._focused){
return;
}
var n = this.cell && this.cell.getNode(this.rowIndex);
if(n){
try{
if(!this.grid.edit.isEditing()){
dojo.toggleClass(n, this.focusClass, true);
this.blurHeader();
dojox.grid.util.fire(n, "focus");
}
}
catch(e){}
}
},
_delayedHeaderFocus: function(){
if(this.isNavHeader()){
this.focusHeader();
this.grid.domNode.focus();
}
},
_initColumnHeaders: function(){
var headers = this._findHeaderCells();
for(var i = 0; i < headers.length; i++){
this._connects.push(dojo.connect(headers[i], "onfocus", this, "doColHeaderFocus"));
this._connects.push(dojo.connect(headers[i], "onblur", this, "doColHeaderBlur"));
}
},
_findHeaderCells: function(){
// This should be a one liner:
// dojo.query("th[tabindex=-1]", this.grid.viewsHeaderNode);
// But there is a bug in dojo.query() for IE -- see trac #7037.
var allHeads = dojo.query("th", this.grid.viewsHeaderNode);
var headers = [];
for (var i = 0; i < allHeads.length; i++){
var aHead = allHeads[i];
var hasTabIdx = dojo.hasAttr(aHead, "tabIndex");
var tabindex = dojo.attr(aHead, "tabIndex");
if (hasTabIdx && tabindex < 0) {
headers.push(aHead);
}
}
return headers;
},
_setActiveColHeader: function(/*Node*/colHeaderNode, /*Integer*/colFocusIdx, /*Integer*/ prevColFocusIdx){
//console.log("setActiveColHeader() - colHeaderNode:colFocusIdx:prevColFocusIdx = " + colHeaderNode + ":" + colFocusIdx + ":" + prevColFocusIdx);
dojo.attr(this.grid.domNode, "aria-activedescendant",colHeaderNode.id);
if (prevColFocusIdx != null && prevColFocusIdx >= 0 && prevColFocusIdx != colFocusIdx){
dojo.toggleClass(this._findHeaderCells()[prevColFocusIdx],this.focusClass,false);
}
dojo.toggleClass(colHeaderNode,this.focusClass, true);
this._colHeadNode = colHeaderNode;
this._colHeadFocusIdx = colFocusIdx;
this._scrollHeader(this._colHeadFocusIdx);
},
scrollIntoView: function(){
var info = (this.cell ? this._scrollInfo(this.cell) : null);
if(!info || !info.s){
return null;
}
var rt = this.grid.scroller.findScrollTop(this.rowIndex);
// place cell within horizontal view
if(info.n && info.sr){
if(info.n.offsetLeft + info.n.offsetWidth > info.sr.l + info.sr.w){
info.s.scrollLeft = info.n.offsetLeft + info.n.offsetWidth - info.sr.w;
}else if(info.n.offsetLeft < info.sr.l){
info.s.scrollLeft = info.n.offsetLeft;
}
}
// place cell within vertical view
if(info.r && info.sr){
if(rt + info.r.offsetHeight > info.sr.t + info.sr.h){
this.grid.setScrollTop(rt + info.r.offsetHeight - info.sr.h);
}else if(rt < info.sr.t){
this.grid.setScrollTop(rt);
}
}
return info.s.scrollLeft;
},
_scrollInfo: function(cell, domNode){
if(cell){
var cl = cell,
sbn = cl.view.scrollboxNode,
sbnr = {
w: sbn.clientWidth,
l: sbn.scrollLeft,
t: sbn.scrollTop,
h: sbn.clientHeight
},
rn = cl.view.getRowNode(this.rowIndex);
return {
c: cl,
s: sbn,
sr: sbnr,
n: (domNode ? domNode : cell.getNode(this.rowIndex)),
r: rn
};
}
return null;
},
_scrollHeader: function(currentIdx){
var info = null;
if(this._colHeadNode){
var cell = this.grid.getCell(currentIdx);
info = this._scrollInfo(cell, cell.getNode(0));
}
if(info && info.s && info.sr && info.n){
// scroll horizontally as needed.
var scroll = info.sr.l + info.sr.w;
if(info.n.offsetLeft + info.n.offsetWidth > scroll){
info.s.scrollLeft = info.n.offsetLeft + info.n.offsetWidth - info.sr.w;
}else if(info.n.offsetLeft < info.sr.l){
info.s.scrollLeft = info.n.offsetLeft;
}else if(dojo.isIE <= 7 && cell && cell.view.headerNode){
// Trac 7158: scroll dojoxGridHeader for IE7 and lower
cell.view.headerNode.scrollLeft = info.s.scrollLeft;
}
}
},
_isHeaderHidden: function(){
// summary:
// determine if the grid headers are hidden
// relies on documented technique of setting .dojoxGridHeader { display:none; }
// returns: Boolean
// true if headers are hidden
// false if headers are not hidden
var curView = this.focusView;
if (!curView){
// find one so we can determine if headers are hidden
// there is no focusView after adding items to empty grid (test_data_grid_empty.html)
for (var i = 0, cView; (cView = this.grid.views.views[i]); i++) {
if(cView.headerNode ){
curView=cView;
break;
}
}
}
return (curView && dojo.getComputedStyle(curView.headerNode).display == "none");
},
colSizeAdjust: function (e, colIdx, delta){ // adjust the column specified by colIdx by the specified delta px
var headers = this._findHeaderCells();
var view = this.focusView;
if (!view) {
for (var i = 0, cView; (cView = this.grid.views.views[i]); i++) {
// find first view with a tableMap in order to work with empty grid
if(cView.header.tableMap.map ){
view=cView;
break;
}
}
}
var curHeader = headers[colIdx];
if (!view || (colIdx == headers.length-1 && colIdx === 0)){
return; // can't adjust single col. grid
}
view.content.baseDecorateEvent(e);
// need to adjust event with header cell info since focus is no longer on header cell
e.cellNode = curHeader; //this.findCellTarget(e.target, e.rowNode);
e.cellIndex = view.content.getCellNodeIndex(e.cellNode);
e.cell = (e.cellIndex >= 0 ? this.grid.getCell(e.cellIndex) : null);
if (view.header.canResize(e)){
var deltaObj = {
l: delta
};
var drag = view.header.colResizeSetup(e,false);
view.header.doResizeColumn(drag, null, deltaObj);
view.update();
}
},
styleRow: function(inRow){
return;
},
setFocusIndex: function(inRowIndex, inCellIndex){
// summary:
// focuses the given grid cell
// inRowIndex: int
// grid row index
// inCellIndex: int
// grid cell index
this.setFocusCell(this.grid.getCell(inCellIndex), inRowIndex);
},
setFocusCell: function(inCell, inRowIndex){
// summary:
// focuses the given grid cell
// inCell: object
// grid cell object
// inRowIndex: int
// grid row index
if(inCell && !this.isFocusCell(inCell, inRowIndex)){
this.tabbingOut = false;
if (this._colHeadNode){
this.blurHeader();
}
this._colHeadNode = this._colHeadFocusIdx = null;
this.focusGridView();
this._focusifyCellNode(false);
this.cell = inCell;
this.rowIndex = inRowIndex;
this._focusifyCellNode(true);
}
// even if this cell isFocusCell, the document focus may need to be rejiggered
// call opera on delay to prevent keypress from altering focus
if(dojo.isOpera){
setTimeout(dojo.hitch(this.grid, 'onCellFocus', this.cell, this.rowIndex), 1);
}else{
this.grid.onCellFocus(this.cell, this.rowIndex);
}
},
next: function(){
// summary:
// focus next grid cell
if(this.cell){
var row=this.rowIndex, col=this.cell.index+1, cc=this.grid.layout.cellCount-1, rc=this.grid.rowCount-1;
if(col > cc){
col = 0;
row++;
}
if(row > rc){
col = cc;
row = rc;
}
if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells
var nextCell = this.grid.getCell(col);
if (!this.isLastFocusCell() && !nextCell.editable){
this.cell=nextCell;
this.rowIndex=row;
this.next();
return;
}
}
this.setFocusIndex(row, col);
}
},
previous: function(){
// summary:
// focus previous grid cell
if(this.cell){
var row=(this.rowIndex || 0), col=(this.cell.index || 0) - 1;
if(col < 0){
col = this.grid.layout.cellCount-1;
row--;
}
if(row < 0){
row = 0;
col = 0;
}
if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells
var prevCell = this.grid.getCell(col);
if (!this.isFirstFocusCell() && !prevCell.editable){
this.cell=prevCell;
this.rowIndex=row;
this.previous();
return;
}
}
this.setFocusIndex(row, col);
}
},
move: function(inRowDelta, inColDelta) {
// summary:
// focus grid cell or simulate focus to column header based on position relative to current focus
// inRowDelta: int
// vertical distance from current focus
// inColDelta: int
// horizontal distance from current focus
var colDir = inColDelta < 0 ? -1 : 1;
// Handle column headers.
if(this.isNavHeader()){
var headers = this._findHeaderCells();
var savedIdx = currentIdx = dojo.indexOf(headers, this._colHeadNode);
currentIdx += inColDelta;
while(currentIdx >=0 && currentIdx < headers.length && headers[currentIdx].style.display == "none"){
// skip over hidden column headers
currentIdx += colDir;
}
if((currentIdx >= 0) && (currentIdx < headers.length)){
this._setActiveColHeader(headers[currentIdx],currentIdx, savedIdx);
}
}else{
if(this.cell){
// Handle grid proper.
var sc = this.grid.scroller,
r = this.rowIndex,
rc = this.grid.rowCount-1,
row = Math.min(rc, Math.max(0, r+inRowDelta));
if(inRowDelta){
if(inRowDelta>0){
if(row > sc.getLastPageRow(sc.page)){
//need to load additional data, let scroller do that
this.grid.setScrollTop(this.grid.scrollTop+sc.findScrollTop(row)-sc.findScrollTop(r));
}
}else if(inRowDelta<0){
if(row <= sc.getPageRow(sc.page)){
//need to load additional data, let scroller do that
this.grid.setScrollTop(this.grid.scrollTop-sc.findScrollTop(r)-sc.findScrollTop(row));
}
}
}
var cc = this.grid.layout.cellCount-1,
i = this.cell.index,
col = Math.min(cc, Math.max(0, i+inColDelta));
var cell = this.grid.getCell(col);
while(col>=0 && col < cc && cell && cell.hidden === true){
// skip hidden cells
col += colDir;
cell = this.grid.getCell(col);
}
if (!cell || cell.hidden === true){
// don't change col if would move to hidden
col = i;
}
this.setFocusIndex(row, col);
if(inRowDelta){
this.grid.updateRow(r);
}
}
}
},
previousKey: function(e){
if(this.grid.edit.isEditing()){
dojo.stopEvent(e);
this.previous();
}else if(!this.isNavHeader() && !this._isHeaderHidden()) {
this.grid.domNode.focus(); // will call doFocus and set focus into header.
dojo.stopEvent(e);
}else{
this.tabOut(this.grid.domNode);
if (this._colHeadFocusIdx != null) { // clear grid header focus
dojo.toggleClass(this._findHeaderCells()[this._colHeadFocusIdx], this.focusClass, false);
this._colHeadFocusIdx = null;
}
this._focusifyCellNode(false);
}
},
nextKey: function(e) {
var isEmpty = (this.grid.rowCount === 0);
if(e.target === this.grid.domNode && this._colHeadFocusIdx == null){
this.focusHeader();
dojo.stopEvent(e);
}else if(this.isNavHeader()){
// if tabbing from col header, then go to grid proper.
this.blurHeader();
if(!this.findAndFocusGridCell()){
this.tabOut(this.grid.lastFocusNode);
}
this._colHeadNode = this._colHeadFocusIdx= null;
}else if(this.grid.edit.isEditing()){
dojo.stopEvent(e);
this.next();
}else{
this.tabOut(this.grid.lastFocusNode);
}
},
tabOut: function(inFocusNode){
this.tabbingOut = true;
inFocusNode.focus();
},
focusGridView: function(){
dojox.grid.util.fire(this.focusView, "focus");
},
focusGrid: function(inSkipFocusCell){
this.focusGridView();
this._focusifyCellNode(true);
},
findAndFocusGridCell: function(){
// summary:
// find the first focusable grid cell
// returns: Boolean
// true if focus was set to a cell
// false if no cell found to set focus onto
var didFocus = true;
var isEmpty = (this.grid.rowCount === 0); // If grid is empty this.grid.rowCount == 0
if (this.isNoFocusCell() && !isEmpty){
var cellIdx = 0;
var cell = this.grid.getCell(cellIdx);
if (cell.hidden) {
// if first cell isn't visible, use _colHeadFocusIdx
// could also use a while loop to find first visible cell - not sure that is worth it
cellIdx = this.isNavHeader() ? this._colHeadFocusIdx : 0;
}
this.setFocusIndex(0, cellIdx);
}
else if (this.cell && !isEmpty){
if (this.focusView && !this.focusView.rowNodes[this.rowIndex]){
// if rowNode for current index is undefined (likely as a result of a sort and because of #7304)
// scroll to that row
this.grid.scrollToRow(this.rowIndex);
}
this.focusGrid();
}else {
didFocus = false;
}
this._colHeadNode = this._colHeadFocusIdx= null;
return didFocus;
},
focusHeader: function(){
var headerNodes = this._findHeaderCells();
var saveColHeadFocusIdx = this._colHeadFocusIdx;
if (this._isHeaderHidden()){
// grid header is hidden, focus a cell
this.findAndFocusGridCell();
}
else if (!this._colHeadFocusIdx) {
if (this.isNoFocusCell()) {
this._colHeadFocusIdx = 0;
}
else {
this._colHeadFocusIdx = this.cell.index;
}
}
this._colHeadNode = headerNodes[this._colHeadFocusIdx];
while(this._colHeadNode && this._colHeadFocusIdx >=0 && this._colHeadFocusIdx < headerNodes.length &&
this._colHeadNode.style.display == "none"){
// skip over hidden column headers
this._colHeadFocusIdx++;
this._colHeadNode = headerNodes[this._colHeadFocusIdx];
}
if(this._colHeadNode && this._colHeadNode.style.display != "none"){
// Column header cells know longer receive actual focus. So, for keyboard invocation of
// contextMenu to work, the contextMenu must be bound to the grid.domNode rather than the viewsHeaderNode.
// unbind the contextmenu from the viewsHeaderNode and to the grid when header cells are active. Reset
// the binding back to the viewsHeaderNode when header cells are no longer acive (in blurHeader) #10483
if (this.headerMenu && this._contextMenuBindNode != this.grid.domNode){
this.headerMenu.unBindDomNode(this.grid.viewsHeaderNode);
this.headerMenu.bindDomNode(this.grid.domNode);
this._contextMenuBindNode = this.grid.domNode;
}
this._setActiveColHeader(this._colHeadNode, this._colHeadFocusIdx, saveColHeadFocusIdx);
this._scrollHeader(this._colHeadFocusIdx);
this._focusifyCellNode(false);
}else {
// all col head nodes are hidden - focus the grid
this.findAndFocusGridCell();
}
},
blurHeader: function(){
dojo.removeClass(this._colHeadNode, this.focusClass);
dojo.removeAttr(this.grid.domNode,"aria-activedescendant");
// reset contextMenu onto viewsHeaderNode so right mouse on header will invoke (see focusHeader)
if (this.headerMenu && this._contextMenuBindNode == this.grid.domNode) {
var viewsHeader = this.grid.viewsHeaderNode;
this.headerMenu.unBindDomNode(this.grid.domNode);
this.headerMenu.bindDomNode(viewsHeader);
this._contextMenuBindNode = viewsHeader;
}
},
doFocus: function(e){
// trap focus only for grid dom node
if(e && e.target != e.currentTarget){
dojo.stopEvent(e);
return;
}
// do not focus for scrolling if grid is about to blur
if(!this.tabbingOut){
this.focusHeader();
}
this.tabbingOut = false;
dojo.stopEvent(e);
},
doBlur: function(e){
dojo.stopEvent(e); // FF2
},
doContextMenu: function(e){
//stop contextMenu event if no header Menu to prevent default/browser contextMenu
if (!this.headerMenu){
dojo.stopEvent(e);
}
},
doLastNodeFocus: function(e){
if (this.tabbingOut){
this._focusifyCellNode(false);
}else if(this.grid.rowCount >0){
if (this.isNoFocusCell()){
this.setFocusIndex(0,0);
}
this._focusifyCellNode(true);
}else {
this.focusHeader();
}
this.tabbingOut = false;
dojo.stopEvent(e); // FF2
},
doLastNodeBlur: function(e){
dojo.stopEvent(e); // FF2
},
doColHeaderFocus: function(e){
this._setActiveColHeader(e.target,dojo.attr(e.target, "idx"),this._colHeadFocusIdx);
this._scrollHeader(this.getHeaderIndex());
dojo.stopEvent(e);
},
doColHeaderBlur: function(e){
dojo.toggleClass(e.target, this.focusClass, false);
}
});
}
if(!dojo._hasResource["dojox.grid._EditManager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._EditManager"] = true;
dojo.provide("dojox.grid._EditManager");
dojo.declare("dojox.grid._EditManager", null, {
// summary:
// Controls grid cell editing process. Owned by grid and used internally for editing.
constructor: function(inGrid){
// inGrid: dojox.Grid
// The dojox.Grid this editor should be attached to
this.grid = inGrid;
this.connections = [];
if(dojo.isIE){
this.connections.push(dojo.connect(document.body, "onfocus", dojo.hitch(this, "_boomerangFocus")));
}
},
info: {},
destroy: function(){
dojo.forEach(this.connections,dojo.disconnect);
},
cellFocus: function(inCell, inRowIndex){
// summary:
// Invoke editing when cell is focused
// inCell: cell object
// Grid cell object
// inRowIndex: Integer
// Grid row index
if(this.grid.singleClickEdit || this.isEditRow(inRowIndex)){
// if same row or quick editing, edit
this.setEditCell(inCell, inRowIndex);
}else{
// otherwise, apply any pending row edits
this.apply();
}
// if dynamic or static editing...
if(this.isEditing() || (inCell && inCell.editable && inCell.alwaysEditing)){
// let the editor focus itself as needed
this._focusEditor(inCell, inRowIndex);
}
},
rowClick: function(e){
if(this.isEditing() && !this.isEditRow(e.rowIndex)){
this.apply();
}
},
styleRow: function(inRow){
if(inRow.index == this.info.rowIndex){
inRow.customClasses += ' dojoxGridRowEditing';
}
},
dispatchEvent: function(e){
var c = e.cell, ed = (c && c["editable"]) ? c : 0;
return ed && ed.dispatchEvent(e.dispatch, e);
},
// Editing
isEditing: function(){
// summary:
// Indicates editing state of the grid.
// returns: Boolean
// True if grid is actively editing
return this.info.rowIndex !== undefined;
},
isEditCell: function(inRowIndex, inCellIndex){
// summary:
// Indicates if the given cell is being edited.
// inRowIndex: Integer
// Grid row index
// inCellIndex: Integer
// Grid cell index
// returns: Boolean
// True if given cell is being edited
return (this.info.rowIndex === inRowIndex) && (this.info.cell.index == inCellIndex);
},
isEditRow: function(inRowIndex){
// summary:
// Indicates if the given row is being edited.
// inRowIndex: Integer
// Grid row index
// returns: Boolean
// True if given row is being edited
return this.info.rowIndex === inRowIndex;
},
setEditCell: function(inCell, inRowIndex){
// summary:
// Set the given cell to be edited
// inRowIndex: Integer
// Grid row index
// inCell: Object
// Grid cell object
if(!this.isEditCell(inRowIndex, inCell.index) && this.grid.canEdit && this.grid.canEdit(inCell, inRowIndex)){
this.start(inCell, inRowIndex, this.isEditRow(inRowIndex) || inCell.editable);
}
},
_focusEditor: function(inCell, inRowIndex){
dojox.grid.util.fire(inCell, "focus", [inRowIndex]);
},
focusEditor: function(){
if(this.isEditing()){
this._focusEditor(this.info.cell, this.info.rowIndex);
}
},
// implement fix for focus boomerang effect on IE
_boomerangWindow: 500,
_shouldCatchBoomerang: function(){
return this._catchBoomerang > new Date().getTime();
},
_boomerangFocus: function(){
//console.log("_boomerangFocus");
if(this._shouldCatchBoomerang()){
// make sure we don't utterly lose focus
this.grid.focus.focusGrid();
// let the editor focus itself as needed
this.focusEditor();
// only catch once
this._catchBoomerang = 0;
}
},
_doCatchBoomerang: function(){
// give ourselves a few ms to boomerang IE focus effects
if(dojo.isIE){this._catchBoomerang = new Date().getTime() + this._boomerangWindow;}
},
// end boomerang fix API
start: function(inCell, inRowIndex, inEditing){
this.grid.beginUpdate();
this.editorApply();
if(this.isEditing() && !this.isEditRow(inRowIndex)){
this.applyRowEdit();
this.grid.updateRow(inRowIndex);
}
if(inEditing){
this.info = { cell: inCell, rowIndex: inRowIndex };
this.grid.doStartEdit(inCell, inRowIndex);
this.grid.updateRow(inRowIndex);
}else{
this.info = {};
}
this.grid.endUpdate();
// make sure we don't utterly lose focus
this.grid.focus.focusGrid();
// let the editor focus itself as needed
this._focusEditor(inCell, inRowIndex);
// give ourselves a few ms to boomerang IE focus effects
this._doCatchBoomerang();
},
_editorDo: function(inMethod){
var c = this.info.cell;
//c && c.editor && c.editor[inMethod](c, this.info.rowIndex);
if(c && c.editable){
c[inMethod](this.info.rowIndex);
}
},
editorApply: function(){
this._editorDo("apply");
},
editorCancel: function(){
this._editorDo("cancel");
},
applyCellEdit: function(inValue, inCell, inRowIndex){
if(this.grid.canEdit(inCell, inRowIndex)){
this.grid.doApplyCellEdit(inValue, inRowIndex, inCell.field);
}
},
applyRowEdit: function(){
this.grid.doApplyEdit(this.info.rowIndex, this.info.cell.field);
},
apply: function(){
// summary:
// Apply a grid edit
if(this.isEditing()){
this.grid.beginUpdate();
this.editorApply();
this.applyRowEdit();
this.info = {};
this.grid.endUpdate();
this.grid.focus.focusGrid();
this._doCatchBoomerang();
}
},
cancel: function(){
// summary:
// Cancel a grid edit
if(this.isEditing()){
this.grid.beginUpdate();
this.editorCancel();
this.info = {};
this.grid.endUpdate();
this.grid.focus.focusGrid();
this._doCatchBoomerang();
}
},
save: function(inRowIndex, inView){
// summary:
// Save the grid editing state
// inRowIndex: Integer
// Grid row index
// inView: Object
// Grid view
var c = this.info.cell;
if(this.isEditRow(inRowIndex) && (!inView || c.view==inView) && c.editable){
c.save(c, this.info.rowIndex);
}
},
restore: function(inView, inRowIndex){
// summary:
// Restores the grid editing state
// inRowIndex: Integer
// Grid row index
// inView: Object
// Grid view
var c = this.info.cell;
if(this.isEditRow(inRowIndex) && c.view == inView && c.editable){
c.restore(c, this.info.rowIndex);
}
}
});
}
if(!dojo._hasResource['dojox.grid.Selection']){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource['dojox.grid.Selection'] = true;
dojo.provide('dojox.grid.Selection');
dojo.declare("dojox.grid.Selection", null, {
// summary:
// Manages row selection for grid. Owned by grid and used internally
// for selection. Override to implement custom selection.
constructor: function(inGrid){
this.grid = inGrid;
this.selected = [];
this.setMode(inGrid.selectionMode);
},
mode: 'extended',
selected: null,
updating: 0,
selectedIndex: -1,
setMode: function(mode){
if(this.selected.length){
this.deselectAll();
}
if(mode != 'extended' && mode != 'multiple' && mode != 'single' && mode != 'none'){
this.mode = 'extended';
}else{
this.mode = mode;
}
},
onCanSelect: function(inIndex){
return this.grid.onCanSelect(inIndex);
},
onCanDeselect: function(inIndex){
return this.grid.onCanDeselect(inIndex);
},
onSelected: function(inIndex){
},
onDeselected: function(inIndex){
},
//onSetSelected: function(inIndex, inSelect) { };
onChanging: function(){
},
onChanged: function(){
},
isSelected: function(inIndex){
if(this.mode == 'none'){
return false;
}
return this.selected[inIndex];
},
getFirstSelected: function(){
if(!this.selected.length||this.mode == 'none'){ return -1; }
for(var i=0, l=this.selected.length; i<l; i++){
if(this.selected[i]){
return i;
}
}
return -1;
},
getNextSelected: function(inPrev){
if(this.mode == 'none'){ return -1; }
for(var i=inPrev+1, l=this.selected.length; i<l; i++){
if(this.selected[i]){
return i;
}
}
return -1;
},
getSelected: function(){
var result = [];
for(var i=0, l=this.selected.length; i<l; i++){
if(this.selected[i]){
result.push(i);
}
}
return result;
},
getSelectedCount: function(){
var c = 0;
for(var i=0; i<this.selected.length; i++){
if(this.selected[i]){
c++;
}
}
return c;
},
_beginUpdate: function(){
if(this.updating === 0){
this.onChanging();
}
this.updating++;
},
_endUpdate: function(){
this.updating--;
if(this.updating === 0){
this.onChanged();
}
},
select: function(inIndex){
if(this.mode == 'none'){ return; }
if(this.mode != 'multiple'){
this.deselectAll(inIndex);
this.addToSelection(inIndex);
}else{
this.toggleSelect(inIndex);
}
},
addToSelection: function(inIndex){
if(this.mode == 'none'){ return; }
if(dojo.isArray(inIndex)){
dojo.forEach(inIndex, this.addToSelection, this);
return;
}
inIndex = Number(inIndex);
if(this.selected[inIndex]){
this.selectedIndex = inIndex;
}else{
if(this.onCanSelect(inIndex) !== false){
this.selectedIndex = inIndex;
var rowNode = this.grid.getRowNode(inIndex);
if(rowNode){
dojo.attr(rowNode,"aria-selected","true");
}
this._beginUpdate();
this.selected[inIndex] = true;
//this.grid.onSelected(inIndex);
this.onSelected(inIndex);
//this.onSetSelected(inIndex, true);
this._endUpdate();
}
}
},
deselect: function(inIndex){
if(this.mode == 'none'){ return; }
if(dojo.isArray(inIndex)){
dojo.forEach(inIndex, this.deselect, this);
return;
}
inIndex = Number(inIndex);
if(this.selectedIndex == inIndex){
this.selectedIndex = -1;
}
if(this.selected[inIndex]){
if(this.onCanDeselect(inIndex) === false){
return;
}
var rowNode = this.grid.getRowNode(inIndex);
if(rowNode){
dojo.attr(rowNode,"aria-selected","false");
}
this._beginUpdate();
delete this.selected[inIndex];
//this.grid.onDeselected(inIndex);
this.onDeselected(inIndex);
//this.onSetSelected(inIndex, false);
this._endUpdate();
}
},
setSelected: function(inIndex, inSelect){
this[(inSelect ? 'addToSelection' : 'deselect')](inIndex);
},
toggleSelect: function(inIndex){
if(dojo.isArray(inIndex)){
dojo.forEach(inIndex, this.toggleSelect, this);
return;
}
this.setSelected(inIndex, !this.selected[inIndex]);
},
_range: function(inFrom, inTo, func){
var s = (inFrom >= 0 ? inFrom : inTo), e = inTo;
if(s > e){
e = s;
s = inTo;
}
for(var i=s; i<=e; i++){
func(i);
}
},
selectRange: function(inFrom, inTo){
this._range(inFrom, inTo, dojo.hitch(this, "addToSelection"));
},
deselectRange: function(inFrom, inTo){
this._range(inFrom, inTo, dojo.hitch(this, "deselect"));
},
insert: function(inIndex){
this.selected.splice(inIndex, 0, false);
if(this.selectedIndex >= inIndex){
this.selectedIndex++;
}
},
remove: function(inIndex){
this.selected.splice(inIndex, 1);
if(this.selectedIndex >= inIndex){
this.selectedIndex--;
}
},
deselectAll: function(inExcept){
for(var i in this.selected){
if((i!=inExcept)&&(this.selected[i]===true)){
this.deselect(i);
}
}
},
clickSelect: function(inIndex, inCtrlKey, inShiftKey){
if(this.mode == 'none'){ return; }
this._beginUpdate();
if(this.mode != 'extended'){
this.select(inIndex);
}else{
var lastSelected = this.selectedIndex;
if(!inCtrlKey){
this.deselectAll(inIndex);
}
if(inShiftKey){
this.selectRange(lastSelected, inIndex);
}else if(inCtrlKey){
this.toggleSelect(inIndex);
}else{
this.addToSelection(inIndex);
}
}
this._endUpdate();
},
clickSelectEvent: function(e){
this.clickSelect(e.rowIndex, dojo.isCopyKey(e), e.shiftKey);
},
clear: function(){
this._beginUpdate();
this.deselectAll();
this._endUpdate();
}
});
}
if(!dojo._hasResource["dojox.grid._Events"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._Events"] = true;
dojo.provide("dojox.grid._Events");
dojo.declare("dojox.grid._Events", null, {
// summary:
// _Grid mixin that provides default implementations for grid events.
// description:
// Default synthetic events dispatched for _Grid. dojo.connect to events to
// retain default implementation or override them for custom handling.
// cellOverClass: String
// css class to apply to grid cells over which the cursor is placed.
cellOverClass: "dojoxGridCellOver",
onKeyEvent: function(e){
// summary: top level handler for Key Events
this.dispatchKeyEvent(e);
},
onContentEvent: function(e){
// summary: Top level handler for Content events
this.dispatchContentEvent(e);
},
onHeaderEvent: function(e){
// summary: Top level handler for header events
this.dispatchHeaderEvent(e);
},
onStyleRow: function(inRow){
// summary:
// Perform row styling on a given row. Called whenever row styling is updated.
//
// inRow: Object
// Object containing row state information: selected, true if the row is selcted; over:
// true of the mouse is over the row; odd: true if the row is odd. Use customClasses and
// customStyles to control row css classes and styles; both properties are strings.
//
// example: onStyleRow({ selected: true, over:true, odd:false })
var i = inRow;
i.customClasses += (i.odd?" dojoxGridRowOdd":"") + (i.selected?" dojoxGridRowSelected":"") + (i.over?" dojoxGridRowOver":"");
this.focus.styleRow(inRow);
this.edit.styleRow(inRow);
},
onKeyDown: function(e){
// summary:
// Grid key event handler. By default enter begins editing and applies edits, escape cancels an edit,
// tab, shift-tab, and arrow keys move grid cell focus.
if(e.altKey || e.metaKey){
return;
}
var dk = dojo.keys;
var colIdx;
switch(e.keyCode){
case dk.ESCAPE:
this.edit.cancel();
break;
case dk.ENTER:
if(!this.edit.isEditing()){
colIdx = this.focus.getHeaderIndex();
if(colIdx >= 0) {
this.setSortIndex(colIdx);
break;
}else {
this.selection.clickSelect(this.focus.rowIndex, dojo.isCopyKey(e), e.shiftKey);
}
dojo.stopEvent(e);
}
if(!e.shiftKey){
var isEditing = this.edit.isEditing();
this.edit.apply();
if(!isEditing){
this.edit.setEditCell(this.focus.cell, this.focus.rowIndex);
}
}
if (!this.edit.isEditing()){
var curView = this.focus.focusView || this.views.views[0]; //if no focusView than only one view
curView.content.decorateEvent(e);
this.onRowClick(e);
}
break;
case dk.SPACE:
if(!this.edit.isEditing()){
colIdx = this.focus.getHeaderIndex();
if(colIdx >= 0) {
this.setSortIndex(colIdx);
break;
}else {
this.selection.clickSelect(this.focus.rowIndex, dojo.isCopyKey(e), e.shiftKey);
}
dojo.stopEvent(e);
}
break;
case dk.TAB:
this.focus[e.shiftKey ? 'previousKey' : 'nextKey'](e);
break;
case dk.LEFT_ARROW:
case dk.RIGHT_ARROW:
if(!this.edit.isEditing()){
var keyCode = e.keyCode; // IE seems to lose after stopEvent when modifier keys
dojo.stopEvent(e);
colIdx = this.focus.getHeaderIndex();
if (colIdx >= 0 && (e.shiftKey && e.ctrlKey)){
this.focus.colSizeAdjust(e, colIdx, (keyCode == dk.LEFT_ARROW ? -1 : 1)*5);
}
else{
var offset = (keyCode == dk.LEFT_ARROW) ? 1 : -1;
if(dojo._isBodyLtr()){ offset *= -1; }
this.focus.move(0, offset);
}
}
break;
case dk.UP_ARROW:
if(!this.edit.isEditing() && this.focus.rowIndex !== 0){
dojo.stopEvent(e);
this.focus.move(-1, 0);
}
break;
case dk.DOWN_ARROW:
if(!this.edit.isEditing() && this.focus.rowIndex+1 != this.rowCount){
dojo.stopEvent(e);
this.focus.move(1, 0);
}
break;
case dk.PAGE_UP:
if(!this.edit.isEditing() && this.focus.rowIndex !== 0){
dojo.stopEvent(e);
if(this.focus.rowIndex != this.scroller.firstVisibleRow+1){
this.focus.move(this.scroller.firstVisibleRow-this.focus.rowIndex, 0);
}else{
this.setScrollTop(this.scroller.findScrollTop(this.focus.rowIndex-1));
this.focus.move(this.scroller.firstVisibleRow-this.scroller.lastVisibleRow+1, 0);
}
}
break;
case dk.PAGE_DOWN:
if(!this.edit.isEditing() && this.focus.rowIndex+1 != this.rowCount){
dojo.stopEvent(e);
if(this.focus.rowIndex != this.scroller.lastVisibleRow-1){
this.focus.move(this.scroller.lastVisibleRow-this.focus.rowIndex-1, 0);
}else{
this.setScrollTop(this.scroller.findScrollTop(this.focus.rowIndex+1));
this.focus.move(this.scroller.lastVisibleRow-this.scroller.firstVisibleRow-1, 0);
}
}
break;
default:
break;
}
},
onMouseOver: function(e){
// summary:
// Event fired when mouse is over the grid.
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
e.rowIndex == -1 ? this.onHeaderCellMouseOver(e) : this.onCellMouseOver(e);
},
onMouseOut: function(e){
// summary:
// Event fired when mouse moves out of the grid.
// e: Event
// Decorated event object that contains reference to grid, cell, and rowIndex
e.rowIndex == -1 ? this.onHeaderCellMouseOut(e) : this.onCellMouseOut(e);
},
onMouseDown: function(e){
// summary:
// Event fired when mouse is down inside grid.
// e: Event
// Decorated event object that contains reference to grid, cell, and rowIndex
e.rowIndex == -1 ? this.onHeaderCellMouseDown(e) : this.onCellMouseDown(e);
},
onMouseOverRow: function(e){
// summary:
// Event fired when mouse is over any row (data or header).
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
if(!this.rows.isOver(e.rowIndex)){
this.rows.setOverRow(e.rowIndex);
e.rowIndex == -1 ? this.onHeaderMouseOver(e) : this.onRowMouseOver(e);
}
},
onMouseOutRow: function(e){
// summary:
// Event fired when mouse moves out of any row (data or header).
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
if(this.rows.isOver(-1)){
this.onHeaderMouseOut(e);
}else if(!this.rows.isOver(-2)){
this.rows.setOverRow(-2);
this.onRowMouseOut(e);
}
},
onMouseDownRow: function(e){
// summary:
// Event fired when mouse is down inside grid row
// e: Event
// Decorated event object that contains reference to grid, cell, and rowIndex
if(e.rowIndex != -1)
this.onRowMouseDown(e);
},
// cell events
onCellMouseOver: function(e){
// summary:
// Event fired when mouse is over a cell.
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
if(e.cellNode){
dojo.addClass(e.cellNode, this.cellOverClass);
}
},
onCellMouseOut: function(e){
// summary:
// Event fired when mouse moves out of a cell.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
if(e.cellNode){
dojo.removeClass(e.cellNode, this.cellOverClass);
}
},
onCellMouseDown: function(e){
// summary:
// Event fired when mouse is down in a header cell.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onCellClick: function(e){
// summary:
// Event fired when a cell is clicked.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
this._click[0] = this._click[1];
this._click[1] = e;
if(!this.edit.isEditCell(e.rowIndex, e.cellIndex)){
this.focus.setFocusCell(e.cell, e.rowIndex);
}
this.onRowClick(e);
},
onCellDblClick: function(e){
// summary:
// Event fired when a cell is double-clicked.
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
if(this._click.length > 1 && dojo.isIE){
this.edit.setEditCell(this._click[1].cell, this._click[1].rowIndex);
}else if(this._click.length > 1 && this._click[0].rowIndex != this._click[1].rowIndex){
this.edit.setEditCell(this._click[0].cell, this._click[0].rowIndex);
}else{
this.edit.setEditCell(e.cell, e.rowIndex);
}
this.onRowDblClick(e);
},
onCellContextMenu: function(e){
// summary:
// Event fired when a cell context menu is accessed via mouse right click.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
this.onRowContextMenu(e);
},
onCellFocus: function(inCell, inRowIndex){
// summary:
// Event fired when a cell receives focus.
// inCell: Object
// Cell object containing properties of the grid column.
// inRowIndex: Integer
// Index of the grid row
this.edit.cellFocus(inCell, inRowIndex);
},
// row events
onRowClick: function(e){
// summary:
// Event fired when a row is clicked.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
this.edit.rowClick(e);
this.selection.clickSelectEvent(e);
},
onRowDblClick: function(e){
// summary:
// Event fired when a row is double clicked.
// e: Event
// decorated event object which contains reference to grid, cell, and rowIndex
},
onRowMouseOver: function(e){
// summary:
// Event fired when mouse moves over a data row.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onRowMouseOut: function(e){
// summary:
// Event fired when mouse moves out of a data row.
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
},
onRowMouseDown: function(e){
// summary:
// Event fired when mouse is down in a row.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onRowContextMenu: function(e){
// summary:
// Event fired when a row context menu is accessed via mouse right click.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
dojo.stopEvent(e);
},
// header events
onHeaderMouseOver: function(e){
// summary:
// Event fired when mouse moves over the grid header.
// e: Event
// Decorated event object contains reference to grid, cell, and rowIndex
},
onHeaderMouseOut: function(e){
// summary:
// Event fired when mouse moves out of the grid header.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onHeaderCellMouseOver: function(e){
// summary:
// Event fired when mouse moves over a header cell.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
if(e.cellNode){
dojo.addClass(e.cellNode, this.cellOverClass);
}
},
onHeaderCellMouseOut: function(e){
// summary:
// Event fired when mouse moves out of a header cell.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
if(e.cellNode){
dojo.removeClass(e.cellNode, this.cellOverClass);
}
},
onHeaderCellMouseDown: function(e) {
// summary:
// Event fired when mouse is down in a header cell.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onHeaderClick: function(e){
// summary:
// Event fired when the grid header is clicked.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onHeaderCellClick: function(e){
// summary:
// Event fired when a header cell is clicked.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
this.setSortIndex(e.cell.index);
this.onHeaderClick(e);
},
onHeaderDblClick: function(e){
// summary:
// Event fired when the grid header is double clicked.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
},
onHeaderCellDblClick: function(e){
// summary:
// Event fired when a header cell is double clicked.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
this.onHeaderDblClick(e);
},
onHeaderCellContextMenu: function(e){
// summary:
// Event fired when a header cell context menu is accessed via mouse right click.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
this.onHeaderContextMenu(e);
},
onHeaderContextMenu: function(e){
// summary:
// Event fired when the grid header context menu is accessed via mouse right click.
// e: Event
// Decorated event object which contains reference to grid, cell, and rowIndex
if(!this.headerMenu){
dojo.stopEvent(e);
}
},
// editing
onStartEdit: function(inCell, inRowIndex){
// summary:
// Event fired when editing is started for a given grid cell
// inCell: Object
// Cell object containing properties of the grid column.
// inRowIndex: Integer
// Index of the grid row
},
onApplyCellEdit: function(inValue, inRowIndex, inFieldIndex){
// summary:
// Event fired when editing is applied for a given grid cell
// inValue: String
// Value from cell editor
// inRowIndex: Integer
// Index of the grid row
// inFieldIndex: Integer
// Index in the grid's data store
},
onCancelEdit: function(inRowIndex){
// summary:
// Event fired when editing is cancelled for a given grid cell
// inRowIndex: Integer
// Index of the grid row
},
onApplyEdit: function(inRowIndex){
// summary:
// Event fired when editing is applied for a given grid row
// inRowIndex: Integer
// Index of the grid row
},
onCanSelect: function(inRowIndex){
// summary:
// Event to determine if a grid row may be selected
// inRowIndex: Integer
// Index of the grid row
// returns: Boolean
// true if the row can be selected
return true;
},
onCanDeselect: function(inRowIndex){
// summary:
// Event to determine if a grid row may be deselected
// inRowIndex: Integer
// Index of the grid row
// returns: Boolean
// true if the row can be deselected
return true;
},
onSelected: function(inRowIndex){
// summary:
// Event fired when a grid row is selected
// inRowIndex: Integer
// Index of the grid row
this.updateRowStyles(inRowIndex);
},
onDeselected: function(inRowIndex){
// summary:
// Event fired when a grid row is deselected
// inRowIndex: Integer
// Index of the grid row
this.updateRowStyles(inRowIndex);
},
onSelectionChanged: function(){
}
});
}
if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.i18n"] = true;
dojo.provide("dojo.i18n");
/*=====
dojo.i18n = {
// summary: Utility classes to enable loading of resources for internationalization (i18n)
};
=====*/
dojo.i18n.getLocalization = function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){
// summary:
// Returns an Object containing the localization for a given resource
// bundle in a package, matching the specified locale.
// description:
// Returns a hash containing name/value pairs in its prototypesuch
// that values can be easily overridden. Throws an exception if the
// bundle is not found. Bundle must have already been loaded by
// `dojo.requireLocalization()` or by a build optimization step. NOTE:
// try not to call this method as part of an object property
// definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In
// some loading situations, the bundle may not be available in time
// for the object definition. Instead, call this method inside a
// function that is run after all modules load or the page loads (like
// in `dojo.addOnLoad()`), or in a widget lifecycle method.
// packageName:
// package which is associated with this resource
// bundleName:
// the base filename of the resource bundle (without the ".js" suffix)
// locale:
// the variant to load (optional). By default, the locale defined by
// the host environment: dojo.locale
locale = dojo.i18n.normalizeLocale(locale);
// look for nearest locale match
var elements = locale.split('-');
var module = [packageName,"nls",bundleName].join('.');
var bundle = dojo._loadedModules[module];
if(bundle){
var localization;
for(var i = elements.length; i > 0; i--){
var loc = elements.slice(0, i).join('_');
if(bundle[loc]){
localization = bundle[loc];
break;
}
}
if(!localization){
localization = bundle.ROOT;
}
// make a singleton prototype so that the caller won't accidentally change the values globally
if(localization){
var clazz = function(){};
clazz.prototype = localization;
return new clazz(); // Object
}
}
throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale);
};
dojo.i18n.normalizeLocale = function(/*String?*/locale){
// summary:
// Returns canonical form of locale, as used by Dojo.
//
// description:
// All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt).
// If no locale is specified, the dojo.locale is returned. dojo.locale is defined by
// the user agent's locale unless overridden by djConfig.
var result = locale ? locale.toLowerCase() : dojo.locale;
if(result == "root"){
result = "ROOT";
}
return result; // String
};
dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){
// summary:
// See dojo.requireLocalization()
// description:
// Called by the bootstrap, but factored out so that it is only
// included in the build when needed.
var targetLocale = dojo.i18n.normalizeLocale(locale);
var bundlePackage = [moduleName, "nls", bundleName].join(".");
// NOTE:
// When loading these resources, the packaging does not match what is
// on disk. This is an implementation detail, as this is just a
// private data structure to hold the loaded resources. e.g.
// `tests/hello/nls/en-us/salutations.js` is loaded as the object
// `tests.hello.nls.salutations.en_us={...}` The structure on disk is
// intended to be most convenient for developers and translators, but
// in memory it is more logical and efficient to store in a different
// order. Locales cannot use dashes, since the resulting path will
// not evaluate as valid JS, so we translate them to underscores.
//Find the best-match locale to load if we have available flat locales.
var bestLocale = "";
if(availableFlatLocales){
var flatLocales = availableFlatLocales.split(",");
for(var i = 0; i < flatLocales.length; i++){
//Locale must match from start of string.
//Using ["indexOf"] so customBase builds do not see
//this as a dojo._base.array dependency.
if(targetLocale["indexOf"](flatLocales[i]) == 0){
if(flatLocales[i].length > bestLocale.length){
bestLocale = flatLocales[i];
}
}
}
if(!bestLocale){
bestLocale = "ROOT";
}
}
//See if the desired locale is already loaded.
var tempLocale = availableFlatLocales ? bestLocale : targetLocale;
var bundle = dojo._loadedModules[bundlePackage];
var localizedBundle = null;
if(bundle){
if(dojo.config.localizationComplete && bundle._built){return;}
var jsLoc = tempLocale.replace(/-/g, '_');
var translationPackage = bundlePackage+"."+jsLoc;
localizedBundle = dojo._loadedModules[translationPackage];
}
if(!localizedBundle){
bundle = dojo["provide"](bundlePackage);
var syms = dojo._getModuleSymbols(moduleName);
var modpath = syms.concat("nls").join("/");
var parent;
dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){
var jsLoc = loc.replace(/-/g, '_');
var translationPackage = bundlePackage + "." + jsLoc;
var loaded = false;
if(!dojo._loadedModules[translationPackage]){
// Mark loaded whether it's found or not, so that further load attempts will not be made
dojo["provide"](translationPackage);
var module = [modpath];
if(loc != "ROOT"){module.push(loc);}
module.push(bundleName);
var filespec = module.join("/") + '.js';
loaded = dojo._loadPath(filespec, null, function(hash){
// Use singleton with prototype to point to parent bundle, then mix-in result from loadPath
var clazz = function(){};
clazz.prototype = parent;
bundle[jsLoc] = new clazz();
for(var j in hash){ bundle[jsLoc][j] = hash[j]; }
});
}else{
loaded = true;
}
if(loaded && bundle[jsLoc]){
parent = bundle[jsLoc];
}else{
bundle[jsLoc] = parent;
}
if(availableFlatLocales){
//Stop the locale path searching if we know the availableFlatLocales, since
//the first call to this function will load the only bundle that is needed.
return true;
}
});
}
//Save the best locale bundle as the target locale bundle when we know the
//the available bundles.
if(availableFlatLocales && targetLocale != bestLocale){
bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')];
}
};
(function(){
// If other locales are used, dojo.requireLocalization should load them as
// well, by default.
//
// Override dojo.requireLocalization to do load the default bundle, then
// iterate through the extraLocale list and load those translations as
// well, unless a particular locale was requested.
var extra = dojo.config.extraLocale;
if(extra){
if(!extra instanceof Array){
extra = [extra];
}
var req = dojo.i18n._requireLocalization;
dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){
req(m,b,locale, availableFlatLocales);
if(locale){return;}
for(var i=0; i<extra.length; i++){
req(m,b,extra[i], availableFlatLocales);
}
};
}
})();
dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){
// summary:
// A helper method to assist in searching for locale-based resources.
// Will iterate through the variants of a particular locale, either up
// or down, executing a callback function. For example, "en-us" and
// true will try "en-us" followed by "en" and finally "ROOT".
locale = dojo.i18n.normalizeLocale(locale);
var elements = locale.split('-');
var searchlist = [];
for(var i = elements.length; i > 0; i--){
searchlist.push(elements.slice(0, i).join('-'));
}
searchlist.push(false);
if(down){searchlist.reverse();}
for(var j = searchlist.length - 1; j >= 0; j--){
var loc = searchlist[j] || "ROOT";
var stop = searchFunc(loc);
if(stop){ break; }
}
};
dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){
// summary:
// Load built, flattened resource bundles, if available for all
// locales used in the page. Only called by built layer files.
function preload(locale){
locale = dojo.i18n.normalizeLocale(locale);
dojo.i18n._searchLocalePath(locale, true, function(loc){
for(var i=0; i<localesGenerated.length;i++){
if(localesGenerated[i] == loc){
dojo["require"](bundlePrefix+"_"+loc);
return true; // Boolean
}
}
return false; // Boolean
});
}
preload();
var extra = dojo.config.extraLocale||[];
for(var i=0; i<extra.length; i++){
preload(extra[i]);
}
};
}
if(!dojo._hasResource["dojox.grid._Grid"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid._Grid"] = true;
dojo.provide("dojox.grid._Grid");
(function(){
// NOTE: this is for backwards compatibility with Dojo 1.3
if(!dojo.isCopyKey){
dojo.isCopyKey = dojo.dnd.getCopyKeyState;
}
/*=====
dojox.grid.__CellDef = function(){
// name: String?
// The text to use in the header of the grid for this cell.
// get: Function?
// function(rowIndex){} rowIndex is of type Integer. This
// function will be called when a cell requests data. Returns the
// unformatted data for the cell.
// value: String?
// If "get" is not specified, this is used as the data for the cell.
// defaultValue: String?
// If "get" and "value" aren't specified or if "get" returns an undefined
// value, this is used as the data for the cell. "formatter" is not run
// on this if "get" returns an undefined value.
// formatter: Function?
// function(data, rowIndex){} data is of type anything, rowIndex
// is of type Integer. This function will be called after the cell
// has its data but before it passes it back to the grid to render.
// Returns the formatted version of the cell's data.
// type: dojox.grid.cells._Base|Function?
// TODO
// editable: Boolean?
// Whether this cell should be editable or not.
// hidden: Boolean?
// If true, the cell will not be displayed.
// noresize: Boolean?
// If true, the cell will not be able to be resized.
// width: Integer|String?
// A CSS size. If it's an Integer, the width will be in em's.
// colSpan: Integer?
// How many columns to span this cell. Will not work in the first
// sub-row of cells.
// rowSpan: Integer?
// How many sub-rows to span this cell.
// styles: String?
// A string of styles to apply to both the header cell and main
// grid cells. Must end in a ';'.
// headerStyles: String?
// A string of styles to apply to just the header cell. Must end
// in a ';'
// cellStyles: String?
// A string of styles to apply to just the main grid cells. Must
// end in a ';'
// classes: String?
// A space separated list of classes to apply to both the header
// cell and the main grid cells.
// headerClasses: String?
// A space separated list of classes to apply to just the header
// cell.
// cellClasses: String?
// A space separated list of classes to apply to just the main
// grid cells.
// attrs: String?
// A space separated string of attribute='value' pairs to add to
// the header cell element and main grid cell elements.
this.name = name;
this.value = value;
this.get = get;
this.formatter = formatter;
this.type = type;
this.editable = editable;
this.hidden = hidden;
this.width = width;
this.colSpan = colSpan;
this.rowSpan = rowSpan;
this.styles = styles;
this.headerStyles = headerStyles;
this.cellStyles = cellStyles;
this.classes = classes;
this.headerClasses = headerClasses;
this.cellClasses = cellClasses;
this.attrs = attrs;
}
=====*/
/*=====
dojox.grid.__ViewDef = function(){
// noscroll: Boolean?
// If true, no scrollbars will be rendered without scrollbars.
// width: Integer|String?
// A CSS size. If it's an Integer, the width will be in em's. If
// "noscroll" is true, this value is ignored.
// cells: dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]]?
// The structure of the cells within this grid.
// type: String?
// A string containing the constructor of a subclass of
// dojox.grid._View. If this is not specified, dojox.grid._View
// is used.
// defaultCell: dojox.grid.__CellDef?
// A cell definition with default values for all cells in this view. If
// a property is defined in a cell definition in the "cells" array and
// this property, the cell definition's property will override this
// property's property.
// onBeforeRow: Function?
// function(rowIndex, cells){} rowIndex is of type Integer, cells
// is of type Array[dojox.grid.__CellDef[]]. This function is called
// before each row of data is rendered. Before the header is
// rendered, rowIndex will be -1. "cells" is a reference to the
// internal structure of this view's cells so any changes you make to
// it will persist between calls.
// onAfterRow: Function?
// function(rowIndex, cells, rowNode){} rowIndex is of type Integer, cells
// is of type Array[dojox.grid.__CellDef[]], rowNode is of type DOMNode.
// This function is called after each row of data is rendered. After the
// header is rendered, rowIndex will be -1. "cells" is a reference to the
// internal structure of this view's cells so any changes you make to
// it will persist between calls.
this.noscroll = noscroll;
this.width = width;
this.cells = cells;
this.type = type;
this.defaultCell = defaultCell;
this.onBeforeRow = onBeforeRow;
this.onAfterRow = onAfterRow;
}
=====*/
dojo.declare('dojox.grid._Grid',
[ dijit._Widget, dijit._Templated, dojox.grid._Events ],
{
// summary:
// A grid widget with virtual scrolling, cell editing, complex rows,
// sorting, fixed columns, sizeable columns, etc.
//
// description:
// _Grid provides the full set of grid features without any
// direct connection to a data store.
//
// The grid exposes a get function for the grid, or optionally
// individual columns, to populate cell contents.
//
// The grid is rendered based on its structure, an object describing
// column and cell layout.
//
// example:
// A quick sample:
//
// define a get function
// | function get(inRowIndex){ // called in cell context
// | return [this.index, inRowIndex].join(', ');
// | }
//
// define the grid structure:
// | var structure = [ // array of view objects
// | { cells: [// array of rows, a row is an array of cells
// | [
// | { name: "Alpha", width: 6 },
// | { name: "Beta" },
// | { name: "Gamma", get: get }]
// | ]}
// | ];
//
// | <div id="grid"
// | rowCount="100" get="get"
// | structure="structure"
// | dojoType="dojox.grid._Grid"></div>
templateString:"<div class=\"dojoxGrid\" hidefocus=\"hidefocus\" wairole=\"grid\" dojoAttachEvent=\"onmouseout:_mouseOut\">\n\t<div class=\"dojoxGridMasterHeader\" dojoAttachPoint=\"viewsHeaderNode\" wairole=\"presentation\"></div>\n\t<div class=\"dojoxGridMasterView\" dojoAttachPoint=\"viewsNode\" wairole=\"presentation\"></div>\n\t<div class=\"dojoxGridMasterMessages\" style=\"display: none;\" dojoAttachPoint=\"messagesNode\"></div>\n\t<span dojoAttachPoint=\"lastFocusNode\" tabindex=\"0\"></span>\n</div>\n",
// classTag: String
// CSS class applied to the grid's domNode
classTag: 'dojoxGrid',
get: function(inRowIndex){
// summary: Default data getter.
// description:
// Provides data to display in a grid cell. Called in grid cell context.
// So this.cell.index is the column index.
// inRowIndex: Integer
// Row for which to provide data
// returns:
// Data to display for a given grid cell.
},
// settings
// rowCount: Integer
// Number of rows to display.
rowCount: 5,
// keepRows: Integer
// Number of rows to keep in the rendering cache.
keepRows: 75,
// rowsPerPage: Integer
// Number of rows to render at a time.
rowsPerPage: 25,
// autoWidth: Boolean
// If autoWidth is true, grid width is automatically set to fit the data.
autoWidth: false,
// initialWidth: String
// A css string to use to set our initial width (only used if autoWidth
// is true). The first rendering of the grid will be this width, any
// resizing of columns, etc will result in the grid switching to
// autoWidth mode. Note, this width will override any styling in a
// stylesheet or directly on the node.
initialWidth: "",
// autoHeight: Boolean|Integer
// If autoHeight is true, grid height is automatically set to fit the data.
// If it is an integer, the height will be automatically set to fit the data
// if there are fewer than that many rows - and the height will be set to show
// that many rows if there are more
autoHeight: '',
// rowHeight: Integer
// If rowHeight is set to a positive number, it will define the height of the rows
// in pixels. This can provide a significant performance advantage, since it
// eliminates the need to measure row sizes during rendering, which is one
// the primary bottlenecks in the DataGrid's performance.
rowHeight: 0,
// autoRender: Boolean
// If autoRender is true, grid will render itself after initialization.
autoRender: true,
// defaultHeight: String
// default height of the grid, measured in any valid css unit.
defaultHeight: '15em',
// height: String
// explicit height of the grid, measured in any valid css unit. This will be populated (and overridden)
// if the height: css attribute exists on the source node.
height: '',
// structure: dojox.grid.__ViewDef|dojox.grid.__ViewDef[]|dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]]
// View layout defintion.
structure: null,
// elasticView: Integer
// Override defaults and make the indexed grid view elastic, thus filling available horizontal space.
elasticView: -1,
// singleClickEdit: boolean
// Single-click starts editing. Default is double-click
singleClickEdit: false,
// selectionMode: String
// Set the selection mode of grid's Selection. Value must be 'single', 'multiple',
// or 'extended'. Default is 'extended'.
selectionMode: 'extended',
// rowSelector: Boolean|String
// If set to true, will add a row selector view to this grid. If set to a CSS width, will add
// a row selector of that width to this grid.
rowSelector: '',
// columnReordering: Boolean
// If set to true, will add drag and drop reordering to views with one row of columns.
columnReordering: false,
// headerMenu: dijit.Menu
// If set to a dijit.Menu, will use this as a context menu for the grid headers.
headerMenu: null,
// placeholderLabel: String
// Label of placeholders to search for in the header menu to replace with column toggling
// menu items.
placeholderLabel: "GridColumns",
// selectable: Boolean
// Set to true if you want to be able to select the text within the grid.
selectable: false,
// Used to store the last two clicks, to ensure double-clicking occurs based on the intended row
_click: null,
// loadingMessage: String
// Message that shows while the grid is loading
loadingMessage: "<span class='dojoxGridLoading'>${loadingState}</span>",
// errorMessage: String
// Message that shows when the grid encounters an error loading
errorMessage: "<span class='dojoxGridError'>${errorState}</span>",
// noDataMessage: String
// Message that shows if the grid has no data - wrap it in a
// span with class 'dojoxGridNoData' if you want it to be
// styled similar to the loading and error messages
noDataMessage: "",
// escapeHTMLInData: Boolean
// This will escape HTML brackets from the data to prevent HTML from
// user-inputted data being rendered with may contain JavaScript and result in
// XSS attacks. This is true by default, and it is recommended that it remain
// true. Setting this to false will allow data to be displayed in the grid without
// filtering, and should be only used if it is known that the data won't contain
// malicious scripts. If HTML is needed in grid cells, it is recommended that
// you use the formatter function to generate the HTML (the output of
// formatter functions is not filtered, even with escapeHTMLInData set to true).
escapeHTMLInData: true,
// formatterScope: Object
// An object to execute format functions within. If not set, the
// format functions will execute within the scope of the cell that
// has a format function.
formatterScope: null,
// editable: boolean
// indicates if the grid contains editable cells, default is false
// set to true if editable cell encountered during rendering
editable: false,
// private
sortInfo: 0,
themeable: true,
_placeholders: null,
// _layoutClass: Object
// The class to use for our layout - can be overridden by grid subclasses
_layoutClass: dojox.grid._Layout,
// initialization
buildRendering: function(){
this.inherited(arguments);
// reset get from blank function (needed for markup parsing) to null, if not changed
if(this.get == dojox.grid._Grid.prototype.get){
this.get = null;
}
if(!this.domNode.getAttribute('tabIndex')){
this.domNode.tabIndex = "0";
}
this.createScroller();
this.createLayout();
this.createViews();
this.createManagers();
this.createSelection();
this.connect(this.selection, "onSelected", "onSelected");
this.connect(this.selection, "onDeselected", "onDeselected");
this.connect(this.selection, "onChanged", "onSelectionChanged");
dojox.html.metrics.initOnFontResize();
this.connect(dojox.html.metrics, "onFontResize", "textSizeChanged");
dojox.grid.util.funnelEvents(this.domNode, this, 'doKeyEvent', dojox.grid.util.keyEvents);
if (this.selectionMode != "none") {
dojo.attr(this.domNode, "aria-multiselectable", this.selectionMode == "single" ? "false" : "true");
}
},
postMixInProperties: function(){
this.inherited(arguments);
var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
if(this.srcNodeRef && this.srcNodeRef.style.height){
this.height = this.srcNodeRef.style.height;
}
// Call this to update our autoheight to start out
this._setAutoHeightAttr(this.autoHeight, true);
this.lastScrollTop = this.scrollTop = 0;
},
postCreate: function(){
this._placeholders = [];
this._setHeaderMenuAttr(this.headerMenu);
this._setStructureAttr(this.structure);
this._click = [];
this.inherited(arguments);
if(this.domNode && this.autoWidth && this.initialWidth){
this.domNode.style.width = this.initialWidth;
}
if (this.domNode && !this.editable){
// default value for aria-readonly is false, set to true if grid is not editable
dojo.attr(this.domNode,"aria-readonly", "true");
}
},
destroy: function(){
this.domNode.onReveal = null;
this.domNode.onSizeChange = null;
// Fixes IE domNode leak
delete this._click;
this.edit.destroy();
delete this.edit;
this.views.destroyViews();
if(this.scroller){
this.scroller.destroy();
delete this.scroller;
}
if(this.focus){
this.focus.destroy();
delete this.focus;
}
if(this.headerMenu&&this._placeholders.length){
dojo.forEach(this._placeholders, function(p){ p.unReplace(true); });
this.headerMenu.unBindDomNode(this.viewsHeaderNode);
}
this.inherited(arguments);
},
_setAutoHeightAttr: function(ah, skipRender){
// Calculate our autoheight - turn it into a boolean or an integer
if(typeof ah == "string"){
if(!ah || ah == "false"){
ah = false;
}else if (ah == "true"){
ah = true;
}else{
ah = window.parseInt(ah, 10);
}
}
if(typeof ah == "number"){
if(isNaN(ah)){
ah = false;
}
// Autoheight must be at least 1, if it's a number. If it's
// less than 0, we'll take that to mean "all" rows (same as
// autoHeight=true - if it is equal to zero, we'll take that
// to mean autoHeight=false
if(ah < 0){
ah = true;
}else if (ah === 0){
ah = false;
}
}
this.autoHeight = ah;
if(typeof ah == "boolean"){
this._autoHeight = ah;
}else if(typeof ah == "number"){
this._autoHeight = (ah >= this.attr('rowCount'));
}else{
this._autoHeight = false;
}
if(this._started && !skipRender){
this.render();
}
},
_getRowCountAttr: function(){
return this.updating && this.invalidated && this.invalidated.rowCount != undefined ?
this.invalidated.rowCount : this.rowCount;
},
textSizeChanged: function(){
this.render();
},
sizeChange: function(){
this.update();
},
createManagers: function(){
// summary:
// create grid managers for various tasks including rows, focus, selection, editing
// row manager
this.rows = new dojox.grid._RowManager(this);
// focus manager
this.focus = new dojox.grid._FocusManager(this);
// edit manager
this.edit = new dojox.grid._EditManager(this);
},
createSelection: function(){
// summary: Creates a new Grid selection manager.
// selection manager
this.selection = new dojox.grid.Selection(this);
},
createScroller: function(){
// summary: Creates a new virtual scroller
this.scroller = new dojox.grid._Scroller();
this.scroller.grid = this;
this.scroller.renderRow = dojo.hitch(this, "renderRow");
this.scroller.removeRow = dojo.hitch(this, "rowRemoved");
},
createLayout: function(){
// summary: Creates a new Grid layout
this.layout = new this._layoutClass(this);
this.connect(this.layout, "moveColumn", "onMoveColumn");
},
onMoveColumn: function(){
this.render();
},
onResizeColumn: function(/*int*/ cellIdx){
// Called when a column is resized.
},
// views
createViews: function(){
this.views = new dojox.grid._ViewManager(this);
this.views.createView = dojo.hitch(this, "createView");
},
createView: function(inClass, idx){
var c = dojo.getObject(inClass);
var view = new c({ grid: this, index: idx });
this.viewsNode.appendChild(view.domNode);
this.viewsHeaderNode.appendChild(view.headerNode);
this.views.addView(view);
return view;
},
buildViews: function(){
for(var i=0, vs; (vs=this.layout.structure[i]); i++){
this.createView(vs.type || dojox._scopeName + ".grid._View", i).setStructure(vs);
}
this.scroller.setContentNodes(this.views.getContentNodes());
},
_setStructureAttr: function(structure){
var s = structure;
if(s && dojo.isString(s)){
dojo.deprecated("dojox.grid._Grid.attr('structure', 'objVar')", "use dojox.grid._Grid.attr('structure', objVar) instead", "2.0");
s=dojo.getObject(s);
}
this.structure = s;
if(!s){
if(this.layout.structure){
s = this.layout.structure;
}else{
return;
}
}
this.views.destroyViews();
if(s !== this.layout.structure){
this.layout.setStructure(s);
}
this._structureChanged();
},
setStructure: function(/* dojox.grid.__ViewDef|dojox.grid.__ViewDef[]|dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]] */ inStructure){
// summary:
// Install a new structure and rebuild the grid.
dojo.deprecated("dojox.grid._Grid.setStructure(obj)", "use dojox.grid._Grid.attr('structure', obj) instead.", "2.0");
this._setStructureAttr(inStructure);
},
getColumnTogglingItems: function(){
// Summary: returns an array of dijit.CheckedMenuItem widgets that can be
// added to a menu for toggling columns on and off.
return dojo.map(this.layout.cells, function(cell){
if(!cell.menuItems){ cell.menuItems = []; }
var self = this;
var item = new dijit.CheckedMenuItem({
label: cell.name,
checked: !cell.hidden,
_gridCell: cell,
onChange: function(checked){
if(self.layout.setColumnVisibility(this._gridCell.index, checked)){
var items = this._gridCell.menuItems;
if(items.length > 1){
dojo.forEach(items, function(item){
if(item !== this){
item.setAttribute("checked", checked);
}
}, this);
}
checked = dojo.filter(self.layout.cells, function(c){
if(c.menuItems.length > 1){
dojo.forEach(c.menuItems, "item.attr('disabled', false);");
}else{
c.menuItems[0].attr('disabled', false);
}
return !c.hidden;
});
if(checked.length == 1){
dojo.forEach(checked[0].menuItems, "item.attr('disabled', true);");
}
}
},
destroy: function(){
var index = dojo.indexOf(this._gridCell.menuItems, this);
this._gridCell.menuItems.splice(index, 1);
delete this._gridCell;
dijit.CheckedMenuItem.prototype.destroy.apply(this, arguments);
}
});
cell.menuItems.push(item);
return item;
}, this); // dijit.CheckedMenuItem[]
},
_setHeaderMenuAttr: function(menu){
if(this._placeholders && this._placeholders.length){
dojo.forEach(this._placeholders, function(p){
p.unReplace(true);
});
this._placeholders = [];
}
if(this.headerMenu){
this.headerMenu.unBindDomNode(this.viewsHeaderNode);
}
this.headerMenu = menu;
if(!menu){ return; }
this.headerMenu.bindDomNode(this.viewsHeaderNode);
if(this.headerMenu.getPlaceholders){
this._placeholders = this.headerMenu.getPlaceholders(this.placeholderLabel);
}
},
setHeaderMenu: function(/* dijit.Menu */ menu){
dojo.deprecated("dojox.grid._Grid.setHeaderMenu(obj)", "use dojox.grid._Grid.attr('headerMenu', obj) instead.", "2.0");
this._setHeaderMenuAttr(menu);
},
setupHeaderMenu: function(){
if(this._placeholders && this._placeholders.length){
dojo.forEach(this._placeholders, function(p){
if(p._replaced){
p.unReplace(true);
}
p.replace(this.getColumnTogglingItems());
}, this);
}
},
_fetch: function(start){
this.setScrollTop(0);
},
getItem: function(inRowIndex){
return null;
},
showMessage: function(message){
if(message){
this.messagesNode.innerHTML = message;
this.messagesNode.style.display = "";
}else{
this.messagesNode.innerHTML = "";
this.messagesNode.style.display = "none";
}
},
_structureChanged: function() {
this.buildViews();
if(this.autoRender && this._started){
this.render();
}
},
hasLayout: function() {
return this.layout.cells.length;
},
// sizing
resize: function(changeSize, resultSize){
// summary:
// Update the grid's rendering dimensions and resize it
// Calling sizeChange calls update() which calls _resize...so let's
// save our input values, if any, and use them there when it gets
// called. This saves us an extra call to _resize(), which can
// get kind of heavy.
this._pendingChangeSize = changeSize;
this._pendingResultSize = resultSize;
this.sizeChange();
},
_getPadBorder: function() {
this._padBorder = this._padBorder || dojo._getPadBorderExtents(this.domNode);
return this._padBorder;
},
_getHeaderHeight: function(){
var vns = this.viewsHeaderNode.style, t = vns.display == "none" ? 0 : this.views.measureHeader();
vns.height = t + 'px';
// header heights are reset during measuring so must be normalized after measuring.
this.views.normalizeHeaderNodeHeight();
return t;
},
_resize: function(changeSize, resultSize){
// Restore our pending values, if any
changeSize = changeSize || this._pendingChangeSize;
resultSize = resultSize || this._pendingResultSize;
delete this._pendingChangeSize;
delete this._pendingResultSize;
// if we have set up everything except the DOM, we cannot resize
if(!this.domNode){ return; }
var pn = this.domNode.parentNode;
if(!pn || pn.nodeType != 1 || !this.hasLayout() || pn.style.visibility == "hidden" || pn.style.display == "none"){
return;
}
// useful measurement
var padBorder = this._getPadBorder();
var hh = undefined;
var h;
// grid height
if(this._autoHeight){
this.domNode.style.height = 'auto';
this.viewsNode.style.height = '';
}else if(typeof this.autoHeight == "number"){
h = hh = this._getHeaderHeight();
h += (this.scroller.averageRowHeight * this.autoHeight);
this.domNode.style.height = h + "px";
}else if(this.domNode.clientHeight <= padBorder.h){
if(pn == document.body){
this.domNode.style.height = this.defaultHeight;
}else if(this.height){
this.domNode.style.height = this.height;
}else{
this.fitTo = "parent";
}
}
// if we are given dimensions, size the grid's domNode to those dimensions
if(resultSize){
changeSize = resultSize;
}
if(changeSize){
dojo.marginBox(this.domNode, changeSize);
this.height = this.domNode.style.height;
delete this.fitTo;
}else if(this.fitTo == "parent"){
h = this._parentContentBoxHeight = this._parentContentBoxHeight || dojo._getContentBox(pn).h;
this.domNode.style.height = Math.max(0, h) + "px";
}
var hasFlex = dojo.some(this.views.views, function(v){ return v.flexCells; });
if(!this._autoHeight && (h || dojo._getContentBox(this.domNode).h) === 0){
// We need to hide the header, since the Grid is essentially hidden.
this.viewsHeaderNode.style.display = "none";
}else{
// Otherwise, show the header and give it an appropriate height.
this.viewsHeaderNode.style.display = "block";
if(!hasFlex && hh === undefined){
hh = this._getHeaderHeight();
}
}
if(hasFlex){
hh = undefined;
}
// NOTE: it is essential that width be applied before height
// Header height can only be calculated properly after view widths have been set.
// This is because flex column width is naturally 0 in Firefox.
// Therefore prior to width sizing flex columns with spaces are maximally wrapped
// and calculated to be too tall.
this.adaptWidth();
this.adaptHeight(hh);
this.postresize();
},
adaptWidth: function() {
// private: sets width and position for views and update grid width if necessary
var doAutoWidth = (!this.initialWidth && this.autoWidth);
var w = doAutoWidth ? 0 : this.domNode.clientWidth || (this.domNode.offsetWidth - this._getPadBorder().w),
vw = this.views.arrange(1, w);
this.views.onEach("adaptWidth");
if(doAutoWidth){
this.domNode.style.width = vw + "px";
}
},
adaptHeight: function(inHeaderHeight){
// private: measures and normalizes header height, then sets view heights, and then updates scroller
// content extent
var t = inHeaderHeight === undefined ? this._getHeaderHeight() : inHeaderHeight;
var h = (this._autoHeight ? -1 : Math.max(this.domNode.clientHeight - t, 0) || 0);
this.views.onEach('setSize', [0, h]);
this.views.onEach('adaptHeight');
if(!this._autoHeight){
var numScroll = 0, numNoScroll = 0;
var noScrolls = dojo.filter(this.views.views, function(v){
var has = v.hasHScrollbar();
if(has){ numScroll++; }else{ numNoScroll++; }
return (!has);
});
if(numScroll > 0 && numNoScroll > 0){
dojo.forEach(noScrolls, function(v){
v.adaptHeight(true);
});
}
}
if(this.autoHeight === true || h != -1 || (typeof this.autoHeight == "number" && this.autoHeight >= this.attr('rowCount'))){
this.scroller.windowHeight = h;
}else{
this.scroller.windowHeight = Math.max(this.domNode.clientHeight - t, 0);
}
},
// startup
startup: function(){
if(this._started){return;}
this.inherited(arguments);
if(this.autoRender){
this.render();
}
},
// render
render: function(){
// summary:
// Render the grid, headers, and views. Edit and scrolling states are reset. To retain edit and
// scrolling states, see Update.
if(!this.domNode){return;}
if(!this._started){return;}
if(!this.hasLayout()) {
this.scroller.init(0, this.keepRows, this.rowsPerPage);
return;
}
//
this.update = this.defaultUpdate;
this._render();
},
_render: function(){
this.scroller.init(this.attr('rowCount'), this.keepRows, this.rowsPerPage);
this.prerender();
this.setScrollTop(0);
this.postrender();
},
prerender: function(){
// if autoHeight, make sure scroller knows not to virtualize; everything must be rendered.
this.keepRows = this._autoHeight ? 0 : this.keepRows;
this.scroller.setKeepInfo(this.keepRows);
this.views.render();
this._resize();
},
postrender: function(){
this.postresize();
this.focus.initFocusView();
// make rows unselectable
dojo.setSelectable(this.domNode, this.selectable);
},
postresize: function(){
// views are position absolute, so they do not inflate the parent
if(this._autoHeight){
var size = Math.max(this.views.measureContent()) + 'px';
this.viewsNode.style.height = size;
}
},
renderRow: function(inRowIndex, inNodes){
// summary: private, used internally to render rows
this.views.renderRow(inRowIndex, inNodes, this._skipRowRenormalize);
},
rowRemoved: function(inRowIndex){
// summary: private, used internally to remove rows
this.views.rowRemoved(inRowIndex);
},
invalidated: null,
updating: false,
beginUpdate: function(){
// summary:
// Use to make multiple changes to rows while queueing row updating.
// NOTE: not currently supporting nested begin/endUpdate calls
this.invalidated = [];
this.updating = true;
},
endUpdate: function(){
// summary:
// Use after calling beginUpdate to render any changes made to rows.
this.updating = false;
var i = this.invalidated, r;
if(i.all){
this.update();
}else if(i.rowCount != undefined){
this.updateRowCount(i.rowCount);
}else{
for(r in i){
this.updateRow(Number(r));
}
}
this.invalidated = [];
},
// update
defaultUpdate: function(){
// note: initial update calls render and subsequently this function.
if(!this.domNode){return;}
if(this.updating){
this.invalidated.all = true;
return;
}
//this.edit.saveState(inRowIndex);
this.lastScrollTop = this.scrollTop;
this.prerender();
this.scroller.invalidateNodes();
this.setScrollTop(this.lastScrollTop);
this.postrender();
//this.edit.restoreState(inRowIndex);
},
update: function(){
// summary:
// Update the grid, retaining edit and scrolling states.
this.render();
},
updateRow: function(inRowIndex){
// summary:
// Render a single row.
// inRowIndex: Integer
// Index of the row to render
inRowIndex = Number(inRowIndex);
if(this.updating){
this.invalidated[inRowIndex]=true;
}else{
this.views.updateRow(inRowIndex);
this.scroller.rowHeightChanged(inRowIndex);
}
},
updateRows: function(startIndex, howMany){
// summary:
// Render consecutive rows at once.
// startIndex: Integer
// Index of the starting row to render
// howMany: Integer
// How many rows to update.
startIndex = Number(startIndex);
howMany = Number(howMany);
var i;
if(this.updating){
for(i=0; i<howMany; i++){
this.invalidated[i+startIndex]=true;
}
}else{
for(i=0; i<howMany; i++){
this.views.updateRow(i+startIndex, this._skipRowRenormalize);
}
this.scroller.rowHeightChanged(startIndex);
}
},
updateRowCount: function(inRowCount){
//summary:
// Change the number of rows.
// inRowCount: int
// Number of rows in the grid.
if(this.updating){
this.invalidated.rowCount = inRowCount;
}else{
this.rowCount = inRowCount;
this._setAutoHeightAttr(this.autoHeight, true);
if(this.layout.cells.length){
this.scroller.updateRowCount(inRowCount);
}
this._resize();
if(this.layout.cells.length){
this.setScrollTop(this.scrollTop);
}
}
},
updateRowStyles: function(inRowIndex){
// summary:
// Update the styles for a row after it's state has changed.
this.views.updateRowStyles(inRowIndex);
},
getRowNode: function(inRowIndex){
// summary:
// find the rowNode that is not a rowSelector
if (this.focus.focusView && !(this.focus.focusView instanceof dojox.grid._RowSelector)){
return this.focus.focusView.rowNodes[inRowIndex];
}else{ // search through views
for (var i = 0, cView; (cView = this.views.views[i]); i++) {
if (!(cView instanceof dojox.grid._RowSelector)) {
return cView.rowNodes[inRowIndex];
}
}
}
return null;
},
rowHeightChanged: function(inRowIndex){
// summary:
// Update grid when the height of a row has changed. Row height is handled automatically as rows
// are rendered. Use this function only to update a row's height outside the normal rendering process.
// inRowIndex: Integer
// index of the row that has changed height
this.views.renormalizeRow(inRowIndex);
this.scroller.rowHeightChanged(inRowIndex);
},
// fastScroll: Boolean
// flag modifies vertical scrolling behavior. Defaults to true but set to false for slower
// scroll performance but more immediate scrolling feedback
fastScroll: true,
delayScroll: false,
// scrollRedrawThreshold: int
// pixel distance a user must scroll vertically to trigger grid scrolling.
scrollRedrawThreshold: (dojo.isIE ? 100 : 50),
// scroll methods
scrollTo: function(inTop){
// summary:
// Vertically scroll the grid to a given pixel position
// inTop: Integer
// vertical position of the grid in pixels
if(!this.fastScroll){
this.setScrollTop(inTop);
return;
}
var delta = Math.abs(this.lastScrollTop - inTop);
this.lastScrollTop = inTop;
if(delta > this.scrollRedrawThreshold || this.delayScroll){
this.delayScroll = true;
this.scrollTop = inTop;
this.views.setScrollTop(inTop);
if(this._pendingScroll){
window.clearTimeout(this._pendingScroll);
}
var _this = this;
this._pendingScroll = window.setTimeout(function(){
delete _this._pendingScroll;
_this.finishScrollJob();
}, 200);
}else{
this.setScrollTop(inTop);
}
},
finishScrollJob: function(){
this.delayScroll = false;
this.setScrollTop(this.scrollTop);
},
setScrollTop: function(inTop){
this.scroller.scroll(this.views.setScrollTop(inTop));
},
scrollToRow: function(inRowIndex){
// summary:
// Scroll the grid to a specific row.
// inRowIndex: Integer
// grid row index
this.setScrollTop(this.scroller.findScrollTop(inRowIndex) + 1);
},
// styling (private, used internally to style individual parts of a row)
styleRowNode: function(inRowIndex, inRowNode){
if(inRowNode){
this.rows.styleRowNode(inRowIndex, inRowNode);
}
},
// called when the mouse leaves the grid so we can deselect all hover rows
_mouseOut: function(e){
this.rows.setOverRow(-2);
},
// cells
getCell: function(inIndex){
// summary:
// Retrieves the cell object for a given grid column.
// inIndex: Integer
// Grid column index of cell to retrieve
// returns:
// a grid cell
return this.layout.cells[inIndex];
},
setCellWidth: function(inIndex, inUnitWidth){
this.getCell(inIndex).unitWidth = inUnitWidth;
},
getCellName: function(inCell){
// summary: Returns the cell name of a passed cell
return "Cell " + inCell.index; // String
},
// sorting
canSort: function(inSortInfo){
// summary:
// Determines if the grid can be sorted
// inSortInfo: Integer
// Sort information, 1-based index of column on which to sort, positive for an ascending sort
// and negative for a descending sort
// returns: Boolean
// True if grid can be sorted on the given column in the given direction
},
sort: function(){
},
getSortAsc: function(inSortInfo){
// summary:
// Returns true if grid is sorted in an ascending direction.
inSortInfo = inSortInfo == undefined ? this.sortInfo : inSortInfo;
return Boolean(inSortInfo > 0); // Boolean
},
getSortIndex: function(inSortInfo){
// summary:
// Returns the index of the column on which the grid is sorted
inSortInfo = inSortInfo == undefined ? this.sortInfo : inSortInfo;
return Math.abs(inSortInfo) - 1; // Integer
},
setSortIndex: function(inIndex, inAsc){
// summary:
// Sort the grid on a column in a specified direction
// inIndex: Integer
// Column index on which to sort.
// inAsc: Boolean
// If true, sort the grid in ascending order, otherwise in descending order
var si = inIndex +1;
if(inAsc != undefined){
si *= (inAsc ? 1 : -1);
} else if(this.getSortIndex() == inIndex){
si = -this.sortInfo;
}
this.setSortInfo(si);
},
setSortInfo: function(inSortInfo){
if(this.canSort(inSortInfo)){
this.sortInfo = inSortInfo;
this.sort();
this.update();
}
},
// DOM event handler
doKeyEvent: function(e){
e.dispatch = 'do' + e.type;
this.onKeyEvent(e);
},
// event dispatch
//: protected
_dispatch: function(m, e){
if(m in this){
return this[m](e);
}
return false;
},
dispatchKeyEvent: function(e){
this._dispatch(e.dispatch, e);
},
dispatchContentEvent: function(e){
this.edit.dispatchEvent(e) || e.sourceView.dispatchContentEvent(e) || this._dispatch(e.dispatch, e);
},
dispatchHeaderEvent: function(e){
e.sourceView.dispatchHeaderEvent(e) || this._dispatch('doheader' + e.type, e);
},
dokeydown: function(e){
this.onKeyDown(e);
},
doclick: function(e){
if(e.cellNode){
this.onCellClick(e);
}else{
this.onRowClick(e);
}
},
dodblclick: function(e){
if(e.cellNode){
this.onCellDblClick(e);
}else{
this.onRowDblClick(e);
}
},
docontextmenu: function(e){
if(e.cellNode){
this.onCellContextMenu(e);
}else{
this.onRowContextMenu(e);
}
},
doheaderclick: function(e){
if(e.cellNode){
this.onHeaderCellClick(e);
}else{
this.onHeaderClick(e);
}
},
doheaderdblclick: function(e){
if(e.cellNode){
this.onHeaderCellDblClick(e);
}else{
this.onHeaderDblClick(e);
}
},
doheadercontextmenu: function(e){
if(e.cellNode){
this.onHeaderCellContextMenu(e);
}else{
this.onHeaderContextMenu(e);
}
},
// override to modify editing process
doStartEdit: function(inCell, inRowIndex){
this.onStartEdit(inCell, inRowIndex);
},
doApplyCellEdit: function(inValue, inRowIndex, inFieldIndex){
this.onApplyCellEdit(inValue, inRowIndex, inFieldIndex);
},
doCancelEdit: function(inRowIndex){
this.onCancelEdit(inRowIndex);
},
doApplyEdit: function(inRowIndex){
this.onApplyEdit(inRowIndex);
},
// row editing
addRow: function(){
// summary:
// Add a row to the grid.
this.updateRowCount(this.attr('rowCount')+1);
},
removeSelectedRows: function(){
// summary:
// Remove the selected rows from the grid.
if(this.allItemsSelected){
this.updateRowCount(0);
}else{
this.updateRowCount(Math.max(0, this.attr('rowCount') - this.selection.getSelected().length));
}
this.selection.clear();
}
});
dojox.grid._Grid.markupFactory = function(props, node, ctor, cellFunc){
var d = dojo;
var widthFromAttr = function(n){
var w = d.attr(n, "width")||"auto";
if((w != "auto")&&(w.slice(-2) != "em")&&(w.slice(-1) != "%")){
w = parseInt(w, 10)+"px";
}
return w;
};
// if(!props.store){ console.debug("no store!"); }
// if a structure isn't referenced, do we have enough
// data to try to build one automatically?
if( !props.structure &&
node.nodeName.toLowerCase() == "table"){
// try to discover a structure
props.structure = d.query("> colgroup", node).map(function(cg){
var sv = d.attr(cg, "span");
var v = {
noscroll: (d.attr(cg, "noscroll") == "true") ? true : false,
__span: (!!sv ? parseInt(sv, 10) : 1),
cells: []
};
if(d.hasAttr(cg, "width")){
v.width = widthFromAttr(cg);
}
return v; // for vendetta
});
if(!props.structure.length){
props.structure.push({
__span: Infinity,
cells: [] // catch-all view
});
}
// check to see if we're gonna have more than one view
// for each tr in our th, create a row of cells
d.query("thead > tr", node).forEach(function(tr, tr_idx){
var cellCount = 0;
var viewIdx = 0;
var lastViewIdx;
var cView = null;
d.query("> th", tr).map(function(th){
// what view will this cell go into?
// NOTE:
// to prevent extraneous iteration, we start counters over
// for each row, incrementing over the surface area of the
// structure that colgroup processing generates and
// creating cell objects for each <th> to place into those
// cell groups. There's a lot of state-keepking logic
// here, but it is what it has to be.
if(!cView){ // current view book keeping
lastViewIdx = 0;
cView = props.structure[0];
}else if(cellCount >= (lastViewIdx+cView.__span)){
viewIdx++;
// move to allocating things into the next view
lastViewIdx += cView.__span;
var lastView = cView;
cView = props.structure[viewIdx];
}
// actually define the cell from what markup hands us
var cell = {
name: d.trim(d.attr(th, "name")||th.innerHTML),
colSpan: parseInt(d.attr(th, "colspan")||1, 10),
type: d.trim(d.attr(th, "cellType")||""),
id: d.trim(d.attr(th,"id")||"")
};
cellCount += cell.colSpan;
var rowSpan = d.attr(th, "rowspan");
if(rowSpan){
cell.rowSpan = rowSpan;
}
if(d.hasAttr(th, "width")){
cell.width = widthFromAttr(th);
}
if(d.hasAttr(th, "relWidth")){
cell.relWidth = window.parseInt(dojo.attr(th, "relWidth"), 10);
}
if(d.hasAttr(th, "hidden")){
cell.hidden = d.attr(th, "hidden") == "true";
}
if(cellFunc){
cellFunc(th, cell);
}
cell.type = cell.type ? dojo.getObject(cell.type) : dojox.grid.cells.Cell;
if(cell.type && cell.type.markupFactory){
cell.type.markupFactory(th, cell);
}
if(!cView.cells[tr_idx]){
cView.cells[tr_idx] = [];
}
cView.cells[tr_idx].push(cell);
});
});
}
return new ctor(props, node);
};
})();
}
if(!dojo._hasResource["dojox.grid.DataSelection"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.DataSelection"] = true;
dojo.provide("dojox.grid.DataSelection");
dojo.declare("dojox.grid.DataSelection", dojox.grid.Selection, {
getFirstSelected: function(){
var idx = dojox.grid.Selection.prototype.getFirstSelected.call(this);
if(idx == -1){ return null; }
return this.grid.getItem(idx);
},
getNextSelected: function(inPrev){
var old_idx = this.grid.getItemIndex(inPrev);
var idx = dojox.grid.Selection.prototype.getNextSelected.call(this, old_idx);
if(idx == -1){ return null; }
return this.grid.getItem(idx);
},
getSelected: function(){
var result = [];
for(var i=0, l=this.selected.length; i<l; i++){
if(this.selected[i]){
result.push(this.grid.getItem(i));
}
}
return result;
},
addToSelection: function(inItemOrIndex){
if(this.mode == 'none'){ return; }
var idx = null;
if(typeof inItemOrIndex == "number" || typeof inItemOrIndex == "string"){
idx = inItemOrIndex;
}else{
idx = this.grid.getItemIndex(inItemOrIndex);
}
dojox.grid.Selection.prototype.addToSelection.call(this, idx);
},
deselect: function(inItemOrIndex){
if(this.mode == 'none'){ return; }
var idx = null;
if(typeof inItemOrIndex == "number" || typeof inItemOrIndex == "string"){
idx = inItemOrIndex;
}else{
idx = this.grid.getItemIndex(inItemOrIndex);
}
dojox.grid.Selection.prototype.deselect.call(this, idx);
},
deselectAll: function(inItemOrIndex){
var idx = null;
if(inItemOrIndex || typeof inItemOrIndex == "number"){
if(typeof inItemOrIndex == "number" || typeof inItemOrIndex == "string"){
idx = inItemOrIndex;
}else{
idx = this.grid.getItemIndex(inItemOrIndex);
}
dojox.grid.Selection.prototype.deselectAll.call(this, idx);
}else{
this.inherited(arguments);
}
}
});
}
if(!dojo._hasResource["dojox.grid.DataGrid"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.DataGrid"] = true;
dojo.provide("dojox.grid.DataGrid");
/*=====
dojo.declare("dojox.grid.__DataCellDef", dojox.grid.__CellDef, {
constructor: function(){
// field: String?
// The attribute to read from the dojo.data item for the row.
// fields: String[]?
// An array of fields to grab the values of and pass as an array to the grid
// get: Function?
// function(rowIndex, item?){} rowIndex is of type Integer, item is of type
// Object. This function will be called when a cell requests data. Returns
// the unformatted data for the cell.
}
});
=====*/
/*=====
dojo.declare("dojox.grid.__DataViewDef", dojox.grid.__ViewDef, {
constructor: function(){
// cells: dojox.grid.__DataCellDef[]|Array[dojox.grid.__DataCellDef[]]?
// The structure of the cells within this grid.
// defaultCell: dojox.grid.__DataCellDef?
// A cell definition with default values for all cells in this view. If
// a property is defined in a cell definition in the "cells" array and
// this property, the cell definition's property will override this
// property's property.
}
});
=====*/
dojo.declare("dojox.grid.DataGrid", dojox.grid._Grid, {
store: null,
query: null,
queryOptions: null,
fetchText: '...',
sortFields: null,
// updateDelay: int
// Time, in milliseconds, to delay updates automatically so that multiple
// calls to onSet/onNew/onDelete don't keep rerendering the grid. Set
// to 0 to immediately cause updates. A higher value will result in
// better performance at the expense of responsiveness of the grid.
updateDelay: 1,
/*=====
// structure: dojox.grid.__DataViewDef|dojox.grid.__DataViewDef[]|dojox.grid.__DataCellDef[]|Array[dojox.grid.__DataCellDef[]]
// View layout defintion.
structure: '',
=====*/
// You can specify items instead of a query, if you like. They do not need
// to be loaded - but the must be items in the store
items: null,
_store_connects: null,
_by_idty: null,
_by_idx: null,
_cache: null,
_pages: null,
_pending_requests: null,
_bop: -1,
_eop: -1,
_requests: 0,
rowCount: 0,
_isLoaded: false,
_isLoading: false,
postCreate: function(){
this._pages = [];
this._store_connects = [];
this._by_idty = {};
this._by_idx = [];
this._cache = [];
this._pending_requests = {};
this._setStore(this.store);
this.inherited(arguments);
},
createSelection: function(){
this.selection = new dojox.grid.DataSelection(this);
},
get: function(inRowIndex, inItem){
if(inItem && this.field == "_item" && !this.fields){
return inItem;
}else if(inItem && this.fields){
var ret = [];
var s = this.grid.store;
dojo.forEach(this.fields, function(f){
ret = ret.concat(s.getValues(inItem, f));
});
return ret;
}
return (!inItem ? this.defaultValue : (!this.field ? this.value : (this.field == "_item" ? inItem : this.grid.store.getValue(inItem, this.field))));
},
_checkUpdateStatus: function(){
if(this.updateDelay > 0){
var iStarted = false;
if(this._endUpdateDelay){
clearTimeout(this._endUpdateDelay);
delete this._endUpdateDelay;
iStarted = true;
}
if(!this.updating){
this.beginUpdate();
iStarted = true;
}
if(iStarted){
var _this = this;
this._endUpdateDelay = setTimeout(function(){
delete _this._endUpdateDelay;
_this.endUpdate();
}, this.updateDelay);
}
}
},
_onSet: function(item, attribute, oldValue, newValue){
this._checkUpdateStatus();
var idx = this.getItemIndex(item);
if(idx>-1){
this.updateRow(idx);
}
},
_createItem: function(item, index){
var idty = this._hasIdentity ? this.store.getIdentity(item) : dojo.toJson(this.query) + ":idx:" + index + ":sort:" + dojo.toJson(this.getSortProps());
var o = this._by_idty[idty] = { idty: idty, item: item };
return o;
},
_addItem: function(item, index, noUpdate){
this._by_idx[index] = this._createItem(item, index);
if(!noUpdate){
this.updateRow(index);
}
},
_onNew: function(item, parentInfo){
this._checkUpdateStatus();
var rowCount = this.attr('rowCount');
this._addingItem = true;
this.updateRowCount(rowCount+1);
this._addingItem = false;
this._addItem(item, rowCount);
this.showMessage();
},
_onDelete: function(item){
this._checkUpdateStatus();
var idx = this._getItemIndex(item, true);
if(idx >= 0){
// When a row is deleted, all rest rows are shifted down,
// and migrate from page to page. If some page is not
// loaded yet empty rows can migrate to initialized pages
// without refreshing. It causes empty rows in some pages, see:
// http://bugs.dojotoolkit.org/ticket/6818
// this code fix this problem by reseting loaded page info
this._pages = [];
this._bop = -1;
this._eop = -1;
var o = this._by_idx[idx];
this._by_idx.splice(idx, 1);
delete this._by_idty[o.idty];
this.updateRowCount(this.attr('rowCount')-1);
if(this.attr('rowCount') === 0){
this.showMessage(this.noDataMessage);
}
}
},
_onRevert: function(){
this._refresh();
},
setStore: function(store, query, queryOptions){
this._setQuery(query, queryOptions);
this._setStore(store);
this._refresh(true);
},
setQuery: function(query, queryOptions){
this._setQuery(query, queryOptions);
this._refresh(true);
},
setItems: function(items){
this.items = items;
this._setStore(this.store);
this._refresh(true);
},
_setQuery: function(query, queryOptions){
this.query = query;
this.queryOptions = queryOptions || this.queryOptions;
},
_setStore: function(store){
if(this.store&&this._store_connects){
dojo.forEach(this._store_connects,function(arr){
dojo.forEach(arr, dojo.disconnect);
});
}
this.store = store;
if(this.store){
var f = this.store.getFeatures();
var h = [];
this._canEdit = !!f["dojo.data.api.Write"] && !!f["dojo.data.api.Identity"];
this._hasIdentity = !!f["dojo.data.api.Identity"];
if(!!f["dojo.data.api.Notification"] && !this.items){
h.push(this.connect(this.store, "onSet", "_onSet"));
h.push(this.connect(this.store, "onNew", "_onNew"));
h.push(this.connect(this.store, "onDelete", "_onDelete"));
}
if(this._canEdit){
h.push(this.connect(this.store, "revert", "_onRevert"));
}
this._store_connects = h;
}
},
_onFetchBegin: function(size, req){
if(!this.scroller){ return; }
if(this.rowCount != size){
if(req.isRender){
this.scroller.init(size, this.keepRows, this.rowsPerPage);
this.rowCount = size;
this._setAutoHeightAttr(this.autoHeight, true);
this._skipRowRenormalize = true;
this.prerender();
this._skipRowRenormalize = false;
}else{
this.updateRowCount(size);
}
}
if(!size){
this.views.render();
this._resize();
this.showMessage(this.noDataMessage);
this.focus.initFocusView();
}else{
this.showMessage();
}
},
_onFetchComplete: function(items, req){
if(!this.scroller){ return; }
if(items && items.length > 0){
//console.log(items);
dojo.forEach(items, function(item, idx){
this._addItem(item, req.start+idx, true);
}, this);
if(this._autoHeight){
this._skipRowRenormalize = true;
}
this.updateRows(req.start, items.length);
if(this._autoHeight){
this._skipRowRenormalize = false;
}
if(req.isRender){
this.setScrollTop(0);
this.postrender();
}else if(this._lastScrollTop){
this.setScrollTop(this._lastScrollTop);
}
}
delete this._lastScrollTop;
if(!this._isLoaded){
this._isLoading = false;
this._isLoaded = true;
}
this._pending_requests[req.start] = false;
},
_onFetchError: function(err, req){
console.log(err);
delete this._lastScrollTop;
if(!this._isLoaded){
this._isLoading = false;
this._isLoaded = true;
this.showMessage(this.errorMessage);
}
this._pending_requests[req.start] = false;
this.onFetchError(err, req);
},
onFetchError: function(err, req){
},
_fetch: function(start, isRender){
start = start || 0;
if(this.store && !this._pending_requests[start]){
if(!this._isLoaded && !this._isLoading){
this._isLoading = true;
this.showMessage(this.loadingMessage);
}
this._pending_requests[start] = true;
//console.log("fetch: ", start);
try{
if(this.items){
var items = this.items;
var store = this.store;
this.rowsPerPage = items.length;
var req = {
start: start,
count: this.rowsPerPage,
isRender: isRender
};
this._onFetchBegin(items.length, req);
// Load them if we need to
var waitCount = 0;
dojo.forEach(items, function(i){
if(!store.isItemLoaded(i)){ waitCount++; }
});
if(waitCount === 0){
this._onFetchComplete(items, req);
}else{
var onItem = function(item){
waitCount--;
if(waitCount === 0){
this._onFetchComplete(items, req);
}
};
dojo.forEach(items, function(i){
if(!store.isItemLoaded(i)){
store.loadItem({item: i, onItem: onItem, scope: this});
}
}, this);
}
}else{
this.store.fetch({
start: start,
count: this.rowsPerPage,
query: this.query,
sort: this.getSortProps(),
queryOptions: this.queryOptions,
isRender: isRender,
onBegin: dojo.hitch(this, "_onFetchBegin"),
onComplete: dojo.hitch(this, "_onFetchComplete"),
onError: dojo.hitch(this, "_onFetchError")
});
}
}catch(e){
this._onFetchError(e, {start: start, count: this.rowsPerPage});
}
}
},
_clearData: function(){
this.updateRowCount(0);
this._by_idty = {};
this._by_idx = [];
this._pages = [];
this._bop = this._eop = -1;
this._isLoaded = false;
this._isLoading = false;
},
getItem: function(idx){
var data = this._by_idx[idx];
if(!data||(data&&!data.item)){
this._preparePage(idx);
return null;
}
return data.item;
},
getItemIndex: function(item){
return this._getItemIndex(item, false);
},
_getItemIndex: function(item, isDeleted){
if(!isDeleted && !this.store.isItem(item)){
return -1;
}
var idty = this._hasIdentity ? this.store.getIdentity(item) : null;
for(var i=0, l=this._by_idx.length; i<l; i++){
var d = this._by_idx[i];
if(d && ((idty && d.idty == idty) || (d.item === item))){
return i;
}
}
return -1;
},
filter: function(query, reRender){
this.query = query;
if(reRender){
this._clearData();
}
this._fetch();
},
_getItemAttr: function(idx, attr){
var item = this.getItem(idx);
return (!item ? this.fetchText : this.store.getValue(item, attr));
},
// rendering
_render: function(){
if(this.domNode.parentNode){
this.scroller.init(this.attr('rowCount'), this.keepRows, this.rowsPerPage);
this.prerender();
this._fetch(0, true);
}
},
// paging
_requestsPending: function(inRowIndex){
return this._pending_requests[inRowIndex];
},
_rowToPage: function(inRowIndex){
return (this.rowsPerPage ? Math.floor(inRowIndex / this.rowsPerPage) : inRowIndex);
},
_pageToRow: function(inPageIndex){
return (this.rowsPerPage ? this.rowsPerPage * inPageIndex : inPageIndex);
},
_preparePage: function(inRowIndex){
if((inRowIndex < this._bop || inRowIndex >= this._eop) && !this._addingItem){
var pageIndex = this._rowToPage(inRowIndex);
this._needPage(pageIndex);
this._bop = pageIndex * this.rowsPerPage;
this._eop = this._bop + (this.rowsPerPage || this.attr('rowCount'));
}
},
_needPage: function(inPageIndex){
if(!this._pages[inPageIndex]){
this._pages[inPageIndex] = true;
this._requestPage(inPageIndex);
}
},
_requestPage: function(inPageIndex){
var row = this._pageToRow(inPageIndex);
var count = Math.min(this.rowsPerPage, this.attr('rowCount') - row);
if(count > 0){
this._requests++;
if(!this._requestsPending(row)){
setTimeout(dojo.hitch(this, "_fetch", row, false), 1);
//this.requestRows(row, count);
}
}
},
getCellName: function(inCell){
return inCell.field;
//console.log(inCell);
},
_refresh: function(isRender){
this._clearData();
this._fetch(0, isRender);
},
sort: function(){
this._lastScrollTop = this.scrollTop;
this._refresh();
},
canSort: function(){
return (!this._isLoading);
},
getSortProps: function(){
var c = this.getCell(this.getSortIndex());
if(!c){
if(this.sortFields){
return this.sortFields;
}
return null;
}else{
var desc = c["sortDesc"];
var si = !(this.sortInfo>0);
if(typeof desc == "undefined"){
desc = si;
}else{
desc = si ? !desc : desc;
}
return [{ attribute: c.field, descending: desc }];
}
},
styleRowState: function(inRow){
// summary: Perform row styling
if(this.store && this.store.getState){
var states=this.store.getState(inRow.index), c='';
for(var i=0, ss=["inflight", "error", "inserting"], s; s=ss[i]; i++){
if(states[s]){
c = ' dojoxGridRow-' + s;
break;
}
}
inRow.customClasses += c;
}
},
onStyleRow: function(inRow){
this.styleRowState(inRow);
this.inherited(arguments);
},
// editing
canEdit: function(inCell, inRowIndex){
return this._canEdit;
},
_copyAttr: function(idx, attr){
var row = {};
var backstop = {};
var src = this.getItem(idx);
return this.store.getValue(src, attr);
},
doStartEdit: function(inCell, inRowIndex){
if(!this._cache[inRowIndex]){
this._cache[inRowIndex] = this._copyAttr(inRowIndex, inCell.field);
}
this.onStartEdit(inCell, inRowIndex);
},
doApplyCellEdit: function(inValue, inRowIndex, inAttrName){
this.store.fetchItemByIdentity({
identity: this._by_idx[inRowIndex].idty,
onItem: dojo.hitch(this, function(item){
var oldValue = this.store.getValue(item, inAttrName);
if(typeof oldValue == 'number'){
inValue = isNaN(inValue) ? inValue : parseFloat(inValue);
}else if(typeof oldValue == 'boolean'){
inValue = inValue == 'true' ? true : inValue == 'false' ? false : inValue;
}else if(oldValue instanceof Date){
var asDate = new Date(inValue);
inValue = isNaN(asDate.getTime()) ? inValue : asDate;
}
this.store.setValue(item, inAttrName, inValue);
this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
})
});
},
doCancelEdit: function(inRowIndex){
var cache = this._cache[inRowIndex];
if(cache){
this.updateRow(inRowIndex);
delete this._cache[inRowIndex];
}
this.onCancelEdit.apply(this, arguments);
},
doApplyEdit: function(inRowIndex, inDataAttr){
var cache = this._cache[inRowIndex];
/*if(cache){
var data = this.getItem(inRowIndex);
if(this.store.getValue(data, inDataAttr) != cache){
this.update(cache, data, inRowIndex);
}
delete this._cache[inRowIndex];
}*/
this.onApplyEdit(inRowIndex);
},
removeSelectedRows: function(){
// summary:
// Remove the selected rows from the grid.
if(this._canEdit){
this.edit.apply();
var fx = dojo.hitch(this, function(items){
if(items.length){
dojo.forEach(items, this.store.deleteItem, this.store);
this.selection.clear();
}
});
if(this.allItemsSelected){
this.store.fetch({
query: this.query,
queryOptions: this.queryOptions,
onComplete: fx});
}else{
fx(this.selection.getSelected());
}
}
}
});
dojox.grid.DataGrid.cell_markupFactory = function(cellFunc, node, cellDef){
var field = dojo.trim(dojo.attr(node, "field")||"");
if(field){
cellDef.field = field;
}
cellDef.field = cellDef.field||cellDef.name;
var fields = dojo.trim(dojo.attr(node, "fields")||"");
if(fields){
cellDef.fields = fields.split(",");
}
if(cellFunc){
cellFunc(node, cellDef);
}
};
dojox.grid.DataGrid.markupFactory = function(props, node, ctor, cellFunc){
return dojox.grid._Grid.markupFactory(props, node, ctor,
dojo.partial(dojox.grid.DataGrid.cell_markupFactory, cellFunc));
};
}
dojo.i18n._preloadLocalizations("dojox.grid.nls.DataGrid", ["ROOT","ar","ca","cs","da","de","de-de","el","en","en-gb","en-us","es","es-es","fi","fi-fi","fr","fr-fr","he","he-il","hu","it","it-it","ja","ja-jp","ko","ko-kr","nb","nl","nl-nl","pl","pt","pt-br","pt-pt","ru","sk","sl","sv","th","tr","xx","zh","zh-cn","zh-tw"]);