There's more than one way to skin a GWT app. In this guest blog post, Erik Uzureau, User Experience Engineer at TravelTripper, shares their approach.
--
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:
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.
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.
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.
DockLayoutPanel
SimplePanel
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.
onModuleLoad()
tt-Header
tt-Footer
tt-EastSidebar
tt-WestSidebar
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.
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:
master.css
designer.css
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.
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>Paradise Hotel Reservation System</title> <!-- Master CSS File --> <!-- All styles in here can be overridden in designer.CSS (below) --> <link rel="stylesheet" href="../reztrip/CSS/master.CSS" type="text/CSS"/> <!-- Designer CSS File --> <!-- Single css file to be created and modified by the customizer. --> <!-- New styles for the frame and overridden style from master.CSS go here --> <link rel="stylesheet" href="style/designer.CSS" type="text/CSS"/> <script type="text/javascript" language="javascript" src="../reztrip/reztrip.nocache.js"></script> </head> <body> <div id="tt-Header" class="tt-Frame-Header"> <!-- because tt-Header has content, we will load it and auto-size --> <!-- it in the header area of the application frame --> <table class="tt-Frame-HeaderTable"> <tr> <td> <img src="img/paradise_logo.png"></img> </td> <td> <img src="img/paradise_menu.png"></img> </td> </tr> </table> </div> <div id="tt-Footer" class="tt-Frame-Footer"> <!-- empty, so no footer will load --> </div> <div id="tt-EastSidebar" class="tt-Frame-EastSidebar"> <!-- empty, so no east sidebar will load --> </div> <div id="tt-WestSidebar" class="tt-Frame-WestSidebar"> <!-- empty, so no west sidebar will load --> </div> <!-- OPTIONAL: include this if you want history support --> <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex="-1" style="position:absolute;width:0;height:0;border:0"></iframe> </body> </html>