341 lines
9.4 KiB
JavaScript
341 lines
9.4 KiB
JavaScript
|
define("dijit/_PaletteMixin", [
|
||
|
"dojo/_base/declare", // declare
|
||
|
"dojo/dom-attr", // domAttr.set
|
||
|
"dojo/dom-class", // domClass.add domClass.remove
|
||
|
"dojo/dom-construct", // domConstruct.create domConstruct.place
|
||
|
"dojo/_base/event", // event.stop
|
||
|
"dojo/keys", // keys
|
||
|
"dojo/_base/lang", // lang.getObject
|
||
|
"./_CssStateMixin",
|
||
|
"./focus",
|
||
|
"./typematic"
|
||
|
], function(declare, domAttr, domClass, domConstruct, event, keys, lang, _CssStateMixin, focus, typematic){
|
||
|
|
||
|
// module:
|
||
|
// dijit/_PaletteMixin
|
||
|
|
||
|
return 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 implements dijit.Dye interface
|
||
|
dyeClass: null,
|
||
|
|
||
|
// summary: String
|
||
|
// Localized summary for the palette table
|
||
|
summary: '',
|
||
|
_setSummaryAttr: "paletteTableNode",
|
||
|
|
||
|
_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.connect(this.gridNode, "ondijitclick", "_onCellClick");
|
||
|
|
||
|
for(var row=0; row < choices.length; row++){
|
||
|
var rowNode = domConstruct.create("tr", {tabIndex: "-1"}, 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,
|
||
|
{charOrCode: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);
|
||
|
|
||
|
event.stop(evt);
|
||
|
},
|
||
|
|
||
|
_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;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/*=====
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
=====*/
|
||
|
|
||
|
});
|