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

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