1 "use strict"; 2 /* 3 * Copyright (C) 2012 Google Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY 18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 var global = { 27 argumentsReceived: false, 28 params: null 29 }; 30 31 /** 32 * @param {Event} event 33 */ 34 function handleMessage(event) { 35 initialize(JSON.parse(event.data)); 36 global.argumentsReceived = true; 37 } 38 39 /** 40 * @param {!Object} args 41 */ 42 function initialize(args) { 43 global.params = args; 44 var main = $("main"); 45 main.innerHTML = ""; 46 var errorString = validateArguments(args); 47 if (errorString) { 48 main.textContent = "Internal error: " + errorString; 49 resizeWindow(main.offsetWidth, main.offsetHeight); 50 } else 51 new ColorPicker(main, args); 52 } 53 54 // The DefaultColorPalette is used when the list of values are empty. 55 var DefaultColorPalette = ["#000000", "#404040", "#808080", "#c0c0c0", 56 "#ffffff", "#980000", "#ff0000", "#ff9900", "#ffff00", "#00ff00", "#00ffff", 57 "#4a86e8", "#0000ff", "#9900ff", "#ff00ff"]; 58 59 function handleArgumentsTimeout() { 60 if (global.argumentsReceived) 61 return; 62 var args = { 63 values : DefaultColorPalette, 64 otherColorLabel: "Other..." 65 }; 66 initialize(args); 67 } 68 69 /** 70 * @param {!Object} args 71 * @return {?string} An error message, or null if the argument has no errors. 72 */ 73 function validateArguments(args) { 74 if (!args.values) 75 return "No values."; 76 if (!args.otherColorLabel) 77 return "No otherColorLabel."; 78 return null; 79 } 80 81 function ColorPicker(element, config) { 82 Picker.call(this, element, config); 83 this._config = config; 84 if (this._config.values.length === 0) 85 this._config.values = DefaultColorPalette; 86 this._container = null; 87 this._layout(); 88 document.body.addEventListener("keydown", this._handleKeyDown.bind(this)); 89 this._element.addEventListener("mousemove", this._handleMouseMove.bind(this)); 90 this._element.addEventListener("mousedown", this._handleMouseDown.bind(this)); 91 } 92 ColorPicker.prototype = Object.create(Picker.prototype); 93 94 var SwatchBorderBoxWidth = 24; // keep in sync with CSS 95 var SwatchBorderBoxHeight = 24; // keep in sync with CSS 96 var SwatchesPerRow = 5; 97 var SwatchesMaxRow = 4; 98 99 ColorPicker.prototype._layout = function() { 100 var container = createElement("div", "color-swatch-container"); 101 container.addEventListener("click", this._handleSwatchClick.bind(this), false); 102 for (var i = 0; i < this._config.values.length; ++i) { 103 var swatch = createElement("button", "color-swatch"); 104 swatch.dataset.index = i; 105 swatch.dataset.value = this._config.values[i]; 106 swatch.title = this._config.values[i]; 107 swatch.style.backgroundColor = this._config.values[i]; 108 container.appendChild(swatch); 109 } 110 var containerWidth = SwatchBorderBoxWidth * SwatchesPerRow; 111 if (this._config.values.length > SwatchesPerRow * SwatchesMaxRow) 112 containerWidth += getScrollbarWidth(); 113 container.style.width = containerWidth + "px"; 114 container.style.maxHeight = (SwatchBorderBoxHeight * SwatchesMaxRow) + "px"; 115 this._element.appendChild(container); 116 var otherButton = createElement("button", "other-color", this._config.otherColorLabel); 117 otherButton.addEventListener("click", this.chooseOtherColor.bind(this), false); 118 this._element.appendChild(otherButton); 119 this._container = container; 120 this._otherButton = otherButton; 121 var elementWidth = this._element.offsetWidth; 122 var elementHeight = this._element.offsetHeight; 123 resizeWindow(elementWidth, elementHeight); 124 }; 125 126 ColorPicker.prototype.selectColorAtIndex = function(index) { 127 index = Math.max(Math.min(this._container.childNodes.length - 1, index), 0); 128 this._container.childNodes[index].focus(); 129 }; 130 131 ColorPicker.prototype._handleMouseMove = function(event) { 132 if (event.target.classList.contains("color-swatch")) 133 event.target.focus(); 134 }; 135 136 ColorPicker.prototype._handleMouseDown = function(event) { 137 // Prevent blur. 138 if (event.target.classList.contains("color-swatch")) 139 event.preventDefault(); 140 }; 141 142 ColorPicker.prototype._handleKeyDown = function(event) { 143 var key = event.keyIdentifier; 144 if (key === "U+001B") // ESC 145 this.handleCancel(); 146 else if (key == "Left" || key == "Up" || key == "Right" || key == "Down") { 147 var selectedElement = document.activeElement; 148 var index = 0; 149 if (selectedElement.classList.contains("other-color")) { 150 if (key != "Right" && key != "Up") 151 return; 152 index = this._container.childNodes.length - 1; 153 } else if (selectedElement.classList.contains("color-swatch")) { 154 index = parseInt(selectedElement.dataset.index, 10); 155 switch (key) { 156 case "Left": 157 index--; 158 break; 159 case "Right": 160 index++; 161 break; 162 case "Up": 163 index -= SwatchesPerRow; 164 break; 165 case "Down": 166 index += SwatchesPerRow; 167 break; 168 } 169 if (index > this._container.childNodes.length - 1) { 170 this._otherButton.focus(); 171 return; 172 } 173 } 174 this.selectColorAtIndex(index); 175 } 176 event.preventDefault(); 177 }; 178 179 ColorPicker.prototype._handleSwatchClick = function(event) { 180 if (event.target.classList.contains("color-swatch")) 181 this.submitValue(event.target.dataset.value); 182 }; 183 184 if (window.dialogArguments) { 185 initialize(dialogArguments); 186 } else { 187 window.addEventListener("message", handleMessage, false); 188 window.setTimeout(handleArgumentsTimeout, 1000); 189 } 190