text: /*******************************************************************************
   * Copyright 2015 DANS - Data Archiving and Networked Services
   * 
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   * 
   *   http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
  *******************************************************************************/
  /*
  Copyright (c) 2010, Yahoo! Inc. All rights reserved.
  Code licensed under the BSD License:
  http://developer.yahoo.com/yui/license.html
  version: 2.8.1
  */
  /**
   * Provides color conversion and validation utils
   * @class YAHOO.util.Color
   * @namespace YAHOO.util
   */
  YAHOO.util.Color = function() {
  
      var ZERO     = "0",
          isArray  = YAHOO.lang.isArray,
          isNumber = YAHOO.lang.isNumber;
  
      return {
  
          /**
           * Converts 0-1 to 0-255
           * @method real2dec
           * @param n {float} the number to convert
           * @return {int} a number 0-255
           */
          real2dec: function(n) {
              return Math.min(255, Math.round(n*256));
          },
  
          /**
           * Converts HSV (h[0-360], s[0-1]), v[0-1] to RGB [255,255,255]
           * @method hsv2rgb
           * @param h {int|[int, float, float]} the hue, or an
           *        array containing all three parameters
           * @param s {float} the saturation
           * @param v {float} the value/brightness
           * @return {[int, int, int]} the red, green, blue values in
           *          decimal.
           */
          hsv2rgb: function(h, s, v) { 
  
              if (isArray(h)) {
                  return this.hsv2rgb.call(this, h[0], h[1], h[2]);
              }
  
              var r, g, b,
                  i = Math.floor((h/60)%6),
                  f = (h/60)-i,
                  p = v*(1-s),
                  q = v*(1-f*s),
                  t = v*(1-(1-f)*s),
                  fn;
  
              switch (i) {
                  case 0: r=v; g=t; b=p; break;
                  case 1: r=q; g=v; b=p; break;
                  case 2: r=p; g=v; b=t; break;
                  case 3: r=p; g=q; b=v; break;
                  case 4: r=t; g=p; b=v; break;
                  case 5: r=v; g=p; b=q; break;
              }
  
              fn=this.real2dec;
  
              return [fn(r), fn(g), fn(b)];
          },
  
          /**
           * Converts to RGB [255,255,255] to HSV (h[0-360], s[0-1]), v[0-1]
           * @method rgb2hsv
           * @param r {int|[int, int, int]} the red value, or an
           *        array containing all three parameters
           * @param g {int} the green value
           * @param b {int} the blue value
           * @return {[int, float, float]} the value converted to hsv
           */
          rgb2hsv: function(r, g, b) {
  
              if (isArray(r)) {
                  return this.rgb2hsv.apply(this, r);
              }
  
              r /= 255;
              g /= 255;
              b /= 255;
  
              var h,s,
                  min = Math.min(Math.min(r,g),b),
                  max = Math.max(Math.max(r,g),b),
                  delta = max-min,
                  hsv;
  
              switch (max) {
                  case min: h=0; break;
                  case r:   h=60*(g-b)/delta; 
                            if (g<b) {
                                h+=360;
                            }
                            break;
                  case g:   h=(60*(b-r)/delta)+120; break;
                  case b:   h=(60*(r-g)/delta)+240; break;
              }
              
              s = (max === 0) ? 0 : 1-(min/max);
  
              hsv = [Math.round(h), s, max];
  
              return hsv;
          },
  
          /**
           * Converts decimal rgb values into a hex string
           * 255,255,255 -> FFFFFF
           * @method rgb2hex
           * @param r {int|[int, int, int]} the red value, or an
           *        array containing all three parameters
           * @param g {int} the green value
           * @param b {int} the blue value
           * @return {string} the hex string
           */
          rgb2hex: function(r, g, b) {
              if (isArray(r)) {
                  return this.rgb2hex.apply(this, r);
              }
  
              var f=this.dec2hex;
              return f(r) + f(g) + f(b);
          },
       
          /**
           * Converts an int 0...255 to hex pair 00...FF
           * @method dec2hex
           * @param n {int} the number to convert
           * @return {string} the hex equivalent
           */
          dec2hex: function(n) {
              n = parseInt(n,10)|0;
              n = (n > 255 || n < 0) ? 0 : n;
  
              return (ZERO+n.toString(16)).slice(-2).toUpperCase();
          },
  
          /**
           * Converts a hex pair 00...FF to an int 0...255 
           * @method hex2dec
           * @param str {string} the hex pair to convert
           * @return {int} the decimal
           */
          hex2dec: function(str) {
              return parseInt(str,16);
          },
  
          /**
           * Converts a hex string to rgb
           * @method hex2rgb
           * @param str {string} the hex string
           * @return {[int, int, int]} an array containing the rgb values
           */
          hex2rgb: function(s) { 
              var f = this.hex2dec;
              return [f(s.slice(0, 2)), f(s.slice(2, 4)), f(s.slice(4, 6))];
          },
  
          /**
           * Returns the closest websafe color to the supplied rgb value.
           * @method websafe
           * @param r {int|[int, int, int]} the red value, or an
           *        array containing all three parameters
           * @param g {int} the green value
           * @param b {int} the blue value
           * @return {[int, int, int]} an array containing the closes
           *                           websafe rgb colors.
           */
          websafe: function(r, g, b) {
  
              if (isArray(r)) {
                  return this.websafe.apply(this, r);
              }
  
              // returns the closest match [0, 51, 102, 153, 204, 255]
              var f = function(v) {
                  if (isNumber(v)) {
                      v = Math.min(Math.max(0, v), 255);
                      var i, next;
                      for (i=0; i<256; i=i+51) {
                          next = i+51;
                          if (v >= i && v <= next) {
                              return (v-i > 25) ? next : i;
                          }
                      }
                  }
  
                  return v;
              };
  
              return [f(r), f(g), f(b)];
          }
      };
  }();
  
  
  /**
   * The colorpicker module provides a widget for selecting colors
   * @module colorpicker
   * @requires yahoo, dom, event, element, slider
   */
  (function() {
  
      var _pickercount = 0,
          util   = YAHOO.util,
          lang   = YAHOO.lang,
          Slider = YAHOO.widget.Slider,
          Color  = util.Color,
          Dom    = util.Dom,
          Event  = util.Event,
          sub    = lang.substitute,
          
          b = "yui-picker";
      
  
      /**
       * 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.
       */
      function ColorPicker(el, attr) {
          _pickercount = _pickercount + 1;
          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
              el = this._createHostElement(attr);
          }
  
      	ColorPicker.superclass.constructor.call(this, el, attr); 
  
          this.initPicker();
      }
  
      YAHOO.extend(ColorPicker, YAHOO.util.Element, {
      
          /**
           * The element ids used by this control
           * @property ID
           * @final
           */
          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
           */
          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
           */
          IMAGE : {
              PICKER_THUMB: "../../build/colorpicker/assets/picker_thumb.png",
              HUE_THUMB: "../../build/colorpicker/assets/hue_thumb.png"
          },
  
          /**
           * Constants for the control's default default values
           * @property DEFAULT
           * @final
           */
          DEFAULT : {
              PICKER_SIZE: 180
          },
  
          /**
           * Constants for the control's configuration attributes
           * @property OPT
           * @final
           */
          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",
              CONTAINER         : "container",
              IDS      : "ids",
              ELEMENTS : "elements",
              TXT      : "txt",
              IMAGES   : "images",
              ANIMATE  : "animate"
          },
  
          /**
           * Flag to allow individual UI updates to forego animation if available.
           * True during construction for initial thumb placement.  Set to false
           * after that.
           *
           * @property skipAnim
           * @type Boolean
           * @default true
           */
          skipAnim : true,
  
          /**
           * Creates the host element if it doesn't exist
           * @method _createHostElement
           * @protected
           */
          _createHostElement : function () {
              var el = document.createElement('div');
  
              if (this.CSS.BASE) {
                  el.className = this.CSS.BASE;
              }
              
              return el;
          },
  
          /**
           * Moves the hue slider into the position dictated by the current state
           * of the control
           * @method _updateHueSlider
           * @protected
           */
          _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.hueSlider.setValue(h, this.skipAnim);
          },
  
          /**
           * Moves the picker slider into the position dictated by the current state
           * of the control
           * @method _updatePickerSlider
           * @protected
           */
          _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.pickerSlider.setRegionValue(s, v, this.skipAnim);
          },
  
          /**
           * Moves the sliders into the position dictated by the current state
           * of the control
           * @method _updateSliders
           * @protected
           */
          _updateSliders : function() {
              this._updateHueSlider();
              this._updatePickerSlider();
          },
  
          /**
           * 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
           */
          setValue : function(rgb, silent) {
              silent = (silent) || false;
              this.set(this.OPT.RGB, rgb, silent);
              this._updateSliders();
          },
  
          /**
           * The hue slider
           * @property hueSlider
           * @type YAHOO.widget.Slider
           */
          hueSlider : null,
          
          /**
           * The picker region
           * @property pickerSlider
           * @type YAHOO.widget.Slider
           */
          pickerSlider : null,
  
          /**
           * Translates the slider value into hue, int[0,359]
           * @method _getH
           * @protected
           * @return {int} the hue from 0 to 359
           */
          _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
           * @protected
           * @return {int} the saturation from 0 to 1
           */
          _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
           * @protected
           * @return {int} the value from 0 to 1
           */
          _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
           * @protected
           */
          _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 = sub(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 = sub(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
           * @protected
           */
          _getValuesFromSliders : function() {
              this.set(this.OPT.RGB, Color.hsv2rgb(this._getH(), this._getS(), this._getV()));
          },
  
          /**
           * Updates the form field controls with the state data contained
           * in the control.
           * @method _updateFormFields
           * @protected
           */
          _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
           * @protected
           */
          _onHueSliderChange : function(newOffset) {
  
              var h        = this._getH(),
                  rgb      = Color.hsv2rgb(h, 1, 1),
                  styleDef = "rgb(" + rgb.join(",") + ")";
  
              this.set(this.OPT.HUE, h, true);
  
              // set picker background to the hue
              Dom.setStyle(this.getElement(this.ID.PICKER_BG), "background-color", styleDef);
  
              if (this.hueSlider.valueChangeSource !== Slider.SOURCE_SET_VALUE) {
                  this._getValuesFromSliders();
              }
  
              this._updateFormFields();
              this._updateSwatch();
          },
  
          /**
           * 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
           * @protected
           */
          _onPickerSliderChange : function(newOffset) {
  
              var s=this._getS(), v=this._getV();
              this.set(this.OPT.SATURATION, Math.round(s*100), true);
              this.set(this.OPT.VALUE, Math.round(v*100), true);
  
              if (this.pickerSlider.valueChangeSource !== Slider.SOURCE_SET_VALUE) {
                  this._getValuesFromSliders();
              }
  
              this._updateFormFields();
              this._updateSwatch();
          },
  
  
          /**
           * 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>
           * @protected
           */
          _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 or ctrl/meta sequences
              } else if ("8, 9, 13, 27, 37, 39".indexOf(c) > -1 ||
                         e.ctrlKey || e.metaKey) { // 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 _useFieldValue
           * @param e {Event} an event
           * @param el {HTMLElement} the field
           * @param prop {string} the key to the linked property
           * @protected
           */
          _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
           * @protected
           */
          _rgbFieldKeypress : function(e, el, prop) {
              var command = this._getCommand(e),
                  inc = (e.shiftKey) ? 10 : 1;
              switch (command) {
                  case 6: // return, update the value
                      this._useFieldValue.apply(this, arguments);
                      break;
                              
                  case 3: // up arrow, increment
                      this.set(prop, Math.min(this.get(prop)+inc, 255));
                      this._updateFormFields();
                      //Event.stopEvent(e);
                      break;
                  case 4: // down arrow, decrement
                      this.set(prop, Math.max(this.get(prop)-inc, 0));
                      this._updateFormFields();
                      //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
           * @protected
           */
          _hexFieldKeypress : function(e, el, prop) {
              var command = this._getCommand(e);
              if (command === 6) { // return, update the value
                  this._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
           * @protected
           * @return {boolean} false if we are canceling the event
           */
          _hexOnly : function(e, numbersOnly) {
              var command = this._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
           * @protected
           * @return {boolean} false if we are canceling the event
           */
          _numbersOnly : function(e) {
              return this._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
           */
          getElement : function(id) { 
              return this.get(this.OPT.ELEMENTS)[this.get(this.OPT.IDS)[id]]; 
          },
  
          _createElements : function() {
              var el, child, img, fld, p,
                  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);
                  };
  
              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);
  
          },
  
          _attachRGBHSV : function(id, config) {
              Event.on(this.getElement(id), "keydown", function(e, me) {
                      me._rgbFieldKeypress(e, this, config);
                  }, this);
              Event.on(this.getElement(id), "keypress", this._numbersOnly, this, true);
              Event.on(this.getElement(id), "blur", function(e, me) {
                      me._useFieldValue(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
           * @protected
           */
          _updateRGB : function() {
              var rgb = [this.get(this.OPT.RED), 
                         this.get(this.OPT.GREEN),
                         this.get(this.OPT.BLUE)];
  
              this.set(this.OPT.RGB, rgb);
  
              this._updateSliders();
          },
  
          /**
           * Creates any missing DOM structure.
           *
           * @method _initElements
           * @protected
           */
          _initElements : 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) {
                  this._createElements();
              } else {
              }
  
              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;
                  }
              }
  
          },
  
          /**
           * Sets the initial state of the sliders
           * @method initPicker
           */
          initPicker : function () {
              this._initSliders();
              this._bindUI();
              this.syncUI(true);
          },
  
          /**
           * Creates the Hue and Value/Saturation Sliders.
           *
           * @method _initSliders
           * @protected
           */
          _initSliders : function () {
              var ID = this.ID,
                  size = this.get(this.OPT.PICKER_SIZE);
  
  
              this.hueSlider = Slider.getVertSlider(
                  this.getElement(ID.HUE_BG), 
                  this.getElement(ID.HUE_THUMB), 0, size);
  
              this.pickerSlider = Slider.getSliderRegion(
                  this.getElement(ID.PICKER_BG), 
                  this.getElement(ID.PICKER_THUMB), 0, size, 0, size);
  
              // Apply animate attribute configuration
              this.set(this.OPT.ANIMATE, this.get(this.OPT.ANIMATE));
          },
  
          /**
           * Adds event listeners to Sliders and UI elements.  Wires everything
           * up.
           *
           * @method _bindUI
           * @protected
           */
          _bindUI : function () {
              var ID = this.ID,
                  O  = this.OPT;
  
              this.hueSlider.subscribe("change",
                  this._onHueSliderChange, this, true);
              this.pickerSlider.subscribe("change",
                  this._onPickerSliderChange, this, true);
  
              Event.on(this.getElement(ID.WEBSAFE_SWATCH), "click", function(e) {
                     this.setValue(this.get(O.WEBSAFE));
                 }, this, true);
  
              Event.on(this.getElement(ID.CONTROLS_LABEL), "click", function(e) {
                     this.set(O.SHOW_CONTROLS, !this.get(O.SHOW_CONTROLS));
                     Event.preventDefault(e);
                 }, this, true);
  
              this._attachRGBHSV(ID.R, O.RED); 
              this._attachRGBHSV(ID.G, O.GREEN); 
              this._attachRGBHSV(ID.B, O.BLUE); 
              this._attachRGBHSV(ID.H, O.HUE); 
              this._attachRGBHSV(ID.S, O.SATURATION); 
              this._attachRGBHSV(ID.V, O.VALUE); 
  
              Event.on(this.getElement(ID.HEX), "keydown", function(e, me) {
                      me._hexFieldKeypress(e, this, O.HEX);
                  }, this);
  
              Event.on(this.getElement(this.ID.HEX), "keypress",
                  this._hexOnly, this,true);
              Event.on(this.getElement(this.ID.HEX), "blur", function(e, me) {
                      me._useFieldValue(e, this, O.HEX);
                  }, this);
          },
  
          /**
           * Wrapper for _updateRGB, but allows setting 
           *
           * @method syncUI
           * @param skipAnim {Boolean} Omit Slider animation for this action
           */
          syncUI : function (skipAnim) {
              this.skipAnim = skipAnim;
              this._updateRGB();
              this.skipAnim = false;
          },
  
  
          /**
           * Updates the RGB values from the current state of the HSV
           * values.  Executed when the one of the HSV form fields are
           * updated
           * _updateRGBFromHSV
           * @protected
           */
          _updateRGBFromHSV : function() {
              var hsv = [this.get(this.OPT.HUE), 
                         this.get(this.OPT.SATURATION)/100,
                         this.get(this.OPT.VALUE)/100],
                  rgb = Color.hsv2rgb(hsv);
  
              this.set(this.OPT.RGB, rgb);
  
              this._updateSliders();
          },
  
          /**
           * 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
           * @protected
           */
          _updateHex : function() {
             
              var hex = this.get(this.OPT.HEX),
                  l   = hex.length,
                  c,i,rgb;
  
              // support #369 -> #336699 shorthand
              if (l === 3) {
                  c = hex.split("");
                  for (i=0; i<l; i=i+1) {
                      c[i] = c[i] + c[i];
                  }
  
                  hex = c.join("");
              }
  
              if (hex.length !== 6) {
                  return false;
              }
  
              rgb = Color.hex2rgb(hex);
  
  
              this.setValue(rgb);
          },
  
  
          /**
           * 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
           * @protected
           */
          _hideShowEl : function(id, on) {
              var el = (lang.isString(id) ? this.getElement(id) : id);
              Dom.setStyle(el, "display", (on) ? "" : "none");
          },
  
  
          /**
           * Sets up the config attributes and the change listeners for this
           * properties
           * @method initAttributes
           * @param attr An object containing default attribute values
           */
          initAttributes : function(attr) {
  
              attr = attr || {};
              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),
                              hex = Color.rgb2hex(rgb),
                              hsv = Color.rgb2hsv(rgb);
  
                          this.set(this.OPT.WEBSAFE, websafe, true);
                          this.set(this.OPT.HEX, hex, true);
  
  
  
                          // fix bug #1754338 - when saturation is 0, hue is
                          // silently always set to 0, but input field not updated
                          if (hsv[1]) {
                              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), i;
  
              if (!attr.ids && _pickercount > 1) {
                  for (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
                  });
  
              /**
               * 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];
  
                          this._hideShowEl(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) {
                          this._hideShowEl(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");
                          this._hideShowEl(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) {
                          this._hideShowEl(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) {
                          this._hideShowEl(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) {
                          this._hideShowEl(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) {
                          if (this.pickerSlider) {
                              this.pickerSlider.animate = on;
                              this.hueSlider.animate = on;
                          }
                      }
                  });
  
              this.on(this.OPT.HUE + "Change", this._updateRGBFromHSV, this, true);
              this.on(this.OPT.SATURATION + "Change", this._updateRGBFromHSV, this, true);
              this.on(this.OPT.VALUE + "Change", this._updateRGBFromHSV, this, true);
  
              this.on(this.OPT.RED + "Change", this._updateRGB, this, true);
              this.on(this.OPT.GREEN + "Change", this._updateRGB, this, true);
              this.on(this.OPT.BLUE + "Change", this._updateRGB, this, true);
  
              this.on(this.OPT.HEX + "Change", this._updateHex, this, true);
  
              this._initElements();
          }
      });
  
      YAHOO.widget.ColorPicker = ColorPicker;
  })();
  YAHOO.register("colorpicker", YAHOO.widget.ColorPicker, {version: "2.8.1", build: "19"});
  