2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * Provides color conversion and validation utils
9 * @class YAHOO.util.Color
10 * @namespace YAHOO.util
12 YAHOO.util.Color = function() {
15 isArray = YAHOO.lang.isArray,
16 isNumber = YAHOO.lang.isNumber;
21 * Converts 0-1 to 0-255
23 * @param n {float} the number to convert
24 * @return {int} a number 0-255
26 real2dec: function(n) {
27 return Math.min(255, Math.round(n*256));
31 * Converts HSV (h[0-360], s[0-1]), v[0-1] to RGB [255,255,255]
33 * @param h {int|[int, float, float]} the hue, or an
34 * array containing all three parameters
35 * @param s {float} the saturation
36 * @param v {float} the value/brightness
37 * @return {[int, int, int]} the red, green, blue values in
40 hsv2rgb: function(h, s, v) {
43 return this.hsv2rgb.call(this, h[0], h[1], h[2]);
47 i = Math.floor((h/60)%6),
55 case 0: r=v; g=t; b=p; break;
56 case 1: r=q; g=v; b=p; break;
57 case 2: r=p; g=v; b=t; break;
58 case 3: r=p; g=q; b=v; break;
59 case 4: r=t; g=p; b=v; break;
60 case 5: r=v; g=p; b=q; break;
65 return [fn(r), fn(g), fn(b)];
69 * Converts to RGB [255,255,255] to HSV (h[0-360], s[0-1]), v[0-1]
71 * @param r {int|[int, int, int]} the red value, or an
72 * array containing all three parameters
73 * @param g {int} the green value
74 * @param b {int} the blue value
75 * @return {[int, float, float]} the value converted to hsv
77 rgb2hsv: function(r, g, b) {
80 return this.rgb2hsv.apply(this, r);
88 min = Math.min(Math.min(r,g),b),
89 max = Math.max(Math.max(r,g),b),
95 case r: h=60*(g-b)/delta;
100 case g: h=(60*(b-r)/delta)+120; break;
101 case b: h=(60*(r-g)/delta)+240; break;
104 s = (max === 0) ? 0 : 1-(min/max);
106 hsv = [Math.round(h), s, max];
112 * Converts decimal rgb values into a hex string
113 * 255,255,255 -> FFFFFF
115 * @param r {int|[int, int, int]} the red value, or an
116 * array containing all three parameters
117 * @param g {int} the green value
118 * @param b {int} the blue value
119 * @return {string} the hex string
121 rgb2hex: function(r, g, b) {
123 return this.rgb2hex.apply(this, r);
127 return f(r) + f(g) + f(b);
131 * Converts an int 0...255 to hex pair 00...FF
133 * @param n {int} the number to convert
134 * @return {string} the hex equivalent
136 dec2hex: function(n) {
137 n = parseInt(n,10)|0;
138 n = (n > 255 || n < 0) ? 0 : n;
140 return (ZERO+n.toString(16)).slice(-2).toUpperCase();
144 * Converts a hex pair 00...FF to an int 0...255
146 * @param str {string} the hex pair to convert
147 * @return {int} the decimal
149 hex2dec: function(str) {
150 return parseInt(str,16);
154 * Converts a hex string to rgb
156 * @param str {string} the hex string
157 * @return {[int, int, int]} an array containing the rgb values
159 hex2rgb: function(s) {
160 var f = this.hex2dec;
161 return [f(s.slice(0, 2)), f(s.slice(2, 4)), f(s.slice(4, 6))];
165 * Returns the closest websafe color to the supplied rgb value.
167 * @param r {int|[int, int, int]} the red value, or an
168 * array containing all three parameters
169 * @param g {int} the green value
170 * @param b {int} the blue value
171 * @return {[int, int, int]} an array containing the closes
172 * websafe rgb colors.
174 websafe: function(r, g, b) {
177 return this.websafe.apply(this, r);
180 // returns the closest match [0, 51, 102, 153, 204, 255]
181 var f = function(v) {
183 v = Math.min(Math.max(0, v), 255);
185 for (i=0; i<256; i=i+51) {
187 if (v >= i && v <= next) {
188 return (v-i > 25) ? next : i;
191 YAHOO.log("Error calculating the websafe value for " + v, "warn");
197 return [f(r), f(g), f(b)];
204 * The colorpicker module provides a widget for selecting colors
205 * @module colorpicker
206 * @requires yahoo, dom, event, element, slider
210 var _pickercount = 0,
213 Slider = YAHOO.widget.Slider,
217 sub = lang.substitute,
223 * A widget to select colors
224 * @namespace YAHOO.widget
225 * @class YAHOO.widget.ColorPicker
226 * @extends YAHOO.util.Element
228 * @param {HTMLElement | String | Object} el(optional) The html
229 * element that represents the colorpicker, or the attribute object to use.
230 * An element will be created if none provided.
231 * @param {Object} attr (optional) A key map of the colorpicker's
232 * initial attributes. Ignored if first arg is attributes object.
234 function ColorPicker(el, attr) {
235 _pickercount = _pickercount + 1;
236 this.logger = new YAHOO.widget.LogWriter("ColorPicker");
238 if (arguments.length === 1 && !YAHOO.lang.isString(el) && !el.nodeName) {
239 attr = el; // treat first arg as attr object
240 el = attr.element || null;
243 if (!el && !attr.element) { // create if we dont have one
244 this.logger.log("creating host element");
245 el = this._createHostElement(attr);
248 ColorPicker.superclass.constructor.call(this, el, attr);
253 YAHOO.extend(ColorPicker, YAHOO.util.Element, {
256 * The element ids used by this control
263 * The id for the "red" form field
267 * @default yui-picker-r
272 * The id for the "red" hex pair output
276 * @default yui-picker-rhex
281 * The id for the "green" form field
285 * @default yui-picker-g
290 * The id for the "green" hex pair output
294 * @default yui-picker-ghex
300 * The id for the "blue" form field
304 * @default yui-picker-b
309 * The id for the "blue" hex pair output
313 * @default yui-picker-bhex
318 * The id for the "hue" form field
322 * @default yui-picker-h
327 * The id for the "saturation" form field
331 * @default yui-picker-s
336 * The id for the "value" form field
340 * @default yui-picker-v
345 * The id for the picker region slider
346 * @property ID.PICKER_BG
349 * @default yui-picker-bg
351 PICKER_BG: b + "-bg",
354 * The id for the picker region thumb
355 * @property ID.PICKER_THUMB
358 * @default yui-picker-thumb
360 PICKER_THUMB: b + "-thumb",
363 * The id for the hue slider
364 * @property ID.HUE_BG
367 * @default yui-picker-hue-bg
369 HUE_BG: b + "-hue-bg",
372 * The id for the hue thumb
373 * @property ID.HUE_THUMB
376 * @default yui-picker-hue-thumb
378 HUE_THUMB: b + "-hue-thumb",
381 * The id for the hex value form field
385 * @default yui-picker-hex
390 * The id for the color swatch
391 * @property ID.SWATCH
394 * @default yui-picker-swatch
396 SWATCH: b + "-swatch",
399 * The id for the websafe color swatch
400 * @property ID.WEBSAFE_SWATCH
403 * @default yui-picker-websafe-swatch
405 WEBSAFE_SWATCH: b + "-websafe-swatch",
408 * The id for the control details
409 * @property ID.CONTROLS
411 * @default yui-picker-controls
413 CONTROLS: b + "-controls",
416 * The id for the rgb controls
417 * @property ID.RGB_CONTROLS
419 * @default yui-picker-rgb-controls
421 RGB_CONTROLS: b + "-rgb-controls",
424 * The id for the hsv controls
425 * @property ID.HSV_CONTROLS
427 * @default yui-picker-hsv-controls
429 HSV_CONTROLS: b + "-hsv-controls",
432 * The id for the hsv controls
433 * @property ID.HEX_CONTROLS
435 * @default yui-picker-hex-controls
437 HEX_CONTROLS: b + "-hex-controls",
440 * The id for the hex summary
441 * @property ID.HEX_SUMMARY
443 * @default yui-picker-hex-summary
445 HEX_SUMMARY: b + "-hex-summary",
448 * The id for the controls section header
449 * @property ID.CONTROLS_LABEL
451 * @default yui-picker-controls-label
453 CONTROLS_LABEL: b + "-controls-label"
457 * Constants for any script-generated messages. The values here
458 * are the default messages. They can be updated by providing
459 * the complete list to the constructor for the "txt" attribute.
464 ILLEGAL_HEX: "Illegal hex value entered",
465 SHOW_CONTROLS: "Show color details",
466 HIDE_CONTROLS: "Hide color details",
467 CURRENT_COLOR: "Currently selected color: {rgb}",
468 CLOSEST_WEBSAFE: "Closest websafe color: {rgb}. Click to select.",
481 * Constants for the default image locations for img tags that are
482 * generated by the control. They can be modified by passing the
483 * complete list to the contructor for the "images" attribute
488 PICKER_THUMB: "../../build/colorpicker/assets/picker_thumb.png",
489 HUE_THUMB: "../../build/colorpicker/assets/hue_thumb.png"
493 * Constants for the control's default default values
502 * Constants for the control's configuration attributes
508 SATURATION : "saturation",
517 PICKER_SIZE : "pickersize",
518 SHOW_CONTROLS : "showcontrols",
519 SHOW_RGB_CONTROLS : "showrgbcontrols",
520 SHOW_HSV_CONTROLS : "showhsvcontrols",
521 SHOW_HEX_CONTROLS : "showhexcontrols",
522 SHOW_HEX_SUMMARY : "showhexsummary",
523 SHOW_WEBSAFE : "showwebsafe",
524 CONTAINER : "container",
526 ELEMENTS : "elements",
533 * Flag to allow individual UI updates to forego animation if available.
534 * True during construction for initial thumb placement. Set to false
544 * Creates the host element if it doesn't exist
545 * @method _createHostElement
548 _createHostElement : function () {
549 var el = document.createElement('div');
552 el.className = this.CSS.BASE;
559 * Moves the hue slider into the position dictated by the current state
561 * @method _updateHueSlider
564 _updateHueSlider : function() {
565 var size = this.get(this.OPT.PICKER_SIZE),
566 h = this.get(this.OPT.HUE);
568 h = size - Math.round(h / 360 * size);
570 // 0 is at the top and bottom of the hue slider. Always go to
571 // the top so we don't end up sending the thumb to the bottom
572 // when the value didn't actually change (e.g., a conversion
573 // produced 360 instead of 0 and the value was already 0).
577 this.logger.log("Hue slider is being set to " + h);
579 this.hueSlider.setValue(h, this.skipAnim);
583 * Moves the picker slider into the position dictated by the current state
585 * @method _updatePickerSlider
588 _updatePickerSlider : function() {
589 var size = this.get(this.OPT.PICKER_SIZE),
590 s = this.get(this.OPT.SATURATION),
591 v = this.get(this.OPT.VALUE);
593 s = Math.round(s * size / 100);
594 v = Math.round(size - (v * size / 100));
596 this.logger.log("Setting picker slider to " + [s, v]);
598 this.pickerSlider.setRegionValue(s, v, this.skipAnim);
602 * Moves the sliders into the position dictated by the current state
604 * @method _updateSliders
607 _updateSliders : function() {
608 this._updateHueSlider();
609 this._updatePickerSlider();
613 * Sets the control to the specified rgb value and
614 * moves the sliders to the proper positions
616 * @param rgb {[int, int, int]} the rgb value
617 * @param silent {boolean} whether or not to fire the change event
619 setValue : function(rgb, silent) {
620 silent = (silent) || false;
621 this.set(this.OPT.RGB, rgb, silent);
622 this._updateSliders();
627 * @property hueSlider
628 * @type YAHOO.widget.Slider
634 * @property pickerSlider
635 * @type YAHOO.widget.Slider
640 * Translates the slider value into hue, int[0,359]
643 * @return {int} the hue from 0 to 359
646 var size = this.get(this.OPT.PICKER_SIZE),
647 h = (size - this.hueSlider.getValue()) / size;
648 h = Math.round(h*360);
649 return (h === 360) ? 0 : h;
653 * Translates the slider value into saturation, int[0,1], left to right
656 * @return {int} the saturation from 0 to 1
659 return this.pickerSlider.getXValue() / this.get(this.OPT.PICKER_SIZE);
663 * Translates the slider value into value/brightness, int[0,1], top
667 * @return {int} the value from 0 to 1
670 var size = this.get(this.OPT.PICKER_SIZE);
671 return (size - this.pickerSlider.getYValue()) / size;
675 * Updates the background of the swatch with the current rbg value.
676 * Also updates the websafe swatch to the closest websafe color
677 * @method _updateSwatch
680 _updateSwatch : function() {
681 var rgb = this.get(this.OPT.RGB),
682 websafe = this.get(this.OPT.WEBSAFE),
683 el = this.getElement(this.ID.SWATCH),
684 color = rgb.join(","),
685 txt = this.get(this.OPT.TXT);
687 Dom.setStyle(el, "background-color", "rgb(" + color + ")");
688 el.title = sub(txt.CURRENT_COLOR, {
689 "rgb": "#" + this.get(this.OPT.HEX)
693 el = this.getElement(this.ID.WEBSAFE_SWATCH);
694 color = websafe.join(",");
696 Dom.setStyle(el, "background-color", "rgb(" + color + ")");
697 el.title = sub(txt.CLOSEST_WEBSAFE, {
698 "rgb": "#" + Color.rgb2hex(websafe)
704 * Reads the sliders and converts the values to RGB, updating the
705 * internal state for all the individual form fields
706 * @method _getValuesFromSliders
709 _getValuesFromSliders : function() {
710 this.logger.log("hsv " + [this._getH(),this._getS(),this._getV()]);
711 this.set(this.OPT.RGB, Color.hsv2rgb(this._getH(), this._getS(), this._getV()));
715 * Updates the form field controls with the state data contained
717 * @method _updateFormFields
720 _updateFormFields : function() {
721 this.getElement(this.ID.H).value = this.get(this.OPT.HUE);
722 this.getElement(this.ID.S).value = this.get(this.OPT.SATURATION);
723 this.getElement(this.ID.V).value = this.get(this.OPT.VALUE);
724 this.getElement(this.ID.R).value = this.get(this.OPT.RED);
725 this.getElement(this.ID.R_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.RED));
726 this.getElement(this.ID.G).value = this.get(this.OPT.GREEN);
727 this.getElement(this.ID.G_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.GREEN));
728 this.getElement(this.ID.B).value = this.get(this.OPT.BLUE);
729 this.getElement(this.ID.B_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.BLUE));
730 this.getElement(this.ID.HEX).value = this.get(this.OPT.HEX);
734 * Event handler for the hue slider.
735 * @method _onHueSliderChange
736 * @param newOffset {int} pixels from the start position
739 _onHueSliderChange : function(newOffset) {
740 this.logger.log("hue update: " + newOffset , "warn");
742 var h = this._getH(),
743 rgb = Color.hsv2rgb(h, 1, 1),
744 styleDef = "rgb(" + rgb.join(",") + ")";
746 this.set(this.OPT.HUE, h, true);
748 // set picker background to the hue
749 Dom.setStyle(this.getElement(this.ID.PICKER_BG), "background-color", styleDef);
751 if (this.hueSlider.valueChangeSource !== Slider.SOURCE_SET_VALUE) {
752 this._getValuesFromSliders();
755 this._updateFormFields();
756 this._updateSwatch();
760 * Event handler for the picker slider, which controls the
761 * saturation and value/brightness.
762 * @method _onPickerSliderChange
763 * @param newOffset {{x: int, y: int}} x/y pixels from the start position
766 _onPickerSliderChange : function(newOffset) {
767 this.logger.log(sub("picker update [{x}, {y}]", newOffset));
769 var s=this._getS(), v=this._getV();
770 this.set(this.OPT.SATURATION, Math.round(s*100), true);
771 this.set(this.OPT.VALUE, Math.round(v*100), true);
773 if (this.pickerSlider.valueChangeSource !== Slider.SOURCE_SET_VALUE) {
774 this._getValuesFromSliders();
777 this._updateFormFields();
778 this._updateSwatch();
783 * Key map to well-known commands for txt field input
784 * @method _getCommand
785 * @param e {Event} the keypress or keydown event
786 * @return {int} a command code
788 * <li>0 = not a number, letter in range, or special key</li>
789 * <li>1 = number</li>
790 * <li>2 = a-fA-F</li>
791 * <li>3 = increment (up arrow)</li>
792 * <li>4 = decrement (down arrow)</li>
793 * <li>5 = special key (tab, delete, return, escape, left, right)</li>
794 * <li>6 = return</li>
798 _getCommand : function(e) {
799 var c = Event.getCharCode(e);
801 //alert(Event.getCharCode(e) + ", " + e.keyCode + ", " + e.charCode);
804 if (c === 38) { // up arrow
806 } else if (c === 13) { // return
808 } else if (c === 40) { // down array
810 } else if (c >= 48 && c<=57) { // 0-9
812 } else if (c >= 97 && c<=102) { // a-f
814 } else if (c >= 65 && c<=70) { // A-F
816 //} else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1 ||
817 // (c >= 112 && c <=123)) { // including F-keys
818 // tab, delete, return, escape, left, right or ctrl/meta sequences
819 } else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1 ||
820 e.ctrlKey || e.metaKey) { // special chars
822 } else { // something we probably don't want
828 * Use the value of the text field to update the control
829 * @method _useFieldValue
830 * @param e {Event} an event
831 * @param el {HTMLElement} the field
832 * @param prop {string} the key to the linked property
835 _useFieldValue : function(e, el, prop) {
838 if (prop !== this.OPT.HEX) {
839 val = parseInt(val, 10);
842 if (val !== this.get(prop)) {
848 * Handle keypress on one of the rgb or hsv fields.
849 * @method _rgbFieldKeypress
850 * @param e {Event} the keypress event
851 * @param el {HTMLElement} the field
852 * @param prop {string} the key to the linked property
855 _rgbFieldKeypress : function(e, el, prop) {
856 var command = this._getCommand(e),
857 inc = (e.shiftKey) ? 10 : 1;
859 case 6: // return, update the value
860 this._useFieldValue.apply(this, arguments);
863 case 3: // up arrow, increment
864 this.set(prop, Math.min(this.get(prop)+inc, 255));
865 this._updateFormFields();
866 //Event.stopEvent(e);
868 case 4: // down arrow, decrement
869 this.set(prop, Math.max(this.get(prop)-inc, 0));
870 this._updateFormFields();
871 //Event.stopEvent(e);
880 * Handle keydown on the hex field
881 * @method _hexFieldKeypress
882 * @param e {Event} the keypress event
883 * @param el {HTMLElement} the field
884 * @param prop {string} the key to the linked property
887 _hexFieldKeypress : function(e, el, prop) {
888 var command = this._getCommand(e);
889 if (command === 6) { // return, update the value
890 this._useFieldValue.apply(this, arguments);
895 * Allows numbers and special chars, and by default allows a-f.
896 * Used for the hex field keypress handler.
898 * @param e {Event} the event
899 * @param numbersOnly omits a-f if set to true
901 * @return {boolean} false if we are canceling the event
903 _hexOnly : function(e, numbersOnly) {
904 var command = this._getCommand(e);
907 case 5: // special char
910 case 2: // hex char (a-f)
911 if (numbersOnly !== true) {
915 // fallthrough is intentional
917 default: // prevent alpha and punctuation
924 * Allows numbers and special chars only. Used for the
925 * rgb and hsv fields keypress handler.
926 * @method _numbersOnly
927 * @param e {Event} the event
929 * @return {boolean} false if we are canceling the event
931 _numbersOnly : function(e) {
932 return this._hexOnly(e, true);
936 * Returns the element reference that is saved. The id can be either
937 * the element id, or the key for this id in the "id" config attribute.
938 * For instance, the host element id can be obtained by passing its
939 * id (default: "yui_picker") or by its key "YUI_PICKER".
940 * @param id {string} the element id, or key
941 * @return {HTMLElement} a reference to the element
943 getElement : function(id) {
944 return this.get(this.OPT.ELEMENTS)[this.get(this.OPT.IDS)[id]];
947 _createElements : function() {
948 this.logger.log("Building markup");
949 var el, child, img, fld, p,
950 ids = this.get(this.OPT.IDS),
951 txt = this.get(this.OPT.TXT),
952 images = this.get(this.OPT.IMAGES),
953 Elem = function(type, o) {
954 var n = document.createElement(type);
956 lang.augmentObject(n, o, true);
960 RGBElem = function(type, obj) {
970 return new Elem(type, o);
973 p = this.get("element");
975 // Picker slider (S and V) ---------------------------------------------
977 el = new Elem("div", {
978 id: ids[this.ID.PICKER_BG],
979 className: "yui-picker-bg",
984 child = new Elem("div", {
985 id: ids[this.ID.PICKER_THUMB],
986 className: "yui-picker-thumb"
989 img = new Elem("img", {
990 src: images.PICKER_THUMB
993 child.appendChild(img);
994 el.appendChild(child);
997 // Hue slider ---------------------------------------------
998 el = new Elem("div", {
999 id: ids[this.ID.HUE_BG],
1000 className: "yui-picker-hue-bg",
1005 child = new Elem("div", {
1006 id: ids[this.ID.HUE_THUMB],
1007 className: "yui-picker-hue-thumb"
1010 img = new Elem("img", {
1011 src: images.HUE_THUMB
1014 child.appendChild(img);
1015 el.appendChild(child);
1019 // controls ---------------------------------------------
1021 el = new Elem("div", {
1022 id: ids[this.ID.CONTROLS],
1023 className: "yui-picker-controls"
1030 el = new Elem("div", {
1034 child = new Elem("a", {
1035 id: ids[this.ID.CONTROLS_LABEL],
1036 //className: "yui-picker-controls-label",
1039 el.appendChild(child);
1043 el = new Elem("div", {
1051 el = new Elem("ul", {
1052 id: ids[this.ID.RGB_CONTROLS],
1053 className: "yui-picker-rgb-controls"
1056 child = new Elem("li");
1057 child.appendChild(document.createTextNode(txt.R + " "));
1059 fld = new RGBElem("input", {
1061 className: "yui-picker-r"
1064 child.appendChild(fld);
1065 el.appendChild(child);
1067 child = new Elem("li");
1068 child.appendChild(document.createTextNode(txt.G + " "));
1070 fld = new RGBElem("input", {
1072 className: "yui-picker-g"
1075 child.appendChild(fld);
1076 el.appendChild(child);
1078 child = new Elem("li");
1079 child.appendChild(document.createTextNode(txt.B + " "));
1081 fld = new RGBElem("input", {
1083 className: "yui-picker-b"
1086 child.appendChild(fld);
1087 el.appendChild(child);
1092 el = new Elem("ul", {
1093 id: ids[this.ID.HSV_CONTROLS],
1094 className: "yui-picker-hsv-controls"
1097 child = new Elem("li");
1098 child.appendChild(document.createTextNode(txt.H + " "));
1100 fld = new RGBElem("input", {
1102 className: "yui-picker-h"
1105 child.appendChild(fld);
1106 child.appendChild(document.createTextNode(" " + txt.DEG));
1108 el.appendChild(child);
1110 child = new Elem("li");
1111 child.appendChild(document.createTextNode(txt.S + " "));
1113 fld = new RGBElem("input", {
1115 className: "yui-picker-s"
1118 child.appendChild(fld);
1119 child.appendChild(document.createTextNode(" " + txt.PERCENT));
1121 el.appendChild(child);
1123 child = new Elem("li");
1124 child.appendChild(document.createTextNode(txt.V + " "));
1126 fld = new RGBElem("input", {
1128 className: "yui-picker-v"
1131 child.appendChild(fld);
1132 child.appendChild(document.createTextNode(" " + txt.PERCENT));
1134 el.appendChild(child);
1140 el = new Elem("ul", {
1141 id: ids[this.ID.HEX_SUMMARY],
1142 className: "yui-picker-hex_summary"
1145 child = new Elem("li", {
1146 id: ids[this.ID.R_HEX]
1148 el.appendChild(child);
1150 child = new Elem("li", {
1151 id: ids[this.ID.G_HEX]
1153 el.appendChild(child);
1155 child = new Elem("li", {
1156 id: ids[this.ID.B_HEX]
1158 el.appendChild(child);
1162 el = new Elem("div", {
1163 id: ids[this.ID.HEX_CONTROLS],
1164 className: "yui-picker-hex-controls"
1166 el.appendChild(document.createTextNode(txt.HEX + " "));
1168 child = new RGBElem("input", {
1169 id: ids[this.ID.HEX],
1170 className: "yui-picker-hex",
1175 el.appendChild(child);
1178 p = this.get("element");
1181 el = new Elem("div", {
1182 id: ids[this.ID.SWATCH],
1183 className: "yui-picker-swatch"
1189 el = new Elem("div", {
1190 id: ids[this.ID.WEBSAFE_SWATCH],
1191 className: "yui-picker-websafe-swatch"
1198 _attachRGBHSV : function(id, config) {
1199 Event.on(this.getElement(id), "keydown", function(e, me) {
1200 me._rgbFieldKeypress(e, this, config);
1202 Event.on(this.getElement(id), "keypress", this._numbersOnly, this, true);
1203 Event.on(this.getElement(id), "blur", function(e, me) {
1204 me._useFieldValue(e, this, config);
1210 * Updates the rgb attribute with the current state of the r,g,b
1211 * fields. This is invoked from change listeners on these
1212 * attributes to facilitate updating these values from the
1213 * individual form fields
1214 * @method _updateRGB
1217 _updateRGB : function() {
1218 var rgb = [this.get(this.OPT.RED),
1219 this.get(this.OPT.GREEN),
1220 this.get(this.OPT.BLUE)];
1222 this.logger.log("RGB value set to " + rgb);
1223 this.set(this.OPT.RGB, rgb);
1225 this._updateSliders();
1229 * Creates any missing DOM structure.
1231 * @method _initElements
1234 _initElements : function () {
1235 // bind all of our elements
1237 ids = this.get(o.IDS),
1238 els = this.get(o.ELEMENTS),
1241 // Add the default value as a key for each element for easier lookup
1242 for (i in this.ID) {
1243 if (lang.hasOwnProperty(this.ID, i)) {
1244 ids[this.ID[i]] = ids[i];
1248 // Check for picker element, if not there, create all of them
1249 el = Dom.get(ids[this.ID.PICKER_BG]);
1251 this._createElements();
1253 this.logger.log("Using pre-existing markup");
1257 if (lang.hasOwnProperty(ids, i)) {
1259 el = Dom.get(ids[i]);
1261 // generate an id if the implementer passed in an element reference,
1262 // and the element did not have an id already
1263 id = Dom.generateId(el);
1265 // update the id in case we generated the id
1266 ids[i] = id; // key is WEBSAFE_SWATCH
1267 ids[ids[i]] = id; // key is websafe_swatch
1269 // store the dom ref
1277 * Sets the initial state of the sliders
1278 * @method initPicker
1280 initPicker : function () {
1281 this._initSliders();
1287 * Creates the Hue and Value/Saturation Sliders.
1289 * @method _initSliders
1292 _initSliders : function () {
1294 size = this.get(this.OPT.PICKER_SIZE);
1296 this.logger.log("picker size" + size);
1298 this.hueSlider = Slider.getVertSlider(
1299 this.getElement(ID.HUE_BG),
1300 this.getElement(ID.HUE_THUMB), 0, size);
1302 this.pickerSlider = Slider.getSliderRegion(
1303 this.getElement(ID.PICKER_BG),
1304 this.getElement(ID.PICKER_THUMB), 0, size, 0, size);
1306 // Apply animate attribute configuration
1307 this.set(this.OPT.ANIMATE, this.get(this.OPT.ANIMATE));
1311 * Adds event listeners to Sliders and UI elements. Wires everything
1317 _bindUI : function () {
1321 this.hueSlider.subscribe("change",
1322 this._onHueSliderChange, this, true);
1323 this.pickerSlider.subscribe("change",
1324 this._onPickerSliderChange, this, true);
1326 Event.on(this.getElement(ID.WEBSAFE_SWATCH), "click", function(e) {
1327 this.setValue(this.get(O.WEBSAFE));
1330 Event.on(this.getElement(ID.CONTROLS_LABEL), "click", function(e) {
1331 this.set(O.SHOW_CONTROLS, !this.get(O.SHOW_CONTROLS));
1332 Event.preventDefault(e);
1335 this._attachRGBHSV(ID.R, O.RED);
1336 this._attachRGBHSV(ID.G, O.GREEN);
1337 this._attachRGBHSV(ID.B, O.BLUE);
1338 this._attachRGBHSV(ID.H, O.HUE);
1339 this._attachRGBHSV(ID.S, O.SATURATION);
1340 this._attachRGBHSV(ID.V, O.VALUE);
1342 Event.on(this.getElement(ID.HEX), "keydown", function(e, me) {
1343 me._hexFieldKeypress(e, this, O.HEX);
1346 Event.on(this.getElement(this.ID.HEX), "keypress",
1347 this._hexOnly, this,true);
1348 Event.on(this.getElement(this.ID.HEX), "blur", function(e, me) {
1349 me._useFieldValue(e, this, O.HEX);
1354 * Wrapper for _updateRGB, but allows setting
1357 * @param skipAnim {Boolean} Omit Slider animation for this action
1359 syncUI : function (skipAnim) {
1360 this.skipAnim = skipAnim;
1362 this.skipAnim = false;
1367 * Updates the RGB values from the current state of the HSV
1368 * values. Executed when the one of the HSV form fields are
1373 _updateRGBFromHSV : function() {
1374 var hsv = [this.get(this.OPT.HUE),
1375 this.get(this.OPT.SATURATION)/100,
1376 this.get(this.OPT.VALUE)/100],
1377 rgb = Color.hsv2rgb(hsv);
1379 this.logger.log("HSV converted to RGB " + hsv + " : " + rgb);
1380 this.set(this.OPT.RGB, rgb);
1382 this._updateSliders();
1386 * Parses the hex string to normalize shorthand values, converts
1387 * the hex value to rgb and updates the rgb attribute (which
1388 * updates the state for all of the other values)
1392 _updateHex : function() {
1394 var hex = this.get(this.OPT.HEX),
1398 // support #369 -> #336699 shorthand
1401 for (i=0; i<l; i=i+1) {
1408 if (hex.length !== 6) {
1409 this.logger.log(this.get(this.TXT.ILLEGAL_HEX), "error");
1413 rgb = Color.hex2rgb(hex);
1415 this.logger.log(sub("Hex value set to {hex} ({rgb})", {
1424 * Returns the cached element reference. If the id is not a string, it
1425 * is assumed that it is an element and this is returned.
1426 * @param id {string|HTMLElement} the element key, id, or ref
1427 * @param on {boolean} hide or show. If true, show
1430 _hideShowEl : function(id, on) {
1431 var el = (lang.isString(id) ? this.getElement(id) : id);
1432 Dom.setStyle(el, "display", (on) ? "" : "none");
1437 * Sets up the config attributes and the change listeners for this
1439 * @method initAttributes
1440 * @param attr An object containing default attribute values
1442 initAttributes : function(attr) {
1445 ColorPicker.superclass.initAttributes.call(this, attr);
1448 * The size of the picker. Trying to change this is not recommended.
1449 * @attribute pickersize
1453 this.setAttributeConfig(this.OPT.PICKER_SIZE, {
1454 value: attr.size || this.DEFAULT.PICKER_SIZE
1458 * The current hue value 0-360
1462 this.setAttributeConfig(this.OPT.HUE, {
1463 value: attr.hue || 0,
1464 validator: lang.isNumber
1468 * The current saturation value 0-100
1469 * @attribute saturation
1472 this.setAttributeConfig(this.OPT.SATURATION, {
1473 value: attr.saturation || 0,
1474 validator: lang.isNumber
1478 * The current value/brightness value 0-100
1482 this.setAttributeConfig(this.OPT.VALUE, {
1483 value: lang.isNumber(attr.value) ? attr.value : 100,
1484 validator: lang.isNumber
1488 * The current red value 0-255
1492 this.setAttributeConfig(this.OPT.RED, {
1493 value: lang.isNumber(attr.red) ? attr.red : 255,
1494 validator: lang.isNumber
1498 * The current green value 0-255
1502 this.setAttributeConfig(this.OPT.GREEN, {
1503 value: lang.isNumber(attr.green) ? attr.green : 255,
1504 validator: lang.isNumber
1508 * The current blue value 0-255
1512 this.setAttributeConfig(this.OPT.BLUE, {
1513 value: lang.isNumber(attr.blue) ? attr.blue : 255,
1514 validator: lang.isNumber
1518 * The current hex value #000000-#FFFFFF, without the #
1522 this.setAttributeConfig(this.OPT.HEX, {
1523 value: attr.hex || "FFFFFF",
1524 validator: lang.isString
1528 * The current rgb value. Updates the state of all of the
1529 * other value fields. Read-only: use setValue to set the
1530 * controls rgb value.
1532 * @type [int, int, int]
1535 this.setAttributeConfig(this.OPT.RGB, {
1536 value: attr.rgb || [255,255,255],
1537 method: function(rgb) {
1539 this.set(this.OPT.RED, rgb[0], true);
1540 this.set(this.OPT.GREEN, rgb[1], true);
1541 this.set(this.OPT.BLUE, rgb[2], true);
1543 var websafe = Color.websafe(rgb),
1544 hex = Color.rgb2hex(rgb),
1545 hsv = Color.rgb2hsv(rgb);
1547 this.set(this.OPT.WEBSAFE, websafe, true);
1548 this.set(this.OPT.HEX, hex, true);
1551 this.logger.log(sub("RGB value set to {rgb} (hsv: {hsv})", {
1552 "hsv": hsv, "rgb": rgb
1555 // fix bug #1754338 - when saturation is 0, hue is
1556 // silently always set to 0, but input field not updated
1558 this.set(this.OPT.HUE, hsv[0], true);
1560 this.set(this.OPT.SATURATION, Math.round(hsv[1]*100), true);
1561 this.set(this.OPT.VALUE, Math.round(hsv[2]*100), true);
1567 * If the color picker will live inside of a container object,
1568 * set, provide a reference to it so the control can use the
1569 * container's events.
1570 * @attribute container
1571 * @type YAHOO.widget.Panel
1573 this.setAttributeConfig(this.OPT.CONTAINER, {
1575 method: function(container) {
1577 // Position can get out of sync when the
1578 // control is manipulated while display is
1579 // none. Resetting the slider constraints
1580 // when it is visible gets the state back in
1582 container.showEvent.subscribe(function() {
1583 // this.pickerSlider.thumb.resetConstraints();
1584 // this.hueSlider.thumb.resetConstraints();
1585 this.pickerSlider.focus();
1591 * The closest current websafe value
1592 * @attribute websafe
1595 this.setAttributeConfig(this.OPT.WEBSAFE, {
1596 value: attr.websafe || [255,255,255]
1600 var ids = attr.ids || lang.merge({}, this.ID), i;
1602 if (!attr.ids && _pickercount > 1) {
1604 if (lang.hasOwnProperty(ids, i)) {
1605 ids[i] = ids[i] + _pickercount;
1612 * A list of element ids and/or element references used by the
1613 * control. The default is the this.ID list, and can be customized
1614 * by passing a list in the contructor
1616 * @type {referenceid: realid}
1619 this.setAttributeConfig(this.OPT.IDS, {
1625 * A list of txt strings for internationalization. Default
1631 this.setAttributeConfig(this.OPT.TXT, {
1632 value: attr.txt || this.TXT,
1637 * The img src default list
1640 * @type {key: image}
1643 this.setAttributeConfig(this.OPT.IMAGES, {
1644 value: attr.images || this.IMAGE,
1648 * The element refs used by this control. Set at initialization
1649 * @attribute elements
1650 * @type {id: HTMLElement}
1653 this.setAttributeConfig(this.OPT.ELEMENTS, {
1659 * Hide/show the entire set of controls
1660 * @attribute showcontrols
1664 this.setAttributeConfig(this.OPT.SHOW_CONTROLS, {
1665 value: lang.isBoolean(attr.showcontrols) ? attr.showcontrols : true,
1666 method: function(on) {
1668 var el = Dom.getElementsByClassName("bd", "div",
1669 this.getElement(this.ID.CONTROLS))[0];
1671 this._hideShowEl(el, on);
1673 this.getElement(this.ID.CONTROLS_LABEL).innerHTML =
1674 (on) ? this.get(this.OPT.TXT).HIDE_CONTROLS :
1675 this.get(this.OPT.TXT).SHOW_CONTROLS;
1681 * Hide/show the rgb controls
1682 * @attribute showrgbcontrols
1686 this.setAttributeConfig(this.OPT.SHOW_RGB_CONTROLS, {
1687 value: lang.isBoolean(attr.showrgbcontrols) ? attr.showrgbcontrols : true,
1688 method: function(on) {
1689 this._hideShowEl(this.ID.RGB_CONTROLS, on);
1694 * Hide/show the hsv controls
1695 * @attribute showhsvcontrols
1699 this.setAttributeConfig(this.OPT.SHOW_HSV_CONTROLS, {
1700 value: lang.isBoolean(attr.showhsvcontrols) ?
1701 attr.showhsvcontrols : false,
1702 method: function(on) {
1703 //Dom.setStyle(this.getElement(this.ID.HSV_CONTROLS), "visibility", (on) ? "" : "hidden");
1704 this._hideShowEl(this.ID.HSV_CONTROLS, on);
1706 // can't show both the hsv controls and the rbg hex summary
1707 if (on && this.get(this.OPT.SHOW_HEX_SUMMARY)) {
1708 this.set(this.OPT.SHOW_HEX_SUMMARY, false);
1714 * Hide/show the hex controls
1715 * @attribute showhexcontrols
1719 this.setAttributeConfig(this.OPT.SHOW_HEX_CONTROLS, {
1720 value: lang.isBoolean(attr.showhexcontrols) ?
1721 attr.showhexcontrols : false,
1722 method: function(on) {
1723 this._hideShowEl(this.ID.HEX_CONTROLS, on);
1728 * Hide/show the websafe swatch
1729 * @attribute showwebsafe
1733 this.setAttributeConfig(this.OPT.SHOW_WEBSAFE, {
1734 value: lang.isBoolean(attr.showwebsafe) ? attr.showwebsafe : true,
1735 method: function(on) {
1736 this._hideShowEl(this.ID.WEBSAFE_SWATCH, on);
1741 * Hide/show the hex summary
1742 * @attribute showhexsummary
1746 this.setAttributeConfig(this.OPT.SHOW_HEX_SUMMARY, {
1747 value: lang.isBoolean(attr.showhexsummary) ? attr.showhexsummary : true,
1748 method: function(on) {
1749 this._hideShowEl(this.ID.HEX_SUMMARY, on);
1751 // can't show both the hsv controls and the rbg hex summary
1752 if (on && this.get(this.OPT.SHOW_HSV_CONTROLS)) {
1753 this.set(this.OPT.SHOW_HSV_CONTROLS, false);
1757 this.setAttributeConfig(this.OPT.ANIMATE, {
1758 value: lang.isBoolean(attr.animate) ? attr.animate : true,
1759 method: function(on) {
1760 if (this.pickerSlider) {
1761 this.pickerSlider.animate = on;
1762 this.hueSlider.animate = on;
1767 this.on(this.OPT.HUE + "Change", this._updateRGBFromHSV, this, true);
1768 this.on(this.OPT.SATURATION + "Change", this._updateRGBFromHSV, this, true);
1769 this.on(this.OPT.VALUE + "Change", this._updateRGBFromHSV, this, true);
1771 this.on(this.OPT.RED + "Change", this._updateRGB, this, true);
1772 this.on(this.OPT.GREEN + "Change", this._updateRGB, this, true);
1773 this.on(this.OPT.BLUE + "Change", this._updateRGB, this, true);
1775 this.on(this.OPT.HEX + "Change", this._updateHex, this, true);
1777 this._initElements();
1781 YAHOO.widget.ColorPicker = ColorPicker;
1783 YAHOO.register("colorpicker", YAHOO.widget.ColorPicker, {version: "2.8.0r4", build: "2449"});