/*
* Copyright 2000-2016 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 java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Iterator;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.parser.Tag;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.popupview.PopupViewServerRpc;
import com.vaadin.shared.ui.popupview.PopupViewState;
import com.vaadin.ui.declarative.DesignContext;
/**
*
* A component for displaying a two different views to data. The minimized view
* is normally used to render the component, and when it is clicked the full
* view is displayed on a popup. The inner class {@link PopupView.Content} is
* used to deliver contents to this component.
*
* @author Vaadin Ltd.
*/
@SuppressWarnings("serial")
public class PopupView extends AbstractComponent implements HasComponents {
private Content content;
private Component visibleComponent;
private static final Method POPUP_VISIBILITY_METHOD;
static {
try {
POPUP_VISIBILITY_METHOD = PopupVisibilityListener.class
.getDeclaredMethod("popupVisibilityChange",
PopupVisibilityEvent.class);
} catch (final java.lang.NoSuchMethodException e) {
// This should never happen
throw new java.lang.RuntimeException(
"Internal error finding methods in PopupView");
}
}
private final PopupViewServerRpc rpc = this::setPopupVisible;
/* Constructors */
/**
* This is an internal constructor. Use
* {@link PopupView#PopupView(String, Component)} instead.
*
* @since 7.5.0
*/
@Deprecated
public PopupView() {
registerRpc(rpc);
setHideOnMouseOut(true);
setContent(createContent("", new Label("")));
}
/**
* A simple way to create a PopupPanel. Note that the minimal representation
* may not be dynamically updated, in order to achieve this create your own
* Content object and use {@link PopupView#PopupView(Content)}.
*
* @param small
* the minimal textual representation as HTML
* @param large
* the full, Component-type representation
*/
public PopupView(final java.lang.String small, final Component large) {
this(createContent(small, large));
}
/**
* Creates a PopupView through the PopupView.Content interface. This allows
* the creator to dynamically change the contents of the PopupView.
*
* @param content
* the PopupView.Content that contains the information for this
*/
public PopupView(PopupView.Content content) {
this();
setContent(content);
}
/**
* Creates a Content from given text representation and popup content.
*
* @since 7.5.0
*
* @param minimizedValue
* text representation when popup is hidden
* @param popupContent
* popup content
* @return content with given data
*/
protected static Content createContent(final String minimizedValue,
final Component popupContent) {
return new Content() {
@Override
public String getMinimizedValueAsHTML() {
return minimizedValue;
}
@Override
public Component getPopupComponent() {
return popupContent;
}
};
}
/**
* This method will replace the current content of the panel with a new one.
*
* @param newContent
* PopupView.Content object containing new information for the
* PopupView
* @throws IllegalArgumentException
* if the method is passed a null value, or if one of the
* content methods returns null
*/
public void setContent(PopupView.Content newContent)
throws IllegalArgumentException {
if (newContent == null) {
throw new IllegalArgumentException("Content must not be null");
}
content = newContent;
markAsDirty();
}
/**
* Returns the content-package for this PopupView.
*
* @return the PopupView.Content for this object or null
*/
public PopupView.Content getContent() {
return content;
}
/**
* Set the visibility of the popup. Does not hide the minimal
* representation.
*
* @param visible
*/
public void setPopupVisible(boolean visible) {
if (isPopupVisible() != visible) {
if (visible) {
visibleComponent = content.getPopupComponent();
if (visibleComponent == null) {
throw new java.lang.IllegalStateException(
"PopupView.Content did not return Component to set visible");
}
if (visibleComponent.getParent() != null) {
// If the component already has a parent, try to remove it
AbstractSingleComponentContainer
.removeFromParent(visibleComponent);
}
visibleComponent.setParent(this);
} else {
if (equals(visibleComponent.getParent())) {
visibleComponent.setParent(null);
}
visibleComponent = null;
}
fireEvent(new PopupVisibilityEvent(this));
markAsDirty();
}
}
@Override
public void beforeClientResponse(boolean initial) {
super.beforeClientResponse(initial);
String html = content.getMinimizedValueAsHTML();
if (html == null) {
html = "";
}
getState().html = html;
}
/**
* Return whether the popup is visible.
*
* @return true if the popup is showing
*/
public boolean isPopupVisible() {
return visibleComponent != null;
}
/**
* Check if this popup will be hidden when the user takes the mouse cursor
* out of the popup area.
*
* @return true if the popup is hidden on mouse out, false otherwise
*/
public boolean isHideOnMouseOut() {
return getState(false).hideOnMouseOut;
}
/**
* Should the popup automatically hide when the user takes the mouse cursor
* out of the popup area? If this is false, the user must click outside the
* popup to close it. The default is true.
*
* @param hideOnMouseOut
*
*/
public void setHideOnMouseOut(boolean hideOnMouseOut) {
getState().hideOnMouseOut = hideOnMouseOut;
}
/*
* Methods inherited from AbstractComponentContainer. These are unnecessary
* (but mandatory). Most of them are not supported in this implementation.
*/
/**
* This class only contains other components when the popup is showing.
*
* @see com.vaadin.ui.ComponentContainer#getComponentIterator()
*/
@Override
public Iterator<Component> iterator() {
if (visibleComponent != null) {
return Collections.singletonList(visibleComponent).iterator();
} else {
return Collections.<Component> emptyList().iterator();
}
}
/**
* Gets the number of contained components.
*
* @return the number of contained components (zero or one)
*/
public int getComponentCount() {
return (visibleComponent != null ? 1 : 0);
}
@Override
public void readDesign(Element design, DesignContext designContext) {
// Read content first to avoid NPE when setting popup visible
Component popupContent = null;
String minimizedValue = "";
for (Node childNode : design.childNodes()) {
if (childNode instanceof Element) {
Element child = (Element) childNode;
if (child.tagName().equals("popup-content")) {
popupContent = designContext.readDesign(child.child(0));
} else {
minimizedValue += child.toString();
}
} else {
minimizedValue += childNode.toString();
}
}
setContent(createContent(minimizedValue.trim(), popupContent));
super.readDesign(design, designContext);
}
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
Element popupContent = new Element(Tag.valueOf("popup-content"), "");
popupContent.appendChild(
designContext.createElement(content.getPopupComponent()));
String minimizedHTML = content.getMinimizedValueAsHTML();
if (minimizedHTML != null && !minimizedHTML.isEmpty()) {
design.append(minimizedHTML);
}
design.appendChild(popupContent);
}
@Override
protected PopupViewState getState() {
return (PopupViewState) super.getState();
}
@Override
protected PopupViewState getState(boolean markAsDirty) {
return (PopupViewState) super.getState(markAsDirty);
}
/**
* Used to deliver customized content-packages to the PopupView. These are
* dynamically loaded when they are redrawn. The user must take care that
* neither of these methods ever return null.
*/
public interface Content extends Serializable {
/**
* This should return a small view of the full data.
*
* @return value in HTML format
*/
public String getMinimizedValueAsHTML();
/**
* This should return the full Component representing the data
*
* @return a Component for the value
*/
public Component getPopupComponent();
}
/**
* Add a listener that is called whenever the visibility of the popup is
* changed.
*
* @see PopupVisibilityListener
* @see PopupVisibilityEvent
*
* @param listener
* the listener to add, not null
* @return a registration object for removing the listener
* @since 8.0
*/
public Registration addPopupVisibilityListener(
PopupVisibilityListener listener) {
return addListener(PopupVisibilityEvent.class, listener,
POPUP_VISIBILITY_METHOD);
}
/**
* Removes a previously added listener, so that it no longer receives events
* when the visibility of the popup changes.
*
* @param listener
* the listener to remove
* @see PopupVisibilityListener
* @see #addPopupVisibilityListener(PopupVisibilityListener)
*
* @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
* registration object returned from
* {@link #addPopupVisibilityListener(PopupVisibilityListener)}.
*/
@Deprecated
public void removePopupVisibilityListener(
PopupVisibilityListener listener) {
removeListener(PopupVisibilityEvent.class, listener,
POPUP_VISIBILITY_METHOD);
}
/**
* This event is received by the PopupVisibilityListeners when the
* visibility of the popup changes. You can get the new visibility directly
* with {@link #isPopupVisible()}, or get the PopupView that produced the
* event with {@link #getPopupView()}.
*
*/
public static class PopupVisibilityEvent extends Event {
public PopupVisibilityEvent(PopupView source) {
super(source);
}
/**
* Get the PopupView instance that is the source of this event.
*
* @return the source PopupView
*/
public PopupView getPopupView() {
return (PopupView) getSource();
}
/**
* Returns the current visibility of the popup.
*
* @return true if the popup is visible
*/
public boolean isPopupVisible() {
return getPopupView().isPopupVisible();
}
}
/**
* Defines a listener that can receive a PopupVisibilityEvent when the
* visibility of the popup changes.
*
*/
@FunctionalInterface
public interface PopupVisibilityListener extends Serializable {
/**
* Pass to {@link PopupView.PopupVisibilityEvent} to start listening for
* popup visibility changes.
*
* @param event
* the event
*
* @see PopupVisibilityEvent
* @see PopupView#addPopupVisibilityListener(PopupVisibilityListener)
*/
public void popupVisibilityChange(PopupVisibilityEvent event);
}
}