Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

VNotification.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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.Timer;
  28. import com.google.gwt.user.client.ui.FlowPanel;
  29. import com.google.gwt.user.client.ui.HTML;
  30. import com.google.gwt.user.client.ui.Label;
  31. import com.google.gwt.user.client.ui.Widget;
  32. import com.vaadin.client.AnimationUtil;
  33. import com.vaadin.client.AnimationUtil.AnimationEndListener;
  34. import com.vaadin.client.ApplicationConnection;
  35. import com.vaadin.client.BrowserInfo;
  36. import com.vaadin.client.UIDL;
  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.UIConstants;
  42. import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration;
  43. public class VNotification extends VOverlay {
  44. public static final Position CENTERED = Position.MIDDLE_CENTER;
  45. public static final Position CENTERED_TOP = Position.TOP_CENTER;
  46. public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
  47. public static final Position TOP_LEFT = Position.TOP_LEFT;
  48. public static final Position TOP_RIGHT = Position.TOP_RIGHT;
  49. public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
  50. public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
  51. private static final String STYLENAME_POSITION_TOP = "v-position-top";
  52. private static final String STYLENAME_POSITION_RIGHT = "v-position-right";
  53. private static final String STYLENAME_POSITION_BOTTOM = "v-position-bottom";
  54. private static final String STYLENAME_POSITION_LEFT = "v-position-left";
  55. private static final String STYLENAME_POSITION_MIDDLE = "v-position-middle";
  56. private static final String STYLENAME_POSITION_CENTER = "v-position-center";
  57. private static final String STYLENAME_POSITION_ASSISTIVE = "v-position-assistive";
  58. /**
  59. * Position that is only accessible for assistive devices, invisible for
  60. * visual users.
  61. */
  62. public static final Position ASSISTIVE = Position.ASSISTIVE;
  63. public static final int DELAY_FOREVER = -1;
  64. public static final int DELAY_NONE = 0;
  65. private static final String STYLENAME = "v-Notification";
  66. private static final int mouseMoveThreshold = 7;
  67. private static final int Z_INDEX_BASE = 20000;
  68. public static final String STYLE_SYSTEM = "system";
  69. private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>();
  70. private boolean infiniteDelay = false;
  71. private int hideDelay = 0;
  72. private Timer delay;
  73. private int x = -1;
  74. private int y = -1;
  75. private String temporaryStyle;
  76. private ArrayList<EventListener> listeners;
  77. private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
  78. /**
  79. * Default constructor. You should use GWT.create instead.
  80. */
  81. public VNotification() {
  82. setStyleName(STYLENAME);
  83. sinkEvents(Event.ONCLICK);
  84. getElement().getStyle().setZIndex(Z_INDEX_BASE);
  85. }
  86. /**
  87. * @deprecated Use static {@link #createNotification(int)} instead to enable
  88. * GWT deferred binding.
  89. *
  90. * @param delayMsec
  91. */
  92. @Deprecated
  93. public VNotification(int delayMsec) {
  94. this();
  95. setDelay(delayMsec);
  96. if (BrowserInfo.get().isTouchDevice()) {
  97. new Timer() {
  98. @Override
  99. public void run() {
  100. if (isAttached()) {
  101. hide();
  102. }
  103. }
  104. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  105. }
  106. }
  107. /**
  108. * @deprecated Use static {@link #createNotification(int, int, int)} instead
  109. * to enable GWT deferred binding.
  110. *
  111. * @param delayMsec
  112. * @param fadeMsec
  113. * @param startOpacity
  114. */
  115. @Deprecated
  116. public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
  117. this(delayMsec);
  118. AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms");
  119. getElement().getStyle().setOpacity(startOpacity / 100);
  120. }
  121. private void setDelay(int delayMsec) {
  122. if (delayMsec < 0) {
  123. infiniteDelay = true;
  124. hideDelay = 0;
  125. } else {
  126. infiniteDelay = false;
  127. hideDelay = delayMsec;
  128. }
  129. }
  130. @Override
  131. public void show() {
  132. show(CENTERED);
  133. }
  134. public void show(String style) {
  135. show(CENTERED, style);
  136. }
  137. public void show(com.vaadin.shared.Position position) {
  138. show(position, null);
  139. }
  140. public void show(Widget widget, Position position, String style) {
  141. NotificationTypeConfiguration styleSetup = getUiState(style);
  142. setWaiAriaRole(styleSetup);
  143. FlowPanel panel = new FlowPanel();
  144. if (hasPrefix(styleSetup)) {
  145. panel.add(new Label(styleSetup.prefix));
  146. AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
  147. true);
  148. }
  149. panel.add(widget);
  150. if (hasPostfix(styleSetup)) {
  151. panel.add(new Label(styleSetup.postfix));
  152. AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
  153. true);
  154. }
  155. setWidget(panel);
  156. show(position, style);
  157. }
  158. private boolean hasPostfix(NotificationTypeConfiguration styleSetup) {
  159. return styleSetup != null && styleSetup.postfix != null
  160. && !styleSetup.postfix.isEmpty();
  161. }
  162. private boolean hasPrefix(NotificationTypeConfiguration styleSetup) {
  163. return styleSetup != null && styleSetup.prefix != null
  164. && !styleSetup.prefix.isEmpty();
  165. }
  166. public void show(String html, Position position, String style) {
  167. NotificationTypeConfiguration styleSetup = getUiState(style);
  168. String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE;
  169. setWaiAriaRole(styleSetup);
  170. String type = "";
  171. String usage = "";
  172. if (hasPrefix(styleSetup)) {
  173. type = "<span class='" + assistiveDeviceOnlyStyle + "'>"
  174. + styleSetup.prefix + "</span>";
  175. }
  176. if (hasPostfix(styleSetup)) {
  177. usage = "<span class='" + assistiveDeviceOnlyStyle + "'>"
  178. + styleSetup.postfix + "</span>";
  179. }
  180. setWidget(new HTML(type + html + usage));
  181. show(position, style);
  182. }
  183. private NotificationTypeConfiguration getUiState(String style) {
  184. if (getApplicationConnection() == null
  185. || getApplicationConnection().getUIConnector() == null) {
  186. return null;
  187. }
  188. return getApplicationConnection().getUIConnector().getState().notificationConfigurations
  189. .get(style);
  190. }
  191. private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) {
  192. Roles.getAlertRole().set(getElement());
  193. if (styleSetup != null && styleSetup.notificationRole != null) {
  194. if (NotificationRole.STATUS == styleSetup.notificationRole) {
  195. Roles.getStatusRole().set(getElement());
  196. }
  197. }
  198. }
  199. public void show(Position position, String style) {
  200. if (temporaryStyle != null) {
  201. removeStyleName(temporaryStyle);
  202. removeStyleDependentName(temporaryStyle);
  203. temporaryStyle = null;
  204. }
  205. if (style != null && style.length() > 0) {
  206. temporaryStyle = style;
  207. addStyleName(style);
  208. addStyleDependentName(style);
  209. }
  210. setPosition(position);
  211. super.show();
  212. updatePositionOffsets(position);
  213. notifications.add(this);
  214. positionOrSizeUpdated();
  215. /**
  216. * Android 4 fails to render notifications correctly without a little
  217. * nudge (#8551)
  218. */
  219. if (BrowserInfo.get().isAndroid()) {
  220. WidgetUtil.setStyleTemporarily(getElement(), "display", "none");
  221. }
  222. }
  223. protected void hideAfterDelay() {
  224. if (delay == null) {
  225. delay = new Timer() {
  226. @Override
  227. public void run() {
  228. VNotification.super.hide();
  229. }
  230. };
  231. delay.schedule(hideDelay);
  232. }
  233. }
  234. @Override
  235. public void hide() {
  236. if (delay != null) {
  237. delay.cancel();
  238. }
  239. // Run only once
  240. if (notifications.contains(this)) {
  241. DOM.removeEventPreview(this);
  242. // Still animating in, wait for it to finish before touching
  243. // the animation delay (which would restart the animation-in
  244. // in some browsers)
  245. if (getStyleName().contains(
  246. VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  247. AnimationUtil.addAnimationEndListener(getElement(),
  248. new AnimationEndListener() {
  249. @Override
  250. public void onAnimationEnd(NativeEvent event) {
  251. if (AnimationUtil
  252. .getAnimationName(event)
  253. .contains(
  254. VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  255. VNotification.this.hide();
  256. }
  257. }
  258. });
  259. } else {
  260. VNotification.super.hide();
  261. fireEvent(new HideEvent(this));
  262. notifications.remove(this);
  263. }
  264. }
  265. }
  266. private void updatePositionOffsets(com.vaadin.shared.Position position) {
  267. final Element el = getElement();
  268. // Remove all offsets (GWT PopupPanel defaults)
  269. el.getStyle().clearTop();
  270. el.getStyle().clearLeft();
  271. switch (position) {
  272. case MIDDLE_LEFT:
  273. case MIDDLE_RIGHT:
  274. center();
  275. el.getStyle().clearLeft();
  276. break;
  277. case TOP_CENTER:
  278. case BOTTOM_CENTER:
  279. center();
  280. el.getStyle().clearTop();
  281. break;
  282. case MIDDLE_CENTER:
  283. center();
  284. break;
  285. }
  286. }
  287. public void setPosition(com.vaadin.shared.Position position) {
  288. final Element el = getElement();
  289. // Remove any previous positions
  290. el.removeClassName(STYLENAME_POSITION_TOP);
  291. el.removeClassName(STYLENAME_POSITION_RIGHT);
  292. el.removeClassName(STYLENAME_POSITION_BOTTOM);
  293. el.removeClassName(STYLENAME_POSITION_LEFT);
  294. el.removeClassName(STYLENAME_POSITION_MIDDLE);
  295. el.removeClassName(STYLENAME_POSITION_CENTER);
  296. el.removeClassName(STYLENAME_POSITION_ASSISTIVE);
  297. switch (position) {
  298. case TOP_LEFT:
  299. el.addClassName(STYLENAME_POSITION_TOP);
  300. el.addClassName(STYLENAME_POSITION_LEFT);
  301. break;
  302. case TOP_RIGHT:
  303. el.addClassName(STYLENAME_POSITION_TOP);
  304. el.addClassName(STYLENAME_POSITION_RIGHT);
  305. break;
  306. case MIDDLE_LEFT:
  307. el.addClassName(STYLENAME_POSITION_MIDDLE);
  308. el.addClassName(STYLENAME_POSITION_LEFT);
  309. break;
  310. case MIDDLE_RIGHT:
  311. el.addClassName(STYLENAME_POSITION_MIDDLE);
  312. el.addClassName(STYLENAME_POSITION_RIGHT);
  313. break;
  314. case BOTTOM_RIGHT:
  315. el.addClassName(STYLENAME_POSITION_BOTTOM);
  316. el.addClassName(STYLENAME_POSITION_RIGHT);
  317. break;
  318. case BOTTOM_LEFT:
  319. el.addClassName(STYLENAME_POSITION_BOTTOM);
  320. el.addClassName(STYLENAME_POSITION_LEFT);
  321. break;
  322. case TOP_CENTER:
  323. el.addClassName(STYLENAME_POSITION_TOP);
  324. el.addClassName(STYLENAME_POSITION_CENTER);
  325. break;
  326. case BOTTOM_CENTER:
  327. el.addClassName(STYLENAME_POSITION_BOTTOM);
  328. el.addClassName(STYLENAME_POSITION_CENTER);
  329. break;
  330. case ASSISTIVE:
  331. el.addClassName(STYLENAME_POSITION_ASSISTIVE);
  332. break;
  333. }
  334. }
  335. @Override
  336. public void onBrowserEvent(Event event) {
  337. hide();
  338. }
  339. @Override
  340. public boolean onEventPreview(Event event) {
  341. int type = DOM.eventGetType(event);
  342. // "modal"
  343. if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) {
  344. if (type == Event.ONCLICK || type == Event.ONTOUCHEND) {
  345. if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
  346. hide();
  347. return false;
  348. }
  349. } else if (type == Event.ONKEYDOWN
  350. && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
  351. hide();
  352. return false;
  353. }
  354. if (temporaryStyle == STYLE_SYSTEM) {
  355. return true;
  356. } else {
  357. return false;
  358. }
  359. }
  360. // default
  361. switch (type) {
  362. case Event.ONMOUSEMOVE:
  363. if (x < 0) {
  364. x = DOM.eventGetClientX(event);
  365. y = DOM.eventGetClientY(event);
  366. } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
  367. || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
  368. hideAfterDelay();
  369. }
  370. break;
  371. case Event.ONMOUSEDOWN:
  372. case Event.ONMOUSEWHEEL:
  373. case Event.ONSCROLL:
  374. hideAfterDelay();
  375. break;
  376. case Event.ONKEYDOWN:
  377. if (event.getRepeat()) {
  378. return true;
  379. }
  380. hideAfterDelay();
  381. break;
  382. default:
  383. break;
  384. }
  385. return true;
  386. }
  387. public void addEventListener(EventListener listener) {
  388. if (listeners == null) {
  389. listeners = new ArrayList<EventListener>();
  390. }
  391. listeners.add(listener);
  392. }
  393. public void removeEventListener(EventListener listener) {
  394. if (listeners == null) {
  395. return;
  396. }
  397. listeners.remove(listener);
  398. }
  399. private void fireEvent(HideEvent event) {
  400. if (listeners != null) {
  401. for (Iterator<EventListener> it = listeners.iterator(); it
  402. .hasNext();) {
  403. EventListener l = it.next();
  404. l.notificationHidden(event);
  405. }
  406. }
  407. }
  408. public static void showNotification(ApplicationConnection client,
  409. final UIDL notification) {
  410. boolean onlyPlainText = notification
  411. .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
  412. String html = "";
  413. if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
  414. String iconUri = notification
  415. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON);
  416. html += client.getIcon(iconUri).getElement().getString();
  417. }
  418. if (notification
  419. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
  420. String caption = notification
  421. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
  422. if (onlyPlainText) {
  423. caption = WidgetUtil.escapeHTML(caption);
  424. caption = caption.replaceAll("\\n", "<br />");
  425. }
  426. html += "<h1>" + caption + "</h1>";
  427. }
  428. if (notification
  429. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
  430. String message = notification
  431. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
  432. if (onlyPlainText) {
  433. message = WidgetUtil.escapeHTML(message);
  434. message = message.replaceAll("\\n", "<br />");
  435. }
  436. html += "<p>" + message + "</p>";
  437. }
  438. final String style = notification
  439. .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
  440. .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
  441. : null;
  442. final int pos = notification
  443. .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
  444. Position position = Position.values()[pos];
  445. final int delay = notification
  446. .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
  447. createNotification(delay, client.getUIConnector().getWidget()).show(
  448. html, position, style);
  449. }
  450. public static VNotification createNotification(int delayMsec, Widget owner) {
  451. final VNotification notification = GWT.create(VNotification.class);
  452. notification.setWaiAriaRole(null);
  453. notification.setDelay(delayMsec);
  454. if (!notification.infiniteDelay && BrowserInfo.get().isTouchDevice()) {
  455. new Timer() {
  456. @Override
  457. public void run() {
  458. if (notification.isAttached()) {
  459. notification.hide();
  460. }
  461. }
  462. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  463. }
  464. notification.setOwner(owner);
  465. return notification;
  466. }
  467. public class HideEvent extends EventObject {
  468. public HideEvent(Object source) {
  469. super(source);
  470. }
  471. }
  472. public interface EventListener extends java.util.EventListener {
  473. public void notificationHidden(HideEvent event);
  474. }
  475. /**
  476. * Moves currently visible notifications to the top of the event preview
  477. * stack. Can be called when opening other overlays such as subwindows to
  478. * ensure the notifications receive the events they need and don't linger
  479. * indefinitely. See #7136.
  480. *
  481. * TODO Should this be a generic Overlay feature instead?
  482. */
  483. public static void bringNotificationsToFront() {
  484. for (VNotification notification : notifications) {
  485. DOM.removeEventPreview(notification);
  486. DOM.addEventPreview(notification);
  487. }
  488. }
  489. }