/* ========================================================= * bootstrap-colorpicker.js * http://www.eyecon.ro/bootstrap-colorpicker * ========================================================= * Copyright 2012 Stefan Petre * * 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. * ========================================================= */ !function( $ ) { // Color object var Color = function(val) { this.value = { h: 1, s: 1, b: 1, a: 1 }; this.setColor(val); }; Color.prototype = { constructor: Color, //parse a string to HSB setColor: function(val){ val = val.toLowerCase(); var that = this; $.each( CPGlobal.stringParsers, function( i, parser ) { var match = parser.re.exec( val ), values = match && parser.parse( match ), space = parser.space||'rgba'; if ( values ) { if (space == 'hsla') { that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values)); } else { that.value = CPGlobal.RGBtoHSB.apply(null, values); } return false; } }); }, setHue: function(h) { this.value.h = 1- h; }, setSaturation: function(s) { this.value.s = s; }, setLightness: function(b) { this.value.b = 1- b; }, setAlpha: function(a) { this.value.a = parseInt((1 - a)*100, 10)/100; }, // HSBtoRGB from RaphaelJS // https://github.com/DmitryBaranovskiy/raphael/ toRGB: function(h, s, b, a) { if (!h) { h = this.value.h; s = this.value.s; b = this.value.b; } h *= 360; var R, G, B, X, C; h = (h % 360) / 60; C = b * s; X = C * (1 - Math.abs(h % 2 - 1)); R = G = B = b - C; h = ~~h; R += [C, X, 0, 0, X, C][h]; G += [X, C, C, X, 0, 0][h]; B += [0, 0, X, C, C, X][h]; return { r: Math.round(R*255), g: Math.round(G*255), b: Math.round(B*255), a: a||this.value.a }; }, toHex: function(h, s, b, a){ var rgb = this.toRGB(h, s, b, a); return '#'+((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1); }, toHSL: function(h, s, b, a){ if (!h) { h = this.value.h; s = this.value.s; b = this.value.b; } var H = h, L = (2 - s) * b, S = s * b; if (L > 0 && L <= 1) { S /= L; } else { S /= 2 - L; } L /= 2; if (S > 1) { S = 1; } return { h: H, s: S, l: L, a: a||this.value.a }; } }; // Picker object var Colorpicker = function(element, options){ this.element = $(element); var format = options.format||this.element.data('color-format')||'hex'; this.format = CPGlobal.translateFormats[format]; this.isInput = this.element.is('input'); this.component = this.element.is('.color') ? this.element.find('.add-on') : false; this.picker = $(CPGlobal.template) .appendTo('body') .on('mousedown', $.proxy(this.mousedown, this)); if (this.isInput) { this.element.on({ 'focus': $.proxy(this.show, this), 'keyup': $.proxy(this.update, this) }); } else if (this.component){ this.component.on({ 'click': $.proxy(this.show, this) }); } else { this.element.on({ 'click': $.proxy(this.show, this) }); } if (format == 'rgba' || format == 'hsla') { this.picker.addClass('alpha'); this.alpha = this.picker.find('.colorpicker-alpha')[0].style; } if (this.component){ this.picker.find('.colorpicker-color').hide(); this.preview = this.element.find('i')[0].style; } else { this.preview = this.picker.find('div:last')[0].style; } this.base = this.picker.find('div:first')[0].style; this.update(); }; Colorpicker.prototype = { constructor: Colorpicker, show: function(e) { this.picker.show(); this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); this.place(); $(window).on('resize', $.proxy(this.place, this)); if (!this.isInput) { if (e) { e.stopPropagation(); e.preventDefault(); } } $(document).on({ 'mousedown': $.proxy(this.hide, this) }); this.element.trigger({ type: 'show', color: this.color }); }, update: function(){ this.color = new Color(this.isInput ? this.element.prop('value') : this.element.data('color')); this.picker.find('i') .eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end() .eq(1).css('top', 100 * (1 - this.color.value.h)).end() .eq(2).css('top', 100 * (1 - this.color.value.a)); this.previewColor(); }, hide: function(){ this.picker.hide(); $(window).off('resize', this.place); if (!this.isInput) { $(document).off({ 'mousedown': this.hide }); if (this.component){ this.element.find('input').prop('value', this.format.call(this)); } this.element.data('color', this.format.call(this)); } else { this.element.prop('value', this.format.call(this)); } this.element.trigger({ type: 'hide', color: this.color }); }, place: function(){ var offset = this.component ? this.component.offset() : this.element.offset(); this.picker.css({ top: offset.top + this.height, left: offset.left }); }, //preview color change previewColor: function(){ this.preview.backgroundColor = this.format.call(this); //set the color for brightness/saturation slider this.base.backgroundColor = this.color.toHex(this.color.value.h, 1, 1, 1); //set te color for alpha slider if (this.alpha) { this.alpha.backgroundColor = this.color.toHex(); } }, pointer: null, slider: null, mousedown: function(e){ e.stopPropagation(); e.preventDefault(); var target = $(e.target); //detect the slider and set the limits and callbacks var zone = target.closest('div'); if (!zone.is('.colorpicker')) { if (zone.is('.colorpicker-saturation')) { this.slider = $.extend({}, CPGlobal.sliders['saturation']); } else if (zone.is('.colorpicker-hue')) { this.slider = $.extend({}, CPGlobal.sliders['hue']); } else if (zone.is('.colorpicker-alpha')) { this.slider = $.extend({}, CPGlobal.sliders['alpha']); } var offset = zone.offset(); //reference to knob's style this.slider.knob = zone.find('i')[0].style; this.slider.left = e.pageX - offset.left; this.slider.top = e.pageY - offset.top; this.pointer = { left: e.pageX, top: e.pageY }; //trigger mousemove to move the knob to the current position $(document).on({ mousemove: $.proxy(this.mousemove, this), mouseup: $.proxy(this.mouseup, this) }).trigger('mousemove'); } return false; }, mousemove: function(e){ e.stopPropagation(); e.preventDefault(); var left = Math.max( 0, Math.min( this.slider.maxLeft, this.slider.left + ((e.pageX||this.pointer.left) - this.pointer.left) ) ); var top = Math.max( 0, Math.min( this.slider.maxTop, this.slider.top + ((e.pageY||this.pointer.top) - this.pointer.top) ) ); this.slider.knob.left = left + 'px'; this.slider.knob.top = top + 'px'; if (this.slider.callLeft) { this.color[this.slider.callLeft].call(this.color, left/100); } if (this.slider.callTop) { this.color[this.slider.callTop].call(this.color, top/100); } this.previewColor(); this.element.trigger({ type: 'changeColor', color: this.color }); return false; }, mouseup: function(e){ e.stopPropagation(); e.preventDefault(); $(document).off({ mousemove: this.mousemove, mouseup: this.mouseup }); return false; } } $.fn.colorpicker = function ( option ) { return this.each(function () { var $this = $(this), data = $this.data('colorpicker'), options = typeof option == 'object' && option; if (!data) { $this.data('colorpicker', (data = new Colorpicker(this, $.extend({}, $.fn.colorpicker.defaults,options)))); } if (typeof option == 'string') data[option](); }); }; $.fn.colorpicker.defaults = { }; $.fn.colorpicker.Constructor = Colorpicker; var CPGlobal = { // translate a format from Color object to a string translateFormats: { 'rgb': function(){ var rgb = this.color.toRGB(); return 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'; }, 'rgba': function(){ var rgb = this.color.toRGB(); return 'rgba('+rgb.r+','+rgb.g+','+rgb.b+','+rgb.a+')'; }, 'hsl': function(){ var hsl = this.color.toHSL(); return 'hsl('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%)'; }, 'hsla': function(){ var hsl = this.color.toHSL(); return 'hsla('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%,'+hsl.a+')'; }, 'hex': function(){ return this.color.toHex(); } }, sliders: { saturation: { maxLeft: 100, maxTop: 100, callLeft: 'setSaturation', callTop: 'setLightness' }, hue: { maxLeft: 0, maxTop: 100, callLeft: false, callTop: 'setHue' }, alpha: { maxLeft: 0, maxTop: 100, callLeft: false, callTop: 'setAlpha' } }, // HSBtoRGB from RaphaelJS // https://github.com/DmitryBaranovskiy/raphael/ RGBtoHSB: function (r, g, b, a){ r /= 255; g /= 255; b /= 255; var H, S, V, C; V = Math.max(r, g, b); C = V - Math.min(r, g, b); H = (C == 0 ? null : V == r ? (g - b) / C : V == g ? (b - r) / C + 2 : (r - g) / C + 4 ); H = ((H + 360) % 6) * 60 / 360; S = C == 0 ? 0 : C / V; return {h: H||1, s: S, b: V, a: a||1}; }, HueToRGB: function (p, q, h) { if (h < 0) h += 1; else if (h > 1) h -= 1; if ((h * 6) < 1) return p + (q - p) * h * 6; else if ((h * 2) < 1) return q; else if ((h * 3) < 2) return p + (q - p) * ((2 / 3) - h) * 6; else return p; }, HSLtoRGB: function (h, s, l, a) { if (s < 0) s = 0; if (l <= 0.5) var q = l * (1 + s); else var q = l + s - (l * s); var p = 2 * l - q; var tr = h + (1 / 3); var tg = h; var tb = h - (1 / 3); var r = Math.round(CPGlobal.HueToRGB(p, q, tr) * 255); var g = Math.round(CPGlobal.HueToRGB(p, q, tg) * 255); var b = Math.round(CPGlobal.HueToRGB(p, q, tb) * 255); return [r, g, b, a||1]; }, // a set of RE's that can match strings and generate color tuples. // from John Resig color plugin // https://github.com/jquery/jquery-color/ stringParsers: [ { re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ], execResult[ 3 ], execResult[ 4 ] ]; } }, { re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ 2.55 * execResult[1], 2.55 * execResult[2], 2.55 * execResult[3], execResult[ 4 ] ]; } }, { re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ], 16 ), parseInt( execResult[ 2 ], 16 ), parseInt( execResult[ 3 ], 16 ) ]; } }, { re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) ]; } }, { re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, space: 'hsla', parse: function( execResult ) { return [ execResult[1]/360, execResult[2] / 100, execResult[3] / 100, execResult[4] ]; } } ], template: '