com.vaadin.server.Page Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.server;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import com.vaadin.annotations.HtmlImport;
import com.vaadin.annotations.StyleSheet;
import com.vaadin.event.EventRouter;
import com.vaadin.event.SerializableEventListener;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.BorderStyle;
import com.vaadin.shared.ui.ui.PageClientRpc;
import com.vaadin.shared.ui.ui.PageState;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIState;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Dependency;
import com.vaadin.ui.JavaScript;
import com.vaadin.ui.LegacyWindow;
import com.vaadin.ui.Link;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.util.ReflectTools;
public class Page implements Serializable {
/**
* Listener that gets notified when the size of the browser window
* containing the uI has changed.
*
* @see #addBrowserWindowResizeListener(BrowserWindowResizeListener)
*/
@FunctionalInterface
public interface BrowserWindowResizeListener extends SerializableEventListener {
/**
* Invoked when the browser window containing a UI has been resized.
*
* @param event
* a browser window resize event
*/
public void browserWindowResized(BrowserWindowResizeEvent event);
}
/**
* Event that is fired when a browser window containing a uI is resized.
*/
public static class BrowserWindowResizeEvent extends EventObject {
private final int width;
private final int height;
/**
* Creates a new event.
*
* @param source
* the uI for which the browser window has been resized
* @param width
* the new width of the browser window
* @param height
* the new height of the browser window
*/
public BrowserWindowResizeEvent(Page source, int width, int height) {
super(source);
this.width = width;
this.height = height;
}
@Override
public Page getSource() {
return (Page) super.getSource();
}
/**
* Gets the new browser window height.
*
* @return an integer with the new pixel height of the browser window
*/
public int getHeight() {
return height;
}
/**
* Gets the new browser window width.
*
* @return an integer with the new pixel width of the browser window
*/
public int getWidth() {
return width;
}
}
/**
* Private class for storing properties related to opening resources.
*/
private class OpenResource implements Serializable {
/**
* The resource to open
*/
private final Resource resource;
/**
* The name of the target window
*/
private final String name;
/**
* The width of the target window
*/
private final int width;
/**
* The height of the target window
*/
private final int height;
/**
* The border style of the target window
*/
private final BorderStyle border;
private final boolean tryToOpenAsPopup;
/**
* Creates a new open resource.
*
* @param url
* The URL to open
* @param name
* The name of the target window
* @param width
* The width of the target window
* @param height
* The height of the target window
* @param border
* The border style of the target window
* @param tryToOpenAsPopup
* Should try to open as a pop-up
*/
private OpenResource(String url, String name, int width, int height,
BorderStyle border, boolean tryToOpenAsPopup) {
this(new ExternalResource(url), name, width, height, border,
tryToOpenAsPopup);
}
/**
* Creates a new open resource.
*
* @param resource
* The resource to open
* @param name
* The name of the target window
* @param width
* The width of the target window
* @param height
* The height of the target window
* @param border
* The border style of the target window
* @param tryToOpenAsPopup
* Should try to open as a pop-up
*/
private OpenResource(Resource resource, String name, int width,
int height, BorderStyle border, boolean tryToOpenAsPopup) {
this.resource = resource;
this.name = name;
this.width = width;
this.height = height;
this.border = border;
this.tryToOpenAsPopup = tryToOpenAsPopup;
}
/**
* Paints the open request. Should be painted inside the window.
*
* @param target
* the paint target
* @throws PaintException
* if the paint operation fails
*/
private void paintContent(PaintTarget target) throws PaintException {
target.startTag("open");
target.addAttribute("src", resource);
if (name != null && !name.isEmpty()) {
target.addAttribute("name", name);
}
if (!tryToOpenAsPopup) {
target.addAttribute("popup", tryToOpenAsPopup);
}
if (width >= 0) {
target.addAttribute("width", width);
}
if (height >= 0) {
target.addAttribute("height", height);
}
switch (border) {
case MINIMAL:
target.addAttribute("border", "minimal");
break;
case NONE:
target.addAttribute("border", "none");
break;
}
target.endTag("open");
}
}
private static final Method BROWSER_RESIZE_METHOD = ReflectTools.findMethod(
BrowserWindowResizeListener.class, "browserWindowResized",
BrowserWindowResizeEvent.class);
/**
* @deprecated As of 7.0, use {@link BorderStyle#NONE} instead.
*/
@Deprecated
public static final BorderStyle BORDER_NONE = BorderStyle.NONE;
/**
* @deprecated As of 7.0, use {@link BorderStyle#MINIMAL} instead.
*/
@Deprecated
public static final BorderStyle BORDER_MINIMAL = BorderStyle.MINIMAL;
/**
* @deprecated As of 7.0, use {@link BorderStyle#DEFAULT} instead.
*/
@Deprecated
public static final BorderStyle BORDER_DEFAULT = BorderStyle.DEFAULT;
/**
* Listener that that gets notified when the URI fragment of the page
* changes.
*
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
* @deprecated Use {@link PopStateListener} instead
*/
@Deprecated
@FunctionalInterface
public interface UriFragmentChangedListener extends SerializableEventListener {
/**
* Event handler method invoked when the URI fragment of the page
* changes. Please note that the initial URI fragment has already been
* set when a new UI is initialized, so there will not be any initial
* event for listeners added during {@link UI#init(VaadinRequest)}.
*
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
*
* @param event
* the URI fragment changed event
*/
public void uriFragmentChanged(UriFragmentChangedEvent event);
}
private static final Method URI_FRAGMENT_CHANGED_METHOD = ReflectTools
.findMethod(Page.UriFragmentChangedListener.class,
"uriFragmentChanged", UriFragmentChangedEvent.class);
/**
* Listener that that gets notified when the URI of the page changes due to
* back/forward functionality of the browser.
*
* @see Page#addPopStateListener(PopStateListener)
* @since 8.0
*/
@FunctionalInterface
public interface PopStateListener extends SerializableEventListener {
/**
* Event handler method invoked when the URI fragment of the page
* changes. Please note that the initial URI fragment has already been
* set when a new UI is initialized, so there will not be any initial
* event for listeners added during {@link UI#init(VaadinRequest)}.
*
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
*
* @param event
* the URI fragment changed event
*/
public void uriChanged(PopStateEvent event);
}
private static final Method URI_CHANGED_METHOD = ReflectTools.findMethod(
Page.PopStateListener.class, "uriChanged", PopStateEvent.class);
/**
* Resources to be opened automatically on next repaint. The list is
* automatically cleared when it has been sent to the client.
*/
private final LinkedList openList = new LinkedList<>();
/**
* Event fired when the URI fragment of a Page
changes.
*
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
*/
public static class UriFragmentChangedEvent extends EventObject {
/**
* The new URI fragment
*/
private final String uriFragment;
/**
* Creates a new instance of UriFragmentReader change event.
*
* @param source
* the Source of the event.
* @param uriFragment
* the new uriFragment
*/
public UriFragmentChangedEvent(Page source, String uriFragment) {
super(source);
this.uriFragment = uriFragment;
}
/**
* Gets the page in which the fragment has changed.
*
* @return the page in which the fragment has changed
*/
public Page getPage() {
return (Page) getSource();
}
/**
* Get the new URI fragment.
*
* @return the new fragment
*/
public String getUriFragment() {
return uriFragment;
}
}
/**
* Event fired when the URI of a Page
changes (aka HTML 5
* popstate event) on the client side due to browsers back/forward
* functionality.
*
* @see Page#addPopStateListener(PopStateListener)
* @since 8.0
*/
public static class PopStateEvent extends EventObject {
/**
* The new URI as String
*/
private final String uri;
/**
* Creates a new instance of PopstateEvent.
*
* @param source
* the Source of the event.
* @param uri
* the new uri
*/
public PopStateEvent(Page source, String uri) {
super(source);
this.uri = uri;
}
/**
* Gets the page in which the uri has changed.
*
* @return the page in which the uri has changed
*/
public Page getPage() {
return (Page) getSource();
}
/**
* Get the new URI.
*
* @return the new uri
*/
public String getUri() {
return uri;
}
}
@FunctionalInterface
private static interface InjectedStyle extends Serializable {
public void paint(int id, PaintTarget target) throws PaintException;
}
private static class InjectedStyleString implements InjectedStyle {
private final String css;
public InjectedStyleString(String css) {
this.css = css;
}
@Override
public void paint(int id, PaintTarget target) throws PaintException {
target.startTag("css-string");
target.addAttribute("id", id);
target.addText(css);
target.endTag("css-string");
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof InjectedStyleString) {
InjectedStyleString that = (InjectedStyleString) obj;
return css.equals(that.css);
} else {
return false;
}
}
@Override
public int hashCode() {
return css.hashCode();
}
}
private static class InjectedStyleResource implements InjectedStyle {
private final Resource resource;
public InjectedStyleResource(Resource resource) {
this.resource = resource;
}
@Override
public void paint(int id, PaintTarget target) throws PaintException {
target.startTag("css-resource");
target.addAttribute("id", id);
target.addAttribute("url", resource);
target.endTag("css-resource");
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof InjectedStyleResource) {
InjectedStyleResource that = (InjectedStyleResource) obj;
return resource.equals(that.resource);
} else {
return false;
}
}
@Override
public int hashCode() {
return resource.hashCode();
}
}
/**
* Contains dynamically injected styles injected in the HTML document at
* runtime.
*
* @since 7.1
*/
public static class Styles implements Serializable {
// For internal use only, visibility is package for enabling testing
LinkedHashSet injectedStyles = new LinkedHashSet<>();
// For internal use only, visibility is package for enabling testing
LinkedHashSet pendingInjections = new LinkedHashSet<>();
private final UI ui;
private Styles(UI ui) {
this.ui = ui;
}
/**
* Injects a raw CSS string into the page.
*
* @param css
* The CSS to inject
*/
public void add(String css) {
if (css == null) {
throw new IllegalArgumentException(
"Cannot inject null CSS string");
}
InjectedStyleString injectedStyleString = new InjectedStyleString(
css);
if (!injectedStyles.contains(injectedStyleString)
&& pendingInjections.add(injectedStyleString)) {
ui.markAsDirty();
}
}
/**
* Injects a CSS resource into the page.
*
* @param resource
* The resource to inject.
*/
public void add(Resource resource) {
if (resource == null) {
throw new IllegalArgumentException(
"Cannot inject null resource");
}
InjectedStyleResource injection = new InjectedStyleResource(
resource);
if (!injectedStyles.contains(injection)
&& pendingInjections.add(injection)) {
ui.markAsDirty();
}
}
private void paint(PaintTarget target) throws PaintException {
// If full repaint repaint all injections
if (target.isFullRepaint()) {
injectedStyles.addAll(pendingInjections);
pendingInjections = injectedStyles;
injectedStyles = new LinkedHashSet<>();
}
if (!pendingInjections.isEmpty()) {
target.startTag("css-injections");
for (InjectedStyle pending : pendingInjections) {
int id = injectedStyles.size();
pending.paint(id, target);
injectedStyles.add(pending);
}
pendingInjections.clear();
target.endTag("css-injections");
}
}
}
private EventRouter eventRouter;
private final UI uI;
private int browserWindowWidth = -1;
private int browserWindowHeight = -1;
private JavaScript javaScript;
private Styles styles;
/**
* The current browser location.
*/
private URI location;
private final PageState state;
private String windowName;
private String newPushState;
private String newReplaceState;
private List pendingDependencies;
public Page(UI uI, PageState state) {
this.uI = uI;
this.state = state;
}
private Registration addListener(Class> eventType, SerializableEventListener listener,
Method method) {
if (!hasEventRouter()) {
eventRouter = new EventRouter();
}
return eventRouter.addListener(eventType, listener, method);
}
private void removeListener(Class> eventType, SerializableEventListener listener,
Method method) {
if (hasEventRouter()) {
eventRouter.removeListener(eventType, listener, method);
}
}
/**
* Adds a listener that gets notified every time the URI fragment of this
* page is changed. Please note that the initial URI fragment has already
* been set when a new UI is initialized, so there will not be any initial
* event for listeners added during {@link UI#init(VaadinRequest)}.
*
* @see #getUriFragment()
* @see #setUriFragment(String)
* @see Registration
*
* @param listener
* the URI fragment listener to add
* @return a registration object for removing the listener
* @deprecated Use {@link Page#addPopStateListener(PopStateListener)}
* instead
* @since 8.0
*/
@Deprecated
public Registration addUriFragmentChangedListener(
Page.UriFragmentChangedListener listener) {
return addListener(UriFragmentChangedEvent.class, listener,
URI_FRAGMENT_CHANGED_METHOD);
}
/**
* Adds a listener that gets notified every time the URI of this page is
* changed due to back/forward functionality of the browser.
*
* Note that one only gets notified when the back/forward button affects
* history changes with-in same UI, created by
* {@link Page#pushState(String)} or {@link Page#replaceState(String)}
* functions.
*
* @see #getLocation()
* @see Registration
*
* @param listener
* the Popstate listener to add
* @return a registration object for removing the listener
* @since 8.0
*/
public Registration addPopStateListener(Page.PopStateListener listener) {
return addListener(PopStateEvent.class, listener, URI_CHANGED_METHOD);
}
/**
* Removes a URI fragment listener that was previously added to this page.
*
* @param listener
* the URI fragment listener to remove
*
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
*
* @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
* registration object returned from
* {@link #addUriFragmentChangedListener(UriFragmentChangedListener)}.
*/
@Deprecated
public void removeUriFragmentChangedListener(
Page.UriFragmentChangedListener listener) {
removeListener(UriFragmentChangedEvent.class, listener,
URI_FRAGMENT_CHANGED_METHOD);
}
/**
* Sets the fragment part in the current location URI. Optionally fires a
* {@link UriFragmentChangedEvent}.
*
* The fragment is the optional last component of a URI, prefixed with a
* hash sign ("#").
*
* Passing an empty string as newFragment
sets an empty
* fragment (a trailing "#" in the URI.) Passing null
if there
* is already a non-null fragment will leave a trailing # in the URI since
* removing it would cause the browser to reload the page. This is not fully
* consistent with the semantics of {@link java.net.URI}.
*
* @param newUriFragment
* The new fragment.
* @param fireEvents
* true to fire event
*
* @see #getUriFragment()
* @see #setLocation(URI)
* @see UriFragmentChangedEvent
* @see Page.UriFragmentChangedListener
*
*/
public void setUriFragment(String newUriFragment, boolean fireEvents) {
String oldUriFragment = location.getFragment();
if (newUriFragment == null && getUriFragment() != null) {
// Can't completely remove the fragment once it has been set, will
// instead set it to the empty string
newUriFragment = "";
}
if (newUriFragment == oldUriFragment || (newUriFragment != null
&& newUriFragment.equals(oldUriFragment))) {
return;
}
try {
location = new URI(location.getScheme(),
location.getSchemeSpecificPart(), newUriFragment);
pushState(location);
} catch (URISyntaxException e) {
// This should not actually happen as the fragment syntax is not
// constrained
throw new RuntimeException(e);
}
if (fireEvents) {
fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
}
}
private void fireEvent(EventObject event) {
if (hasEventRouter()) {
eventRouter.fireEvent(event);
}
}
/**
* Sets URI fragment. This method fires a {@link UriFragmentChangedEvent}
*
* @param newUriFragment
* id of the new fragment
* @see UriFragmentChangedEvent
* @see Page.UriFragmentChangedListener
*/
public void setUriFragment(String newUriFragment) {
setUriFragment(newUriFragment, true);
}
/**
* Gets the currently set URI fragment.
*
* Returns null
if there is no fragment and an empty string if
* there is an empty fragment.
*
* To listen to changes in fragment, hook a
* {@link Page.UriFragmentChangedListener}.
*
* @return the current fragment in browser location URI.
*
* @see #getLocation()
* @see #setUriFragment(String)
* @see #addUriFragmentChangedListener(UriFragmentChangedListener)
*/
public String getUriFragment() {
return location.getFragment();
}
public void init(VaadinRequest request) {
// NOTE: UI.refresh makes assumptions about the semantics of this
// method.
// It should be kept in sync if this method is changed.
// Extract special parameter sent by vaadinBootstrap.js
String location = request.getParameter("v-loc");
String clientWidth = request.getParameter("v-cw");
String clientHeight = request.getParameter("v-ch");
windowName = request.getParameter("v-wn");
if (location != null) {
try {
this.location = new URI(location);
} catch (URISyntaxException e) {
throw new RuntimeException(
"Invalid location URI received from client", e);
}
}
if (clientWidth != null && clientHeight != null) {
try {
browserWindowWidth = Integer.parseInt(clientWidth);
browserWindowHeight = Integer.parseInt(clientHeight);
} catch (NumberFormatException e) {
throw new RuntimeException(
"Invalid window size received from client", e);
}
}
}
public WebBrowser getWebBrowser() {
return uI.getSession().getBrowser();
}
/**
* Gets the window.name value of the browser window of this page.
*
* @since 7.2
*
* @return the window name, null
if the name is not known
*/
public String getWindowName() {
return windowName;
}
/**
* For internal use only. Updates the internal state with the given values.
* Does not resize the Page or browser window.
*
* @deprecated As of 7.2, use
* {@link #updateBrowserWindowSize(int, int, boolean)} instead.
*
* @param width
* the new browser window width
* @param height
* the new browse window height
*/
@Deprecated
public void updateBrowserWindowSize(int width, int height) {
updateBrowserWindowSize(width, height, true);
}
/**
* For internal use only. Updates the internal state with the given values.
* Does not resize the Page or browser window.
*
* @since 7.2
*
* @param width
* the new browser window width
* @param height
* the new browser window height
* @param fireEvents
* whether to fire {@link BrowserWindowResizeEvent} if the size
* changes
*/
public void updateBrowserWindowSize(int width, int height,
boolean fireEvents) {
boolean sizeChanged = false;
if (width != browserWindowWidth) {
browserWindowWidth = width;
sizeChanged = true;
}
if (height != browserWindowHeight) {
browserWindowHeight = height;
sizeChanged = true;
}
if (fireEvents && sizeChanged) {
fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth,
browserWindowHeight));
}
}
/**
* Adds a new {@link BrowserWindowResizeListener} to this UI. The listener
* will be notified whenever the browser window within which this UI resides
* is resized.
*
* In most cases, the UI should be in lazy resize mode when using browser
* window resize listeners. Otherwise, a large number of events can be
* received while a resize is being performed. Use
* {@link UI#setResizeLazy(boolean)}.
*
*
* @param resizeListener
* the listener to add
* @return a registration object for removing the listener
*
* @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent)
* @see UI#setResizeLazy(boolean)
* @see Registration
* @since 8.0
*/
public Registration addBrowserWindowResizeListener(
BrowserWindowResizeListener resizeListener) {
Registration registration = addListener(BrowserWindowResizeEvent.class,
resizeListener, BROWSER_RESIZE_METHOD);
getState(true).hasResizeListeners = true;
return () -> {
registration.remove();
getState(true).hasResizeListeners = hasEventRouter()
&& eventRouter.hasListeners(BrowserWindowResizeEvent.class);
};
}
/**
* Removes a {@link BrowserWindowResizeListener} from this UI. The listener
* will no longer be notified when the browser window is resized.
*
* @param resizeListener
* the listener to remove
*
* @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
* registration object returned from
* {@link #addBrowserWindowResizeListener(BrowserWindowResizeListener)}
* .
*/
@Deprecated
public void removeBrowserWindowResizeListener(
BrowserWindowResizeListener resizeListener) {
removeListener(BrowserWindowResizeEvent.class, resizeListener,
BROWSER_RESIZE_METHOD);
getState(true).hasResizeListeners = hasEventRouter()
&& eventRouter.hasListeners(BrowserWindowResizeEvent.class);
}
/**
* Gets the last known height of the browser window in which this UI
* resides.
*
* @return the browser window height in pixels
*/
public int getBrowserWindowHeight() {
return browserWindowHeight;
}
/**
* Gets the last known width of the browser window in which this uI resides.
*
* @return the browser window width in pixels
*/
public int getBrowserWindowWidth() {
return browserWindowWidth;
}
public JavaScript getJavaScript() {
if (javaScript == null) {
// Create and attach on first use
javaScript = new JavaScript();
javaScript.extend(uI);
}
return javaScript;
}
/**
* Returns that stylesheet associated with this Page. The stylesheet
* contains additional styles injected at runtime into the HTML document.
*
* @since 7.1
*/
public Styles getStyles() {
if (styles == null) {
styles = new Styles(uI);
}
return styles;
}
public void paintContent(PaintTarget target) throws PaintException {
if (!openList.isEmpty()) {
for (OpenResource anOpenList : openList) {
(anOpenList).paintContent(target);
}
openList.clear();
}
if (newPushState != null) {
target.addAttribute(UIConstants.ATTRIBUTE_PUSH_STATE, newPushState);
newPushState = null;
}
if (newReplaceState != null) {
target.addAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE,
newReplaceState);
newReplaceState = null;
}
if (styles != null) {
styles.paint(target);
}
}
/**
* Navigates this page to the given URI. The contents of this page in the
* browser is replaced with whatever is returned for the given URI.
*
* This method should not be used to start downloads, as the client side
* will assume the browser will navigate away when opening the URI. Use one
* of the {@code Page.open} methods or {@code FileDownloader} instead.
*
* @see #open(String, String)
* @see FileDownloader
*
* @param uri
* the URI to show
*/
public void setLocation(String uri) {
openList.add(
new OpenResource(uri, "_self", -1, -1, BORDER_DEFAULT, false));
uI.markAsDirty();
}
/**
* Navigates this page to the given URI. The contents of this page in the
* browser is replaced with whatever is returned for the given URI.
*
* This method should not be used to start downloads, as the client side
* will assume the browser will navigate away when opening the URI. Use one
* of the {@code Page.open} methods or {@code FileDownloader} instead.
*
* @see #open(String, String)
* @see FileDownloader
*
* @param uri
* the URI to show
*/
public void setLocation(URI uri) {
setLocation(uri.toString());
}
/**
* Returns the location URI of this page, as reported by the browser. Note
* that this may not be consistent with the server URI the application is
* deployed in due to potential proxies, redirections and similar.
*
* @return The browser location URI.
* @throws IllegalStateException
* if the
* {@link DeploymentConfiguration#isSendUrlsAsParameters()} is
* set to {@code false}
*/
public URI getLocation() throws IllegalStateException {
if (location == null) {
if (uI.getSession() != null && !uI.getSession().getConfiguration()
.isSendUrlsAsParameters()) {
throw new IllegalStateException("Location is not available as the "
+ Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
+ " parameter is configured as false");
} else if (VaadinSession.getCurrent() == null) {
throw new IllegalStateException("Location is not available as the "
+ Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
+ " parameter state cannot be determined");
} else if (!VaadinSession.getCurrent().getConfiguration()
.isSendUrlsAsParameters()) {
throw new IllegalStateException("Location is not available as the "
+ Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
+ " parameter is configured as false");
}
}
return location;
}
/**
* Updates the browsers URI without causing actual page change. This method
* is useful if you wish implement "deep linking" to your application.
* Calling the method also adds a new entry to clients browser history and
* you can further use {@link PopStateListener} to track the usage of
* back/forward feature in browser.
*
* Note, the current implementation supports setting only one new uri in one
* user interaction.
*
* @param uri
* to be used for pushState operation. The URI is resolved over
* the current location. If the given URI is absolute, it must be
* of same origin as the current URI or the browser will not
* accept the new value.
* @since 8.0
*/
public void pushState(String uri) {
newPushState = uri;
uI.markAsDirty();
location = location.resolve(uri);
}
/**
* Updates the browsers URI without causing actual page change. This method
* is useful if you wish implement "deep linking" to your application.
* Calling the method also adds a new entry to clients browser history and
* you can further use {@link PopStateListener} to track the usage of
* back/forward feature in browser.
*
* Note, the current implementation supports setting only one new uri in one
* user interaction.
*
* @param uri
* the URI to be used for pushState operation. The URI is
* resolved over the current location. If the given URI is
* absolute, it must be of same origin as the current URI or the
* browser will not accept the new value.
* @since 8.0
*/
public void pushState(URI uri) {
pushState(uri.toString());
}
/**
* Updates the browsers URI without causing actual page change in the same
* way as {@link #pushState(String)}, but does not add new entry to browsers
* history.
*
* @param uri
* the URI to be used for replaceState operation. The URI is
* resolved over the current location. If the given URI is
* absolute, it must be of same origin as the current URI or the
* browser will not accept the new value.
* @since 8.0
*/
public void replaceState(String uri) {
newReplaceState = uri;
uI.markAsDirty();
location = location.resolve(uri);
}
/**
* Updates the browsers URI without causing actual page change in the same
* way as {@link #pushState(URI)}, but does not add new entry to browsers
* history.
*
* @param uri
* the URI to be used for replaceState operation. The URI is
* resolved over the current location. If the given URI is
* absolute, it must be of same origin as the current URI or the
* browser will not accept the new value.
* @since 8.0
*/
public void replaceState(URI uri) {
replaceState(uri.toString());
}
/**
* For internal use only. Used to update the server-side location when the
* client-side location changes.
*
* @deprecated As of 7.2, use {@link #updateLocation(String, boolean, boolean)}
* instead.
*
* @param location
* the new location URI
*/
@Deprecated
public void updateLocation(String location) {
updateLocation(location, true, false);
}
/**
* For internal use only. Used to update the server-side location when the
* client-side location changes.
*
* @since 8.0
*
* @param location
* the new location URI
* @param fireEvents
* whether to fire {@link UriFragmentChangedEvent} if the URI
* fragment changes
* @param firePopstate
* whether to fire {@link PopStateEvent}
*/
public void updateLocation(String location, boolean fireEvents,
boolean firePopstate) {
try {
String oldUriFragment = this.location.getFragment();
this.location = new URI(location);
String newUriFragment = this.location.getFragment();
if (fireEvents
&& !SharedUtil.equals(oldUriFragment, newUriFragment)) {
fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
}
if (firePopstate) {
fireEvent(new PopStateEvent(this, location));
}
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
/**
* Opens the given url in a window with the given name. Equivalent to
* {@link #open(String, String, boolean) open} (url, windowName, true) .
*
* The supplied {@code windowName} is used as the target name in a
* window.open call in the client. This means that special values such as
* "_blank", "_self", "_top", "_parent" have special meaning. An empty or
* null
window name is also a special case.
*
*
* "", null and "_self" as {@code windowName} all causes the URL to be
* opened in the current window, replacing any old contents. For
* downloadable content you should avoid "_self" as "_self" causes the
* client to skip rendering of any other changes as it considers them
* irrelevant (the page will be replaced by the response from the URL). This
* can speed up the opening of a URL, but it might also put the client side
* into an inconsistent state if the window content is not completely
* replaced e.g., if the URL is downloaded instead of displayed in the
* browser.
*
*
* "_blank" as {@code windowName} causes the URL to always be opened in a
* new window or tab (depends on the browser and browser settings).
*
*
* "_top" and "_parent" as {@code windowName} works as specified by the HTML
* standard.
*
*
* Any other {@code windowName} will open the URL in a window with that
* name, either by opening a new window/tab in the browser or by replacing
* the contents of an existing window with that name.
*
*
* Please note that opening a popup window in this way may be blocked by the
* browser's popup-blocker because the new browser window is opened when
* processing a response from the server. To avoid this, you should instead
* use {@link Link} for opening the window because browsers are more
* forgiving when the window is opened directly from a client-side click
* event.
*
*
* @param url
* the URL to open.
* @param windowName
* the name of the window.
*/
public void open(String url, String windowName) {
open(url, windowName, true);
}
/**
* Opens the given url in a window with the given name. Equivalent to
* {@link #open(String, String, boolean) open} (url, windowName, true) .
*
* The supplied {@code windowName} is used as the target name in a
* window.open call in the client. This means that special values such as
* "_blank", "_self", "_top", "_parent" have special meaning. An empty or
* null
window name is also a special case.
*
*
* "", null and "_self" as {@code windowName} all causes the URL to be
* opened in the current window, replacing any old contents. For
* downloadable content you should avoid "_self" as "_self" causes the
* client to skip rendering of any other changes as it considers them
* irrelevant (the page will be replaced by the response from the URL). This
* can speed up the opening of a URL, but it might also put the client side
* into an inconsistent state if the window content is not completely
* replaced e.g., if the URL is downloaded instead of displayed in the
* browser.
*
*
* "_blank" as {@code windowName} causes the URL to always be opened in a
* new window or tab (depends on the browser and browser settings).
*
*
* "_top" and "_parent" as {@code windowName} works as specified by the HTML
* standard.
*
*
* Any other {@code windowName} will open the URL in a window with that
* name, either by opening a new window/tab in the browser or by replacing
* the contents of an existing window with that name.
*
*
* Please note that opening a popup window in this way may be blocked by the
* browser's popup-blocker because the new browser window is opened when
* processing a response from the server. To avoid this, you should instead
* use {@link Link} for opening the window because browsers are more
* forgiving when the window is opened directly from a client-side click
* event.
*
*
* @param url
* the URL to open.
* @param windowName
* the name of the window.
* @param tryToOpenAsPopup
* Whether to try to force the resource to be opened in a new
* window
*/
public void open(String url, String windowName, boolean tryToOpenAsPopup) {
openList.add(new OpenResource(url, windowName, -1, -1, BORDER_DEFAULT,
tryToOpenAsPopup));
uI.markAsDirty();
}
/**
* Opens the given URL in a window with the given size, border and name. For
* more information on the meaning of {@code windowName}, see
* {@link #open(String, String)}.
*
* Please note that opening a popup window in this way may be blocked by the
* browser's popup-blocker because the new browser window is opened when
* processing a response from the server. To avoid this, you should instead
* use {@link Link} for opening the window because browsers are more
* forgiving when the window is opened directly from a client-side click
* event.
*
*
* @param url
* the URL to open.
* @param windowName
* the name of the window.
* @param width
* the width of the window in pixels
* @param height
* the height of the window in pixels
* @param border
* the border style of the window.
*/
public void open(String url, String windowName, int width, int height,
BorderStyle border) {
openList.add(
new OpenResource(url, windowName, width, height, border, true));
uI.markAsDirty();
}
/**
* @deprecated As of 7.0, only retained to maintain compatibility with
* LegacyWindow.open methods. See documentation for
* {@link LegacyWindow#open(Resource, String, int, int, BorderStyle)}
* for discussion about replacing API.
*/
@Deprecated
public void open(Resource resource, String windowName, int width,
int height, BorderStyle border) {
openList.add(new OpenResource(resource, windowName, width, height,
border, true));
uI.markAsDirty();
}
/**
* @deprecated As of 7.0, only retained to maintain compatibility with
* LegacyWindow.open methods. See documentation for
* {@link LegacyWindow#open(Resource, String, boolean)} for
* discussion about replacing API.
*/
@Deprecated
public void open(Resource resource, String windowName,
boolean tryToOpenAsPopup) {
openList.add(new OpenResource(resource, windowName, -1, -1,
BORDER_DEFAULT, tryToOpenAsPopup));
uI.markAsDirty();
}
/**
* Shows a notification message.
*
* @see Notification
*
* @param notification
* The notification message to show
*
* @deprecated As of 7.0, use Notification.show(Page) instead.
*/
@Deprecated
public void showNotification(Notification notification) {
notification.show(this);
}
/**
* Gets the Page to which the current uI belongs. This is automatically
* defined when processing requests to the server. In other cases, (e.g.
* from background threads), the current uI is not automatically defined.
*
* @see UI#getCurrent()
*
* @return the current page instance if available, otherwise
* null
*/
public static Page getCurrent() {
UI currentUI = UI.getCurrent();
if (currentUI == null) {
return null;
}
return currentUI.getPage();
}
/**
* Sets the page title. The page title is displayed by the browser e.g. as
* the title of the browser window or as the title of the tab.
*
* If this value is set to null, the previously set page title will be left
* as-is. Set to empty string to clear the title.
*
* @param title
* the page title to set
*/
public void setTitle(String title) {
getState(true).title = title;
}
/**
* Reloads the page in the browser.
*/
public void reload() {
uI.getRpcProxy(PageClientRpc.class).reload();
}
/**
* Returns the page state.
*
* The page state is transmitted to UIConnector together with
* {@link UIState} rather than as an individual entity.
*
*
* The state should be considered an internal detail of Page. Classes
* outside of Page should not access it directly but only through public
* APIs provided by Page.
*
*
* @since 7.1
* @param markAsDirty
* true to mark the state as dirty
* @return PageState object that can be read in any case and modified if
* markAsDirty is true
*/
protected PageState getState(boolean markAsDirty) {
if (markAsDirty) {
uI.markAsDirty();
}
return state;
}
private boolean hasEventRouter() {
return eventRouter != null;
}
/**
* Add a dependency that should be added to the current page.
*
* These dependencies are always added before the dependencies included by
* using the annotations {@link HtmlImport}, {@link JavaScript} and
* {@link StyleSheet} during the same request.
*
* Please note that these dependencies are always sent to the client side
* and not filtered out by any {@link DependencyFilter}.
*
* @param dependency
* the dependency to add
* @since 8.1
*/
public void addDependency(Dependency dependency) {
if (pendingDependencies == null) {
pendingDependencies = new ArrayList<>();
}
pendingDependencies.add(dependency);
}
/**
* Returns all pending dependencies.
*
* For internal use only, calling this method will clear the pending
* dependencies.
*
* @return the pending dependencies to the current page
* @since 8.1
*/
public Collection getPendingDependencies() {
List copy = new ArrayList<>();
if (pendingDependencies != null) {
copy.addAll(pendingDependencies);
}
pendingDependencies = null;
return copy;
}
/**
* Returns the {@link UI} of this {@link Page}.
*
* @return the {@link UI} of this {@link Page}.
*
* @since 8.2
*/
public UI getUI() {
return uI;
}
}