2 Copyright (c) 2007, 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() {
14 var HCHARS="0123456789ABCDEF", lang=YAHOO.lang;
19 * Converts 0-1 to 0-255
21 * @param n {float} the number to convert
22 * @return {int} a number 0-255
24 real2dec: function(n) {
25 return Math.min(255, Math.round(n*256));
29 * Converts HSV (h[0-360], s[0-1]), v[0-1] to RGB [255,255,255]
31 * @param h {int|[int, float, float]} the hue, or an
32 * array containing all three parameters
33 * @param s {float} the saturation
34 * @param v {float} the value/brightness
35 * @return {[int, int, int]} the red, green, blue values in
38 hsv2rgb: function(h, s, v) {
40 if (lang.isArray(h)) {
41 return this.hsv2rgb.call(this, h[0], h[1], h[2]);
44 var r, g, b, i, f, p, q, t;
45 i = Math.floor((h/60)%6);
51 case 0: r=v; g=t; b=p; break;
52 case 1: r=q; g=v; b=p; break;
53 case 2: r=p; g=v; b=t; break;
54 case 3: r=p; g=q; b=v; break;
55 case 4: r=t; g=p; b=v; break;
56 case 5: r=v; g=p; b=q; break;
61 return [fn(r), fn(g), fn(b)];
65 * Converts to RGB [255,255,255] to HSV (h[0-360], s[0-1]), v[0-1]
67 * @param r {int|[int, int, int]} the red value, or an
68 * array containing all three parameters
69 * @param g {int} the green value
70 * @param b {int} the blue value
71 * @return {[int, float, float]} the value converted to hsv
73 rgb2hsv: function(r, g, b) {
75 if (lang.isArray(r)) {
76 return this.rgb2hsv.call(this, r[0], r[1], r[2]);
83 var min,max,delta,h,s,v;
84 min = Math.min(Math.min(r,g),b);
85 max = Math.max(Math.max(r,g),b);
90 case r: h=60*(g-b)/delta;
95 case g: h=(60*(b-r)/delta)+120; break;
96 case b: h=(60*(r-g)/delta)+240; break;
99 s = (max === 0) ? 0 : 1-(min/max);
101 var hsv = [Math.round(h), s, max];
108 * Converts decimal rgb values into a hex string
109 * 255,255,255 -> FFFFFF
111 * @param r {int|[int, int, int]} the red value, or an
112 * array containing all three parameters
113 * @param g {int} the green value
114 * @param b {int} the blue value
115 * @return {string} the hex string
117 rgb2hex: function(r, g, b) {
118 if (lang.isArray(r)) {
119 return this.rgb2hex.call(this, r[0], r[1], r[2]);
123 return f(r) + f(g) + f(b);
127 * Converts an int 0...255 to hex pair 00...FF
129 * @param n {int} the number to convert
130 * @return {string} the hex equivalent
132 dec2hex: function(n) {
134 n = (lang.isNumber(n)) ? n : 0;
135 n = (n > 255 || n < 0) ? 0 : n;
137 return HCHARS.charAt((n - n % 16) / 16) + HCHARS.charAt(n % 16);
141 * Converts a hex pair 00...FF to an int 0...255
143 * @param str {string} the hex pair to convert
144 * @return {int} the decimal
146 hex2dec: function(str) {
147 var f = function(c) {
148 return HCHARS.indexOf(c.toUpperCase());
153 return ((f(s[0]) * 16) + f(s[1]));
157 * Converts a hex string to rgb
159 * @param str {string} the hex string
160 * @return {[int, int, int]} an array containing the rgb values
162 hex2rgb: function(s) {
163 var f = this.hex2dec;
164 return [f(s.substr(0, 2)), f(s.substr(2, 2)), f(s.substr(4, 2))];
168 * Returns the closest websafe color to the supplied rgb value.
170 * @param r {int|[int, int, int]} the red value, or an
171 * array containing all three parameters
172 * @param g {int} the green value
173 * @param b {int} the blue value
174 * @return {[int, int, int]} an array containing the closes
175 * websafe rgb colors.
177 websafe: function(r, g, b) {
179 if (lang.isArray(r)) {
180 return this.websafe.call(this, r[0], r[1], r[2]);
183 // returns the closest match [0, 51, 102, 153, 204, 255]
184 var f = function(v) {
185 if (lang.isNumber(v)) {
186 v = Math.min(Math.max(0, v), 255);
188 for (i=0; i<256; i=i+51) {
190 if (v >= i && v <= next) {
191 return (v-i > 25) ? next : i;
194 YAHOO.log("Error calculating the websafe value for " + v, "warn");
200 return [f(r), f(g), f(b)];
211 * The colorpicker module provides a widget for selecting colors
212 * @module colorpicker
213 * @requires yahoo, dom, event, element, slider
217 * A widget to select colors
218 * @namespace YAHOO.widget
219 * @class YAHOO.widget.ColorPicker
220 * @extends YAHOO.util.Element
222 * @param {HTMLElement | String | Object} el(optional) The html
223 * element that represents the colorpicker, or the attribute object to use.
224 * An element will be created if none provided.
225 * @param {Object} attr (optional) A key map of the colorpicker's
226 * initial attributes. Ignored if first arg is attributes object.
228 YAHOO.widget.ColorPicker = function(el, attr) {
229 pickercount = pickercount + 1;
230 this.logger = new YAHOO.widget.LogWriter("ColorPicker");
232 if (arguments.length === 1 && !YAHOO.lang.isString(el) && !el.nodeName) {
233 attr = el; // treat first arg as attr object
234 el = attr.element || null;
237 if (!el && !attr.element) { // create if we dont have one
238 this.logger.log("creating host element");
239 el = _createHostElement.call(this, attr);
242 YAHOO.widget.ColorPicker.superclass.constructor.call(this, el, attr);
245 YAHOO.extend(YAHOO.widget.ColorPicker, YAHOO.util.Element);
247 var proto = YAHOO.widget.ColorPicker.prototype,
248 Slider=YAHOO.widget.Slider,
249 Color=YAHOO.util.Color,
250 Dom = YAHOO.util.Dom,
251 Event = YAHOO.util.Event,
253 sub = lang.substitute;
256 var b = "yui-picker";
259 * The element ids used by this control
266 * The id for the "red" form field
270 * @default yui-picker-r
275 * The id for the "red" hex pair output
279 * @default yui-picker-rhex
284 * The id for the "green" form field
288 * @default yui-picker-g
293 * The id for the "green" hex pair output
297 * @default yui-picker-ghex
303 * The id for the "blue" form field
307 * @default yui-picker-b
312 * The id for the "blue" hex pair output
316 * @default yui-picker-bhex
321 * The id for the "hue" form field
325 * @default yui-picker-h
330 * The id for the "saturation" form field
334 * @default yui-picker-s
339 * The id for the "value" form field
343 * @default yui-picker-v
348 * The id for the picker region slider
349 * @property ID.PICKER_BG
352 * @default yui-picker-bg
354 PICKER_BG: b + "-bg",
357 * The id for the picker region thumb
358 * @property ID.PICKER_THUMB
361 * @default yui-picker-thumb
363 PICKER_THUMB: b + "-thumb",
366 * The id for the hue slider
367 * @property ID.HUE_BG
370 * @default yui-picker-hue-bg
372 HUE_BG: b + "-hue-bg",
375 * The id for the hue thumb
376 * @property ID.HUE_THUMB
379 * @default yui-picker-hue-thumb
381 HUE_THUMB: b + "-hue-thumb",
384 * The id for the hex value form field
388 * @default yui-picker-hex
393 * The id for the color swatch
394 * @property ID.SWATCH
397 * @default yui-picker-swatch
399 SWATCH: b + "-swatch",
402 * The id for the websafe color swatch
403 * @property ID.WEBSAFE_SWATCH
406 * @default yui-picker-websafe-swatch
408 WEBSAFE_SWATCH: b + "-websafe-swatch",
411 * The id for the control details
412 * @property ID.CONTROLS
414 * @default yui-picker-controls
416 CONTROLS: b + "-controls",
419 * The id for the rgb controls
420 * @property ID.RGB_CONTROLS
422 * @default yui-picker-rgb-controls
424 RGB_CONTROLS: b + "-rgb-controls",
427 * The id for the hsv controls
428 * @property ID.HSV_CONTROLS
430 * @default yui-picker-hsv-controls
432 HSV_CONTROLS: b + "-hsv-controls",
435 * The id for the hsv controls
436 * @property ID.HEX_CONTROLS
438 * @default yui-picker-hex-controls
440 HEX_CONTROLS: b + "-hex-controls",
443 * The id for the hex summary
444 * @property ID.HEX_SUMMARY
446 * @default yui-picker-hex-summary
448 HEX_SUMMARY: b + "-hex-summary",
451 * The id for the controls section header
452 * @property ID.CONTROLS_LABEL
454 * @default yui-picker-controls-label
456 CONTROLS_LABEL: b + "-controls-label"
460 * Constants for any script-generated messages. The values here
461 * are the default messages. They can be updated by providing
462 * the complete list to the constructor for the "txt" attribute.
467 ILLEGAL_HEX: "Illegal hex value entered",
468 SHOW_CONTROLS: "Show color details",
469 HIDE_CONTROLS: "Hide color details",
470 CURRENT_COLOR: "Currently selected color: {rgb}",
471 CLOSEST_WEBSAFE: "Closest websafe color: {rgb}. Click to select.",
484 * Constants for the default image locations for img tags that are
485 * generated by the control. They can be modified by passing the
486 * complete list to the contructor for the "images" attribute
491 PICKER_THUMB: "../../build/colorpicker/assets/picker_thumb.png",
492 HUE_THUMB: "../../build/colorpicker/assets/hue_thumb.png"
496 * Constants for the control's custom event names. subscribe
497 * to the rgbChange event instead.
508 * Constants for the control's default default values
517 * Constants for the control's configuration attributes
523 SATURATION: "saturation",
532 PICKER_SIZE: "pickersize",
533 SHOW_CONTROLS: "showcontrols",
534 SHOW_RGB_CONTROLS: "showrgbcontrols",
535 SHOW_HSV_CONTROLS: "showhsvcontrols",
536 SHOW_HEX_CONTROLS: "showhexcontrols",
537 SHOW_HEX_SUMMARY: "showhexsummary",
538 SHOW_WEBSAFE: "showwebsafe",
539 //SHOW_SUBMIT: "showsubmit",
540 CONTAINER: "container",
542 ELEMENTS: "elements",
549 * Sets the control to the specified rgb value and
550 * moves the sliders to the proper positions
552 * @param rgb {[int, int, int]} the rgb value
553 * @param silent {boolean} whether or not to fire the change event
555 proto.setValue = function(rgb, silent) {
556 silent = (silent) || false;
557 this.set(this.OPT.RGB, rgb, silent);
558 _updateSliders.call(this);
563 * @property hueSlider
564 * @type YAHOO.widget.Slider
566 proto.hueSlider = null;
570 * @property pickerSlider
571 * @type YAHOO.widget.Slider
573 proto.pickerSlider = null;
576 * Translates the slider value into hue, int[0,359]
579 * @return {int} the hue from 0 to 359
581 var _getH = function() {
582 var size = this.get(this.OPT.PICKER_SIZE),
583 h = (size - this.hueSlider.getValue()) / size;
584 h = Math.round(h*360);
585 return (h === 360) ? 0 : h;
589 * Translates the slider value into saturation, int[0,1], left to right
592 * @return {int} the saturation from 0 to 1
594 var _getS = function() {
595 return this.pickerSlider.getXValue() / this.get(this.OPT.PICKER_SIZE);
599 * Translates the slider value into value/brightness, int[0,1], top
603 * @return {int} the value from 0 to 1
605 var _getV = function() {
606 var size = this.get(this.OPT.PICKER_SIZE);
607 return (size - this.pickerSlider.getYValue()) / size;
611 * Updates the background of the swatch with the current rbg value.
612 * Also updates the websafe swatch to the closest websafe color
613 * @method _updateSwatch
616 var _updateSwatch = function() {
617 var rgb = this.get(this.OPT.RGB),
618 websafe = this.get(this.OPT.WEBSAFE),
619 el = this.getElement(this.ID.SWATCH),
620 color = rgb.join(","),
621 txt = this.get(this.OPT.TXT);
623 Dom.setStyle(el, "background-color", "rgb(" + color + ")");
624 el.title = lang.substitute(txt.CURRENT_COLOR, {
625 "rgb": "#" + this.get(this.OPT.HEX)
629 el = this.getElement(this.ID.WEBSAFE_SWATCH);
630 color = websafe.join(",");
632 Dom.setStyle(el, "background-color", "rgb(" + color + ")");
633 el.title = lang.substitute(txt.CLOSEST_WEBSAFE, {
634 "rgb": "#" + Color.rgb2hex(websafe)
640 * Reads the sliders and converts the values to RGB, updating the
641 * internal state for all the individual form fields
642 * @method _getValuesFromSliders
645 var _getValuesFromSliders = function() {
646 var h=_getH.call(this), s=_getS.call(this), v=_getV.call(this);
647 YAHOO.log("hsv " + [h, s, v]);
649 rgb = Color.hsv2rgb(h, s, v);
650 var websafe = Color.websafe(rgb);
651 var hex = Color.rgb2hex(rgb[0], rgb[1], rgb[2]);
653 this.set(this.OPT.RGB, rgb);
657 * Updates the form field controls with the state data contained
659 * @method _updateFormFields
662 var _updateFormFields = function() {
663 this.getElement(this.ID.H).value = this.get(this.OPT.HUE);
664 this.getElement(this.ID.S).value = this.get(this.OPT.SATURATION);
665 this.getElement(this.ID.V).value = this.get(this.OPT.VALUE);
666 this.getElement(this.ID.R).value = this.get(this.OPT.RED);
667 this.getElement(this.ID.R_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.RED));
668 this.getElement(this.ID.G).value = this.get(this.OPT.GREEN);
669 this.getElement(this.ID.G_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.GREEN));
670 this.getElement(this.ID.B).value = this.get(this.OPT.BLUE);
671 this.getElement(this.ID.B_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.BLUE));
672 this.getElement(this.ID.HEX).value = this.get(this.OPT.HEX);
676 * Event handler for the hue slider.
677 * @method _onHueSliderChange
678 * @param newOffset {int} pixels from the start position
681 var _onHueSliderChange = function(newOffset) {
682 this.logger.log("hue update: " + newOffset , "warn");
684 var h = _getH.call(this);
685 this.set(this.OPT.HUE, h, true);
687 // set picker background to the hue
688 var rgb = Color.hsv2rgb(h, 1, 1);
689 var styleDef = "rgb(" + rgb.join(",") + ")";
691 Dom.setStyle(this.getElement(this.ID.PICKER_BG), "background-color", styleDef);
693 if (this.hueSlider.valueChangeSource === this.hueSlider.SOURCE_UI_EVENT) {
694 _getValuesFromSliders.call(this);
697 _updateFormFields.call(this);
698 _updateSwatch.call(this);
702 * Event handler for the picker slider, which controls the
703 * saturation and value/brightness.
704 * @method _onPickerSliderChange
705 * @param newOffset {{x: int, y: int}} x/y pixels from the start position
708 var _onPickerSliderChange = function(newOffset) {
709 this.logger.log(sub("picker update [{x}, {y}]", newOffset));
711 var s=_getS.call(this), v=_getV.call(this);
712 this.set(this.OPT.SATURATION, Math.round(s*100), true);
713 this.set(this.OPT.VALUE, Math.round(v*100), true);
715 if (this.pickerSlider.valueChangeSource === this.pickerSlider.SOURCE_UI_EVENT) {
716 _getValuesFromSliders.call(this);
719 _updateFormFields.call(this);
720 _updateSwatch.call(this);
725 * Key map to well-known commands for txt field input
726 * @method _getCommand
727 * @param e {Event} the keypress or keydown event
728 * @return {int} a command code
730 * <li>0 = not a number, letter in range, or special key</li>
731 * <li>1 = number</li>
732 * <li>2 = a-fA-F</li>
733 * <li>3 = increment (up arrow)</li>
734 * <li>4 = decrement (down arrow)</li>
735 * <li>5 = special key (tab, delete, return, escape, left, right)</li>
736 * <li>6 = return</li>
740 var _getCommand = function(e) {
741 var c = Event.getCharCode(e);
743 //alert(Event.getCharCode(e) + ", " + e.keyCode + ", " + e.charCode);
746 if (c === 38) { // up arrow
748 } else if (c === 13) { // return
750 } else if (c === 40) { // down array
752 } else if (c >= 48 && c<=57) { // 0-9
754 } else if (c >= 97 && c<=102) { // a-f
756 } else if (c >= 65 && c<=70) { // A-F
758 //} else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1 ||
759 // (c >= 112 && c <=123)) { // including F-keys
760 // tab, delete, return, escape, left, right
761 } else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1) { // special chars
763 } else { // something we probably don't want
769 * Handle keypress on one of the rgb or hsv fields.
770 * @method _rgbFieldKeypress
771 * @param e {Event} the keypress event
772 * @param el {HTMLElement} the field
773 * @param prop {string} the key to the linked property
776 var _rgbFieldKeypress = function(e, el, prop) {
777 var command = _getCommand(e);
778 var inc = (e.shiftKey) ? 10 : 1;
780 case 6: // return, update the value
781 _useFieldValue.apply(this, arguments);
784 case 3: // up arrow, increment
785 this.set(prop, Math.min(this.get(prop)+inc, 255));
786 _updateFormFields.call(this);
787 //Event.stopEvent(e);
789 case 4: // down arrow, decrement
790 this.set(prop, Math.max(this.get(prop)-inc, 0));
791 _updateFormFields.call(this);
792 //Event.stopEvent(e);
801 * Handle keydown on the hex field
802 * @method _hexFieldKeypress
803 * @param e {Event} the keypress event
804 * @param el {HTMLElement} the field
805 * @param prop {string} the key to the linked property
808 var _hexFieldKeypress = function(e, el, prop) {
809 var command = _getCommand(e);
810 if (command === 6) { // return, update the value
811 _useFieldValue.apply(this, arguments);
816 * Use the value of the text field to update the control
817 * @method _hexFieldKeypress
818 * @param e {Event} an event
819 * @param el {HTMLElement} the field
820 * @param prop {string} the key to the linked property
823 var _useFieldValue = function(e, el, prop) {
826 if (prop !== this.OPT.HEX) {
827 val = parseInt(val, 10);
830 if (val !== this.get(prop)) {
836 * Allows numbers and special chars only. Used for the
837 * rgb and hsv fields keypress handler.
838 * @method _numbersOnly
839 * @param e {Event} the event
841 * @return {boolean} false if we are canceling the event
843 var _numbersOnly = function(e) {
844 return _hexOnly(e, true);
848 * Allows numbers and special chars, and by default allows a-f.
849 * Used for the hex field keypress handler.
851 * @param e {Event} the event
852 * @param numbersOnly omits a-f if set to true
854 * @return {boolean} false if we are canceling the event
856 var _hexOnly = function(e, numbersOnly) {
857 var command = _getCommand(e);
860 case 5: // special char
863 case 2: // hex char (a-f)
864 if (numbersOnly !== true) {
868 // fallthrough is intentional
870 default: // prevent alpha and punctuation
877 * Returns the element reference that is saved. The id can be either
878 * the element id, or the key for this id in the "id" config attribute.
879 * For instance, the host element id can be obtained by passing its
880 * id (default: "yui_picker") or by its key "YUI_PICKER".
881 * @param id {string} the element id, or key
882 * @return {HTMLElement} a reference to the element
884 proto.getElement = function(id) {
885 return this.get(this.OPT.ELEMENTS)[this.get(this.OPT.IDS)[id]];
888 _createElements = function() {
889 this.logger.log("Building markup");
890 var el, child, img, fld, i,
891 ids = this.get(this.OPT.IDS),
892 txt = this.get(this.OPT.TXT),
893 images = this.get(this.OPT.IMAGES),
894 Elem = function(type, o) {
895 var n = document.createElement(type);
897 lang.augmentObject(n, o, true);
901 RGBElem = function(type, obj) {
911 return new Elem(type, o);
914 var p = this.get("element");
916 // Picker slider (S and V) ---------------------------------------------
918 el = new Elem("div", {
919 id: ids[this.ID.PICKER_BG],
920 className: "yui-picker-bg",
925 child = new Elem("div", {
926 id: ids[this.ID.PICKER_THUMB],
927 className: "yui-picker-thumb"
930 img = new Elem("img", {
931 src: images.PICKER_THUMB
934 child.appendChild(img);
935 el.appendChild(child);
938 // Hue slider ---------------------------------------------
939 el = new Elem("div", {
940 id: ids[this.ID.HUE_BG],
941 className: "yui-picker-hue-bg",
946 child = new Elem("div", {
947 id: ids[this.ID.HUE_THUMB],
948 className: "yui-picker-hue-thumb"
951 img = new Elem("img", {
952 src: images.HUE_THUMB
955 child.appendChild(img);
956 el.appendChild(child);
960 // controls ---------------------------------------------
962 el = new Elem("div", {
963 id: ids[this.ID.CONTROLS],
964 className: "yui-picker-controls"
971 el = new Elem("div", {
975 child = new Elem("a", {
976 id: ids[this.ID.CONTROLS_LABEL],
977 //className: "yui-picker-controls-label",
980 el.appendChild(child);
984 el = new Elem("div", {
992 el = new Elem("ul", {
993 id: ids[this.ID.RGB_CONTROLS],
994 className: "yui-picker-rgb-controls"
997 child = new Elem("li");
998 child.appendChild(document.createTextNode(txt.R + " "));
1000 fld = new RGBElem("input", {
1002 className: "yui-picker-r"
1005 child.appendChild(fld);
1006 el.appendChild(child);
1008 child = new Elem("li");
1009 child.appendChild(document.createTextNode(txt.G + " "));
1011 fld = new RGBElem("input", {
1013 className: "yui-picker-g"
1016 child.appendChild(fld);
1017 el.appendChild(child);
1019 child = new Elem("li");
1020 child.appendChild(document.createTextNode(txt.B + " "));
1022 fld = new RGBElem("input", {
1024 className: "yui-picker-b"
1027 child.appendChild(fld);
1028 el.appendChild(child);
1033 el = new Elem("ul", {
1034 id: ids[this.ID.HSV_CONTROLS],
1035 className: "yui-picker-hsv-controls"
1038 child = new Elem("li");
1039 child.appendChild(document.createTextNode(txt.H + " "));
1041 fld = new RGBElem("input", {
1043 className: "yui-picker-h"
1046 child.appendChild(fld);
1047 child.appendChild(document.createTextNode(" " + txt.DEG));
1049 el.appendChild(child);
1051 child = new Elem("li");
1052 child.appendChild(document.createTextNode(txt.S + " "));
1054 fld = new RGBElem("input", {
1056 className: "yui-picker-s"
1059 child.appendChild(fld);
1060 child.appendChild(document.createTextNode(" " + txt.PERCENT));
1062 el.appendChild(child);
1064 child = new Elem("li");
1065 child.appendChild(document.createTextNode(txt.V + " "));
1067 fld = new RGBElem("input", {
1069 className: "yui-picker-v"
1072 child.appendChild(fld);
1073 child.appendChild(document.createTextNode(" " + txt.PERCENT));
1075 el.appendChild(child);
1081 el = new Elem("ul", {
1082 id: ids[this.ID.HEX_SUMMARY],
1083 className: "yui-picker-hex_summary"
1086 child = new Elem("li", {
1087 id: ids[this.ID.R_HEX]
1089 el.appendChild(child);
1091 child = new Elem("li", {
1092 id: ids[this.ID.G_HEX]
1094 el.appendChild(child);
1096 child = new Elem("li", {
1097 id: ids[this.ID.B_HEX]
1099 el.appendChild(child);
1103 el = new Elem("div", {
1104 id: ids[this.ID.HEX_CONTROLS],
1105 className: "yui-picker-hex-controls"
1107 el.appendChild(document.createTextNode(txt.HEX + " "));
1109 child = new RGBElem("input", {
1110 id: ids[this.ID.HEX],
1111 className: "yui-picker-hex",
1116 el.appendChild(child);
1119 p = this.get("element");
1122 el = new Elem("div", {
1123 id: ids[this.ID.SWATCH],
1124 className: "yui-picker-swatch"
1130 el = new Elem("div", {
1131 id: ids[this.ID.WEBSAFE_SWATCH],
1132 className: "yui-picker-websafe-swatch"
1140 * Sets the initial state of the sliders
1141 * @method initPicker
1143 proto.initPicker = function () {
1145 // bind all of our elements
1147 ids = this.get(o.IDS),
1148 els = this.get(o.ELEMENTS),
1151 // Add the default value as a key for each element for easier lookup
1152 for (i in this.ID) {
1153 if (lang.hasOwnProperty(this.ID, i)) {
1154 ids[this.ID[i]] = ids[i];
1158 // Check for picker element, if not there, create all of them
1159 el = Dom.get(ids[this.ID.PICKER_BG]);
1161 _createElements.call(this);
1163 this.logger.log("Using pre-existing markup");
1167 if (lang.hasOwnProperty(ids, i)) {
1169 el = Dom.get(ids[i]);
1171 // generate an id if the implementer passed in an element reference,
1172 // and the element did not have an id already
1173 id = Dom.generateId(el);
1175 // update the id in case we generated the id
1176 ids[i] = id; // key is WEBSAFE_SWATCH
1177 ids[ids[i]] = id; // key is websafe_swatch
1179 // store the dom ref
1184 // set the initial visibility state of our controls
1185 els = [o.SHOW_CONTROLS,
1186 o.SHOW_RGB_CONTROLS,
1187 o.SHOW_HSV_CONTROLS,
1188 o.SHOW_HEX_CONTROLS,
1193 for (i=0; i<els.length; i=i+1) {
1194 this.set(els[i], this.get(els[i]));
1197 var s = this.get(o.PICKER_SIZE);
1198 this.logger.log("picker size" + s);
1200 this.hueSlider = Slider.getVertSlider(this.getElement(this.ID.HUE_BG),
1201 this.getElement(this.ID.HUE_THUMB), 0, s);
1202 this.hueSlider.subscribe("change", _onHueSliderChange, this, true);
1204 this.pickerSlider = Slider.getSliderRegion(this.getElement(this.ID.PICKER_BG),
1205 this.getElement(this.ID.PICKER_THUMB), 0, s, 0, s);
1206 this.pickerSlider.subscribe("change", _onPickerSliderChange, this, true);
1208 //_onHueSliderChange.call(this, 0);
1210 Event.on(this.getElement(this.ID.WEBSAFE_SWATCH), "click", function(e) {
1211 this.setValue(this.get(o.WEBSAFE));
1215 Event.on(this.getElement(this.ID.CONTROLS_LABEL), "click", function(e) {
1216 this.set(o.SHOW_CONTROLS, !this.get(o.SHOW_CONTROLS));
1217 Event.preventDefault(e);
1220 _attachRGBHSV.call(this, this.ID.R, this.OPT.RED);
1221 _attachRGBHSV.call(this, this.ID.G, this.OPT.GREEN);
1222 _attachRGBHSV.call(this, this.ID.B, this.OPT.BLUE);
1223 _attachRGBHSV.call(this, this.ID.H, this.OPT.HUE);
1224 _attachRGBHSV.call(this, this.ID.S, this.OPT.SATURATION);
1225 _attachRGBHSV.call(this, this.ID.V, this.OPT.VALUE);
1227 Event.on(this.getElement(this.ID.HEX), "keydown", function(e, me) {
1228 _hexFieldKeypress.call(me, e, this, me.OPT.HEX);
1231 Event.on(this.getElement(this.ID.HEX), "keypress", _hexOnly, this);
1232 Event.on(this.getElement(this.ID.HEX), "blur", function(e, me) {
1233 _useFieldValue.call(me, e, this, me.OPT.HEX);
1237 _attachRGBHSV = function(id, config) {
1238 Event.on(this.getElement(id), "keydown", function(e, me) {
1239 _rgbFieldKeypress.call(me, e, this, config);
1241 Event.on(this.getElement(id), "keypress", _numbersOnly, this);
1242 Event.on(this.getElement(id), "blur", function(e, me) {
1243 _useFieldValue.call(me, e, this, config);
1249 * Sets up the config attributes and the change listeners for this
1251 * @method initAttributes
1252 * @param attr An object containing default attribute values
1254 proto.initAttributes = function(attr) {
1257 YAHOO.widget.ColorPicker.superclass.initAttributes.call(this, attr);
1260 * The size of the picker. Trying to change this is not recommended.
1261 * @config pickersize
1265 this.setAttributeConfig(this.OPT.PICKER_SIZE, {
1266 value: attr.size || this.DEFAULT.PICKER_SIZE
1270 * The current hue value 0-360
1274 this.setAttributeConfig(this.OPT.HUE, {
1275 value: attr.hue || 0,
1276 validator: lang.isNumber
1280 * The current saturation value 0-100
1281 * @config saturation
1284 this.setAttributeConfig(this.OPT.SATURATION, {
1285 value: attr.saturation || 0,
1286 validator: lang.isNumber
1290 * The current value/brightness value 0-100
1294 this.setAttributeConfig(this.OPT.VALUE, {
1295 value: attr.value || 100,
1296 validator: lang.isNumber
1300 * The current red value 0-255
1304 this.setAttributeConfig(this.OPT.RED, {
1305 value: attr.red || 255,
1306 validator: lang.isNumber
1310 * The current green value 0-255
1314 this.setAttributeConfig(this.OPT.GREEN, {
1315 value: attr.red || 255,
1316 validator: lang.isNumber
1320 * The current blue value 0-255
1324 this.setAttributeConfig(this.OPT.BLUE, {
1325 value: attr.blue || 255,
1326 validator: lang.isNumber
1330 * The current hex value #000000-#FFFFFF, without the #
1334 this.setAttributeConfig(this.OPT.HEX, {
1335 value: attr.hex || "FFFFFF",
1336 validator: lang.isString
1340 * The current rgb value. Updates the state of all of the
1341 * other value fields. Read-only: use setValue to set the
1342 * controls rgb value.
1344 * @type [int, int, int]
1347 this.setAttributeConfig(this.OPT.RGB, {
1348 value: attr.rgb || [255,255,255],
1349 method: function(rgb) {
1351 this.set(this.OPT.RED, rgb[0], true);
1352 this.set(this.OPT.GREEN, rgb[1], true);
1353 this.set(this.OPT.BLUE, rgb[2], true);
1355 var websafe = Color.websafe(rgb);
1356 this.set(this.OPT.WEBSAFE, websafe, true);
1358 var hex = Color.rgb2hex(rgb);
1359 this.set(this.OPT.HEX, hex, true);
1361 var hsv = Color.rgb2hsv(rgb);
1363 this.logger.log(sub("RGB value set to {rgb} (hsv: {hsv})", {
1364 "hsv": hsv, "rgb": rgb
1367 this.set(this.OPT.HUE, hsv[0], true);
1368 this.set(this.OPT.SATURATION, Math.round(hsv[1]*100), true);
1369 this.set(this.OPT.VALUE, Math.round(hsv[2]*100), true);
1375 * If the color picker will live inside of a container object,
1376 * set, provide a reference to it so the control can use the
1377 * container's events.
1379 * @type YAHOO.widget.Panel
1381 this.setAttributeConfig(this.OPT.CONTAINER, {
1383 method: function(container) {
1385 // Position can get out of sync when the
1386 // control is manipulated while display is
1387 // none. Resetting the slider constraints
1388 // when it is visible gets the state back in
1390 container.showEvent.subscribe(function() {
1391 // this.pickerSlider.thumb.resetConstraints();
1392 // this.hueSlider.thumb.resetConstraints();
1393 this.pickerSlider.focus();
1399 * The closest current websafe value
1403 this.setAttributeConfig(this.OPT.WEBSAFE, {
1404 value: attr.websafe || [255,255,255]
1408 ids = attr.ids || lang.merge({}, this.ID);
1410 if (!attr.ids && pickercount > 1) {
1411 for (var i in ids) {
1412 if (lang.hasOwnProperty(ids, i)) {
1413 ids[i] = ids[i] + pickercount;
1420 * A list of element ids and/or element references used by the
1421 * control. The default is the this.ID list, and can be customized
1422 * by passing a list in the contructor
1424 * @type {referenceid: realid}
1427 this.setAttributeConfig(this.OPT.IDS, {
1433 * A list of txt strings for internationalization. Default
1439 this.setAttributeConfig(this.OPT.TXT, {
1440 value: attr.txt || this.TXT,
1445 * The img src default list
1448 * @type {key: image}
1451 this.setAttributeConfig(this.OPT.IMAGES, {
1452 value: attr.images || this.IMAGE,
1456 * The element refs used by this control. Set at initialization
1458 * @type {id: HTMLElement}
1461 this.setAttributeConfig(this.OPT.ELEMENTS, {
1467 * Returns the cached element reference. If the id is not a string, it
1468 * is assumed that it is an element and this is returned.
1469 * @param id {string|HTMLElement} the element key, id, or ref
1470 * @param on {boolean} hide or show. If true, show
1472 _hideShowEl = function(id, on) {
1473 var el = (lang.isString(id) ? this.getElement(id) : id);
1474 //Dom.setStyle(id, "visibility", (on) ? "" : "hidden");
1475 Dom.setStyle(el, "display", (on) ? "" : "none");
1479 * Hide/show the entire set of controls
1480 * @config showcontrols
1484 this.setAttributeConfig(this.OPT.SHOW_CONTROLS, {
1485 value: (attr.showcontrols) || true,
1486 method: function(on) {
1488 var el = Dom.getElementsByClassName("bd", "div",
1489 this.getElement(this.ID.CONTROLS))[0];
1491 _hideShowEl.call(this, el, on);
1493 this.getElement(this.ID.CONTROLS_LABEL).innerHTML =
1494 (on) ? this.get(this.OPT.TXT).HIDE_CONTROLS :
1495 this.get(this.OPT.TXT).SHOW_CONTROLS;
1501 * Hide/show the rgb controls
1502 * @config showrgbcontrols
1506 this.setAttributeConfig(this.OPT.SHOW_RGB_CONTROLS, {
1507 value: (attr.showrgbcontrols) || true,
1508 method: function(on) {
1509 //Dom.setStyle(this.getElement(this.ID.RBG_CONTROLS), "visibility", (on) ? "" : "hidden");
1510 _hideShowEl.call(this, this.ID.RGB_CONTROLS, on);
1515 * Hide/show the hsv controls
1516 * @config showhsvcontrols
1520 this.setAttributeConfig(this.OPT.SHOW_HSV_CONTROLS, {
1521 value: (attr.showhsvcontrols) || false,
1522 method: function(on) {
1523 //Dom.setStyle(this.getElement(this.ID.HSV_CONTROLS), "visibility", (on) ? "" : "hidden");
1524 _hideShowEl.call(this, this.ID.HSV_CONTROLS, on);
1526 // can't show both the hsv controls and the rbg hex summary
1527 if (on && this.get(this.OPT.SHOW_HEX_SUMMARY)) {
1528 this.set(this.OPT.SHOW_HEX_SUMMARY, false);
1534 * Hide/show the hex controls
1535 * @config showhexcontrols
1539 this.setAttributeConfig(this.OPT.SHOW_HEX_CONTROLS, {
1540 value: (attr.showhexcontrols) || false,
1541 method: function(on) {
1542 _hideShowEl.call(this, this.ID.HEX_CONTROLS, on);
1547 * Hide/show the websafe swatch
1548 * @config showwebsafe
1552 this.setAttributeConfig(this.OPT.SHOW_WEBSAFE, {
1553 value: (attr.showwebsafe) || true,
1554 method: function(on) {
1555 _hideShowEl.call(this, this.ID.WEBSAFE_SWATCH, on);
1560 * Hide/show the hex summary
1561 * @config showhexsummary
1565 this.setAttributeConfig(this.OPT.SHOW_HEX_SUMMARY, {
1566 value: (attr.showhexsummary) || true,
1567 method: function(on) {
1568 _hideShowEl.call(this, this.ID.HEX_SUMMARY, on);
1570 // can't show both the hsv controls and the rbg hex summary
1571 if (on && this.get(this.OPT.SHOW_HSV_CONTROLS)) {
1572 this.set(this.OPT.SHOW_HSV_CONTROLS, false);
1576 this.setAttributeConfig(this.OPT.ANIMATE, {
1577 value: (attr.animate) || true,
1578 method: function(on) {
1579 this.pickerSlider.animate = on;
1580 this.hueSlider.animate = on;
1584 this.on(this.OPT.HUE + "Change", _updateRGBFromHSV, this, true);
1585 this.on(this.OPT.SATURATION + "Change", _updateRGBFromHSV, this, true);
1586 this.on(this.OPT.VALUE + "Change", _updatePickerSlider, this, true);
1588 this.on(this.OPT.RED + "Change", _updateRGB, this, true);
1589 this.on(this.OPT.GREEN + "Change", _updateRGB, this, true);
1590 this.on(this.OPT.BLUE + "Change", _updateRGB, this, true);
1592 this.on(this.OPT.HEX + "Change", _updateHex, this, true);
1598 * Updates the rgb attribute with the current state of the r,g,b
1599 * fields. This is invoked from change listeners on these
1600 * attributes to facilitate updating these values from the
1601 * individual form fields
1602 * @method _updateRGB
1605 var _updateRGB = function() {
1606 var rgb = [this.get(this.OPT.RED),
1607 this.get(this.OPT.GREEN),
1608 this.get(this.OPT.BLUE)];
1610 this.logger.log("RGB value set to " + rgb);
1611 this.set(this.OPT.RGB, rgb);
1613 _updateSliders.call(this);
1617 * Updates the RGB values from the current state of the HSV
1618 * values. Executed when the one of the HSV form fields are
1623 var _updateRGBFromHSV = function() {
1624 var hsv = [this.get(this.OPT.HUE),
1625 this.get(this.OPT.SATURATION)/100,
1626 this.get(this.OPT.VALUE)/100];
1628 var rgb = Color.hsv2rgb(hsv);
1630 this.logger.log("HSV converted to RGB " + hsv + " : " + rgb);
1631 this.set(this.OPT.RGB, rgb);
1633 _updateSliders.call(this);
1637 * Parses the hex string to normalize shorthand values, converts
1638 * the hex value to rgb and updates the rgb attribute (which
1639 * updates the state for all of the other values)
1643 var _updateHex = function() {
1645 var hex = this.get(this.OPT.HEX), l=hex.length;
1647 // support #369 -> #336699 shorthand
1649 var c = hex.split(""), i;
1650 for (i=0; i<l; i=i+1) {
1657 if (hex.length !== 6) {
1658 this.logger.log(this.get(this.TXT.ILLEGAL_HEX), "error");
1662 var rgb = Color.hex2rgb(hex);
1664 this.logger.log(sub("Hex value set to {hex} ({rgb})", {
1670 //_updateSliders.call(this);
1675 * Moves the sliders into the position dictated by the current state
1677 * @method _updateSliders
1680 var _updateSliders = function() {
1681 _updateHueSlider.call(this);
1682 _updatePickerSlider.call(this);
1686 * Moves the hue slider into the position dictated by the current state
1688 * @method _updateHueSlider
1691 var _updateHueSlider = function() {
1692 var size = this.get(this.OPT.PICKER_SIZE),
1693 h = this.get(this.OPT.HUE);
1695 h = size - Math.round(h / 360 * size);
1697 // 0 is at the top and bottom of the hue slider. Always go to
1698 // the top so we don't end up sending the thumb to the bottom
1699 // when the value didn't actually change (e.g., a conversion
1700 // produced 360 instead of 0 and the value was already 0).
1704 this.logger.log("Hue slider is being set to " + h);
1706 this.hueSlider.setValue(h);
1710 * Moves the picker slider into the position dictated by the current state
1712 * @method _updatePickerSlider
1715 var _updatePickerSlider = function() {
1716 var size = this.get(this.OPT.PICKER_SIZE),
1717 s = this.get(this.OPT.SATURATION),
1718 v = this.get(this.OPT.VALUE);
1720 s = Math.round(s * size / 100);
1721 v = Math.round(size - (v * size / 100));
1723 this.logger.log("Setting picker slider to " + [s, v]);
1725 this.pickerSlider.setRegionValue(s, v);
1729 * Creates the host element if it doesn't exist
1730 * @method _createHostElement
1733 var _createHostElement = function() {
1734 var el = document.createElement('div');
1736 if (this.CSS.BASE) {
1737 el.className = this.CSS.BASE;
1745 YAHOO.register("colorpicker", YAHOO.widget.ColorPicker, {version: "2.3.0", build: "442"});