Styling and skinning your apps with TravelTripper

Styling/Skinning RezTrip

At TravelTripper, we make hotel reservation software. Our main product is a “booking engine” called RezTrip, a web based application that allows visitors of a hotel’s website to directly book a stay with that hotel.

As GWT applications go, we think RezTrip, when it comes to the question of styling, presents an interesting departure from traditional development. As a “white label” application, we needed to create our app in such a way that allows our hotel clients the ability to customize not only the “frame” around the application, but also the internal style of the application itself, such as fonts, colors, etc.

In other words, each hotel needs the ability to create their own custom header, footer, or sidebar and have it wrap the booking “application” portion of the page. Furthermore, each hotel needs to be able to change all the colors, fonts, and even some icons within the application.

The desired end result is a single booking engine application, running on multiple web sites, but always mimicking the look and feel of each individual hotel site.

We have two additional constraints:

  1. Cost – While our first priority is building a system with ultimate flexibility, the time spent to create each customization represents a direct bite into our profit margins. The business guys were explicit about keeping these costs to a minimum.
  2. Dynamic Changes – Clients are naturally picky about the appearance of their website, and their tailored booking engine is no exception. Experience told us that we would be fielding constant requests to tweak different aspects of the customized properties. This has to be easy to do and have minimal impact on the overall site performance.

Satisfying our Constraints

Keeping Costs Low

Our in-house GWT team is top-notch, but expensive. The previous version of our application was built on basic JSP/HTML/CSS technology, and the customization work had been done by a more affordable entry-level web designer. Similarly, for this version of the application, we wanted to limit the involvement of our GWT developers as much as possible, where possibly leaving stylistic tweaks to our web designer.

We want the customizer to be able to do *all* the work, without requiring any Java or GWT knowledge.

Making Changes Easy and Harmless

We realized that GWT’s application compilation philosophy changed a lot of our longstanding web development assumptions. We didn’t want to create custom UiBinder files for each hotel’s frame, or have to make spot changes to CSS that would require a full recompile and redeployment of the application.

We want to be able to make CSS changes without recompiling or redeploying the app.

Our Solution

The only way to satisfy the above two constraints is to have all the customization work happen in simple HTML/CSS files that live outside the GWT project and WAR directory. This allows the customizer to work in pure HTML/CSS, directly with the files on the server, without ever having to modify the internals of the GWT application. Changes can take effect immediately, without a need to redeploy the app.

The Application Frame

We decided to have a separate index.html file for each property’s customization. This allows custom header/footer/sidebar HTML, CSS and JavaScript to be included in the hotel’s main page.

Another challenge for us was the need for the application portion of the booking engine to be able to be dynamically resized relative to the user’s browser. To accomplish this, we decided to use a DockLayoutPanel, which handles the separation between the main application and the custom frame. We load an empty SimplePanel into each of the North, South, West, East sections of the DockLayoutPanel, and our application in the Center.

Next, we add special code that runs directly from onModuleLoad() that scours the host HTML document for four DIVs with 4 unique ids: tt-Header, tt-Footer, tt-EastSidebar, and tt-WestSidebar. If the app finds a DIV with those ids, it loads it into the corresponding SimplePanel and auto-sizes to the contents. If no corresponding DIV is found, the app hides the SimplePanel entirely and sets the width or height to 0.

What this means is, that the customizer doesn’t get to lay out the HTML page exactly like it will be displayed when live. Instead s/he must smash the relevant content into the four qualified DIVs. This is a minor annoyance, but at the end of the day, the code itself is still the same basic HTML/CSS/JavaScript, and so it is perfectly manageable by the web designer. The GWT application is already compiled to super-fast JavaScript, so there’s no need for the customizer to know any GWT. Instead, the customizer can just edit the contents in the HTML file and hit refresh to see the changes.

The CSS

Here again, we had to come up with a custom solution. To avoid the recompile/redeploy issue and also to keep it simple for the customizer, we had to handle the CSS for customizing the application without having to modify any code inside the GWT project.

What we ended up doing was creating three levels of CSS:

  1. UiBinder – Any time we needed to use a CSS style to adjust the size or layout of a widget or panel, we kept that CSS code in UiBinder XML. We only want the actual GWT developers to change the layout/size of the UI elements, so they needed to be in a sense “hidden” from the customizer. We loved how the default UiBinder behaviour is to generate md5 class names, allowing us to create lots of custom CSS rules, without worrying about namespace overlap and also ensure that the customizer would know not to override them.
  2. master.css – Next, we created a “master” CSS file where we put all the other CSS styles, which we thought were fair game, to be overridden by the customizer. This is a huge CSS file, but when minimized (yuicompressor) and gzipped, the end result was still better than the latency hit we had when originally we tried to spread these across multiple files (for organizational purposes). Note that the master.css file is loaded directly from the main HTML file.
  3. designer.css – The final layer is the “designer” CSS, which is where we have the customizer put all the CSS rules which override the defaults. The class names in this file all match the class names in the master CSS, but since we load the designer CSS in the page *after* we load the master CSS, the former will always override the latter.

Summary

Despite our constraints, we were able to configure our GWT application to perform exactly as we desired. Our application is fully customizable, both in terms of the surrounding frame layout and also the internal application’s colors and fonts, all without the customizer having to know any Java or GWT. By carefully separating the different layers of the app, we were able to make it easily and efficiently customizable on the fly, without ever having to redeploy the application.

Beauty On the Outside, High Replication on the Inside

Today we launched Google Art Project in collaboration with 17 of the world’s most renowned museums. Google Art Project is built on top of App Engine and lets you take virtual tours of famous museums using internal Street View technology, view high resolution images of famous art work, and create personal virtual artwork collections.

When Art Project started development several months ago, the team built the application using Java and the Master/Slave Datastore. However, as their launch date approached, we released the new High Replication Datastore configuration and, with a scheduled maintenance period so soon after the site’s launch, they decided to switch over to the High Replication Datastore.

Before switching, they ran a load test to set a performance baseline for comparison after the application’s data was migrated. Now that the application has launched, we wanted to share the results of the test with you as an example of what to expect after a switch to the High Replication Datastore. Below are the mean numbers for latency of different parts of the site.

Here’s a description of what each page does behind the scenes:

Homepage: This is the landing page that just serves a static webpage for site navigation. Since this page does not pull information from the datastore, the latency is stable.

Collections: Art Project lets users create individual museum collections. These load tests specifically targeted adding and deleting paintings from a user’s personal collection, as well as rendering those collections. We notice the slightly increased latency from saving and deleting entities in the datastore.

Level Maps: These pages simply performed get() calls on the datastore using entity keys. Latency on these pages is consistent across instances.

Info Spots: This handler performs the most data intensive calculations of all of the handlers. It calculates all line of sight interest points for a user’s map position in a museum gallery room, and saves the points of interest to the datastore for that location. The good news is, this calculation doesn’t have to happen for every user. Once this data has been calculated for a given spot, it can re-used for other visitors to the site.

As you can see, while there was some increased latency when switching to the High Replication datastore, the site latency is still very low. And the migration required no major code changes and no modification to the datastore structure between the two load tests.

For more information about the High Replication Datastore, see the Datastore documentation. The next scheduled maintenance period for the Master/Slave datastore is February 7th, 2011 from 5pm – 6pm PST. The High Replication datastore and Google Art Project will not need to be read-only during the by this period. On the High Replication Datastore, your application won’t need to be either.

YouTube Captions Uploader Web App

Captions can greatly enhance the experience of viewing a YouTube video, and the YouTube API has offered developers ways to upload and retrieve caption data in authorized requests for a while now. However, the various YouTube API client libraries don’t natively support interacting with captions at this time, and writing your own code for uploading or retrieving captions can be challenging.

With that in mind, we’re happy to announce the YouTube Captions Uploader open source project on Google Code, which provides real-world code for uploading captions to YouTube. The code is written for the Java App Engine environment, and it uses some nifty new App Engine features like the Channel API, the Blobstore Service, and Task Queues. And even if you’re not an App Engine developer, we hope that the code that interacts with the YouTube API’s captions service will provide a good starting point for writing your own code.

In addition to open sourcing the code for this project, we’re also running the code itself on a public App Engine instance, http://yt-captions-uploader.appspot.com/. So, even if you’re not a developer, you can still use the application to upload captions for videos in your YouTube account.

Please share your comments or feedback via the project’s issue tracker. We hope that you find it useful both as a standalone web application and as a starting point for writing your own code!