com.vaadin.flow.component.notification.Notification Maven / Gradle / Ivy
/*
* Copyright 2000-2017 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.flow.component.notification;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.component.DetachEvent;
import com.vaadin.flow.component.HasComponents;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.internal.HtmlUtils;
import com.vaadin.flow.shared.Registration;
/**
* Server-side component for the vaadin-notification
element.
*
* @author Vaadin Ltd
*/
@HtmlImport("frontend://flow-component-renderer.html")
public class Notification extends GeneratedVaadinNotification
implements HasComponents {
private static final int DEFAULT_DURATION = 5000;
private static final Position DEFAULT_POSITION = Position.BOTTOM_START;
private final Element container = new Element("div", false);
private final Element templateElement = new Element("template", false);
private boolean autoAddedToTheUi = false;
/**
* Enumeration of all available positions for notification component
*/
public enum Position {
TOP_STRETCH, TOP_START, TOP_CENTER, TOP_END, MIDDLE, BOTTOM_START, BOTTOM_CENTER, BOTTOM_END, BOTTOM_STRETCH,;
private final String clientName;
Position() {
this.clientName = name().toLowerCase(Locale.ENGLISH).replace('_',
'-');
}
/**
* Gets name that is used in the client side representation of the
* component.
*
* @return the name used in the client side representation of the
* component.
*/
public String getClientName() {
return clientName;
}
/**
* Creates {@link Position} from the client side representation property
* name
*
* @param clientName
* the client side representation of the property
* @return corresponding {@link Position}
*/
static Position fromClientName(String clientName) {
return clientName == null ? null
: Position.valueOf(clientName.replace('-', '_')
.toUpperCase(Locale.ENGLISH));
}
}
/**
* Default constructor. Create an empty notification with component support
* and non-auto-closing
*
* Note: To mix text and child components in notification that also supports
* child components, use the {@link Text} component for the textual parts.
*/
public Notification() {
initBaseElementsAndListeners();
getElement().getNode()
.runWhenAttached(ui -> ui.beforeClientResponse(this,
context -> attachComponentTemplate(ui)));
setPosition(DEFAULT_POSITION);
setDuration(0);
}
/**
* Creates a Notification with the given String rendered as its HTML text,
* that does not close automatically.
*
* @param text
* the text of the Notification
*/
public Notification(String text) {
this(text, 0, DEFAULT_POSITION);
}
/**
* Creates a Notification with given String rendered as its HTML text and
* given Integer rendered as its duration.
*
* Set to {@code 0} or a negative number to disable the notification
* auto-closing.
*
* @param text
* the text of the Notification
* @param duration
* the duration in milliseconds to show the notification
*/
public Notification(String text, int duration) {
this(text, duration, DEFAULT_POSITION);
}
/**
* Creates a Notification with given text String, duration and position
*
* Set to {@code 0} or a negative number to disable the notification
* auto-closing.
*
* @param text
* the text of the notification
* @param duration
* the duration in milliseconds to show the notification
* @param position
* the position of the notification. Valid enumerate values are
* TOP_STRETCH, TOP_START, TOP_CENTER, TOP_END, MIDDLE,
* BOTTOM_START, BOTTOM_CENTER, BOTTOM_END, BOTTOM_STRETCH
*/
public Notification(String text, int duration, Position position) {
initBaseElementsAndListeners();
setText(text);
setDuration((double) duration);
setPosition(position);
}
/**
* Creates a notification with given components inside.
*
* Note: To mix text and child components in a component that also supports
* child components, use the {@link Text} component for the textual parts.
*
* @param components
* the components inside the notification
* @see #add(Component...)
*/
public Notification(Component... components) {
this();
add(components);
}
private void initBaseElementsAndListeners() {
getElement().appendChild(templateElement);
getElement().appendVirtualChild(container);
getElement().addEventListener("opened-changed", event -> {
if (autoAddedToTheUi && !isOpened()) {
getElement().removeFromParent();
autoAddedToTheUi = false;
}
});
}
/**
* Shows a notification in the current page with given text, duration and
* position.
*
* @param text
* the text of the Notification
* @param duration
* the duration in milliseconds to show the notification
* @param position
* the position of the notification. Valid enumerate values are
* TOP_STRETCH, TOP_START, TOP_CENTER, TOP_END, MIDDLE,
* BOTTOM_START, BOTTOM_CENTER, BOTTOM_END, BOTTOM_STRETCH
* @return the notification
*/
public static Notification show(String text, int duration,
Position position) {
Notification notification = new Notification(text, duration, position);
notification.open();
return notification;
}
/**
* Shows a notification in the current page with given text.
*
* This is the convenience method for {@link #show(String, int, Position)}
* which uses default web-component values for duration (which is 5000 ms)
* and position ({@literal Position.BOTTOM_START}).
*
*
* @param text
* the text of the Notification
* @return the notification
*/
public static Notification show(String text) {
return show(text, DEFAULT_DURATION, DEFAULT_POSITION);
}
/**
* Set the text of the notification with given String
*
* NOTE: When mixing this method with {@link #Notification()} and
* {@link #Notification(Component...)}. Method will remove all the
* components from the notification.
*
* @param text
* the text of the Notification
*/
public void setText(String text) {
removeAll();
getElement().getNode().runWhenAttached(
ui -> ui.beforeClientResponse(this, context -> templateElement
.setProperty("innerHTML", HtmlUtils.escape(text))));
}
/**
* Set position of the notification.
*
*
* @param position
* the position of the notification. Valid enumerate values are
* {@code TOP_STRETCH, TOP_START, TOP_CENTER, TOP_END, MIDDLE, BOTTOM_START, BOTTOM_CENTER, BOTTOM_END, BOTTOM_STRETCH},
* not {@code null}
*/
public void setPosition(Position position) {
setPosition(position.getClientName());
}
/**
*
* Description copied from corresponding location in WebComponent:
*
*
* Alignment of the notification in the viewport Valid values are
* {@code top-stretch|top-start|top-center|top-end|middle|bottom-start|bottom-center|bottom-end|bottom-stretch}
*
* This property is not synchronized automatically from the client side, so
* the returned value may not be the same as in client side.
*
*
* The default position value is {@literal Position.BOTTOM_START}.
*
* @return the {@link Position} property from the webcomponent
*/
public Position getPosition() {
String position = getPositionString();
return Optional.ofNullable(position).map(Position::fromClientName)
.orElse(DEFAULT_POSITION);
}
/**
* Opens the notification.
*/
@Override
public void open() {
setOpened(true);
}
/**
* Closes the notification.
*
* Note: This method also removes the notification component from the DOM
* after closing it, unless you have added the component manually.
*/
@Override
public void close() {
setOpened(false);
}
/**
* Adds the given components into this notification.
*
* The elements in the DOM will not be children of the
* {@code } element, but will be inserted into an
* overlay that is attached into the {@code }.
*
* NOTE: When mixing this method with {@link #Notification(String)},
* {@link #Notification(String, int)} and
* {@link #Notification(String, int, Position)} method will remove the text
* content.
*
* @param components
* the components to add
*/
@Override
public void add(Component... components) {
assert components != null;
for (Component component : components) {
assert component != null;
container.appendChild(component.getElement());
}
getElement().getNode()
.runWhenAttached(ui -> ui.beforeClientResponse(this,
context -> attachComponentTemplate(ui)));
}
/**
* Remove the given components from this notification.
*
* @param components
* the components to remove
*/
@Override
public void remove(Component... components) {
for (Component component : components) {
assert component != null;
if (container.equals(component.getElement().getParent())) {
container.removeChild(component.getElement());
} else {
throw new IllegalArgumentException("The given component ("
+ component + ") is not a child of this component");
}
}
}
/**
* Remove all the components from this notification.
*/
@Override
public void removeAll() {
container.removeAllChildren();
}
@Override
public Stream getChildren() {
Builder childComponents = Stream.builder();
container.getChildren().forEach(childElement -> ComponentUtil
.findComponents(childElement, childComponents::add));
return childComponents.build();
}
private void attachComponentTemplate(UI ui) {
String appId = ui.getInternals().getAppId();
int nodeId = container.getNode().getId();
String template = String.format(
" ",
appId, nodeId);
templateElement.setProperty("innerHTML", template);
}
/**
* Opens or closes the notification.
*
* Note: You don't need to add the component anywhere before opening it.
* Since {@code }'s location in the DOM doesn't really
* matter, opening a notification will automatically add it to the
* {@code } if it's not yet attached anywhere.
*
* @param opened
* {@code true} to open the notification, {@code false} to close
* it
*/
@Override
public void setOpened(boolean opened) {
UI ui = UI.getCurrent();
if (ui == null) {
throw new IllegalStateException("UI instance is not available. "
+ "It means that you are calling this method "
+ "out of a normal workflow where it's always implicitely set. "
+ "That may happen if you call the method from the custom thread without "
+ "'UI::access' or from tests without proper initialization.");
}
if (opened && getElement().getNode().getParent() == null) {
ui.beforeClientResponse(ui, context -> {
ui.add(this);
autoAddedToTheUi = true;
});
}
super.setOpened(opened);
}
/**
*
* Description copied from corresponding location in WebComponent:
*
*
* True if the notification is currently displayed.
*
* This property is synchronized automatically from client side when a
* 'opened-changed' event happens.
*
*
* @return the {@code opened} property from the webcomponent
*/
public boolean isOpened() {
return isOpenedBoolean();
}
@Override
public Registration addOpenedChangeListener(
ComponentEventListener> listener) {
return super.addOpenedChangeListener(listener);
}
/**
*
* Description copied from corresponding location in WebComponent:
*
*
* The duration in milliseconds to show the notification. Set to {@code 0}
* or a negative number to disable the notification auto-closing.
*
*
* @param duration
* the value to set
*/
public void setDuration(int duration) {
setDuration((double) duration);
}
/**
*
* Description copied from corresponding location in WebComponent:
*
*
* The duration in milliseconds to show the notification. Set to {@code 0}
* or a negative number to disable the notification auto-closing.
*
* This property is not synchronized automatically from the client side, so
* the returned value may not be the same as in client side.
*
*
* @return the {@code duration} property from the webcomponent
*/
public int getDuration() {
return (int) getDurationDouble();
}
/**
* {@inheritDoc}
*
* Note: To listen for opening the notification, you should use
* {@link #addOpenedChangeListener(ComponentEventListener)}.
*/
@Override
public Registration addAttachListener(
ComponentEventListener listener) {
return super.addAttachListener(listener);
}
/**
* {@inheritDoc}
*
* Note: To listen for closing the notification, you should use
* {@link #addOpenedChangeListener(ComponentEventListener)}, as the
* component is not necessarily removed from the DOM when closing.
*/
@Override
public Registration addDetachListener(
ComponentEventListener listener) {
return super.addDetachListener(listener);
}
}