Tuesday, August 28, 2012

Registering a Close Handler for a newly opened Window in GWT

It seems that GWT doesn't have any built in way to add handlers to a new Window opened with the standard Window.open(...) call. In all honesty, I don't have any real useful reason for doing this, but here's a simple example of how to in case it ever needs to happen. keep in mind this is a really trivial example meant to show how to do this as simply as possible

Here's the setup - I created a simple project and dropped the exposure101 logo into the war file. I added a button on the screen which opens the picture in a new window whenever the button is clicked.

Currently, When you use Window.open(...) in GWT, it does not give you a handle to the new Window. According to Joel Webber from 2006 (This is actually the most recent explanation I could find), "GWT cannot allow you to pass objects easily back and forth, because the methods and properties on those objects are compressed and unpredictable (i.e. Foo.bar() in Java is probably called something like x.y() in Javascript). There are also other optimizations that the compiler performs that would make this problematic." So, in order to actually get a handle to it we will have to drop into JSNI.

First thing is a method to get a handle to the Window. (the syntax formatter is having trouble with the JSNI blocks - they aren't comments)

native JavaScriptObject openWindow(String url) /*-{
  return $wnd.open(url, 'blank');
}-*/;  

Next thing to do is to create a Java callback that the new Window JavaScriptObject can call when it's closed.

private void onWindowClosed() {
  // do something useful
}

Next thing is to add another JSNI function to listen for the window close event.  I'm not a big Javascript guy and from what I've read on stackoverflow it seems like this function may not be the best way to handle window close events.  So far I've been using this on both Chrome and Firefox and haven't seen an issue - that being said people that are way better at Javascript than I am have said it's unreliable - so if you actually do need to create this type of listener, you might need to look further into handling window close events with Javascript.  Anyway, for this example, here's the function I've been using.

native JavaScriptObject registerHandlers(Jsni jsni, JavaScriptObject window) /*-{
  window.onbeforeunload = doOnbeforeunload;
  function doOnbeforeunload() {
    jsni.@com.exposure101.examples.jsni.client.Jsni::onWindowClosed()();
  }
}-*/;

* There's something kind of tricky here.  the JSNI documentation says you can call Java instance methods from JSNI using both an instance of the class or this.  I have not been able to call instance methods using this.  I could not get anything to happen using this:
 this.@com.exposure101.examples.jsni.client.Jsni::onWindowClosed()();

Anyway, those 3 functions are pretty much the bulk of it.  The complete code is given below.  I don't know how useful this will really be to anybody but I couldn't find any examples of how to actually implement this anywhere.

package com.exposure101.examples.jsni.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Jsni implements EntryPoint {

  /**
   * This is the entry point method.
   */
  public void onModuleLoad() {
    final Button openButton = new Button("open");
    final VerticalPanel panel = new VerticalPanel();
    panel.add(openButton);
    RootPanel.get("center").add(panel);

    openButton.addClickHandler(new ClickHandler() {

      @Override
      public void onClick(ClickEvent event) {
        final String url = GWT.getHostPageBaseURL() + "exposure101-logo.png";
        final JavaScriptObject window = openWindow(url);
        registerHandlers(Jsni.this, window);
      }
    });
  }

  native JavaScriptObject openWindow(String url) /*-{
    return $wnd.open(url, 'blank');
  }-*/;
  
  native JavaScriptObject registerHandlers(Jsni jsni, JavaScriptObject window) /*-{
    window.onbeforeunload = doOnbeforeunload;
    function doOnbeforeunload() {
      jsni.@com.exposure101.examples.jsni.client.Jsni::onWindowClosed()();
    }
  }-*/;
  
  private void onWindowClosed() {
    Window.alert("nice catch blanco nino");
  }
}