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.

Page.java 41KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237
  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.server;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.net.URI;
  20. import java.net.URISyntaxException;
  21. import java.util.EventObject;
  22. import java.util.LinkedHashSet;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import com.vaadin.event.EventRouter;
  26. import com.vaadin.event.FieldEvents.FocusListener;
  27. import com.vaadin.shared.Registration;
  28. import com.vaadin.shared.ui.BorderStyle;
  29. import com.vaadin.shared.ui.ui.PageClientRpc;
  30. import com.vaadin.shared.ui.ui.PageState;
  31. import com.vaadin.shared.ui.ui.UIConstants;
  32. import com.vaadin.shared.ui.ui.UIState;
  33. import com.vaadin.shared.util.SharedUtil;
  34. import com.vaadin.ui.JavaScript;
  35. import com.vaadin.ui.LegacyWindow;
  36. import com.vaadin.ui.Link;
  37. import com.vaadin.ui.Notification;
  38. import com.vaadin.ui.UI;
  39. import com.vaadin.util.ReflectTools;
  40. public class Page implements Serializable {
  41. /**
  42. * Listener that gets notified when the size of the browser window
  43. * containing the uI has changed.
  44. *
  45. * @see UI#addListener(BrowserWindowResizeListener)
  46. */
  47. @FunctionalInterface
  48. public interface BrowserWindowResizeListener extends Serializable {
  49. /**
  50. * Invoked when the browser window containing a UI has been resized.
  51. *
  52. * @param event
  53. * a browser window resize event
  54. */
  55. public void browserWindowResized(BrowserWindowResizeEvent event);
  56. }
  57. /**
  58. * Event that is fired when a browser window containing a uI is resized.
  59. */
  60. public static class BrowserWindowResizeEvent extends EventObject {
  61. private final int width;
  62. private final int height;
  63. /**
  64. * Creates a new event
  65. *
  66. * @param source
  67. * the uI for which the browser window has been resized
  68. * @param width
  69. * the new width of the browser window
  70. * @param height
  71. * the new height of the browser window
  72. */
  73. public BrowserWindowResizeEvent(Page source, int width, int height) {
  74. super(source);
  75. this.width = width;
  76. this.height = height;
  77. }
  78. @Override
  79. public Page getSource() {
  80. return (Page) super.getSource();
  81. }
  82. /**
  83. * Gets the new browser window height
  84. *
  85. * @return an integer with the new pixel height of the browser window
  86. */
  87. public int getHeight() {
  88. return height;
  89. }
  90. /**
  91. * Gets the new browser window width
  92. *
  93. * @return an integer with the new pixel width of the browser window
  94. */
  95. public int getWidth() {
  96. return width;
  97. }
  98. }
  99. /**
  100. * Private class for storing properties related to opening resources.
  101. */
  102. private class OpenResource implements Serializable {
  103. /**
  104. * The resource to open
  105. */
  106. private final Resource resource;
  107. /**
  108. * The name of the target window
  109. */
  110. private final String name;
  111. /**
  112. * The width of the target window
  113. */
  114. private final int width;
  115. /**
  116. * The height of the target window
  117. */
  118. private final int height;
  119. /**
  120. * The border style of the target window
  121. */
  122. private final BorderStyle border;
  123. private final boolean tryToOpenAsPopup;
  124. /**
  125. * Creates a new open resource.
  126. *
  127. * @param url
  128. * The URL to open
  129. * @param name
  130. * The name of the target window
  131. * @param width
  132. * The width of the target window
  133. * @param height
  134. * The height of the target window
  135. * @param border
  136. * The border style of the target window
  137. * @param tryToOpenAsPopup
  138. * Should try to open as a pop-up
  139. */
  140. private OpenResource(String url, String name, int width, int height,
  141. BorderStyle border, boolean tryToOpenAsPopup) {
  142. this(new ExternalResource(url), name, width, height, border,
  143. tryToOpenAsPopup);
  144. }
  145. /**
  146. * Creates a new open resource.
  147. *
  148. * @param resource
  149. * The resource to open
  150. * @param name
  151. * The name of the target window
  152. * @param width
  153. * The width of the target window
  154. * @param height
  155. * The height of the target window
  156. * @param border
  157. * The border style of the target window
  158. * @param tryToOpenAsPopup
  159. * Should try to open as a pop-up
  160. */
  161. private OpenResource(Resource resource, String name, int width,
  162. int height, BorderStyle border, boolean tryToOpenAsPopup) {
  163. this.resource = resource;
  164. this.name = name;
  165. this.width = width;
  166. this.height = height;
  167. this.border = border;
  168. this.tryToOpenAsPopup = tryToOpenAsPopup;
  169. }
  170. /**
  171. * Paints the open request. Should be painted inside the window.
  172. *
  173. * @param target
  174. * the paint target
  175. * @throws PaintException
  176. * if the paint operation fails
  177. */
  178. private void paintContent(PaintTarget target) throws PaintException {
  179. target.startTag("open");
  180. target.addAttribute("src", resource);
  181. if (name != null && name.length() > 0) {
  182. target.addAttribute("name", name);
  183. }
  184. if (!tryToOpenAsPopup) {
  185. target.addAttribute("popup", tryToOpenAsPopup);
  186. }
  187. if (width >= 0) {
  188. target.addAttribute("width", width);
  189. }
  190. if (height >= 0) {
  191. target.addAttribute("height", height);
  192. }
  193. switch (border) {
  194. case MINIMAL:
  195. target.addAttribute("border", "minimal");
  196. break;
  197. case NONE:
  198. target.addAttribute("border", "none");
  199. break;
  200. }
  201. target.endTag("open");
  202. }
  203. }
  204. private static final Method BROWSER_RESIZE_METHOD = ReflectTools.findMethod(
  205. BrowserWindowResizeListener.class, "browserWindowResized",
  206. BrowserWindowResizeEvent.class);
  207. /**
  208. * @deprecated As of 7.0, use {@link BorderStyle#NONE} instead.
  209. */
  210. @Deprecated
  211. public static final BorderStyle BORDER_NONE = BorderStyle.NONE;
  212. /**
  213. * @deprecated As of 7.0, use {@link BorderStyle#MINIMAL} instead.
  214. */
  215. @Deprecated
  216. public static final BorderStyle BORDER_MINIMAL = BorderStyle.MINIMAL;
  217. /**
  218. * @deprecated As of 7.0, use {@link BorderStyle#DEFAULT} instead.
  219. */
  220. @Deprecated
  221. public static final BorderStyle BORDER_DEFAULT = BorderStyle.DEFAULT;
  222. /**
  223. * Listener that that gets notified when the URI fragment of the page
  224. * changes.
  225. *
  226. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  227. */
  228. @FunctionalInterface
  229. public interface UriFragmentChangedListener extends Serializable {
  230. /**
  231. * Event handler method invoked when the URI fragment of the page
  232. * changes. Please note that the initial URI fragment has already been
  233. * set when a new UI is initialized, so there will not be any initial
  234. * event for listeners added during {@link UI#init(VaadinRequest)}.
  235. *
  236. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  237. *
  238. * @param event
  239. * the URI fragment changed event
  240. */
  241. public void uriFragmentChanged(UriFragmentChangedEvent event);
  242. }
  243. private static final Method URI_FRAGMENT_CHANGED_METHOD = ReflectTools
  244. .findMethod(Page.UriFragmentChangedListener.class,
  245. "uriFragmentChanged", UriFragmentChangedEvent.class);
  246. /**
  247. * Resources to be opened automatically on next repaint. The list is
  248. * automatically cleared when it has been sent to the client.
  249. */
  250. private final LinkedList<OpenResource> openList = new LinkedList<>();
  251. /**
  252. * A list of notifications that are waiting to be sent to the client.
  253. * Cleared (set to null) when the notifications have been sent.
  254. */
  255. private List<Notification> notifications;
  256. /**
  257. * Event fired when the URI fragment of a <code>Page</code> changes.
  258. *
  259. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  260. */
  261. public static class UriFragmentChangedEvent extends EventObject {
  262. /**
  263. * The new URI fragment
  264. */
  265. private final String uriFragment;
  266. /**
  267. * Creates a new instance of UriFragmentReader change event.
  268. *
  269. * @param source
  270. * the Source of the event.
  271. * @param uriFragment
  272. * the new uriFragment
  273. */
  274. public UriFragmentChangedEvent(Page source, String uriFragment) {
  275. super(source);
  276. this.uriFragment = uriFragment;
  277. }
  278. /**
  279. * Gets the page in which the fragment has changed.
  280. *
  281. * @return the page in which the fragment has changed
  282. */
  283. public Page getPage() {
  284. return (Page) getSource();
  285. }
  286. /**
  287. * Get the new URI fragment
  288. *
  289. * @return the new fragment
  290. */
  291. public String getUriFragment() {
  292. return uriFragment;
  293. }
  294. }
  295. @FunctionalInterface
  296. private static interface InjectedStyle extends Serializable {
  297. public void paint(int id, PaintTarget target) throws PaintException;
  298. }
  299. private static class InjectedStyleString implements InjectedStyle {
  300. private final String css;
  301. public InjectedStyleString(String css) {
  302. this.css = css;
  303. }
  304. @Override
  305. public void paint(int id, PaintTarget target) throws PaintException {
  306. target.startTag("css-string");
  307. target.addAttribute("id", id);
  308. target.addText(css);
  309. target.endTag("css-string");
  310. }
  311. }
  312. private static class InjectedStyleResource implements InjectedStyle {
  313. private final Resource resource;
  314. public InjectedStyleResource(Resource resource) {
  315. this.resource = resource;
  316. }
  317. @Override
  318. public void paint(int id, PaintTarget target) throws PaintException {
  319. target.startTag("css-resource");
  320. target.addAttribute("id", id);
  321. target.addAttribute("url", resource);
  322. target.endTag("css-resource");
  323. }
  324. @Override
  325. public boolean equals(Object obj) {
  326. if (obj == this) {
  327. return true;
  328. } else if (obj instanceof InjectedStyleResource) {
  329. InjectedStyleResource that = (InjectedStyleResource) obj;
  330. return resource.equals(that.resource);
  331. } else {
  332. return false;
  333. }
  334. }
  335. @Override
  336. public int hashCode() {
  337. return resource.hashCode();
  338. }
  339. }
  340. /**
  341. * Contains dynamically injected styles injected in the HTML document at
  342. * runtime.
  343. *
  344. * @since 7.1
  345. */
  346. public static class Styles implements Serializable {
  347. private LinkedHashSet<InjectedStyle> injectedStyles = new LinkedHashSet<>();
  348. private LinkedHashSet<InjectedStyle> pendingInjections = new LinkedHashSet<>();
  349. private final UI ui;
  350. private Styles(UI ui) {
  351. this.ui = ui;
  352. }
  353. /**
  354. * Injects a raw CSS string into the page.
  355. *
  356. * @param css
  357. * The CSS to inject
  358. */
  359. public void add(String css) {
  360. if (css == null) {
  361. throw new IllegalArgumentException(
  362. "Cannot inject null CSS string");
  363. }
  364. pendingInjections.add(new InjectedStyleString(css));
  365. ui.markAsDirty();
  366. }
  367. /**
  368. * Injects a CSS resource into the page
  369. *
  370. * @param resource
  371. * The resource to inject.
  372. */
  373. public void add(Resource resource) {
  374. if (resource == null) {
  375. throw new IllegalArgumentException(
  376. "Cannot inject null resource");
  377. }
  378. InjectedStyleResource injection = new InjectedStyleResource(
  379. resource);
  380. if (!injectedStyles.contains(injection)
  381. && pendingInjections.add(injection)) {
  382. ui.markAsDirty();
  383. }
  384. }
  385. private void paint(PaintTarget target) throws PaintException {
  386. // If full repaint repaint all injections
  387. if (target.isFullRepaint()) {
  388. injectedStyles.addAll(pendingInjections);
  389. pendingInjections = injectedStyles;
  390. injectedStyles = new LinkedHashSet<>();
  391. }
  392. if (!pendingInjections.isEmpty()) {
  393. target.startTag("css-injections");
  394. for (InjectedStyle pending : pendingInjections) {
  395. int id = injectedStyles.size();
  396. pending.paint(id, target);
  397. injectedStyles.add(pending);
  398. }
  399. pendingInjections.clear();
  400. target.endTag("css-injections");
  401. }
  402. }
  403. }
  404. private EventRouter eventRouter;
  405. private final UI uI;
  406. private int browserWindowWidth = -1;
  407. private int browserWindowHeight = -1;
  408. private JavaScript javaScript;
  409. private Styles styles;
  410. /**
  411. * The current browser location.
  412. */
  413. private URI location;
  414. private final PageState state;
  415. private String windowName;
  416. public Page(UI uI, PageState state) {
  417. this.uI = uI;
  418. this.state = state;
  419. }
  420. private Registration addListener(Class<?> eventType, Object target,
  421. Method method) {
  422. if (!hasEventRouter()) {
  423. eventRouter = new EventRouter();
  424. }
  425. return eventRouter.addListener(eventType, target, method);
  426. }
  427. private void removeListener(Class<?> eventType, Object target,
  428. Method method) {
  429. if (hasEventRouter()) {
  430. eventRouter.removeListener(eventType, target, method);
  431. }
  432. }
  433. /**
  434. * Adds a listener that gets notified every time the URI fragment of this
  435. * page is changed. Please note that the initial URI fragment has already
  436. * been set when a new UI is initialized, so there will not be any initial
  437. * event for listeners added during {@link UI#init(VaadinRequest)}.
  438. *
  439. * @see #getUriFragment()
  440. * @see #setUriFragment(String)
  441. * @see Registration
  442. *
  443. * @param listener
  444. * the URI fragment listener to add
  445. * @return a registration object for removing the listener
  446. */
  447. public Registration addUriFragmentChangedListener(
  448. Page.UriFragmentChangedListener listener) {
  449. return addListener(UriFragmentChangedEvent.class, listener,
  450. URI_FRAGMENT_CHANGED_METHOD);
  451. }
  452. /**
  453. * Removes a URI fragment listener that was previously added to this page.
  454. *
  455. * @param listener
  456. * the URI fragment listener to remove
  457. *
  458. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  459. *
  460. * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
  461. * registration object returned from
  462. * {@link #addUriFragmentChangedListener(FocusListener)}.
  463. */
  464. @Deprecated
  465. public void removeUriFragmentChangedListener(
  466. Page.UriFragmentChangedListener listener) {
  467. removeListener(UriFragmentChangedEvent.class, listener,
  468. URI_FRAGMENT_CHANGED_METHOD);
  469. }
  470. /**
  471. * Sets the fragment part in the current location URI. Optionally fires a
  472. * {@link UriFragmentChangedEvent}.
  473. * <p>
  474. * The fragment is the optional last component of a URI, prefixed with a
  475. * hash sign ("#").
  476. * <p>
  477. * Passing an empty string as <code>newFragment</code> sets an empty
  478. * fragment (a trailing "#" in the URI.) Passing <code>null</code> if there
  479. * is already a non-null fragment will leave a trailing # in the URI since
  480. * removing it would cause the browser to reload the page. This is not fully
  481. * consistent with the semantics of {@link java.net.URI}.
  482. *
  483. * @param newUriFragment
  484. * The new fragment.
  485. * @param fireEvents
  486. * true to fire event
  487. *
  488. * @see #getUriFragment()
  489. * @see #setLocation(URI)
  490. * @see UriFragmentChangedEvent
  491. * @see Page.UriFragmentChangedListener
  492. *
  493. */
  494. public void setUriFragment(String newUriFragment, boolean fireEvents) {
  495. String oldUriFragment = location.getFragment();
  496. if (newUriFragment == null && getUriFragment() != null) {
  497. // Can't completely remove the fragment once it has been set, will
  498. // instead set it to the empty string
  499. newUriFragment = "";
  500. }
  501. if (newUriFragment == oldUriFragment || (newUriFragment != null
  502. && newUriFragment.equals(oldUriFragment))) {
  503. return;
  504. }
  505. try {
  506. location = new URI(location.getScheme(),
  507. location.getSchemeSpecificPart(), newUriFragment);
  508. } catch (URISyntaxException e) {
  509. // This should not actually happen as the fragment syntax is not
  510. // constrained
  511. throw new RuntimeException(e);
  512. }
  513. if (fireEvents) {
  514. fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
  515. }
  516. uI.markAsDirty();
  517. }
  518. private void fireEvent(EventObject event) {
  519. if (hasEventRouter()) {
  520. eventRouter.fireEvent(event);
  521. }
  522. }
  523. /**
  524. * Sets URI fragment. This method fires a {@link UriFragmentChangedEvent}
  525. *
  526. * @param newUriFragment
  527. * id of the new fragment
  528. * @see UriFragmentChangedEvent
  529. * @see Page.UriFragmentChangedListener
  530. */
  531. public void setUriFragment(String newUriFragment) {
  532. setUriFragment(newUriFragment, true);
  533. }
  534. /**
  535. * Gets the currently set URI fragment.
  536. * <p>
  537. * Returns <code>null</code> if there is no fragment and an empty string if
  538. * there is an empty fragment.
  539. * <p>
  540. * To listen to changes in fragment, hook a
  541. * {@link Page.UriFragmentChangedListener}.
  542. *
  543. * @return the current fragment in browser location URI.
  544. *
  545. * @see #getLocation()
  546. * @see #setUriFragment(String)
  547. * @see #addUriFragmentChangedListener(UriFragmentChangedListener)
  548. */
  549. public String getUriFragment() {
  550. return location.getFragment();
  551. }
  552. public void init(VaadinRequest request) {
  553. // NOTE: UI.refresh makes assumptions about the semantics of this
  554. // method.
  555. // It should be kept in sync if this method is changed.
  556. // Extract special parameter sent by vaadinBootstrap.js
  557. String location = request.getParameter("v-loc");
  558. String clientWidth = request.getParameter("v-cw");
  559. String clientHeight = request.getParameter("v-ch");
  560. windowName = request.getParameter("v-wn");
  561. if (location != null) {
  562. try {
  563. this.location = new URI(location);
  564. } catch (URISyntaxException e) {
  565. throw new RuntimeException(
  566. "Invalid location URI received from client", e);
  567. }
  568. }
  569. if (clientWidth != null && clientHeight != null) {
  570. try {
  571. browserWindowWidth = Integer.parseInt(clientWidth);
  572. browserWindowHeight = Integer.parseInt(clientHeight);
  573. } catch (NumberFormatException e) {
  574. throw new RuntimeException(
  575. "Invalid window size received from client", e);
  576. }
  577. }
  578. }
  579. public WebBrowser getWebBrowser() {
  580. return uI.getSession().getBrowser();
  581. }
  582. /**
  583. * Gets the window.name value of the browser window of this page.
  584. *
  585. * @since 7.2
  586. *
  587. * @return the window name, <code>null</code> if the name is not known
  588. */
  589. public String getWindowName() {
  590. return windowName;
  591. }
  592. /**
  593. * For internal use only. Updates the internal state with the given values.
  594. * Does not resize the Page or browser window.
  595. *
  596. * @deprecated As of 7.2, use
  597. * {@link #updateBrowserWindowSize(int, int, boolean)} instead.
  598. *
  599. * @param width
  600. * the new browser window width
  601. * @param height
  602. * the new browse window height
  603. */
  604. @Deprecated
  605. public void updateBrowserWindowSize(int width, int height) {
  606. updateBrowserWindowSize(width, height, true);
  607. }
  608. /**
  609. * For internal use only. Updates the internal state with the given values.
  610. * Does not resize the Page or browser window.
  611. *
  612. * @since 7.2
  613. *
  614. * @param width
  615. * the new browser window width
  616. * @param height
  617. * the new browser window height
  618. * @param fireEvents
  619. * whether to fire {@link BrowserWindowResizeEvent} if the size
  620. * changes
  621. */
  622. public void updateBrowserWindowSize(int width, int height,
  623. boolean fireEvents) {
  624. boolean sizeChanged = false;
  625. if (width != browserWindowWidth) {
  626. browserWindowWidth = width;
  627. sizeChanged = true;
  628. }
  629. if (height != browserWindowHeight) {
  630. browserWindowHeight = height;
  631. sizeChanged = true;
  632. }
  633. if (fireEvents && sizeChanged) {
  634. fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth,
  635. browserWindowHeight));
  636. }
  637. }
  638. /**
  639. * Adds a new {@link BrowserWindowResizeListener} to this UI. The listener
  640. * will be notified whenever the browser window within which this UI resides
  641. * is resized.
  642. * <p>
  643. * In most cases, the UI should be in lazy resize mode when using browser
  644. * window resize listeners. Otherwise, a large number of events can be
  645. * received while a resize is being performed. Use
  646. * {@link UI#setResizeLazy(boolean)}.
  647. * </p>
  648. *
  649. * @param resizeListener
  650. * the listener to add
  651. * @return a registration object for removing the listener
  652. *
  653. * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent)
  654. * @see UI#setResizeLazy(boolean)
  655. * @see Registration
  656. */
  657. public Registration addBrowserWindowResizeListener(
  658. BrowserWindowResizeListener resizeListener) {
  659. Registration registration = addListener(BrowserWindowResizeEvent.class,
  660. resizeListener, BROWSER_RESIZE_METHOD);
  661. getState(true).hasResizeListeners = true;
  662. return () -> {
  663. registration.remove();
  664. getState(true).hasResizeListeners = hasEventRouter()
  665. && eventRouter.hasListeners(BrowserWindowResizeEvent.class);
  666. };
  667. }
  668. /**
  669. * Removes a {@link BrowserWindowResizeListener} from this UI. The listener
  670. * will no longer be notified when the browser window is resized.
  671. *
  672. * @param resizeListener
  673. * the listener to remove
  674. *
  675. * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
  676. * registration object returned from
  677. * {@link #addBrowserWindowResizeListener(BrowserWindowResizeListener)}
  678. * .
  679. */
  680. @Deprecated
  681. public void removeBrowserWindowResizeListener(
  682. BrowserWindowResizeListener resizeListener) {
  683. removeListener(BrowserWindowResizeEvent.class, resizeListener,
  684. BROWSER_RESIZE_METHOD);
  685. getState(true).hasResizeListeners = hasEventRouter()
  686. && eventRouter.hasListeners(BrowserWindowResizeEvent.class);
  687. }
  688. /**
  689. * Gets the last known height of the browser window in which this UI
  690. * resides.
  691. *
  692. * @return the browser window height in pixels
  693. */
  694. public int getBrowserWindowHeight() {
  695. return browserWindowHeight;
  696. }
  697. /**
  698. * Gets the last known width of the browser window in which this uI resides.
  699. *
  700. * @return the browser window width in pixels
  701. */
  702. public int getBrowserWindowWidth() {
  703. return browserWindowWidth;
  704. }
  705. public JavaScript getJavaScript() {
  706. if (javaScript == null) {
  707. // Create and attach on first use
  708. javaScript = new JavaScript();
  709. javaScript.extend(uI);
  710. }
  711. return javaScript;
  712. }
  713. /**
  714. * Returns that stylesheet associated with this Page. The stylesheet
  715. * contains additional styles injected at runtime into the HTML document.
  716. *
  717. * @since 7.1
  718. */
  719. public Styles getStyles() {
  720. if (styles == null) {
  721. styles = new Styles(uI);
  722. }
  723. return styles;
  724. }
  725. public void paintContent(PaintTarget target) throws PaintException {
  726. if (!openList.isEmpty()) {
  727. for (OpenResource anOpenList : openList) {
  728. (anOpenList).paintContent(target);
  729. }
  730. openList.clear();
  731. }
  732. // Paint notifications
  733. if (notifications != null) {
  734. target.startTag("notifications");
  735. for (final Notification n : notifications) {
  736. target.startTag("notification");
  737. if (n.getCaption() != null) {
  738. target.addAttribute(
  739. UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION,
  740. n.getCaption());
  741. }
  742. if (n.getDescription() != null) {
  743. target.addAttribute(
  744. UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE,
  745. n.getDescription());
  746. }
  747. if (n.getIcon() != null) {
  748. target.addAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON,
  749. n.getIcon());
  750. }
  751. if (!n.isHtmlContentAllowed()) {
  752. target.addAttribute(
  753. UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED,
  754. true);
  755. }
  756. target.addAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION,
  757. n.getPosition().ordinal());
  758. target.addAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY,
  759. n.getDelayMsec());
  760. if (n.getStyleName() != null) {
  761. target.addAttribute(
  762. UIConstants.ATTRIBUTE_NOTIFICATION_STYLE,
  763. n.getStyleName());
  764. }
  765. target.endTag("notification");
  766. }
  767. target.endTag("notifications");
  768. notifications = null;
  769. }
  770. if (location != null) {
  771. target.addAttribute(UIConstants.LOCATION_VARIABLE,
  772. location.toString());
  773. }
  774. if (styles != null) {
  775. styles.paint(target);
  776. }
  777. }
  778. /**
  779. * Navigates this page to the given URI. The contents of this page in the
  780. * browser is replaced with whatever is returned for the given URI.
  781. * <p>
  782. * This method should not be used to start downloads, as the client side
  783. * will assume the browser will navigate away when opening the URI. Use one
  784. * of the {@code Page.open} methods or {@code FileDownloader} instead.
  785. *
  786. * @see #open(String, String)
  787. * @see FileDownloader
  788. *
  789. * @param uri
  790. * the URI to show
  791. */
  792. public void setLocation(String uri) {
  793. openList.add(
  794. new OpenResource(uri, "_self", -1, -1, BORDER_DEFAULT, false));
  795. uI.markAsDirty();
  796. }
  797. /**
  798. * Navigates this page to the given URI. The contents of this page in the
  799. * browser is replaced with whatever is returned for the given URI.
  800. * <p>
  801. * This method should not be used to start downloads, as the client side
  802. * will assume the browser will navigate away when opening the URI. Use one
  803. * of the {@code Page.open} methods or {@code FileDownloader} instead.
  804. *
  805. * @see #open(String, String)
  806. * @see FileDownloader
  807. *
  808. * @param uri
  809. * the URI to show
  810. */
  811. public void setLocation(URI uri) {
  812. setLocation(uri.toString());
  813. }
  814. /**
  815. * Returns the location URI of this page, as reported by the browser. Note
  816. * that this may not be consistent with the server URI the application is
  817. * deployed in due to potential proxies, redirections and similar.
  818. *
  819. * @return The browser location URI.
  820. */
  821. public URI getLocation() {
  822. if (location == null && !uI.getSession().getConfiguration()
  823. .isSendUrlsAsParameters()) {
  824. throw new IllegalStateException("Location is not available as the "
  825. + Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
  826. + " parameter is configured as false");
  827. }
  828. return location;
  829. }
  830. /**
  831. * For internal use only. Used to update the server-side location when the
  832. * client-side location changes.
  833. *
  834. * @deprecated As of 7.2, use {@link #updateLocation(String, boolean)}
  835. * instead.
  836. *
  837. * @param location
  838. * the new location URI
  839. */
  840. @Deprecated
  841. public void updateLocation(String location) {
  842. updateLocation(location, true);
  843. }
  844. /**
  845. * For internal use only. Used to update the server-side location when the
  846. * client-side location changes.
  847. *
  848. * @since 7.2
  849. *
  850. * @param location
  851. * the new location URI
  852. * @param fireEvents
  853. * whether to fire {@link UriFragmentChangedEvent} if the URI
  854. * fragment changes
  855. */
  856. public void updateLocation(String location, boolean fireEvents) {
  857. try {
  858. String oldUriFragment = this.location.getFragment();
  859. this.location = new URI(location);
  860. String newUriFragment = this.location.getFragment();
  861. if (fireEvents
  862. && !SharedUtil.equals(oldUriFragment, newUriFragment)) {
  863. fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
  864. }
  865. } catch (URISyntaxException e) {
  866. throw new RuntimeException(e);
  867. }
  868. }
  869. /**
  870. * Opens the given url in a window with the given name. Equivalent to
  871. * {@link #open(String, String, boolean) open} (url, windowName, true) .
  872. * <p>
  873. * The supplied {@code windowName} is used as the target name in a
  874. * window.open call in the client. This means that special values such as
  875. * "_blank", "_self", "_top", "_parent" have special meaning. An empty or
  876. * <code>null</code> window name is also a special case.
  877. * </p>
  878. * <p>
  879. * "", null and "_self" as {@code windowName} all causes the URL to be
  880. * opened in the current window, replacing any old contents. For
  881. * downloadable content you should avoid "_self" as "_self" causes the
  882. * client to skip rendering of any other changes as it considers them
  883. * irrelevant (the page will be replaced by the response from the URL). This
  884. * can speed up the opening of a URL, but it might also put the client side
  885. * into an inconsistent state if the window content is not completely
  886. * replaced e.g., if the URL is downloaded instead of displayed in the
  887. * browser.
  888. * </p>
  889. * <p>
  890. * "_blank" as {@code windowName} causes the URL to always be opened in a
  891. * new window or tab (depends on the browser and browser settings).
  892. * </p>
  893. * <p>
  894. * "_top" and "_parent" as {@code windowName} works as specified by the HTML
  895. * standard.
  896. * </p>
  897. * <p>
  898. * Any other {@code windowName} will open the URL in a window with that
  899. * name, either by opening a new window/tab in the browser or by replacing
  900. * the contents of an existing window with that name.
  901. * </p>
  902. * <p>
  903. * Please note that opening a popup window in this way may be blocked by the
  904. * browser's popup-blocker because the new browser window is opened when
  905. * processing a response from the server. To avoid this, you should instead
  906. * use {@link Link} for opening the window because browsers are more
  907. * forgiving when the window is opened directly from a client-side click
  908. * event.
  909. * </p>
  910. *
  911. * @param url
  912. * the URL to open.
  913. * @param windowName
  914. * the name of the window.
  915. */
  916. public void open(String url, String windowName) {
  917. open(url, windowName, true);
  918. }
  919. /**
  920. * Opens the given url in a window with the given name. Equivalent to
  921. * {@link #open(String, String, boolean) open} (url, windowName, true) .
  922. * <p>
  923. * The supplied {@code windowName} is used as the target name in a
  924. * window.open call in the client. This means that special values such as
  925. * "_blank", "_self", "_top", "_parent" have special meaning. An empty or
  926. * <code>null</code> window name is also a special case.
  927. * </p>
  928. * <p>
  929. * "", null and "_self" as {@code windowName} all causes the URL to be
  930. * opened in the current window, replacing any old contents. For
  931. * downloadable content you should avoid "_self" as "_self" causes the
  932. * client to skip rendering of any other changes as it considers them
  933. * irrelevant (the page will be replaced by the response from the URL). This
  934. * can speed up the opening of a URL, but it might also put the client side
  935. * into an inconsistent state if the window content is not completely
  936. * replaced e.g., if the URL is downloaded instead of displayed in the
  937. * browser.
  938. * </p>
  939. * <p>
  940. * "_blank" as {@code windowName} causes the URL to always be opened in a
  941. * new window or tab (depends on the browser and browser settings).
  942. * </p>
  943. * <p>
  944. * "_top" and "_parent" as {@code windowName} works as specified by the HTML
  945. * standard.
  946. * </p>
  947. * <p>
  948. * Any other {@code windowName} will open the URL in a window with that
  949. * name, either by opening a new window/tab in the browser or by replacing
  950. * the contents of an existing window with that name.
  951. * </p>
  952. * <p>
  953. * Please note that opening a popup window in this way may be blocked by the
  954. * browser's popup-blocker because the new browser window is opened when
  955. * processing a response from the server. To avoid this, you should instead
  956. * use {@link Link} for opening the window because browsers are more
  957. * forgiving when the window is opened directly from a client-side click
  958. * event.
  959. * </p>
  960. *
  961. * @param url
  962. * the URL to open.
  963. * @param windowName
  964. * the name of the window.
  965. * @param tryToOpenAsPopup
  966. * Whether to try to force the resource to be opened in a new
  967. * window
  968. */
  969. public void open(String url, String windowName, boolean tryToOpenAsPopup) {
  970. openList.add(new OpenResource(url, windowName, -1, -1, BORDER_DEFAULT,
  971. tryToOpenAsPopup));
  972. uI.markAsDirty();
  973. }
  974. /**
  975. * Opens the given URL in a window with the given size, border and name. For
  976. * more information on the meaning of {@code windowName}, see
  977. * {@link #open(String, String)}.
  978. * <p>
  979. * Please note that opening a popup window in this way may be blocked by the
  980. * browser's popup-blocker because the new browser window is opened when
  981. * processing a response from the server. To avoid this, you should instead
  982. * use {@link Link} for opening the window because browsers are more
  983. * forgiving when the window is opened directly from a client-side click
  984. * event.
  985. * </p>
  986. *
  987. * @param url
  988. * the URL to open.
  989. * @param windowName
  990. * the name of the window.
  991. * @param width
  992. * the width of the window in pixels
  993. * @param height
  994. * the height of the window in pixels
  995. * @param border
  996. * the border style of the window.
  997. */
  998. public void open(String url, String windowName, int width, int height,
  999. BorderStyle border) {
  1000. openList.add(
  1001. new OpenResource(url, windowName, width, height, border, true));
  1002. uI.markAsDirty();
  1003. }
  1004. /**
  1005. * @deprecated As of 7.0, only retained to maintain compatibility with
  1006. * LegacyWindow.open methods. See documentation for
  1007. * {@link LegacyWindow#open(Resource, String, int, int, BorderStyle)}
  1008. * for discussion about replacing API.
  1009. */
  1010. @Deprecated
  1011. public void open(Resource resource, String windowName, int width,
  1012. int height, BorderStyle border) {
  1013. openList.add(new OpenResource(resource, windowName, width, height,
  1014. border, true));
  1015. uI.markAsDirty();
  1016. }
  1017. /**
  1018. * @deprecated As of 7.0, only retained to maintain compatibility with
  1019. * LegacyWindow.open methods. See documentation for
  1020. * {@link LegacyWindow#open(Resource, String, boolean)} for
  1021. * discussion about replacing API.
  1022. */
  1023. @Deprecated
  1024. public void open(Resource resource, String windowName,
  1025. boolean tryToOpenAsPopup) {
  1026. openList.add(new OpenResource(resource, windowName, -1, -1,
  1027. BORDER_DEFAULT, tryToOpenAsPopup));
  1028. uI.markAsDirty();
  1029. }
  1030. /**
  1031. * Internal helper method to actually add a notification.
  1032. *
  1033. * @param notification
  1034. * the notification to add
  1035. */
  1036. private void addNotification(Notification notification) {
  1037. if (notifications == null) {
  1038. notifications = new LinkedList<>();
  1039. }
  1040. notifications.add(notification);
  1041. uI.markAsDirty();
  1042. }
  1043. /**
  1044. * Shows a notification message.
  1045. *
  1046. * @see Notification
  1047. *
  1048. * @param notification
  1049. * The notification message to show
  1050. *
  1051. * @deprecated As of 7.0, use Notification.show(Page) instead.
  1052. */
  1053. @Deprecated
  1054. public void showNotification(Notification notification) {
  1055. addNotification(notification);
  1056. }
  1057. /**
  1058. * Gets the Page to which the current uI belongs. This is automatically
  1059. * defined when processing requests to the server. In other cases, (e.g.
  1060. * from background threads), the current uI is not automatically defined.
  1061. *
  1062. * @see UI#getCurrent()
  1063. *
  1064. * @return the current page instance if available, otherwise
  1065. * <code>null</code>
  1066. */
  1067. public static Page getCurrent() {
  1068. UI currentUI = UI.getCurrent();
  1069. if (currentUI == null) {
  1070. return null;
  1071. }
  1072. return currentUI.getPage();
  1073. }
  1074. /**
  1075. * Sets the page title. The page title is displayed by the browser e.g. as
  1076. * the title of the browser window or as the title of the tab.
  1077. * <p>
  1078. * If the title is set to null, it will not left as-is. Set to empty string
  1079. * to clear the title.
  1080. *
  1081. * @param title
  1082. * the page title to set
  1083. */
  1084. public void setTitle(String title) {
  1085. getState(true).title = title;
  1086. }
  1087. /**
  1088. * Reloads the page in the browser.
  1089. */
  1090. public void reload() {
  1091. uI.getRpcProxy(PageClientRpc.class).reload();
  1092. }
  1093. /**
  1094. * Returns the page state.
  1095. * <p>
  1096. * The page state is transmitted to UIConnector together with
  1097. * {@link UIState} rather than as an individual entity.
  1098. * </p>
  1099. * <p>
  1100. * The state should be considered an internal detail of Page. Classes
  1101. * outside of Page should not access it directly but only through public
  1102. * APIs provided by Page.
  1103. * </p>
  1104. *
  1105. * @since 7.1
  1106. * @param markAsDirty
  1107. * true to mark the state as dirty
  1108. * @return PageState object that can be read in any case and modified if
  1109. * markAsDirty is true
  1110. */
  1111. protected PageState getState(boolean markAsDirty) {
  1112. if (markAsDirty) {
  1113. uI.markAsDirty();
  1114. }
  1115. return state;
  1116. }
  1117. private boolean hasEventRouter() {
  1118. return eventRouter != null;
  1119. }
  1120. }