diff options
author | Knoobie <Knoobie@gmx.de> | 2018-12-18 14:29:59 +0100 |
---|---|---|
committer | Sun Zhe <31067185+ZheSun88@users.noreply.github.com> | 2018-12-18 15:29:59 +0200 |
commit | 353ba29cfdefddb032122cbeae5f02f6d9de76ba (patch) | |
tree | 05ee1da595faaab05976e9ab339472799648135b /server | |
parent | 9c9c962549eb04adac87b0cfe26d24b17843fb6a (diff) | |
download | vaadin-framework-353ba29cfdefddb032122cbeae5f02f6d9de76ba.tar.gz vaadin-framework-353ba29cfdefddb032122cbeae5f02f6d9de76ba.zip |
Checkbox allow customizing of input and label classNames. (#11372)
* add client side integration for custom styles for checkbox.label and checkbox.input
* add server side integration for checkbox element styling
* add server side tests
* add client side test
Diffstat (limited to 'server')
-rw-r--r-- | server/src/main/java/com/vaadin/ui/CheckBox.java | 201 | ||||
-rw-r--r-- | server/src/main/java/com/vaadin/ui/HasStyleNames.java | 179 | ||||
-rw-r--r-- | server/src/test/java/com/vaadin/ui/CheckBoxTest.java | 107 |
3 files changed, 487 insertions, 0 deletions
diff --git a/server/src/main/java/com/vaadin/ui/CheckBox.java b/server/src/main/java/com/vaadin/ui/CheckBox.java index 30ec0487b2..c0e259f330 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBox.java +++ b/server/src/main/java/com/vaadin/ui/CheckBox.java @@ -16,8 +16,12 @@ package com.vaadin.ui; +import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.Objects; +import java.util.StringTokenizer; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; @@ -30,6 +34,7 @@ import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Registration; +import com.vaadin.shared.ui.ComponentStateUtil; import com.vaadin.shared.ui.checkbox.CheckBoxServerRpc; import com.vaadin.shared.ui.checkbox.CheckBoxState; import com.vaadin.ui.declarative.DesignAttributeHandler; @@ -64,6 +69,175 @@ public class CheckBox extends AbstractField<Boolean> } }; + private CheckBoxInputElement checkBoxInputElement = null; + private CheckBoxLabelElement checkBoxLabelElement = null; + + /** + * The inner input element of the CheckBox. + */ + public static class CheckBoxInputElement implements HasStyleNames { + + private final CheckBox checkBox; + + private CheckBoxInputElement(CheckBox checkBox){ + this.checkBox = checkBox; + } + + @Override + // Implementation copied from AbstractComponent + public String getStyleName() { + // replaced String with StringBuilder + StringBuilder s = new StringBuilder(); + if (ComponentStateUtil.hasStyles(checkBox.getState(false).inputStyles)) { + for (final Iterator<String> it = checkBox.getState(false).inputStyles + .iterator(); it.hasNext();) { + s.append(it.next()); + if (it.hasNext()) { + s.append(" "); + } + } + } + return s.toString(); + } + + @Override + // Implementation copied from AbstractComponent + public void setStyleName(String style) { + if (style == null || style.isEmpty()) { + checkBox.getState().inputStyles = null; + return; + } + if (checkBox.getState().inputStyles == null) { + checkBox.getState().inputStyles = new ArrayList<>(); + } + List<String> styles = checkBox.getState().inputStyles; + styles.clear(); + StringTokenizer tokenizer = new StringTokenizer(style, " "); + while (tokenizer.hasMoreTokens()) { + styles.add(tokenizer.nextToken()); + } + } + + @Override + // Implementation copied from AbstractComponent + public void addStyleName(String style) { + if (style == null || style.isEmpty()) { + return; + } + if (checkBox.getState().inputStyles != null && checkBox.getState().inputStyles.contains(style)) { + return; + } + if (style.contains(" ")) { + // Split space separated style names and add them one by one. + StringTokenizer tokenizer = new StringTokenizer(style, " "); + while (tokenizer.hasMoreTokens()) { + addStyleName(tokenizer.nextToken()); + } + return; + } + + if (checkBox.getState().inputStyles == null) { + checkBox.getState().inputStyles = new ArrayList<>(); + } + List<String> styles = checkBox.getState().inputStyles; + styles.add(style); + } + + @Override + // Implementation copied from AbstractComponent + public void removeStyleName(String style) { + if (ComponentStateUtil.hasStyles(checkBox.getState().inputStyles)) { + StringTokenizer tokenizer = new StringTokenizer(style, " "); + while (tokenizer.hasMoreTokens()) { + checkBox.getState().inputStyles.remove(tokenizer.nextToken()); + } + } + } + } + + /** + * The inner label element of the CheckBox. + */ + public static class CheckBoxLabelElement implements HasStyleNames { + + private final CheckBox checkBox; + + private CheckBoxLabelElement(CheckBox checkBox){ + this.checkBox = checkBox; + } + + @Override + // Implementation copied from AbstractComponent + public String getStyleName() { + // replaced String with StringBuilder + StringBuilder s = new StringBuilder(); + if (ComponentStateUtil.hasStyles(checkBox.getState(false).labelStyles)) { + for (final Iterator<String> it = checkBox.getState(false).labelStyles + .iterator(); it.hasNext();) { + s.append(it.next()); + if (it.hasNext()) { + s.append(" "); + } + } + } + return s.toString(); + } + + @Override + // Implementation copied from AbstractComponent + public void setStyleName(String style) { + if (style == null || style.isEmpty()) { + checkBox.getState().labelStyles = null; + return; + } + if (checkBox.getState().labelStyles == null) { + checkBox.getState().labelStyles = new ArrayList<>(); + } + List<String> styles = checkBox.getState().labelStyles; + styles.clear(); + StringTokenizer tokenizer = new StringTokenizer(style, " "); + while (tokenizer.hasMoreTokens()) { + styles.add(tokenizer.nextToken()); + } + } + + @Override + // Implementation copied from AbstractComponent + public void addStyleName(String style) { + if (style == null || style.isEmpty()) { + return; + } + if (checkBox.getState().labelStyles != null && checkBox.getState().labelStyles.contains(style)) { + return; + } + if (style.contains(" ")) { + // Split space separated style names and add them one by one. + StringTokenizer tokenizer = new StringTokenizer(style, " "); + while (tokenizer.hasMoreTokens()) { + addStyleName(tokenizer.nextToken()); + } + return; + } + + if (checkBox.getState().labelStyles == null) { + checkBox.getState().labelStyles = new ArrayList<>(); + } + List<String> styles = checkBox.getState().labelStyles; + styles.add(style); + } + + @Override + // Implementation copied from AbstractComponent + public void removeStyleName(String style) { + if (ComponentStateUtil.hasStyles(checkBox.getState().labelStyles)) { + StringTokenizer tokenizer = new StringTokenizer(style, " "); + while (tokenizer.hasMoreTokens()) { + checkBox.getState().labelStyles.remove(tokenizer.nextToken()); + } + } + } + } + /** * Creates a new checkbox. */ @@ -211,4 +385,31 @@ public class CheckBox extends AbstractField<Boolean> def.getValue(), Boolean.class, designContext); } + /** + * Returns the {@link CheckBoxInputElement} element to manipulate + * the style name of the {@code input} element of the {@link CheckBox}. + * + * @since + * @return the current {@link CheckBoxInputElement}, not {@code null}. + */ + public CheckBoxInputElement getInputElement() { + if(checkBoxInputElement == null) { + checkBoxInputElement = new CheckBoxInputElement(this); + } + return checkBoxInputElement; + } + + /** + * Returns the {@link CheckBoxLabelElement} element to manipulate + * the style name of the {@code label} element of the {@link CheckBox}. + * + * @since + * @return the current {@link CheckBoxLabelElement}, not {@code null}. + */ + public CheckBoxLabelElement getLabelElement() { + if(checkBoxLabelElement == null) { + checkBoxLabelElement = new CheckBoxLabelElement(this); + } + return checkBoxLabelElement; + } } diff --git a/server/src/main/java/com/vaadin/ui/HasStyleNames.java b/server/src/main/java/com/vaadin/ui/HasStyleNames.java new file mode 100644 index 0000000000..953711b1e6 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/HasStyleNames.java @@ -0,0 +1,179 @@ +/* + * 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; + +import java.io.Serializable; + +/** + * Implemented by components which support style names. + * + * <p> + * Each style name will occur only once as specified and it is not + * prefixed with the style name of the component. + * </p> + * + * @since + */ +public interface HasStyleNames extends Serializable { + + /** + * Gets all user-defined CSS style names of a component. If the component + * has multiple style names defined, the return string is a space-separated + * list of style names. Built-in style names defined in Vaadin or GWT are + * not returned. + * + * <p> + * The style names are returned only in the basic form in which they were + * added. + * </p> + * + * @since + * @return the style name or a space-separated list of user-defined style + * names of the component + * @see #setStyleName(String) + * @see #addStyleName(String) + * @see #removeStyleName(String) + */ + String getStyleName(); + + /** + * Sets one or more user-defined style names of the component, replacing any + * previous user-defined styles. Multiple styles can be specified as a + * space-separated list of style names. The style names must be valid CSS + * class names. + * + * <p> + * It is normally a good practice to use {@link #addStyleName(String) + * addStyleName()} rather than this setter, as different software + * abstraction layers can then add their own styles without accidentally + * removing those defined in other layers. + * </p> + * + * @since + * @param style + * the new style or styles of the component as a space-separated + * list + * @see #getStyleName() + * @see #addStyleName(String) + * @see #removeStyleName(String) + */ + void setStyleName(String style); + + /** + * Adds or removes a style name. Multiple styles can be specified as a + * space-separated list of style names. + * + * If the {@code add} parameter is true, the style name is added to the + * component. If the {@code add} parameter is false, the style name is + * removed from the component. + * <p> + * Functionally this is equivalent to using {@link #addStyleName(String)} or + * {@link #removeStyleName(String)} + * + * @since + * @param style + * the style name to be added or removed + * @param add + * <code>true</code> to add the given style, <code>false</code> + * to remove it + * @see #addStyleName(String) + * @see #removeStyleName(String) + */ + default void setStyleName(String style, boolean add) { + if (add) { + addStyleName(style); + } else { + removeStyleName(style); + } + } + + /** + * Adds one or more style names to this component. Multiple styles can be + * specified as a space-separated list of style names. The style name will + * be rendered as a HTML class name, which can be used in a CSS definition. + * + * + * @since + * @param style + * the new style to be added to the component + * @see #getStyleName() + * @see #setStyleName(String) + * @see #removeStyleName(String) + */ + void addStyleName(String style); + + /** + * Adds one or more style names to this component by using one or multiple + * parameters. + * + * @since + * @param styles + * the style name or style names to be added to the component + * @see #addStyleName(String) + * @see #setStyleName(String) + * @see #removeStyleName(String) + */ + default void addStyleNames(String... styles) { + for (String style : styles) { + addStyleName(style); + } + } + + /** + * Removes one or more style names from component. Multiple styles can be + * specified as a space-separated list of style names. + * + * <p> + * The parameter must be a valid CSS style name. Only user-defined style + * names added with {@link #addStyleName(String) addStyleName()} or + * {@link #setStyleName(String) setStyleName()} can be removed; built-in + * style names defined in Vaadin or GWT can not be removed. + * </p> + * + * @since + * @param style + * the style name or style names to be removed + * @see #getStyleName() + * @see #setStyleName(String) + * @see #addStyleName(String) + */ + void removeStyleName(String style); + + /** + * Removes one or more style names from component. Multiple styles can be + * specified by using multiple parameters. + * + * <p> + * The parameter must be a valid CSS style name. Only user-defined style + * names added with {@link #addStyleName(String) addStyleName()} or + * {@link #setStyleName(String) setStyleName()} can be removed; built-in + * style names defined in Vaadin or GWT can not be removed. + * </p> + * + * @since + * @param styles + * the style name or style names to be removed + * @see #removeStyleName(String) + * @see #setStyleName(String) + * @see #addStyleName(String) + */ + default void removeStyleNames(String... styles) { + for (String style : styles) { + removeStyleName(style); + } + } + +} diff --git a/server/src/test/java/com/vaadin/ui/CheckBoxTest.java b/server/src/test/java/com/vaadin/ui/CheckBoxTest.java index 042bc07482..0fbd6fa694 100644 --- a/server/src/test/java/com/vaadin/ui/CheckBoxTest.java +++ b/server/src/test/java/com/vaadin/ui/CheckBoxTest.java @@ -1,10 +1,13 @@ package com.vaadin.ui; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Ignore; import org.junit.Test; import com.vaadin.server.ServerRpcManager; @@ -68,4 +71,108 @@ public class CheckBoxTest { cb.setValue(null); } + @Test + public void getComboBoxInput() { + CheckBox cb = new CheckBox(); + assertNotNull("getInputElement should always return a element", cb.getInputElement()); + assertHasStyleNames(cb.getInputElement()); + } + + @Test + public void getCheckBoxLabel() { + CheckBox cb = new CheckBox(); + assertNotNull("getLabelElement should always return a element", cb.getLabelElement()); + assertHasStyleNames(cb.getLabelElement()); + } + + @Test + @Ignore("Component#setStyleName(null, false) should not throw a NPE") + public void setStyleName_null_false_throws_NPE() { + // FIXME? - Currently it throws a NPE like the implementation in Component.java + // waiting for other ticket that fixes the behaviour in Component.java before + CheckBox cb = new CheckBox(); + cb.getLabelElement().addStyleName("first"); + cb.getLabelElement().setStyleName(null, false); + assertEquals("Removing a null style should be ignored", + "first", cb.getLabelElement().getStyleName()); + } + + private void assertHasStyleNames(HasStyleNames hasStyleNames) { + assertEquals("Given element should not have a default style name", + "", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleName("first"); + assertEquals("first", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleName("first"); + assertEquals("Adding two times the same style should be ignored", + "first", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleName(null); + assertEquals("Adding null as style should be ignored", + "first", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleName(""); + assertEquals("Adding an empty string as style should be ignored", + "first", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleName("second"); + assertEquals("first second", hasStyleNames.getStyleName()); + + hasStyleNames.removeStyleName("second"); + assertEquals("first", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleName("second third fourth"); + assertEquals("first second third fourth", hasStyleNames.getStyleName()); + + hasStyleNames.removeStyleName("third fourth"); + assertEquals("first second", hasStyleNames.getStyleName()); + + hasStyleNames.addStyleNames("third", "fourth"); + assertEquals("first second third fourth", hasStyleNames.getStyleName()); + + hasStyleNames.removeStyleNames("second", "fourth"); + assertEquals("first third", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName(null); + assertEquals("Setting null as style should reset them", + "", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("set-style"); + assertEquals("set-style", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName(""); + assertEquals("Setting an empty string as style should reset them", + "", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("set-style multiple values"); + assertEquals("set-style multiple values", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("set-style", false); + assertEquals("multiple values", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("", false); + assertEquals("Removing an empty style should be ignored", + "multiple values", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("", true); + assertEquals("Adding an empty style should be ignored", + "multiple values", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName(null, true); + assertEquals("Adding a null style should be ignored", + "multiple values", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("multiple values", false); + assertEquals("Removing all set style names should result in an empty style name", + "", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("set-style", true); + assertEquals("set-style", hasStyleNames.getStyleName()); + + hasStyleNames.setStyleName("multiple values", true); + assertEquals("set-style multiple values", hasStyleNames.getStyleName()); + + } + } |