2 * Bootstrap Colorpicker
3 * http://mjolnic.github.io/bootstrap-colorpicker/
5 * Originally written by (c) 2012 Stefan Petre
6 * Licensed under the Apache License v2.0
7 * http://www.apache.org/licenses/LICENSE-2.0.txt
15 var Color = function(val) {
22 this.origFormat = null; // original string format
24 if (val.toLowerCase !== undefined) {
26 } else if (val.h !== undefined) {
34 _sanitizeNumber: function(val) {
35 if (typeof val === 'number') {
38 if (isNaN(val) || (val === null) || (val === '') || (val === undefined)) {
41 if (val.toLowerCase !== undefined) {
42 return parseFloat(val);
46 //parse a string to HSB
47 setColor: function(strVal) {
48 strVal = strVal.toLowerCase();
49 this.value = this.stringToHSB(strVal) || {
56 stringToHSB: function(strVal) {
57 strVal = strVal.toLowerCase();
60 $.each(this.stringParsers, function(i, parser) {
61 var match = parser.re.exec(strVal),
62 values = match && parser.parse.apply(that, [match]),
63 format = parser.format || 'rgba';
65 if (format.match(/hsla?/)) {
66 result = that.RGBtoHSB.apply(that, that.HSLtoRGB.apply(that, values));
68 result = that.RGBtoHSB.apply(that, values);
70 that.origFormat = format;
80 setSaturation: function(s) {
83 setBrightness: function(b) {
86 setAlpha: function(a) {
87 this.value.a = parseInt((1 - a) * 100, 10) / 100;
89 toRGB: function(h, s, v, a) {
90 h = h || this.value.h;
91 s = s || this.value.s;
92 v = v || this.value.b;
93 a = a || this.value.a;
95 var r, g, b, i, f, p, q, t;
96 if (h && s === undefined && v === undefined) {
97 s = h.s, v = h.v, h = h.h;
99 i = Math.floor(h * 6);
103 t = v * (1 - (1 - f) * s);
125 r: Math.floor(r * 255),
126 g: Math.floor(g * 255),
127 b: Math.floor(b * 255),
131 toHex: function(h, s, b, a) {
132 var rgb = this.toRGB(h, s, b, a);
133 return '#' + ((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1);
135 toHSL: function(h, s, b, a) {
136 h = h || this.value.h;
137 s = s || this.value.s;
138 b = b || this.value.b;
139 a = a || this.value.a;
144 if (L > 0 && L <= 1) {
160 RGBtoHSB: function(r, g, b, a) {
166 V = Math.max(r, g, b);
167 C = V - Math.min(r, g, b);
168 H = (C === 0 ? null :
169 V === r ? (g - b) / C :
170 V === g ? (b - r) / C + 2 :
173 H = ((H + 360) % 6) * 60 / 360;
174 S = C === 0 ? 0 : C / V;
176 h: this._sanitizeNumber(H),
179 a: this._sanitizeNumber(a)
182 HueToRGB: function(p, q, h) {
189 return p + (q - p) * h * 6;
190 } else if ((h * 2) < 1) {
192 } else if ((h * 3) < 2) {
193 return p + (q - p) * ((2 / 3) - h) * 6;
198 HSLtoRGB: function(h, s, l, a) {
211 var tr = h + (1 / 3);
213 var tb = h - (1 / 3);
215 var r = Math.round(this.HueToRGB(p, q, tr) * 255);
216 var g = Math.round(this.HueToRGB(p, q, tg) * 255);
217 var b = Math.round(this.HueToRGB(p, q, tb) * 255);
218 return [r, g, b, this._sanitizeNumber(a)];
220 toString: function(format) {
221 format = format || 'rgba';
225 var rgb = this.toRGB();
226 return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
231 var rgb = this.toRGB();
232 return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')';
237 var hsl = this.toHSL();
238 return 'hsl(' + Math.round(hsl.h * 360) + ',' + Math.round(hsl.s * 100) + '%,' + Math.round(hsl.l * 100) + '%)';
243 var hsl = this.toHSL();
244 return 'hsla(' + Math.round(hsl.h * 360) + ',' + Math.round(hsl.s * 100) + '%,' + Math.round(hsl.l * 100) + '%,' + hsl.a + ')';
259 // a set of RE's that can match strings and generate color tuples.
260 // from John Resig color plugin
261 // https://github.com/jquery/jquery-color/
263 re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
265 parse: function(execResult) {
267 parseInt(execResult[1], 16),
268 parseInt(execResult[2], 16),
269 parseInt(execResult[3], 16),
274 re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
276 parse: function(execResult) {
278 parseInt(execResult[1] + execResult[1], 16),
279 parseInt(execResult[2] + execResult[2], 16),
280 parseInt(execResult[3] + execResult[3], 16),
285 re: /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/,
287 parse: function(execResult) {
296 re: /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/,
298 parse: function(execResult) {
300 2.55 * execResult[1],
301 2.55 * execResult[2],
302 2.55 * execResult[3],
307 re: /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
309 parse: function(execResult) {
318 re: /rgba\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
320 parse: function(execResult) {
322 2.55 * execResult[1],
323 2.55 * execResult[2],
324 2.55 * execResult[3],
329 re: /hsl\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/,
331 parse: function(execResult) {
340 re: /hsla\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
342 parse: function(execResult) {
351 //predefined color name
354 parse: function(execResult) {
355 var hexval = this.colorNameToHex(execResult[0]) || '#000000';
356 var match = this.stringParsers[0].re.exec(hexval),
357 values = match && this.stringParsers[0].parse.apply(this, [match]);
361 colorNameToHex: function(name) {
362 // 140 predefined colors from the HTML Colors spec
364 "aliceblue": "#f0f8ff",
365 "antiquewhite": "#faebd7",
367 "aquamarine": "#7fffd4",
372 "blanchedalmond": "#ffebcd",
374 "blueviolet": "#8a2be2",
376 "burlywood": "#deb887",
377 "cadetblue": "#5f9ea0",
378 "chartreuse": "#7fff00",
379 "chocolate": "#d2691e",
381 "cornflowerblue": "#6495ed",
382 "cornsilk": "#fff8dc",
383 "crimson": "#dc143c",
385 "darkblue": "#00008b",
386 "darkcyan": "#008b8b",
387 "darkgoldenrod": "#b8860b",
388 "darkgray": "#a9a9a9",
389 "darkgreen": "#006400",
390 "darkkhaki": "#bdb76b",
391 "darkmagenta": "#8b008b",
392 "darkolivegreen": "#556b2f",
393 "darkorange": "#ff8c00",
394 "darkorchid": "#9932cc",
395 "darkred": "#8b0000",
396 "darksalmon": "#e9967a",
397 "darkseagreen": "#8fbc8f",
398 "darkslateblue": "#483d8b",
399 "darkslategray": "#2f4f4f",
400 "darkturquoise": "#00ced1",
401 "darkviolet": "#9400d3",
402 "deeppink": "#ff1493",
403 "deepskyblue": "#00bfff",
404 "dimgray": "#696969",
405 "dodgerblue": "#1e90ff",
406 "firebrick": "#b22222",
407 "floralwhite": "#fffaf0",
408 "forestgreen": "#228b22",
409 "fuchsia": "#ff00ff",
410 "gainsboro": "#dcdcdc",
411 "ghostwhite": "#f8f8ff",
413 "goldenrod": "#daa520",
416 "greenyellow": "#adff2f",
417 "honeydew": "#f0fff0",
418 "hotpink": "#ff69b4",
419 "indianred ": "#cd5c5c",
420 "indigo ": "#4b0082",
423 "lavender": "#e6e6fa",
424 "lavenderblush": "#fff0f5",
425 "lawngreen": "#7cfc00",
426 "lemonchiffon": "#fffacd",
427 "lightblue": "#add8e6",
428 "lightcoral": "#f08080",
429 "lightcyan": "#e0ffff",
430 "lightgoldenrodyellow": "#fafad2",
431 "lightgrey": "#d3d3d3",
432 "lightgreen": "#90ee90",
433 "lightpink": "#ffb6c1",
434 "lightsalmon": "#ffa07a",
435 "lightseagreen": "#20b2aa",
436 "lightskyblue": "#87cefa",
437 "lightslategray": "#778899",
438 "lightsteelblue": "#b0c4de",
439 "lightyellow": "#ffffe0",
441 "limegreen": "#32cd32",
443 "magenta": "#ff00ff",
445 "mediumaquamarine": "#66cdaa",
446 "mediumblue": "#0000cd",
447 "mediumorchid": "#ba55d3",
448 "mediumpurple": "#9370d8",
449 "mediumseagreen": "#3cb371",
450 "mediumslateblue": "#7b68ee",
451 "mediumspringgreen": "#00fa9a",
452 "mediumturquoise": "#48d1cc",
453 "mediumvioletred": "#c71585",
454 "midnightblue": "#191970",
455 "mintcream": "#f5fffa",
456 "mistyrose": "#ffe4e1",
457 "moccasin": "#ffe4b5",
458 "navajowhite": "#ffdead",
460 "oldlace": "#fdf5e6",
462 "olivedrab": "#6b8e23",
464 "orangered": "#ff4500",
466 "palegoldenrod": "#eee8aa",
467 "palegreen": "#98fb98",
468 "paleturquoise": "#afeeee",
469 "palevioletred": "#d87093",
470 "papayawhip": "#ffefd5",
471 "peachpuff": "#ffdab9",
475 "powderblue": "#b0e0e6",
478 "rosybrown": "#bc8f8f",
479 "royalblue": "#4169e1",
480 "saddlebrown": "#8b4513",
482 "sandybrown": "#f4a460",
483 "seagreen": "#2e8b57",
484 "seashell": "#fff5ee",
487 "skyblue": "#87ceeb",
488 "slateblue": "#6a5acd",
489 "slategray": "#708090",
491 "springgreen": "#00ff7f",
492 "steelblue": "#4682b4",
495 "thistle": "#d8bfd8",
497 "turquoise": "#40e0d0",
501 "whitesmoke": "#f5f5f5",
503 "yellowgreen": "#9acd32"
506 if (typeof colors[name.toLowerCase()] !== 'undefined') {
507 return colors[name.toLowerCase()];
515 horizontal: false, // horizontal mode layout ?
516 inline: false, //forces to show the colorpicker as an inline element
517 color: false, //forces a color
518 format: false, //forces a format
519 input: 'input', // children input selector
520 container: false, // container selector
521 component: '.add-on, .input-group-addon', // children component selector
526 callLeft: 'setSaturation',
527 callTop: 'setBrightness'
546 callLeft: 'setSaturation',
547 callTop: 'setBrightness'
558 callLeft: 'setAlpha',
562 template: '<div class="colorpicker dropdown-menu">' +
563 '<div class="colorpicker-saturation"><i><b></b></i></div>' +
564 '<div class="colorpicker-hue"><i></i></div>' +
565 '<div class="colorpicker-alpha"><i></i></div>' +
566 '<div class="colorpicker-color"><div /></div>' +
570 var Colorpicker = function(element, options) {
571 this.element = $(element).addClass('colorpicker-element');
572 this.options = $.extend({}, defaults, this.element.data(), options);
573 this.component = this.options.component;
574 this.component = (this.component !== false) ? this.element.find(this.component) : false;
575 if (this.component && (this.component.length === 0)) {
576 this.component = false;
578 this.container = (this.options.container === true) ? this.element : this.options.container;
579 this.container = (this.container !== false) ? $(this.container) : false;
581 // Is the element an input? Should we search inside for any input?
582 this.input = this.element.is('input') ? this.element : (this.options.input ?
583 this.element.find(this.options.input) : false);
584 if (this.input && (this.input.length === 0)) {
588 this.color = new Color(this.options.color !== false ? this.options.color : this.getValue());
589 this.format = this.options.format !== false ? this.options.format : this.color.origFormat;
592 this.picker = $(this.options.template);
593 if (this.options.inline) {
594 this.picker.addClass('colorpicker-inline colorpicker-visible');
596 this.picker.addClass('colorpicker-hidden');
598 if (this.options.horizontal) {
599 this.picker.addClass('colorpicker-horizontal');
601 if (this.format === 'rgba' || this.format === 'hsla') {
602 this.picker.addClass('colorpicker-with-alpha');
604 this.picker.on('mousedown.colorpicker', $.proxy(this.mousedown, this));
605 this.picker.appendTo(this.container ? this.container : $('body'));
608 if (this.input !== false) {
610 'keyup.colorpicker': $.proxy(this.keyup, this)
612 if (this.component === false) {
614 'focus.colorpicker': $.proxy(this.show, this)
617 if (this.options.inline === false) {
619 'focusout.colorpicker': $.proxy(this.hide, this)
624 if (this.component !== false) {
626 'click.colorpicker': $.proxy(this.show, this)
630 if ((this.input === false) && (this.component === false)) {
632 'click.colorpicker': $.proxy(this.show, this)
637 $($.proxy(function() {
638 this.element.trigger('create');
642 Colorpicker.version = '2.0.0-beta';
644 Colorpicker.Color = Color;
646 Colorpicker.prototype = {
647 constructor: Colorpicker,
648 destroy: function() {
649 this.picker.remove();
650 this.element.removeData('colorpicker').off('.colorpicker');
651 if (this.input !== false) {
652 this.input.off('.colorpicker');
654 if (this.component !== false) {
655 this.component.off('.colorpicker');
657 this.element.removeClass('colorpicker-element');
658 this.element.trigger({
662 reposition: function() {
663 if (this.options.inline !== false) {
666 var offset = this.component ? this.component.offset() : this.element.offset();
668 top: offset.top + (this.component ? this.component.outerHeight() : this.element.outerHeight()),
673 if (this.isDisabled()) {
676 this.picker.addClass('colorpicker-visible').removeClass('colorpicker-hidden');
678 $(window).on('resize.colorpicker', $.proxy(this.reposition, this));
679 if (!this.hasInput() && e) {
680 if (e.stopPropagation && e.preventDefault) {
685 if (this.options.inline === false) {
686 $(window.document).on({
687 'mousedown.colorpicker': $.proxy(this.hide, this)
690 this.element.trigger({
696 this.picker.addClass('colorpicker-hidden').removeClass('colorpicker-visible');
697 $(window).off('resize.colorpicker', this.reposition);
699 'mousedown.colorpicker': this.hide
702 this.element.trigger({
707 updateData: function(val) {
708 val = val || this.color.toString(this.format);
709 this.element.data('color', val);
712 updateInput: function(val) {
713 val = val || this.color.toString(this.format);
714 if (this.input !== false) {
715 this.input.prop('value', val);
719 updatePicker: function(val) {
720 if (val !== undefined) {
721 this.color = new Color(val);
723 var sl = (this.options.horizontal === false) ? this.options.sliders : this.options.slidersHorz;
724 var icns = this.picker.find('i');
725 if (icns.length === 0) {
728 if (this.options.horizontal === false) {
729 sl = this.options.sliders;
730 icns.eq(1).css('top', sl.hue.maxTop * (1 - this.color.value.h)).end()
731 .eq(2).css('top', sl.alpha.maxTop * (1 - this.color.value.a));
733 sl = this.options.slidersHorz;
734 icns.eq(1).css('left', sl.hue.maxLeft * (1 - this.color.value.h)).end()
735 .eq(2).css('left', sl.alpha.maxLeft * (1 - this.color.value.a));
738 'top': sl.saturation.maxTop - this.color.value.b * sl.saturation.maxTop,
739 'left': this.color.value.s * sl.saturation.maxLeft
741 this.picker.find('.colorpicker-saturation').css('backgroundColor', this.color.toHex(this.color.value.h, 1, 1, 1));
742 this.picker.find('.colorpicker-alpha').css('backgroundColor', this.color.toHex());
743 this.picker.find('.colorpicker-color, .colorpicker-color div').css('backgroundColor', this.color.toString(this.format));
746 updateComponent: function(val) {
747 val = val || this.color.toString(this.format);
748 if (this.component !== false) {
749 var icn = this.component.find('i').eq(0);
750 if (icn.length > 0) {
752 'backgroundColor': val
756 'backgroundColor': val
762 update: function(force) {
763 var val = this.updateComponent();
764 if ((this.getValue(false) !== false) || (force === true)) {
765 // Update input/data only if the current value is not blank
766 this.updateInput(val);
767 this.updateData(val);
773 setValue: function(val) { // set color manually
774 this.color = new Color(val);
776 this.element.trigger({
782 getValue: function(defaultValue) {
783 defaultValue = (defaultValue === undefined) ? '#000000' : defaultValue;
785 if (this.hasInput()) {
786 val = this.input.val();
788 val = this.element.data('color');
790 if ((val === undefined) || (val === '') || (val === null)) {
791 // if not defined or empty, return default
796 hasInput: function() {
797 return (this.input !== false);
799 isDisabled: function() {
800 if (this.hasInput()) {
801 return (this.input.prop('disabled') === true);
805 disable: function() {
806 if (this.hasInput()) {
807 this.input.prop('disabled', true);
813 if (this.hasInput()) {
814 this.input.prop('disabled', false);
824 mousedown: function(e) {
828 var target = $(e.target);
830 //detect the slider and set the limits and callbacks
831 var zone = target.closest('div');
832 var sl = this.options.horizontal ? this.options.slidersHorz : this.options.sliders;
833 if (!zone.is('.colorpicker')) {
834 if (zone.is('.colorpicker-saturation')) {
835 this.currentSlider = $.extend({}, sl.saturation);
836 } else if (zone.is('.colorpicker-hue')) {
837 this.currentSlider = $.extend({}, sl.hue);
838 } else if (zone.is('.colorpicker-alpha')) {
839 this.currentSlider = $.extend({}, sl.alpha);
843 var offset = zone.offset();
844 //reference to guide's style
845 this.currentSlider.guide = zone.find('i')[0].style;
846 this.currentSlider.left = e.pageX - offset.left;
847 this.currentSlider.top = e.pageY - offset.top;
848 this.mousePointer = {
852 //trigger mousemove to move the guide to the current position
854 'mousemove.colorpicker': $.proxy(this.mousemove, this),
855 'mouseup.colorpicker': $.proxy(this.mouseup, this)
856 }).trigger('mousemove');
860 mousemove: function(e) {
866 this.currentSlider.maxLeft,
867 this.currentSlider.left + ((e.pageX || this.mousePointer.left) - this.mousePointer.left)
873 this.currentSlider.maxTop,
874 this.currentSlider.top + ((e.pageY || this.mousePointer.top) - this.mousePointer.top)
877 this.currentSlider.guide.left = left + 'px';
878 this.currentSlider.guide.top = top + 'px';
879 if (this.currentSlider.callLeft) {
880 this.color[this.currentSlider.callLeft].call(this.color, left / 100);
882 if (this.currentSlider.callTop) {
883 this.color[this.currentSlider.callTop].call(this.color, top / 100);
887 this.element.trigger({
893 mouseup: function(e) {
897 'mousemove.colorpicker': this.mousemove,
898 'mouseup.colorpicker': this.mouseup
903 if ((e.keyCode === 38)) {
904 if (this.color.value.a < 1) {
905 this.color.value.a = Math.round((this.color.value.a + 0.01) * 100) / 100;
908 } else if ((e.keyCode === 40)) {
909 if (this.color.value.a > 0) {
910 this.color.value.a = Math.round((this.color.value.a - 0.01) * 100) / 100;
914 var val = this.input.val();
915 this.color = new Color(val);
916 if (this.getValue(false) !== false) {
918 this.updateComponent();
922 this.element.trigger({
930 $.colorpicker = Colorpicker;
932 $.fn.colorpicker = function(option) {
933 return this.each(function() {
935 inst = $this.data('colorpicker'),
936 options = ((typeof option === 'object') ? option : {});
937 if ((!inst) && (typeof option !== 'string')) {
938 $this.data('colorpicker', new Colorpicker(this, options));
940 if (typeof option === 'string') {
941 inst[option].apply(inst, Array.prototype.slice.call(arguments, 1));
947 $.fn.colorpicker.constructor = Colorpicker;