1 | /* ========================================================= |
---|
2 | * bootstrap-colorpicker.js |
---|
3 | * http://www.eyecon.ro/bootstrap-colorpicker |
---|
4 | * ========================================================= |
---|
5 | * Copyright 2012 Stefan Petre |
---|
6 | * |
---|
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
---|
8 | * you may not use this file except in compliance with the License. |
---|
9 | * You may obtain a copy of the License at |
---|
10 | * |
---|
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
---|
12 | * |
---|
13 | * Unless required by applicable law or agreed to in writing, software |
---|
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
---|
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
---|
16 | * See the License for the specific language governing permissions and |
---|
17 | * limitations under the License. |
---|
18 | * ========================================================= */ |
---|
19 | |
---|
20 | !function( $ ) { |
---|
21 | |
---|
22 | // Color object |
---|
23 | |
---|
24 | var Color = function(val) { |
---|
25 | this.value = { |
---|
26 | h: 1, |
---|
27 | s: 1, |
---|
28 | b: 1, |
---|
29 | a: 1 |
---|
30 | }; |
---|
31 | this.setColor(val); |
---|
32 | }; |
---|
33 | |
---|
34 | Color.prototype = { |
---|
35 | constructor: Color, |
---|
36 | |
---|
37 | //parse a string to HSB |
---|
38 | setColor: function(val){ |
---|
39 | val = val.toLowerCase(); |
---|
40 | var that = this; |
---|
41 | $.each( CPGlobal.stringParsers, function( i, parser ) { |
---|
42 | var match = parser.re.exec( val ), |
---|
43 | values = match && parser.parse( match ), |
---|
44 | space = parser.space||'rgba'; |
---|
45 | if ( values ) { |
---|
46 | if (space === 'hsla') { |
---|
47 | that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values)); |
---|
48 | } else { |
---|
49 | that.value = CPGlobal.RGBtoHSB.apply(null, values); |
---|
50 | } |
---|
51 | return false; |
---|
52 | } |
---|
53 | }); |
---|
54 | }, |
---|
55 | |
---|
56 | setHue: function(h) { |
---|
57 | this.value.h = 1- h; |
---|
58 | }, |
---|
59 | |
---|
60 | setSaturation: function(s) { |
---|
61 | this.value.s = s; |
---|
62 | }, |
---|
63 | |
---|
64 | setLightness: function(b) { |
---|
65 | this.value.b = 1- b; |
---|
66 | }, |
---|
67 | |
---|
68 | setAlpha: function(a) { |
---|
69 | this.value.a = parseInt((1 - a)*100, 10)/100; |
---|
70 | }, |
---|
71 | |
---|
72 | // HSBtoRGB from RaphaelJS |
---|
73 | // https://github.com/DmitryBaranovskiy/raphael/ |
---|
74 | toRGB: function(h, s, b, a) { |
---|
75 | if (!h) { |
---|
76 | h = this.value.h; |
---|
77 | s = this.value.s; |
---|
78 | b = this.value.b; |
---|
79 | } |
---|
80 | h *= 360; |
---|
81 | var R, G, B, X, C; |
---|
82 | h = (h % 360) / 60; |
---|
83 | C = b * s; |
---|
84 | X = C * (1 - Math.abs(h % 2 - 1)); |
---|
85 | R = G = B = b - C; |
---|
86 | |
---|
87 | h = ~~h; |
---|
88 | R += [C, X, 0, 0, X, C][h]; |
---|
89 | G += [X, C, C, X, 0, 0][h]; |
---|
90 | B += [0, 0, X, C, C, X][h]; |
---|
91 | return { |
---|
92 | r: Math.round(R*255), |
---|
93 | g: Math.round(G*255), |
---|
94 | b: Math.round(B*255), |
---|
95 | a: a||this.value.a |
---|
96 | }; |
---|
97 | }, |
---|
98 | |
---|
99 | toHex: function(h, s, b, a){ |
---|
100 | var rgb = this.toRGB(h, s, b, a); |
---|
101 | return '#'+((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1); |
---|
102 | }, |
---|
103 | |
---|
104 | toHSL: function(h, s, b, a){ |
---|
105 | if (!h) { |
---|
106 | h = this.value.h; |
---|
107 | s = this.value.s; |
---|
108 | b = this.value.b; |
---|
109 | } |
---|
110 | var H = h, |
---|
111 | L = (2 - s) * b, |
---|
112 | S = s * b; |
---|
113 | if (L > 0 && L <= 1) { |
---|
114 | S /= L; |
---|
115 | } else { |
---|
116 | S /= 2 - L; |
---|
117 | } |
---|
118 | L /= 2; |
---|
119 | if (S > 1) { |
---|
120 | S = 1; |
---|
121 | } |
---|
122 | return { |
---|
123 | h: H, |
---|
124 | s: S, |
---|
125 | l: L, |
---|
126 | a: a||this.value.a |
---|
127 | }; |
---|
128 | } |
---|
129 | }; |
---|
130 | |
---|
131 | // Picker object |
---|
132 | |
---|
133 | var Colorpicker = function(element, options){ |
---|
134 | this.element = $(element); |
---|
135 | var format = options.format||this.element.data('color-format')||'hex'; |
---|
136 | this.format = CPGlobal.translateFormats[format]; |
---|
137 | this.isInput = this.element.is('input'); |
---|
138 | this.component = this.element.is('.color') ? this.element.find('.add-on') : false; |
---|
139 | |
---|
140 | this.picker = $(CPGlobal.template) |
---|
141 | .appendTo('body') |
---|
142 | .on('mousedown', $.proxy(this.mousedown, this)); |
---|
143 | |
---|
144 | if (this.isInput) { |
---|
145 | this.element.on({ |
---|
146 | 'focus': $.proxy(this.show, this), |
---|
147 | 'keyup': $.proxy(this.update, this) |
---|
148 | }); |
---|
149 | } else if (this.component){ |
---|
150 | this.component.on({ |
---|
151 | 'click': $.proxy(this.show, this) |
---|
152 | }); |
---|
153 | } else { |
---|
154 | this.element.on({ |
---|
155 | 'click': $.proxy(this.show, this) |
---|
156 | }); |
---|
157 | } |
---|
158 | if (format === 'rgba' || format === 'hsla') { |
---|
159 | this.picker.addClass('alpha'); |
---|
160 | this.alpha = this.picker.find('.colorpicker-alpha')[0].style; |
---|
161 | } |
---|
162 | |
---|
163 | if (this.component){ |
---|
164 | this.picker.find('.colorpicker-color').hide(); |
---|
165 | this.preview = this.element.find('i')[0].style; |
---|
166 | } else { |
---|
167 | this.preview = this.picker.find('div:last')[0].style; |
---|
168 | } |
---|
169 | |
---|
170 | this.base = this.picker.find('div:first')[0].style; |
---|
171 | this.update(); |
---|
172 | }; |
---|
173 | |
---|
174 | Colorpicker.prototype = { |
---|
175 | constructor: Colorpicker, |
---|
176 | |
---|
177 | show: function(e) { |
---|
178 | this.picker.show(); |
---|
179 | this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); |
---|
180 | this.place(); |
---|
181 | $(window).on('resize', $.proxy(this.place, this)); |
---|
182 | if (!this.isInput) { |
---|
183 | if (e) { |
---|
184 | e.stopPropagation(); |
---|
185 | e.preventDefault(); |
---|
186 | } |
---|
187 | } |
---|
188 | $(document).on({ |
---|
189 | 'mousedown': $.proxy(this.hide, this) |
---|
190 | }); |
---|
191 | this.element.trigger({ |
---|
192 | type: 'show', |
---|
193 | color: this.color |
---|
194 | }); |
---|
195 | }, |
---|
196 | |
---|
197 | update: function(){ |
---|
198 | this.color = new Color(this.isInput ? this.element.prop('value') : this.element.data('color')); |
---|
199 | this.picker.find('i') |
---|
200 | .eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end() |
---|
201 | .eq(1).css('top', 100 * (1 - this.color.value.h)).end() |
---|
202 | .eq(2).css('top', 100 * (1 - this.color.value.a)); |
---|
203 | this.previewColor(); |
---|
204 | }, |
---|
205 | |
---|
206 | setValue: function(newColor) { |
---|
207 | this.color = new Color(newColor); |
---|
208 | this.picker.find('i') |
---|
209 | .eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end() |
---|
210 | .eq(1).css('top', 100 * (1 - this.color.value.h)).end() |
---|
211 | .eq(2).css('top', 100 * (1 - this.color.value.a)); |
---|
212 | this.previewColor(); |
---|
213 | this.element.trigger({ |
---|
214 | type: 'changeColor', |
---|
215 | color: this.color |
---|
216 | }); |
---|
217 | }, |
---|
218 | |
---|
219 | hide: function(){ |
---|
220 | this.picker.hide(); |
---|
221 | $(window).off('resize', this.place); |
---|
222 | if (!this.isInput) { |
---|
223 | $(document).off({ |
---|
224 | 'mousedown': this.hide |
---|
225 | }); |
---|
226 | if (this.component){ |
---|
227 | this.element.find('input').prop('value', this.format.call(this)); |
---|
228 | } |
---|
229 | this.element.data('color', this.format.call(this)); |
---|
230 | } else { |
---|
231 | this.element.prop('value', this.format.call(this)); |
---|
232 | } |
---|
233 | this.element.trigger({ |
---|
234 | type: 'hide', |
---|
235 | color: this.color |
---|
236 | }); |
---|
237 | }, |
---|
238 | |
---|
239 | place: function(){ |
---|
240 | var offset = this.component ? this.component.offset() : this.element.offset(); |
---|
241 | this.picker.css({ |
---|
242 | top: offset.top + this.height, |
---|
243 | left: offset.left |
---|
244 | }); |
---|
245 | }, |
---|
246 | |
---|
247 | //preview color change |
---|
248 | previewColor: function(){ |
---|
249 | try { |
---|
250 | this.preview.backgroundColor = this.format.call(this); |
---|
251 | } catch(e) { |
---|
252 | this.preview.backgroundColor = this.color.toHex(); |
---|
253 | } |
---|
254 | //set the color for brightness/saturation slider |
---|
255 | this.base.backgroundColor = this.color.toHex(this.color.value.h, 1, 1, 1); |
---|
256 | //set te color for alpha slider |
---|
257 | if (this.alpha) { |
---|
258 | this.alpha.backgroundColor = this.color.toHex(); |
---|
259 | } |
---|
260 | }, |
---|
261 | |
---|
262 | pointer: null, |
---|
263 | |
---|
264 | slider: null, |
---|
265 | |
---|
266 | mousedown: function(e){ |
---|
267 | e.stopPropagation(); |
---|
268 | e.preventDefault(); |
---|
269 | |
---|
270 | var target = $(e.target); |
---|
271 | |
---|
272 | //detect the slider and set the limits and callbacks |
---|
273 | var zone = target.closest('div'); |
---|
274 | if (!zone.is('.colorpicker')) { |
---|
275 | if (zone.is('.colorpicker-saturation')) { |
---|
276 | this.slider = $.extend({}, CPGlobal.sliders.saturation); |
---|
277 | } |
---|
278 | else if (zone.is('.colorpicker-hue')) { |
---|
279 | this.slider = $.extend({}, CPGlobal.sliders.hue); |
---|
280 | } |
---|
281 | else if (zone.is('.colorpicker-alpha')) { |
---|
282 | this.slider = $.extend({}, CPGlobal.sliders.alpha); |
---|
283 | } else { |
---|
284 | return false; |
---|
285 | } |
---|
286 | var offset = zone.offset(); |
---|
287 | //reference to knob's style |
---|
288 | this.slider.knob = zone.find('i')[0].style; |
---|
289 | this.slider.left = e.pageX - offset.left; |
---|
290 | this.slider.top = e.pageY - offset.top; |
---|
291 | this.pointer = { |
---|
292 | left: e.pageX, |
---|
293 | top: e.pageY |
---|
294 | }; |
---|
295 | //trigger mousemove to move the knob to the current position |
---|
296 | $(document).on({ |
---|
297 | mousemove: $.proxy(this.mousemove, this), |
---|
298 | mouseup: $.proxy(this.mouseup, this) |
---|
299 | }).trigger('mousemove'); |
---|
300 | } |
---|
301 | return false; |
---|
302 | }, |
---|
303 | |
---|
304 | mousemove: function(e){ |
---|
305 | e.stopPropagation(); |
---|
306 | e.preventDefault(); |
---|
307 | var left = Math.max( |
---|
308 | 0, |
---|
309 | Math.min( |
---|
310 | this.slider.maxLeft, |
---|
311 | this.slider.left + ((e.pageX||this.pointer.left) - this.pointer.left) |
---|
312 | ) |
---|
313 | ); |
---|
314 | var top = Math.max( |
---|
315 | 0, |
---|
316 | Math.min( |
---|
317 | this.slider.maxTop, |
---|
318 | this.slider.top + ((e.pageY||this.pointer.top) - this.pointer.top) |
---|
319 | ) |
---|
320 | ); |
---|
321 | this.slider.knob.left = left + 'px'; |
---|
322 | this.slider.knob.top = top + 'px'; |
---|
323 | if (this.slider.callLeft) { |
---|
324 | this.color[this.slider.callLeft].call(this.color, left/100); |
---|
325 | } |
---|
326 | if (this.slider.callTop) { |
---|
327 | this.color[this.slider.callTop].call(this.color, top/100); |
---|
328 | } |
---|
329 | this.previewColor(); |
---|
330 | this.element.trigger({ |
---|
331 | type: 'changeColor', |
---|
332 | color: this.color |
---|
333 | }); |
---|
334 | return false; |
---|
335 | }, |
---|
336 | |
---|
337 | mouseup: function(e){ |
---|
338 | e.stopPropagation(); |
---|
339 | e.preventDefault(); |
---|
340 | $(document).off({ |
---|
341 | mousemove: this.mousemove, |
---|
342 | mouseup: this.mouseup |
---|
343 | }); |
---|
344 | return false; |
---|
345 | } |
---|
346 | } |
---|
347 | |
---|
348 | $.fn.colorpicker = function ( option, val ) { |
---|
349 | return this.each(function () { |
---|
350 | var $this = $(this), |
---|
351 | data = $this.data('colorpicker'), |
---|
352 | options = typeof option === 'object' && option; |
---|
353 | if (!data) { |
---|
354 | $this.data('colorpicker', (data = new Colorpicker(this, $.extend({}, $.fn.colorpicker.defaults,options)))); |
---|
355 | } |
---|
356 | if (typeof option === 'string') data[option](val); |
---|
357 | }); |
---|
358 | }; |
---|
359 | |
---|
360 | $.fn.colorpicker.defaults = { |
---|
361 | }; |
---|
362 | |
---|
363 | $.fn.colorpicker.Constructor = Colorpicker; |
---|
364 | |
---|
365 | var CPGlobal = { |
---|
366 | |
---|
367 | // translate a format from Color object to a string |
---|
368 | translateFormats: { |
---|
369 | 'rgb': function(){ |
---|
370 | var rgb = this.color.toRGB(); |
---|
371 | return 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'; |
---|
372 | }, |
---|
373 | |
---|
374 | 'rgba': function(){ |
---|
375 | var rgb = this.color.toRGB(); |
---|
376 | return 'rgba('+rgb.r+','+rgb.g+','+rgb.b+','+rgb.a+')'; |
---|
377 | }, |
---|
378 | |
---|
379 | 'hsl': function(){ |
---|
380 | var hsl = this.color.toHSL(); |
---|
381 | return 'hsl('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%)'; |
---|
382 | }, |
---|
383 | |
---|
384 | 'hsla': function(){ |
---|
385 | var hsl = this.color.toHSL(); |
---|
386 | return 'hsla('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%,'+hsl.a+')'; |
---|
387 | }, |
---|
388 | |
---|
389 | 'hex': function(){ |
---|
390 | return this.color.toHex(); |
---|
391 | } |
---|
392 | }, |
---|
393 | |
---|
394 | sliders: { |
---|
395 | saturation: { |
---|
396 | maxLeft: 100, |
---|
397 | maxTop: 100, |
---|
398 | callLeft: 'setSaturation', |
---|
399 | callTop: 'setLightness' |
---|
400 | }, |
---|
401 | |
---|
402 | hue: { |
---|
403 | maxLeft: 0, |
---|
404 | maxTop: 100, |
---|
405 | callLeft: false, |
---|
406 | callTop: 'setHue' |
---|
407 | }, |
---|
408 | |
---|
409 | alpha: { |
---|
410 | maxLeft: 0, |
---|
411 | maxTop: 100, |
---|
412 | callLeft: false, |
---|
413 | callTop: 'setAlpha' |
---|
414 | } |
---|
415 | }, |
---|
416 | |
---|
417 | // HSBtoRGB from RaphaelJS |
---|
418 | // https://github.com/DmitryBaranovskiy/raphael/ |
---|
419 | RGBtoHSB: function (r, g, b, a){ |
---|
420 | r /= 255; |
---|
421 | g /= 255; |
---|
422 | b /= 255; |
---|
423 | |
---|
424 | var H, S, V, C; |
---|
425 | V = Math.max(r, g, b); |
---|
426 | C = V - Math.min(r, g, b); |
---|
427 | H = (C === 0 ? null : |
---|
428 | V == r ? (g - b) / C : |
---|
429 | V == g ? (b - r) / C + 2 : |
---|
430 | (r - g) / C + 4 |
---|
431 | ); |
---|
432 | H = ((H + 360) % 6) * 60 / 360; |
---|
433 | S = C === 0 ? 0 : C / V; |
---|
434 | return {h: H||1, s: S, b: V, a: a||1}; |
---|
435 | }, |
---|
436 | |
---|
437 | HueToRGB: function (p, q, h) { |
---|
438 | if (h < 0) |
---|
439 | h += 1; |
---|
440 | else if (h > 1) |
---|
441 | h -= 1; |
---|
442 | |
---|
443 | if ((h * 6) < 1) |
---|
444 | return p + (q - p) * h * 6; |
---|
445 | else if ((h * 2) < 1) |
---|
446 | return q; |
---|
447 | else if ((h * 3) < 2) |
---|
448 | return p + (q - p) * ((2 / 3) - h) * 6; |
---|
449 | else |
---|
450 | return p; |
---|
451 | }, |
---|
452 | |
---|
453 | HSLtoRGB: function (h, s, l, a) |
---|
454 | { |
---|
455 | if (s < 0) { |
---|
456 | s = 0; |
---|
457 | } |
---|
458 | var q; |
---|
459 | if (l <= 0.5) { |
---|
460 | q = l * (1 + s); |
---|
461 | } else { |
---|
462 | q = l + s - (l * s); |
---|
463 | } |
---|
464 | |
---|
465 | var p = 2 * l - q; |
---|
466 | |
---|
467 | var tr = h + (1 / 3); |
---|
468 | var tg = h; |
---|
469 | var tb = h - (1 / 3); |
---|
470 | |
---|
471 | var r = Math.round(CPGlobal.HueToRGB(p, q, tr) * 255); |
---|
472 | var g = Math.round(CPGlobal.HueToRGB(p, q, tg) * 255); |
---|
473 | var b = Math.round(CPGlobal.HueToRGB(p, q, tb) * 255); |
---|
474 | return [r, g, b, a||1]; |
---|
475 | }, |
---|
476 | |
---|
477 | // a set of RE's that can match strings and generate color tuples. |
---|
478 | // from John Resig color plugin |
---|
479 | // https://github.com/jquery/jquery-color/ |
---|
480 | stringParsers: [ |
---|
481 | { |
---|
482 | re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, |
---|
483 | parse: function( execResult ) { |
---|
484 | return [ |
---|
485 | execResult[ 1 ], |
---|
486 | execResult[ 2 ], |
---|
487 | execResult[ 3 ], |
---|
488 | execResult[ 4 ] |
---|
489 | ]; |
---|
490 | } |
---|
491 | }, { |
---|
492 | re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, |
---|
493 | parse: function( execResult ) { |
---|
494 | return [ |
---|
495 | 2.55 * execResult[1], |
---|
496 | 2.55 * execResult[2], |
---|
497 | 2.55 * execResult[3], |
---|
498 | execResult[ 4 ] |
---|
499 | ]; |
---|
500 | } |
---|
501 | }, { |
---|
502 | re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, |
---|
503 | parse: function( execResult ) { |
---|
504 | return [ |
---|
505 | parseInt( execResult[ 1 ], 16 ), |
---|
506 | parseInt( execResult[ 2 ], 16 ), |
---|
507 | parseInt( execResult[ 3 ], 16 ) |
---|
508 | ]; |
---|
509 | } |
---|
510 | }, { |
---|
511 | re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, |
---|
512 | parse: function( execResult ) { |
---|
513 | return [ |
---|
514 | parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), |
---|
515 | parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), |
---|
516 | parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) |
---|
517 | ]; |
---|
518 | } |
---|
519 | }, { |
---|
520 | re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, |
---|
521 | space: 'hsla', |
---|
522 | parse: function( execResult ) { |
---|
523 | return [ |
---|
524 | execResult[1]/360, |
---|
525 | execResult[2] / 100, |
---|
526 | execResult[3] / 100, |
---|
527 | execResult[4] |
---|
528 | ]; |
---|
529 | } |
---|
530 | } |
---|
531 | ], |
---|
532 | template: '<div class="colorpicker dropdown-menu">'+ |
---|
533 | '<div class="colorpicker-saturation"><i><b></b></i></div>'+ |
---|
534 | '<div class="colorpicker-hue"><i></i></div>'+ |
---|
535 | '<div class="colorpicker-alpha"><i></i></div>'+ |
---|
536 | '<div class="colorpicker-color"><div /></div>'+ |
---|
537 | '</div>' |
---|
538 | }; |
---|
539 | |
---|
540 | }( window.jQuery ) |
---|