Project: Dojo Toolkit
Code Location: http://svn.dojotoolkit.org/src/dijit/trunk//dijit/trunk
Browse
/
Download File
_PaletteMixin.js
define([
	"dojo/_base/declare", // declare
	"dojo/dom-attr", // domAttr.set
	"dojo/dom-class", // domClass.add domClass.remove
	"dojo/dom-construct", // domConstruct.create domConstruct.place
	"dojo/keys", // keys
	"dojo/_base/lang", // lang.getObject
	"dojo/on",
	"./_CssStateMixin",
	"./a11yclick",
	"./focus",
	"./typematic"
], function(declare, domAttr, domClass, domConstruct, keys, lang, on, _CssStateMixin, a11yclick, focus, typematic){

	// module:
	//		dijit/_PaletteMixin

	var PaletteMixin = declare("dijit._PaletteMixin", _CssStateMixin, {
		// summary:
		//		A keyboard accessible palette, for picking a color/emoticon/etc.
		// description:
		//		A mixin for a grid showing various entities, so the user can pick a certain entity.

		// defaultTimeout: Number
		//		Number of milliseconds before a held key or button becomes typematic
		defaultTimeout: 500,

		// timeoutChangeRate: Number
		//		Fraction of time used to change the typematic timer between events
		//		1.0 means that each typematic event fires at defaultTimeout intervals
		//		Less than 1.0 means that each typematic event fires at an increasing faster rate
		timeoutChangeRate: 0.90,

		// value: String
		//		Currently selected color/emoticon/etc.
		value: "",

		// _selectedCell: [private] Integer
		//		Index of the currently selected cell. Initially, none selected
		_selectedCell: -1,

		/*=====
		 // _currentFocus: [private] DomNode
		 //		The currently focused cell (if the palette itself has focus), or otherwise
		 //		the cell to be focused when the palette itself gets focus.
		 //		Different from value, which represents the selected (i.e. clicked) cell.
		 _currentFocus: null,
		 =====*/

		/*=====
		 // _xDim: [protected] Integer
		 //		This is the number of cells horizontally across.
		 _xDim: null,
		 =====*/

		/*=====
		 // _yDim: [protected] Integer
		 //		This is the number of cells vertically down.
		 _yDim: null,
		 =====*/

		// tabIndex: String
		//		Widget tab index.
		tabIndex: "0",

		// cellClass: [protected] String
		//		CSS class applied to each cell in the palette
		cellClass: "dijitPaletteCell",

		// dyeClass: [protected] Constructor
		//		Constructor for Object created for each cell of the palette.
		//		dyeClass should implement the dijit/_PaletteMixin.__Dye interface.
		dyeClass: null,

		_dyeFactory: function(value /*===== , row, col, title =====*/){
			// summary:
			//		Return instance of dijit.Dye for specified cell of palette
			// tags:
			//		extension

			// Remove string support for 2.0
			var dyeClassObj = typeof this.dyeClass == "string" ? lang.getObject(this.dyeClass) : this.dyeClass;
			return new dyeClassObj(value);
		},

		_preparePalette: function(choices, titles){
			// summary:
			//		Subclass must call _preparePalette() from postCreate(), passing in the tooltip
			//		for each cell
			// choices: String[][]
			//		id's for each cell of the palette, used to create Dye JS object for each cell
			// titles: String[]
			//		Localized tooltip for each cell

			this._cells = [];
			var url = this._blankGif;

			this.own(on(this.gridNode, a11yclick, lang.hitch(this, "_onCellClick")));

			for(var row = 0; row < choices.length; row++){
				var rowNode = domConstruct.create("tr", {tabIndex: "-1", role: "row"}, this.gridNode);
				for(var col = 0; col < choices[row].length; col++){
					var value = choices[row][col];
					if(value){
						var cellObject = this._dyeFactory(value, row, col, titles[value]);

						var cellNode = domConstruct.create("td", {
							"class": this.cellClass,
							tabIndex: "-1",
							title: titles[value],
							role: "gridcell"
						}, rowNode);

						// prepare cell inner structure
						cellObject.fillCell(cellNode, url);

						cellNode.idx = this._cells.length;

						// save cell info into _cells
						this._cells.push({node: cellNode, dye: cellObject});
					}
				}
			}
			this._xDim = choices[0].length;
			this._yDim = choices.length;

			// Now set all events
			// The palette itself is navigated to with the tab key on the keyboard
			// Keyboard navigation within the Palette is with the arrow keys
			// Spacebar selects the cell.
			// For the up key the index is changed by negative the x dimension.

			var keyIncrementMap = {
				UP_ARROW: -this._xDim,
				// The down key the index is increase by the x dimension.
				DOWN_ARROW: this._xDim,
				// Right and left move the index by 1.
				RIGHT_ARROW: this.isLeftToRight() ? 1 : -1,
				LEFT_ARROW: this.isLeftToRight() ? -1 : 1
			};
			for(var key in keyIncrementMap){
				this.own(
					typematic.addKeyListener(
						this.domNode,
						{keyCode: keys[key], ctrlKey: false, altKey: false, shiftKey: false},
						this,
						function(){
							var increment = keyIncrementMap[key];
							return function(count){
								this._navigateByKey(increment, count);
							};
						}(),
						this.timeoutChangeRate,
						this.defaultTimeout
					)
				);
			}
		},

		postCreate: function(){
			this.inherited(arguments);

			// Set initial navigable node.
			this._setCurrent(this._cells[0].node);
		},

		focus: function(){
			// summary:
			//		Focus this widget.  Puts focus on the most recently focused cell.

			// The cell already has tabIndex set, just need to set CSS and focus it
			focus.focus(this._currentFocus);
		},

		_onCellClick: function(/*Event*/ evt){
			// summary:
			//		Handler for click, enter key & space key. Selects the cell.
			// evt:
			//		The event.
			// tags:
			//		private

			var target = evt.target;

			// Find TD associated with click event.   For ColorPalette user likely clicked IMG inside of TD
			while(target.tagName != "TD"){
				if(!target.parentNode || target == this.gridNode){    // probably can never happen, but just in case
					return;
				}
				target = target.parentNode;
			}

			var value = this._getDye(target).getValue();

			// First focus the clicked cell, and then send onChange() notification.
			// onChange() (via _setValueAttr) must be after the focus call, because
			// it may trigger a refocus to somewhere else (like the Editor content area), and that
			// second focus should win.
			this._setCurrent(target);
			focus.focus(target);
			this._setValueAttr(value, true);

			evt.stopPropagation();
			evt.preventDefault();
		},

		_setCurrent: function(/*DomNode*/ node){
			// summary:
			//		Sets which node is the focused cell.
			// description:
			//		At any point in time there's exactly one
			//		cell with tabIndex != -1.   If focus is inside the palette then
			//		focus is on that cell.
			//
			//		After calling this method, arrow key handlers and mouse click handlers
			//		should focus the cell in a setTimeout().
			// tags:
			//		protected
			if("_currentFocus" in this){
				// Remove tabIndex on old cell
				domAttr.set(this._currentFocus, "tabIndex", "-1");
			}

			// Set tabIndex of new cell
			this._currentFocus = node;
			if(node){
				domAttr.set(node, "tabIndex", this.tabIndex);
			}
		},

		_setValueAttr: function(value, priorityChange){
			// summary:
			//		This selects a cell. It triggers the onChange event.
			// value: String
			//		Value of the cell to select
			// tags:
			//		protected
			// priorityChange: Boolean?
			//		Optional parameter used to tell the select whether or not to fire
			//		onChange event.

			// clear old selected cell
			if(this._selectedCell >= 0){
				domClass.remove(this._cells[this._selectedCell].node, this.cellClass + "Selected");
			}
			this._selectedCell = -1;

			// search for cell matching specified value
			if(value){
				for(var i = 0; i < this._cells.length; i++){
					if(value == this._cells[i].dye.getValue()){
						this._selectedCell = i;
						domClass.add(this._cells[i].node, this.cellClass + "Selected");
						break;
					}
				}
			}

			// record new value, or null if no matching cell
			this._set("value", this._selectedCell >= 0 ? value : null);

			if(priorityChange || priorityChange === undefined){
				this.onChange(value);
			}
		},

		onChange: function(/*===== value =====*/){
			// summary:
			//		Callback when a cell is selected.
			// value: String
			//		Value corresponding to cell.
		},

		_navigateByKey: function(increment, typeCount){
			// summary:
			//		This is the callback for typematic.
			//		It changes the focus and the highlighed cell.
			// increment:
			//		How much the key is navigated.
			// typeCount:
			//		How many times typematic has fired.
			// tags:
			//		private

			// typecount == -1 means the key is released.
			if(typeCount == -1){
				return;
			}

			var newFocusIndex = this._currentFocus.idx + increment;
			if(newFocusIndex < this._cells.length && newFocusIndex > -1){
				var focusNode = this._cells[newFocusIndex].node;
				this._setCurrent(focusNode);

				// Actually focus the node, for the benefit of screen readers.
				// Use defer because IE doesn't like changing focus inside of an event handler
				this.defer(lang.hitch(focus, "focus", focusNode));
			}
		},

		_getDye: function(/*DomNode*/ cell){
			// summary:
			//		Get JS object for given cell DOMNode

			return this._cells[cell.idx].dye;
		}
	});

	/*=====
	 PaletteMixin.__Dye = declare("dijit.Dye", null, {
		 // summary:
		 //		Interface for the JS Object associated with a palette cell (i.e. DOMNode)

		 constructor: function(alias, row, col){
			 // summary:
			 //		Initialize according to value or alias like "white"
			 // alias: String
		 },

		 getValue: function(){
			 // summary:
			 //		Return "value" of cell; meaning of "value" varies by subclass.
			 // description:
			 //		For example color hex value, emoticon ascii value etc, entity hex value.
		 },

		 fillCell: function(cell, blankGif){
			 // summary:
			 //		Add cell DOMNode inner structure
			 // cell: DomNode
			 //		The surrounding cell
			 // blankGif: String
			 //		URL for blank cell image
		 }
	 });
	 =====*/

	return PaletteMixin;
});