var FwColourPicker = function(callback, options, colour, onClose) { if(typeof callback !== 'function') return; if(typeof colour !== 'number') colour = parseInt(colour || 0); if(typeof options !== 'object') options = {}; var readThres = 168, lumiRed = .299, lumiGreen = .587, lumiBlue = .114; var extractRGB = function(raw) { return [ (raw >> 16) & 0xFF, (raw >> 8) & 0xFF, raw & 0xFF, ]; }; var calcLumi = function(raw) { var rgb = extractRGB(raw); return rgb[0] * lumiRed + rgb[1] * lumiGreen + rgb[2] * lumiBlue; }; var textColour = function(raw) { return calcLumi(raw) > readThres ? 0 : 0xFFFFFF; }; var weightNum = function(n1, n2, w) { w = Math.min(1, Math.max(0, w)); return Math.round((n1 * w) + (n2 * (1 - w))); }; var weightColour = function(c1, c2, w) { c1 = extractRGB(c1); c2 = extractRGB(c2); return (weightNum(c1[0], c2[0], w) << 16) | (weightNum(c1[1], c2[1], w) << 8) | weightNum(c1[2], c2[2], w); }; var shadeColour = function(raw, offset) { if(offset == 0) return raw; var dir = 0xFFFFFF; if(offset < 0) { dir = 0; offset *= -1; } return weightColour(dir, raw, offset); }; var hexFormat = FwColourPicker.hexFormat; var verifyOption = function(name, type, def) { if(typeof options[name] !== type) options[name] = def; }; verifyOption('posX', 'number', -1); verifyOption('posY', 'number', -1); verifyOption('presets', 'object', []); verifyOption('showPresetsTab', 'boolean', options.presets.length > 0); verifyOption('showGridTab', 'boolean', true); verifyOption('showSlidersTab', 'boolean', true); verifyOption('showHexValue', 'boolean', true); verifyOption('showRawValue', 'boolean', true); verifyOption('autoClose', 'boolean', true); var onColourChange = []; var runOnColourChange = function() { var text = textColour(colour); for(var i = 0; i < onColourChange.length; ++i) onColourChange[i](colour, text); }; var setColour = function(raw) { colour = parseInt(raw || 0) & 0xFFFFFF; runOnColourChange(); }; var apply = function() { callback(pub, colour); if(options.autoClose) close(); }; var cancel = function() { callback(pub, null); if(options.autoClose) close(); }; var close = function() { if(onClose && onClose()) return; container.parentNode.removeChild(container); }; var height = 96; var container = document.createElement('div'); container.className = 'fw-colour-picker'; onColourChange.push(function(colour) { container.style.borderColor = hexFormat(colour); }); var form = document.createElement('form'); form.className = 'fw-colour-picker-form'; container.appendChild(form); form.onsubmit = function(ev) { ev.preventDefault(); apply(); return false; }; var tabs = {}; var activeTab = undefined; onColourChange.push(function(colour, text) { if(activeTab) { activeTab.b.style.background = hexFormat(colour); activeTab.b.style.borderColor = hexFormat(colour); activeTab.b.style.color = hexFormat(text); } }); var tabbed = document.createElement('div'); tabbed.className = 'fw-colour-picker-tabbed'; var tabbedContainer = document.createElement('div'); tabbedContainer.className = 'fw-colour-picker-tabbed-container'; tabbed.appendChild(tabbedContainer); onColourChange.push(function(colour) { tabbedContainer.style.borderColor = hexFormat(colour); }); var tabbedList = document.createElement('div'); tabbedList.className = 'fw-colour-picker-tabbed-list'; tabbed.appendChild(tabbedList); var switchTab = function(id) { if(activeTab == tabs[id]) return; if(activeTab) { activeTab.c.classList.remove('fw-colour-picker-tab-container-active'); activeTab.b.classList.remove('fw-colour-picker-tab-button-active'); activeTab.b.style.background = ''; activeTab.b.style.borderColor = ''; activeTab.b.style.color = ''; } activeTab = tabs[id] || undefined; if(activeTab) { activeTab.c.classList.add('fw-colour-picker-tab-container-active'); activeTab.b.classList.add('fw-colour-picker-tab-button-active'); activeTab.b.style.background = hexFormat(colour); activeTab.b.style.borderColor = hexFormat(colour); activeTab.b.style.color = hexFormat(textColour(colour)); } }; var createTab = function(id, name, construct) { var tabContainer = construct(); tabContainer.className = 'fw-colour-picker-tab-container fw-colour-picker-tab-' + id + '-container'; tabbedContainer.appendChild(tabContainer); var tabButton = document.createElement('input'); tabButton.type = 'button'; tabButton.value = name; tabButton.className = 'fw-colour-picker-tab-button fw-colour-picker-tab-' + id + '-button'; tabButton.onclick = function() { switchTab(id); }; tabbedList.appendChild(tabButton); tabs[id] = { c: tabContainer, b: tabButton }; if(activeTab === undefined) { activeTab = tabs[id]; tabContainer.className += ' fw-colour-picker-tab-container-active'; tabButton.className += ' fw-colour-picker-tab-button-active'; } }; if(options.showPresetsTab) createTab('presets', 'Presets', function() { var presets = options.presets; var cont = document.createElement('div'); for(var i = 0; i < presets.length; ++i) (function(preset) { var option = document.createElement('a'); option.href = 'javascript:void(0);'; option.className = 'fw-colour-picker-presets-option'; option.style.background = hexFormat(preset.c); option.title = preset.n; option.onclick = function() { setColour(preset.c); }; onColourChange.push(function(value) { option.classList[(value === preset.c ? 'add' : 'remove')]('fw-colour-picker-presets-option-active'); }); cont.appendChild(option); })(presets[i]); return cont; }); if(options.showGridTab) createTab('grid', 'Grid', function() { var greys = [0xFFFFFF, 0xEBEBEB, 0xD6D6D6, 0xC2C2C2, 0xADADAD, 0x999999, 0x858585, 0x707070, 0x5C5C5C, 0x474747, 0x333333, 0]; var colours = [0x00A1D8, 0x0061FE, 0x4D22B2, 0x982ABC, 0xB92D5D, 0xFF4015, 0xFF6A00, 0xFFAB01, 0xFDC700, 0xFEFB41, 0xD9EC37, 0x76BB40]; var shades = [-.675, -.499, -.345, -.134, 0, .134, .345, .499, .675]; var cont = document.createElement('div'); for(var i = 0; i < greys.length; ++i) (function(grey) { var option = document.createElement('a'); option.href = 'javascript:void(0);'; option.className = 'fw-colour-picker-grid-option'; option.style.background = hexFormat(grey); option.onclick = function() { setColour(grey); }; onColourChange.push(function(value) { option.classList[(value === grey ? 'add' : 'remove')]('fw-colour-picker-grid-option-active'); }); cont.appendChild(option); })(greys[i]); for(var i = 0; i < shades.length; ++i) for(var j = 0; j < colours.length; ++j) (function(colour) { var option = document.createElement('a'); option.href = 'javascript:void(0);'; option.className = 'fw-colour-picker-grid-option'; option.style.background = hexFormat(colour); option.onclick = function() { setColour(colour); }; onColourChange.push(function(value) { option.classList[(value === colour ? 'add' : 'remove')]('fw-colour-picker-grid-option-active'); }); cont.appendChild(option); })(shadeColour(colours[j], shades[i])); return cont; }); if(options.showSlidersTab) createTab('sliders', 'Sliders', function() { var cont = document.createElement('div'); var addSlider = function(id, name, update, apply) { var sCont = document.createElement('div'); sCont.className = 'fw-colour-picker-slider fw-colour-picker-slider-' + id; cont.appendChild(sCont); var sName = document.createElement('div'); sName.className = 'fw-colour-picker-slider-name'; sName.textContent = name; sCont.appendChild(sName); var sValue = document.createElement('div'); sValue.className = 'fw-colour-picker-slider-value'; sCont.appendChild(sValue); var sValueSliderCont = document.createElement('div'); sValueSliderCont.className = 'fw-colour-picker-slider-value-slider-container'; sValue.appendChild(sValueSliderCont); var sGradient = document.createElement('div'); sGradient.className = 'fw-colour-picker-slider-gradient'; sValueSliderCont.appendChild(sGradient); var sValueSlider = document.createElement('input'); sValueSlider.type = 'range'; if(sValueSlider.type === 'range') { sValueSlider.className = 'fw-colour-picker-slider-value-slider'; sValueSlider.min = '0'; sValueSlider.max = '255'; sValueSlider.onchange = function() { setColour(apply(colour, sValueSlider.value)); }; sValueSliderCont.appendChild(sValueSlider); } else sValueSlider = undefined; var sValueInputContainer = document.createElement('div'); sValueInputContainer.className = 'fw-colour-picker-slider-value-input-container'; sValue.appendChild(sValueInputContainer); var sValueInput = document.createElement('input'); sValueInput.className = 'fw-colour-picker-slider-value-input'; sValueInput.type = 'number'; sValueInput.min = '0'; sValueInput.max = '255'; sValueInput.onchange = function() { setColour(apply(colour, sValueInput.value)); }; sValueInputContainer.appendChild(sValueInput); sGradient.onmousedown = function(ev) { if(ev.button === 0) setColour(apply(colour, Math.floor(255 * (Math.max(0, Math.min(200, (ev.layerX - 5))) / 200)))); }; onColourChange.push(function(colour) { if(sValueSlider) sValueSlider.value = update(colour); sValueInput.value = update(colour); var gradient = 'linear-gradient(to right, ' + hexFormat(apply(colour, 0)) + ', ' + hexFormat(apply(colour, 0xFF)) + ')'; sGradient.style.background = ''; sGradient.style.background = gradient; if(!sGradient.style.background) sGradient.style.background = '-moz-' + gradient; if(!sGradient.style.background) sGradient.style.background = '-webkit-' + gradient; }); }; addSlider('red', 'Red', function(value) { return (value >> 16) & 0xFF; }, function(colour, value) { return (colour & 0xFFFF) | (Math.min(255, Math.max(0, value)) << 16); } ); addSlider('green', 'Green', function(value) { return (value >> 8) & 0xFF; }, function(colour, value) { return (colour & 0xFF00FF) | (Math.min(255, Math.max(0, value)) << 8); } ); addSlider('blue', 'Blue', function(value) { return value & 0xFF; }, function(colour, value) { return (colour & 0xFFFF00) | Math.min(255, Math.max(0, value)); } ); return cont; }); if(activeTab) { height += 261; form.appendChild(tabbed); } var middleRow = document.createElement('div'); middleRow.className = 'fw-colour-picker-middle-row'; form.appendChild(middleRow); var colourPreviewContainer = document.createElement('div'); colourPreviewContainer.className = 'fw-colour-picker-colour-preview-container'; middleRow.appendChild(colourPreviewContainer); var colourPreview = document.createElement('div'); colourPreview.className = 'fw-colour-picker-colour-preview'; colourPreviewContainer.appendChild(colourPreview); onColourChange.push(function(colour) { colourPreview.style.background = hexFormat(colour); }); var values = {}; var valuesContainer = document.createElement('div'); valuesContainer.className = 'fw-colour-picker-values-container'; middleRow.appendChild(valuesContainer); var addValue = function(id, name, type, format, change) { var valueContainer = document.createElement('label'); valueContainer.className = 'fw-colour-picker-values-child fw-colour-picker-' + id + '-value'; valuesContainer.appendChild(valueContainer); var valueLabel = document.createElement('div'); valueLabel.textContent = name; valueLabel.className = 'fw-colour-picker-values-child-label fw-colour-picker-' + id + '-value-label'; valueContainer.appendChild(valueLabel); var valueInput = document.createElement('input'); valueInput.type = type; valueInput.value = '0'; valueInput.className = 'fw-colour-picker-values-child-input fw-colour-picker-' + id + '-value-input'; valueInput.onchange = function() { change(valueInput.value); }; valueContainer.appendChild(valueInput); onColourChange.push(function(colour) { valueInput.value = format(colour); }); values[id] = { c: valueContainer, l: valueLabel, i: valueInput }; }; if(options.showHexValue) addValue('hex', 'Hex', 'text', function(value) { return hexFormat(value); }, function(value) { while(value.substring(0, 1) === '#') value = value.substring(1); value = value.substring(0, 6); if(value.length === 3) value = value.substring(0, 1) + value.substring(0, 1) + value.substring(1, 2) + value.substring(1, 2) + value.substring(2, 3) + value.substring(2, 3); if(value.length === 6) setColour(parseInt(value, 16)); }); if(options.showRawValue) addValue('raw', 'Raw', 'number', function(value) { return value; }, function(value) { setColour(Math.min(0xFFFFFF, Math.max(0, parseInt(value)))); }); var buttons = {}; var buttonsContainer = document.createElement('div'); buttonsContainer.className = 'fw-colour-picker-buttons-container'; form.appendChild(buttonsContainer); var buttonsContainerInner = document.createElement('div'); buttonsContainerInner.className = 'fw-colour-picker-buttons-container-inner'; buttonsContainer.appendChild(buttonsContainerInner); var addButton = function(id, name, action) { var button = document.createElement('input'); button.className = 'fw-colour-picker-buttons-button fw-colour-picker-buttons-' + id + '-button'; if(action === null) { button.className += ' fw-colour-picker-buttons-button-submit'; onColourChange.push(function(colour, text) { button.style.background = hexFormat(colour); button.style.color = hexFormat(text); }); button.type = 'submit'; } else { button.onclick = function() { action(); }; button.type = 'button'; } button.value = name; buttonsContainerInner.appendChild(button); buttons[id] = { b: button }; }; addButton('cancel', 'Cancel', cancel); addButton('apply', 'Apply', null); var setPosition = function(x, y) { container.style.top = y >= 0 ? (y.toString() + 'px') : ''; container.style.left = x >= 0 ? (x.toString() + 'px') : ''; }; setPosition(options.posX, options.posY); runOnColourChange(); var appendTo = function(parent) { parent.appendChild(container); }; var pub = { getWidth: function() { return 290; }, getHeight: function() { return height; }, getColour: function() { return colour; }, getContainer: function() { return container; }, setColour: setColour, appendTo: appendTo, setPosition: setPosition, switchTab: switchTab, close: close, suggestPosition: function(mouseEvent) { var x = 10, y = 10; if(document.body.clientWidth > 340) { x = mouseEvent.clientX; y = mouseEvent.clientY; var height = pub.getHeight(), width = pub.getWidth(); if(y > height + 20) y -= height; if(x > document.body.clientWidth - width - 20) x -= width; } return { x: x, y: y, }; }, }; return pub; }; FwColourPicker.hexFormat = function(raw) { var str = raw.toString(16).substring(0, 6); if(str.length < 6) str = '000000'.substring(str.length) + str; return '#' + str; };