com.vaadin.ui.CssLayout 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.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import org.jsoup.nodes.Element;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
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.csslayout.CssLayoutServerRpc;
import com.vaadin.shared.ui.csslayout.CssLayoutState;
import com.vaadin.ui.declarative.DesignContext;
/**
* CssLayout is a layout component that can be used in browser environment only.
* It simply renders components and their captions into a same div element.
* Component layout can then be adjusted with css.
*
* In comparison to {@link HorizontalLayout} and {@link VerticalLayout}
*
* - rather similar server side api
*
- no spacing, alignment or expand ratios
*
- much simpler DOM that can be styled by skilled web developer
*
- no abstraction of browser differences (developer must ensure that the
* result works properly on each browser)
*
- different kind of handling for relative sizes (that are set from server
* side) (*)
*
- noticeably faster rendering time in some situations as we rely more on
* the browser's rendering engine.
*
*
* With {@link CustomLayout} one can often achieve similar results (good looking
* layouts with web technologies), but with CustomLayout developer needs to work
* with fixed templates.
*
* By extending CssLayout one can also inject some css rules straight to child
* components using {@link #getCss(Component)}.
*
*
* (*) Relative sizes (set from server side) are treated bit differently than in
* other layouts in Vaadin. In cssLayout the size is calculated relatively to
* CSS layouts content area which is pretty much as in html and css. In other
* layouts the size of component is calculated relatively to the "slot" given by
* layout.
*
* Also note that client side framework in Vaadin modifies inline style
* properties width and height. This happens on each update to component. If one
* wants to set component sizes with CSS, component must have undefined size on
* server side (which is not the default for all components) and the size must
* be defined with class styles - not by directly injecting width and height.
*
* @since 6.1 brought in from "FastLayouts" incubator project
*
*/
public class CssLayout extends AbstractLayout implements LayoutClickNotifier {
private CssLayoutServerRpc rpc = (MouseEventDetails mouseDetails,
Connector clickedConnector) -> fireEvent(
LayoutClickEvent.createEvent(CssLayout.this, mouseDetails,
clickedConnector));
/**
* Custom layout slots containing the components.
*/
protected LinkedList components = new LinkedList<>();
/**
* Constructs an empty CssLayout.
*/
public CssLayout() {
registerRpc(rpc);
}
/**
* Constructs a CssLayout with the given components in the given order.
*
* @see #addComponents(Component...)
*
* @param children
* Components to add to the container.
*/
public CssLayout(Component... children) {
this();
addComponents(children);
}
/**
* Add a component into this container. The component is added to the right
* or below the previous component.
*
* @param c
* the component to be added.
*/
@Override
public void addComponent(Component c) {
// Add to components before calling super.addComponent
// so that it is available to AttachListeners
components.add(c);
try {
super.addComponent(c);
} catch (IllegalArgumentException e) {
components.remove(c);
throw e;
}
}
/**
* Adds a component into this container. The component is added to the left
* or on top of the other components.
*
* @param c
* the component to be added.
*/
public void addComponentAsFirst(Component c) {
// If c is already in this, we must remove it before proceeding
// see ticket #7668
if (equals(c.getParent())) {
removeComponent(c);
}
components.addFirst(c);
try {
super.addComponent(c);
} catch (IllegalArgumentException e) {
components.remove(c);
throw e;
}
}
/**
* Adds a component into indexed position in this container.
*
* @param c
* the component to be added.
* @param index
* the index of the component position. The components currently
* in and after the position are shifted forwards.
*/
public void addComponent(Component c, int index) {
// If c is already in this, we must remove it before proceeding
// see ticket #7668
if (equals(c.getParent())) {
// When c is removed, all components after it are shifted down
if (index > getComponentIndex(c)) {
index--;
}
removeComponent(c);
}
components.add(index, c);
try {
super.addComponent(c);
} catch (IllegalArgumentException e) {
components.remove(c);
throw e;
}
}
/**
* Removes the component from this container.
*
* @param c
* the component to be removed.
*/
@Override
public void removeComponent(Component c) {
components.remove(c);
super.removeComponent(c);
}
/**
* Gets the component container iterator for going trough all the components
* in the container.
*
* @return the Iterator of the components inside the container.
*/
@Override
public Iterator iterator() {
return Collections.unmodifiableCollection(components).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 components.size();
}
@Override
public void beforeClientResponse(boolean initial) {
super.beforeClientResponse(initial);
// This is an obsolete hack that was required before Map
// was supported. The workaround is to instead use a Map with
// the connector id as the key, but that can only be used once the
// connector has been attached.
getState().childCss.clear();
for (Iterator ci = getComponentIterator(); ci.hasNext();) {
Component child = ci.next();
String componentCssString = getCss(child);
if (componentCssString != null) {
getState().childCss.put(child, componentCssString);
}
}
}
@Override
protected CssLayoutState getState() {
return (CssLayoutState) super.getState();
}
@Override
protected CssLayoutState getState(boolean markAsDirty) {
return (CssLayoutState) super.getState(markAsDirty);
}
/**
* Returns styles to be applied to given component. Override this method to
* inject custom style rules to components.
*
*
* Note that styles are injected over previous styles before actual child
* rendering. Previous styles are not cleared, but overridden.
*
*
* Note that one most often achieves better code style, by separating
* styling to theme (with custom theme and {@link #addStyleName(String)}.
* With own custom styles it is also very easy to break browser
* compatibility.
*
* @param c
* the component
* @return css rules to be applied to component
*/
protected String getCss(Component c) {
return null;
}
/* Documented in superclass */
@Override
public void replaceComponent(Component oldComponent,
Component newComponent) {
// Gets the locations
int oldLocation = -1;
int newLocation = -1;
int location = 0;
for (final Component component : components) {
if (component == oldComponent) {
oldLocation = location;
}
if (component == newComponent) {
newLocation = location;
}
location++;
}
if (oldLocation == -1) {
addComponent(newComponent);
} else if (newLocation == -1) {
removeComponent(oldComponent);
addComponent(newComponent, oldLocation);
} else {
if (oldLocation > newLocation) {
components.remove(oldComponent);
components.add(newLocation, oldComponent);
components.remove(newComponent);
components.add(oldLocation, newComponent);
} else {
components.remove(newComponent);
components.add(oldLocation, newComponent);
components.remove(oldComponent);
components.add(newLocation, oldComponent);
}
markAsDirty();
}
}
@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);
}
/**
* Returns the index of the given component.
*
* @param component
* The component to look up.
* @return The index of the component or -1 if the component is not a child.
*/
public int getComponentIndex(Component component) {
return components.indexOf(component);
}
/**
* Returns the component at the given position.
*
* @param index
* The position of the component.
* @return The component at the given index.
* @throws IndexOutOfBoundsException
* If the index is out of range.
*/
public Component getComponent(int index) throws IndexOutOfBoundsException {
return components.get(index);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
* 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()) {
Component newChild = designContext.readDesign(childComponent);
addComponent(newChild);
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
* , com.vaadin.ui.declarative.DesignContext)
*/
@Override
public void writeDesign(Element design, DesignContext designContext) {
// write default attributes
super.writeDesign(design, designContext);
CssLayout def = designContext.getDefaultInstance(this);
// handle children
if (!designContext.shouldWriteChildren(this, def)) {
return;
}
Element designElement = design;
for (Component child : this) {
Element childNode = designContext.createElement(child);
designElement.appendChild(childNode);
}
}
}