text: define([
  	'dojo/_base/declare',
  	'dojo/_base/lang',
  	'dojo/_base/sniff',
  	'dojo/dom-construct',
  	'dojo/dom-class',
  	'./Selection'
  ], function (declare, lang, has, domConstruct, domClass, Selection) {
  
  	return declare(Selection, {
  		// summary:
  		//		Adds an input field (checkbox or radio) to a column that when checked, selects the row
  		//		that contains the input field.  To enable, add a "selector" property to a column definition.
  		//
  		// description:
  		//		The selector property should contain "checkbox", "radio", or be a function that renders the input.
  		//		If set to "radio", the input field will be a radio button and only one input in the column will be
  		//		checked.  If the value of selector is a function, then the function signature is
  		//		renderSelectorInput(column, value, cell, object) where:
  		//		* column - the column definition
  		//		* value - the cell's value
  		//		* cell - the cell's DOM node
  		//		* object - the row's data object
  		//		The custom renderSelectorInput function must return an input field.
  
  		postCreate: function () {
  			this.inherited(arguments);
  
  			// Register one listener at the top level that receives events delegated
  			var handleSelectorClick = lang.hitch(this, '_handleSelectorClick');
  			// Some mobile browsers do not like the checkbox element to be modified when responding to a "click"
  			// event.  Responding to the "change" event works better.
  			this.on('.dgrid-selector:change', handleSelectorClick);
  			this.on('.dgrid-selector:click,.dgrid-selector:keydown', function (event) {
  				if (event.target.nodeName !== 'INPUT') {
  					handleSelectorClick(event);
  				}
  			});
  			// Register listeners to the select and deselect events to change the input checked value
  			this.on('dgrid-select', lang.hitch(this, '_changeSelectorInput', true));
  			this.on('dgrid-deselect', lang.hitch(this, '_changeSelectorInput', false));
  		},
  
  		_defaultRenderSelectorInput: function (column, selected, cell, object) {
  			var grid = column.grid;
  
  			domClass.add(cell, 'dgrid-selector');
  			return (cell.input = domConstruct.create('input', {
  				'aria-checked': selected,
  				checked: selected,
  				disabled: !grid.allowSelect(grid.row(object)),
  				tabIndex: isNaN(column.tabIndex) ? -1 : column.tabIndex,
  				type: column.selector
  			}, cell));
  		},
  
  		_configureSelectorColumn: function (column) {
  			var self = this;
  			var selector = column.selector;
  
  			this._selectorColumns.push(column);
  			this._selectorSingleRow = this._selectorSingleRow || column.selector === 'radio';
  
  			var renderSelectorInput = typeof selector === 'function' ?
  				selector : this._defaultRenderSelectorInput;
  
  			column.sortable = false;
  
  			column.renderCell = function (object, value, cell) {
  				var row = object && self.row(object);
  				value = row && self.selection[row.id];
  				renderSelectorInput(column, !!value, cell, object);
  			};
  
  			column.renderHeaderCell = function (th) {
  				var label = 'label' in column ? column.label : column.field || '';
  
  				if (column.selector === 'radio' || !self.allowSelectAll) {
  					th.appendChild(document.createTextNode(label));
  				}
  				else {
  					column._selectorHeaderCheckbox = renderSelectorInput(column, false, th, {});
  					self._hasSelectorHeaderCheckbox = true;
  				}
  			};
  		},
  
  		_handleSelectorClick: function (event) {
  			// Avoid double-triggering code below due to space key on input automatically triggering click (#731)
  			if (event.target.nodeName === 'INPUT' && event.type === 'keydown' && event.keyCode === 32) {
  				return;
  			}
  
  			var cell = this.cell(event);
  			var row = cell.row;
  
  			// We would really only care about click, since other input sources like spacebar
  			// trigger a click, but the click event doesn't provide access to the shift key in firefox, so
  			// listen for keydown as well to get an event in firefox that we can properly retrieve
  			// the shiftKey property
  			if (event.type === 'change' || event.type === 'click' || event.keyCode === 32 ||
  				(!has('opera') && event.keyCode === 13) || event.keyCode === 0) {
  
  				this._selectionTriggerEvent = event;
  
  				if (row) {
  					if (this.allowSelect(row)) {
  						var lastRow = this._lastSelected && this.row(this._lastSelected);
  
  						if (this._selectorSingleRow) {
  							if (!lastRow || lastRow.id !== row.id) {
  								this.clearSelection();
  								this.select(row, null, true);
  								this._lastSelected = row.element;
  							}
  						}
  						else {
  							if (row) {
  								if (event.shiftKey) {
  									// Make sure the last input always ends up checked for shift key
  									this._changeSelectorInput(true, {rows: [row]});
  								}
  								else {
  									// No shift key, so no range selection
  									lastRow = null;
  								}
  								lastRow = event.shiftKey ? lastRow : null;
  								this.select(lastRow || row, row, lastRow ? undefined : null);
  								this._lastSelected = row.element;
  							}
  						}
  					}
  				}
  				else {
  					// No row resolved; must be the select-all checkbox.
  					this[this.allSelected ? 'clearSelection' : 'selectAll']();
  				}
  
  				this._selectionTriggerEvent = null;
  			}
  		},
  
  		_changeSelectorInput: function (value, event) {
  			if (this._selectorColumns.length) {
  				this._updateRowSelectors(value, event);
  			}
  			if (this._hasSelectorHeaderCheckbox) {
  				this._updateHeaderCheckboxes();
  			}
  		},
  
  		_updateRowSelectors: function (value, event) {
  			var rows = event.rows;
  			var lenRows = rows.length;
  			var lenCols = this._selectorColumns.length;
  
  			for (var iRows = 0; iRows < lenRows; iRows++) {
  				for (var iCols = 0; iCols < lenCols; iCols++) {
  					var column = this._selectorColumns[iCols];
  					var element = this.cell(rows[iRows], column.id).element;
  					if (!element) {
  						// Skip if row has been entirely removed
  						continue;
  					}
  					element = (element.contents || element).input;
  					if (element && !element.disabled) {
  						// Only change the value if it is not disabled
  						element.checked = value;
  						element.setAttribute('aria-checked', value);
  					}
  				}
  			}
  		},
  
  		_updateHeaderCheckboxes: function () {
  			/* jshint eqeqeq: false */
  			var lenCols = this._selectorColumns.length;
  			for (var iCols = 0; iCols < lenCols; iCols++) {
  				var column = this._selectorColumns[iCols];
  				var state = 'false';
  				var selection;
  				var mixed;
  				var selectorHeaderCheckbox = column._selectorHeaderCheckbox;
  				if (selectorHeaderCheckbox) {
  					selection = this.selection;
  					mixed = false;
  					// See if the header checkbox needs to be indeterminate
  					for (var i in selection) {
  						// If there is anything in the selection, than it is indeterminate
  						// (Intentionally coerce since selection[i] can be undefined)
  						if (selection[i] != this.allSelected) {
  							mixed = true;
  							break;
  						}
  					}
  					selectorHeaderCheckbox.indeterminate = mixed;
  					selectorHeaderCheckbox.checked = this.allSelected;
  					if (mixed) {
  						state = 'mixed';
  					}
  					else if (this.allSelected) {
  						state = 'true';
  					}
  					selectorHeaderCheckbox.setAttribute('aria-checked', state);
  				}
  			}
  		},
  
  		configStructure: function () {
  			this.inherited(arguments);
  			var columns = this.columns;
  			this._selectorColumns = [];
  			this._hasSelectorHeaderCheckbox = this._selectorSingleRow = false;
  
  			for (var k in columns) {
  				if (columns[k].selector) {
  					this._configureSelectorColumn(columns[k]);
  				}
  			}
  		},
  
  		_handleSelect: function (event) {
  			// Ignore the default select handler for events that originate from the selector column
  			var column = this.cell(event).column;
  			if (!column || !column.selector) {
  				this.inherited(arguments);
  			}
  		}
  	});
  });
  