summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/ui/AbstractComponent.java
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/ui/AbstractComponent.java')
-rw-r--r--server/src/com/vaadin/ui/AbstractComponent.java471
1 files changed, 409 insertions, 62 deletions
diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java
index 5c4fba739d..9ff6dff21e 100644
--- a/server/src/com/vaadin/ui/AbstractComponent.java
+++ b/server/src/com/vaadin/ui/AbstractComponent.java
@@ -16,29 +16,44 @@
package com.vaadin.ui;
-import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.StringTokenizer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.logging.Logger;
+
+import org.jsoup.nodes.Attribute;
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
import com.vaadin.event.ActionManager;
import com.vaadin.event.ConnectorActionManager;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.AbstractClientConnector;
+import com.vaadin.server.AbstractErrorMessage.ContentMode;
import com.vaadin.server.ComponentSizeValidator;
import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.ErrorMessage.ErrorLevel;
+import com.vaadin.server.Extension;
import com.vaadin.server.Resource;
+import com.vaadin.server.Responsive;
+import com.vaadin.server.SizeWithUnit;
+import com.vaadin.server.Sizeable;
+import com.vaadin.server.UserError;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ComponentConstants;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Field.ValueChangeEvent;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.util.ReflectTools;
/**
@@ -46,7 +61,7 @@ import com.vaadin.util.ReflectTools;
* {@link Component} interface. Basic UI components that are not derived from an
* external component can inherit this class to easily qualify as Vaadin
* components. Most components in Vaadin do just that.
- *
+ *
* @author Vaadin Ltd.
* @since 3.0
*/
@@ -83,8 +98,6 @@ public abstract class AbstractComponent extends AbstractClientConnector
private float height = SIZE_UNDEFINED;
private Unit widthUnit = Unit.PIXELS;
private Unit heightUnit = Unit.PIXELS;
- private static final Pattern sizePattern = Pattern
- .compile(SharedUtil.SIZE_PATTERN);
/**
* Keeps track of the Actions added to this component; the actual
@@ -98,6 +111,8 @@ public abstract class AbstractComponent extends AbstractClientConnector
private Boolean explicitImmediateValue;
+ protected static final String DESIGN_ATTR_PLAIN_TEXT = "plain-text";
+
/* Constructor */
/**
@@ -241,7 +256,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
* Sets the component's caption <code>String</code>. Caption is the visible
* name of the component. This method will trigger a
* {@link RepaintRequestEvent}.
- *
+ *
* @param caption
* the new caption <code>String</code> for the component.
*/
@@ -250,6 +265,35 @@ public abstract class AbstractComponent extends AbstractClientConnector
getState().caption = caption;
}
+ /**
+ * Sets whether the caption is rendered as HTML.
+ * <p>
+ * If set to true, the captions are rendered in the browser as HTML and the
+ * developer is responsible for ensuring no harmful HTML is used. If set to
+ * false, the caption is rendered in the browser as plain text.
+ * <p>
+ * The default is false, i.e. to render that caption as plain text.
+ *
+ * @param captionAsHtml
+ * true if the captions are rendered as HTML, false if rendered
+ * as plain text
+ */
+ public void setCaptionAsHtml(boolean captionAsHtml) {
+ getState().captionAsHtml = captionAsHtml;
+ }
+
+ /**
+ * Checks whether captions are rendered as HTML
+ * <p>
+ * The default is false, i.e. to render that caption as plain text.
+ *
+ * @return true if the captions are rendered as HTML, false if rendered as
+ * plain text
+ */
+ public boolean isCaptionAsHtml() {
+ return getState(false).captionAsHtml;
+ }
+
/*
* Don't add a JavaDoc comment here, we use the default documentation from
* implemented interface.
@@ -272,7 +316,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Sets the locale of this component.
- *
+ *
* <pre>
* // Component for which the locale is meaningful
* InlineDateField date = new InlineDateField(&quot;Datum&quot;);
@@ -284,8 +328,8 @@ public abstract class AbstractComponent extends AbstractClientConnector
* date.setResolution(DateField.RESOLUTION_DAY);
* layout.addComponent(date);
* </pre>
- *
- *
+ *
+ *
* @param locale
* the locale to become this component's locale.
*/
@@ -311,7 +355,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Sets the component's icon. This method will trigger a
* {@link RepaintRequestEvent}.
- *
+ *
* @param icon
* the icon to be shown with the component's caption.
*/
@@ -377,7 +421,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Sets the component's immediate mode to the specified status.
- *
+ *
* @param immediate
* the boolean value specifying if the component should be in the
* immediate mode after the call.
@@ -438,11 +482,11 @@ public abstract class AbstractComponent extends AbstractClientConnector
* Sets the component's description. See {@link #getDescription()} for more
* information on what the description is. This method will trigger a
* {@link RepaintRequestEvent}.
- *
+ *
* The description is displayed as HTML in tooltips or directly in certain
* components so care should be taken to avoid creating the possibility for
* HTML injection and possibly XSS vulnerabilities.
- *
+ *
* @param description
* the new description string for the component.
*/
@@ -491,7 +535,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
* To find the Window that contains the component, use {@code Window w =
* getParent(Window.class);}
* </p>
- *
+ *
* @param <T>
* The type of the ancestor
* @param parentType
@@ -512,7 +556,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Gets the error message for this component.
- *
+ *
* @return ErrorMessage containing the description of the error state of the
* component or null, if the component contains no errors. Extending
* classes should override this method if they support other error
@@ -525,9 +569,9 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Gets the component's error message.
- *
+ *
* @link Terminal.ErrorMessage#ErrorMessage(String, int)
- *
+ *
* @return the component's error message.
*/
public ErrorMessage getComponentError() {
@@ -537,9 +581,9 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Sets the component's error message. The message may contain certain XML
* tags, for more information see
- *
+ *
* @link Component.ErrorMessage#ErrorMessage(String, int)
- *
+ *
* @param componentError
* the new <code>ErrorMessage</code> of the component.
*/
@@ -616,7 +660,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Build CSS compatible string representation of height.
- *
+ *
* @return CSS height
*/
private String getCSSHeight() {
@@ -625,7 +669,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Build CSS compatible string representation of width.
- *
+ *
* @return CSS width
*/
private String getCSSWidth() {
@@ -635,12 +679,12 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Returns the shared state bean with information to be sent from the server
* to the client.
- *
+ *
* Subclasses should override this method and set any relevant fields of the
* state returned by super.getState().
- *
+ *
* @since 7.0
- *
+ *
* @return updated component shared state
*/
@Override
@@ -731,7 +775,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Sets the data object, that can be used for any application specific data.
* The component does not use or modify this data.
- *
+ *
* @param data
* the Application specific data.
* @since 3.1
@@ -742,7 +786,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Gets the application specific data. See {@link #setData(Object)}.
- *
+ *
* @return the Application specific data set with setData function.
* @since 3.1
*/
@@ -873,7 +917,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
*/
@Override
public void setWidth(String width) {
- Size size = parseStringSize(width);
+ SizeWithUnit size = SizeWithUnit.parseStringSize(width);
if (size != null) {
setWidth(size.getSize(), size.getUnit());
} else {
@@ -888,7 +932,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
*/
@Override
public void setHeight(String height) {
- Size size = parseStringSize(height);
+ SizeWithUnit size = SizeWithUnit.parseStringSize(height);
if (size != null) {
setHeight(size.getSize(), size.getUnit());
} else {
@@ -897,51 +941,350 @@ public abstract class AbstractComponent extends AbstractClientConnector
}
/*
- * Returns array with size in index 0 unit in index 1. Null or empty string
- * will produce {-1,Unit#PIXELS}
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#readDesign(org.jsoup.nodes.Element,
+ * com.vaadin.ui.declarative.DesignContext)
*/
- private static Size parseStringSize(String s) {
- if (s == null) {
- return null;
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ Attributes attr = design.attributes();
+ // handle default attributes
+ for (String attribute : getDefaultAttributes()) {
+ if (design.hasAttr(attribute)) {
+ DesignAttributeHandler.assignValue(this, attribute,
+ design.attr(attribute));
+ }
+
+ }
+ // handle immediate
+ if (attr.hasKey("immediate")) {
+ setImmediate(DesignAttributeHandler.parseBoolean(attr
+ .get("immediate")));
+ }
+
+ // handle locale
+ if (attr.hasKey("locale")) {
+ setLocale(getLocaleFromString(attr.get("locale")));
+ }
+ // handle width and height
+ readSize(attr);
+ // handle component error
+ if (attr.hasKey("error")) {
+ UserError error = new UserError(attr.get("error"),
+ ContentMode.HTML, ErrorLevel.ERROR);
+ setComponentError(error);
+ }
+ // Tab index when applicable
+ if (design.hasAttr("tabindex") && this instanceof Focusable) {
+ ((Focusable) this).setTabIndex(DesignAttributeHandler
+ .readAttribute("tabindex", design.attributes(),
+ Integer.class));
+ }
+
+ // handle responsive
+ if (attr.hasKey("responsive")) {
+ setResponsive(DesignAttributeHandler.parseBoolean(attr
+ .get("responsive")));
+ }
+ // check for unsupported attributes
+ Set<String> supported = new HashSet<String>();
+ supported.addAll(getDefaultAttributes());
+ supported.addAll(getCustomAttributes());
+ for (Attribute a : attr) {
+ if (!a.getKey().startsWith(":") && !supported.contains(a.getKey())) {
+ getLogger().info(
+ "Unsupported attribute found when reading from design : "
+ + a.getKey());
+ }
}
- s = s.trim();
- if ("".equals(s)) {
+ }
+
+ /**
+ * Constructs a Locale corresponding to the given string. The string should
+ * consist of one, two or three parts with '_' between the different parts
+ * if there is more than one part. The first part specifies the language,
+ * the second part the country and the third part the variant of the locale.
+ *
+ * @param localeString
+ * the locale specified as a string
+ * @return the Locale object corresponding to localeString
+ */
+ private Locale getLocaleFromString(String localeString) {
+ if (localeString == null) {
return null;
}
- float size = 0;
- Unit unit = null;
- Matcher matcher = sizePattern.matcher(s);
- if (matcher.find()) {
- size = Float.parseFloat(matcher.group(1));
- if (size < 0) {
- size = -1;
- unit = Unit.PIXELS;
- } else {
- String symbol = matcher.group(2);
- unit = Unit.getUnitFromSymbol(symbol);
+ String[] parts = localeString.split("_");
+ if (parts.length > 3) {
+ throw new RuntimeException("Cannot parse the locale string: "
+ + localeString);
+ }
+ switch (parts.length) {
+ case 1:
+ return new Locale(parts[0]);
+ case 2:
+ return new Locale(parts[0], parts[1]);
+ default:
+ return new Locale(parts[0], parts[1], parts[2]);
+ }
+ }
+
+ /**
+ * Toggles responsiveness of this component.
+ *
+ * @since 7.4
+ * @param responsive
+ * boolean enables responsiveness, false disables
+ */
+ private void setResponsive(boolean responsive) {
+ if (responsive) {
+ // make responsive if necessary
+ if (!isResponsive()) {
+ Responsive.makeResponsive(this);
+ }
+ } else {
+ // remove responsive extensions
+ List<Extension> extensions = new ArrayList<Extension>(
+ getExtensions());
+ for (Extension e : extensions) {
+ if (e instanceof Responsive) {
+ removeExtension(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the component is responsive
+ *
+ * @since 7.4
+ * @return true if the component is responsive
+ */
+ private boolean isResponsive() {
+ for (Extension e : getExtensions()) {
+ if (e instanceof Responsive) {
+ return true;
}
+ }
+ return false;
+ }
+
+ /**
+ * Reads the size of this component from the given design attributes. If the
+ * attributes do not contain relevant size information, defaults is
+ * consulted.
+ *
+ * @param attributes
+ * the design attributes
+ * @param defaultInstance
+ * instance of the class that has default sizing.
+ */
+ private void readSize(Attributes attributes) {
+ // read width
+ if (attributes.hasKey("width-auto") || attributes.hasKey("size-auto")) {
+ this.setWidth(null);
+ } else if (attributes.hasKey("width-full")
+ || attributes.hasKey("size-full")) {
+ this.setWidth("100%");
+ } else if (attributes.hasKey("width")) {
+ this.setWidth(attributes.get("width"));
+ }
+
+ // read height
+ if (attributes.hasKey("height-auto") || attributes.hasKey("size-auto")) {
+ this.setHeight(null);
+ } else if (attributes.hasKey("height-full")
+ || attributes.hasKey("size-full")) {
+ this.setHeight("100%");
+ } else if (attributes.hasKey("height")) {
+ this.setHeight(attributes.get("height"));
+ }
+ }
+
+ /**
+ * Writes the size related attributes for the component if they differ from
+ * the defaults
+ *
+ * @param component
+ * the component
+ * @param attributes
+ * the attribute map where the attribute are written
+ * @param defaultInstance
+ * the default instance of the class for fetching the default
+ * values
+ */
+ private void writeSize(Attributes attributes, Component defaultInstance) {
+ if (hasEqualSize(defaultInstance)) {
+ // we have default values -> ignore
+ return;
+ }
+ boolean widthFull = getWidth() == 100f
+ && getWidthUnits().equals(Sizeable.Unit.PERCENTAGE);
+ boolean heightFull = getHeight() == 100f
+ && getHeightUnits().equals(Sizeable.Unit.PERCENTAGE);
+ boolean widthAuto = getWidth() == -1;
+ boolean heightAuto = getHeight() == -1;
+
+ // first try the full shorthands
+ if (widthFull && heightFull) {
+ attributes.put("size-full", "true");
+ } else if (widthAuto && heightAuto) {
+ attributes.put("size-auto", "true");
} else {
- throw new IllegalArgumentException("Invalid size argument: \"" + s
- + "\" (should match " + sizePattern.pattern() + ")");
+ // handle width
+ if (!hasEqualWidth(defaultInstance)) {
+ if (widthFull) {
+ attributes.put("width-full", "true");
+ } else if (widthAuto) {
+ attributes.put("width-auto", "true");
+ } else {
+ String widthString = DesignAttributeHandler
+ .formatFloat(getWidth())
+ + getWidthUnits().getSymbol();
+ attributes.put("width", widthString);
+
+ }
+ }
+ if (!hasEqualHeight(defaultInstance)) {
+ // handle height
+ if (heightFull) {
+ attributes.put("height-full", "true");
+ } else if (heightAuto) {
+ attributes.put("height-auto", "true");
+ } else {
+ String heightString = DesignAttributeHandler
+ .formatFloat(getHeight())
+ + getHeightUnits().getSymbol();
+ attributes.put("height", heightString);
+ }
+ }
}
- return new Size(size, unit);
}
- private static class Size implements Serializable {
- float size;
- Unit unit;
+ /**
+ * Test if the given component has equal width with this instance
+ *
+ * @param component
+ * the component for the width comparison
+ * @return true if the widths are equal
+ */
+ private boolean hasEqualWidth(Component component) {
+ return getWidth() == component.getWidth()
+ && getWidthUnits().equals(component.getWidthUnits());
+ }
- public Size(float size, Unit unit) {
- this.size = size;
- this.unit = unit;
+ /**
+ * Test if the given component has equal height with this instance
+ *
+ * @param component
+ * the component for the height comparison
+ * @return true if the heights are equal
+ */
+ private boolean hasEqualHeight(Component component) {
+ return getHeight() == component.getHeight()
+ && getHeightUnits().equals(component.getHeightUnits());
+ }
+
+ /**
+ * Test if the given components has equal size with this instance
+ *
+ * @param component
+ * the component for the size comparison
+ * @return true if the sizes are equal
+ */
+ private boolean hasEqualSize(Component component) {
+ return hasEqualWidth(component) && hasEqualHeight(component);
+ }
+
+ /**
+ * Returns a collection of attributes that do not require custom handling
+ * when reading or writing design. These are typically attributes of some
+ * primitive type. The default implementation searches setters with
+ * primitive values
+ *
+ * @return a collection of attributes that can be read and written using the
+ * default approach.
+ */
+ private Collection<String> getDefaultAttributes() {
+ Collection<String> attributes = DesignAttributeHandler
+ .getSupportedAttributes(this.getClass());
+ attributes.removeAll(getCustomAttributes());
+ return attributes;
+ }
+
+ /**
+ * Returns a collection of attributes that should not be handled by the
+ * basic implementation of the {@link readDesign} and {@link writeDesign}
+ * methods. Typically these are handled in a custom way in the overridden
+ * versions of the above methods
+ *
+ * @since 7.4
+ *
+ * @return the collection of attributes that are not handled by the basic
+ * implementation
+ */
+ protected Collection<String> getCustomAttributes() {
+ ArrayList<String> l = new ArrayList<String>(
+ Arrays.asList(customAttributes));
+ if (this instanceof Focusable) {
+ l.add("tab-index");
+ l.add("tabindex");
}
+ return l;
+ }
+
+ private static final String[] customAttributes = new String[] { "width",
+ "height", "debug-id", "error", "width-auto", "height-auto",
+ "width-full", "height-full", "size-auto", "size-full",
+ "responsive", "immediate", "locale", "read-only", "_id" };
- public float getSize() {
- return size;
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#writeDesign(org.jsoup.nodes.Element,
+ * com.vaadin.ui.declarative.DesignContext)
+ */
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ // clear element contents
+ DesignAttributeHandler.clearElement(design);
+ AbstractComponent def = designContext.getDefaultInstance(this);
+ Attributes attr = design.attributes();
+ // handle default attributes
+ for (String attribute : getDefaultAttributes()) {
+ DesignAttributeHandler.writeAttribute(this, attribute, attr, def);
+ }
+ // handle immediate
+ if (explicitImmediateValue != null) {
+ DesignAttributeHandler.writeAttribute("immediate", attr,
+ explicitImmediateValue, def.isImmediate(), Boolean.class);
+ }
+ // handle locale
+ if (getLocale() != null
+ && (getParent() == null || !getLocale().equals(
+ getParent().getLocale()))) {
+ design.attr("locale", getLocale().toString());
+ }
+ // handle size
+ writeSize(attr, def);
+ // handle component error
+ String errorMsg = getComponentError() != null ? getComponentError()
+ .getFormattedHtmlMessage() : null;
+ String defErrorMsg = def.getComponentError() != null ? def
+ .getComponentError().getFormattedHtmlMessage() : null;
+ if (!SharedUtil.equals(errorMsg, defErrorMsg)) {
+ attr.put("error", errorMsg);
+ }
+ // handle tab index
+ if (this instanceof Focusable) {
+ DesignAttributeHandler.writeAttribute("tabindex", attr,
+ ((Focusable) this).getTabIndex(),
+ ((Focusable) def).getTabIndex(), Integer.class);
}
- public Unit getUnit() {
- return unit;
+ // handle responsive
+ if (isResponsive()) {
+ attr.put("responsive", "");
}
}
@@ -952,7 +1295,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Gets the {@link ActionManager} used to manage the
* {@link ShortcutListener}s added to this {@link Field}.
- *
+ *
* @return the ActionManager in use
*/
protected ActionManager getActionManager() {
@@ -996,7 +1339,7 @@ public abstract class AbstractComponent extends AbstractClientConnector
/**
* Determine whether a <code>content</code> component is equal to, or the
* ancestor of this component.
- *
+ *
* @param content
* the potential ancestor element
* @return <code>true</code> if the relationship holds
@@ -1012,4 +1355,8 @@ public abstract class AbstractComponent extends AbstractClientConnector
}
return false;
}
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(AbstractComponent.class.getName());
+ }
}