123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- /*
- * 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.client.ui;
-
- import java.util.ArrayList;
- import java.util.EventObject;
- import java.util.Iterator;
-
- import com.google.gwt.aria.client.Roles;
- import com.google.gwt.core.client.GWT;
- import com.google.gwt.dom.client.Element;
- import com.google.gwt.dom.client.NativeEvent;
- import com.google.gwt.event.dom.client.KeyCodes;
- import com.google.gwt.user.client.DOM;
- import com.google.gwt.user.client.Event;
- import com.google.gwt.user.client.Event.NativePreviewEvent;
- import com.google.gwt.user.client.Timer;
- import com.google.gwt.user.client.ui.FlowPanel;
- import com.google.gwt.user.client.ui.HTML;
- import com.google.gwt.user.client.ui.Label;
- import com.google.gwt.user.client.ui.Widget;
- import com.vaadin.client.AnimationUtil;
- import com.vaadin.client.AnimationUtil.AnimationEndListener;
- import com.vaadin.client.ApplicationConnection;
- import com.vaadin.client.BrowserInfo;
- import com.vaadin.client.UIDL;
- import com.vaadin.client.WidgetUtil;
- import com.vaadin.client.ui.aria.AriaHelper;
- import com.vaadin.shared.Position;
- import com.vaadin.shared.ui.ui.NotificationRole;
- import com.vaadin.shared.ui.ui.UIConstants;
- import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration;
-
- public class VNotification extends VOverlay {
-
- public static final Position CENTERED = Position.MIDDLE_CENTER;
- public static final Position CENTERED_TOP = Position.TOP_CENTER;
- public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
- public static final Position TOP_LEFT = Position.TOP_LEFT;
- public static final Position TOP_RIGHT = Position.TOP_RIGHT;
- public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
- public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
-
- private static final String STYLENAME_POSITION_TOP = "v-position-top";
- private static final String STYLENAME_POSITION_RIGHT = "v-position-right";
- private static final String STYLENAME_POSITION_BOTTOM = "v-position-bottom";
- private static final String STYLENAME_POSITION_LEFT = "v-position-left";
- private static final String STYLENAME_POSITION_MIDDLE = "v-position-middle";
- private static final String STYLENAME_POSITION_CENTER = "v-position-center";
- private static final String STYLENAME_POSITION_ASSISTIVE = "v-position-assistive";
-
- public static final String CAPTION = "caption";
- public static final String DESCRIPTION = "description";
- public static final String DETAILS = "details";
-
- /**
- * Position that is only accessible for assistive devices, invisible for
- * visual users.
- */
- public static final Position ASSISTIVE = Position.ASSISTIVE;
-
- public static final int DELAY_FOREVER = -1;
- public static final int DELAY_NONE = 0;
-
- private static final String STYLENAME = "v-Notification";
- private static final int mouseMoveThreshold = 7;
- private static final int Z_INDEX_BASE = 20000;
- public static final String STYLE_SYSTEM = "system";
-
- private static final ArrayList<VNotification> notifications = new ArrayList<>();
-
- private boolean infiniteDelay = false;
- private int hideDelay = 0;
-
- private Timer delay;
-
- private int x = -1;
- private int y = -1;
-
- private String temporaryStyle;
-
- private ArrayList<EventListener> listeners;
- private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
-
- /**
- * Default constructor. You should use GWT.create instead.
- */
- public VNotification() {
- setStyleName(STYLENAME);
- sinkEvents(Event.ONCLICK);
- getElement().getStyle().setZIndex(Z_INDEX_BASE);
- }
-
- /**
- * @deprecated Use static {@link #createNotification(int)} instead to enable
- * GWT deferred binding.
- *
- * @param delayMsec
- */
- @Deprecated
- public VNotification(int delayMsec) {
- this();
- setDelay(delayMsec);
-
- if (BrowserInfo.get().isTouchDevice()) {
- new Timer() {
- @Override
- public void run() {
- if (isAttached()) {
- hide();
- }
- }
- }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
- }
- }
-
- /**
- * @deprecated Use static {@link #createNotification(int, int, int)} instead
- * to enable GWT deferred binding.
- *
- * @param delayMsec
- * @param fadeMsec
- * @param startOpacity
- */
- @Deprecated
- public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
- this(delayMsec);
- AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms");
- getElement().getStyle().setOpacity(startOpacity / 100);
- }
-
- private void setDelay(int delayMsec) {
- if (delayMsec < 0) {
- infiniteDelay = true;
- hideDelay = 0;
- } else {
- infiniteDelay = false;
- hideDelay = delayMsec;
- }
- }
-
- @Override
- public void show() {
- show(CENTERED);
- }
-
- public void show(String style) {
- show(CENTERED, style);
- }
-
- public void show(com.vaadin.shared.Position position) {
- show(position, null);
- }
-
- public void show(Widget widget, Position position, String style) {
- NotificationTypeConfiguration styleSetup = getUiState(style);
- setWaiAriaRole(styleSetup);
-
- FlowPanel panel = new FlowPanel();
- if (hasPrefix(styleSetup)) {
- panel.add(new Label(styleSetup.prefix));
- AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
- true);
- }
-
- panel.add(widget);
-
- if (hasPostfix(styleSetup)) {
- panel.add(new Label(styleSetup.postfix));
- AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
- true);
- }
- setWidget(panel);
- show(position, style);
- }
-
- private boolean hasPostfix(NotificationTypeConfiguration styleSetup) {
- return styleSetup != null && styleSetup.postfix != null
- && !styleSetup.postfix.isEmpty();
- }
-
- private boolean hasPrefix(NotificationTypeConfiguration styleSetup) {
- return styleSetup != null && styleSetup.prefix != null
- && !styleSetup.prefix.isEmpty();
- }
-
- public void show(String html, Position position, String style) {
- NotificationTypeConfiguration styleSetup = getUiState(style);
- String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE;
-
- setWaiAriaRole(styleSetup);
-
- String type = "";
- String usage = "";
-
- if (hasPrefix(styleSetup)) {
- type = "<span class='" + assistiveDeviceOnlyStyle + "'>"
- + styleSetup.prefix + "</span>";
- }
-
- if (hasPostfix(styleSetup)) {
- usage = "<span class='" + assistiveDeviceOnlyStyle + "'>"
- + styleSetup.postfix + "</span>";
- }
-
- setWidget(new HTML(type + html + usage));
- show(position, style);
- }
-
- private NotificationTypeConfiguration getUiState(String style) {
- if (getApplicationConnection() == null
- || getApplicationConnection().getUIConnector() == null) {
- return null;
- }
-
- return getApplicationConnection().getUIConnector()
- .getState().notificationConfigurations.get(style);
- }
-
- private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) {
- Roles.getAlertRole().set(getElement());
-
- if (styleSetup != null && styleSetup.notificationRole != null) {
- if (NotificationRole.STATUS == styleSetup.notificationRole) {
- Roles.getStatusRole().set(getElement());
- }
- }
- }
-
- public void show(Position position, String style) {
- if (temporaryStyle != null) {
- removeStyleName(temporaryStyle);
- removeStyleDependentName(temporaryStyle);
- temporaryStyle = null;
- }
- if (style != null && !style.isEmpty()) {
- temporaryStyle = style;
- addStyleName(style);
- addStyleDependentName(style);
- }
-
- setPosition(position);
- super.show();
- updatePositionOffsets(position);
- notifications.add(this);
- positionOrSizeUpdated();
- /**
- * Android 4 fails to render notifications correctly without a little
- * nudge (#8551) Chrome 41 now requires this too (#17252)
- */
- if (BrowserInfo.get().isAndroid() || isChrome41OrHigher()) {
- WidgetUtil.setStyleTemporarily(getElement(), "display", "none");
- }
- }
-
- private boolean isChrome41OrHigher() {
- return BrowserInfo.get().isChrome()
- && BrowserInfo.get().getBrowserMajorVersion() >= 41;
- }
-
- protected void hideAfterDelay() {
- if (delay == null) {
- delay = new Timer() {
- @Override
- public void run() {
- VNotification.super.hide();
- }
- };
- delay.schedule(hideDelay);
- }
- }
-
- @Override
- public void hide() {
- if (delay != null) {
- delay.cancel();
- }
- // Run only once
- if (notifications.contains(this)) {
- DOM.removeEventPreview(this);
-
- // Still animating in, wait for it to finish before touching
- // the animation delay (which would restart the animation-in
- // in some browsers)
- if (getStyleName()
- .contains(VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
- AnimationUtil.addAnimationEndListener(getElement(),
- new AnimationEndListener() {
- @Override
- public void onAnimationEnd(NativeEvent event) {
- if (AnimationUtil.getAnimationName(event)
- .contains(
- VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
- VNotification.this.hide();
- }
- }
- });
- } else {
- VNotification.super.hide();
- fireEvent(new HideEvent(this));
- notifications.remove(this);
- }
- }
- }
-
- private void updatePositionOffsets(com.vaadin.shared.Position position) {
- final Element el = getElement();
-
- // Remove all offsets (GWT PopupPanel defaults)
- el.getStyle().clearTop();
- el.getStyle().clearLeft();
-
- switch (position) {
- case MIDDLE_LEFT:
- case MIDDLE_RIGHT:
- center();
- el.getStyle().clearLeft();
- break;
- case TOP_CENTER:
- case BOTTOM_CENTER:
- center();
- el.getStyle().clearTop();
- break;
- case MIDDLE_CENTER:
- center();
- break;
- }
- }
-
- public void setPosition(com.vaadin.shared.Position position) {
- final Element el = getElement();
-
- // Remove any previous positions
- el.removeClassName(STYLENAME_POSITION_TOP);
- el.removeClassName(STYLENAME_POSITION_RIGHT);
- el.removeClassName(STYLENAME_POSITION_BOTTOM);
- el.removeClassName(STYLENAME_POSITION_LEFT);
- el.removeClassName(STYLENAME_POSITION_MIDDLE);
- el.removeClassName(STYLENAME_POSITION_CENTER);
- el.removeClassName(STYLENAME_POSITION_ASSISTIVE);
-
- switch (position) {
- case TOP_LEFT:
- el.addClassName(STYLENAME_POSITION_TOP);
- el.addClassName(STYLENAME_POSITION_LEFT);
- break;
- case TOP_RIGHT:
- el.addClassName(STYLENAME_POSITION_TOP);
- el.addClassName(STYLENAME_POSITION_RIGHT);
- break;
- case MIDDLE_LEFT:
- el.addClassName(STYLENAME_POSITION_MIDDLE);
- el.addClassName(STYLENAME_POSITION_LEFT);
- break;
- case MIDDLE_RIGHT:
- el.addClassName(STYLENAME_POSITION_MIDDLE);
- el.addClassName(STYLENAME_POSITION_RIGHT);
- break;
- case BOTTOM_RIGHT:
- el.addClassName(STYLENAME_POSITION_BOTTOM);
- el.addClassName(STYLENAME_POSITION_RIGHT);
- break;
- case BOTTOM_LEFT:
- el.addClassName(STYLENAME_POSITION_BOTTOM);
- el.addClassName(STYLENAME_POSITION_LEFT);
- break;
- case TOP_CENTER:
- el.addClassName(STYLENAME_POSITION_TOP);
- el.addClassName(STYLENAME_POSITION_CENTER);
- break;
- case BOTTOM_CENTER:
- el.addClassName(STYLENAME_POSITION_BOTTOM);
- el.addClassName(STYLENAME_POSITION_CENTER);
- break;
- case ASSISTIVE:
- el.addClassName(STYLENAME_POSITION_ASSISTIVE);
- break;
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- hide();
- }
-
- @Override
- /*
- * Fix for #14689: {@link #onEventPreview(Event)} method is deprecated and
- * it's called now only for the very first handler (see super impl). We need
- * it to work for any handler. So let's call old {@link
- * #onEventPreview(Event)} method explicitly with updated logic for {@link
- * #onPreviewNativeEvent(Event)}.
- */
- protected void onPreviewNativeEvent(NativePreviewEvent event) {
- if (!onEventPreview(Event.as(event.getNativeEvent()))) {
- event.cancel();
- }
- }
-
- @Override
- public boolean onEventPreview(Event event) {
- int type = DOM.eventGetType(event);
- // "modal"
- if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) {
- if (type == Event.ONCLICK || type == Event.ONTOUCHEND) {
- if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
- hide();
- return false;
- }
- } else if (type == Event.ONKEYDOWN
- && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
- hide();
- return false;
- }
- return temporaryStyle == STYLE_SYSTEM;
- }
- // default
- switch (type) {
- case Event.ONMOUSEMOVE:
- if (x < 0) {
- x = DOM.eventGetClientX(event);
- y = DOM.eventGetClientY(event);
- } else if (Math
- .abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
- || Math.abs(DOM.eventGetClientY(event)
- - y) > mouseMoveThreshold) {
- hideAfterDelay();
- }
- break;
- case Event.ONMOUSEDOWN:
- case Event.ONMOUSEWHEEL:
- case Event.ONSCROLL:
- hideAfterDelay();
- break;
- case Event.ONKEYDOWN:
- if (event.getRepeat()) {
- return true;
- }
- hideAfterDelay();
- break;
- default:
- break;
- }
- return true;
- }
-
- public void addEventListener(EventListener listener) {
- if (listeners == null) {
- listeners = new ArrayList<>();
- }
- listeners.add(listener);
- }
-
- public void removeEventListener(EventListener listener) {
- if (listeners != null) {
- listeners.remove(listener);
- }
- }
-
- private void fireEvent(HideEvent event) {
- if (listeners != null) {
- for (EventListener l : listeners) {
- l.notificationHidden(event);
- }
- }
- }
-
- public static void showNotification(ApplicationConnection client,
- final UIDL notification) {
- boolean onlyPlainText = notification.hasAttribute(
- UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
- String html = "";
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
- String iconUri = notification.getStringAttribute(
- UIConstants.ATTRIBUTE_NOTIFICATION_ICON);
- html += client.getIcon(iconUri).getElement().getString();
- }
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
- String caption = notification.getStringAttribute(
- UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
- if (onlyPlainText) {
- caption = WidgetUtil.escapeHTML(caption);
- caption = caption.replaceAll("\\n", "<br />");
- }
- html += "<h1 class='" + getDependentStyle(client, CAPTION) + "'>"
- + caption + "</h1>";
- }
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
- String message = notification.getStringAttribute(
- UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
- if (onlyPlainText) {
- message = WidgetUtil.escapeHTML(message);
- message = message.replaceAll("\\n", "<br />");
- }
- html += "<p class='" + getDependentStyle(client, DESCRIPTION) + "'>"
- + message + "</p>";
- }
-
- final String style = notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
- ? notification.getStringAttribute(
- UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
- : null;
-
- final int pos = notification
- .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
- Position position = Position.values()[pos];
-
- final int delay = notification
- .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
- createNotification(delay, client.getUIConnector().getWidget())
- .show(html, position, style);
- }
-
- /**
- * Meant for internal usage only.
- *
- * @since 7.5.0
- * @param client
- * application connection
- * @param style
- * the dependent style name
- * @return the given dependent style name prefixed with current notification
- * primary style
- */
- public static String getDependentStyle(ApplicationConnection client,
- String style) {
- VNotification notification = createNotification(-1,
- client.getUIConnector().getWidget());
- String styleName = notification.getStyleName();
- notification.addStyleDependentName(style);
- String extendedStyle = notification.getStyleName();
- return extendedStyle.substring(styleName.length()).trim();
- }
-
- public static VNotification createNotification(int delayMsec,
- Widget owner) {
- final VNotification notification = GWT.create(VNotification.class);
- notification.setWaiAriaRole(null);
- notification.setDelay(delayMsec);
-
- if (!notification.infiniteDelay && BrowserInfo.get().isTouchDevice()) {
- new Timer() {
- @Override
- public void run() {
- if (notification.isAttached()) {
- notification.hide();
- }
- }
- }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
- }
- notification.setOwner(owner);
- return notification;
- }
-
- public class HideEvent extends EventObject {
-
- public HideEvent(Object source) {
- super(source);
- }
- }
-
- public interface EventListener extends java.util.EventListener {
- public void notificationHidden(HideEvent event);
- }
-
- /**
- * Moves currently visible notifications to the top of the event preview
- * stack. Can be called when opening other overlays such as subwindows to
- * ensure the notifications receive the events they need and don't linger
- * indefinitely. See #7136.
- *
- * TODO Should this be a generic Overlay feature instead?
- */
- public static void bringNotificationsToFront() {
- for (VNotification notification : notifications) {
- DOM.removeEventPreview(notification);
- DOM.addEventPreview(notification);
- }
- }
-
- /**
- * Shows an error notification and redirects the user to the given URL when
- * she clicks on the notification.
- *
- * If both message and caption are null, redirects the user to the url
- * immediately
- *
- * @since 7.5.1
- * @param connection
- * A reference to the ApplicationConnection
- * @param caption
- * The caption for the error or null to exclude the caption
- * @param message
- * The message for the error or null to exclude the message
- * @param details
- * A details message or null to exclude the details
- * @param url
- * A url to redirect to after the user clicks the error
- * notification
- */
- public static void showError(ApplicationConnection connection,
- String caption, String message, String details, String url) {
-
- StringBuilder html = new StringBuilder();
- if (caption != null) {
- html.append("<h1 class='");
- html.append(getDependentStyle(connection, CAPTION));
- html.append("'>");
- html.append(caption);
- html.append("</h1>");
- }
- if (message != null) {
- html.append("<p class='");
- html.append(getDependentStyle(connection, DESCRIPTION));
- html.append("'>");
- html.append(message);
- html.append("</p>");
- }
-
- if (html.length() != 0) {
-
- // Add error description
- if (details != null) {
- html.append("<p class='");
- html.append(getDependentStyle(connection, DETAILS));
- html.append("'>");
- html.append("<i style=\"font-size:0.7em\">");
- html.append(details);
- html.append("</i></p>");
- }
-
- VNotification n = VNotification.createNotification(1000 * 60 * 45,
- connection.getUIConnector().getWidget());
- n.addEventListener(new NotificationRedirect(url));
- n.show(html.toString(), VNotification.CENTERED_TOP,
- VNotification.STYLE_SYSTEM);
- } else {
- WidgetUtil.redirect(url);
- }
- }
-
- /**
- * Listens for Notification hide event, and redirects. Used for system
- * messages, such as session expired.
- *
- */
- private static class NotificationRedirect
- implements VNotification.EventListener {
- String url;
-
- NotificationRedirect(String url) {
- this.url = url;
- }
-
- @Override
- public void notificationHidden(HideEvent event) {
- WidgetUtil.redirect(url);
- }
-
- }
-
- }
|