diff options
author | KatriHaapalinna <katri@vaadin.com> | 2018-04-19 15:03:16 +0300 |
---|---|---|
committer | Ilia Motornyi <elmot@vaadin.com> | 2018-04-19 15:03:16 +0300 |
commit | bdbb0b4328a36a66bb492973ca72497bfd34f6ad (patch) | |
tree | 62272a7e1fbe88f868b2fe13345f2d10fadc2c7e /server/src/main/java/com/vaadin | |
parent | 07c6456da8f15d279340188dc86d7424cee36cd0 (diff) | |
download | vaadin-framework-bdbb0b4328a36a66bb492973ca72497bfd34f6ad.tar.gz vaadin-framework-bdbb0b4328a36a66bb492973ca72497bfd34f6ad.zip |
Colorpicker validation handling (#10821)
* ColorTextField, helper methods, and regex for validating and handling text input
* Refactored structure to avoid creating new component for validation
* Style to adapt to error indicator
* Tests for validating input in ColorPickerPreview component's TextField
* Merge branch 'master' into colorpicker_validation
* Fix path to server class
* Fix test: Submit the new value
* Fix test: ignore Phantom JS
* Fix hsl+hsla validation patterns to accept '%', test value tweaking
* Merge branch 'master' of github.com:vaadin/framework into colorpicker_validation
* Fix: remove warning when color is updated from elsewhere
* Revisions: input validation only once, Logging level WARN
* Revisions: unit tests for color pattern matching
* Revisions: moved parsing to utility class, tests for parsing all accepted input formats
* Fixed import in tests, comments
* Revisions: Logger as constant, ignore utility class in serialization test
* Corner case tests
* Revisions: protected method for parsing error text, fix to test
* Revisions: NPE fix
Diffstat (limited to 'server/src/main/java/com/vaadin')
-rw-r--r-- | server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java | 100 | ||||
-rw-r--r-- | server/src/main/java/com/vaadin/ui/components/colorpicker/ColorUtil.java | 229 |
2 files changed, 271 insertions, 58 deletions
diff --git a/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java b/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java index f68ae0ce9f..57da8949bd 100644 --- a/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java +++ b/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java @@ -16,9 +16,15 @@ package com.vaadin.ui.components.colorpicker; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; import com.vaadin.data.HasValue; +import com.vaadin.server.AbstractErrorMessage.ContentMode; +import com.vaadin.server.ErrorMessage; +import com.vaadin.server.UserError; import com.vaadin.shared.Registration; +import com.vaadin.shared.ui.ErrorLevel; import com.vaadin.shared.ui.colorpicker.Color; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; @@ -31,6 +37,8 @@ import com.vaadin.ui.TextField; * @since 7.0.0 */ public class ColorPickerPreview extends CssLayout implements HasValue<Color> { + private static final Logger LOGGER = Logger + .getLogger(ColorPickerPreview.class.getName()); private static final String STYLE_DARK_COLOR = "v-textfield-dark"; private static final String STYLE_LIGHT_COLOR = "v-textfield-light"; @@ -86,6 +94,7 @@ public class ColorPickerPreview extends CssLayout implements HasValue<Color> { String colorCSS = color.getCSS(); field.setValue(colorCSS); + field.setComponentError(null); oldValue = colorCSS; @@ -120,68 +129,29 @@ public class ColorPickerPreview extends CssLayout implements HasValue<Color> { } private void valueChange(ValueChangeEvent<String> event) { + ErrorMessage errorMessage = null; String value = event.getValue(); + value = Objects.toString(value, "").trim(); Color oldColor = color; try { - if (value != null) { - /* - * Description of supported formats see - * http://www.w3schools.com/cssref/css_colors_legal.asp - */ - if (value.length() == 7 && value.startsWith("#")) { - // CSS color format (e.g. #000000) - int red = Integer.parseInt(value.substring(1, 3), 16); - int green = Integer.parseInt(value.substring(3, 5), 16); - int blue = Integer.parseInt(value.substring(5, 7), 16); - color = new Color(red, green, blue); - - } else if (value.startsWith("rgb")) { - // RGB color format rgb/rgba(255,255,255,0.1) - String[] colors = value.substring(value.indexOf('(') + 1, - value.length() - 1).split(","); - - int red = Integer.parseInt(colors[0]); - int green = Integer.parseInt(colors[1]); - int blue = Integer.parseInt(colors[2]); - if (colors.length > 3) { - int alpha = (int) (Double.parseDouble(colors[3]) - * 255d); - color = new Color(red, green, blue, alpha); - } else { - color = new Color(red, green, blue); - } - - } else if (value.startsWith("hsl")) { - // HSL color format hsl/hsla(100,50%,50%,1.0) - String[] colors = value.substring(value.indexOf('(') + 1, - value.length() - 1).split(","); - - int hue = Integer.parseInt(colors[0]); - int saturation = Integer - .parseInt(colors[1].replace("%", "")); - int lightness = Integer - .parseInt(colors[2].replace("%", "")); - int rgb = Color.HSLtoRGB(hue, saturation, lightness); - - if (colors.length > 3) { - int alpha = (int) (Double.parseDouble(colors[3]) - * 255d); - color = new Color(rgb); - color.setAlpha(alpha); - } else { - color = new Color(rgb); - } - } - - oldValue = value; - fireEvent(new ValueChangeEvent<>(this, oldColor, - event.isUserOriginated())); - } - } catch (NumberFormatException nfe) { - // Revert value - field.setValue(oldValue); + /* + * Description of supported formats see + * http://www.w3schools.com/cssref/css_colors_legal.asp + */ + color = ColorUtil.stringToColor(value); + + oldValue = value; + fireEvent(new ValueChangeEvent<>(this, oldColor, + event.isUserOriginated())); + } catch (NumberFormatException e) { + // Pattern matching ensures the validity of + // the input, this should never happen + LOGGER.log(Level.INFO, e.getMessage()); + errorMessage = new UserError(getUserErrorText(value), + ContentMode.TEXT, ErrorLevel.WARNING); } + field.setComponentError(errorMessage); } @Override @@ -224,4 +194,18 @@ public class ColorPickerPreview extends CssLayout implements HasValue<Color> { } } } -} + + /** + * Get the client error message text for color input parsing error. + * + * @param value + * input which caused the error + * @return error message text + */ + protected String getUserErrorText(String value) { + return value.isEmpty() ? "Input cannot be empty" + : "Input '".concat(value) + .concat("' is not in any recognized format"); + } + +}
\ No newline at end of file diff --git a/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorUtil.java b/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorUtil.java new file mode 100644 index 0000000000..ae942ea0ce --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorUtil.java @@ -0,0 +1,229 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * 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. + */ +package com.vaadin.ui.components.colorpicker; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.vaadin.shared.ui.colorpicker.Color; + +/** + * Utility class for matching and parsing {@link Color} objects from + * {@code String} input. + * + * Description of supported formats see + * http://www.w3schools.com/cssref/css_colors_legal.asp + * + * @since + */ +public class ColorUtil { + private ColorUtil() { + } + + /** + * Parses {@link Color} from any of the following {@link String} inputs: + * <br> + * - RGB hex (e.g. "#FFAA00"), {@link #HEX_PATTERN}<br> + * - RGB "function" (e.g. "rgb(128,0,255)"), {@link #RGB_PATTERN}<br> + * - RGBA "function" (e.g. "rgba(50,50,50,0.2)"), {@link #RGBA_PATTERN}<br> + * - HSL "function" (e.g. "hsl(50,50,50)"), {@link #HSL_PATTERN}<br> + * - HSLA "function" (e.g. "hsl(50,50,50,0.2)"), {@link #HSLA_PATTERN} + * <p> + * Parsing is case-insensitive. + * + * @param input + * String input + * @return {@link Color} parsed from input + * @throws NumberFormatException + * Input does not match any recognized pattern + */ + public static Color stringToColor(String input) { + Matcher m = HEX_PATTERN.matcher(input); + if (m.matches()) { + return getHexPatternColor(m); + } + m = RGB_PATTERN.matcher(input); + if (m.matches()) { + return getRGBPatternColor(m); + } + m = RGBA_PATTERN.matcher(input); + if (m.matches()) { + return getRGBAPatternColor(m); + } + m = HSL_PATTERN.matcher(input); + if (m.matches()) { + return getHSLPatternColor(m); + } + m = HSLA_PATTERN.matcher(input); + if (m.matches()) { + return getHSLAPatternColor(m); + } + + throw new NumberFormatException("Parsing color from input failed."); + } + + /** + * Parses {@link Color} from matched hexadecimal {@link Matcher}. + * + * @param matcher + * {@link Matcher} matching hexadecimal pattern with named regex + * groups {@code red}, {@code green}, and {@code blue} + * @return {@link Color} parsed from {@link Matcher} + */ + public static Color getHexPatternColor(Matcher matcher) { + int red = Integer.parseInt(matcher.group("red"), 16); + int green = Integer.parseInt(matcher.group("green"), 16); + int blue = Integer.parseInt(matcher.group("blue"), 16); + return new Color(red, green, blue); + } + + /** + * Parses {@link Color} from matched RGB {@link Matcher}. + * + * @param matcher + * {@link Matcher} matching RGB pattern with named regex groups + * {@code red}, {@code green}, and {@code blue} + * @return {@link Color} parsed from {@link Matcher} + */ + public static Color getRGBPatternColor(Matcher matcher) { + int red = Integer.parseInt(matcher.group("red")); + int green = Integer.parseInt(matcher.group("green")); + int blue = Integer.parseInt(matcher.group("blue")); + return new Color(red, green, blue); + } + + /** + * Parses {@link Color} from matched RGBA {@link Matcher}. + * + * @param matcher + * {@link Matcher} matching RGBA pattern with named regex groups + * {@code red}, {@code green}, {@code blue}, and {@code alpha} + * @return {@link Color} parsed from {@link Matcher} + */ + public static Color getRGBAPatternColor(Matcher matcher) { + Color c = getRGBPatternColor(matcher); + c.setAlpha((int) (Double.parseDouble(matcher.group("alpha")) * 255d)); + return c; + } + + /** + * Parses {@link Color} from matched HSL {@link Matcher}. + * + * @param matcher + * {@link Matcher} matching HSL pattern with named regex groups + * {@code hue}, {@code saturation}, and {@code light} + * @return {@link Color} parsed from {@link Matcher} + */ + public static Color getHSLPatternColor(Matcher matcher) { + int hue = Integer.parseInt(matcher.group("hue")); + int saturation = Integer.parseInt(matcher.group("saturation")); + int light = Integer.parseInt(matcher.group("light")); + int rgb = Color.HSLtoRGB(hue, saturation, light); + return new Color(rgb); + } + + /** + * Parses {@link Color} from matched HSLA {@link Matcher}. + * + * @param matcher + * {@link Matcher} matching HSLA pattern with named regex groups + * {@code hue}, {@code saturation}, {@code light}, and + * {@code alpha} + * @return {@link Color} parsed from {@link Matcher} + */ + public static Color getHSLAPatternColor(Matcher matcher) { + Color c = getHSLPatternColor(matcher); + c.setAlpha((int) (Double.parseDouble(matcher.group("alpha")) * 255d)); + return c; + } + + /** + * Case-insensitive {@link Pattern} with regular expression matching the + * default hexadecimal color presentation pattern:<br> + * '#' followed by six <code>[\da-fA-F]</code> characters. + * <p> + * Pattern contains named groups <code>red</code>, <code>green</code>, and + * <code>blue</code>, which represent the individual values. + */ + public static final Pattern HEX_PATTERN = Pattern.compile( + "(?i)^#\\s*(?<red>[\\da-f]{2})(?<green>[\\da-f]{2})(?<blue>[\\da-f]{2}" + + ")\\s*$"); + /** + * Case-insensitive {@link Pattern} with regular expression matching common + * RGB color presentation patterns:<br> + * 'rgb' followed by three [0-255] number values. Values can be separated + * with either comma or whitespace. + * <p> + * Pattern contains named groups <code>red</code>, <code>green</code>, and + * <code>blue</code>, which represent the individual values. + */ + public static final Pattern RGB_PATTERN = Pattern.compile( + "(?i)^rgb\\(\\s*(?<red>[01]?\\d{1,2}|2[0-4]\\d|25[0-5])(?:\\s*[,+|\\" + + "s+]\\s*)(?<green>[01]?\\d\\d?|2[0-4]\\d|25[0-5])(?:\\s*[," + + "+|\\s+]\\s*)(?<blue>[01]?\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\" + + ")$"); + /** + * Case-insensitive {@link Pattern} with regular expression matching common + * RGBA presentation patterns:<br> + * 'rgba' followed by three [0-255] values and one [0.0-1.0] value. Values + * can be separated with either comma or whitespace. The only accepted + * decimal marker is point ('.'). + * <p> + * Pattern contains named groups <code>red</code>, <code>green</code>, + * <code>blue</code>, and <code>alpha</code>, which represent the individual + * values. + */ + public static final Pattern RGBA_PATTERN = Pattern.compile( + "(?i)^rgba\\(\\s*(?<red>[01]?\\d{1,2}|2[0-4]\\d|25[0-5])(?:\\s*[,+|" + + "\\s+]\\s*)(?<green>[01]?\\d\\d?|2[0-4]\\d|25[0-5])(?:\\s" + + "*[,+|\\s+]\\s*)(?<blue>[01]?\\d\\d?|2[0-4]\\d|25[0-5])(?" + + ":\\s*[,+|\\s+]\\s*)(?<alpha>0(?:\\.\\d{1,2})?|0?(?:\\.\\" + + "d{1,2})|1(?:\\.0{1,2})?)\\s*\\)$"); + + /** + * Case-insensitive {@link Pattern} with regular expression matching common + * HSL presentation patterns:<br> + * 'hsl' followed by one [0-360] value and two [0-100] percentage value. + * Values can be separated with either comma or whitespace. The percent sign + * ('%') is optional. + * <p> + * Pattern contains named groups <code>hue</code>,<code>saturation</code>, + * and <code>light</code>, which represent the individual values. + */ + public static final Pattern HSL_PATTERN = Pattern.compile( + "(?i)hsl\\(\\s*(?<hue>[12]?\\d{1,2}|3[0-5]\\d|360)(?:\\s*[,+|\\s+]" + + "\\s*)(?<saturation>\\d{1,2}|100)(?:\\s*%?\\s*[,+|\\s+]\\" + + "s*)(?<light>\\d{1,2}|100)(?:\\s*%?\\s*)\\)$"); + + /** + * Case-insensitive {@link Pattern} with regular expression matching common + * HSLA presentation patterns:<br> + * 'hsla' followed by one [0-360] value, two [0-100] percentage values, and + * one [0.0-1.0] value. Values can be separated with either comma or + * whitespace. The percent sign ('%') is optional. The only accepted decimal + * marker is point ('.'). + * <p> + * Pattern contains named groups <code>hue</code>,<code>saturation</code>, + * <code>light</code>, and <code>alpha</code>, which represent the + * individual values. + */ + public static final Pattern HSLA_PATTERN = Pattern.compile( + "(?i)hsla\\(\\s*(?<hue>[12]?\\d{1,2}|3[0-5]\\d|360)(?:\\s*[,+|\\s+" + + "]\\s*)(?<saturation>\\d{1,2}|100)(?:\\s*%?\\s*[,+|\\s+]\\s*" + + ")(?<light>\\d{1,2}|100)(?:\\s*%?[,+|\\s+]\\s*)(?<alpha>" + + "0(?:\\.\\d{1,2})?|0?(?:\\.\\d{1,2})|1(?:\\.0{1,2})?)" + + "\\s*\\)$"); +} |