dojox
Version:
Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.
586 lines (504 loc) • 17.5 kB
JavaScript
define([
"dojo/_base/kernel","dojo/_base/declare","dojo/_base/lang","dojo/_base/array",
"dojo/_base/html","dojo/_base/connect","dojo/_base/sniff","dojo/_base/window",
"dojo/_base/event","dojo/dom","dojo/dom-class","dojo/keys","dojo/fx","dojo/dnd/move",
"dijit/registry","dijit/_base/focus","dijit/form/_FormWidget","dijit/typematic",
"dojox/color","dojo/i18n","dojo/i18n!./nls/ColorPicker","dojo/i18n!dojo/cldr/nls/number",
"dojo/text!./ColorPicker/ColorPicker.html"
], function(kernel,declare,lang,ArrayUtil,html,Hub,has,win,Event,DOM,DOMClass,Keys,fx,move,
registry,FocusManager,FormWidget,Typematic,color,i18n,bundle1,bundle2,template){
kernel.experimental("dojox.widget.ColorPicker");
var webSafeFromHex = function(hex){
// stub, this is planned later:
return hex;
};
// TODO: shouldn't this extend _FormValueWidget?
return declare("dojox.widget.ColorPicker", FormWidget, {
// summary:
// a HSV color picker - similar to Photoshop picker
// description:
// Provides an interactive HSV ColorPicker similar to
// PhotoShop's color selction tool. This is an enhanced
// version of the default dijit.ColorPalette, though provides
// no accessibility.
// example:
// | var picker = new dojox.widget.ColorPicker({
// | // a couple of example toggles:
// | animatePoint:false,
// | showHsv: false,
// | webSafe: false,
// | showRgb: false
// | });
// example:
// | <!-- markup: -->
// | <div dojoType="dojox.widget.ColorPicker"></div>
// showRgb: Boolean
// show/update RGB input nodes
showRgb: true,
// showHsv: Boolean
// show/update HSV input nodes
showHsv: true,
// showHex: Boolean
// show/update Hex value field
showHex: true,
// webSafe: Boolean
// deprecated? or just use a toggle to show/hide that node, too?
webSafe: true,
// animatePoint: Boolean
// toggle to use slideTo (true) or just place the cursor (false) on click
animatePoint: true,
// slideDuration: Integer
// time in ms picker node will slide to next location (non-dragging) when animatePoint=true
slideDuration: 250,
// liveUpdate: Boolean
// Set to true to fire onChange in an indeterminate way
liveUpdate: false,
// PICKER_HUE_H: int
// Height of the hue picker, used to calculate positions
PICKER_HUE_H: 150,
// PICKER_SAT_VAL_H: int
// Height of the 2d picker, used to calculate positions
PICKER_SAT_VAL_H: 150,
// PICKER_SAT_VAL_W: int
// Width of the 2d picker, used to calculate positions
PICKER_SAT_VAL_W: 150,
// PICKER_HUE_SELECTOR_H: int
// Height of the hue selector DOM node, used to calc offsets so that selection
// is center of the image node.
PICKER_HUE_SELECTOR_H: 8,
// PICKER_SAT_SELECTOR_H: int
// Height of the saturation selector DOM node, used to calc offsets so that selection
// is center of the image node.
PICKER_SAT_SELECTOR_H: 10,
// PICKER_SAT_SELECTOR_W: int
// Width of the saturation selector DOM node, used to calc offsets so that selection
// is center of the image node.
PICKER_SAT_SELECTOR_W: 10,
// value: String
// Default color for this component. Only hex values are accepted as incoming/returned
// values. Adjust this value with `.attr`, eg: dijit.byId("myPicker").attr("value", "#ededed");
// to cause the points to adjust and the values to reflect the current color.
value: "#ffffff",
_underlay: require.toUrl("dojox/widget/ColorPicker/images/underlay.png"),
_hueUnderlay: require.toUrl("dojox/widget/ColorPicker/images/hue.png"),
_pickerPointer: require.toUrl("dojox/widget/ColorPicker/images/pickerPointer.png"),
_huePickerPointer: require.toUrl("dojox/widget/ColorPicker/images/hueHandle.png"),
_huePickerPointerAlly: require.toUrl("dojox/widget/ColorPicker/images/hueHandleA11y.png"),
templateString: template,
postMixInProperties: function(){
if(DOMClass.contains(win.body(), "dijit_a11y")){
// Use the pointer that will show up in high contrast.
this._huePickerPointer = this._huePickerPointerAlly;
}
this._uId = registry.getUniqueId(this.id);
lang.mixin(this, i18n.getLocalization("dojox.widget", "ColorPicker"));
lang.mixin(this, i18n.getLocalization("dojo.cldr", "number"));
this.inherited(arguments);
},
postCreate: function(){
// summary:
// As quickly as we can, set up ie6 alpha-filter support for our
// underlay. we don't do image handles (done in css), just the 'core'
// of this widget: the underlay.
this.inherited(arguments);
if(has("ie") < 7){
this.colorUnderlay.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this._underlay+"', sizingMethod='scale')";
this.colorUnderlay.src = this._blankGif.toString();
}
// hide toggle-able nodes:
if(!this.showRgb){ this.rgbNode.style.visibility = "hidden"; }
if(!this.showHsv){ this.hsvNode.style.visibility = "hidden"; }
if(!this.showHex){ this.hexNode.style.visibility = "hidden"; }
if(!this.webSafe){ this.safePreviewNode.style.visibility = "hidden"; }
},
startup: function(){
if(this._started){
return;
}
this._started = true;
this.set("value", this.value);
this._mover = new move.boxConstrainedMoveable(this.cursorNode, {
box: {
t: -(this.PICKER_SAT_SELECTOR_H/2),
l: -(this.PICKER_SAT_SELECTOR_W/2),
w:this.PICKER_SAT_VAL_W,
h:this.PICKER_SAT_VAL_H
}
});
this._hueMover = new move.boxConstrainedMoveable(this.hueCursorNode, {
box: {
t: -(this.PICKER_HUE_SELECTOR_H/2),
l:0,
w:0,
h:this.PICKER_HUE_H
}
});
this._subs = [];
// no dnd/move/move published ... use a timer:
this._subs.push(Hub.subscribe("/dnd/move/stop", lang.hitch(this, "_clearTimer")));
this._subs.push(Hub.subscribe("/dnd/move/start", lang.hitch(this, "_setTimer")));
// Bind to up, down, left and right arrows on the hue and saturation nodes.
this._keyListeners = [];
this._connects.push(Typematic.addKeyListener(this.hueCursorNode,{
charOrCode: Keys.UP_ARROW,
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}, this, lang.hitch(this, this._updateHueCursorNode), 25, 25));
this._connects.push(Typematic.addKeyListener(this.hueCursorNode,{
charOrCode: Keys.DOWN_ARROW,
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}, this, lang.hitch(this, this._updateHueCursorNode), 25, 25));
this._connects.push(Typematic.addKeyListener(this.cursorNode,{
charOrCode: Keys.UP_ARROW,
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}, this, lang.hitch(this, this._updateCursorNode), 25, 25));
this._connects.push(Typematic.addKeyListener(this.cursorNode,{
charOrCode: Keys.DOWN_ARROW,
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}, this, lang.hitch(this, this._updateCursorNode), 25, 25));
this._connects.push(Typematic.addKeyListener(this.cursorNode,{
charOrCode: Keys.LEFT_ARROW,
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}, this, lang.hitch(this, this._updateCursorNode), 25, 25));
this._connects.push(Typematic.addKeyListener(this.cursorNode,{
charOrCode: Keys.RIGHT_ARROW,
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}, this, lang.hitch(this, this._updateCursorNode), 25, 25));
},
_setValueAttr: function(value, fireOnChange){
if(!this._started){ return; }
this.setColor(value, fireOnChange);
},
setColor: function(/* String */col, force){
// summary:
// Set a color on a picker. Usually used to set
// initial color as an alternative to passing defaultColor option
// to the constructor.
col = color.fromString(col);
this._updatePickerLocations(col);
this._updateColorInputs(col);
this._updateValue(col, force);
},
_setTimer: function(/* dojo/dnd/Mover */mover){
if(mover.node != this.cursorNode && mover.node != this.hueCursorNode){ return; }
// FIXME: should I assume this? focus on mouse down so on mouse up
FocusManager.focus(mover.node);
DOM.setSelectable(this.domNode,false);
this._timer = setInterval(lang.hitch(this, "_updateColor"), 45);
},
_clearTimer: function(/* dojo/dnd/Mover */mover){
if(!this._timer){ return; }
clearInterval(this._timer);
this._timer = null;
this.onChange(this.value);
DOM.setSelectable(this.domNode,true);
},
_setHue: function(/* Float */h){
// summary:
// Sets a natural color background for the
// underlay image against closest hue value (full saturation)
// h:
// 0..360
html.style(this.colorUnderlay, "backgroundColor", color.fromHsv(h,100,100).toHex());
},
_updateHueCursorNode: function(count, node, e){
// summary:
// Function used by the typematic code to handle cursor position and update
// via keyboard.
// count: Number
// -1 means stop, anything else is just how many times it was called.
// node: DomNode
// The node generating the event.
// e: Event
// The event.
if(count !== -1){
var y = html.style(this.hueCursorNode, "top");
var selCenter = this.PICKER_HUE_SELECTOR_H/2;
// Account for our offset
y += selCenter;
var update = false;
if(e.charOrCode == Keys.UP_ARROW){
if(y > 0){
y -= 1;
update = true;
}
}else if(e.charOrCode == Keys.DOWN_ARROW){
if(y < this.PICKER_HUE_H){
y += 1;
update = true;
}
}
y -= selCenter;
if(update){
html.style(this.hueCursorNode, "top", y + "px");
}
}else{
this._updateColor(true);
}
},
_updateCursorNode: function(count, node, e){
// summary:
// Function used by the typematic code to handle cursor position and update
// via keyboard.
// count:
// -1 means stop, anything else is just how many times it was called.
// node:
// The node generating the event.
// e:
// The event.
var selCenterH = this.PICKER_SAT_SELECTOR_H/2;
var selCenterW = this.PICKER_SAT_SELECTOR_W/2;
if(count !== -1){
var y = html.style(this.cursorNode, "top");
var x = html.style(this.cursorNode, "left");
// Account for our offsets to center
y += selCenterH;
x += selCenterW;
var update = false;
if(e.charOrCode == Keys.UP_ARROW){
if(y > 0){
y -= 1;
update = true;
}
}else if(e.charOrCode == Keys.DOWN_ARROW){
if(y < this.PICKER_SAT_VAL_H){
y += 1;
update = true;
}
}else if(e.charOrCode == Keys.LEFT_ARROW){
if(x > 0){
x -= 1;
update = true;
}
}else if(e.charOrCode == Keys.RIGHT_ARROW){
if(x < this.PICKER_SAT_VAL_W){
x += 1;
update = true;
}
}
if(update){
// Account for our offsets to center
y -= selCenterH;
x -= selCenterW;
html.style(this.cursorNode, "top", y + "px");
html.style(this.cursorNode, "left", x + "px");
}
}else{
this._updateColor(true);
}
},
_updateColor: function(fireChange){
// summary:
// update the previewNode color, and input values [optional]
var hueSelCenter = this.PICKER_HUE_SELECTOR_H/2,
satSelCenterH = this.PICKER_SAT_SELECTOR_H/2,
satSelCenterW = this.PICKER_SAT_SELECTOR_W/2;
var _huetop = html.style(this.hueCursorNode,"top") + hueSelCenter,
_pickertop = html.style(this.cursorNode,"top") + satSelCenterH,
_pickerleft = html.style(this.cursorNode,"left") + satSelCenterW,
h = Math.round(360 - (_huetop / this.PICKER_HUE_H * 360)),
col = color.fromHsv(h, _pickerleft / this.PICKER_SAT_VAL_W * 100, 100 - (_pickertop / this.PICKER_SAT_VAL_H * 100))
;
this._updateColorInputs(col);
this._updateValue(col, fireChange);
// update hue, not all the pickers
if(h!=this._hue){
this._setHue(h);
}
},
_colorInputChange: function(e){
// summary:
// updates picker position and inputs
// according to rgb, hex or hsv input changes
var col, hasit = false;
switch(e.target){
//transform to hsv to pixels
case this.hexCode:
col = color.fromString(e.target.value);
hasit = true;
break;
case this.Rval:
case this.Gval:
case this.Bval:
col = color.fromArray([this.Rval.value, this.Gval.value, this.Bval.value]);
hasit = true;
break;
case this.Hval:
case this.Sval:
case this.Vval:
col = color.fromHsv(this.Hval.value, this.Sval.value, this.Vval.value);
hasit = true;
break;
}
if(hasit){
this._updatePickerLocations(col);
this._updateColorInputs(col);
this._updateValue(col, true);
}
},
_updateValue: function(/* dojox/color/Color */col, /* Boolean */fireChange){
// summary:
// updates the value of the widget
// can cancel reverse onChange by specifying second param
var hex = col.toHex();
this.value = this.valueNode.value = hex;
// anytime we muck with the color, fire onChange?
if(fireChange && (!this._timer || this.liveUpdate)){
this.onChange(hex);
}
},
_updatePickerLocations: function(/* dojox/color/Color */col){
// summary:
// update handles on the pickers acording to color values
var hueSelCenter = this.PICKER_HUE_SELECTOR_H/2,
satSelCenterH = this.PICKER_SAT_SELECTOR_H/2,
satSelCenterW = this.PICKER_SAT_SELECTOR_W/2;
var hsv = col.toHsv(),
ypos = Math.round(this.PICKER_HUE_H - hsv.h / 360 * this.PICKER_HUE_H) - hueSelCenter,
newLeft = Math.round(hsv.s / 100 * this.PICKER_SAT_VAL_W) - satSelCenterW,
newTop = Math.round(this.PICKER_SAT_VAL_H - hsv.v / 100 * this.PICKER_SAT_VAL_H) - satSelCenterH
;
if(this.animatePoint){
fx.slideTo({
node: this.hueCursorNode,
duration: this.slideDuration,
top: ypos,
left: 0
}).play();
fx.slideTo({
node: this.cursorNode,
duration: this.slideDuration,
top: newTop,
left: newLeft
}).play();
}
else {
html.style(this.hueCursorNode, "top", ypos + "px");
html.style(this.cursorNode, {
left: newLeft + "px",
top: newTop + "px"
});
}
// limit hue calculations to only when it changes
if(hsv.h != this._hue){
this._setHue(hsv.h);
}
},
_updateColorInputs: function(/* dojox/color/Color */ col){
// summary:
// updates color inputs that were changed through other inputs
// or by clicking on the picker
var hex = col.toHex();
if(this.showRgb){
this.Rval.value = col.r;
this.Gval.value = col.g;
this.Bval.value = col.b;
}
if(this.showHsv){
var hsv = col.toHsv();
this.Hval.value = Math.round((hsv.h)); // convert to 0..360
this.Sval.value = Math.round(hsv.s);
this.Vval.value = Math.round(hsv.v);
}
if(this.showHex){
this.hexCode.value = hex;
}
this.previewNode.style.backgroundColor = hex;
if(this.webSafe){
this.safePreviewNode.style.backgroundColor = webSafeFromHex(hex);
}
},
_setHuePoint: function(/* Event */evt){
// summary:
// set the hue picker handle on relative y coordinates
//#13268 Fix for IE and Edge, as they don't support evt.layerX/Y
var selCenter = this.PICKER_HUE_SELECTOR_H/2;
var ypos = evt.layerY || (evt.y - evt.target.getBoundingClientRect().top);
ypos -= selCenter;
if(this.animatePoint){
fx.slideTo({
node: this.hueCursorNode,
duration:this.slideDuration,
top: ypos,
left: 0,
onEnd: lang.hitch(this, function(){ this._updateColor(true); FocusManager.focus(this.hueCursorNode); })
}).play();
}else{
html.style(this.hueCursorNode, "top", ypos + "px");
this._updateColor(true);
}
},
_setPoint: function(/* Event */evt){
// summary:
// set our picker point based on relative x/y coordinates
// evt.preventDefault();
var satSelCenterH = this.PICKER_SAT_SELECTOR_H/2;
var satSelCenterW = this.PICKER_SAT_SELECTOR_W/2;
//#13268 Fix for IE and Edge, as they don't support evt.layerX/Y
var newTop = evt.layerY || (evt.y - evt.target.getBoundingClientRect().top);
newTop -= satSelCenterH;
var newLeft = evt.layerX || (evt.x - evt.target.getBoundingClientRect().left);
newLeft -= satSelCenterW;
if(evt){ FocusManager.focus(evt.target); }
if(this.animatePoint){
fx.slideTo({
node: this.cursorNode,
duration: this.slideDuration,
top: newTop,
left: newLeft,
onEnd: lang.hitch(this, function(){ this._updateColor(true); FocusManager.focus(this.cursorNode); })
}).play();
}else{
html.style(this.cursorNode, {
left: newLeft + "px",
top: newTop + "px"
});
this._updateColor(true);
}
},
_handleKey: function(/* Event */e){
// TODO: not implemented YET
// var keys = d.keys;
},
focus: function(){
// summary:
// Put focus on this widget, only if focus isn't set on it already.
if(!this.focused){
FocusManager.focus(this.focusNode);
}
},
_stopDrag: function(e){
// summary:
// Function to halt the mouse down default
// to disable dragging of images out of the color
// picker.
Event.stop(e);
},
destroy: function(){
// summary:
// Over-ride to clean up subscriptions, etc.
this.inherited(arguments);
ArrayUtil.forEach(this._subs, function(sub){
Hub.unsubscribe(sub);
});
delete this._subs;
}
});
});