com.vaadin.client.ui.orderedlayout.Slot.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.client.ui.orderedlayout.Slot.java

Source

/*
 * 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.client.ui.orderedlayout;

import java.util.List;
import java.util.Locale;

import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.StyleConstants;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.WidgetUtil.ErrorUtil;
import com.vaadin.client.ui.FontIcon;
import com.vaadin.client.ui.HasErrorIndicatorElement;
import com.vaadin.client.ui.Icon;
import com.vaadin.client.ui.ImageIcon;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.shared.ui.AlignmentInfo;
import com.vaadin.shared.ui.ErrorLevel;

/**
 * Represents a slot which contains the actual widget in the layout.
 */
public class Slot extends SimplePanel implements HasErrorIndicatorElement {

    private static final String ALIGN_CLASS_PREFIX = "v-align-";

    // this must be set at construction time and not changed afterwards
    private VAbstractOrderedLayout layout;

    public static final String SLOT_CLASSNAME = "v-slot";

    private Element spacer;
    private Element captionWrap;
    private Element caption;
    private Element captionText;
    private Icon icon;
    private Element errorIcon;
    private Element requiredIcon;

    private ElementResizeListener captionResizeListener;

    private ElementResizeListener widgetResizeListener;

    private ElementResizeListener spacingResizeListener;

    // Caption is placed after component unless there is some part which
    // moves it above.
    private CaptionPosition captionPosition = CaptionPosition.RIGHT;

    private AlignmentInfo alignment;

    private double expandRatio = -1;

    /**
     * Constructs a slot.
     *
     * When using this constructor, the layout and widget must be set before any
     * other operations are performed on the slot.
     *
     * @since 7.6
     */
    public Slot() {
        setStyleName(SLOT_CLASSNAME);
    }

    /**
     * Set the layout in which this slot is. This method must be called exactly
     * once at slot construction time when using the default constructor.
     *
     * The method should normally only be called by
     * {@link VAbstractOrderedLayout#createSlot(Widget)}.
     *
     * @since 7.6
     * @param layout
     *            the layout containing the slot
     */
    public void setLayout(VAbstractOrderedLayout layout) {
        this.layout = layout;
    }

    /**
     * Constructs a slot.
     *
     * @param layout
     *            The layout to which this slot belongs
     * @param widget
     *            The widget to put in the slot
     * @deprecated use {@link GWT#create(Class)}, {@link #setWidget(Widget)} and
     *             {@link #setLayout(VAbstractOrderedLayout)} instead
     */
    @Deprecated
    public Slot(VAbstractOrderedLayout layout, Widget widget) {
        setLayout(layout);
        setStyleName(SLOT_CLASSNAME);
        setWidget(widget);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user
     * .client.ui.Widget)
     */
    @Override
    public boolean remove(Widget w) {
        detachListeners();
        return super.remove(w);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt
     * .user.client.ui.Widget)
     */
    @Override
    public void setWidget(Widget w) {
        detachListeners();
        super.setWidget(w);
        attachListeners();
    }

    /**
     * Attaches resize listeners to the widget, caption and spacing elements
     */
    private void attachListeners() {
        if (getWidget() != null && layout.getLayoutManager() != null) {
            LayoutManager lm = layout.getLayoutManager();
            if (getCaptionElement() != null && captionResizeListener != null) {
                lm.addElementResizeListener(getCaptionElement(), captionResizeListener);
            }
            if (widgetResizeListener != null) {
                lm.addElementResizeListener(getWidget().getElement(), widgetResizeListener);
            }
            if (getSpacingElement() != null && spacingResizeListener != null) {
                lm.addElementResizeListener(getSpacingElement(), spacingResizeListener);
            }

        }
    }

    /**
     * Detaches resize listeners from the widget, caption and spacing elements
     */
    private void detachListeners() {
        if (getWidget() != null && layout.getLayoutManager() != null) {
            LayoutManager lm = layout.getLayoutManager();
            if (getCaptionElement() != null && captionResizeListener != null) {
                lm.removeElementResizeListener(getCaptionElement(), captionResizeListener);
            }
            if (widgetResizeListener != null) {
                lm.removeElementResizeListener(getWidget().getElement(), widgetResizeListener);
            }
            // in many cases, the listener has already been removed by
            // setSpacing(false)
            if (getSpacingElement() != null && spacingResizeListener != null) {
                lm.removeElementResizeListener(getSpacingElement(), spacingResizeListener);
            }

        }
    }

    public ElementResizeListener getCaptionResizeListener() {
        return captionResizeListener;
    }

    public void setCaptionResizeListener(ElementResizeListener captionResizeListener) {
        detachListeners();
        this.captionResizeListener = captionResizeListener;
        attachListeners();
    }

    public ElementResizeListener getWidgetResizeListener() {
        return widgetResizeListener;
    }

    public void setWidgetResizeListener(ElementResizeListener widgetResizeListener) {
        detachListeners();
        this.widgetResizeListener = widgetResizeListener;
        attachListeners();
    }

    public ElementResizeListener getSpacingResizeListener() {
        return spacingResizeListener;
    }

    public void setSpacingResizeListener(ElementResizeListener spacingResizeListener) {
        detachListeners();
        this.spacingResizeListener = spacingResizeListener;
        attachListeners();
    }

    /**
     * Returns the alignment for the slot.
     *
     */
    public AlignmentInfo getAlignment() {
        return alignment;
    }

    /**
     * Sets the style names for the slot containing the widget.
     *
     * @param stylenames
     *            The style names for the slot
     */
    protected void setStyleNames(String... stylenames) {
        setStyleName(SLOT_CLASSNAME);
        if (stylenames != null) {
            for (String stylename : stylenames) {
                addStyleDependentName(stylename);
            }
        }

        // Ensure alignment style names are correct
        setAlignment(alignment);
    }

    /**
     * Sets how the widget is aligned inside the slot.
     *
     * @param alignment
     *            The alignment inside the slot
     */
    public void setAlignment(AlignmentInfo alignment) {
        this.alignment = alignment;

        if (alignment != null && alignment.isHorizontalCenter()) {
            addStyleName(ALIGN_CLASS_PREFIX + "center");
            removeStyleName(ALIGN_CLASS_PREFIX + "right");
        } else if (alignment != null && alignment.isRight()) {
            addStyleName(ALIGN_CLASS_PREFIX + "right");
            removeStyleName(ALIGN_CLASS_PREFIX + "center");
        } else {
            removeStyleName(ALIGN_CLASS_PREFIX + "right");
            removeStyleName(ALIGN_CLASS_PREFIX + "center");
        }

        if (alignment != null && alignment.isVerticalCenter()) {
            addStyleName(ALIGN_CLASS_PREFIX + "middle");
            removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
        } else if (alignment != null && alignment.isBottom()) {
            addStyleName(ALIGN_CLASS_PREFIX + "bottom");
            removeStyleName(ALIGN_CLASS_PREFIX + "middle");
        } else {
            removeStyleName(ALIGN_CLASS_PREFIX + "middle");
            removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
        }
    }

    /**
     * Set how the slot should be expanded relative to the other slots. 0 means
     * that the slot should not participate in the division of space based on
     * the expand ratios but instead be allocated space based on its natural
     * size. Other values causes the slot to get a share of the otherwise
     * unallocated space in proportion to the slot's expand ratio value.
     *
     * @param expandRatio
     *            The ratio of the space the slot should occupy
     *
     */
    public void setExpandRatio(double expandRatio) {
        this.expandRatio = expandRatio;
    }

    /**
     * Get the expand ratio for the slot. The expand ratio describes how the
     * slot should be resized compared to other slots in the layout
     *
     * @return the expand ratio of the slot
     *
     * @see #setExpandRatio(double)
     */
    public double getExpandRatio() {
        return expandRatio;
    }

    /**
     * Set the spacing for the slot. The spacing determines if there should be
     * empty space around the slot when the slot.
     *
     * @param spacing
     *            Should spacing be enabled
     */
    public void setSpacing(boolean spacing) {
        if (spacing && spacer == null) {
            spacer = DOM.createDiv();
            spacer.addClassName("v-spacing");

            /*
             * This has to be done here for the initial render. In other cases
             * where the spacer already exists onAttach will handle it.
             */
            getElement().getParentElement().insertBefore(spacer, getElement());
        } else if (!spacing && spacer != null) {
            // Remove listener before spacer to avoid memory leak
            LayoutManager lm = layout.getLayoutManager();
            if (lm != null && spacingResizeListener != null) {
                lm.removeElementResizeListener(spacer, spacingResizeListener);
            }

            spacer.removeFromParent();
            spacer = null;
        }
    }

    /**
     * Get the element which is added to make the spacing.
     *
     * @return
     */
    public com.google.gwt.user.client.Element getSpacingElement() {
        return DOM.asOld(spacer);
    }

    /**
     * Does the slot have spacing.
     */
    public boolean hasSpacing() {
        return getSpacingElement() != null;
    }

    /**
     * Get the vertical amount in pixels of the spacing.
     */
    protected int getVerticalSpacing() {
        if (spacer == null) {
            return 0;
        } else if (layout.getLayoutManager() != null) {
            return layout.getLayoutManager().getOuterHeight(spacer);
        }
        return spacer.getOffsetHeight();
    }

    /**
     * Get the horizontal amount of pixels of the spacing.
     *
     * @return
     */
    protected int getHorizontalSpacing() {
        if (spacer == null) {
            return 0;
        } else if (layout.getLayoutManager() != null) {
            return layout.getLayoutManager().getOuterWidth(spacer);
        }
        return spacer.getOffsetWidth();
    }

    /**
     * Set the position of the caption relative to the slot.
     *
     * @param captionPosition
     *            The position of the caption
     */
    public void setCaptionPosition(CaptionPosition captionPosition) {
        if (caption == null) {
            return;
        }
        captionWrap.removeClassName("v-caption-on-" + this.captionPosition.name().toLowerCase(Locale.ROOT));

        this.captionPosition = captionPosition;
        if (captionPosition == CaptionPosition.BOTTOM || captionPosition == CaptionPosition.RIGHT) {
            captionWrap.appendChild(caption);
        } else {
            captionWrap.insertFirst(caption);
        }

        captionWrap.addClassName("v-caption-on-" + captionPosition.name().toLowerCase(Locale.ROOT));
    }

    /**
     * Get the position of the caption relative to the slot.
     */
    public CaptionPosition getCaptionPosition() {
        return captionPosition;
    }

    /**
     * Set the caption of the slot.
     *
     * @param captionText
     *            The text of the caption
     * @param iconUrl
     *            The icon URL, must already be run trough translateVaadinUri()
     * @param styles
     *            The style names
     * @param error
     *            The error message
     * @param showError
     *            Should the error message be shown
     * @param required
     *            Is the (field) required
     * @param enabled
     *            Is the component enabled
     *
     * @deprecated Use
     *             {@link #setCaption(String, Icon, List, String, boolean, boolean, boolean)}
     *             instead
     */
    @Deprecated
    public void setCaption(String captionText, String iconUrl, List<String> styles, String error, boolean showError,
            boolean required, boolean enabled) {
        Icon icon;
        if (FontIcon.isFontIconUri(iconUrl)) {
            icon = GWT.create(FontIcon.class);
        } else {
            icon = GWT.create(ImageIcon.class);
        }
        icon.setUri(iconUrl);

        setCaption(captionText, icon, styles, error, showError, required, enabled);
    }

    /**
     * Set the caption of the slot as text.
     *
     * @param captionText
     *            The text of the caption
     * @param icon
     *            The icon
     * @param styles
     *            The style names
     * @param error
     *            The error message
     * @param showError
     *            Should the error message be shown
     * @param required
     *            Is the (field) required
     * @param enabled
     *            Is the component enabled
     */
    public void setCaption(String captionText, Icon icon, List<String> styles, String error, boolean showError,
            boolean required, boolean enabled) {
        setCaption(captionText, icon, styles, error, showError, required, enabled, false);
    }

    /**
     * Set the caption of the slot.
     *
     * @param captionText
     *            The text of the caption
     * @param icon
     *            The icon
     * @param styles
     *            The style names
     * @param error
     *            The error message
     * @param showError
     *            Should the error message be shown
     * @param required
     *            Is the (field) required
     * @param enabled
     *            Is the component enabled
     * @param captionAsHtml
     *            true if the caption should be rendered as HTML, false
     *            otherwise
     */
    public void setCaption(String captionText, Icon icon, List<String> styles, String error, boolean showError,
            boolean required, boolean enabled, boolean captionAsHtml) {
        setCaption(captionText, icon, styles, error, null, showError, required, enabled, captionAsHtml);
    }

    /**
     * Set the caption of the slot.
     *
     * @param captionText
     *            The text of the caption
     * @param icon
     *            The icon
     * @param styles
     *            The style names
     * @param error
     *            The error message
     * @param errorLevel
     *            The error level
     * @param showError
     *            Should the error message be shown
     * @param required
     *            Is the (field) required
     * @param enabled
     *            Is the component enabled
     * @param captionAsHtml
     *            true if the caption should be rendered as HTML, false
     *            otherwise
     * @since 8.2
     */
    public void setCaption(String captionText, Icon icon, List<String> styles, String error, ErrorLevel errorLevel,
            boolean showError, boolean required, boolean enabled, boolean captionAsHtml) {

        // TODO place for optimization: check if any of these have changed
        // since last time, and only run those changes

        // Caption wrappers
        Widget widget = getWidget();
        final Element focusedElement = WidgetUtil.getFocusedElement();
        // By default focus will not be lost
        boolean focusLost = false;
        if (captionText != null || icon != null || error != null || required) {
            if (caption == null) {
                caption = DOM.createDiv();
                captionWrap = DOM.createDiv();
                captionWrap.addClassName(StyleConstants.UI_WIDGET);
                captionWrap.addClassName("v-has-caption");
                getElement().appendChild(captionWrap);
                orphan(widget);
                captionWrap.appendChild(widget.getElement());
                adopt(widget);

                // Made changes to DOM. Focus can be lost if it was in the
                // widget.
                focusLost = (focusedElement == null ? false : widget.getElement().isOrHasChild(focusedElement));
            }
        } else if (caption != null) {
            orphan(widget);
            getElement().appendChild(widget.getElement());
            adopt(widget);
            captionWrap.removeFromParent();
            caption = null;
            captionWrap = null;

            // Made changes to DOM. Focus can be lost if it was in the widget.
            focusLost = (focusedElement == null ? false : widget.getElement().isOrHasChild(focusedElement));
        }

        // Caption text
        if (captionText != null) {
            if (this.captionText == null) {
                this.captionText = DOM.createSpan();
                this.captionText.addClassName("v-captiontext");
                caption.appendChild(this.captionText);
            }
            if (captionText.trim().isEmpty()) {
                this.captionText.setInnerHTML("&nbsp;");
            } else {
                if (captionAsHtml) {
                    this.captionText.setInnerHTML(captionText);
                } else {
                    this.captionText.setInnerText(captionText);
                }
            }
        } else if (this.captionText != null) {
            this.captionText.removeFromParent();
            this.captionText = null;
        }

        // Icon
        if (this.icon != null) {
            this.icon.getElement().removeFromParent();
        }
        if (icon != null) {
            caption.insertFirst(icon.getElement());
        }
        this.icon = icon;

        // Required
        if (required) {
            if (requiredIcon == null) {
                requiredIcon = DOM.createSpan();
                // TODO decide something better (e.g. use CSS to insert the
                // character)
                requiredIcon.setInnerHTML("*");
                requiredIcon.setClassName("v-required-field-indicator");

                // The star should not be read by the screen reader, as it is
                // purely visual. Required state is set at the element level for
                // the screen reader.
                Roles.getTextboxRole().setAriaHiddenState(requiredIcon, true);
            }
            caption.appendChild(requiredIcon);
        } else if (requiredIcon != null) {
            requiredIcon.removeFromParent();
            requiredIcon = null;
        }

        // Error
        if (error != null && showError) {
            setErrorIndicatorElementVisible(true);
            ErrorUtil.setErrorLevelStyle(getErrorIndicatorElement(), StyleConstants.STYLE_NAME_ERROR_INDICATOR,
                    errorLevel);
        } else {
            setErrorIndicatorElementVisible(false);
        }

        if (caption != null) {
            // Styles
            caption.setClassName("v-caption");

            if (styles != null) {
                for (String style : styles) {
                    caption.addClassName("v-caption-" + style);
                }
            }

            if (enabled) {
                caption.removeClassName("v-disabled");
            } else {
                caption.addClassName("v-disabled");
            }

            // Caption position
            if (captionText != null || icon != null) {
                setCaptionPosition(CaptionPosition.TOP);
            } else {
                setCaptionPosition(CaptionPosition.RIGHT);
            }
        }

        if (focusLost) {
            // Find out what element is currently focused.
            Element currentFocus = WidgetUtil.getFocusedElement();
            if (currentFocus != null && currentFocus.equals(Document.get().getBody())) {
                // Focus has moved to BodyElement and should be moved back to
                // original location. This happened because of adding or
                // removing the captionWrap
                focusedElement.focus();
            } else if (currentFocus != focusedElement) {
                // Focus is either moved somewhere else on purpose or IE has
                // lost it. Investigate further.
                Timer focusTimer = new Timer() {

                    @Override
                    public void run() {
                        if (WidgetUtil.getFocusedElement() == null) {
                            // This should never become an infinite loop and
                            // even if it does it will be stopped once something
                            // is done with the browser.
                            schedule(25);
                        } else if (WidgetUtil.getFocusedElement().equals(Document.get().getBody())) {
                            // Focus found it's way to BodyElement. Now it can
                            // be restored
                            focusedElement.focus();
                        }
                    }
                };
                // Newer IE versions can handle things immediately.
                focusTimer.run();
            }
        }
    }

    /**
     * Does the slot have a caption.
     */
    public boolean hasCaption() {
        return caption != null;
    }

    /**
     * Get the slots caption element.
     */
    public com.google.gwt.user.client.Element getCaptionElement() {
        return DOM.asOld(caption);
    }

    private boolean relativeWidth = false;

    /**
     * Set if the slot has a relative width.
     *
     * @param relativeWidth
     *            True if slot uses relative width, false if the slot has a
     *            static width
     */
    public void setRelativeWidth(boolean relativeWidth) {
        this.relativeWidth = relativeWidth;
        updateRelativeSize(relativeWidth, "width");
    }

    public boolean hasRelativeWidth() {
        return relativeWidth;
    }

    private boolean relativeHeight = false;

    /**
     * Set if the slot has a relative height.
     *
     * @param relativeHeight
     *            True if the slot uses a relative height, false if the slot has
     *            a static height
     */
    public void setRelativeHeight(boolean relativeHeight) {
        this.relativeHeight = relativeHeight;
        updateRelativeSize(relativeHeight, "height");
    }

    public boolean hasRelativeHeight() {
        return relativeHeight;
    }

    /**
     * Updates the captions size if the slot is relative
     *
     * @param isRelativeSize
     *            Is the slot relatively sized
     * @param direction
     *            The direction of the caption
     */
    private void updateRelativeSize(boolean isRelativeSize, String direction) {
        if (isRelativeSize && hasCaption()) {
            captionWrap.getStyle().setProperty(direction,
                    getWidget().getElement().getStyle().getProperty(direction));
            captionWrap.addClassName("v-has-" + direction);
        } else if (hasCaption()) {
            if (direction.equals("height")) {
                captionWrap.getStyle().clearHeight();
            } else {
                captionWrap.getStyle().clearWidth();
            }
            captionWrap.removeClassName("v-has-" + direction);
            captionWrap.getStyle().clearPaddingTop();
            captionWrap.getStyle().clearPaddingRight();
            captionWrap.getStyle().clearPaddingBottom();
            captionWrap.getStyle().clearPaddingLeft();
            caption.getStyle().clearMarginTop();
            caption.getStyle().clearMarginRight();
            caption.getStyle().clearMarginBottom();
            caption.getStyle().clearMarginLeft();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
     * .user.client.Event)
     */
    @Override
    public void onBrowserEvent(Event event) {
        super.onBrowserEvent(event);
        if (DOM.eventGetType(event) == Event.ONLOAD && icon != null
                && icon.getElement() == DOM.eventGetTarget(event)) {
            if (layout.getLayoutManager() != null) {
                layout.getLayoutManager().layoutLater();
            } else {
                layout.updateCaptionOffset(caption);
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement()
     */
    @Override
    protected com.google.gwt.user.client.Element getContainerElement() {
        if (captionWrap == null) {
            return getElement();
        } else {
            return DOM.asOld(captionWrap);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.google.gwt.user.client.ui.Widget#onDetach()
     */
    @Override
    protected void onDetach() {
        if (spacer != null) {
            spacer.removeFromParent();
        }
        super.onDetach();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.google.gwt.user.client.ui.Widget#onAttach()
     */
    @Override
    protected void onAttach() {
        super.onAttach();
        if (spacer != null) {
            getElement().getParentElement().insertBefore(spacer, getElement());
        }
    }

    public boolean isRelativeInDirection(boolean vertical) {
        if (vertical) {
            return hasRelativeHeight();
        } else {
            return hasRelativeWidth();
        }
    }

    @Override
    public Element getErrorIndicatorElement() {
        return errorIcon;
    }

    @Override
    public void setErrorIndicatorElementVisible(boolean visible) {
        if (visible) {
            if (errorIcon == null) {
                errorIcon = ErrorUtil.createErrorIndicatorElement();
            }
            caption.appendChild(errorIcon);
        } else if (errorIcon != null) {
            errorIcon.removeFromParent();
            errorIcon = null;
        }
    }
}