# Yahoo! UI Library

### colorpicker 2.5.1

Yahoo! UI Library > colorpicker > ColorPicker.js (source view)

Show Private Show Protected

    (function() {

        var _pickercount = 0;

        /**
         * The colorpicker module provides a widget for selecting colors
         * @module colorpicker
         * @requires yahoo, dom, event, element, slider
         */

        /**
         * Creates the host element if it doesn't exist
         * @method _createHostElement
         * @private
         */
        var _createHostElement = function() {
            var el = document.createElement('div');

            if (this.CSS.BASE) {
                el.className = this.CSS.BASE;
            }

            return el;
        };

        /**
         * A widget to select colors
         * @namespace YAHOO.widget
         * @class YAHOO.widget.ColorPicker
         * @extends YAHOO.util.Element
         * @constructor
         * @param {HTMLElement | String | Object} el(optional) The html
         * element that represents the colorpicker, or the attribute object to use.
         * An element will be created if none provided.
         * @param {Object} attr (optional) A key map of the colorpicker's
         * initial attributes.  Ignored if first arg is attributes object.
         */
        YAHOO.widget.ColorPicker = function(el, attr) {
            _pickercount = _pickercount + 1;
            this.logger = new YAHOO.widget.LogWriter("ColorPicker");
            attr = attr || {};
            if (arguments.length === 1 && !YAHOO.lang.isString(el) && !el.nodeName) {
                attr = el; // treat first arg as attr object
                el = attr.element || null;
            }

            if (!el && !attr.element) { // create if we dont have one
                this.logger.log("creating host element");
                el = _createHostElement.call(this, attr);
            }

        	YAHOO.widget.ColorPicker.superclass.constructor.call(this, el, attr);
        };

        YAHOO.extend(YAHOO.widget.ColorPicker, YAHOO.util.Element);

        var proto = YAHOO.widget.ColorPicker.prototype,
            Slider=YAHOO.widget.Slider,
            Color=YAHOO.util.Color,
            Dom = YAHOO.util.Dom,
            Event = YAHOO.util.Event,
            lang = YAHOO.lang,
            sub = lang.substitute;

        var b = "yui-picker";

        /**
         * The element ids used by this control
         * @property ID
         * @final
         */
        proto.ID = {

            /**
             * The id for the "red" form field
             * @property ID.R
             * @type String
             * @final
             * @default yui-picker-r
             */
            R: b + "-r",

            /**
             * The id for the "red" hex pair output
             * @property ID.R_HEX
             * @type String
             * @final
             * @default yui-picker-rhex
             */
            R_HEX: b + "-rhex",

            /**
             * The id for the "green" form field
             * @property ID.G
             * @type String
             * @final
             * @default yui-picker-g
             */
            G: b + "-g",

            /**
             * The id for the "green" hex pair output
             * @property ID.G_HEX
             * @type String
             * @final
             * @default yui-picker-ghex
             */
            G_HEX: b + "-ghex",

            /**
             * The id for the "blue" form field
             * @property ID.B
             * @type String
             * @final
             * @default yui-picker-b
             */
            B: b + "-b",

            /**
             * The id for the "blue" hex pair output
             * @property ID.B_HEX
             * @type String
             * @final
             * @default yui-picker-bhex
             */
            B_HEX: b + "-bhex",

            /**
             * The id for the "hue" form field
             * @property ID.H
             * @type String
             * @final
             * @default yui-picker-h
             */
            H: b + "-h",

            /**
             * The id for the "saturation" form field
             * @property ID.S
             * @type String
             * @final
             * @default yui-picker-s
             */
            S: b + "-s",

            /**
             * The id for the "value" form field
             * @property ID.V
             * @type String
             * @final
             * @default yui-picker-v
             */
            V: b + "-v",

            /**
             * The id for the picker region slider
             * @property ID.PICKER_BG
             * @type String
             * @final
             * @default yui-picker-bg
             */
            PICKER_BG:      b + "-bg",

            /**
             * The id for the picker region thumb
             * @property ID.PICKER_THUMB
             * @type String
             * @final
             * @default yui-picker-thumb
             */
            PICKER_THUMB:   b + "-thumb",

            /**
             * The id for the hue slider
             * @property ID.HUE_BG
             * @type String
             * @final
             * @default yui-picker-hue-bg
             */
            HUE_BG:         b + "-hue-bg",

            /**
             * The id for the hue thumb
             * @property ID.HUE_THUMB
             * @type String
             * @final
             * @default yui-picker-hue-thumb
             */
            HUE_THUMB:      b + "-hue-thumb",

            /**
             * The id for the hex value form field
             * @property ID.HEX
             * @type String
             * @final
             * @default yui-picker-hex
             */
            HEX:            b + "-hex",

            /**
             * The id for the color swatch
             * @property ID.SWATCH
             * @type String
             * @final
             * @default yui-picker-swatch
             */
            SWATCH:         b + "-swatch",

            /**
             * The id for the websafe color swatch
             * @property ID.WEBSAFE_SWATCH
             * @type String
             * @final
             * @default yui-picker-websafe-swatch
             */
            WEBSAFE_SWATCH: b + "-websafe-swatch",

            /**
             * The id for the control details
             * @property ID.CONTROLS
             * @final
             * @default yui-picker-controls
             */
            CONTROLS: b + "-controls",

            /**
             * The id for the rgb controls
             * @property ID.RGB_CONTROLS
             * @final
             * @default yui-picker-rgb-controls
             */
            RGB_CONTROLS: b + "-rgb-controls",

            /**
             * The id for the hsv controls
             * @property ID.HSV_CONTROLS
             * @final
             * @default yui-picker-hsv-controls
             */
            HSV_CONTROLS: b + "-hsv-controls",

            /**
             * The id for the hsv controls
             * @property ID.HEX_CONTROLS
             * @final
             * @default yui-picker-hex-controls
             */
            HEX_CONTROLS: b + "-hex-controls",

            /**
             * The id for the hex summary
             * @property ID.HEX_SUMMARY
             * @final
             * @default yui-picker-hex-summary
             */
            HEX_SUMMARY: b + "-hex-summary",

            /**
             * The id for the controls section header
             * @property ID.CONTROLS_LABEL
             * @final
             * @default yui-picker-controls-label
             */
            CONTROLS_LABEL: b + "-controls-label"
        };

        /**
         * Constants for any script-generated messages.  The values here
         * are the default messages.  They can be updated by providing
         * the complete list to the constructor for the "txt" attribute.
         * @property TXT
         * @final
         */
        proto.TXT = {
            ILLEGAL_HEX: "Illegal hex value entered",
            SHOW_CONTROLS: "Show color details",
            HIDE_CONTROLS: "Hide color details",
            CURRENT_COLOR: "Currently selected color: {rgb}",
            CLOSEST_WEBSAFE: "Closest websafe color: {rgb}. Click to select.",
            R: "R",
            G: "G",
            B: "B",
            H: "H",
            S: "S",
            V: "V",
            HEX: "#",
            DEG: "\u00B0",
            PERCENT: "%"
        };

        /**
         * Constants for the default image locations for img tags that are
         * generated by the control.  They can be modified by passing the
         * complete list to the contructor for the "images" attribute
         * @property IMAGE
         * @final
         */
        proto.IMAGE = {
            PICKER_THUMB: "../../build/colorpicker/assets/picker_thumb.png",
            HUE_THUMB: "../../build/colorpicker/assets/hue_thumb.png"
        };

        /*
         * Constants for the control's custom event names.  subscribe
         * to the rgbChange event instead.
         * @property EVENT
         * @final
         */
        //proto.EVENT = {
            //CHANGE: "change"
        //};

        //proto.CSS = { };

        /**
         * Constants for the control's default default values
         * @property DEFAULT
         * @final
         */
        proto.DEFAULT = {
            PICKER_SIZE: 180
        };

        /**
         * Constants for the control's configuration attributes
         * @property OPT
         * @final
         */
        proto.OPT = {
            HUE: "hue",
            SATURATION: "saturation",
            VALUE: "value",
            RED: "red",
            GREEN: "green",
            BLUE: "blue",
            HSV: "hsv",
            RGB: "rgb",
            WEBSAFE: "websafe",
            HEX: "hex",
            PICKER_SIZE: "pickersize",
            SHOW_CONTROLS: "showcontrols",
            SHOW_RGB_CONTROLS: "showrgbcontrols",
            SHOW_HSV_CONTROLS: "showhsvcontrols",
            SHOW_HEX_CONTROLS: "showhexcontrols",
            SHOW_HEX_SUMMARY: "showhexsummary",
            SHOW_WEBSAFE: "showwebsafe",
            //SHOW_SUBMIT: "showsubmit",
            CONTAINER: "container",
            IDS: "ids",
            ELEMENTS: "elements",
            TXT: "txt",
            IMAGES: "images",
            ANIMATE: "animate"
        };

        /**
         * Moves the hue slider into the position dictated by the current state
         * of the control
         * @method _updateHueSlider
         * @private
         */
        var _updateHueSlider = function() {
            var size = this.get(this.OPT.PICKER_SIZE),
                h = this.get(this.OPT.HUE);

            h = size - Math.round(h / 360 * size);

            // 0 is at the top and bottom of the hue slider.  Always go to
            // the top so we don't end up sending the thumb to the bottom
            // when the value didn't actually change (e.g., a conversion
            // produced 360 instead of 0 and the value was already 0).
            if (h === size) {
                h = 0;
            }
            this.logger.log("Hue slider is being set to " + h);

            this.hueSlider.setValue(h);
        };

        /**
         * Moves the picker slider into the position dictated by the current state
         * of the control
         * @method _updatePickerSlider
         * @private
         */
        var _updatePickerSlider = function() {
            var size = this.get(this.OPT.PICKER_SIZE),
                s = this.get(this.OPT.SATURATION),
                v = this.get(this.OPT.VALUE);

            s = Math.round(s * size / 100);
            v = Math.round(size - (v * size / 100));

            this.logger.log("Setting picker slider to " + [s, v]);

            this.pickerSlider.setRegionValue(s, v);
        };

        /**
         * Moves the sliders into the position dictated by the current state
         * of the control
         * @method _updateSliders
         * @private
         */
        var _updateSliders = function() {
            _updateHueSlider.call(this);
            _updatePickerSlider.call(this);
        };

        /**
         * Sets the control to the specified rgb value and
         * moves the sliders to the proper positions
         * @method setValue
         * @param rgb {[int, int, int]} the rgb value
         * @param silent {boolean} whether or not to fire the change event
         */
        proto.setValue = function(rgb, silent) {
            silent = (silent) || false;
            this.set(this.OPT.RGB, rgb, silent);
            _updateSliders.call(this);
        };

        /**
         * The hue slider
         * @property hueSlider
         * @type YAHOO.widget.Slider
         */
        proto.hueSlider = null;

        /**
         * The picker region
         * @property pickerSlider
         * @type YAHOO.widget.Slider
         */
        proto.pickerSlider = null;

        /**
         * Translates the slider value into hue, int[0,359]
         * @method _getH
         * @private
         * @return {int} the hue from 0 to 359
         */
        var _getH = function() {
            var size = this.get(this.OPT.PICKER_SIZE),
                h = (size - this.hueSlider.getValue()) / size;
            h = Math.round(h*360);
            return (h === 360) ? 0 : h;
        };

        /**
         * Translates the slider value into saturation, int[0,1], left to right
         * @method _getS
         * @private
         * @return {int} the saturation from 0 to 1
         */
        var _getS = function() {
            return this.pickerSlider.getXValue() / this.get(this.OPT.PICKER_SIZE);
        };

        /**
         * Translates the slider value into value/brightness, int[0,1], top
         * to bottom
         * @method _getV
         * @private
         * @return {int} the value from 0 to 1
         */
        var _getV = function() {
            var size = this.get(this.OPT.PICKER_SIZE);
            return (size - this.pickerSlider.getYValue()) / size;
        };

        /**
         * Updates the background of the swatch with the current rbg value.
         * Also updates the websafe swatch to the closest websafe color
         * @method _updateSwatch
         * @private
         */
        var _updateSwatch = function() {
            var rgb = this.get(this.OPT.RGB),
                websafe = this.get(this.OPT.WEBSAFE),
                el = this.getElement(this.ID.SWATCH),
                color = rgb.join(","),
                txt = this.get(this.OPT.TXT);

            Dom.setStyle(el, "background-color", "rgb(" + color  + ")");
            el.title = lang.substitute(txt.CURRENT_COLOR, {
                    "rgb": "#" + this.get(this.OPT.HEX)
                });

            el = this.getElement(this.ID.WEBSAFE_SWATCH);
            color = websafe.join(",");

            Dom.setStyle(el, "background-color", "rgb(" + color + ")");
            el.title = lang.substitute(txt.CLOSEST_WEBSAFE, {
                    "rgb": "#" + Color.rgb2hex(websafe)
                });

        };

        /**
         * Reads the sliders and converts the values to RGB, updating the
         * internal state for all the individual form fields
         * @method _getValuesFromSliders
         * @private
         */
        var _getValuesFromSliders = function() {
            var h=_getH.call(this), s=_getS.call(this), v=_getV.call(this);
            YAHOO.log("hsv " + [h, s, v]);

            var rgb = Color.hsv2rgb(h, s, v);
            //var websafe = Color.websafe(rgb);
            //var hex = Color.rgb2hex(rgb[0], rgb[1], rgb[2]);

            this.set(this.OPT.RGB, rgb);
        };

        /**
         * Updates the form field controls with the state data contained
         * in the control.
         * @method _updateFormFields
         * @private
         */
        var _updateFormFields = function() {
            this.getElement(this.ID.H).value = this.get(this.OPT.HUE);
            this.getElement(this.ID.S).value = this.get(this.OPT.SATURATION);
            this.getElement(this.ID.V).value = this.get(this.OPT.VALUE);
            this.getElement(this.ID.R).value = this.get(this.OPT.RED);
            this.getElement(this.ID.R_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.RED));
            this.getElement(this.ID.G).value = this.get(this.OPT.GREEN);
            this.getElement(this.ID.G_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.GREEN));
            this.getElement(this.ID.B).value = this.get(this.OPT.BLUE);
            this.getElement(this.ID.B_HEX).innerHTML = Color.dec2hex(this.get(this.OPT.BLUE));
            this.getElement(this.ID.HEX).value = this.get(this.OPT.HEX);
        };

        /**
         * Event handler for the hue slider.
         * @method _onHueSliderChange
         * @param newOffset {int} pixels from the start position
         * @private
         */
        var _onHueSliderChange = function(newOffset) {
            this.logger.log("hue update: " + newOffset , "warn");

            var h = _getH.call(this);
            this.set(this.OPT.HUE, h, true);

            // set picker background to the hue
            var rgb = Color.hsv2rgb(h, 1, 1);
            var styleDef = "rgb(" + rgb.join(",") + ")";

            Dom.setStyle(this.getElement(this.ID.PICKER_BG), "background-color", styleDef);

            if (this.hueSlider.valueChangeSource === this.hueSlider.SOURCE_UI_EVENT) {
                _getValuesFromSliders.call(this);
            }

            _updateFormFields.call(this);
            _updateSwatch.call(this);
        };

        /**
         * Event handler for the picker slider, which controls the
         * saturation and value/brightness.
         * @method _onPickerSliderChange
         * @param newOffset {{x: int, y: int}} x/y pixels from the start position
         * @private
         */
        var _onPickerSliderChange = function(newOffset) {
            this.logger.log(sub("picker update [{x}, {y}]", newOffset));

            var s=_getS.call(this), v=_getV.call(this);
            this.set(this.OPT.SATURATION, Math.round(s*100), true);
            this.set(this.OPT.VALUE, Math.round(v*100), true);

            if (this.pickerSlider.valueChangeSource === this.pickerSlider.SOURCE_UI_EVENT) {
                _getValuesFromSliders.call(this);
            }

            _updateFormFields.call(this);
            _updateSwatch.call(this);
        };

        /**
         * Key map to well-known commands for txt field input
         * @method _getCommand
         * @param e {Event} the keypress or keydown event
         * @return {int} a command code
         * <ul>
         * <li>0 = not a number, letter in range, or special key</li>
         * <li>1 = number</li>
         * <li>2 = a-fA-F</li>
         * <li>3 = increment (up arrow)</li>
         * <li>4 = decrement (down arrow)</li>
         * <li>5 = special key (tab, delete, return, escape, left, right)</li>
         * <li>6 = return</li>
         * </ul>
         * @private
         */
        var _getCommand = function(e) {
            var c = Event.getCharCode(e);

            //alert(Event.getCharCode(e) + ", " + e.keyCode + ", " + e.charCode);

            // special keys
            if (c === 38) { // up arrow
                return 3;
            } else if (c === 13) { // return
                return 6;
            } else if (c === 40) { // down array
                return 4;
            } else if (c >= 48 && c<=57) { // 0-9
                return 1;
            } else if (c >= 97 && c<=102) { // a-f
                return 2;
            } else if (c >= 65 && c<=70) { // A-F
                return 2;
            //} else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1 ||
            //              (c >= 112 && c <=123)) { // including F-keys
            // tab, delete, return, escape, left, right
            } else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1) { // special chars
                return 5;
            } else { // something we probably don't want
                return 0;
            }
        };

        /**
         * Use the value of the text field to update the control
         * @method _hexFieldKeypress
         * @param e {Event} an event
         * @param el {HTMLElement} the field
         * @param prop {string} the key to the linked property
         * @private
         */
        var _useFieldValue = function(e, el, prop) {
            var val = el.value;

            if (prop !== this.OPT.HEX) {
                val = parseInt(val, 10);
            }

            if (val !== this.get(prop)) {
                this.set(prop, val);
            }
        };

        /**
         * Handle keypress on one of the rgb or hsv fields.
         * @method _rgbFieldKeypress
         * @param e {Event} the keypress event
         * @param el {HTMLElement} the field
         * @param prop {string} the key to the linked property
         * @private
         */
        var _rgbFieldKeypress = function(e, el, prop) {
            var command = _getCommand(e);
            var inc = (e.shiftKey) ? 10 : 1;
            switch (command) {
                case 6: // return, update the value
                    _useFieldValue.apply(this, arguments);
                    break;

                case 3: // up arrow, increment
                    this.set(prop, Math.min(this.get(prop)+inc, 255));
                    _updateFormFields.call(this);
                    //Event.stopEvent(e);
                    break;
                case 4: // down arrow, decrement
                    this.set(prop, Math.max(this.get(prop)-inc, 0));
                    _updateFormFields.call(this);
                    //Event.stopEvent(e);
                    break;

                default:
            }

        };

        /**
         * Handle keydown on the hex field
         * @method _hexFieldKeypress
         * @param e {Event} the keypress event
         * @param el {HTMLElement} the field
         * @param prop {string} the key to the linked property
         * @private
         */
        var _hexFieldKeypress = function(e, el, prop) {
            var command = _getCommand(e);
            if (command === 6) { // return, update the value
                _useFieldValue.apply(this, arguments);
            }
        };

        /**
         * Allows numbers and special chars, and by default allows a-f.
         * Used for the hex field keypress handler.
         * @method _hexOnly
         * @param e {Event} the event
         * @param numbersOnly omits a-f if set to true
         * @private
         * @return {boolean} false if we are canceling the event
         */
        var _hexOnly = function(e, numbersOnly) {
            var command = _getCommand(e);
            switch (command) {
                case 6: // return
                case 5: // special char
                case 1: // number
                    break;
                case 2: // hex char (a-f)
                    if (numbersOnly !== true) {
                        break;
                    }

                    // fallthrough is intentional

                default: // prevent alpha and punctuation
                    Event.stopEvent(e);
                    return false;
            }
        };

        /**
         * Allows numbers and special chars only.  Used for the
         * rgb and hsv fields keypress handler.
         * @method _numbersOnly
         * @param e {Event} the event
         * @private
         * @return {boolean} false if we are canceling the event
         */
        var _numbersOnly = function(e) {
            return _hexOnly(e, true);
        };

        /**
         * Returns the element reference that is saved.  The id can be either
         * the element id, or the key for this id in the "id" config attribute.
         * For instance, the host element id can be obtained by passing its
         * id (default: "yui_picker") or by its key "YUI_PICKER".
         * @param id {string} the element id, or key
         * @return {HTMLElement} a reference to the element
         */
        proto.getElement = function(id) {
            return this.get(this.OPT.ELEMENTS)[this.get(this.OPT.IDS)[id]];
        };

        var _createElements = function() {
            this.logger.log("Building markup");
            var el, child, img, fld, i,
                ids = this.get(this.OPT.IDS),
                txt = this.get(this.OPT.TXT),
                images = this.get(this.OPT.IMAGES),
                Elem = function(type, o) {
                    var n = document.createElement(type);
                    if (o) {
                        lang.augmentObject(n, o, true);
                    }
                    return n;
                },
                RGBElem = function(type, obj) {
                    var o = lang.merge({
                            //type: "txt",
                            autocomplete: "off",
                            value: "0",
                            size: 3,
                            maxlength: 3
                        }, obj);

                    o.name = o.id;
                    return new Elem(type, o);
                };

            var p = this.get("element");

            // Picker slider (S and V) ---------------------------------------------

            el = new Elem("div", {
                id: ids[this.ID.PICKER_BG],
                className: "yui-picker-bg",
                tabIndex: -1,
                hideFocus: true
            });

            child = new Elem("div", {
                id: ids[this.ID.PICKER_THUMB],
                className: "yui-picker-thumb"
            });

            img = new Elem("img", {
                src: images.PICKER_THUMB
            });

            child.appendChild(img);
            el.appendChild(child);
            p.appendChild(el);

            // Hue slider ---------------------------------------------
            el = new Elem("div", {
                id: ids[this.ID.HUE_BG],
                className: "yui-picker-hue-bg",
                tabIndex: -1,
                hideFocus: true
            });

            child = new Elem("div", {
                id: ids[this.ID.HUE_THUMB],
                className: "yui-picker-hue-thumb"
            });

            img = new Elem("img", {
                src: images.HUE_THUMB
            });

            child.appendChild(img);
            el.appendChild(child);
            p.appendChild(el);

            // controls ---------------------------------------------

            el = new Elem("div", {
                id: ids[this.ID.CONTROLS],
                className: "yui-picker-controls"
            });

            p.appendChild(el);
            p = el;

                // controls header
                el = new Elem("div", {
                    className: "hd"
                });

                child = new Elem("a", {
                    id: ids[this.ID.CONTROLS_LABEL],
                    //className: "yui-picker-controls-label",
                    href: "#"
                });
                el.appendChild(child);
                p.appendChild(el);

                // bd
                el = new Elem("div", {
                    className: "bd"
                });

                p.appendChild(el);
                p = el;

                    // rgb
                    el = new Elem("ul", {
                        id: ids[this.ID.RGB_CONTROLS],
                        className: "yui-picker-rgb-controls"
                    });

                    child = new Elem("li");
                    child.appendChild(document.createTextNode(txt.R + " "));

                    fld = new RGBElem("input", {
                        id: ids[this.ID.R],
                        className: "yui-picker-r"
                    });

                    child.appendChild(fld);
                    el.appendChild(child);

                    child = new Elem("li");
                    child.appendChild(document.createTextNode(txt.G + " "));

                    fld = new RGBElem("input", {
                        id: ids[this.ID.G],
                        className: "yui-picker-g"
                    });

                    child.appendChild(fld);
                    el.appendChild(child);

                    child = new Elem("li");
                    child.appendChild(document.createTextNode(txt.B + " "));

                    fld = new RGBElem("input", {
                        id: ids[this.ID.B],
                        className: "yui-picker-b"
                    });

                    child.appendChild(fld);
                    el.appendChild(child);

                    p.appendChild(el);

                    // hsv
                    el = new Elem("ul", {
                        id: ids[this.ID.HSV_CONTROLS],
                        className: "yui-picker-hsv-controls"
                    });

                    child = new Elem("li");
                    child.appendChild(document.createTextNode(txt.H + " "));

                    fld = new RGBElem("input", {
                        id: ids[this.ID.H],
                        className: "yui-picker-h"
                    });

                    child.appendChild(fld);
                    child.appendChild(document.createTextNode(" " + txt.DEG));

                    el.appendChild(child);

                    child = new Elem("li");
                    child.appendChild(document.createTextNode(txt.S + " "));

                    fld = new RGBElem("input", {
                        id: ids[this.ID.S],
                        className: "yui-picker-s"
                    });

                    child.appendChild(fld);
                    child.appendChild(document.createTextNode(" " + txt.PERCENT));

                    el.appendChild(child);

                    child = new Elem("li");
                    child.appendChild(document.createTextNode(txt.V + " "));

                    fld = new RGBElem("input", {
                        id: ids[this.ID.V],
                        className: "yui-picker-v"
                    });

                    child.appendChild(fld);
                    child.appendChild(document.createTextNode(" " + txt.PERCENT));

                    el.appendChild(child);
                    p.appendChild(el);

                    // hex summary

                    el = new Elem("ul", {
                        id: ids[this.ID.HEX_SUMMARY],
                        className: "yui-picker-hex_summary"
                    });

                    child = new Elem("li", {
                        id: ids[this.ID.R_HEX]
                    });
                    el.appendChild(child);

                    child = new Elem("li", {
                        id: ids[this.ID.G_HEX]
                    });
                    el.appendChild(child);

                    child = new Elem("li", {
                        id: ids[this.ID.B_HEX]
                    });
                    el.appendChild(child);
                    p.appendChild(el);

                    // hex field
                    el = new Elem("div", {
                        id: ids[this.ID.HEX_CONTROLS],
                        className: "yui-picker-hex-controls"
                    });
                    el.appendChild(document.createTextNode(txt.HEX + " "));

                    child = new RGBElem("input", {
                        id: ids[this.ID.HEX],
                        className: "yui-picker-hex",
                        size: 6,
                        maxlength: 6
                    });

                    el.appendChild(child);
                    p.appendChild(el);

                    p = this.get("element");

                    // swatch
                    el = new Elem("div", {
                        id: ids[this.ID.SWATCH],
                        className: "yui-picker-swatch"
                    });

                    p.appendChild(el);

                    // websafe swatch
                    el = new Elem("div", {
                        id: ids[this.ID.WEBSAFE_SWATCH],
                        className: "yui-picker-websafe-swatch"
                    });

                    p.appendChild(el);

        };

        var _attachRGBHSV = function(id, config) {
            Event.on(this.getElement(id), "keydown", function(e, me) {
                    _rgbFieldKeypress.call(me, e, this, config);
                }, this);
            Event.on(this.getElement(id), "keypress", _numbersOnly, this);
            Event.on(this.getElement(id), "blur", function(e, me) {
                    _useFieldValue.call(me, e, this, config);
                }, this);
        };

        /**
         * Updates the rgb attribute with the current state of the r,g,b
         * fields.  This is invoked from change listeners on these
         * attributes to facilitate updating these values from the
         * individual form fields
         * @method _updateRGB
         * @private
         */
        var _updateRGB = function() {
            var rgb = [this.get(this.OPT.RED),
                       this.get(this.OPT.GREEN),
                       this.get(this.OPT.BLUE)];

            this.logger.log("RGB value set to " + rgb);
            this.set(this.OPT.RGB, rgb);

            _updateSliders.call(this);
        };

        /**
         * Sets the initial state of the sliders
         * @method initPicker
         */
        proto.initPicker = function () {

            // bind all of our elements
            var o=this.OPT,
                ids = this.get(o.IDS),
                els = this.get(o.ELEMENTS),
                      i, el, id;

            // Add the default value as a key for each element for easier lookup
            for (i in this.ID) {
                if (lang.hasOwnProperty(this.ID, i)) {
                    ids[this.ID[i]] = ids[i];
                }
            }

            // Check for picker element, if not there, create all of them
            el = Dom.get(ids[this.ID.PICKER_BG]);
            if (!el) {
                _createElements.call(this);
            } else {
                this.logger.log("Using pre-existing markup");
            }

            for (i in ids) {
                if (lang.hasOwnProperty(ids, i)) {
                    // look for element
                    el = Dom.get(ids[i]);

                    // generate an id if the implementer passed in an element reference,
                    // and the element did not have an id already
                    id = Dom.generateId(el);

                    // update the id in case we generated the id
                    ids[i] = id; // key is WEBSAFE_SWATCH
                    ids[ids[i]] = id; // key is websafe_swatch

                    // store the dom ref
                    els[id] = el;
                }
            }

            // set the initial visibility state of our controls
                els = [o.SHOW_CONTROLS,
                       o.SHOW_RGB_CONTROLS,
                       o.SHOW_HSV_CONTROLS,
                       o.SHOW_HEX_CONTROLS,
                       o.SHOW_HEX_SUMMARY,
                       o.SHOW_WEBSAFE
                       ];

            for (i=0; i<els.length; i=i+1) {
                this.set(els[i], this.get(els[i]));
            }

            var s = this.get(o.PICKER_SIZE);
            this.logger.log("picker size" + s);

            this.hueSlider = Slider.getVertSlider(this.getElement(this.ID.HUE_BG),
                                                  this.getElement(this.ID.HUE_THUMB), 0, s);
            this.hueSlider.subscribe("change", _onHueSliderChange, this, true);

            this.pickerSlider = Slider.getSliderRegion(this.getElement(this.ID.PICKER_BG),
                                                       this.getElement(this.ID.PICKER_THUMB), 0, s, 0, s);
            this.pickerSlider.subscribe("change", _onPickerSliderChange, this, true);

            // Set the animate state
            this.set(o.ANIMATE,this.get(o.ANIMATE));

            //_onHueSliderChange.call(this, 0);

            Event.on(this.getElement(this.ID.WEBSAFE_SWATCH), "click", function(e) {
                   this.setValue(this.get(o.WEBSAFE));
                   //_updateSliders
               }, this, true);

            Event.on(this.getElement(this.ID.CONTROLS_LABEL), "click", function(e) {
                   this.set(o.SHOW_CONTROLS, !this.get(o.SHOW_CONTROLS));
                   Event.preventDefault(e);
               }, this, true);

            _attachRGBHSV.call(this, this.ID.R, this.OPT.RED);
            _attachRGBHSV.call(this, this.ID.G, this.OPT.GREEN);
            _attachRGBHSV.call(this, this.ID.B, this.OPT.BLUE);
            _attachRGBHSV.call(this, this.ID.H, this.OPT.HUE);
            _attachRGBHSV.call(this, this.ID.S, this.OPT.SATURATION);
            _attachRGBHSV.call(this, this.ID.V, this.OPT.VALUE);

            Event.on(this.getElement(this.ID.HEX), "keydown", function(e, me) {
                    _hexFieldKeypress.call(me, e, this, me.OPT.HEX);
                }, this);

            Event.on(this.getElement(this.ID.HEX), "keypress", _hexOnly, this);
            Event.on(this.getElement(this.ID.HEX), "blur", function(e, me) {
                    _useFieldValue.call(me, e, this, me.OPT.HEX);
                }, this);

            _updateRGB.call(this);
        };

        /**
         * Updates the RGB values from the current state of the HSV
         * values.  Executed when the one of the HSV form fields are
         * updated
         * _updateRGBFromHSV
         * @private
         */
        var _updateRGBFromHSV = function() {
            var hsv = [this.get(this.OPT.HUE),
                       this.get(this.OPT.SATURATION)/100,
                       this.get(this.OPT.VALUE)/100];

            var rgb = Color.hsv2rgb(hsv);

            this.logger.log("HSV converted to RGB " + hsv + " : " + rgb);
            this.set(this.OPT.RGB, rgb);

            _updateSliders.call(this);
        };

        /**
         * Parses the hex string to normalize shorthand values, converts
         * the hex value to rgb and updates the rgb attribute (which
         * updates the state for all of the other values)
         * method _updateHex
         * @private
         */
        var _updateHex = function() {

            var hex = this.get(this.OPT.HEX), l=hex.length;

            // support #369 -> #336699 shorthand
            if (l === 3) {
                var c = hex.split(""), i;
                for (i=0; i<l; i=i+1) {
                    c[i] = c[i] + c[i];
                }

                hex = c.join("");
            }

            if (hex.length !== 6) {
                this.logger.log(this.get(this.TXT.ILLEGAL_HEX), "error");
                return false;
            }

            var rgb = Color.hex2rgb(hex);

            this.logger.log(sub("Hex value set to {hex} ({rgb})", {
                    hex: hex, rgb: rgb
                }));

            this.setValue(rgb);

            //_updateSliders.call(this);

        };

        /**
         * Sets up the config attributes and the change listeners for this
         * properties
         * @method initAttributes
         * @param attr An object containing default attribute values
         */
        proto.initAttributes = function(attr) {

            attr = attr || {};
            YAHOO.widget.ColorPicker.superclass.initAttributes.call(this, attr);

            /**
             * The size of the picker. Trying to change this is not recommended.
             * @attribute pickersize
             * @default 180
             * @type int
             */
            this.setAttributeConfig(this.OPT.PICKER_SIZE, {
                    value: attr.size || this.DEFAULT.PICKER_SIZE
                });

            /**
             * The current hue value 0-360
             * @attribute hue
             * @type int
             */
            this.setAttributeConfig(this.OPT.HUE, {
                    value: attr.hue || 0,
                    validator: lang.isNumber
                });

            /**
             * The current saturation value 0-100
             * @attribute saturation
             * @type int
             */
            this.setAttributeConfig(this.OPT.SATURATION, {
                    value: attr.saturation || 0,
                    validator: lang.isNumber
                });

            /**
             * The current value/brightness value 0-100
             * @attribute value
             * @type int
             */
            this.setAttributeConfig(this.OPT.VALUE, {
                    value: lang.isNumber(attr.value) ? attr.value : 100,
                    validator: lang.isNumber
                });

            /**
             * The current red value 0-255
             * @attribute red
             * @type int
             */
            this.setAttributeConfig(this.OPT.RED, {
                    value: lang.isNumber(attr.red) ? attr.red : 255,
                    validator: lang.isNumber
                });

            /**
             * The current green value 0-255
             * @attribute green
             * @type int
             */
            this.setAttributeConfig(this.OPT.GREEN, {
                    value: lang.isNumber(attr.green) ? attr.green : 255,
                    validator: lang.isNumber
                });

            /**
             * The current blue value 0-255
             * @attribute blue
             * @type int
             */
            this.setAttributeConfig(this.OPT.BLUE, {
                    value: lang.isNumber(attr.blue) ? attr.blue : 255,
                    validator: lang.isNumber
                });

            /**
             * The current hex value #000000-#FFFFFF, without the #
             * @attribute hex
             * @type string
             */
            this.setAttributeConfig(this.OPT.HEX, {
                    value: attr.hex || "FFFFFF",
                    validator: lang.isString
                });

            /**
             * The current rgb value.  Updates the state of all of the
             * other value fields.  Read-only: use setValue to set the
             * controls rgb value.
             * @attribute hex
             * @type [int, int, int]
             * @readonly
             */
            this.setAttributeConfig(this.OPT.RGB, {
                    value: attr.rgb || [255,255,255],
                    method: function(rgb) {

                        this.set(this.OPT.RED, rgb[0], true);
                        this.set(this.OPT.GREEN, rgb[1], true);
                        this.set(this.OPT.BLUE, rgb[2], true);

                        var websafe = Color.websafe(rgb);
                        this.set(this.OPT.WEBSAFE, websafe, true);

                        var hex = Color.rgb2hex(rgb);
                        this.set(this.OPT.HEX, hex, true);

                        var hsv = Color.rgb2hsv(rgb);

                        this.logger.log(sub("RGB value set to {rgb} (hsv: {hsv})", {
                                "hsv": hsv, "rgb": rgb
                            }));

                        this.set(this.OPT.HUE, hsv[0], true);
                        this.set(this.OPT.SATURATION, Math.round(hsv[1]*100), true);
                        this.set(this.OPT.VALUE, Math.round(hsv[2]*100), true);
                    },
                    readonly: true
                });

            /**
             * If the color picker will live inside of a container object,
             * set, provide a reference to it so the control can use the
             * container's events.
             * @attribute container
             * @type YAHOO.widget.Panel
             */
            this.setAttributeConfig(this.OPT.CONTAINER, {
                        value: null,
                        method: function(container) {
                            if (container) {
                                // Position can get out of sync when the
                                // control is manipulated while display is
                                // none.  Resetting the slider constraints
                                // when it is visible gets the state back in
                                // order.
                                container.showEvent.subscribe(function() {
                                    // this.pickerSlider.thumb.resetConstraints();
                                    // this.hueSlider.thumb.resetConstraints();
                                    this.pickerSlider.focus();
                                }, this, true);
                            }
                        }
                    });
            /**
             * The closest current websafe value
             * @attribute websafe
             * @type int
             */
            this.setAttributeConfig(this.OPT.WEBSAFE, {
                    value: attr.websafe || [255,255,255]
                });

            var ids = attr.ids || lang.merge({}, this.ID);

            if (!attr.ids && _pickercount > 1) {
                for (var i in ids) {
                    if (lang.hasOwnProperty(ids, i)) {
                        ids[i] = ids[i] + _pickercount;
                    }
                }
            }

            /**
             * A list of element ids and/or element references used by the
             * control.  The default is the this.ID list, and can be customized
             * by passing a list in the contructor
             * @attribute ids
             * @type {referenceid: realid}
             * @writeonce
             */
            this.setAttributeConfig(this.OPT.IDS, {
                    value: ids,
                    writeonce: true
                });

            /**
             * A list of txt strings for internationalization.  Default
             * is this.TXT
             * @attribute txt
             * @type {key: txt}
             * @writeonce
             */
            this.setAttributeConfig(this.OPT.TXT, {
                    value: attr.txt || this.TXT,
                    writeonce: true
                });

            /**
             * The img src default list
             * is this.IMAGES
             * @attribute images
             * @type {key: image}
             * @writeonce
             */
            this.setAttributeConfig(this.OPT.IMAGES, {
                    value: attr.images || this.IMAGE,
                    writeonce: true
                });
            /**
             * The element refs used by this control.  Set at initialization
             * @attribute elements
             * @type {id: HTMLElement}
             * @readonly
             */
            this.setAttributeConfig(this.OPT.ELEMENTS, {
                    value: {},
                    readonly: true
                });

            /**
             * Returns the cached element reference.  If the id is not a string, it
             * is assumed that it is an element and this is returned.
             * @param id {string|HTMLElement} the element key, id, or ref
             * @param on {boolean} hide or show.  If true, show
             * @private */
            var _hideShowEl = function(id, on) {
                var el = (lang.isString(id) ? this.getElement(id) : id);
                //Dom.setStyle(id, "visibility", (on) ? "" : "hidden");
                Dom.setStyle(el, "display", (on) ? "" : "none");
            };

            /**
             * Hide/show the entire set of controls
             * @attribute showcontrols
             * @type boolean
             * @default true
             */
            this.setAttributeConfig(this.OPT.SHOW_CONTROLS, {
                    value: lang.isBoolean(attr.showcontrols) ? attr.showcontrols : true,
                    method: function(on) {

                        var el = Dom.getElementsByClassName("bd", "div",
                                this.getElement(this.ID.CONTROLS))[0];

                        _hideShowEl.call(this, el, on);

                        this.getElement(this.ID.CONTROLS_LABEL).innerHTML =
                            (on) ? this.get(this.OPT.TXT).HIDE_CONTROLS :
                                   this.get(this.OPT.TXT).SHOW_CONTROLS;

                    }
                });

            /**
             * Hide/show the rgb controls
             * @attribute showrgbcontrols
             * @type boolean
             * @default true
             */
            this.setAttributeConfig(this.OPT.SHOW_RGB_CONTROLS, {
                    value: lang.isBoolean(attr.showrgbcontrols) ? attr.showrgbcontrols : true,
                    method: function(on) {
                        //Dom.setStyle(this.getElement(this.ID.RBG_CONTROLS), "visibility", (on) ? "" : "hidden");
                        _hideShowEl.call(this, this.ID.RGB_CONTROLS, on);
                    }
                });

            /**
             * Hide/show the hsv controls
             * @attribute showhsvcontrols
             * @type boolean
             * @default false
             */
            this.setAttributeConfig(this.OPT.SHOW_HSV_CONTROLS, {
                    value: lang.isBoolean(attr.showhsvcontrols) ?
                                          attr.showhsvcontrols : false,
                    method: function(on) {
                        //Dom.setStyle(this.getElement(this.ID.HSV_CONTROLS), "visibility", (on) ? "" : "hidden");
                        _hideShowEl.call(this, this.ID.HSV_CONTROLS, on);

                        // can't show both the hsv controls and the rbg hex summary
                        if (on && this.get(this.OPT.SHOW_HEX_SUMMARY)) {
                            this.set(this.OPT.SHOW_HEX_SUMMARY, false);
                        }
                    }
                });

            /**
             * Hide/show the hex controls
             * @attribute showhexcontrols
             * @type boolean
             * @default true
             */
            this.setAttributeConfig(this.OPT.SHOW_HEX_CONTROLS, {
                    value: lang.isBoolean(attr.showhexcontrols) ?
                                          attr.showhexcontrols : false,
                    method: function(on) {
                        _hideShowEl.call(this, this.ID.HEX_CONTROLS, on);
                    }
                });

            /**
             * Hide/show the websafe swatch
             * @attribute showwebsafe
             * @type boolean
             * @default true
             */
            this.setAttributeConfig(this.OPT.SHOW_WEBSAFE, {
                    value: lang.isBoolean(attr.showwebsafe) ? attr.showwebsafe : true,
                    method: function(on) {
                        _hideShowEl.call(this, this.ID.WEBSAFE_SWATCH, on);
                    }
                });

            /**
             * Hide/show the hex summary
             * @attribute showhexsummary
             * @type boolean
             * @default true
             */
            this.setAttributeConfig(this.OPT.SHOW_HEX_SUMMARY, {
                    value: lang.isBoolean(attr.showhexsummary) ? attr.showhexsummary : true,
                    method: function(on) {
                        _hideShowEl.call(this, this.ID.HEX_SUMMARY, on);

                        // can't show both the hsv controls and the rbg hex summary
                        if (on && this.get(this.OPT.SHOW_HSV_CONTROLS)) {
                            this.set(this.OPT.SHOW_HSV_CONTROLS, false);
                        }
                    }
                });
            this.setAttributeConfig(this.OPT.ANIMATE, {
                    value: lang.isBoolean(attr.animate) ? attr.animate : true,
                    method: function(on) {
                        this.pickerSlider.animate = on;
                        this.hueSlider.animate = on;
                    }
                });

            this.on(this.OPT.HUE + "Change", _updateRGBFromHSV, this, true);
            this.on(this.OPT.SATURATION + "Change", _updateRGBFromHSV, this, true);
            this.on(this.OPT.VALUE + "Change", _updatePickerSlider, this, true);

            this.on(this.OPT.RED + "Change", _updateRGB, this, true);
            this.on(this.OPT.GREEN + "Change", _updateRGB, this, true);
            this.on(this.OPT.BLUE + "Change", _updateRGB, this, true);

            this.on(this.OPT.HEX + "Change", _updateHex, this, true);

            this.initPicker();
        };

    })();

Copyright © 2007 Yahoo! Inc. All rights reserved.