Vaadin supports two types of windows: application-level windows and sub-windows. The application-level windows are native browser windows or tabs. The main window is the special initial application-level window created when the user first started the application session by opening the URL in the browser. Sub-windows are freely floating HTML windows inside a browser window, as described in Section 4.3, “Sub-Windows”.
Application-level windows of the same application use the same
Application
object and therefore share the same
session. Each window is identified with a URL that is used to access it. This
makes it possible to bookmark application-level windows. Such windows can even
be created dynamically based on the URLs.
Application-level windows allow several common use cases for browser-based applications.
Because of the special nature of AJAX applications, these uses require some caveats. We will go through them later in Section 12.2.5, “Caveats in Using Multiple Windows”.
Creating a new application-level window is much like creating a child
window (see Section 4.3, “Sub-Windows”), except that the
window is added with addWindow()
to the application
object instead of the main window.
public class WindowTestApplication extends Application { public void init() { // First create the main window. final Window main = new Window ("My Test Application"); setMainWindow(main); // Create another application-level window. final Window mywindow = new Window("Second Window"); // Manually set the name of the window. mywindow.setName("mywindow"); // Add some content to the window. mywindow.addComponent(new Label("Has content.")); // Add the window to the application. addWindow(mywindow); } }
This creates the window object that a user can view by opening a URL in
a browser. Creating an application-level window object does not open a new
browser window automatically to view the object, but if you wish to open
one, you have to do it explicitly as shown below. An application-level
window has a unique URL, which is based on the application URL and the
name of the window given with the setName()
method. For example, if the application URL is
http://localhost:8080/myapp/
and the window name is
mywindow
, the URL for the window will be
http://localhost:8080/myapp/mywindow/
. If the name of a window
is not explicitly set with setName()
, an
automatically generated name will be used. The name can be retrieved with
the getName()
method and the entire URL with
getURL()
.
There are three typical ways to open a new window: using the
open()
method of Window
class, a Link
, or referencing it from HTML or
JavaScript code written inside a Label
component.
The Window
open()
method
takes as parameters a resource to open and the target name. You can use
ExternalResource
to open a specific URL, which you
get from the window to be opened with the
getURL()
method.
/* Create a button to open a new window. */ main.addComponent(new Button("Click to open new window", new Button.ClickListener() { public void buttonClick(ClickEvent event) { // Open the window. main.open(new ExternalResource(mywindow.getURL()), "_new"); } }));
The target name is one of the default HTML target names (_new
, _blank
, _top
, etc.) or a custom target name. How the window is
exactly opened depends on the browser. Browsers that support tabbed
browsing can open the window in another tab, depending on the browser
settings.
Another typical way to open windows is to use a Link
component with the window URL as an
ExternalResource
.
/* Add a link to the second window. */ Link link = new Link("Click to open second window", new ExternalResource(mywindow.getURL())); link.setTargetName("second"); link.setTargetHeight(300); link.setTargetWidth(300); link.setTargetBorder(Link.TARGET_BORDER_DEFAULT); main.addComponent(link);
Using a Link
allows you to specify parameters for
the window that opens by clicking on the link. Above, we set the
dimensions of the window and specify what window controls the window
should contain. The Link.TARGET_BORDER_DEFAULT
specifies to use the default, which includes most of the usual window
controls, such as the menu, the toolbar, and the status bar.
Another way to allow the user to open a window is to insert the URL in
HTML code inside a Label
. This allows even more
flexibility in specifying how the window should be opened.
// Add the link manually inside a Label. main.addComponent( new Label("Second window: <a href='" + mywindow.getURL() + "' target='second'>click to open</a>", Label.CONTENT_XHTML)); main.addComponent( new Label("The second window can be accessed through URL: " + mywindow.getURL()));
When an application-level window is closed in the browser the
close()
method is normally called just like
for a child window and the Window
object is
purged from the application. However, there are situations where
close()
might not be called. See
Section 12.2.4, “Closing Windows” for more information.
You can create a window object dynamically by its URL sub-path when it is
first requested by overriding the getWindow()
method of the Application
class. The method gets a
window name as its parameter and must return the corresponding
Window
object. The window name is determined from
the first URL path element after the application URL (the name may not
contain slashes). See the notes below for setting the actual name of the
dynamically created windows below.
The following example allows opening windows with a window name that
begins with "planet-
" prefix. Since the method is
called for every browser request for the
application, we filter only the requests where a window with the given
name does not yet exist.
public class WindowTestApplication extends Application { ... @Override public Window getWindow(String name) { // If a dynamically created window is requested, but // it does not exist yet, create it. if (name.startsWith("planet-") && super.getWindow(name) == null) { String planetName = name.substring("planet-".length()); // Create the window object. Window newWindow = new Window("Window about " + planetName); // DANGEROUS: Set the name explicitly. Otherwise, // an automatically generated name is used, which // is usually safer. newWindow.setName(name); // Put some content in it. newWindow.addComponent( new Label("This window contains details about " + planetName + ".")); // Add it to the application as a regular // application-level window. addWindow(newWindow); return newWindow; } // Otherwise the Application object manages existing // windows by their name. return super.getWindow(name); }
The window name must be a unique indentifier for each
Window
object instance. If you use
setName()
to set the window name explicitly, as
we did above, any browser window that has the same URL (within the same
browser) would open the same window object. This is
dangerous and generally not recommended, because the
browser windows would share the same window object. Opening two windows
with the same static name would immediately lead to a synchronization
error, as is shown in Figure 12.1, “Synchronization Error Between Windows with the Same Name” below. (While also the
window captions are same, they are irrelevant for this problem.)
There are some cases where setting the name explicitly is useful. The launch application below is one example, as it always opens the other windows in a window target that is specific to the window name, thereby never creating two windows with the same URL. Similarly, if you had embedded the application in a browser frame and the link would open the window in a frame, you would not have problems. Having a single window instance for a URL is also useful if the browser crashes and the user opens the window again, as it will have kept its previous (server-side) state.
Having multiple browser windows or tabs open in the same website and even
the same page is one of the basic use cases of web browsing. The creation
of Window
objects described in the previous section
allows opening multiple special-purpose windows with different URLs, but
how to open multiple windows with the same URL? The solution is based on
the fact that Vaadin doesn't identify windows only by their URL subpath,
but also by an invisible window name.
Leaving the window name to be automatically generated allows opening
multiple windows with the same URL, while each of the windows will have a
separate state. The URL in the location bar stays unchanged and the
generated window name is used only for the Ajax communications to identify
the window object. A generated name is a string representation of a unique
random number, such as "1928676448
". You should be
aware of the generated window names when overriding the
getWindow()
method (and not unintentionally
create a new window instance dynamically for each such request). The
condition in the above example would also filter out the requests for an
already existing window with a generated name.
Figure 12.2, “A Dynamically Created Window” shows a
dynamically created application-level window with the URL shown in the
address bar. The URL for the application is here
http://localhost:8080/book-examples/windowexample/
, including
the application context, application path. The dynamically created
window's name is planet-mars
.
The application knows the windows it already has and can return them after the creation. The application also handles closing and destruction of application-level window objects, as discussed in Section 12.2.4, “Closing Windows”.
Such dynamic windows could be opened as in the following example:
public void init() { final Window main = new Window("Window Test"); setMainWindow(main); // Have some IDs for the dynamically creatable windows. final String[] items = new String[] { "mercury", "venus", "earth", "mars", "jupiter", "saturn", "uranus", "neptune" }; // Create a list of links to each of the available window. for (int i = 0; i < items.length; i++) { // Create a URL for the window. String windowUrl = getURL() + "planet-" + items[i]; // Create a link to the window URL. Using the // item ID for the target also opens it in a new // browser window (or tab) unique to the window name. main.addComponent( new Link("Open window about " + items[i], new ExternalResource(windowUrl), items[i], -1, -1, Window.BORDER_DEFAULT)); } }
When the user closes an application-level window, the Client-Side Engine
running in the browser will report the event to the server before the page
is actually removed. You can catch the event with a
Window.CloseListener
, as is done in the example
below.
newWindow.addListener(new Window.CloseListener() { @Override public void windowClose(CloseEvent e) { // Do something. System.out.println(e.getWindow().getName() + " was closed"); // Add a text to the main window about closing. // (This does not update the main window.) getMainWindow().addComponent( new Label("Window '" + e.getWindow().getName() + "' was closed.")); } });
Notice that the change to the server-side state of the main window (or another application-level window) does not refresh the window in the browser, so the change will be unseen until user interaction or polling refreshes the window. This problem and its dangers are discussed in Section 12.2.5, “Caveats in Using Multiple Windows” below.
The close event does not occur if the browser crashes or the connection is otherwise severed violently. In such a situation, the window object will be left hanging, which could become a resource problem if you allow the users to open many such application-level windows. The positive side is that the user can reconnect to the window using the window URL.
For cases where you need communication between windows, we recommend using floating child windows. In Vaadin Release 5, an application window can not update the data in other windows. The contents of a window can only be updated when the particular window makes a request to the server. The request can be caused by user input or through polling.
Changing the server-side state of a window while processing a user event from another window can potentially cause serious problems. Changing the client-side state of a window does not always immediately communicate the changes to the server. The server-side state can therefore be out of sync with the client-side state.
The following example creates a second window that changes the contents of the main window, as illustrated in the figure above. In this simple case, changing the main window contents is safe.
// Create a table in the main window to hold items added // in the second window final Table table = new Table(); table.setPageLength(5); table.getSize().setWidth(100, Size.UNITS_PERCENTAGE); table.addContainerProperty("Name", String.class, ""); main.addComponent(table); // Create the second window final Window adderWindow = new Window("Add Items"); adderWindow.setName("win-adder"); main.getApplication().addWindow(adderWindow); // Create selection component to add items to the table final NativeSelect select = new NativeSelect("Select item to add"); select.setImmediate(true); adderWindow.addComponent(select); // Add some items to the selection String items[] = new String[]{"-- Select --", "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}; for (int i=0; i<items.length; i++) select.addItem(items[i]); select.setNullSelectionItemId(items[0]); // When an item is selected in the second window, add // table in the main window select.addListener(new ValueChangeListener() { public void valueChange(ValueChangeEvent event) { // If the selected value is something else // but a null selection item. if (select.getValue() != null) { // Add the selected item to the table // in the main window table.addItem(new Object[]{select.getValue()}, new Integer(table.size())); } } }); // Link to open the selection window Link link = new Link("Click to open second window", new ExternalResource(adderWindow.getURL()), "_new", 50, 200, Link.TARGET_BORDER_DEFAULT); main.addComponent(link); // Enable polling to update the main window ProgressIndicator poller = new ProgressIndicator(); poller.addStyleName("invisible"); main.addComponent(poller);
The example uses an invisible ProgressIndicator
to implement polling. This is sort of a trick and a more proper API
for polling is under design. Making the progress indicator invisible
requires the following CSS style definition:
.v-progressindicator-invisible { display: none; }