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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /*
  2. * Copyright 2000-2014 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<VNotification>();
  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().getState().notificationConfigurations
  193. .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.length() > 0) {
  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().contains(
  254. VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  255. AnimationUtil.addAnimationEndListener(getElement(),
  256. new AnimationEndListener() {
  257. @Override
  258. public void onAnimationEnd(NativeEvent event) {
  259. if (AnimationUtil
  260. .getAnimationName(event)
  261. .contains(
  262. VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  263. VNotification.this.hide();
  264. }
  265. }
  266. });
  267. } else {
  268. VNotification.super.hide();
  269. fireEvent(new HideEvent(this));
  270. notifications.remove(this);
  271. }
  272. }
  273. }
  274. private void updatePositionOffsets(com.vaadin.shared.Position position) {
  275. final Element el = getElement();
  276. // Remove all offsets (GWT PopupPanel defaults)
  277. el.getStyle().clearTop();
  278. el.getStyle().clearLeft();
  279. switch (position) {
  280. case MIDDLE_LEFT:
  281. case MIDDLE_RIGHT:
  282. center();
  283. el.getStyle().clearLeft();
  284. break;
  285. case TOP_CENTER:
  286. case BOTTOM_CENTER:
  287. center();
  288. el.getStyle().clearTop();
  289. break;
  290. case MIDDLE_CENTER:
  291. center();
  292. break;
  293. }
  294. }
  295. public void setPosition(com.vaadin.shared.Position position) {
  296. final Element el = getElement();
  297. // Remove any previous positions
  298. el.removeClassName(STYLENAME_POSITION_TOP);
  299. el.removeClassName(STYLENAME_POSITION_RIGHT);
  300. el.removeClassName(STYLENAME_POSITION_BOTTOM);
  301. el.removeClassName(STYLENAME_POSITION_LEFT);
  302. el.removeClassName(STYLENAME_POSITION_MIDDLE);
  303. el.removeClassName(STYLENAME_POSITION_CENTER);
  304. el.removeClassName(STYLENAME_POSITION_ASSISTIVE);
  305. switch (position) {
  306. case TOP_LEFT:
  307. el.addClassName(STYLENAME_POSITION_TOP);
  308. el.addClassName(STYLENAME_POSITION_LEFT);
  309. break;
  310. case TOP_RIGHT:
  311. el.addClassName(STYLENAME_POSITION_TOP);
  312. el.addClassName(STYLENAME_POSITION_RIGHT);
  313. break;
  314. case MIDDLE_LEFT:
  315. el.addClassName(STYLENAME_POSITION_MIDDLE);
  316. el.addClassName(STYLENAME_POSITION_LEFT);
  317. break;
  318. case MIDDLE_RIGHT:
  319. el.addClassName(STYLENAME_POSITION_MIDDLE);
  320. el.addClassName(STYLENAME_POSITION_RIGHT);
  321. break;
  322. case BOTTOM_RIGHT:
  323. el.addClassName(STYLENAME_POSITION_BOTTOM);
  324. el.addClassName(STYLENAME_POSITION_RIGHT);
  325. break;
  326. case BOTTOM_LEFT:
  327. el.addClassName(STYLENAME_POSITION_BOTTOM);
  328. el.addClassName(STYLENAME_POSITION_LEFT);
  329. break;
  330. case TOP_CENTER:
  331. el.addClassName(STYLENAME_POSITION_TOP);
  332. el.addClassName(STYLENAME_POSITION_CENTER);
  333. break;
  334. case BOTTOM_CENTER:
  335. el.addClassName(STYLENAME_POSITION_BOTTOM);
  336. el.addClassName(STYLENAME_POSITION_CENTER);
  337. break;
  338. case ASSISTIVE:
  339. el.addClassName(STYLENAME_POSITION_ASSISTIVE);
  340. break;
  341. }
  342. }
  343. @Override
  344. public void onBrowserEvent(Event event) {
  345. hide();
  346. }
  347. @Override
  348. /*
  349. * Fix for #14689: {@link #onEventPreview(Event)} method is deprecated and
  350. * it's called now only for the very first handler (see super impl). We need
  351. * it to work for any handler. So let's call old {@link
  352. * #onEventPreview(Event)} method explicitly with updated logic for {@link
  353. * #onPreviewNativeEvent(Event)}.
  354. */
  355. protected void onPreviewNativeEvent(NativePreviewEvent event) {
  356. if (!onEventPreview(Event.as(event.getNativeEvent()))) {
  357. event.cancel();
  358. }
  359. }
  360. @Override
  361. public boolean onEventPreview(Event event) {
  362. int type = DOM.eventGetType(event);
  363. // "modal"
  364. if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) {
  365. if (type == Event.ONCLICK || type == Event.ONTOUCHEND) {
  366. if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
  367. hide();
  368. return false;
  369. }
  370. } else if (type == Event.ONKEYDOWN
  371. && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
  372. hide();
  373. return false;
  374. }
  375. if (temporaryStyle == STYLE_SYSTEM) {
  376. return true;
  377. } else {
  378. return false;
  379. }
  380. }
  381. // default
  382. switch (type) {
  383. case Event.ONMOUSEMOVE:
  384. if (x < 0) {
  385. x = DOM.eventGetClientX(event);
  386. y = DOM.eventGetClientY(event);
  387. } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
  388. || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
  389. hideAfterDelay();
  390. }
  391. break;
  392. case Event.ONMOUSEDOWN:
  393. case Event.ONMOUSEWHEEL:
  394. case Event.ONSCROLL:
  395. hideAfterDelay();
  396. break;
  397. case Event.ONKEYDOWN:
  398. if (event.getRepeat()) {
  399. return true;
  400. }
  401. hideAfterDelay();
  402. break;
  403. default:
  404. break;
  405. }
  406. return true;
  407. }
  408. public void addEventListener(EventListener listener) {
  409. if (listeners == null) {
  410. listeners = new ArrayList<EventListener>();
  411. }
  412. listeners.add(listener);
  413. }
  414. public void removeEventListener(EventListener listener) {
  415. if (listeners == null) {
  416. return;
  417. }
  418. listeners.remove(listener);
  419. }
  420. private void fireEvent(HideEvent event) {
  421. if (listeners != null) {
  422. for (Iterator<EventListener> it = listeners.iterator(); it
  423. .hasNext();) {
  424. EventListener l = it.next();
  425. l.notificationHidden(event);
  426. }
  427. }
  428. }
  429. public static void showNotification(ApplicationConnection client,
  430. final UIDL notification) {
  431. boolean onlyPlainText = notification
  432. .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
  433. String html = "";
  434. if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
  435. String iconUri = notification
  436. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON);
  437. html += client.getIcon(iconUri).getElement().getString();
  438. }
  439. if (notification
  440. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
  441. String caption = notification
  442. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
  443. if (onlyPlainText) {
  444. caption = WidgetUtil.escapeHTML(caption);
  445. caption = caption.replaceAll("\\n", "<br />");
  446. }
  447. html += "<h1 class='" + getDependentStyle(client, CAPTION) + "'>"
  448. + caption + "</h1>";
  449. }
  450. if (notification
  451. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
  452. String message = notification
  453. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
  454. if (onlyPlainText) {
  455. message = WidgetUtil.escapeHTML(message);
  456. message = message.replaceAll("\\n", "<br />");
  457. }
  458. html += "<p class='" + getDependentStyle(client, DESCRIPTION)
  459. + "'>" + message + "</p>";
  460. }
  461. final String style = notification
  462. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
  463. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
  464. : null;
  465. final int pos = notification
  466. .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
  467. Position position = Position.values()[pos];
  468. final int delay = notification
  469. .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
  470. createNotification(delay, client.getUIConnector().getWidget()).show(
  471. html, position, style);
  472. }
  473. /**
  474. * Meant for internal usage only.
  475. *
  476. * @since 7.5.0
  477. * @param client
  478. * application connection
  479. * @param style
  480. * the dependent style name
  481. * @return the given dependent style name prefixed with current notification
  482. * primary style
  483. */
  484. public static String getDependentStyle(ApplicationConnection client,
  485. String style) {
  486. VNotification notification = createNotification(-1, client
  487. .getUIConnector().getWidget());
  488. String styleName = notification.getStyleName();
  489. notification.addStyleDependentName(style);
  490. String extendedStyle = notification.getStyleName();
  491. return extendedStyle.substring(styleName.length()).trim();
  492. }
  493. public static VNotification createNotification(int delayMsec, 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 implements
  594. 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. }