Sencha Inc. | HTML5 Apps

Blog

Using GXT in Sencha Desktop Packager

July 09, 2013 | Colin Alworth

Using GXT in Sencha Desktop PackagerLast month, Ariya wrote an article on Sencha Desktop Packager, a tool that wraps your web applications and delivers them as native solutions. In this article, we’ll look at using this tool to package GWT applications.

Since GWT applications are written in Java but run in JavaScript, you might want to package them to run Java on the desktop without a traditional browser. There could be advantages to structuring your application like this: you could build the product for the web and then offer a local solution for better file system and menu access. It might also be beneficial to ship with a specific version of an already supported browser, one which cannot reach other sites and will not receive updates to break the app. In this article, we’ll look at three different sample applications — a simple hello world with native features, one based on the GXT Explorer, and one based on the Quake2 GWT Demo.

Hello!

To start playing with Desktop Packager, I built a new GWT application in Eclipse and called it HelloDesktop. I renamed the default html file to index.html, because that is the required starting point for the application.

Talking to a Server

It is important to note that Desktop Packager will not run a real web server, so any calls that need to be made to a centralized app server will need to be configured to point directly at that server. For RPC, you will invoke setServiceEntryPoint, while for RequestFactory you will need to build a DefaultRequestTransport and call setRequestUrl to point to the real server url.

Because of this requirement, I deleted the sample RPC call, and modified the EntryPoint to just display a single message on the page, to confirm it works:

 
public class HelloDesktop implements EntryPoint {
	@Override
	public void onModuleLoad() {
		Window.alert("Hello, desktop!");
	}
}
 

Here’s what my sample project looked like after making these changes:

Using GXT in Sencha Desktop Packager

Compiling and Packaging a GWT Application

The first step to packaging an application is to create a manifest file. A manifest file describes details about where to find the resources to package, and how to start up the application. For most GWT projects, you’ll either be packaging the war/ directory, or if you use Maven, the target/<project&gr;-<version>/ directory. I created a new GWT application in Eclipse called HelloDesktop, and used the following as my manifest file, saved as HelloDesktop.json in the root of the project:

 
{
    "organizationName": "Sencha",
    "applicationName": "Hello Desktop",
    "versionString": "1.0",
    "outputPath": "packaged app",
    "webAppPath": "war/",
    "settings": {
        "mainWindow": {
          "autoShow": true
        },
        "security": {
          "allowCrossSite": true
        }
    }
}
 

Before we actually package the app, there is one important step we must do: compile the application to JavaScript. We don’t presently have a Dev Mode plugin for the Desktop Packager, so we have two main options for debugging:

  • Run in Chrome with GWT Dev Mode. Since the Sencha Desktop Packager uses a modified version of Chromium, and it should behave mostly like Chrome, without the Ion APIs.
  • Compile with style set to PRETTY and use the remote JS debugging features to perform debugging and read any logged messages.

Finally, I ran the ionpackager command, opened the application, and saw the alert on the screen:

Using GXT in Sencha Desktop Packager

Adding GXT to such a project is just as simple as following the setup.txt instructions in GXT, or reading the getting started guide in our new online GXT guides. Once configured, we can build our applications in the same way as we normally would, using layouts, data widgets, etc.

Differences Between Ion and a Browser

There are several differences between running in a packaged app and running in a real browser that may require you to tweak your application slightly. First off, there is no address bar, no bookmarks, no back button, and no refresh button. As a result, the Window.Location class cannot be used to navigate the app, and the History class cannot be used. Instead, you need to structure your application differently to hold state internally, and to avoid making any calls to modify the url — since there is no url to modify, this can cause errors.

The second difference is that files are not being loaded from a webserver, but from within the packaged application. This usually doesn’t make a big difference, but some applications verify that they get a “200 OK” from the server when they load a file, or a “404 File Not Found” if it wasn’t available. Instead, all files will be loaded with a status code of zero, but missing files will have no content. This isn’t usually a concern, but it does come up from time to time.

Using the Ion APIs in GWT

We’ve released a beta version of a GWT module that provides access to the underlying Ion API. This module is a generated jar that is based on the API documentation and is still in its early stages. It automatically includes all other content as Javadocs, but sometimes isn’t very clear on what kind of argument is to be passed in or what should be returned. We welcome any questions or bug reports about the Ion bindings in the Desktop Packager forums.

As mentioned above, there is no url to use so when we ported the GXT Explorer, we removed the PlaceHistoryHandler to prevent it from interacting with the address bar. This enables us to continue to use Activities and Places in the application without the back/forward button or bookmarks. An application could replace this with a custom PlaceChangeHandler that stores the current place in local storage, and upon starting the application, reads that last token out as the PlaceHistoryHandler.handleCurrentHistory() method does. Just as a native application would have no address bar, it would have menu bars to more easily reach certain parts of the application. So, we replaced the history manipulation with a native menu bar that lists all possible examples:

 
// Attach the Examples menu to the menubar
Menu m = Ui.mainWindowStatic().menuBar().addMenu("Examples");
// Add an overview item 
// (example is a reference to the overview)
m.addMenuItem("Overview", new Function(loadExampleFunc(
  placeController, 
  new ExamplePlace(example.getId()))), "");
// For each category, 
List<Category> cats = exampleModel.getCategories();
for (int i = 0; i < cats.size(); i++) {
  Category c = cats.get(i);
  if (!ExplorerApp.OVERVIEW.equalsIgnoreCase(c.getName())) {
    // add a new menu, and iterate through the examples there,
    Menu catMenu = m.addMenu(c.getName());
    List<Example> examples = c.getExamples();
    for (int j = 0; j < examples.size(); j++) {
      Example ex = examples.get(j);
      if (!ExplorerApp.OVERVIEW.equalsIgnoreCase(examples.get(j).getName())) {
        // adding a reference to each as we go
        catMenu.addMenuItem(ex.getName(), new Function(
          loadExampleFunc(placeController, 
          new ExamplePlace(ex.getId()))), "");
      }
    }
  }
}
 

This code currently requires the use of a helper method called loadExampleFunc written in JSNI to build a function that can be used by the Ion JavaScript API and can call into our GWT code:

 
private native JavaScriptObject loadExampleFunc(PlaceController placeController, ExamplePlace examplePlace) /*-{
  return $entry(function() {
     placeController.@com.google.gwt.place.shared.PlaceController::goTo(*)(examplePlace);
  });
}-*/;
 

The last argument, left blank in this sample, would enable a keyboard shortcut. In an app like the explorer with so many possible options, it makes more sense to skip using a shortcut, and leave the user to go through the options to find what they are looking for.

Using GXT in Sencha Desktop Packager

Playing (games) with HTML5

The Sencha Desktop Packager uses Chromium, so our application can use any features found in modern browsers such as LocalStorage, Canvas, WebGL, WebSockets, etc. When starting to write this article, I tried to think of a GWT project that uses many modern browser features to wrap up as a desktop application, and decided that the Quake 2 GWT port would be a fun and informative choice.

I started by building the project as instructed, and verifying that it worked correctly in current versions of Firefox and Chrome. While the game would play, I couldn’t seem to make multi-player work correctly — some quick investigation suggested that the websocket implementation in the nightly jetty build used doesn’t appear to work with recent browsers.

Next, I created a manifest that pointed at the war/ directory, enabled remote debugging to find any issues, compiled and ran the application. The application ran, and got us to the start screen, but the game itself wouldn’t start. Looking at the console, I discovered that many files were not loading correctly, or that the app thought that there was a problem with them. I ended up making a change to the filesystem code in GwtResourceLoaderImpl.loadResourceAsync to deal with cases where the file loaded without a 200 status code:

 
//...
String response;
// if (xhr.getStatus() != 200) {
// If we get a 200, then we loaded over http successfully
// If we get a 0 and are in the the finished readyState, then:
// - file size is not 0, and we loaded successfully
// - file size is zero, and there was an error
if ((xhr.getStatus() != 200 && xhr.getStatus() != 0) || xhr.getResponseText().length() == 0) {
  Com.Printf("Failed to load file #" + mySequenceNumber + ": " + path + " status: " + 
    xhr.getStatus() + "/" + xhr.getStatusText() + "\n");
  ResourceLoader.fail(new IOException("status = " + xhr.getStatus()));
  response = null;
} else {
  response = xhr.getResponseText();
  Com.Printf("Received response #" + mySequenceNumber + ": " + path + "\r");
}
//...
 

With that change, the game was playable! The sound wasn’t working correctly — I wasn’t yet sure if this was an issue with an unsupported codec, another file loading issue, or a bug in the packager tool with <audio> tags. I also found that with Sencha Desktop Packager 1.1, the skin on the player and the other characters wasn’t being rendered - this is fixed in Desktop Packager 1.2. There were occasionally problems with the mouse or keyboard not responding, but clicking in the window seemed to resolve them.

Using GXT in Sencha Desktop Packager

Finally, to get the original Quake experience, I made one last modification — to run the game as full screen as soon as it started. This time, since I was only invoking the one method, rather than inheriting the Ion for GWT module, I called directly into JSNI to trigger it. First, I created this new JSNI method in GwtQuake.java:

 
private native void fullscreen() /*-{
	$wnd.Ion.ui.mainWindow.fullscreen();
}-*/;
 

Then, at the beginning of onModuleLoad, I called it:

 
public void onModuleLoad() {
  fullscreen();
  // Initialize drivers.
  Document doc = Document.get();
  doc.setTitle("GWT Quake II");
  //...
 

This resizes the application to fullscreen as soon as it starts. This gets much closer to the original Quake 2 experience, but I didn’t go far enough to try playing with the mouse lock API to see if that can keep the mouse from stopping at the edge of the window. You may still experience focus issues — click on the screen if keys or mouselook aren’t working.

Building your own

Download Sencha Desktop Packager, and try it out for yourself! If you have questions, check out the documentation, and visit our forums.

UPDATE: You can find the jar file to enable the Ion APIs in your GWT project in the Desktop Packager forums, along with some specific setup instructions and documentation.

There is 1 response. Add yours.

Frank Hossfeld

1 year ago

That’s cool!
Is it correct, that there is no chance to press refresh or go back?

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.