Tuesday, December 9, 2008

GWT: No need to shortchange your style

StudyBlue is an academic social networking application, built with GWT, that helps students study smarter, faster. StudyBlue has done a great job with their application styling and design, so much so that they are shooting for a Crunchie Award on Tech Crunch for Best Design 2008.

We've been approached by many programmers and designers alike that question how we got such a custom look using GWT. Our response is simple, "Use GWT to build the house, not to paint it." When you build a new house, you don't have your building contractor pick out the furniture, choose the color palette, or stencil the walls. Similarly, you shouldn't use GWT to inject all the "style" into your website. Instead, you rough it out with GWT and push the design to CSS.

Whether you're building a small widget to embed in an existing site, or you're building an entire web-application from scratch (that's us :-) ), we recommend the following three tenants to ensure maximum design efficiency and flexibility:

  1. Know your design team

  2. Keep all visual elements in the CSS

  3. Deploy to a test environment

Know Your Design Team

Sticking with our home construction analogy, you might go about building a home by first having an architect envision a space, then having his apprentice detail the blueprints, before finally having a contractor build the house. Similarly, you should assemble a good design team before diving into the construction of your site with GWT. Chances are, if you are a strong Java programmer (i.e. "the GWT Programmer"), you are not as strong at CSS design and image slicing. You need to know what team members you have at your disposal, and what their strengths are. Once you're set with a Designer and a CSS Stylist (they may be the same person), we recommend you follow this general workflow:

  1. Create a Photoshop image of the widget/site. This file should assume that "anything is possible" and be the ideal look for your widget/site.

  2. Have the Web Designer slice the image into pieces that can be implemented using HTML Tables or DIVs. Even better if this stage can be accomplished with future flexibility in mind for mulitple uses, flexibility, etc...

  3. Layout the "blueprint" in complete HTML. Create exactly what the HTML code should look like after the respective GWT application code renders the widget/site.

  4. Begin building the layout using GWT. Remember to make mini-widgets out of every piece of reusable code.

Let's elaborate a bit on points 2, 3 and 4.

Part of knowing your team means understanding what your Web Designer's preferences are when it comes to using Tables or DIVs. GWT offers widgets that encompass both Tables (HorizontalPanel, FlexTable, etc.) and DIVs (FlowPanel, HTML, etc.). There are benefits and drawbacks to using each of them. For example, Tables might be good for vertical/horizontal centering or adding stability to a grid. They are poor for dynamic positioning, hover states, "rounded corners", etc. Whatever the case, both the GWT Programmer and the Web Designer should have an agreed understanding on which layouts (Tables or DIVs) are to be used and when it's appropriate to make exceptions. We prefer to do use DIVs, using FlowPanel much more often than HorizontalPanel or VerticalPanel.

Creating a "blueprint" is important for three reasons. First, it helps eliminate guess work from the GWT Programmer. An HTML "blueprint" can quickly be tested on all browsers to prove that the style renders to the structure as expected. Secondly, "blueprints" are an easy way to communicate between GWT Programmer and Web Designer without both parties having to be in the same room at the same time. Plus it makes it easy to assign blame when things don't look right :-). Finally, creating the "blueprint" will help you realize what looks in the original artwork are impossible to recreate on the site (hopefully none).

Note: Using the WebDeveloper plugin (or Firebug's Inspect) for Mozilla Firefox is a great way to inspect your final layout as a GWT Programmer, to make sure you match the "blueprint". More on this later.

Finally, when you start creating the layout in GWT, remember to break down each piece into a Composite class. Not every Composite you create will be reusable. Sometimes you have to make your widgets so specific that they can't be reused. That's OK, it's a common occurrence in UI programming. However, many widgets you can design to be reusable. One obvious example is "buttons" on your website.


Keep all visual elements in the CSS

We can't stress this point enough: PUSH YOUR STYLING TO A CASCADING STYLE SHEET (CSS). Just about everything you can do style wise can be done with CSS, so keep it that way.


BAD:

java
HTML randomText = new HTML("This is some random text in a DIV"); DOM.setElementProperty(randomText.getElement(), "color", "green"); DOM.setElementProperty(randomText.getElement(), "fontSize", "16px");

GOOD:

java
HTML randomText = new HTML("This is some random text in a DIV"); randomText.setStyleName("sb-TextWidgets-RandomText");
CSS
  .sb-TextWidgets-RandomText{    color: green;    font-size: 16px;  }

So, what are we gaining by doing this?
  1. Changing something as simple as color or font-size requires only a CSS update as opposed to redeploying the whole GWT code.

  2. In the above case, we cut out one line of java code. When your application is 40,000+ lines, saving 1-4 lines for every single widget can greatly improve compile time and size.

  3. The Web Designer can make style changes without ever having to look at or run Hosted Mode. (*assuming you have a good test environment - see below)

So let's look at a more complex example, a navigation bar with "hover" and "selected" states. Below you'll notice we've created reusable code for our "hover" and "selected" state that can be used by any widget that we want to implement "hover" or "selected".

Hover Class:

public class Hover {      private static MouseListenerAdapter mla;      public static MouseListenerAdapter getHoverMla(){         if(mla == null){             mla = new MouseListenerAdapter(){                     public void onMouseEnter(Widget hoverableWidget){                        hoverableWidget.addStyleDependentName("hover");                     }                     public void onMouseLeave(Widget hoverableWidget){                         hoverableWidget.removeStyleDependentName("hover");                     }                 };         }         return mla;     }         .     .     . } 
Selected Class:
public class Selected {      private List<HTML> itemList;      private ClickListener cl;         public Selected(){          itemList = new ArrayList<HTML>();          cl = new ClickListener(){             public void onClick(Widget selectedItem) {                 for(HTML item : itemList){                     if(item.equals(selectedItem)){                         item.addStyleDependentName("selected");                     }                     else{                         item.removeStyleDependentName("selected");                     }                 }             }         };             }         public ClickListener getSelectedCL(){         return cl;     }         public void addItem(HTML item){         itemList.add(item);         item.addClickListener(cl);     }         public void removeItem(HTML item){         itemList.remove(item);         item.removeClickListener(cl);     }     }
Navigation Bar Class:
public class NavBar extends Composite{      public static String BLOG_HOME="http://blog.studyblue.com/";         private int ID=1;     private Selected selector;      public NavBar(){         super();          selector = new Selected();                 FlowPanel holder = new FlowPanel();         holder.setStyleName("NavBar-Holder");                     holder.add(link("Blog",""));         holder.add(link("About Us","about"));         holder.add(link("Jobs","about/jobs"));                setWidget(holder);     }      private HTML link(String text, String location){         HTML ret = new HTML(text);         ret.setStyleName("nav-item-"+ID);         ret.addStyleName("nav-item");          selector.addItem(ret);         ret.addMouseListener(Hover.getHoverMla());         ret.addClickListener(new ClickListener(){             public void onClick(Widget sender){                ///Open popup with blog page location             }         });          ID++;          return ret;     }  } 
CSS code:
.nav-item {     background-image: url(images/nav.png);        background-repeat: no-repeat;        background-position: 0 0;        display: block;        height: 0px;        padding: 30px 0 0 0;        overflow: hidden; }  .nav-item-1 { background-position: 0 0;        width: 65px; } .nav-item-2 { background-position: -65px 0;    width: 90px; } .nav-item-3 { background-position: -155px 0;   width: 65px; }  .nav-item-1-hover { background-position: 0 -30px; } .nav-item-2-hover { background-position: -65px -30px; } .nav-item-3-hover { background-position: -155px -30px; }  .nav-item-1-selected { background-position: 0 -60px; } .nav-item-2-selected { background-position: -65px -60px; } .nav-item-3-selected { background-position: -155px -60px; }
Image File (nav.png)


So, let's break down what is going on here. The NavBar is a Composite which is holding a FlowPanel ("holder"). This FlowPanel is a DIV which is going to hold our 3 links: "blog", "about us", and "jobs". Each one of these links is an HTML widget (i.e. a DIV). Inside the method "link" we first are creating each HTML with the appropriate text, setting its style name with a unique integer, adding an additional style name. From there, we add the link to our "selector" as well as add a "hover" listener. Finally, we add a ClickListener which initiates an action (in this case taking the user to the blog).

Some things to note:

  • Note the difference between "setStyleName" and "addStyleName". HTML widgets come default with "gwt-HTML" as the SET style name. When you call setStyleName(), you replace "gwt-HTML" with whatever you choose. This new SET style name is what is used when the method "addStyleDependentName" is called. addStyleName() simply just adds an additional style, which is not used for "addStyleDependentName".

  • In our case, the "hover" and "selected" style names were added dynamically using addStyleDependentName(). So, we needed to make sure that the SET style name was the complete identity (i.e. "nav-item-1"). addStyleDependentName() takes care of adding the additional style names (i.e. "nav-item-1-hover" and/or "nav-item-1-selected").

  • The additional style name, "nav-item" is added permanently onto each link, but is not the SET style name. Thus, it is always present, despite the addition/subtraction of style dependent names.

  • The Web Designer and GWT Programmer both know that all widgets will have a style DEPENDENT name of "hover" and "selected" (if necessary), which means the GWT Programmer can recycle the Hover and Selected code, and the Web Developer can anticipate what style names will be present for "hover" and "selected"

  • *CSS Tip: Loading all of the states (off, selected and hover) into the one nav.png image, helps prevent flickering when the user hovers or selects the link.

Without our CSS, the result would simply look like this:

Blog About Us Jobs


However, by adding a few style names, we are able to achieve a look like this:


We also streamlined our development by standardizing the use of stand-alone buttons on the site. That way, the GWT Programmer can create a button widget that can be reused depending on the size and color button needed. The widget uses DEPENDENT style names to produce small, medium, or large buttons colored with green, white, blue, or red that are preset in the style sheet and work for flexible content lengths.


Deploy To A Test Environment

It is really important to have a test environment for your Web Designer to mess with. As a GWT Programmer, you can't expect that your Web Designer will be able to run Hosted Mode. Also, until the GWT guys implement Out-of-Process Hosted Mode (OOPHM), you will definitely want to take a look at your product in all the major browsers before deploying to production. We run a Tomcat instance on a powerful box over at Amazon Web Services. Using SVN and Ant, we update all our code to the test machine, compile it, create a WAR and deploy it using one command. The key here is that this process takes over 6 minutes every time we want to deploy new code. Six minutes may not seem like that long, but when your Web Developer is waiting 6 minutes every time you need to make a small change to the layout, it gets annoying. So we implemented two ideas to help ease the pain.

First, if you take one thing away from this post, don't style using the GWT DOM class. Set your style names and let the CSS do the work. If we had put all the styles in the GWT code, it would take us 6 minutes to change the color of some trivial text, only to find out that we liked it better the old way (another 6 minutes).

Second, pull out your CSS files and images directory to some location outside your WAR. The idea here is that the Web Designer can manipulate the CSS and imagery without ever getting inside your WAR. This way, when its time to deploy new code, you don't overwrite the Web Designer's changes by replacing the CSS file and/or images on accident. This also helps with remote collaboration. The Web Designer gives the GWT Programmer the "blueprints", the GWT Programmer builds the app and deploys it to the test server, the Web Designer is free to make changes to the CSS and images while checking compatibility across all the browsers. Theoretically, you'd never have to be in the same room (although we don't prefer it that way).

Now get out there and build an awesome GWT house!


[NFGB] Link - from Google Web Toolkit Blog
Related From Google Blogs:
The Santa countdown begins...
Spice up your inbox with colors and themes
Sorting 1PB with MapReduce
Our international approach to search

No comments: