require({cache:{
'dojox/main':function(){
define(["dojo/_base/kernel"], function(dojo) {
// module:
// dojox/main
/*=====
return {
// summary:
// The dojox package main module; dojox package is somewhat unusual in that the main module currently just provides an empty object.
// Apps should require modules from the dojox packages directly, rather than loading this module.
};
=====*/
return dojo.dojox;
});
},
'dojox/grid/_Grid':function(){
define([
"dojo/_base/kernel",
"../main",
"dojo/_base/declare",
"./_Events",
"./_Scroller",
"./_Layout",
"./_View",
"./_ViewManager",
"./_RowManager",
"./_FocusManager",
"./_EditManager",
"./Selection",
"./_RowSelector",
"./util",
"dijit/_Widget",
"dijit/_TemplatedMixin",
"dijit/CheckedMenuItem",
"dojo/text!./resources/_Grid.html",
"dojo/string",
"dojo/_base/array",
"dojo/_base/lang",
"dojo/_base/sniff",
"dojox/html/metrics",
"dojo/_base/html",
"dojo/query",
"dojo/dnd/common",
"dojo/i18n!dijit/nls/loading"
], function(dojo, dojox, declare, _Events, _Scroller, _Layout, _View, _ViewManager,
_RowManager, _FocusManager, _EditManager, Selection, _RowSelector, util, _Widget,
_TemplatedMixin, CheckedMenuItem, template, string, array, lang, has, metrics, html, query){
// NOTE: this is for backwards compatibility with Dojo 1.3
if(!dojo.isCopyKey){
dojo.isCopyKey = dojo.dnd.getCopyKeyState;
}
/*=====
dojox.grid.__CellDef = {
// 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.
};
=====*/
/*=====
dojox.grid.__ViewDef = {
// 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.
};
=====*/
var _Grid = declare('dojox.grid._Grid',
[ _Widget, _TemplatedMixin, _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 }]
// | ]}
// | ];
//
// |
templateString: template,
// classTag: String
// CSS class applied to the grid's domNode
classTag: 'dojoxGrid',
// 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 definition.
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: "${loadingState}",
// errorMessage: String
// Message that shows when the grid encounters an error loading
errorMessage: "${errorState}",
// 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,
// summary: String
// Customizable summary descriptions which will be added to grid.domNode
summary: '',
_setSummaryAttr: 'domNode',
// sortInfo: [private] Number
sortInfo: 0,
// _placeholders: [private] Array
_placeholders: null,
// _layoutClass: Object
// The class to use for our layout - can be overridden by grid subclasses
_layoutClass: _Layout,
// initialization
buildRendering: function(){
this.inherited(arguments);
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");
metrics.initOnFontResize();
this.connect(metrics, "onFontResize", "textSizeChanged");
util.funnelEvents(this.domNode, this, 'doKeyEvent', util.keyEvents);
if (this.selectionMode != "none") {
this.domNode.setAttribute("aria-multiselectable", this.selectionMode == "single" ? "false" : "true");
}
html.addClass(this.domNode, this.classTag);
if(!this.isLeftToRight()){
html.addClass(this.domNode, this.classTag+"Rtl");
}
if(this.rowHeight > 0){
html.addClass(this.viewsNode, this.classTag + "FixedRowHeight");
}
},
postMixInProperties: function(){
this.inherited(arguments);
var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
this.loadingMessage = string.substitute(this.loadingMessage, messages);
this.errorMessage = 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
html.attr(this.domNode,"aria-readonly", "true");
}
},
destroy: function(){
this.domNode.onReveal = null;
this.domNode.onSizeChange = null;
// Fixes IE domNode leak
delete this._click;
if(this.scroller){
this.scroller.destroy();
delete this.scroller;
}
this.edit.destroy();
delete this.edit;
this.views.destroyViews();
if(this.focus){
this.focus.destroy();
delete this.focus;
}
if(this.headerMenu&&this._placeholders.length){
array.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.get('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 _RowManager(this);
// focus manager
this.focus = new _FocusManager(this);
// edit manager
this.edit = new _EditManager(this);
},
createSelection: function(){
// summary: Creates a new Grid selection manager.
// selection manager
this.selection = new Selection(this);
},
createScroller: function(){
// summary:
// Creates a new virtual scroller
this.scroller = new _Scroller();
this.scroller.grid = this;
this.scroller.renderRow = lang.hitch(this, "renderRow");
this.scroller.removeRow = lang.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.update();
},
onResizeColumn: function(/*int*/ cellIdx){
// Called when a column is resized.
},
// views
createViews: function(){
this.views = new _ViewManager(this);
this.views.createView = lang.hitch(this, "createView");
},
createView: function(inClass, idx){
var c = lang.getObject(inClass);
var view = new c({ grid: this, index: idx });
this.viewsNode.appendChild(view.domNode);
this.viewsHeaderNode.appendChild(view.headerNode);
this.views.addView(view);
html.attr(this.domNode, "align", this.isLeftToRight() ? 'left' : 'right');
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 && lang.isString(s)){
dojo.deprecated("dojox.grid._Grid.set('structure', 'objVar')", "use dojox.grid._Grid.set('structure', objVar) instead", "2.0");
s=lang.getObject(s);
}
this.structure = s;
if(!s){
if(this.layout.structure){
s = this.layout.structure;
}else{
return;
}
}
this.views.destroyViews();
this.focus.focusView = null;
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.set('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.
var items, checkedItems = [];
items = array.map(this.layout.cells, function(cell){
if(!cell.menuItems){ cell.menuItems = []; }
var self = this;
var item = new 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){
array.forEach(items, function(item){
if(item !== this){
item.setAttribute("checked", checked);
}
}, this);
}
checked = array.filter(self.layout.cells, function(c){
if(c.menuItems.length > 1){
array.forEach(c.menuItems, "item.set('disabled', false);");
}else{
c.menuItems[0].set('disabled', false);
}
return !c.hidden;
});
if(checked.length == 1){
array.forEach(checked[0].menuItems, "item.set('disabled', true);");
}
}
},
destroy: function(){
var index = array.indexOf(this._gridCell.menuItems, this);
this._gridCell.menuItems.splice(index, 1);
delete this._gridCell;
CheckedMenuItem.prototype.destroy.apply(this, arguments);
}
});
cell.menuItems.push(item);
if(!cell.hidden) {
checkedItems.push(item);
}
return item;
}, this); // dijit.CheckedMenuItem[]
if(checkedItems.length == 1) {
checkedItems[0].set('disabled', true);
}
return items;
},
_setHeaderMenuAttr: function(menu){
if(this._placeholders && this._placeholders.length){
array.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.set('headerMenu', obj) instead.", "2.0");
this._setHeaderMenuAttr(menu);
},
setupHeaderMenu: function(){
if(this._placeholders && this._placeholders.length){
array.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 || html._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';
}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(!this._autoHeight && changeSize){
html.marginBox(this.domNode, changeSize);
this.height = this.domNode.style.height;
delete this.fitTo;
}else if(this.fitTo == "parent"){
h = this._parentContentBoxHeight = (this._parentContentBoxHeight > 0 ? this._parentContentBoxHeight : html._getContentBox(pn).h);
this.domNode.style.height = Math.max(0, h) + "px";
}
var hasFlex = array.some(this.views.views, function(v){ return v.flexCells; });
if(!this._autoHeight && (h || html._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() {
// summary:
// sets width and position for views and update grid width if necessary
// tags:
// private
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){
// summary:
// measures and normalizes header height, then sets view heights, and then updates scroller
// content extent
// tags:
// private
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 = array.filter(this.views.views, function(v){
var has = v.hasHScrollbar();
if(has){ numScroll++; }else{ numNoScroll++; }
return (!has);
});
if(numScroll > 0 && numNoScroll > 0){
array.forEach(noScrolls, function(v){
v.adaptHeight(true);
});
}
}
if(this.autoHeight === true || h != -1 || (typeof this.autoHeight == "number" && this.autoHeight >= this.get('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.get('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
html.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:
// used internally to render rows
// tags:
// private
this.views.renderRow(inRowIndex, inNodes, this._skipRowRenormalize);
},
rowRemoved: function(inRowIndex){
// summary:
// used internally to remove rows
// tags:
// private
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 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);
},
styleRowNode: function(inRowIndex, inRowNode){
// summary:
// styling (used internally to style individual parts of a row)
// tags:
// private
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.get('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.get('rowCount') - this.selection.getSelected().length));
}
this.selection.clear();
}
});
_Grid.markupFactory = function(props, node, ctor, cellFunc){
var widthFromAttr = function(n){
var w = html.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 = query("> colgroup", node).map(function(cg){
var sv = html.attr(cg, "span");
var v = {
noscroll: (html.attr(cg, "noscroll") == "true") ? true : false,
__span: (!!sv ? parseInt(sv, 10) : 1),
cells: []
};
if(html.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
query("thead > tr", node).forEach(function(tr, tr_idx){
var cellCount = 0;
var viewIdx = 0;
var lastViewIdx;
var cView = null;
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
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: lang.trim(html.attr(th, "name")||th.innerHTML),
colSpan: parseInt(html.attr(th, "colspan")||1, 10),
type: lang.trim(html.attr(th, "cellType")||""),
id: lang.trim(html.attr(th,"id")||"")
};
cellCount += cell.colSpan;
var rowSpan = html.attr(th, "rowspan");
if(rowSpan){
cell.rowSpan = rowSpan;
}
if(html.hasAttr(th, "width")){
cell.width = widthFromAttr(th);
}
if(html.hasAttr(th, "relWidth")){
cell.relWidth = window.parseInt(html.attr(th, "relWidth"), 10);
}
if(html.hasAttr(th, "hidden")){
cell.hidden = (html.attr(th, "hidden") == "true" || html.attr(th, "hidden") === true/*always boolean true in Chrome*/);
}
if(cellFunc){
cellFunc(th, cell);
}
cell.type = cell.type ? lang.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);
};
return _Grid;
});
},
'dojox/grid/_Events':function(){
define([
"dojo/keys",
"dojo/dom-class",
"dojo/_base/declare",
"dojo/_base/event",
"dojo/_base/sniff"
], function(keys, domClass, declare, event, has){
return 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 colIdx;
switch(e.keyCode){
case keys.ESCAPE:
this.edit.cancel();
break;
case keys.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);
}
event.stop(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);
event.stop(e);
}
break;
case keys.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);
// Set focus back on the cell they were on for keyboard accessibility
this.focus._focusifyCellNode(true);
this.focus.setFocusCell(this.focus.cell, this.focus.rowIndex); }
event.stop(e);
}
break;
case keys.TAB:
this.focus[e.shiftKey ? 'previousKey' : 'nextKey'](e);
break;
case keys.LEFT_ARROW:
case keys.RIGHT_ARROW:
if(!this.edit.isEditing()){
var keyCode = e.keyCode; // IE seems to lose after stopEvent when modifier keys
event.stop(e);
colIdx = this.focus.getHeaderIndex();
if (colIdx >= 0 && (e.shiftKey && e.ctrlKey)){
this.focus.colSizeAdjust(e, colIdx, (keyCode == keys.LEFT_ARROW ? -1 : 1)*5);
}
else{
var offset = (keyCode == keys.LEFT_ARROW) ? 1 : -1;
if(this.isLeftToRight()){ offset *= -1; }
this.focus.move(0, offset);
}
}
break;
case keys.UP_ARROW:
if(!this.edit.isEditing() && this.focus.rowIndex !== 0){
event.stop(e);
this.focus.move(-1, 0);
}
break;
case keys.DOWN_ARROW:
if(!this.edit.isEditing() && this.focus.rowIndex+1 != this.rowCount){
event.stop(e);
this.focus.move(1, 0);
}
break;
case keys.PAGE_UP:
if(!this.edit.isEditing() && this.focus.rowIndex !== 0){
event.stop(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 keys.PAGE_DOWN:
if(!this.edit.isEditing() && this.focus.rowIndex+1 != this.rowCount){
event.stop(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){
domClass.add(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){
domClass.remove(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);
}
// in some cases click[0] is null which causes false doubeClicks. Fixes #100703
if(this._click.length > 1 && this._click[0] == null){
this._click.shift();
}
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
var event;
if(this._click.length > 1 && has('ie')){
event = this._click[1];
}else if(this._click.length > 1 && this._click[0].rowIndex != this._click[1].rowIndex){
event = this._click[0];
}else{
event = e;
}
this.focus.setFocusCell(event.cell, event.rowIndex);
this.edit.setEditCell(event.cell, event.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
event.stop(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){
domClass.add(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){
domClass.remove(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){
event.stop(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(){
}
});
});
},
'dojox/grid/_Scroller':function(){
define([
"dijit/registry",
"dojo/_base/declare",
"dojo/_base/lang",
"./util",
"dojo/_base/html"
], function(dijitRegistry, declare, lang, util, html){
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;
}
dojo.forEach(dijitRegistry.toArray(), function(w){
if(w.domNode && html.isDescendant(w.domNode, inNode, true)){
w.destroy();
}
});
};
var getTagName = function(inNodeOrId){
var node = html.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');
};
return 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.grid.rowHeight > 0 ? this.grid.rowHeight : 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 = lang.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.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){
return ((inPageIndex + 1) * this.rowsPerPage > this.rowCount ?
this.rowCount - inPageIndex * this.rowsPerPage :
this.rowsPerPage) * this.grid.rowHeight;
}
var n = this.getDefaultPageNode(inPageIndex);
return (n && n.innerHTML) ? n.offsetHeight : undefined;
},
positionPage: function(inPageIndex, inPos){
for(var i=0; i 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 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, fromAsynRendering){
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.get("autoHeight");
if((typeof ah == "number" && ah > this.rowCount)||(ah === true && !fromBuild)){
if(!fromAsynRendering){
this.grid.sizeChange();
}else{//fix #11101 by using fromAsynRendering to avoid deadlock
var ns = this.grid.viewsNode.style;
ns.height = parseInt(ns.height) + h - oh + 'px';
this.repositionPages(inPageIndex);
}
}else{
this.repositionPages(inPageIndex);
}
}
return h;
}
return 0;
},
rowHeightChanged: function(inRowIndex, fromAsynRendering){
this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage), false, fromAsynRendering);
},
// scroller core
invalidateNodes: function(){
while(this.stack.length){
this.destroyPage(this.popPage());
}
},
createPageNode: function(){
var p = document.createElement('div');
html.attr(p,"role","presentation");
p.style.position = 'absolute';
//p.style.width = '100%';
p.style[this.grid.isLeftToRight() ? "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= 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.grid._autoHeight/*fix #10543*/ && 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= 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=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= 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
// even though they are logically children of other widgets.
if(!widget._destroyed){
if(widget.destroyRecursive){
widget.destroyRecursive();
}else if(widget.destroy){
widget.destroy();
}
}
});
},
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.nodeType == 1 && node.getAttribute("widgetId");
if(id){
return hash[id];
}
node = node.parentNode;
}
return null;
},
// In case someone needs to access hash.
// Actually, this is accessed from WidgetSet back-compatibility code
_hash: hash
};
dijit.registry = registry;
return registry;
});
},
'dijit/main':function(){
define([
"dojo/_base/kernel"
], function(dojo){
// module:
// dijit/main
/*=====
return {
// summary:
// The dijit package main module.
// Deprecated. Users should access individual modules (ex: dijit/registry) directly.
};
=====*/
return dojo.dijit;
});
},
'dojox/grid/util':function(){
define([
"../main",
"dojo/_base/lang",
"dojo/dom",
"dojo/_base/sniff"
], function(dojox, lang, dom, has){
var dgu = lang.getObject("grid.util", true, dojox);
/*=====
dgu = {
// summary:
// grid utility library
};
=====*/
dgu.na = '...';
dgu.rowIndexTag = "gridRowIndex";
dgu.gridViewTag = "gridView";
dgu.fire = function(ob, ev, args){
// Find parent node that scrolls, either vertically or horizontally
function getScrollParent(node, horizontal){
if(node == null) {
return null;
}
var dimension = horizontal ? 'Width' : 'Height';
if(node['scroll' + dimension] > node['client' + dimension]){
return node;
}else{
return getScrollParent(node.parentNode, horizontal);
}
}
// In Webkit browsers focusing an element will scroll this element into view.
// This may even happen if the element already is in view, but near the edge.
// This may move the element away from the mouse cursor on the first click
// of a double click and you end up hitting a different element.
// Avoid this by storing the scroll position and restoring it after focusing.
var verticalScrollParent, horizontalScrollParent, scrollTop, scrollLeft, obNode;
if(has("webkit") && (ev == "focus")){
obNode = ob.domNode ? ob.domNode : ob;
verticalScrollParent = getScrollParent(obNode, false);
if(verticalScrollParent){
scrollTop = verticalScrollParent.scrollTop;
}
horizontalScrollParent = getScrollParent(obNode, true);
if(horizontalScrollParent){
scrollLeft = horizontalScrollParent.scrollLeft;
}
}
var fn = ob && ev && ob[ev];
var result = fn && (args ? fn.apply(ob, args) : ob[ev]());
// Restore scrolling position
if(has("webkit") && (ev == "focus")){
if(verticalScrollParent){
verticalScrollParent.scrollTop = scrollTop;
}
if(horizontalScrollParent){
horizontalScrollParent.scrollLeft = scrollLeft;
}
}
return result;
};
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 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 = lang.clone(inDef);
props.unitWidth = getCellWidth(new_cell._props);
new_cell = lang.mixin(new_cell, this._defaultCellProps, inDef._props, props);
return new_cell;
}
var cell_type = inDef.type || inDef.cellType || this._defaultCellProps.type || this._defaultCellProps.cellType || dojox.grid.cells.Cell;
if(lang.isString(cell_type)){
cell_type = lang.getObject(cell_type);
}
props.unitWidth = getCellWidth(inDef);
return new cell_type(lang.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 %
array.forEach(result, function(cell){
if(cell.relWidth){
cell.width = cell.unitWidth = ((cell.relWidth / relSum) * (100 - pctSum)) + "%";
}
});
}
return result;
},
addRowsDef: function(inDef){
var result = [];
if(lang.isArray(inDef)){
// inDef[0] could be a NodeList if the Grid is defined in a declarative way.
// lang.isArray() does not recognize a NodeList as an array, now so the wrong path will be chosen.
// lang.isArrayLike() does the right match against a NodeList, instead.
if(lang.isArrayLike(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 lang.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(lang.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(lang.isArray(def)){
if(lang.isArray(def[0]) || isCell(def[0])){
return true;
}
}
return false;
};
var isView = function(def){
return (def !== null && lang.isObject(def) &&
("cells" in def || "rows" in def || ("type" in def && !isCell(def))));
};
if(lang.isArrayLike(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();
}
});
});
},
'dojox/grid/cells':function(){
define(["../main", "./cells/_base"], function(dojox){
return dojox.grid.cells;
});
},
'dojox/grid/cells/_base':function(){
define([
"dojo/_base/kernel",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/_base/event",
"dojo/_base/connect",
"dojo/_base/array",
"dojo/_base/sniff",
"dojo/dom",
"dojo/dom-attr",
"dojo/dom-construct",
"dijit/_Widget",
"../util"
], function(dojo, declare, lang, event, connect, array, has, dom, domAttr, domConstruct, _Widget, util){
var _DeferredTextWidget = declare("dojox.grid._DeferredTextWidget", _Widget, {
deferred: null,
_destroyOnRemove: true,
postCreate: function(){
if(this.deferred){
this.deferred.addBoth(lang.hitch(this, function(text){
if(this.domNode){
this.domNode.innerHTML = text;
}
}));
}
}
});
var focusSelectNode = function(inNode){
try{
util.fire(inNode, "focus");
util.fire(inNode, "select");
}catch(e){// IE sux bad
}
};
var whenIdle = function(/*inContext, inMethod, args ...*/){
setTimeout(lang.hitch.apply(dojo, arguments), 0);
};
var BaseCell = declare("dojox.grid.cells._Base", null, {
// summary:
// Represents 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 || {};
lang.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 _DeferredTextWidget({deferred: v},
domConstruct.create("span", {innerHTML: this.defaultValue}));
}
if(v && v.declaredClass && v.startup){
return "
" +
this.defaultValue +
"
";
}
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 i = this.grid.edit.info;
var d = this.get ? this.get(inRowIndex, inItem) : (this.value || this.defaultValue);
if (d && d.replace && this.grid.escapeHTMLInData) {
d = d.replace(/&/g, '&').replace(/';
},
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){
event.stop(e);
}
}
},
_finish: function(inRowIndex){
this.inherited(arguments);
var n = this.getEditNode(inRowIndex);
try{
util.fire(n, "blur");
}catch(e){}
}
});
Cell.markupFactory = function(node, cellDef){
BaseCell.markupFactory(node, cellDef);
var keyFilter = lang.trim(domAttr.get(node, "keyFilter")||"");
if(keyFilter){
cellDef.keyFilter = new RegExp(keyFilter);
}
};
var RowIndex = BaseCell.RowIndex = declare("dojox.grid.cells.RowIndex", Cell, {
name: 'Row',
postscript: function(){
this.editable = false;
},
get: function(inRowIndex){
return inRowIndex + 1;
}
});
RowIndex.markupFactory = function(node, cellDef){
Cell.markupFactory(node, cellDef);
};
var Select = BaseCell.Select = declare("dojox.grid.cells.Select", 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 = [ '');
return h.join('');
},
_defaultFormat: function(inValue, callArgs){
var v = this.inherited(arguments);
// when 'values' and 'options' both provided and there is no cutomized formatter,
// then we use 'options' as label in order to be consistent
if(!this.formatter && this.values && this.options){
var i = array.indexOf(this.values, v);
if(i >= 0){
v = this.options[i];
}
}
return v;
},
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;
}
}
});
Select.markupFactory = function(node, cell){
Cell.markupFactory(node, cell);
var options = lang.trim(domAttr.get(node, "options")||"");
if(options){
var o = options.split(',');
if(o[0] != options){
cell.options = o;
}
}
var values = lang.trim(domAttr.get(node, "values")||"");
if(values){
var v = values.split(',');
if(v[0] != values){
cell.values = v;
}
}
};
var AlwaysEdit = BaseCell.AlwaysEdit = declare("dojox.grid.cells.AlwaysEdit", 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);
}
});
AlwaysEdit.markupFactory = function(node, cell){
Cell.markupFactory(node, cell);
};
var Bool = BaseCell.Bool = declare("dojox.grid.cells.Bool", AlwaysEdit, {
// summary:
// grid cell that provides a standard checkbox that is always on for editing
_valueProp: "checked",
formatEditing: function(inDatum, inRowIndex){
return '';
},
doclick: function(e){
if(e.target.tagName == 'INPUT'){
this.applyStaticValue(e.rowIndex);
}
}
});
Bool.markupFactory = function(node, cell){
AlwaysEdit.markupFactory(node, cell);
};
return BaseCell;
});
},
'dijit/_Widget':function(){
define([
"dojo/aspect", // aspect.around
"dojo/_base/config", // config.isDebug
"dojo/_base/connect", // connect.connect
"dojo/_base/declare", // declare
"dojo/has",
"dojo/_base/kernel", // kernel.deprecated
"dojo/_base/lang", // lang.hitch
"dojo/query",
"dojo/ready",
"./registry", // registry.byNode
"./_WidgetBase",
"./_OnDijitClickMixin",
"./_FocusMixin",
"dojo/uacss", // browser sniffing (included for back-compat; subclasses may be using)
"./hccss" // high contrast mode sniffing (included to set CSS classes on , module ret value unused)
], function(aspect, config, connect, declare, has, kernel, lang, query, ready,
registry, _WidgetBase, _OnDijitClickMixin, _FocusMixin){
// module:
// dijit/_Widget
function connectToDomNode(){
// summary:
// If user connects to a widget method === this function, then they will
// instead actually be connecting the equivalent event on this.domNode
}
// Trap dojo.connect() calls to connectToDomNode methods, and redirect to _Widget.on()
function aroundAdvice(originalConnect){
return function(obj, event, scope, method){
if(obj && typeof event == "string" && obj[event] == connectToDomNode){
return obj.on(event.substring(2).toLowerCase(), lang.hitch(scope, method));
}
return originalConnect.apply(connect, arguments);
};
}
aspect.around(connect, "connect", aroundAdvice);
if(kernel.connect){
aspect.around(kernel, "connect", aroundAdvice);
}
var _Widget = declare("dijit._Widget", [_WidgetBase, _OnDijitClickMixin, _FocusMixin], {
// summary:
// Old base class for widgets. New widgets should extend `dijit/_WidgetBase` instead
// description:
// Old Base class for Dijit widgets.
//
// Extends _WidgetBase, adding support for:
//
// - declaratively/programatically specifying widget initialization parameters like
// onMouseMove="foo" that call foo when this.domNode gets a mousemove event
// - ondijitclick:
// Support new data-dojo-attach-event="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress
// - focus related functions:
// In particular, the onFocus()/onBlur() callbacks. Driven internally by
// dijit/_base/focus.js.
// - deprecated methods
// - onShow(), onHide(), onClose()
//
// Also, by loading code in dijit/_base, turns on:
//
// - browser sniffing (putting browser class like `dj_ie` on `` node)
// - high contrast mode sniffing (add `dijit_a11y` class to `` if machine is in high contrast mode)
////////////////// DEFERRED CONNECTS ///////////////////
onClick: connectToDomNode,
/*=====
onClick: function(event){
// summary:
// Connect to this function to receive notifications of mouse click events.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onDblClick: connectToDomNode,
/*=====
onDblClick: function(event){
// summary:
// Connect to this function to receive notifications of mouse double click events.
// event:
// mouse Event
// tags:
// callback
},
=====*/
onKeyDown: connectToDomNode,
/*=====
onKeyDown: function(event){
// summary:
// Connect to this function to receive notifications of keys being pressed down.
// event:
// key Event
// tags:
// callback
},
=====*/
onKeyPress: connectToDomNode,
/*=====
onKeyPress: function(event){
// summary:
// Connect to this function to receive notifications of printable keys being typed.
// event:
// key Event
// tags:
// callback
},
=====*/
onKeyUp: connectToDomNode,
/*=====
onKeyUp: function(event){
// summary:
// Connect to this function to receive notifications of keys being released.
// event:
// key Event
// tags:
// callback
},
=====*/
onMouseDown: connectToDomNode,
/*=====
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: connectToDomNode,
/*=====
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: connectToDomNode,
/*=====
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: connectToDomNode,
/*=====
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: connectToDomNode,
/*=====
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: connectToDomNode,
/*=====
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: connectToDomNode,
/*=====
onMouseUp: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse button is released.
// event:
// mouse Event
// tags:
// callback
},
=====*/
constructor: function(params /*===== ,srcNodeRef =====*/){
// summary:
// Create the widget.
// params: Object|null
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
// and functions, typically callbacks like onClick.
// The hash can contain any of the widget's properties, excluding read-only properties.
// srcNodeRef: DOMNode|String?
// 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
// extract parameters like onMouseMove that should connect directly to this.domNode
this._toConnect = {};
for(var name in params){
if(this[name] === connectToDomNode){
this._toConnect[name.replace(/^on/, "").toLowerCase()] = params[name];
delete params[name];
}
}
},
postCreate: function(){
this.inherited(arguments);
// perform connection from this.domNode to user specified handlers (ex: onMouseMove)
for(var name in this._toConnect){
this.on(name, this._toConnect[name]);
}
delete this._toConnect;
},
on: function(/*String|Function*/ type, /*Function*/ func){
if(this[this._onMap(type)] === connectToDomNode){
// Use connect.connect() rather than on() to get handling for "onmouseenter" on non-IE,
// normalization of onkeypress/onkeydown to behave like firefox, etc.
// Also, need to specify context as "this" rather than the default context of the DOMNode
// Remove in 2.0.
return connect.connect(this.domNode, type.toLowerCase(), this, func);
}
return this.inherited(arguments);
},
_setFocusedAttr: function(val){
// Remove this method in 2.0 (or sooner), just here to set _focused == focused, for back compat
// (but since it's a private variable we aren't required to keep supporting it).
this._focused = val;
this._set("focused", val);
},
////////////////// DEPRECATED METHODS ///////////////////
setAttribute: function(/*String*/ attr, /*anything*/ value){
// summary:
// Deprecated. Use set() instead.
// tags:
// deprecated
kernel.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
this.set(attr, value);
},
attr: function(/*String|Object*/name, /*Object?*/value){
// summary:
// This method is deprecated, use get() or set() directly.
// 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.
// tags:
// deprecated
var args = arguments.length;
if(args >= 2 || typeof name === "object"){ // setter
return this.set.apply(this, arguments);
}else{ // getter
return this.get(name);
}
},
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.
kernel.deprecated(this.declaredClass+"::getDescendants() is deprecated. Use getChildren() instead.", "", "2.0");
return this.containerNode ? query('[widgetId]', this.containerNode).map(registry.byNode) : []; // dijit/_WidgetBase[]
},
////////////////// MISCELLANEOUS METHODS ///////////////////
_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
},
onClose: function(){
// summary:
// Called when this widget is being displayed as a popup (ex: a Calendar popped
// up from a DateTextBox), and it is hidden.
// This is called from the dijit.popup code, and should not be called directly.
//
// Also used as a parameter for children of `dijit/layout/StackContainer` or subclasses.
// Callback if a user tries to close the child. Child will be closed if this function returns true.
// tags:
// extension
return true; // Boolean
}
});
// For back-compat, remove in 2.0.
if(has("dijit-legacy-requires")){
ready(0, function(){
var requires = ["dijit/_base"];
require(requires); // use indirection so modules not rolled into a build
});
}
return _Widget;
});
},
'dijit/_WidgetBase':function(){
define([
"require", // require.toUrl
"dojo/_base/array", // array.forEach array.map
"dojo/aspect",
"dojo/_base/config", // config.blankGif
"dojo/_base/connect", // connect.connect
"dojo/_base/declare", // declare
"dojo/dom", // dom.byId
"dojo/dom-attr", // domAttr.set domAttr.remove
"dojo/dom-class", // domClass.add domClass.replace
"dojo/dom-construct", // domConstruct.destroy domConstruct.place
"dojo/dom-geometry", // isBodyLtr
"dojo/dom-style", // domStyle.set, domStyle.get
"dojo/has",
"dojo/_base/kernel",
"dojo/_base/lang", // mixin(), isArray(), etc.
"dojo/on",
"dojo/ready",
"dojo/Stateful", // Stateful
"dojo/topic",
"dojo/_base/window", // win.body()
"./Destroyable",
"dojo/has!dojo-bidi?./_BidiMixin",
"./registry" // registry.getUniqueId(), registry.findWidgets()
], function(require, array, aspect, config, connect, declare,
dom, domAttr, domClass, domConstruct, domGeometry, domStyle, has, kernel,
lang, on, ready, Stateful, topic, win, Destroyable, _BidiMixin, registry){
// module:
// dijit/_WidgetBase
// Flag to make dijit load modules the app didn't explicitly request, for backwards compatibility
has.add("dijit-legacy-requires", !kernel.isAsync);
// Flag to enable support for textdir attribute
has.add("dojo-bidi", false);
// For back-compat, remove in 2.0.
if(has("dijit-legacy-requires")){
ready(0, function(){
var requires = ["dijit/_base/manager"];
require(requires); // use indirection so modules not rolled into a build
});
}
// Nested hash listing attributes for each tag, all strings in lowercase.
// ex: {"div": {"style": true, "tabindex" true}, "form": { ...
var tagAttrs = {};
function getAttrs(obj){
var ret = {};
for(var attr in obj){
ret[attr.toLowerCase()] = true;
}
return ret;
}
function nonEmptyAttrToDom(attr){
// summary:
// Returns a setter function that copies the attribute to this.domNode,
// or removes the attribute from this.domNode, depending on whether the
// value is defined or not.
return function(val){
domAttr[val ? "set" : "remove"](this.domNode, attr, val);
this._set(attr, val);
};
}
function isEqual(a, b){
// summary:
// Function that determines whether two values are identical,
// taking into account that NaN is not normally equal to itself
// in JS.
return a === b || (/* a is NaN */ a !== a && /* b is NaN */ b !== b);
}
var _WidgetBase = declare("dijit._WidgetBase", [Stateful, Destroyable], {
// summary:
// Future base class for all Dijit widgets.
// description:
// Future base class for all Dijit widgets.
// _Widget extends this class adding support for various features needed by desktop.
//
// Provides stubs for widget lifecycle methods for subclasses to extend, like postMixInProperties(), buildRendering(),
// postCreate(), startup(), and destroy(), and also public API methods like set(), get(), and watch().
//
// Widgets can provide custom setters/getters for widget attributes, which are called automatically by set(name, value).
// For an attribute XXX, define methods _setXXXAttr() and/or _getXXXAttr().
//
// _setXXXAttr can also be a string/hash/array mapping from a widget attribute XXX to the widget's DOMNodes:
//
// - DOM node attribute
// | _setFocusAttr: {node: "focusNode", type: "attribute"}
// | _setFocusAttr: "focusNode" (shorthand)
// | _setFocusAttr: "" (shorthand, maps to this.domNode)
// Maps this.focus to this.focusNode.focus, or (last example) this.domNode.focus
//
// - DOM node innerHTML
// | _setTitleAttr: { node: "titleNode", type: "innerHTML" }
// Maps this.title to this.titleNode.innerHTML
//
// - DOM node innerText
// | _setTitleAttr: { node: "titleNode", type: "innerText" }
// Maps this.title to this.titleNode.innerText
//
// - DOM node CSS class
// | _setMyClassAttr: { node: "domNode", type: "class" }
// Maps this.myClass to this.domNode.className
//
// - Toggle DOM node CSS class
// | _setMyClassAttr: { node: "domNode", type: "toggleClass" }
// Toggles myClass on this.domNode by this.myClass
//
// If the value of _setXXXAttr is an array, then each element in the array matches one of the
// formats of the above list.
//
// If the custom setter is null, no action is performed other than saving the new value
// in the widget (in this).
//
// If no custom setter is defined for an attribute, then it will be copied
// to this.focusNode (if the widget defines a focusNode), or this.domNode otherwise.
// That's only done though for attributes that match DOMNode attributes (title,
// alt, aria-labelledby, etc.)
// 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: "",
_setIdAttr: "domNode", // to copy to this.domNode even for auto-generated id's
// 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: "",
// set on domNode even when there's a focus node. but don't set lang="", since that's invalid.
_setLangAttr: nonEmptyAttrToDom("lang"),
// dir: [const] String
// 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". If undefined, widgets renders in page's
// default direction.
dir: "",
// set on domNode even when there's a focus node. but don't set dir="", since that's invalid.
_setDirAttr: nonEmptyAttrToDom("dir"), // to set on domNode even when there's a focus node
// class: String
// HTML class attribute
"class": "",
_setClassAttr: { node: "domNode", type: "class" },
// Override automatic assigning type --> focusNode, it causes exception on IE6-8.
// Instead, type must be specified as ${type} in the template, as part of the original DOM.
_setTypeAttr: null,
// 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. In this case it's
// interpreted as HTML.
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: "",
// baseClass: [protected] String
// Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
// widget state.
baseClass: "",
// 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 data-dojo-attach-point 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:
//
// |
// | here's a plain DOM node
// | and a widget
// | and another plain DOM node
// |
//
// containerNode would point to:
//
// | here's a plain DOM node
// | and a widget
// | and another plain DOM node
//
// In templated widgets, "containerNode" is set via a
// data-dojo-attach-point 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,
// ownerDocument: [const] Document?
// The document this widget belongs to. If not specified to constructor, will default to
// srcNodeRef.ownerDocument, or if no sourceRef specified, then to the document global
ownerDocument: null,
_setOwnerDocumentAttr: function(val){
// this setter is merely to avoid automatically trying to set this.domNode.ownerDocument
this._set("ownerDocument", val);
},
/*=====
// _started: [readonly] Boolean
// startup() has completed.
_started: false,
=====*/
// attributeMap: [protected] Object
// Deprecated. Instead of attributeMap, widget should have a _setXXXAttr attribute
// for each XXX attribute to be mapped to the DOM.
//
// 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 set('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: {},
// _blankGif: [protected] String
// Path to a blank 1x1 image.
// Used by `` nodes in templates that really get their image via CSS background-image.
_blankGif: config.blankGif || require.toUrl("dojo/resources/blank.gif"),
// textDir: String
// Bi-directional support, the main variable which is responsible for the direction of the text.
// The text direction can be different than the GUI direction by using this parameter in creation
// of a widget.
//
// This property is only effective when `has("dojo-bidi")` is defined to be true.
//
// Allowed values:
//
// 1. "" - default value; text is same direction as widget
// 2. "ltr"
// 3. "rtl"
// 4. "auto" - contextual the direction of a text defined by first strong letter.
textDir: "",
//////////// INITIALIZATION METHODS ///////////////////////////////////////
/*=====
constructor: function(params, srcNodeRef){
// summary:
// Create the widget.
// params: Object|null
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
// and functions, typically callbacks like onClick.
// The hash can contain any of the widget's properties, excluding read-only properties.
// srcNodeRef: DOMNode|String?
// 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
},
=====*/
_introspect: function(){
// summary:
// Collect metadata about this widget (only once per class, not once per instance):
//
// - list of attributes with custom setters, storing in this.constructor._setterAttrs
// - generate this.constructor._onMap, mapping names like "mousedown" to functions like onMouseDown
var ctor = this.constructor;
if(!ctor._setterAttrs){
var proto = ctor.prototype,
attrs = ctor._setterAttrs = [], // attributes with custom setters
onMap = (ctor._onMap = {});
// Items in this.attributeMap are like custom setters. For back-compat, remove for 2.0.
for(var name in proto.attributeMap){
attrs.push(name);
}
// Loop over widget properties, collecting properties with custom setters and filling in ctor._onMap.
for(name in proto){
if(/^on/.test(name)){
onMap[name.substring(2).toLowerCase()] = name;
}
if(/^_set[A-Z](.*)Attr$/.test(name)){
name = name.charAt(4).toLowerCase() + name.substr(5, name.length - 9);
if(!proto.attributeMap || !(name in proto.attributeMap)){
attrs.push(name);
}
}
}
// Note: this isn't picking up info on properties like aria-label and role, that don't have custom setters
// but that set() maps to attributes on this.domNode or this.focusNode
}
},
postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
// summary:
// Kicks off widget instantiation. See create() for details.
// tags:
// private
// Note that we skip calling this.inherited(), i.e. dojo/Stateful::postscript(), because 1.x widgets don't
// expect their custom setters to get called until after buildRendering(). Consider changing for 2.0.
this.create(params, srcNodeRef);
},
create: function(params, srcNodeRef){
// summary:
// Kick off the life-cycle of a widget
// description:
// Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
// etc.), some of which of you'll want to override. See http://dojotoolkit.org/reference-guide/dijit/_WidgetBase.html
// 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.
// params: Object|null
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
// and functions, typically callbacks like onClick.
// The hash can contain any of the widget's properties, excluding read-only properties.
// srcNodeRef: DOMNode|String?
// 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
// tags:
// private
// First time widget is instantiated, scan prototype to figure out info about custom setters etc.
this._introspect();
// store pointer to original DOM tree
this.srcNodeRef = dom.byId(srcNodeRef);
// No longer used, remove for 2.0.
this._connects = [];
this._supportingWidgets = [];
// this is here for back-compat, remove in 2.0 (but check NodeList-instantiate.html test)
if(this.srcNodeRef && this.srcNodeRef.id && (typeof this.srcNodeRef.id == "string")){
this.id = this.srcNodeRef.id;
}
// mix in our passed parameters
if(params){
this.params = params;
lang.mixin(this, params);
}
this.postMixInProperties();
// Generate an id for the widget if one wasn't specified, or it was specified as id: undefined.
// Do this before buildRendering() because it might expect the id to be there.
if(!this.id){
this.id = registry.getUniqueId(this.declaredClass.replace(/\./g, "_"));
if(this.params){
// if params contains {id: undefined}, prevent _applyAttributes() from processing it
delete this.params.id;
}
}
// The document and node this widget is associated with
this.ownerDocument = this.ownerDocument || (this.srcNodeRef ? this.srcNodeRef.ownerDocument : document);
this.ownerDocumentBody = win.body(this.ownerDocument);
registry.add(this);
this.buildRendering();
var deleteSrcNodeRef;
if(this.domNode){
// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
// Also calls custom setters for all attributes with custom setters.
this._applyAttributes();
// If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
// For 2.0, move this after postCreate(). postCreate() shouldn't depend on the
// widget being attached to the DOM since it isn't when a widget is created programmatically like
// new MyWidget({}). See #11635.
var source = this.srcNodeRef;
if(source && source.parentNode && this.domNode !== source){
source.parentNode.replaceChild(this.domNode, source);
deleteSrcNodeRef = true;
}
// Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
// assuming that dojo._scopeName even exists in 2.0
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.
// I think for back-compatibility it isn't deleting srcNodeRef until after postCreate() has run.
if(deleteSrcNodeRef){
delete this.srcNodeRef;
}
this._created = true;
},
_applyAttributes: function(){
// summary:
// Step during widget creation to copy widget attributes to the
// DOM according to attributeMap and _setXXXAttr objects, and also to call
// custom _setXXXAttr() methods.
//
// 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.
//
// For backwards-compatibility reasons attributeMap overrides _setXXXAttr when
// _setXXXAttr is a hash/string/array, but _setXXXAttr as a functions override attributeMap.
// tags:
// private
// Call this.set() for each property that was either specified as parameter to constructor,
// or is in the list found above. For correlated properties like value and displayedValue, the one
// specified as a parameter should take precedence.
// Particularly important for new DateTextBox({displayedValue: ...}) since DateTextBox's default value is
// NaN and thus is not ignored like a default value of "".
// Step 1: Save the current values of the widget properties that were specified as parameters to the constructor.
// Generally this.foo == this.params.foo, except if postMixInProperties() changed the value of this.foo.
var params = {};
for(var key in this.params || {}){
params[key] = this._get(key);
}
// Step 2: Call set() for each property with a non-falsy value that wasn't passed as a parameter to the constructor
array.forEach(this.constructor._setterAttrs, function(key){
if(!(key in params)){
var val = this._get(key);
if(val){
this.set(key, val);
}
}
}, this);
// Step 3: Call set() for each property that was specified as parameter to constructor.
// Use params hash created above to ignore side effects from step #2 above.
for(key in params){
this.set(key, params[key]);
}
},
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.
// Most widgets will mixin `dijit._TemplatedMixin`, which implements this method.
// tags:
// protected
if(!this.domNode){
// Create root node if it wasn't created by _TemplatedMixin
this.domNode = this.srcNodeRef || this.ownerDocument.createElement("div");
}
// baseClass is a single class name or occasionally a space-separated list of names.
// Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix.
// TODO: make baseClass custom setter
if(this.baseClass){
var classes = this.baseClass.split(" ");
if(!this.isLeftToRight()){
classes = classes.concat(array.map(classes, function(name){
return name + "Rtl";
}));
}
domClass.add(this.domNode, classes);
}
},
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().
//
// Note that startup() may be called while the widget is still hidden, for example if the widget is
// inside a hidden dijit/Dialog or an unselected tab of a dijit/layout/TabContainer.
// For widgets that need to do layout, it's best to put that layout code inside resize(), and then
// extend dijit/layout/_LayoutWidget so that resize() is called when the widget is visible.
if(this._started){
return;
}
this._started = true;
array.forEach(this.getChildren(), function(obj){
if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
obj.startup();
obj._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._TemplatedMixin widgets.
this._beingDestroyed = true;
this.destroyDescendants(preserveDom);
this.destroy(preserveDom);
},
destroy: function(/*Boolean*/ preserveDom){
// summary:
// Destroy this widget, but not its descendants. Descendants means widgets inside of
// this.containerNode. Will also destroy any resources (including widgets) registered via this.own().
//
// This method will also destroy internal widgets such as those created from a template,
// assuming those widgets exist inside of this.domNode but outside of this.containerNode.
//
// For 2.0 it's planned that this method will also destroy descendant widgets, so apps should not
// depend on the current ability to destroy a widget without destroying its descendants. Generally
// they should use destroyRecursive() for widgets with children.
// preserveDom: Boolean
// If true, this method will leave the original DOM structure alone.
// Note: This will not yet work with _TemplatedMixin widgets
this._beingDestroyed = true;
this.uninitialize();
function destroy(w){
if(w.destroyRecursive){
w.destroyRecursive(preserveDom);
}else if(w.destroy){
w.destroy(preserveDom);
}
}
// Back-compat, remove for 2.0
array.forEach(this._connects, lang.hitch(this, "disconnect"));
array.forEach(this._supportingWidgets, destroy);
// Destroy supporting widgets, but not child widgets under this.containerNode (for 2.0, destroy child widgets
// here too). if() statement is to guard against exception if destroy() called multiple times (see #15815).
if(this.domNode){
array.forEach(registry.findWidgets(this.domNode, this.containerNode), destroy);
}
this.destroyRendering(preserveDom);
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){
domAttr.remove(this.domNode, "widgetId");
}else{
domConstruct.destroy(this.domNode);
}
delete this.domNode;
}
if(this.srcNodeRef){
if(!preserveDom){
domConstruct.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
array.forEach(this.getChildren(), function(widget){
if(widget.destroyRecursive){
widget.destroyRecursive(preserveDom);
}
});
},
uninitialize: function(){
// summary:
// Deprecated. Override destroy() instead to implement custom widget tear-down
// behavior.
// tags:
// protected
return false;
},
////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
_setStyleAttr: function(/*String||Object*/ value){
// summary:
// Sets the style attribute 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.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(lang.isObject(value)){
domStyle.set(mapNode, value);
}else{
if(mapNode.style.cssText){
mapNode.style.cssText += "; " + value;
}else{
mapNode.style.cssText = value;
}
}
this._set("style", value);
},
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
// summary:
// Reflect a widget attribute (title, tabIndex, duration etc.) to
// the widget DOM, as specified by commands parameter.
// If commands isn't specified then it's looked up from attributeMap.
// Note some attributes like "type"
// cannot be processed this way as they are not mutable.
// attr:
// Name of member variable (ex: "focusNode" maps to this.focusNode) pointing
// to DOMNode inside the widget, or alternately pointing to a subwidget
// tags:
// private
commands = arguments.length >= 3 ? commands : this.attributeMap[attr];
array.forEach(lang.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(lang.isFunction(value)){ // functions execute in the context of the widget
value = lang.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);
if(mapNode.tagName){
// Normal case, mapping to a DOMNode. Note that modern browsers will have a mapNode.set()
// method, but for consistency we still call domAttr
domAttr.set(mapNode, attrName, value);
}else{
// mapping to a sub-widget
mapNode.set(attrName, value);
}
break;
case "innerText":
// Deprecated, use "textContent" instead.
mapNode.innerHTML = "";
mapNode.appendChild(this.ownerDocument.createTextNode(value));
break;
case "textContent":
mapNode.textContent = value;
break;
case "innerHTML":
mapNode.innerHTML = value;
break;
case "class":
domClass.replace(mapNode, value, this[attr]);
break;
case "toggleClass":
domClass.toggle(mapNode, command.className || attr, value);
break;
}
}, this);
},
get: function(name){
// summary:
// Get a property from a widget.
// name:
// The property to get.
// description:
// Get a named property from a widget. The property may
// potentially be retrieved via a getter method. If no getter is defined, this
// just retrieves the object's property.
//
// For example, if the widget has properties `foo` and `bar`
// and a method named `_getFooAttr()`, calling:
// `myWidget.get("foo")` would be equivalent to calling
// `widget._getFooAttr()` and `myWidget.get("bar")`
// would be equivalent to the expression
// `widget.bar2`
var names = this._getAttrNames(name);
return this[names.g] ? this[names.g]() : this._get(name);
},
set: function(name, value){
// summary:
// Set a property on a widget
// name:
// The property to set.
// value:
// The value to set in the property.
// description:
// Sets named properties on a widget which may potentially be handled by a
// setter in the widget.
//
// For example, if the widget has properties `foo` and `bar`
// and a method named `_setFooAttr()`, calling
// `myWidget.set("foo", "Howdy!")` would be equivalent to calling
// `widget._setFooAttr("Howdy!")` and `myWidget.set("bar", 3)`
// would be equivalent to the statement `widget.bar = 3;`
//
// set() may also be called with a hash of name/value pairs, ex:
//
// | myWidget.set({
// | foo: "Howdy",
// | bar: 3
// | });
//
// This is equivalent to calling `set(foo, "Howdy")` and `set(bar, 3)`
if(typeof name === "object"){
for(var x in name){
this.set(x, name[x]);
}
return this;
}
var names = this._getAttrNames(name),
setter = this[names.s];
if(lang.isFunction(setter)){
// use the explicit setter
var result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
}else{
// Mapping from widget attribute to DOMNode/subwidget attribute/value/etc.
// Map according to:
// 1. attributeMap setting, if one exists (TODO: attributeMap deprecated, remove in 2.0)
// 2. _setFooAttr: {...} type attribute in the widget (if one exists)
// 3. apply to focusNode or domNode if standard attribute name, excluding funcs like onClick.
// Checks if an attribute is a "standard attribute" by whether the DOMNode JS object has a similar
// attribute name (ex: accept-charset attribute matches jsObject.acceptCharset).
// Note also that Tree.focusNode() is a function not a DOMNode, so test for that.
var defaultNode = this.focusNode && !lang.isFunction(this.focusNode) ? "focusNode" : "domNode",
tag = this[defaultNode] && this[defaultNode].tagName,
attrsForTag = tag && (tagAttrs[tag] || (tagAttrs[tag] = getAttrs(this[defaultNode]))),
map = name in this.attributeMap ? this.attributeMap[name] :
names.s in this ? this[names.s] :
((attrsForTag && names.l in attrsForTag && typeof value != "function") ||
/^aria-|^data-|^role$/.test(name)) ? defaultNode : null;
if(map != null){
this._attrToDom(name, value, map);
}
this._set(name, value);
}
return result || this;
},
_attrPairNames: {}, // shared between all widgets
_getAttrNames: function(name){
// summary:
// Helper function for get() and set().
// 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.replace(/^[a-z]|-[a-zA-Z]/g, function(c){
return c.charAt(c.length - 1).toUpperCase();
});
return (apn[name] = {
n: name + "Node",
s: "_set" + uc + "Attr", // converts dashes to camel case, ex: accept-charset --> _setAcceptCharsetAttr
g: "_get" + uc + "Attr",
l: uc.toLowerCase() // lowercase name w/out dashes, ex: acceptcharset
});
},
_set: function(/*String*/ name, /*anything*/ value){
// summary:
// Helper function to set new value for specified property, and call handlers
// registered with watch() if the value has changed.
var oldValue = this[name];
this[name] = value;
if(this._created && !isEqual(oldValue, value)){
if(this._watchCallbacks){
this._watchCallbacks(name, oldValue, value);
}
this.emit("attrmodified-" + name, {
detail: {
prevValue: oldValue,
newValue: value
}
});
}
},
_get: function(/*String*/ name){
// summary:
// Helper function to get value for specified property stored by this._set(),
// i.e. for properties with custom setters. Used mainly by custom getters.
//
// For example, CheckBox._getValueAttr() calls this._get("value").
// future: return name in this.props ? this.props[name] : this[name];
return this[name];
},
emit: function(/*String*/ type, /*Object?*/ eventObj, /*Array?*/ callbackArgs){
// summary:
// Used by widgets to signal that a synthetic event occurred, ex:
// | myWidget.emit("attrmodified-selectedChildWidget", {}).
//
// Emits an event on this.domNode named type.toLowerCase(), based on eventObj.
// Also calls onType() method, if present, and returns value from that method.
// By default passes eventObj to callback, but will pass callbackArgs instead, if specified.
// Modifies eventObj by adding missing parameters (bubbles, cancelable, widget).
// tags:
// protected
// Specify fallback values for bubbles, cancelable in case they are not set in eventObj.
// Also set pointer to widget, although since we can't add a pointer to the widget for native events
// (see #14729), maybe we shouldn't do it here?
eventObj = eventObj || {};
if(eventObj.bubbles === undefined){
eventObj.bubbles = true;
}
if(eventObj.cancelable === undefined){
eventObj.cancelable = true;
}
if(!eventObj.detail){
eventObj.detail = {};
}
eventObj.detail.widget = this;
var ret, callback = this["on" + type];
if(callback){
ret = callback.apply(this, callbackArgs ? callbackArgs : [eventObj]);
}
// Emit event, but avoid spurious emit()'s as parent sets properties on child during startup/destroy
if(this._started && !this._beingDestroyed){
on.emit(this.domNode, type.toLowerCase(), eventObj);
}
return ret;
},
on: function(/*String|Function*/ type, /*Function*/ func){
// summary:
// Call specified function when event occurs, ex: myWidget.on("click", function(){ ... }).
// type:
// Name of event (ex: "click") or extension event like touch.press.
// description:
// Call specified function when event `type` occurs, ex: `myWidget.on("click", function(){ ... })`.
// Note that the function is not run in any particular scope, so if (for example) you want it to run in the
// widget's scope you must do `myWidget.on("click", lang.hitch(myWidget, func))`.
// For backwards compatibility, if there's an onType() method in the widget then connect to that.
// Remove in 2.0.
var widgetMethod = this._onMap(type);
if(widgetMethod){
return aspect.after(this, widgetMethod, func, true);
}
// Otherwise, just listen for the event on this.domNode.
return this.own(on(this.domNode, type, func))[0];
},
_onMap: function(/*String|Function*/ type){
// summary:
// Maps on() type parameter (ex: "mousemove") to method name (ex: "onMouseMove").
// If type is a synthetic event like touch.press then returns undefined.
var ctor = this.constructor, map = ctor._onMap;
if(!map){
map = (ctor._onMap = {});
for(var attr in ctor.prototype){
if(/^on/.test(attr)){
map[attr.replace(/^on/, "").toLowerCase()] = attr;
}
}
}
return map[typeof type == "string" && type.toLowerCase()]; // String
},
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
},
getChildren: function(){
// summary:
// Returns all direct children of this widget, i.e. all widgets underneath this.containerNode whose parent
// is this widget. Note that it does not return all descendants, but rather just direct children.
// Analogous to [Node.childNodes](https://developer.mozilla.org/en-US/docs/DOM/Node.childNodes),
// except containing widgets rather than DOMNodes.
//
// The result intentionally excludes internally created widgets (a.k.a. supporting widgets)
// outside of this.containerNode.
//
// Note that the array returned is a simple array. Application code should not assume
// existence of methods like forEach().
return this.containerNode ? registry.findWidgets(this.containerNode) : []; // dijit/_WidgetBase[]
},
getParent: function(){
// summary:
// Returns the parent widget of this widget.
return registry.getEnclosingWidget(this.domNode.parentNode);
},
connect: function(/*Object|null*/ obj, /*String|Function*/ event, /*String|Function*/ method){
// summary:
// Deprecated, will be removed in 2.0, use this.own(on(...)) or this.own(aspect.after(...)) instead.
//
// Connects specified obj/event to specified method of this object
// and registers for disconnect() on widget destroy.
//
// Provide widget-specific analog to dojo.connect, except with the
// implicit use of this widget as the target object.
// Events connected with `this.connect` are disconnected upon
// destruction.
// returns:
// A handle that can be passed to `disconnect` in order to disconnect before
// the widget is destroyed.
// example:
// | var btn = new 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
return this.own(connect.connect(obj, event, this, method))[0]; // handle
},
disconnect: function(handle){
// summary:
// Deprecated, will be removed in 2.0, use handle.remove() instead.
//
// Disconnects handle created by `connect`.
// tags:
// protected
handle.remove();
},
subscribe: function(t, method){
// summary:
// Deprecated, will be removed in 2.0, use this.own(topic.subscribe()) instead.
//
// Subscribes to the specified topic and calls the specified method
// of this object and registers for unsubscribe() on widget destroy.
//
// Provide widget-specific analog to dojo.subscribe, except with the
// implicit use of this widget as the target object.
// t: String
// The topic
// method: Function
// The callback
// example:
// | var btn = new 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.set("label", v);
// | });
// tags:
// protected
return this.own(topic.subscribe(t, lang.hitch(this, method)))[0]; // handle
},
unsubscribe: function(/*Object*/ handle){
// summary:
// Deprecated, will be removed in 2.0, use handle.remove() instead.
//
// Unsubscribes handle created by this.subscribe.
// Also removes handle from this widget's list of subscriptions
// tags:
// protected
handle.remove();
},
isLeftToRight: function(){
// summary:
// Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
// tags:
// protected
return this.dir ? (this.dir.toLowerCase() == "ltr") : domGeometry.isBodyLtr(this.ownerDocument); //Boolean
},
isFocusable: function(){
// summary:
// Return true if this widget can currently be focused
// and false if not
return this.focus && (domStyle.get(this.domNode, "display") != "none");
},
placeAt: function(/*String|DomNode|DocumentFragment|dijit/_WidgetBase*/ reference, /*String|Int?*/ position){
// summary:
// Place this widget somewhere in the DOM based
// on standard domConstruct.place() conventions.
// 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:
// Widget, DOMNode, DocumentFragment, or id of widget or DOMNode
// position:
// If reference is a widget (or id of widget), and that widget has an ".addChild" method,
// it will be called passing this widget instance into that method, supplying the optional
// position index passed. In this case position (if specified) should be an integer.
//
// If reference is a DOMNode (or id matching a DOMNode but not a widget),
// the position argument can be a numeric index or a string
// "first", "last", "before", or "after", same as dojo/dom-construct::place().
// returns: dijit/_WidgetBase
// 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 Button({ label:"click" }).placeAt(win.body());
// | // now, 'button' is still the widget reference to the newly created button
// | button.on("click", 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 Button({},"src").placeAt("wrapper");
// example:
// | // place a new button as the first element of some div
// | var button = new Button({ label:"click" }).placeAt("wrapper","first");
// example:
// | // create a contentpane and add it to a TabContainer
// | var tc = dijit.byId("myTabs");
// | new ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
var refWidget = !reference.tagName && registry.byId(reference);
if(refWidget && refWidget.addChild && (!position || typeof position === "number")){
// Adding this to refWidget and can use refWidget.addChild() to handle everything.
refWidget.addChild(this, position);
}else{
// "reference" is a plain DOMNode, or we can't use refWidget.addChild(). Use domConstruct.place() and
// target refWidget.containerNode for nested placement (position==number, "first", "last", "only"), and
// refWidget.domNode otherwise ("after"/"before"/"replace"). (But not supported officially, see #14946.)
var ref = refWidget && ("domNode" in refWidget) ?
(refWidget.containerNode && !/after|before|replace/.test(position || "") ?
refWidget.containerNode : refWidget.domNode) : dom.byId(reference, this.ownerDocument);
domConstruct.place(this.domNode, ref, position);
// Start this iff it has a parent widget that's already started.
// TODO: for 2.0 maybe it should also start the widget when this.getParent() returns null??
if(!this._started && (this.getParent() || {})._started){
this.startup();
}
}
return this;
},
defer: function(fcn, delay){
// summary:
// Wrapper to setTimeout to avoid deferred functions executing
// after the originating widget has been destroyed.
// Returns an object handle with a remove method (that returns null) (replaces clearTimeout).
// fcn: Function
// Function reference.
// delay: Number?
// Delay, defaults to 0.
// tags:
// protected
var timer = setTimeout(lang.hitch(this,
function(){
if(!timer){
return;
}
timer = null;
if(!this._destroyed){
lang.hitch(this, fcn)();
}
}),
delay || 0
);
return {
remove: function(){
if(timer){
clearTimeout(timer);
timer = null;
}
return null; // so this works well: handle = handle.remove();
}
};
}
});
if(has("dojo-bidi")){
_WidgetBase.extend(_BidiMixin);
}
return _WidgetBase;
});
},
'dojo/Stateful':function(){
define(["./_base/declare", "./_base/lang", "./_base/array", "./when"], function(declare, lang, array, when){
// module:
// dojo/Stateful
return declare("dojo.Stateful", null, {
// summary:
// Base class for objects that provide named properties with optional getter/setter
// control and the ability to watch for property changes
//
// The class also provides the functionality to auto-magically manage getters
// and setters for object attributes/properties.
//
// Getters and Setters should follow the format of _xxxGetter or _xxxSetter where
// the xxx is a name of the attribute to handle. So an attribute of "foo"
// would have a custom getter of _fooGetter and a custom setter of _fooSetter.
//
// example:
// | require(["dojo/Stateful", function(Stateful) {
// | var obj = new Stateful();
// | obj.watch("foo", function(){
// | console.log("foo changed to " + this.get("foo"));
// | });
// | obj.set("foo","bar");
// | });
// _attrPairNames: Hash
// Used across all instances a hash to cache attribute names and their getter
// and setter names.
_attrPairNames: {},
_getAttrNames: function(name){
// summary:
// Helper function for get() and set().
// 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]; }
return (apn[name] = {
s: "_" + name + "Setter",
g: "_" + name + "Getter"
});
},
postscript: function(/*Object?*/ params){
// Automatic setting of params during construction
if (params){ this.set(params); }
},
_get: function(name, names){
// summary:
// Private function that does a get based off a hash of names
// names:
// Hash of names of custom attributes
return typeof this[names.g] === "function" ? this[names.g]() : this[name];
},
get: function(/*String*/name){
// summary:
// Get a property on a Stateful instance.
// name:
// The property to get.
// returns:
// The property value on this Stateful instance.
// description:
// Get a named property on a Stateful object. The property may
// potentially be retrieved via a getter method in subclasses. In the base class
// this just retrieves the object's property.
// example:
// | require(["dojo/Stateful", function(Stateful) {
// | var stateful = new Stateful({foo: 3});
// | stateful.get("foo") // returns 3
// | stateful.foo // returns 3
// | });
return this._get(name, this._getAttrNames(name)); //Any
},
set: function(/*String*/name, /*Object*/value){
// summary:
// Set a property on a Stateful instance
// name:
// The property to set.
// value:
// The value to set in the property.
// returns:
// The function returns this dojo.Stateful instance.
// description:
// Sets named properties on a stateful object and notifies any watchers of
// the property. A programmatic setter may be defined in subclasses.
// example:
// | require(["dojo/Stateful", function(Stateful) {
// | var stateful = new Stateful();
// | stateful.watch(function(name, oldValue, value){
// | // this will be called on the set below
// | }
// | stateful.set(foo, 5);
// set() may also be called with a hash of name/value pairs, ex:
// | stateful.set({
// | foo: "Howdy",
// | bar: 3
// | });
// | });
// This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
// If an object is used, iterate through object
if(typeof name === "object"){
for(var x in name){
if(name.hasOwnProperty(x) && x !="_watchCallbacks"){
this.set(x, name[x]);
}
}
return this;
}
var names = this._getAttrNames(name),
oldValue = this._get(name, names),
setter = this[names.s],
result;
if(typeof setter === "function"){
// use the explicit setter
result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
}else{
// no setter so set attribute directly
this[name] = value;
}
if(this._watchCallbacks){
var self = this;
// If setter returned a promise, wait for it to complete, otherwise call watches immediately
when(result, function(){
self._watchCallbacks(name, oldValue, value);
});
}
return this; // dojo/Stateful
},
_changeAttrValue: function(name, value){
// summary:
// Internal helper for directly changing an attribute value.
//
// name: String
// The property to set.
// value: Mixed
// The value to set in the property.
//
// description:
// Directly change the value of an attribute on an object, bypassing any
// accessor setter. Also handles the calling of watch and emitting events.
// It is designed to be used by descendant class when there are two values
// of attributes that are linked, but calling .set() is not appropriate.
var oldValue = this.get(name);
this[name] = value;
if(this._watchCallbacks){
this._watchCallbacks(name, oldValue, value);
}
return this; // dojo/Stateful
},
watch: function(/*String?*/name, /*Function*/callback){
// summary:
// Watches a property for changes
// name:
// Indicates the property to watch. This is optional (the callback may be the
// only parameter), and if omitted, all the properties will be watched
// returns:
// An object handle for the watch. The unwatch method of this object
// can be used to discontinue watching this property:
// | var watchHandle = obj.watch("foo", callback);
// | watchHandle.unwatch(); // callback won't be called now
// callback:
// The function to execute when the property changes. This will be called after
// the property has been changed. The callback will be called with the |this|
// set to the instance, the first argument as the name of the property, the
// second argument as the old value and the third argument as the new value.
var callbacks = this._watchCallbacks;
if(!callbacks){
var self = this;
callbacks = this._watchCallbacks = function(name, oldValue, value, ignoreCatchall){
var notify = function(propertyCallbacks){
if(propertyCallbacks){
propertyCallbacks = propertyCallbacks.slice();
for(var i = 0, l = propertyCallbacks.length; i < l; i++){
propertyCallbacks[i].call(self, name, oldValue, value);
}
}
};
notify(callbacks['_' + name]);
if(!ignoreCatchall){
notify(callbacks["*"]); // the catch-all
}
}; // we use a function instead of an object so it will be ignored by JSON conversion
}
if(!callback && typeof name === "function"){
callback = name;
name = "*";
}else{
// prepend with dash to prevent name conflicts with function (like "name" property)
name = '_' + name;
}
var propertyCallbacks = callbacks[name];
if(typeof propertyCallbacks !== "object"){
propertyCallbacks = callbacks[name] = [];
}
propertyCallbacks.push(callback);
// TODO: Remove unwatch in 2.0
var handle = {};
handle.unwatch = handle.remove = function(){
var index = array.indexOf(propertyCallbacks, callback);
if(index > -1){
propertyCallbacks.splice(index, 1);
}
};
return handle; //Object
}
});
});
},
'dijit/Destroyable':function(){
define([
"dojo/_base/array", // array.forEach array.map
"dojo/aspect",
"dojo/_base/declare"
], function(array, aspect, declare){
// module:
// dijit/Destroyable
return declare("dijit.Destroyable", null, {
// summary:
// Mixin to track handles and release them when instance is destroyed.
// description:
// Call this.own(...) on list of handles (returned from dojo/aspect, dojo/on,
// dojo/Stateful::watch, or any class (including widgets) with a destroyRecursive() or destroy() method.
// Then call destroy() later to destroy this instance and release the resources.
destroy: function(/*Boolean*/ preserveDom){
// summary:
// Destroy this class, releasing any resources registered via own().
this._destroyed = true;
},
own: function(){
// summary:
// Track specified handles and remove/destroy them when this instance is destroyed, unless they were
// already removed/destroyed manually.
// tags:
// protected
// returns:
// The array of specified handles, so you can do for example:
// | var handle = this.own(on(...))[0];
var cleanupMethods = [
"destroyRecursive",
"destroy",
"remove"
];
array.forEach(arguments, function(handle){
// When this.destroy() is called, destroy handle. Since I'm using aspect.before(),
// the handle will be destroyed before a subclass's destroy() method starts running, before it calls
// this.inherited() or even if it doesn't call this.inherited() at all. If that's an issue, make an
// onDestroy() method and connect to that instead.
var destroyMethodName;
var odh = aspect.before(this, "destroy", function (preserveDom){
handle[destroyMethodName](preserveDom);
});
// Callback for when handle is manually destroyed.
var hdhs = [];
function onManualDestroy(){
odh.remove();
array.forEach(hdhs, function(hdh){
hdh.remove();
});
}
// Setup listeners for manual destroy of handle.
// Also computes destroyMethodName, used in listener above.
if(handle.then){
// Special path for Promises. Detect when Promise is resolved, rejected, or
// canceled (nb: cancelling a Promise causes it to be rejected).
destroyMethodName = "cancel";
handle.then(onManualDestroy, onManualDestroy);
}else{
// Path for other handles. Just use AOP to detect when handle is manually destroyed.
array.forEach(cleanupMethods, function(cleanupMethod){
if(typeof handle[cleanupMethod] === "function"){
if(!destroyMethodName){
// Use first matching method name in above listener (prefer destroyRecursive() to destroy())
destroyMethodName = cleanupMethod;
}
hdhs.push(aspect.after(handle, cleanupMethod, onManualDestroy, true));
}
});
}
}, this);
return arguments; // handle
}
});
});
},
'dijit/_OnDijitClickMixin':function(){
define([
"dojo/on",
"dojo/_base/array", // array.forEach
"dojo/keys", // keys.ENTER keys.SPACE
"dojo/_base/declare", // declare
"dojo/has", // has("dom-addeventlistener")
"./a11yclick"
], function(on, array, keys, declare, has, a11yclick){
// module:
// dijit/_OnDijitClickMixin
var ret = declare("dijit._OnDijitClickMixin", null, {
// summary:
// Deprecated. New code should access the dijit/a11yclick event directly, ex:
// | this.own(on(node, a11yclick, function(){ ... }));
//
// Mixing in this class will make _WidgetBase.connect(node, "ondijitclick", ...) work.
// It also used to be necessary to make templates with ondijitclick work, but now you can just require
// dijit/a11yclick.
connect: function(obj, event, method){
// override _WidgetBase.connect() to make this.connect(node, "ondijitclick", ...) work
return this.inherited(arguments, [obj, event == "ondijitclick" ? a11yclick : event, method]);
}
});
ret.a11yclick = a11yclick; // back compat
return ret;
});
},
'dijit/a11yclick':function(){
define([
"dojo/keys", // keys.ENTER keys.SPACE
"dojo/mouse",
"dojo/on",
"dojo/touch" // touch support for click is now there
], function(keys, mouse, on, touch){
// module:
// dijit/a11yclick
/*=====
return {
// summary:
// Custom press, release, and click synthetic events
// which trigger on a left mouse click, touch, or space/enter keyup.
click: function(node, listener){
// summary:
// Logical click operation for mouse, touch, or keyboard (space/enter key)
},
press: function(node, listener){
// summary:
// Mousedown (left button), touchstart, or keydown (space or enter) corresponding to logical click operation.
},
release: function(node, listener){
// summary:
// Mouseup (left button), touchend, or keyup (space or enter) corresponding to logical click operation.
},
move: function(node, listener){
// summary:
// Mouse cursor or a finger is dragged over the given node.
}
};
=====*/
function clickKey(/*Event*/ e){
// Test if this keyboard event should be tracked as the start (if keydown) or end (if keyup) of a click event.
// Only track for nodes marked to be tracked, and not for buttons or inputs,
// since buttons handle keyboard click natively, and text inputs should not
// prevent typing spaces or newlines.
if((e.keyCode === keys.ENTER || e.keyCode === keys.SPACE) && !/input|button|textarea/i.test(e.target.nodeName)){
// Test if a node or its ancestor has been marked with the dojoClick property to indicate special processing
for(var node = e.target; node; node = node.parentNode){
if(node.dojoClick){ return true; }
}
}
}
var lastKeyDownNode;
on(document, "keydown", function(e){
//console.log("a11yclick: onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
if(clickKey(e)){
// needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
lastKeyDownNode = e.target;
// Prevent viewport scrolling on space key in IE<9.
// (Reproducible on test_Button.html on any of the first dijit/form/Button examples)
e.preventDefault();
}else{
lastKeyDownNode = null;
}
});
on(document, "keyup", function(e){
//console.log("a11yclick: onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
if(clickKey(e) && e.target == lastKeyDownNode){ // === breaks greasemonkey
//need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
lastKeyDownNode = null;
on.emit(e.target, "click", {
cancelable: true,
bubbles: true,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
altKey: e.altKey,
_origType: e.type
});
}
});
// I want to return a hash of the synthetic events, but for backwards compatibility the main return value
// needs to be the click event. Change for 2.0.
var click = function(node, listener){
// Set flag on node so that keydown/keyup above emits click event.
// Also enables fast click processing from dojo/touch.
node.dojoClick = true;
return on(node, "click", listener);
};
click.click = click; // forward compatibility with 2.0
click.press = function(node, listener){
var touchListener = on(node, touch.press, function(evt){
if(evt.type == "mousedown" && !mouse.isLeft(evt)){
// Ignore right click
return;
}
listener(evt);
}), keyListener = on(node, "keydown", function(evt){
if(evt.keyCode === keys.ENTER || evt.keyCode === keys.SPACE){
listener(evt);
}
});
return {
remove: function(){
touchListener.remove();
keyListener.remove();
}
};
};
click.release = function(node, listener){
var touchListener = on(node, touch.release, function(evt){
if(evt.type == "mouseup" && !mouse.isLeft(evt)){
// Ignore right click
return;
}
listener(evt);
}), keyListener = on(node, "keyup", function(evt){
if(evt.keyCode === keys.ENTER || evt.keyCode === keys.SPACE){
listener(evt);
}
});
return {
remove: function(){
touchListener.remove();
keyListener.remove();
}
};
};
click.move = touch.move; // just for convenience
return click;
});
},
'dojo/touch':function(){
define(["./_base/kernel", "./aspect", "./dom", "./dom-class", "./_base/lang", "./on", "./has", "./mouse", "./domReady", "./_base/window"],
function(dojo, aspect, dom, domClass, lang, on, has, mouse, domReady, win){
// module:
// dojo/touch
var ios4 = has("ios") < 5;
// Detect if platform supports Pointer Events, and if so, the names of the events (pointerdown vs. MSPointerDown).
var hasPointer = has("pointer-events") || has("MSPointer"),
pointer = (function () {
var pointer = {};
for (var type in { down: 1, move: 1, up: 1, cancel: 1, over: 1, out: 1 }) {
pointer[type] = has("MSPointer") ?
"MSPointer" + type.charAt(0).toUpperCase() + type.slice(1) :
"pointer" + type;
}
return pointer;
})();
// Detect if platform supports the webkit touchstart/touchend/... events
var hasTouch = has("touch-events");
// Click generation variables
var clicksInited, clickTracker, useTarget = false, clickTarget, clickX, clickY, clickDx, clickDy, clickTime;
// Time of most recent touchstart, touchmove, or touchend event
var lastTouch;
function dualEvent(mouseType, touchType, pointerType){
// Returns synthetic event that listens for both the specified mouse event and specified touch event.
// But ignore fake mouse events that were generated due to the user touching the screen.
if(hasPointer && pointerType){
// IE10+: MSPointer* events are designed to handle both mouse and touch in a uniform way,
// so just use that regardless of hasTouch.
return function(node, listener){
return on(node, pointerType, listener);
};
}else if(hasTouch){
return function(node, listener){
var handle1 = on(node, touchType, function(evt){
listener.call(this, evt);
// On slow mobile browsers (see https://bugs.dojotoolkit.org/ticket/17634),
// a handler for a touch event may take >1s to run. That time shouldn't
// be included in the calculation for lastTouch.
lastTouch = (new Date()).getTime();
}),
handle2 = on(node, mouseType, function(evt){
if(!lastTouch || (new Date()).getTime() > lastTouch + 1000){
listener.call(this, evt);
}
});
return {
remove: function(){
handle1.remove();
handle2.remove();
}
};
};
}else{
// Avoid creating listeners for touch events on performance sensitive older browsers like IE6
return function(node, listener){
return on(node, mouseType, listener);
};
}
}
function marked(/*DOMNode*/ node){
// Search for node ancestor has been marked with the dojoClick property to indicate special processing.
// Returns marked ancestor.
do{
if(node.dojoClick !== undefined){ return node; }
}while(node = node.parentNode);
}
function doClicks(e, moveType, endType){
// summary:
// Setup touch listeners to generate synthetic clicks immediately (rather than waiting for the browser
// to generate clicks after the double-tap delay) and consistently (regardless of whether event.preventDefault()
// was called in an event listener. Synthetic clicks are generated only if a node or one of its ancestors has
// its dojoClick property set to truthy. If a node receives synthetic clicks because one of its ancestors has its
// dojoClick property set to truthy, you can disable synthetic clicks on this node by setting its own dojoClick property
// to falsy.
if(mouse.isRight(e)){
return; // avoid spurious dojoclick event on IE10+; right click is just for context menu
}
var markedNode = marked(e.target);
clickTracker = !e.target.disabled && markedNode && markedNode.dojoClick; // click threshold = true, number, x/y object, or "useTarget"
if(clickTracker){
useTarget = (clickTracker == "useTarget");
clickTarget = (useTarget?markedNode:e.target);
if(useTarget){
// We expect a click, so prevent any other
// default action on "touchpress"
e.preventDefault();
}
clickX = e.changedTouches ? e.changedTouches[0].pageX - win.global.pageXOffset : e.clientX;
clickY = e.changedTouches ? e.changedTouches[0].pageY - win.global.pageYOffset : e.clientY;
clickDx = (typeof clickTracker == "object" ? clickTracker.x : (typeof clickTracker == "number" ? clickTracker : 0)) || 4;
clickDy = (typeof clickTracker == "object" ? clickTracker.y : (typeof clickTracker == "number" ? clickTracker : 0)) || 4;
// add move/end handlers only the first time a node with dojoClick is seen,
// so we don't add too much overhead when dojoClick is never set.
if(!clicksInited){
clicksInited = true;
function updateClickTracker(e){
if(useTarget){
clickTracker = dom.isDescendant(
win.doc.elementFromPoint(
(e.changedTouches ? e.changedTouches[0].pageX - win.global.pageXOffset : e.clientX),
(e.changedTouches ? e.changedTouches[0].pageY - win.global.pageYOffset : e.clientY)),
clickTarget);
}else{
clickTracker = clickTracker &&
(e.changedTouches ? e.changedTouches[0].target : e.target) == clickTarget &&
Math.abs((e.changedTouches ? e.changedTouches[0].pageX - win.global.pageXOffset : e.clientX) - clickX) <= clickDx &&
Math.abs((e.changedTouches ? e.changedTouches[0].pageY - win.global.pageYOffset : e.clientY) - clickY) <= clickDy;
}
}
win.doc.addEventListener(moveType, function(e){
if(mouse.isRight(e)){
return; // avoid spurious dojoclick event on IE10+; right click is just for context menu
}
updateClickTracker(e);
if(useTarget){
// prevent native scroll event and ensure touchend is
// fire after touch moves between press and release.
e.preventDefault();
}
}, true);
win.doc.addEventListener(endType, function(e){
if(mouse.isRight(e)){
return; // avoid spurious dojoclick event on IE10+; right click is just for context menu
}
updateClickTracker(e);
if(clickTracker){
clickTime = (new Date()).getTime();
var target = (useTarget?clickTarget:e.target);
if(target.tagName === "LABEL"){
// when clicking on a label, forward click to its associated input if any
target = dom.byId(target.getAttribute("for")) || target;
}
//some attributes can be on the Touch object, not on the Event:
//http://www.w3.org/TR/touch-events/#touch-interface
var src = (e.changedTouches) ? e.changedTouches[0] : e;
function createMouseEvent(type){
//create the synthetic event.
//http://www.w3.org/TR/DOM-Level-3-Events/#widl-MouseEvent-initMouseEvent
var evt = document.createEvent("MouseEvents");
evt._dojo_click = true;
evt.initMouseEvent(type,
true, //bubbles
true, //cancelable
e.view,
e.detail,
src.screenX,
src.screenY,
src.clientX,
src.clientY,
e.ctrlKey,
e.altKey,
e.shiftKey,
e.metaKey,
0, //button
null //related target
);
return evt;
}
var mouseDownEvt = createMouseEvent("mousedown");
var mouseUpEvt = createMouseEvent("mouseup");
var clickEvt = createMouseEvent("click");
setTimeout(function(){
on.emit(target, "mousedown", mouseDownEvt);
on.emit(target, "mouseup", mouseUpEvt);
on.emit(target, "click", clickEvt);
// refresh clickTime in case app-defined click handler took a long time to run
clickTime = (new Date()).getTime();
}, 0);
}
}, true);
function stopNativeEvents(type){
win.doc.addEventListener(type, function(e){
// Stop native events when we emitted our own click event. Note that the native click may occur
// on a different node than the synthetic click event was generated on. For example,
// click on a menu item, causing the menu to disappear, and then (~300ms later) the browser
// sends a click event to the node that was *underneath* the menu. So stop all native events
// sent shortly after ours, similar to what is done in dualEvent.
// The INPUT.dijitOffScreen test is for offscreen inputs used in dijit/form/Button, on which
// we call click() explicitly, we don't want to stop this event.
var target = e.target;
if(clickTracker && !e._dojo_click &&
(new Date()).getTime() <= clickTime + 1000 &&
!(target.tagName == "INPUT" && domClass.contains(target, "dijitOffScreen"))){
e.stopPropagation();
e.stopImmediatePropagation && e.stopImmediatePropagation();
if(type == "click" &&
(target.tagName != "INPUT" ||
(target.type == "radio" &&
// #18352 Do not preventDefault for radios that are not dijit or
// dojox/mobile widgets.
// (The CSS class dijitCheckBoxInput holds for both checkboxes and radio buttons.)
(domClass.contains(target, "dijitCheckBoxInput") ||
domClass.contains(target, "mblRadioButton"))) ||
(target.type == "checkbox" &&
// #18352 Do not preventDefault for checkboxes that are not dijit or
// dojox/mobile widgets.
(domClass.contains(target, "dijitCheckBoxInput") ||
domClass.contains(target, "mblCheckBox")))) &&
target.tagName != "TEXTAREA" && target.tagName != "AUDIO" && target.tagName != "VIDEO"){
// preventDefault() breaks textual s on android, keyboard doesn't popup,
// but it is still needed for checkboxes and radio buttons, otherwise in some cases
// the checked state becomes inconsistent with the widget's state
e.preventDefault();
}
}
}, true);
}
stopNativeEvents("click");
// We also stop mousedown/up since these would be sent well after with our "fast" click (300ms),
// which can confuse some dijit widgets.
stopNativeEvents("mousedown");
stopNativeEvents("mouseup");
}
}
}
var hoveredNode;
if(has("touch")){
if(hasPointer){
// MSPointer (IE10+) already has support for over and out, so we just need to init click support
domReady(function(){
win.doc.addEventListener(pointer.down, function(evt){
doClicks(evt, pointer.move, pointer.up);
}, true);
});
}else{
domReady(function(){
// Keep track of currently hovered node
hoveredNode = win.body(); // currently hovered node
win.doc.addEventListener("touchstart", function(evt){
lastTouch = (new Date()).getTime();
// Precede touchstart event with touch.over event. DnD depends on this.
// Use addEventListener(cb, true) to run cb before any touchstart handlers on node run,
// and to ensure this code runs even if the listener on the node does event.stop().
var oldNode = hoveredNode;
hoveredNode = evt.target;
on.emit(oldNode, "dojotouchout", {
relatedTarget: hoveredNode,
bubbles: true
});
on.emit(hoveredNode, "dojotouchover", {
relatedTarget: oldNode,
bubbles: true
});
doClicks(evt, "touchmove", "touchend"); // init click generation
}, true);
function copyEventProps(evt){
// Make copy of event object and also set bubbles:true. Used when calling on.emit().
var props = lang.delegate(evt, {
bubbles: true
});
if(has("ios") >= 6){
// On iOS6 "touches" became a non-enumerable property, which
// is not hit by for...in. Ditto for the other properties below.
props.touches = evt.touches;
props.altKey = evt.altKey;
props.changedTouches = evt.changedTouches;
props.ctrlKey = evt.ctrlKey;
props.metaKey = evt.metaKey;
props.shiftKey = evt.shiftKey;
props.targetTouches = evt.targetTouches;
}
return props;
}
on(win.doc, "touchmove", function(evt){
lastTouch = (new Date()).getTime();
var newNode = win.doc.elementFromPoint(
evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
);
if(newNode){
// Fire synthetic touchover and touchout events on nodes since the browser won't do it natively.
if(hoveredNode !== newNode){
// touch out on the old node
on.emit(hoveredNode, "dojotouchout", {
relatedTarget: newNode,
bubbles: true
});
// touchover on the new node
on.emit(newNode, "dojotouchover", {
relatedTarget: hoveredNode,
bubbles: true
});
hoveredNode = newNode;
}
// Unlike a listener on "touchmove", on(node, "dojotouchmove", listener) fires when the finger
// drags over the specified node, regardless of which node the touch started on.
if(!on.emit(newNode, "dojotouchmove", copyEventProps(evt))){
// emit returns false when synthetic event "dojotouchmove" is cancelled, so we prevent the
// default behavior of the underlying native event "touchmove".
evt.preventDefault();
}
}
});
// Fire a dojotouchend event on the node where the finger was before it was removed from the screen.
// This is different than the native touchend, which fires on the node where the drag started.
on(win.doc, "touchend", function(evt){
lastTouch = (new Date()).getTime();
var node = win.doc.elementFromPoint(
evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
) || win.body(); // if out of the screen
on.emit(node, "dojotouchend", copyEventProps(evt));
});
});
}
}
//device neutral events - touch.press|move|release|cancel/over/out
var touch = {
press: dualEvent("mousedown", "touchstart", pointer.down),
move: dualEvent("mousemove", "dojotouchmove", pointer.move),
release: dualEvent("mouseup", "dojotouchend", pointer.up),
cancel: dualEvent(mouse.leave, "touchcancel", hasPointer ? pointer.cancel : null),
over: dualEvent("mouseover", "dojotouchover", pointer.over),
out: dualEvent("mouseout", "dojotouchout", pointer.out),
enter: mouse._eventHandler(dualEvent("mouseover","dojotouchover", pointer.over)),
leave: mouse._eventHandler(dualEvent("mouseout", "dojotouchout", pointer.out))
};
/*=====
touch = {
// summary:
// This module provides unified touch event handlers by exporting
// press, move, release and cancel which can also run well on desktop.
// Based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
// Also, if the dojoClick property is set to truthy on a DOM node, dojo/touch generates
// click events immediately for this node and its descendants (except for descendants that
// have a dojoClick property set to falsy), to avoid the delay before native browser click events,
// and regardless of whether evt.preventDefault() was called in a touch.press event listener.
//
// example:
// Used with dojo/on
// | define(["dojo/on", "dojo/touch"], function(on, touch){
// | on(node, touch.press, function(e){});
// | on(node, touch.move, function(e){});
// | on(node, touch.release, function(e){});
// | on(node, touch.cancel, function(e){});
// example:
// Used with touch.* directly
// | touch.press(node, function(e){});
// | touch.move(node, function(e){});
// | touch.release(node, function(e){});
// | touch.cancel(node, function(e){});
// example:
// Have dojo/touch generate clicks without delay, with a default move threshold of 4 pixels
// | node.dojoClick = true;
// example:
// Have dojo/touch generate clicks without delay, with a move threshold of 10 pixels horizontally and vertically
// | node.dojoClick = 10;
// example:
// Have dojo/touch generate clicks without delay, with a move threshold of 50 pixels horizontally and 10 pixels vertically
// | node.dojoClick = {x:50, y:5};
// example:
// Disable clicks without delay generated by dojo/touch on a node that has an ancestor with property dojoClick set to truthy
// | node.dojoClick = false;
press: function(node, listener){
// summary:
// Register a listener to 'touchstart'|'mousedown' for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
move: function(node, listener){
// summary:
// Register a listener that fires when the mouse cursor or a finger is dragged over the given node.
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
release: function(node, listener){
// summary:
// Register a listener to releasing the mouse button while the cursor is over the given node
// (i.e. "mouseup") or for removing the finger from the screen while touching the given node.
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
cancel: function(node, listener){
// summary:
// Register a listener to 'touchcancel'|'mouseleave' for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
over: function(node, listener){
// summary:
// Register a listener to 'mouseover' or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
out: function(node, listener){
// summary:
// Register a listener to 'mouseout' or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
enter: function(node, listener){
// summary:
// Register a listener to mouse.enter or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
leave: function(node, listener){
// summary:
// Register a listener to mouse.leave or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
}
};
=====*/
1 && (dojo.touch = touch);
return touch;
});
},
'dijit/_FocusMixin':function(){
define([
"./focus",
"./_WidgetBase",
"dojo/_base/declare", // declare
"dojo/_base/lang" // lang.extend
], function(focus, _WidgetBase, declare, lang){
// module:
// dijit/_FocusMixin
// We don't know where _FocusMixin will occur in the inheritance chain, but we need the _onFocus()/_onBlur() below
// to be last in the inheritance chain, so mixin to _WidgetBase.
lang.extend(_WidgetBase, {
// focused: [readonly] Boolean
// This widget or a widget it contains has focus, or is "active" because
// it was recently clicked.
focused: false,
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(){
// 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();
}
});
return declare("dijit._FocusMixin", null, {
// summary:
// Mixin to widget to provide _onFocus() and _onBlur() methods that
// fire when a widget or its descendants get/lose focus
// flag that I want _onFocus()/_onBlur() notifications from focus manager
_focusManager: focus
});
});
},
'dijit/focus':function(){
define([
"dojo/aspect",
"dojo/_base/declare", // declare
"dojo/dom", // domAttr.get dom.isDescendant
"dojo/dom-attr", // domAttr.get dom.isDescendant
"dojo/dom-class",
"dojo/dom-construct", // connect to domConstruct.empty, domConstruct.destroy
"dojo/Evented",
"dojo/_base/lang", // lang.hitch
"dojo/on",
"dojo/domReady",
"dojo/sniff", // has("ie")
"dojo/Stateful",
"dojo/_base/window", // win.body
"dojo/window", // winUtils.get
"./a11y", // a11y.isTabNavigable
"./registry", // registry.byId
"./main" // to set dijit.focus
], function(aspect, declare, dom, domAttr, domClass, domConstruct, Evented, lang, on, domReady, has, Stateful, win, winUtils,
a11y, registry, dijit){
// module:
// dijit/focus
// Time of the last focusin event
var lastFocusin;
// Time of the last touch/mousedown or focusin event
var lastTouchOrFocusin;
var FocusManager = declare([Stateful, Evented], {
// summary:
// Tracks the currently focused node, and which widgets are currently "active".
// Access via require(["dijit/focus"], function(focus){ ... }).
//
// A widget is considered active if it or a descendant widget has focus,
// or if a non-focusable node of this widget or a descendant was recently clicked.
//
// Call focus.watch("curNode", callback) to track the current focused DOMNode,
// or focus.watch("activeStack", callback) to track the currently focused stack of widgets.
//
// Call focus.on("widget-blur", func) or focus.on("widget-focus", ...) to monitor when
// when widgets become active/inactive
//
// Finally, focus(node) will focus a node, suppressing errors if the node doesn't exist.
// curNode: DomNode
// Currently focused item on screen
curNode: null,
// activeStack: dijit/_WidgetBase[]
// List of currently active widgets (focused widget and it's ancestors)
activeStack: [],
constructor: function(){
// Don't leave curNode/prevNode pointing to bogus elements
var check = lang.hitch(this, function(node){
if(dom.isDescendant(this.curNode, node)){
this.set("curNode", null);
}
if(dom.isDescendant(this.prevNode, node)){
this.set("prevNode", null);
}
});
aspect.before(domConstruct, "empty", check);
aspect.before(domConstruct, "destroy", check);
},
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 `