init : function () { if (jsc.jscolor.lookupClass) { jsc.jscolor.installByClassName(jsc.jscolor.lookupClass); } },
tryInstallOnElements : function (elms, className) { var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i');
for (var i = 0; i < elms.length; i += 1) { if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') { if (jsc.isColorAttrSupported) { // skip inputs of type 'color' if supported by the browser continue; } } var m; if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) { var targetElm = elms[i]; var optsStr = null;
var dataOptions = jsc.getDataAttr(targetElm, 'jscolor'); if (dataOptions !== null) { optsStr = dataOptions; } else if (m[4]) { optsStr = m[4]; }
var opts = {}; if (optsStr) { try { opts = (new Function ('return (' + optsStr + ')'))(); } catch(eParseError) { jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr); } } targetElm.jscolor = new jsc.jscolor(targetElm, opts); } } },
isColorAttrSupported : (function () { var elm = document.createElement('input'); if (elm.setAttribute) { elm.setAttribute('type', 'color'); if (elm.type.toLowerCase() == 'color') { return true; } } return false; })(),
isCanvasSupported : (function () { var elm = document.createElement('canvas'); return !!(elm.getContext && elm.getContext('2d')); })(),
getDataAttr : function (el, name) { var attrName = 'data-' + name; var attrValue = el.getAttribute(attrName); if (attrValue !== null) { return attrValue; } return null; },
attachEvent : function (el, evnt, func) { if (el.addEventListener) { el.addEventListener(evnt, func, false); } else if (el.attachEvent) { el.attachEvent('on' + evnt, func); } },
detachEvent : function (el, evnt, func) { if (el.removeEventListener) { el.removeEventListener(evnt, func, false); } else if (el.detachEvent) { el.detachEvent('on' + evnt, func); } },
_attachedGroupEvents : {},
attachGroupEvent : function (groupName, el, evnt, func) { if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { jsc._attachedGroupEvents[groupName] = []; } jsc._attachedGroupEvents[groupName].push([el, evnt, func]); jsc.attachEvent(el, evnt, func); },
detachGroupEvents : function (groupName) { if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { var evt = jsc._attachedGroupEvents[groupName][i]; jsc.detachEvent(evt[0], evt[1], evt[2]); } delete jsc._attachedGroupEvents[groupName]; } },
attachDOMReadyEvent : function (func) { var fired = false; var fireOnce = function () { if (!fired) { fired = true; func(); } };
// IE7/8 if (document.documentElement.doScroll && window == window.top) { var tryScroll = function () { if (!document.body) { return; } try { document.documentElement.doScroll('left'); fireOnce(); } catch (e) { setTimeout(tryScroll, 1); } }; tryScroll(); } } },
warn : function (msg) { if (window.console && window.console.warn) { window.console.warn(msg); } },
preventDefault : function (e) { if (e.preventDefault) { e.preventDefault(); } e.returnValue = false; },
captureTarget : function (target) { // IE if (target.setCapture) { jsc._capturedTarget = target; jsc._capturedTarget.setCapture(); } },
releaseTarget : function () { // IE if (jsc._capturedTarget) { jsc._capturedTarget.releaseCapture(); jsc._capturedTarget = null; } },
fireEvent : function (el, evnt) { if (!el) { return; } if (document.createEvent) { var ev = document.createEvent('HTMLEvents'); ev.initEvent(evnt, true, true); el.dispatchEvent(ev); } else if (document.createEventObject) { var ev = document.createEventObject(); el.fireEvent('on' + evnt, ev); } else if (el['on' + evnt]) { // alternatively use the traditional event model el['on' + evnt](); } },
classNameToList : function (className) { return className.replace(/^\s+|\s+$/g, '').split(/\s+/); },
// The className parameter (str) can only contain a single class name hasClass : function (elm, className) { if (!className) { return false; } return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); },
// The className parameter (str) can contain multiple class names separated by whitespace setClass : function (elm, className) { var classList = jsc.classNameToList(className); for (var i = 0; i < classList.length; i += 1) { if (!jsc.hasClass(elm, classList[i])) { elm.className += (elm.className ? ' ' : '') + classList[i]; } } },
// The className parameter (str) can contain multiple class names separated by whitespace unsetClass : function (elm, className) { var classList = jsc.classNameToList(className); for (var i = 0; i < classList.length; i += 1) { var repl = new RegExp( '^\\s*' + classList[i] + '\\s*|' + '\\s*' + classList[i] + '\\s*$|' + '\\s+' + classList[i] + '(\\s+)', 'g' ); elm.className = elm.className.replace(repl, '$1'); } },
setStyle : (function () { var helper = document.createElement('div'); var getSupportedProp = function (names) { for (var i = 0; i < names.length; i += 1) { if (names[i] in helper.style) { return names[i]; } } }; var props = { borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']), boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow']) }; return function (elm, prop, value) { switch (prop.toLowerCase()) { case 'opacity': var alphaOpacity = Math.round(parseFloat(value) * 100); elm.style.opacity = value; elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')'; break; default: elm.style[props[prop]] = value; break; } }; })(),
setBorderRadius : function (elm, value) { jsc.setStyle(elm, 'borderRadius', value || '0'); },
setBoxShadow : function (elm, value) { jsc.setStyle(elm, 'boxShadow', value || 'none'); },
getElementPos : function (e, relativeToViewport) { var x=0, y=0; var rect = e.getBoundingClientRect(); x = rect.left; y = rect.top; if (!relativeToViewport) { var viewPos = jsc.getViewPos(); x += viewPos[0]; y += viewPos[1]; } return [x, y]; },
getElementSize : function (e) { return [e.offsetWidth, e.offsetHeight]; },
// get pointer's X/Y coordinates relative to viewport getAbsPointerPos : function (e) { if (!e) { e = window.event; } var x = 0, y = 0; if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { // touch devices x = e.changedTouches[0].clientX; y = e.changedTouches[0].clientY; } else if (typeof e.clientX === 'number') { x = e.clientX; y = e.clientY; } return { x: x, y: y }; },
// get pointer's X/Y coordinates relative to target element getRelPointerPos : function (e) { if (!e) { e = window.event; } var target = e.target || e.srcElement; var targetRect = target.getBoundingClientRect();
getViewSize : function () { var doc = document.documentElement; return [ (window.innerWidth || doc.clientWidth), (window.innerHeight || doc.clientHeight), ]; },
redrawPosition : function () {
if (jsc.picker && jsc.picker.owner) { var thisObj = jsc.picker.owner;
var tp, vp;
if (thisObj.fixed) { // Fixed elements are positioned relative to viewport, // therefore we can ignore the scroll offset tp = jsc.getElementPos(thisObj.targetElement, true); // target pos vp = [0, 0]; // view pos } else { tp = jsc.getElementPos(thisObj.targetElement); // target pos vp = jsc.getViewPos(); // view pos }
var ts = jsc.getElementSize(thisObj.targetElement); // target size var vs = jsc.getViewSize(); // view size var ps = jsc.getPickerOuterDims(thisObj); // picker size var a, b, c; switch (thisObj.position.toLowerCase()) { case 'left': a=1; b=0; c=-1; break; case 'right':a=1; b=0; c=1; break; case 'top': a=0; b=1; c=-1; break; default: a=0; b=1; c=1; break; } var l = (ts[b]+ps[b])/2;
getPadYComponent : function (thisObj) { switch (thisObj.mode.charAt(1).toLowerCase()) { case 'v': return 'v'; break; } return 's'; },
getSliderComponent : function (thisObj) { if (thisObj.mode.length > 2) { switch (thisObj.mode.charAt(2).toLowerCase()) { case 's': return 's'; break; case 'v': return 'v'; break; } } return null; },
onDocumentMouseDown : function (e) { if (!e) { e = window.event; } var target = e.target || e.srcElement;
if (target._jscLinkedInstance) { if (target._jscLinkedInstance.showOnClick) { target._jscLinkedInstance.show(); } } else if (target._jscControlName) { jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse'); } else { // Mouse is outside the picker controls -> hide the color picker! if (jsc.picker && jsc.picker.owner) { jsc.picker.owner.hide(); } } },
onDocumentTouchStart : function (e) { if (!e) { e = window.event; } var target = e.target || e.srcElement;
if (target._jscLinkedInstance) { if (target._jscLinkedInstance.showOnClick) { target._jscLinkedInstance.show(); } } else if (target._jscControlName) { jsc.onControlPointerStart(e, target, target._jscControlName, 'touch'); } else { if (jsc.picker && jsc.picker.owner) { jsc.picker.owner.hide(); } } },
onWindowResize : function (e) { jsc.redrawPosition(); },
onParentScroll : function (e) { // hide the picker when one of the parent elements is scrolled if (jsc.picker && jsc.picker.owner) { jsc.picker.owner.hide(); } },
if (window.parent && window.frameElement) { var rect = window.frameElement.getBoundingClientRect(); var ofs = [-rect.left, -rect.top]; registerDragEvents(window.parent.window.document, ofs); }
var abs = jsc.getAbsPointerPos(e); var rel = jsc.getRelPointerPos(e); jsc._pointerOrigin = { x: abs.x - rel.x, y: abs.y - rel.y };
switch (controlName) { case 'pad': // if the slider is at the bottom, move it up switch (jsc.getSliderComponent(thisObj)) { case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break; case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break; } jsc.setPad(thisObj, e, 0, 0); break;
case 'sld': jsc.setSld(thisObj, e, 0); break; }
jsc.dispatchFineChange(thisObj); },
onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { return function (e) { var thisObj = target._jscInstance; switch (controlName) { case 'pad': if (!e) { e = window.event; } jsc.setPad(thisObj, e, offset[0], offset[1]); jsc.dispatchFineChange(thisObj); break;
case 'sld': if (!e) { e = window.event; } jsc.setSld(thisObj, e, offset[1]); jsc.dispatchFineChange(thisObj); break; } } },
onDocumentPointerEnd : function (e, target, controlName, pointerType) { return function (e) { var thisObj = target._jscInstance; jsc.detachGroupEvents('drag'); jsc.releaseTarget(); // Always dispatch changes after detaching outstanding mouse handlers, // in case some user interaction will occur in user's onchange callback // that would intrude with current mouse events jsc.dispatchChange(thisObj); }; },
dispatchChange : function (thisObj) { if (thisObj.valueElement) { if (jsc.isElementType(thisObj.valueElement, 'input')) { jsc.fireEvent(thisObj.valueElement, 'change'); } } },
dispatchFineChange : function (thisObj) { if (thisObj.onFineChange) { var callback; if (typeof thisObj.onFineChange === 'string') { callback = new Function (thisObj.onFineChange); } else { callback = thisObj.onFineChange; } callback.call(thisObj); } },
setPad : function (thisObj, e, ofsX, ofsY) { var pointerAbs = jsc.getAbsPointerPos(e); var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth; var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth;
var xVal = x * (360 / (thisObj.width - 1)); var yVal = 100 - (y * (100 / (thisObj.height - 1)));
switch (jsc.getPadYComponent(thisObj)) { case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break; case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break; } },
setSld : function (thisObj, e, ofsY) { var pointerAbs = jsc.getAbsPointerPos(e); var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth;
// Colors must be specified during every redraw, otherwise IE won't display // a full gradient during a subsequential redraw hGrad.color = '#F00'; hGrad.color2 = '#F00';
// // Usage: // var myColor = new jscolor(<targetElement> [, <options>]) //
jscolor : function (targetElement, options) {
// General options // this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB() this.valueElement = targetElement; // element that will be used to display and input the color code this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor this.required = true; // whether the associated text <input> can be left empty this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace) this.hash = false; // whether to prefix the HEX color code with # symbol this.uppercase = true; // whether to uppercase the color code this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code) this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it this.minS = 0; // min allowed saturation (0 - 100) this.maxS = 100; // max allowed saturation (0 - 100) this.minV = 0; // min allowed value (brightness) (0 - 100) this.maxV = 100; // max allowed value (brightness) (0 - 100)
// Color Picker options // this.width = 181; // width of color palette (in px) this.height = 101; // height of color palette (in px) this.showOnClick = true; // whether to display the color picker when user clicks on its target element this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls this.position = 'bottom'; // left | right | top | bottom - position relative to the target element this.smartPosition = true; // automatically change picker position when there is not enough space for it this.sliderSize = 16; // px this.crossSize = 8; // px this.closable = false; // whether to display the Close button this.closeText = 'Close'; this.buttonColor = '#000000'; // CSS color this.buttonHeight = 18; // px this.padding = 12; // px this.backgroundColor = '#FFFFFF'; // CSS color this.borderWidth = 1; // px this.borderColor = '#BBBBBB'; // CSS color this.borderRadius = 8; // px this.insetWidth = 1; // px this.insetColor = '#BBBBBB'; // CSS color this.shadow = true; // whether to display shadow this.shadowBlur = 15; // px this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color this.pointerColor = '#4C4C4C'; // px this.pointerBorderColor = '#FFFFFF'; // px this.pointerBorderWidth = 1; // px this.pointerThickness = 2; // px this.zIndex = 1000; this.container = null; // where to append the color picker (BODY element by default)
for (var opt in options) { if (options.hasOwnProperty(opt)) { this[opt] = options[opt]; } }
this.hide = function () { if (isPickerOwner()) { detachPicker(); } };
this.show = function () { drawPicker(); };
this.redraw = function () { if (isPickerOwner()) { drawPicker(); } };
this.importColor = function () { if (!this.valueElement) { this.exportColor(); } else { if (jsc.isElementType(this.valueElement, 'input')) { if (!this.refine) { if (!this.fromString(this.valueElement.value, jsc.leaveValue)) { if (this.styleElement) { this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; this.styleElement.style.color = this.styleElement._jscOrigStyle.color; } this.exportColor(jsc.leaveValue | jsc.leaveStyle); } } else if (!this.required && /^\s*$/.test(this.valueElement.value)) { this.valueElement.value = ''; if (this.styleElement) { this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; this.styleElement.style.color = this.styleElement._jscOrigStyle.color; } this.exportColor(jsc.leaveValue | jsc.leaveStyle);
} else if (this.fromString(this.valueElement.value)) { // managed to import color successfully from the value -> OK, don't do anything } else { this.exportColor(); } } else { // not an input element -> doesn't have any value this.exportColor(); } } };
this.exportColor = function (flags) { if (!(flags & jsc.leaveValue) && this.valueElement) { var value = this.toString(); if (this.uppercase) { value = value.toUpperCase(); } if (this.hash) { value = '#' + value; }
// update RGB according to final HSV, as some values might be trimmed var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); this.rgb[0] = rgb[0]; this.rgb[1] = rgb[1]; this.rgb[2] = rgb[2];
this.exportColor(flags); };
this.fromString = function (str, flags) { var m; if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) { // HEX notation //
this._processParentElementsInDOM = function () { if (this._linkedElementsProcessed) { return; } this._linkedElementsProcessed = true;
var elm = this.targetElement; do { // If the target element or one of its parent nodes has fixed position, // then use fixed positioning instead // // Note: In Firefox, getComputedStyle returns null in a hidden iframe, // that's why we need to check if the returned style object is non-empty var currStyle = jsc.getStyle(elm); if (currStyle && currStyle.position.toLowerCase() === 'fixed') { this.fixed = true; }
if (elm !== this.targetElement) { // Ensure to attach onParentScroll only once to each parent element // (multiple targetElements can share the same parent nodes) // // Note: It's not just offsetParents that can be scrollable, // that's why we loop through all parent nodes if (!elm._jscEventsAttached) { jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); elm._jscEventsAttached = true; } } } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body')); };
// r: 0-255 // g: 0-255 // b: 0-255 // // returns: [ 0-360, 0-100, 0-100 ] // function RGB_HSV (r, g, b) { r /= 255; g /= 255; b /= 255; var n = Math.min(Math.min(r,g),b); var v = Math.max(Math.max(r,g),b); var m = v - n; if (m === 0) { return [ null, 0, 100 * v ]; } var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); return [ 60 * (h===6?0:h), 100 * (m/v), 100 * v ]; }
// h: 0-360 // s: 0-100 // v: 0-100 // // returns: [ 0-255, 0-255, 0-255 ] // function HSV_RGB (h, s, v) { var u = 255 * (v / 100);
if (h === null) { return [ u, u, u ]; }
h /= 60; s /= 100;
var i = Math.floor(h); var f = i%2 ? h-i : 1-(h-i); var m = u * (1 - s); var n = u * (1 - s * f); switch (i) { case 6: case 0: return [u,n,m]; case 1: return [n,u,m]; case 2: return [m,u,n]; case 3: return [m,n,u]; case 4: return [n,m,u]; case 5: return [u,m,n]; } }
function detachPicker () { jsc.unsetClass(THIS.targetElement, THIS.activeClass); jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); delete jsc.picker.owner; }
function drawPicker () {
// At this point, when drawing the picker, we know what the parent elements are // and we can do all related DOM operations, such as registering events on them // or checking their positioning THIS._processParentElementsInDOM();
// IE hack: // If the element is transparent, IE will trigger the event on the elements under it, // e.g. on Canvas or on elements with border p.padM.style.background = p.sldM.style.background = '#FFF'; jsc.setStyle(p.padM, 'opacity', '0'); jsc.setStyle(p.sldM, 'opacity', '0');
// If we are changing the owner without first closing the picker, // make sure to first deal with the old owner if (jsc.picker.owner && jsc.picker.owner !== THIS) { jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass); }
// Set the new picker owner jsc.picker.owner = THIS;
// The redrawPosition() method needs picker.owner to be set, that's why we call it here, // after setting the owner if (jsc.isElementType(container, 'body')) { jsc.redrawPosition(); } else { jsc._drawPosition(THIS, 0, 0, 'relative', false); }
if (p.wrap.parentNode != container) { container.appendChild(p.wrap); }
// Find the target element if (typeof targetElement === 'string') { var id = targetElement; var elm = document.getElementById(id); if (elm) { this.targetElement = elm; } else { jsc.warn('Could not find target element with ID \'' + id + '\''); } } else if (targetElement) { this.targetElement = targetElement; } else { jsc.warn('Invalid target element: \'' + targetElement + '\''); }
if (this.targetElement._jscLinkedInstance) { jsc.warn('Cannot link jscolor twice to the same element. Skipping.'); return; } this.targetElement._jscLinkedInstance = this;
// Find the value element this.valueElement = jsc.fetchElement(this.valueElement); // Find the style element this.styleElement = jsc.fetchElement(this.styleElement);
var THIS = this; var container = this.container ? jsc.fetchElement(this.container) : document.getElementsByTagName('body')[0]; var sliderPtrSpace = 3; // px
// For BUTTON elements it's important to stop them from sending the form when clicked // (e.g. in Safari) if (jsc.isElementType(this.targetElement, 'button')) { if (this.targetElement.onclick) { var origCallback = this.targetElement.onclick; this.targetElement.onclick = function (evt) { origCallback.call(this, evt); return false; }; } else { this.targetElement.onclick = function () { return false; }; } }
/* var elm = this.targetElement; do { // If the target element or one of its offsetParents has fixed position, // then use fixed positioning instead // // Note: In Firefox, getComputedStyle returns null in a hidden iframe, // that's why we need to check if the returned style object is non-empty var currStyle = jsc.getStyle(elm); if (currStyle && currStyle.position.toLowerCase() === 'fixed') { this.fixed = true; }
if (elm !== this.targetElement) { // attach onParentScroll so that we can recompute the picker position // when one of the offsetParents is scrolled if (!elm._jscEventsAttached) { jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); elm._jscEventsAttached = true; } } } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body')); */
// valueElement if (this.valueElement) { if (jsc.isElementType(this.valueElement, 'input')) { var updateField = function () { THIS.fromString(THIS.valueElement.value, jsc.leaveValue); jsc.dispatchFineChange(THIS); }; jsc.attachEvent(this.valueElement, 'keyup', updateField); jsc.attachEvent(this.valueElement, 'input', updateField); jsc.attachEvent(this.valueElement, 'blur', blurValue); this.valueElement.setAttribute('autocomplete', 'off'); } }
// styleElement if (this.styleElement) { this.styleElement._jscOrigStyle = { backgroundImage : this.styleElement.style.backgroundImage, backgroundColor : this.styleElement.style.backgroundColor, color : this.styleElement.style.color }; }
if (this.value) { // Try to set the color from the .value option and if unsuccessful, // export the current color this.fromString(this.value) || this.exportColor(); } else { this.importColor(); } }
};
//================================ // Public properties and methods //================================
// By default, search for all elements with class="jscolor" and install a color picker on them. // // You can change what class name will be looked for by setting the property jscolor.lookupClass // anywhere in your HTML document. To completely disable the automatic lookup, set it to null. // jsc.jscolor.lookupClass = 'jscolor';
jsc.jscolor.installByClassName = function (className) { var inputElms = document.getElementsByTagName('input'); var buttonElms = document.getElementsByTagName('button');
EmoticonEmoticon