com.vaadin.ui.AbsoluteLayout Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.ui;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
import com.vaadin.server.Sizeable;
import com.vaadin.shared.Connector;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.absolutelayout.AbsoluteLayoutServerRpc;
import com.vaadin.shared.ui.absolutelayout.AbsoluteLayoutState;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
/**
* AbsoluteLayout is a layout implementation that mimics html absolute
* positioning.
*
*/
@SuppressWarnings("serial")
public class AbsoluteLayout extends AbstractLayout
implements LayoutClickNotifier {
// constants for design attributes
private static final String ATTR_TOP = ":top";
private static final String ATTR_RIGHT = ":right";
private static final String ATTR_BOTTOM = ":bottom";
private static final String ATTR_LEFT = ":left";
private static final String ATTR_Z_INDEX = ":z-index";
private final AbsoluteLayoutServerRpc rpc = (MouseEventDetails mouseDetails,
Connector clickedConnector) -> fireEvent(
LayoutClickEvent.createEvent(AbsoluteLayout.this,
mouseDetails, clickedConnector));
// Maps each component to a position
private final LinkedHashMap componentToCoordinates = new LinkedHashMap<>();
/**
* Creates an AbsoluteLayout with full size.
*/
public AbsoluteLayout() {
registerRpc(rpc);
setSizeFull();
}
@Override
protected AbsoluteLayoutState getState() {
return (AbsoluteLayoutState) super.getState();
}
@Override
protected AbsoluteLayoutState getState(boolean markAsDirty) {
return (AbsoluteLayoutState) super.getState(markAsDirty);
}
/**
* Gets an iterator for going through all components enclosed in the
* absolute layout.
*/
@Override
public Iterator iterator() {
return Collections
.unmodifiableCollection(componentToCoordinates.keySet())
.iterator();
}
/**
* Gets the number of contained components. Consistent with the iterator
* returned by {@link #getComponentIterator()}.
*
* @return the number of contained components
*/
@Override
public int getComponentCount() {
return componentToCoordinates.size();
}
/**
* Replaces one component with another one. The new component inherits the
* old components position.
*/
@Override
public void replaceComponent(Component oldComponent,
Component newComponent) {
ComponentPosition position = getPosition(oldComponent);
removeComponent(oldComponent);
addComponent(newComponent, position);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponentContainer#addComponent(com.vaadin.ui.
* Component )
*/
@Override
public void addComponent(Component c) {
addComponent(c, new ComponentPosition());
}
/**
* Adds a component to the layout. The component can be positioned by
* providing a string formatted in CSS-format.
*
* For example the string "top:10px;left:10px" will position the component
* 10 pixels from the left and 10 pixels from the top. The identifiers:
* "top","left","right" and "bottom" can be used to specify the position.
*
*
* @param c
* The component to add to the layout
* @param cssPosition
* The css position string
*/
public void addComponent(Component c, String cssPosition) {
ComponentPosition position = new ComponentPosition();
position.setCSSString(cssPosition);
addComponent(c, position);
}
/**
* Adds the component using the given position. Ensures the position is only
* set if the component is added correctly.
*
* @param c
* The component to add
* @param position
* The position info for the component. Must not be null.
* @throws IllegalArgumentException
* If adding the component failed
*/
private void addComponent(Component c, ComponentPosition position)
throws IllegalArgumentException {
if (equals(c.getParent())) {
removeComponent(c);
}
/*
* Create position instance and add it to componentToCoordinates map. We
* need to do this before we call addComponent so the attachListeners
* can access this position. #6368
*/
internalSetPosition(c, position);
try {
super.addComponent(c);
} catch (IllegalArgumentException e) {
internalRemoveComponent(c);
throw e;
}
}
/**
* Removes the component from all internal data structures. Does not
* actually remove the component from the layout (this is assumed to have
* been done by the caller).
*
* @param c
* The component to remove
*/
private void internalRemoveComponent(Component c) {
componentToCoordinates.remove(c);
}
@Override
public void beforeClientResponse(boolean initial) {
super.beforeClientResponse(initial);
// This could be in internalRemoveComponent and internalSetComponent if
// Map was supported. We cannot get the child
// connectorId unless the component is attached to the application so
// the String->String map cannot be populated in internal* either.
Map connectorToPosition = new HashMap<>();
for (Iterator ci = getComponentIterator(); ci.hasNext();) {
Component c = ci.next();
connectorToPosition.put(c.getConnectorId(),
getPosition(c).getCSSString());
}
getState().connectorToCssPosition = connectorToPosition;
}
/*
* (non-Javadoc)
*
* @see
* com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui
* .Component)
*/
@Override
public void removeComponent(Component c) {
internalRemoveComponent(c);
super.removeComponent(c);
}
/**
* Gets the position of a component in the layout. Returns null if component
* is not attached to the layout.
*
* Note that you cannot update the position by updating this object. Call
* {@link #setPosition(Component, ComponentPosition)} with the updated
* {@link ComponentPosition} object.
*
*
* @param component
* The component which position is needed
* @return An instance of ComponentPosition containing the position of the
* component, or null if the component is not enclosed in the
* layout.
*/
public ComponentPosition getPosition(Component component) {
return componentToCoordinates.get(component);
}
/**
* Sets the position of a component in the layout.
*
* @param component
* @param position
*/
public void setPosition(Component component, ComponentPosition position) {
if (!componentToCoordinates.containsKey(component)) {
throw new IllegalArgumentException(
"Component must be a child of this layout");
}
internalSetPosition(component, position);
}
/**
* Updates the position for a component. Caller must ensure component is a
* child of this layout.
*
* @param component
* The component. Must be a child for this layout. Not enforced.
* @param position
* New position. Must not be null.
*/
private void internalSetPosition(Component component,
ComponentPosition position) {
componentToCoordinates.put(component, position);
markAsDirty();
}
/**
* The CompontPosition class represents a components position within the
* absolute layout. It contains the attributes for left, right, top and
* bottom and the units used to specify them.
*/
public class ComponentPosition implements Serializable {
private int zIndex = -1;
private Float topValue = null;
private Float rightValue = null;
private Float bottomValue = null;
private Float leftValue = null;
private Unit topUnits = Unit.PIXELS;
private Unit rightUnits = Unit.PIXELS;
private Unit bottomUnits = Unit.PIXELS;
private Unit leftUnits = Unit.PIXELS;
/**
* Sets the position attributes using CSS syntax. Attributes not
* included in the string are reset to their unset states.
*
*
* setCSSString("top:10px;left:20%;z-index:16;");
*
*
* @param css
*/
public void setCSSString(String css) {
topValue = rightValue = bottomValue = leftValue = null;
topUnits = rightUnits = bottomUnits = leftUnits = Unit.PIXELS;
zIndex = -1;
if (css == null) {
return;
}
for (String cssProperty : css.split(";")) {
String[] keyValuePair = cssProperty.split(":");
String key = keyValuePair[0].trim();
if (key.isEmpty()) {
continue;
}
if (key.equals("z-index")) {
zIndex = Integer.parseInt(keyValuePair[1].trim());
} else {
String value;
if (keyValuePair.length > 1) {
value = keyValuePair[1].trim();
} else {
value = "";
}
String symbol = value.replaceAll("[0-9\\.\\-]+", "");
if (!symbol.isEmpty()) {
value = value.substring(0, value.indexOf(symbol))
.trim();
}
float v = Float.parseFloat(value);
Unit unit = Unit.getUnitFromSymbol(symbol);
if (key.equals("top")) {
topValue = v;
topUnits = unit;
} else if (key.equals("right")) {
rightValue = v;
rightUnits = unit;
} else if (key.equals("bottom")) {
bottomValue = v;
bottomUnits = unit;
} else if (key.equals("left")) {
leftValue = v;
leftUnits = unit;
}
}
}
markAsDirty();
}
/**
* Converts the internal values into a valid CSS string.
*
* @return A valid CSS string
*/
public String getCSSString() {
String s = "";
if (topValue != null) {
s += "top:" + topValue + topUnits.getSymbol() + ";";
}
if (rightValue != null) {
s += "right:" + rightValue + rightUnits.getSymbol() + ";";
}
if (bottomValue != null) {
s += "bottom:" + bottomValue + bottomUnits.getSymbol() + ";";
}
if (leftValue != null) {
s += "left:" + leftValue + leftUnits.getSymbol() + ";";
}
if (zIndex >= 0) {
s += "z-index:" + zIndex + ";";
}
return s;
}
/**
* Sets the 'top' attribute; distance from the top of the component to
* the top edge of the layout.
*
* @param topValue
* The value of the 'top' attribute
* @param topUnits
* The unit of the 'top' attribute. See UNIT_SYMBOLS for a
* description of the available units.
*/
public void setTop(Float topValue, Unit topUnits) {
this.topValue = topValue;
this.topUnits = topUnits;
markAsDirty();
}
/**
* Sets the 'right' attribute; distance from the right of the component
* to the right edge of the layout.
*
* @param rightValue
* The value of the 'right' attribute
* @param rightUnits
* The unit of the 'right' attribute. See UNIT_SYMBOLS for a
* description of the available units.
*/
public void setRight(Float rightValue, Unit rightUnits) {
this.rightValue = rightValue;
this.rightUnits = rightUnits;
markAsDirty();
}
/**
* Sets the 'bottom' attribute; distance from the bottom of the
* component to the bottom edge of the layout.
*
* @param bottomValue
* The value of the 'bottom' attribute
* @param bottomUnits
* The unit of the 'bottom' attribute. See UNIT_SYMBOLS for a
* description of the available units.
*/
public void setBottom(Float bottomValue, Unit bottomUnits) {
this.bottomValue = bottomValue;
this.bottomUnits = bottomUnits;
markAsDirty();
}
/**
* Sets the 'left' attribute; distance from the left of the component to
* the left edge of the layout.
*
* @param leftValue
* The value of the 'left' attribute
* @param leftUnits
* The unit of the 'left' attribute. See UNIT_SYMBOLS for a
* description of the available units.
*/
public void setLeft(Float leftValue, Unit leftUnits) {
this.leftValue = leftValue;
this.leftUnits = leftUnits;
markAsDirty();
}
/**
* Sets the 'z-index' attribute; the visual stacking order.
*
* @param zIndex
* The z-index for the component.
*/
public void setZIndex(int zIndex) {
this.zIndex = zIndex;
markAsDirty();
}
/**
* Sets the value of the 'top' attribute; distance from the top of the
* component to the top edge of the layout.
*
* @param topValue
* The value of the 'left' attribute
*/
public void setTopValue(Float topValue) {
this.topValue = topValue;
markAsDirty();
}
/**
* Gets the 'top' attributes value in current units.
*
* @see #getTopUnits()
* @return The value of the 'top' attribute, null if not set
*/
public Float getTopValue() {
return topValue;
}
/**
* Gets the 'right' attributes value in current units.
*
* @return The value of the 'right' attribute, null if not set
* @see #getRightUnits()
*/
public Float getRightValue() {
return rightValue;
}
/**
* Sets the 'right' attribute value (distance from the right of the
* component to the right edge of the layout). Currently active units
* are maintained.
*
* @param rightValue
* The value of the 'right' attribute
* @see #setRightUnits(Unit)
*/
public void setRightValue(Float rightValue) {
this.rightValue = rightValue;
markAsDirty();
}
/**
* Gets the 'bottom' attributes value using current units.
*
* @return The value of the 'bottom' attribute, null if not set
* @see #getBottomUnits()
*/
public Float getBottomValue() {
return bottomValue;
}
/**
* Sets the 'bottom' attribute value (distance from the bottom of the
* component to the bottom edge of the layout). Currently active units
* are maintained.
*
* @param bottomValue
* The value of the 'bottom' attribute
* @see #setBottomUnits(Unit)
*/
public void setBottomValue(Float bottomValue) {
this.bottomValue = bottomValue;
markAsDirty();
}
/**
* Gets the 'left' attributes value using current units.
*
* @return The value of the 'left' attribute, null if not set
* @see #getLeftUnits()
*/
public Float getLeftValue() {
return leftValue;
}
/**
* Sets the 'left' attribute value (distance from the left of the
* component to the left edge of the layout). Currently active units are
* maintained.
*
* @param leftValue
* The value of the 'left' CSS-attribute
* @see #setLeftUnits(Unit)
*/
public void setLeftValue(Float leftValue) {
this.leftValue = leftValue;
markAsDirty();
}
/**
* Gets the unit for the 'top' attribute.
*
* @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public Unit getTopUnits() {
return topUnits;
}
/**
* Sets the unit for the 'top' attribute.
*
* @param topUnits
* See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public void setTopUnits(Unit topUnits) {
this.topUnits = topUnits;
markAsDirty();
}
/**
* Gets the unit for the 'right' attribute.
*
* @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public Unit getRightUnits() {
return rightUnits;
}
/**
* Sets the unit for the 'right' attribute.
*
* @param rightUnits
* See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public void setRightUnits(Unit rightUnits) {
this.rightUnits = rightUnits;
markAsDirty();
}
/**
* Gets the unit for the 'bottom' attribute.
*
* @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public Unit getBottomUnits() {
return bottomUnits;
}
/**
* Sets the unit for the 'bottom' attribute.
*
* @param bottomUnits
* See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public void setBottomUnits(Unit bottomUnits) {
this.bottomUnits = bottomUnits;
markAsDirty();
}
/**
* Gets the unit for the 'left' attribute.
*
* @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public Unit getLeftUnits() {
return leftUnits;
}
/**
* Sets the unit for the 'left' attribute.
*
* @param leftUnits
* See {@link Sizeable} UNIT_SYMBOLS for a description of the
* available units.
*/
public void setLeftUnits(Unit leftUnits) {
this.leftUnits = leftUnits;
markAsDirty();
}
/**
* Gets the 'z-index' attribute.
*
* @return the zIndex The z-index attribute
*/
public int getZIndex() {
return zIndex;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getCSSString();
}
}
@Override
public Registration addLayoutClickListener(LayoutClickListener listener) {
return addListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
LayoutClickEvent.class, listener,
LayoutClickListener.clickMethod);
}
@Override
@Deprecated
public void removeLayoutClickListener(LayoutClickListener listener) {
removeListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
LayoutClickEvent.class, listener);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Node,
* com.vaadin.ui.declarative.DesignContext)
*/
@Override
public void readDesign(Element design, DesignContext designContext) {
// process default attributes
super.readDesign(design, designContext);
// handle children
for (Element childComponent : design.children()) {
Attributes attr = childComponent.attributes();
Component newChild = designContext.readDesign(childComponent);
StringBuilder css = new StringBuilder();
if (attr.hasKey(ATTR_TOP)) {
css.append("top:").append(attr.get(ATTR_TOP)).append(';');
}
if (attr.hasKey(ATTR_RIGHT)) {
css.append("right:").append(attr.get(ATTR_RIGHT)).append(';');
}
if (attr.hasKey(ATTR_BOTTOM)) {
css.append("bottom:").append(attr.get(ATTR_BOTTOM)).append(';');
}
if (attr.hasKey(ATTR_LEFT)) {
css.append("left:").append(attr.get(ATTR_LEFT)).append(';');
}
if (attr.hasKey(ATTR_Z_INDEX)) {
css.append("z-index:").append(attr.get(ATTR_Z_INDEX))
.append(';');
}
addComponent(newChild, css.toString());
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Node,
* com.vaadin.ui.declarative.DesignContext)
*/
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
AbsoluteLayout def = designContext.getDefaultInstance(this);
if (!designContext.shouldWriteChildren(this, def)) {
return;
}
// handle children
for (Component child : this) {
Element childElement = designContext.createElement(child);
design.appendChild(childElement);
// handle position
ComponentPosition position = getPosition(child);
writePositionAttribute(childElement, ATTR_TOP,
position.getTopUnits().getSymbol(), position.getTopValue());
writePositionAttribute(childElement, ATTR_RIGHT,
position.getRightUnits().getSymbol(),
position.getRightValue());
writePositionAttribute(childElement, ATTR_BOTTOM,
position.getBottomUnits().getSymbol(),
position.getBottomValue());
writePositionAttribute(childElement, ATTR_LEFT,
position.getLeftUnits().getSymbol(),
position.getLeftValue());
// handle z-index
if (position.getZIndex() >= 0) {
childElement.attr(ATTR_Z_INDEX,
String.valueOf(position.zIndex));
}
}
}
/**
* Private method for writing position attributes
*
* @since 7.4
* @param node
* target node
* @param key
* attribute key
* @param symbol
* value symbol
* @param value
* the value
*/
private void writePositionAttribute(Node node, String key, String symbol,
Float value) {
if (value != null) {
String valueString = DesignAttributeHandler.getFormatter()
.format(value);
node.attr(key, valueString + symbol);
}
}
}