All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.ui.AbstractSingleComponentContainer Maven / Gradle / Ivy

There is a newer version: 8.27.3
Show newest version
/*
 * 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 org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.vaadin.server.ComponentSizeValidator;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.AbstractSingleComponentContainerState;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;

/**
 * Abstract base class for component containers that have only one child
 * component.
 *
 * For component containers that support multiple children, inherit
 * {@link AbstractComponentContainer} instead of this class.
 *
 * @since 7.0
 */
public abstract class AbstractSingleComponentContainer extends AbstractComponent
        implements SingleComponentContainer {

    private Component content;

    @Override
    public int getComponentCount() {
        return (content != null) ? 1 : 0;
    }

    @Override
    public Iterator iterator() {
        if (content != null) {
            return Collections.singletonList(content).iterator();
        } else {
            return Collections. emptyList().iterator();
        }
    }

    /* documented in interface */
    @Override
    public Registration addComponentAttachListener(
            ComponentAttachListener listener) {
        return addListener(ComponentAttachEvent.class, listener,
                ComponentAttachListener.attachMethod);
    }

    /* documented in interface */
    @Override
    @Deprecated
    public void removeComponentAttachListener(
            ComponentAttachListener listener) {
        removeListener(ComponentAttachEvent.class, listener,
                ComponentAttachListener.attachMethod);
    }

    /* documented in interface */
    @Override
    public Registration addComponentDetachListener(
            ComponentDetachListener listener) {
        return addListener(ComponentDetachEvent.class, listener,
                ComponentDetachListener.detachMethod);
    }

    /* documented in interface */
    @Override
    @Deprecated
    public void removeComponentDetachListener(
            ComponentDetachListener listener) {
        removeListener(ComponentDetachEvent.class, listener,
                ComponentDetachListener.detachMethod);
    }

    /**
     * Fires the component attached event. This is called by the
     * {@link #setContent(Component)} method after the component has been set as
     * the content.
     *
     * @param component
     *            the component that has been added to this container.
     */
    protected void fireComponentAttachEvent(Component component) {
        fireEvent(new ComponentAttachEvent(this, component));
    }

    /**
     * Fires the component detached event. This is called by the
     * {@link #setContent(Component)} method after the content component has
     * been replaced by other content.
     *
     * @param component
     *            the component that has been removed from this container.
     */
    protected void fireComponentDetachEvent(Component component) {
        fireEvent(new ComponentDetachEvent(this, component));
    }

    @Override
    public Component getContent() {
        return content;
    }

    /**
     * Sets the content of this container. The content is a component that
     * serves as the outermost item of the visual contents.
     *
     * The content must always be set, either with a constructor parameter or by
     * calling this method.
     *
     * Previous versions of Vaadin used a {@link VerticalLayout} with margins
     * enabled as the default content but that is no longer the case.
     *
     * @param content
     *            a component (typically a layout) to use as content
     */
    @Override
    public void setContent(Component content) {
        // Make sure we're not adding the component inside it's own content
        if (isOrHasAncestor(content)) {
            throw new IllegalArgumentException(
                    "Component cannot be added inside it's own content");
        }

        Component oldContent = getContent();
        if (oldContent == content) {
            // do not set the same content twice
            return;
        }
        if (oldContent != null && equals(oldContent.getParent())) {
            oldContent.setParent(null);
            fireComponentDetachEvent(oldContent);
        }
        this.content = content;
        if (content != null) {
            removeFromParent(content);

            content.setParent(this);
            fireComponentAttachEvent(content);
        }

        markAsDirty();
    }

    /**
     * Utility method for removing a component from its parent (if possible).
     *
     * @param content
     *            component to remove
     */
    // TODO move utility method elsewhere?
    public static void removeFromParent(Component content)
            throws IllegalArgumentException {
        // Verify the appropriate session is locked
        UI parentUI = content.getUI();
        if (parentUI != null) {
            VaadinSession parentSession = parentUI.getSession();
            if (parentSession != null && !parentSession.hasLock()) {
                String message = "Cannot remove from parent when the session is not locked.";
                if (VaadinService.isOtherSessionLocked(parentSession)) {
                    message += " Furthermore, there is another locked session, indicating that the component might be about to be moved from one session to another.";
                }
                throw new IllegalStateException(message);
            }
        }

        HasComponents parent = content.getParent();
        if (parent instanceof ComponentContainer) {
            // If the component already has a parent, try to remove it
            ComponentContainer oldParent = (ComponentContainer) parent;
            oldParent.removeComponent(content);
        } else if (parent instanceof SingleComponentContainer) {
            SingleComponentContainer oldParent = (SingleComponentContainer) parent;
            if (oldParent.getContent() == content) {
                oldParent.setContent(null);
            }
        } else if (parent != null) {
            throw new IllegalArgumentException(
                    "Content is already attached to another parent");
        }
    }

    // the setHeight()/setWidth() methods duplicated and simplified from
    // AbstractComponentContainer

    @Override
    public void setWidth(float width, Unit unit) {
        /*
         * child tree repaints may be needed, due to our fall back support for
         * invalid relative sizes
         */
        boolean dirtyChild = false;
        boolean childrenMayBecomeUndefined = false;
        if (getWidth() == SIZE_UNDEFINED && width != SIZE_UNDEFINED) {
            // children currently in invalid state may need repaint
            dirtyChild = getInvalidSizedChild(false);
        } else if ((width == SIZE_UNDEFINED && getWidth() != SIZE_UNDEFINED)
                || (unit == Unit.PERCENTAGE
                        && getWidthUnits() != Unit.PERCENTAGE
                        && !ComponentSizeValidator
                                .parentCanDefineWidth(this))) {
            /*
             * relative width children may get to invalid state if width becomes
             * invalid. Width may also become invalid if units become percentage
             * due to the fallback support
             */
            childrenMayBecomeUndefined = true;
            dirtyChild = getInvalidSizedChild(false);
        }
        super.setWidth(width, unit);
        repaintChangedChildTree(dirtyChild, childrenMayBecomeUndefined, false);
    }

    private void repaintChangedChildTree(boolean invalidChild,
            boolean childrenMayBecomeUndefined, boolean vertical) {
        if (getContent() == null) {
            return;
        }
        boolean needRepaint = false;
        if (childrenMayBecomeUndefined) {
            // if became invalid now
            needRepaint = !invalidChild && getInvalidSizedChild(vertical);
        } else if (invalidChild) {
            // if not still invalid
            needRepaint = !getInvalidSizedChild(vertical);
        }
        if (needRepaint) {
            getContent().markAsDirtyRecursive();
        }
    }

    private boolean getInvalidSizedChild(final boolean vertical) {
        Component content = getContent();
        if (content == null) {
            return false;
        }
        if (vertical) {
            return !ComponentSizeValidator.checkHeights(content);
        } else {
            return !ComponentSizeValidator.checkWidths(content);
        }
    }

    @Override
    public void setHeight(float height, Unit unit) {
        /*
         * child tree repaints may be needed, due to our fall back support for
         * invalid relative sizes
         */
        boolean dirtyChild = false;
        boolean childrenMayBecomeUndefined = false;
        if (getHeight() == SIZE_UNDEFINED && height != SIZE_UNDEFINED) {
            // children currently in invalid state may need repaint
            dirtyChild = getInvalidSizedChild(true);
        } else if ((height == SIZE_UNDEFINED && getHeight() != SIZE_UNDEFINED)
                || (unit == Unit.PERCENTAGE
                        && getHeightUnits() != Unit.PERCENTAGE
                        && !ComponentSizeValidator
                                .parentCanDefineHeight(this))) {
            /*
             * relative height children may get to invalid state if height
             * becomes invalid. Height may also become invalid if units become
             * percentage due to the fallback support.
             */
            childrenMayBecomeUndefined = true;
            dirtyChild = getInvalidSizedChild(true);
        }
        super.setHeight(height, unit);
        repaintChangedChildTree(dirtyChild, childrenMayBecomeUndefined, true);
    }

    /*
     * (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);
        readDesignChildren(design.children(), designContext);
    }

    /**
     * Reads the content component from the list of child elements of a design.
     * The list must be empty or contain a single element; if the design
     * contains multiple child elements, a DesignException is thrown. This
     * method should be overridden by subclasses whose design may contain
     * non-content child elements.
     *
     * @since 7.5.0
     *
     * @param children
     *            the child elements of the design that is being read
     * @param context
     *            the DesignContext instance used to parse the design
     *
     * @throws DesignException
     *             if there are multiple child elements
     * @throws DesignException
     *             if a child element could not be parsed as a Component
     */
    protected void readDesignChildren(Elements children,
            DesignContext context) {
        if (children.size() > 1) {
            throw new DesignException("The container of type " + getClass()
                    + " can have only one child component.");
        } else if (children.size() == 1) {
            setContent(context.readDesign(children.first()));
        }
    }

    /*
     * (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 (also clears children and attributes)
        super.writeDesign(design, designContext);
        AbstractSingleComponentContainer def = designContext
                .getDefaultInstance(this);
        if (!designContext.shouldWriteChildren(this, def)) {
            return;
        }
        // handle child component
        Component child = getContent();
        if (child != null) {
            Element childNode = designContext.createElement(child);
            design.appendChild(childNode);
        }
    }

    @Override
    protected AbstractSingleComponentContainerState getState() {
        return (AbstractSingleComponentContainerState) super.getState();
    }

    @Override
    protected AbstractSingleComponentContainerState getState(
            boolean markAsDirty) {
        return (AbstractSingleComponentContainerState) super.getState(
                markAsDirty);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy