You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VNotification.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui;
  17. import java.util.ArrayList;
  18. import java.util.EventObject;
  19. import java.util.Iterator;
  20. import com.google.gwt.aria.client.Roles;
  21. import com.google.gwt.core.client.GWT;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.dom.client.NativeEvent;
  24. import com.google.gwt.event.dom.client.KeyCodes;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.Event;
  27. import com.google.gwt.user.client.Event.NativePreviewEvent;
  28. import com.google.gwt.user.client.Timer;
  29. import com.google.gwt.user.client.ui.FlowPanel;
  30. import com.google.gwt.user.client.ui.HTML;
  31. import com.google.gwt.user.client.ui.Label;
  32. import com.google.gwt.user.client.ui.Widget;
  33. import com.vaadin.client.AnimationUtil;
  34. import com.vaadin.client.AnimationUtil.AnimationEndListener;
  35. import com.vaadin.client.ApplicationConnection;
  36. import com.vaadin.client.BrowserInfo;
  37. import com.vaadin.client.UIDL;
  38. import com.vaadin.client.WidgetUtil;
  39. import com.vaadin.client.ui.aria.AriaHelper;
  40. import com.vaadin.shared.Position;
  41. import com.vaadin.shared.ui.ui.NotificationRole;
  42. import com.vaadin.shared.ui.ui.UIConstants;
  43. import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration;
  44. public class VNotification extends VOverlay {
  45. public static final Position CENTERED = Position.MIDDLE_CENTER;
  46. public static final Position CENTERED_TOP = Position.TOP_CENTER;
  47. public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
  48. public static final Position TOP_LEFT = Position.TOP_LEFT;
  49. public static final Position TOP_RIGHT = Position.TOP_RIGHT;
  50. public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
  51. public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
  52. private static final String STYLENAME_POSITION_TOP = "v-position-top";
  53. private static final String STYLENAME_POSITION_RIGHT = "v-position-right";
  54. private static final String STYLENAME_POSITION_BOTTOM = "v-position-bottom";
  55. private static final String STYLENAME_POSITION_LEFT = "v-position-left";
  56. private static final String STYLENAME_POSITION_MIDDLE = "v-position-middle";
  57. private static final String STYLENAME_POSITION_CENTER = "v-position-center";
  58. private static final String STYLENAME_POSITION_ASSISTIVE = "v-position-assistive";
  59. public static final String CAPTION = "caption";
  60. public static final String DESCRIPTION = "description";
  61. public static final String DETAILS = "details";
  62. /**
  63. * Position that is only accessible for assistive devices, invisible for
  64. * visual users.
  65. */
  66. public static final Position ASSISTIVE = Position.ASSISTIVE;
  67. public static final int DELAY_FOREVER = -1;
  68. public static final int DELAY_NONE = 0;
  69. private static final String STYLENAME = "v-Notification";
  70. private static final int mouseMoveThreshold = 7;
  71. private static final int Z_INDEX_BASE = 20000;
  72. public static final String STYLE_SYSTEM = "system";
  73. private static final ArrayList<VNotification> notifications = new ArrayList<>();
  74. private boolean infiniteDelay = false;
  75. private int hideDelay = 0;
  76. private Timer delay;
  77. private int x = -1;
  78. private int y = -1;
  79. private String temporaryStyle;
  80. private ArrayList<EventListener> listeners;
  81. private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
  82. /**
  83. * Default constructor. You should use GWT.create instead.
  84. */
  85. public VNotification() {
  86. setStyleName(STYLENAME);
  87. sinkEvents(Event.ONCLICK);
  88. getElement().getStyle().setZIndex(Z_INDEX_BASE);
  89. }
  90. /**
  91. * @deprecated Use static {@link #createNotification(int)} instead to enable
  92. * GWT deferred binding.
  93. *
  94. * @param delayMsec
  95. */
  96. @Deprecated
  97. public VNotification(int delayMsec) {
  98. this();
  99. setDelay(delayMsec);
  100. if (BrowserInfo.get().isTouchDevice()) {
  101. new Timer() {
  102. @Override
  103. public void run() {
  104. if (isAttached()) {
  105. hide();
  106. }
  107. }
  108. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  109. }
  110. }
  111. /**
  112. * @deprecated Use static {@link #createNotification(int, int, int)} instead
  113. * to enable GWT deferred binding.
  114. *
  115. * @param delayMsec
  116. * @param fadeMsec
  117. * @param startOpacity
  118. */
  119. @Deprecated
  120. public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
  121. this(delayMsec);
  122. AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms");
  123. getElement().getStyle().setOpacity(startOpacity / 100);
  124. }
  125. private void setDelay(int delayMsec) {
  126. if (delayMsec < 0) {
  127. infiniteDelay = true;
  128. hideDelay = 0;
  129. } else {
  130. infiniteDelay = false;
  131. hideDelay = delayMsec;
  132. }
  133. }
  134. @Override
  135. public void show() {
  136. show(CENTERED);
  137. }
  138. public void show(String style) {
  139. show(CENTERED, style);
  140. }
  141. public void show(com.vaadin.shared.Position position) {
  142. show(position, null);
  143. }
  144. public void show(Widget widget, Position position, String style) {
  145. NotificationTypeConfiguration styleSetup = getUiState(style);
  146. setWaiAriaRole(styleSetup);
  147. FlowPanel panel = new FlowPanel();
  148. if (hasPrefix(styleSetup)) {
  149. panel.add(new Label(styleSetup.prefix));
  150. AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
  151. true);
  152. }
  153. panel.add(widget);
  154. if (hasPostfix(styleSetup)) {
  155. panel.add(new Label(styleSetup.postfix));
  156. AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
  157. true);
  158. }
  159. setWidget(panel);
  160. show(position, style);
  161. }
  162. private boolean hasPostfix(NotificationTypeConfiguration styleSetup) {
  163. return styleSetup != null && styleSetup.postfix != null
  164. && !styleSetup.postfix.isEmpty();
  165. }
  166. private boolean hasPrefix(NotificationTypeConfiguration styleSetup) {
  167. return styleSetup != null && styleSetup.prefix != null
  168. && !styleSetup.prefix.isEmpty();
  169. }
  170. public void show(String html, Position position, String style) {
  171. NotificationTypeConfiguration styleSetup = getUiState(style);
  172. String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE;
  173. setWaiAriaRole(styleSetup);
  174. String type = "";
  175. String usage = "";
  176. if (hasPrefix(styleSetup)) {
  177. type = "<span class='" + assistiveDeviceOnlyStyle + "'>"
  178. + styleSetup.prefix + "</span>";
  179. }
  180. if (hasPostfix(styleSetup)) {
  181. usage = "<span class='" + assistiveDeviceOnlyStyle + "'>"
  182. + styleSetup.postfix + "</span>";
  183. }
  184. setWidget(new HTML(type + html + usage));
  185. show(position, style);
  186. }
  187. private NotificationTypeConfiguration getUiState(String style) {
  188. if (getApplicationConnection() == null
  189. || getApplicationConnection().getUIConnector() == null) {
  190. return null;
  191. }
  192. return getApplicationConnection().getUIConnector()
  193. .getState().notificationConfigurations.get(style);
  194. }
  195. private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) {
  196. Roles.getAlertRole().set(getElement());
  197. if (styleSetup != null && styleSetup.notificationRole != null) {
  198. if (NotificationRole.STATUS == styleSetup.notificationRole) {
  199. Roles.getStatusRole().set(getElement());
  200. }
  201. }
  202. }
  203. public void show(Position position, String style) {
  204. if (temporaryStyle != null) {
  205. removeStyleName(temporaryStyle);
  206. removeStyleDependentName(temporaryStyle);
  207. temporaryStyle = null;
  208. }
  209. if (style != null && !style.isEmpty()) {
  210. temporaryStyle = style;
  211. addStyleName(style);
  212. addStyleDependentName(style);
  213. }
  214. setPosition(position);
  215. super.show();
  216. updatePositionOffsets(position);
  217. notifications.add(this);
  218. positionOrSizeUpdated();
  219. /**
  220. * Android 4 fails to render notifications correctly without a little
  221. * nudge (#8551) Chrome 41 now requires this too (#17252)
  222. */
  223. if (BrowserInfo.get().isAndroid() || isChrome41OrHigher()) {
  224. WidgetUtil.setStyleTemporarily(getElement(), "display", "none");
  225. }
  226. }
  227. private boolean isChrome41OrHigher() {
  228. return BrowserInfo.get().isChrome()
  229. && BrowserInfo.get().getBrowserMajorVersion() >= 41;
  230. }
  231. protected void hideAfterDelay() {
  232. if (delay == null) {
  233. delay = new Timer() {
  234. @Override
  235. public void run() {
  236. VNotification.super.hide();
  237. }
  238. };
  239. delay.schedule(hideDelay);
  240. }
  241. }
  242. @Override
  243. public void hide() {
  244. if (delay != null) {
  245. delay.cancel();
  246. }
  247. // Run only once
  248. if (notifications.contains(this)) {
  249. DOM.removeEventPreview(this);
  250. // Still animating in, wait for it to finish before touching
  251. // the animation delay (which would restart the animation-in
  252. // in some browsers)
  253. if (getStyleName()
  254. .contains(VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  255. AnimationUtil.addAnimationEndListener(getElement(),
  256. new AnimationEndListener() {
  257. @Override
  258. public void onAnimationEnd(NativeEvent event) {
  259. if (AnimationUtil.getAnimationName(event)
  260. .contains(
  261. VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  262. VNotification.this.hide();
  263. }
  264. }
  265. });
  266. } else {
  267. VNotification.super.hide();
  268. fireEvent(new HideEvent(this));
  269. notifications.remove(this);
  270. }
  271. }
  272. }
  273. private void updatePositionOffsets(com.vaadin.shared.Position position) {
  274. final Element el = getElement();
  275. // Remove all offsets (GWT PopupPanel defaults)
  276. el.getStyle().clearTop();
  277. el.getStyle().clearLeft();
  278. switch (position) {
  279. case MIDDLE_LEFT:
  280. case MIDDLE_RIGHT:
  281. center();
  282. el.getStyle().clearLeft();
  283. break;
  284. case TOP_CENTER:
  285. case BOTTOM_CENTER:
  286. center();
  287. el.getStyle().clearTop();
  288. break;
  289. case MIDDLE_CENTER:
  290. center();
  291. break;
  292. }
  293. }
  294. public void setPosition(com.vaadin.shared.Position position) {
  295. final Element el = getElement();
  296. // Remove any previous positions
  297. el.removeClassName(STYLENAME_POSITION_TOP);
  298. el.removeClassName(STYLENAME_POSITION_RIGHT);
  299. el.removeClassName(STYLENAME_POSITION_BOTTOM);
  300. el.removeClassName(STYLENAME_POSITION_LEFT);
  301. el.removeClassName(STYLENAME_POSITION_MIDDLE);
  302. el.removeClassName(STYLENAME_POSITION_CENTER);
  303. el.removeClassName(STYLENAME_POSITION_ASSISTIVE);
  304. switch (position) {
  305. case TOP_LEFT:
  306. el.addClassName(STYLENAME_POSITION_TOP);
  307. el.addClassName(STYLENAME_POSITION_LEFT);
  308. break;
  309. case TOP_RIGHT:
  310. el.addClassName(STYLENAME_POSITION_TOP);
  311. el.addClassName(STYLENAME_POSITION_RIGHT);
  312. break;
  313. case MIDDLE_LEFT:
  314. el.addClassName(STYLENAME_POSITION_MIDDLE);
  315. el.addClassName(STYLENAME_POSITION_LEFT);
  316. break;
  317. case MIDDLE_RIGHT:
  318. el.addClassName(STYLENAME_POSITION_MIDDLE);
  319. el.addClassName(STYLENAME_POSITION_RIGHT);
  320. break;
  321. case BOTTOM_RIGHT:
  322. el.addClassName(STYLENAME_POSITION_BOTTOM);
  323. el.addClassName(STYLENAME_POSITION_RIGHT);
  324. break;
  325. case BOTTOM_LEFT:
  326. el.addClassName(STYLENAME_POSITION_BOTTOM);
  327. el.addClassName(STYLENAME_POSITION_LEFT);
  328. break;
  329. case TOP_CENTER:
  330. el.addClassName(STYLENAME_POSITION_TOP);
  331. el.addClassName(STYLENAME_POSITION_CENTER);
  332. break;
  333. case BOTTOM_CENTER:
  334. el.addClassName(STYLENAME_POSITION_BOTTOM);
  335. el.addClassName(STYLENAME_POSITION_CENTER);
  336. break;
  337. case ASSISTIVE:
  338. el.addClassName(STYLENAME_POSITION_ASSISTIVE);
  339. break;
  340. }
  341. }
  342. @Override
  343. public void onBrowserEvent(Event event) {
  344. hide();
  345. }
  346. @Override
  347. /*
  348. * Fix for #14689: {@link #onEventPreview(Event)} method is deprecated and
  349. * it's called now only for the very first handler (see super impl). We need
  350. * it to work for any handler. So let's call old {@link
  351. * #onEventPreview(Event)} method explicitly with updated logic for {@link
  352. * #onPreviewNativeEvent(Event)}.
  353. */
  354. protected void onPreviewNativeEvent(NativePreviewEvent event) {
  355. if (!onEventPreview(Event.as(event.getNativeEvent()))) {
  356. event.cancel();
  357. }
  358. }
  359. @Override
  360. public boolean onEventPreview(Event event) {
  361. int type = DOM.eventGetType(event);
  362. // "modal"
  363. if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) {
  364. if (type == Event.ONCLICK || type == Event.ONTOUCHEND) {
  365. if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
  366. hide();
  367. return false;
  368. }
  369. } else if (type == Event.ONKEYDOWN
  370. && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
  371. hide();
  372. return false;
  373. }
  374. return temporaryStyle == STYLE_SYSTEM;
  375. }
  376. // default
  377. switch (type) {
  378. case Event.ONMOUSEMOVE:
  379. if (x < 0) {
  380. x = DOM.eventGetClientX(event);
  381. y = DOM.eventGetClientY(event);
  382. } else if (Math
  383. .abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
  384. || Math.abs(DOM.eventGetClientY(event)
  385. - y) > mouseMoveThreshold) {
  386. hideAfterDelay();
  387. }
  388. break;
  389. case Event.ONMOUSEDOWN:
  390. case Event.ONMOUSEWHEEL:
  391. case Event.ONSCROLL:
  392. hideAfterDelay();
  393. break;
  394. case Event.ONKEYDOWN:
  395. if (event.getRepeat()) {
  396. return true;
  397. }
  398. hideAfterDelay();
  399. break;
  400. default:
  401. break;
  402. }
  403. return true;
  404. }
  405. public void addEventListener(EventListener listener) {
  406. if (listeners == null) {
  407. listeners = new ArrayList<>();
  408. }
  409. listeners.add(listener);
  410. }
  411. public void removeEventListener(EventListener listener) {
  412. if (listeners != null) {
  413. listeners.remove(listener);
  414. }
  415. }
  416. private void fireEvent(HideEvent event) {
  417. if (listeners != null) {
  418. for (EventListener l : listeners) {
  419. l.notificationHidden(event);
  420. }
  421. }
  422. }
  423. public static void showNotification(ApplicationConnection client,
  424. final UIDL notification) {
  425. boolean onlyPlainText = notification.hasAttribute(
  426. UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
  427. String html = "";
  428. if (notification
  429. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
  430. String iconUri = notification.getStringAttribute(
  431. UIConstants.ATTRIBUTE_NOTIFICATION_ICON);
  432. html += client.getIcon(iconUri).getElement().getString();
  433. }
  434. if (notification
  435. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
  436. String caption = notification.getStringAttribute(
  437. UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
  438. if (onlyPlainText) {
  439. caption = WidgetUtil.escapeHTML(caption);
  440. caption = caption.replaceAll("\\n", "<br />");
  441. }
  442. html += "<h1 class='" + getDependentStyle(client, CAPTION) + "'>"
  443. + caption + "</h1>";
  444. }
  445. if (notification
  446. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
  447. String message = notification.getStringAttribute(
  448. UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
  449. if (onlyPlainText) {
  450. message = WidgetUtil.escapeHTML(message);
  451. message = message.replaceAll("\\n", "<br />");
  452. }
  453. html += "<p class='" + getDependentStyle(client, DESCRIPTION) + "'>"
  454. + message + "</p>";
  455. }
  456. final String style = notification
  457. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
  458. ? notification.getStringAttribute(
  459. UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
  460. : null;
  461. final int pos = notification
  462. .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
  463. Position position = Position.values()[pos];
  464. final int delay = notification
  465. .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
  466. createNotification(delay, client.getUIConnector().getWidget())
  467. .show(html, position, style);
  468. }
  469. /**
  470. * Meant for internal usage only.
  471. *
  472. * @since 7.5.0
  473. * @param client
  474. * application connection
  475. * @param style
  476. * the dependent style name
  477. * @return the given dependent style name prefixed with current notification
  478. * primary style
  479. */
  480. public static String getDependentStyle(ApplicationConnection client,
  481. String style) {
  482. VNotification notification = createNotification(-1,
  483. client.getUIConnector().getWidget());
  484. String styleName = notification.getStyleName();
  485. notification.addStyleDependentName(style);
  486. String extendedStyle = notification.getStyleName();
  487. return extendedStyle.substring(styleName.length()).trim();
  488. }
  489. public static VNotification createNotification(int delayMsec,
  490. Widget owner) {
  491. final VNotification notification = GWT.create(VNotification.class);
  492. notification.setWaiAriaRole(null);
  493. notification.setDelay(delayMsec);
  494. if (!notification.infiniteDelay && BrowserInfo.get().isTouchDevice()) {
  495. new Timer() {
  496. @Override
  497. public void run() {
  498. if (notification.isAttached()) {
  499. notification.hide();
  500. }
  501. }
  502. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  503. }
  504. notification.setOwner(owner);
  505. return notification;
  506. }
  507. public class HideEvent extends EventObject {
  508. public HideEvent(Object source) {
  509. super(source);
  510. }
  511. }
  512. public interface EventListener extends java.util.EventListener {
  513. public void notificationHidden(HideEvent event);
  514. }
  515. /**
  516. * Moves currently visible notifications to the top of the event preview
  517. * stack. Can be called when opening other overlays such as subwindows to
  518. * ensure the notifications receive the events they need and don't linger
  519. * indefinitely. See #7136.
  520. *
  521. * TODO Should this be a generic Overlay feature instead?
  522. */
  523. public static void bringNotificationsToFront() {
  524. for (VNotification notification : notifications) {
  525. DOM.removeEventPreview(notification);
  526. DOM.addEventPreview(notification);
  527. }
  528. }
  529. /**
  530. * Shows an error notification and redirects the user to the given URL when
  531. * she clicks on the notification.
  532. *
  533. * If both message and caption are null, redirects the user to the url
  534. * immediately
  535. *
  536. * @since 7.5.1
  537. * @param connection
  538. * A reference to the ApplicationConnection
  539. * @param caption
  540. * The caption for the error or null to exclude the caption
  541. * @param message
  542. * The message for the error or null to exclude the message
  543. * @param details
  544. * A details message or null to exclude the details
  545. * @param url
  546. * A url to redirect to after the user clicks the error
  547. * notification
  548. */
  549. public static void showError(ApplicationConnection connection,
  550. String caption, String message, String details, String url) {
  551. StringBuilder html = new StringBuilder();
  552. if (caption != null) {
  553. html.append("<h1 class='");
  554. html.append(getDependentStyle(connection, CAPTION));
  555. html.append("'>");
  556. html.append(caption);
  557. html.append("</h1>");
  558. }
  559. if (message != null) {
  560. html.append("<p class='");
  561. html.append(getDependentStyle(connection, DESCRIPTION));
  562. html.append("'>");
  563. html.append(message);
  564. html.append("</p>");
  565. }
  566. if (html.length() != 0) {
  567. // Add error description
  568. if (details != null) {
  569. html.append("<p class='");
  570. html.append(getDependentStyle(connection, DETAILS));
  571. html.append("'>");
  572. html.append("<i style=\"font-size:0.7em\">");
  573. html.append(details);
  574. html.append("</i></p>");
  575. }
  576. VNotification n = VNotification.createNotification(1000 * 60 * 45,
  577. connection.getUIConnector().getWidget());
  578. n.addEventListener(new NotificationRedirect(url));
  579. n.show(html.toString(), VNotification.CENTERED_TOP,
  580. VNotification.STYLE_SYSTEM);
  581. } else {
  582. WidgetUtil.redirect(url);
  583. }
  584. }
  585. /**
  586. * Listens for Notification hide event, and redirects. Used for system
  587. * messages, such as session expired.
  588. *
  589. */
  590. private static class NotificationRedirect
  591. implements VNotification.EventListener {
  592. String url;
  593. NotificationRedirect(String url) {
  594. this.url = url;
  595. }
  596. @Override
  597. public void notificationHidden(HideEvent event) {
  598. WidgetUtil.redirect(url);
  599. }
  600. }
  601. }