com.vaadin.ui.CustomLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.ui.CustomLayout.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.ui;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jsoup.nodes.Element;

import com.vaadin.server.JsonPaintTarget;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.shared.ui.customlayout.CustomLayoutState;
import com.vaadin.ui.declarative.DesignContext;

/**
 * <p>
 * A container component with freely designed layout and style. The layout
 * consists of items with textually represented locations. Each item contains
 * one sub-component, which can be any Vaadin component, such as a layout. The
 * adapter and theme are responsible for rendering the layout with a given style
 * by placing the items in the defined locations.
 * </p>
 *
 * <p>
 * The placement of the locations is not fixed - different themes can define the
 * locations in a way that is suitable for them. One typical example would be to
 * create visual design for a web site as a custom layout: the visual design
 * would define locations for "menu", "body", and "title", for example. The
 * layout would then be implemented as an HTML template for each theme.
 * </p>
 *
 * <p>
 * A location is identified with the attribute "data-location" or "location"
 * which has the location name as its value.
 * </p>
 *
 * <p>
 * The default theme handles the styles that are not defined by drawing the
 * subcomponents just as in OrderedLayout.
 * </p>
 *
 * @author Vaadin Ltd.
 * @since 3.0
 */
@SuppressWarnings("serial")
public class CustomLayout extends AbstractLayout implements LegacyComponent {

    private static final int BUFFER_SIZE = 10000;

    /**
     * Custom layout slots containing the components.
     */
    private final Map<String, Component> slots = new HashMap<>();

    /**
     * Default constructor only used by subclasses. Subclasses are responsible
     * for setting the appropriate fields. Either
     * {@link #setTemplateName(String)}, that makes layout fetch the template
     * from theme, or {@link #setTemplateContents(String)}.
     *
     * @since 7.5.0
     */
    public CustomLayout() {
        setWidth(100, Unit.PERCENTAGE);
    }

    /**
     * Constructs a custom layout with the template given in the stream.
     *
     * @param templateStream
     *            Stream containing template data. Must be using UTF-8 encoding.
     *            To use a String as a template use for instance new
     *            ByteArrayInputStream("&lt;template&gt;".getBytes()).
     * @throws IOException
     */
    public CustomLayout(InputStream templateStream) throws IOException {
        this();
        initTemplateContentsFromInputStream(templateStream);
    }

    /**
     * Constructor for custom layout with given template name. Template file is
     * fetched from "&lt;theme&gt;/layouts/&lt;templateName&gt;".
     */
    public CustomLayout(String template) {
        this();
        setTemplateName(template);
    }

    protected void initTemplateContentsFromInputStream(InputStream templateStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(templateStream, UTF_8));
        StringBuilder builder = new StringBuilder(BUFFER_SIZE);
        try {
            char[] cbuf = new char[BUFFER_SIZE];
            int nRead;
            while ((nRead = reader.read(cbuf, 0, BUFFER_SIZE)) > 0) {
                builder.append(cbuf, 0, nRead);
            }
        } finally {
            reader.close();
        }

        setTemplateContents(builder.toString());
    }

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

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

    /**
     * Adds the component into this container to given location. If the location
     * is already populated, the old component is removed.
     *
     * @param c
     *            the component to be added.
     * @param location
     *            the location of the component.
     */
    public void addComponent(Component c, String location) {
        final Component old = slots.get(location);
        if (old != null) {
            removeComponent(old);
        }
        slots.put(location, c);
        getState().childLocations.put(c, location);

        super.addComponent(c);
    }

    /**
     * Adds the component into this container. The component is added without
     * specifying the location (empty string is then used as location). Only one
     * component can be added to the default "" location and adding more
     * components into that location overwrites the old components.
     *
     * @param c
     *            the component to be added.
     */
    @Override
    public void addComponent(Component c) {
        this.addComponent(c, "");
    }

    /**
     * Removes the component from this container.
     *
     * @param c
     *            the component to be removed.
     */
    @Override
    public void removeComponent(Component c) {
        if (c == null) {
            return;
        }
        slots.values().remove(c);
        getState().childLocations.remove(c);
        super.removeComponent(c);
    }

    /**
     * Removes the component from this container from given location.
     *
     * @param location
     *            the Location identifier of the component.
     */
    public void removeComponent(String location) {
        this.removeComponent(slots.get(location));
    }

    /**
     * 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<Component> iterator() {
        return Collections.unmodifiableCollection(slots.values()).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 slots.values().size();
    }

    /**
     * Gets the child-component by its location.
     *
     * @param location
     *            the name of the location where the requested component
     *            resides.
     * @return the Component in the given location or null if not found.
     */
    public Component getComponent(String location) {
        return slots.get(location);
    }

    /* Documented in superclass */
    @Override
    public void replaceComponent(Component oldComponent, Component newComponent) {

        // Gets the locations
        String oldLocation = null;
        String newLocation = null;
        for (final String location : slots.keySet()) {
            final Component component = slots.get(location);
            if (component == oldComponent) {
                oldLocation = location;
            }
            if (component == newComponent) {
                newLocation = location;
            }
        }

        if (oldLocation == null) {
            addComponent(newComponent);
        } else if (newLocation == null) {
            removeComponent(oldLocation);
            addComponent(newComponent, oldLocation);
        } else {
            slots.put(newLocation, oldComponent);
            slots.put(oldLocation, newComponent);
            getState().childLocations.put(newComponent, oldLocation);
            getState().childLocations.put(oldComponent, newLocation);
        }
    }

    /** Get the name of the template. */
    public String getTemplateName() {
        return getState(false).templateName;
    }

    /** Get the contents of the template. */
    public String getTemplateContents() {
        return getState(false).templateContents;
    }

    /**
     * Set the name of the template used to draw custom layout.
     *
     * With GWT-adapter, the template with name 'templatename' is loaded from
     * VAADIN/themes/themename/layouts/templatename.html. If the theme has not
     * been set (with Application.setTheme()), themename is 'default'.
     *
     * @param templateName
     */
    public void setTemplateName(String templateName) {
        getState().templateName = templateName;
        getState().templateContents = null;
    }

    /**
     * Set the contents of the template used to draw the custom layout.
     *
     * Note: setTemplateContents can be applied only before CustomLayout
     * instance has been attached.
     *
     * @param templateContents
     */
    public void setTemplateContents(String templateContents) {
        getState().templateContents = templateContents;
        getState().templateName = null;
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables) {
        // Nothing to see here
    }

    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        // Workaround to make the CommunicationManager read the template file
        // and send it to the client
        String templateName = getState(false).templateName;
        if (templateName != null && !templateName.isEmpty()) {
            Set<Object> usedResources = ((JsonPaintTarget) target).getUsedResources();
            String resourceName = "layouts/" + templateName + ".html";
            usedResources.add(resourceName);
        }
    }

    @Override
    public void readDesign(Element design, DesignContext designContext) {
        super.readDesign(design, designContext);

        for (Element child : design.children()) {

            Component childComponent = designContext.readDesign(child);

            if (child.hasAttr(":location")) {
                addComponent(childComponent, child.attr(":location"));
            } else {
                addComponent(childComponent);
            }
        }
    }

    @Override
    public void writeDesign(Element design, DesignContext designContext) {
        super.writeDesign(design, designContext);

        for (Entry<String, Component> slot : slots.entrySet()) {
            Element child = designContext.createElement(slot.getValue());
            if (slots.size() > 1 || !"".equals(slot.getKey())) {
                child.attr(":location", slot.getKey());
            }
            design.appendChild(child);
        }
    }
}