GWT: Dynamically Changing CSS Styles

Are you building a Google Web Toolkit application? Has someone asked you to personalize colors or images for their account? If so, you may have tried to look for things like dynamic CSS in GWT as well. While Googling these terms, I could not find what I needed. After some more Googling and reading some not so helpful posts, I found a project titled Advanced GWT Components.

This project has a set of widgets which support switching themes at runtime. That was exactly what I was looking for, but I had no need for the widgets. So I picked the project apart and adapted the way it changes CSS on runtime to fit our application. In this post I will take you through the pieces of code we used to complete this. Note: this is going to be quite a long and code heavy post ^_^

We’ve got the following:

  • GWT views with UIBinder
  • Default styling in CSS
  • Only a few things which are customizable in the application (mostly colors)
  • A preference page which allows users to change these colors
  • A servlet that generates CSS based on the settings

The (highly simplified) diagram below shows the main components and their interactions. Basically what happens is the user performs some action in the GWT Client that changes something like a color setting. This causes the client to send the update over RPC to the server. After a successful store, an event is triggered inside the GWT client that this setting has been altered. Some listener picks up the event and changes the CSS reference. This change forces the browser to load the ‘new’ CSS file from the server. The server then generates this CSS file based on the settings. By now the circle is round and the updated CSS is loaded and applied.

Diagram of GWT Client/Server interaction for dynamic CSS

I’m not going to talk about the Settings page and the RPC call as these are just standard GWT. You can simply incorporate the following parts into your GWT application and fire the SettingChangedEvent (or any other event) and change the CSS to reload it.

Coding Time!

Let’s introduce some pieces of code we have here. Our settings backend stores settings with a String key and fires off events of changes in settings to the GWT client. The Event triggered to notify the application of these changes looks like this:

/**
 * An Event of this type indicates one of the settings has been changed.
 * 
 * @author J.H. Kuperus
 */
public class SettingChangedEvent extends GwtEvent<SettingChangedEvent.Handler> {

  /** Handler interface for the {@link SettingChangedEvent}. */
  public interface Handler extends EventHandler {
    void onSettingChanged(SettingChangedEvent event);
  }

  private String propertyName;

  public SettingChangedEvent(String propertyName) {
    this.propertyName = propertyName;
  }

  public String getPropertyName() {
    return propertyName;
  }

  // --- Boilerplate code for GWTEvents

  // ...	
}

It simply passes the name of the changed setting around. This is what we respond to on the client side to change the CSS. If it is any property that is part of the dynamic CSS, we change the URL of the referenced CSS document, causing it to be reloaded by the browser. The piece of code that handles the event looks like this:

public class ColorChangeListener implements SettingChangedEvent.Handler {

  @Inject
  public ColorChangeListener(EventBus eventBus) {
    eventBus.addHandler(SettingChangedEvent.TYPE, this);
  }

  public void onSettingChanged(SettingChangedEvent event) {
    if (event.getPropertyName().startsWith("core.color")) {
      // If the color has changed, force a reload on the CSS
      ThemeUtil.setLinkHref("themeCssElement", "/activeTheme.css"
          + "?v=" + System.currentTimeMillis());
    }
  }
}

The listener implements the event’s Handler interface and registers itself to the EventBus. Note that we are using GIT injection to resolve these dependencies. If you obtain your EventBus in a different fashion, adjust your code accordingly.

The parameter added to the URL of the CSS-file is purely bogus, but prevents any proxy or browser from caching the file. The TemeUtil.setLinkHref is the piece of code I borrowed from the Advanced GWT Components library and looks like this:

public class ThemeUtil {
  public static native void setLinkHref(String linkElementId, String url) /*-{
    var link = $doc.getElementById(linkElementId);
    if (link != null && link != undefined) {
      link.href = url;
    }
  }-*/;
}

The setLinkHref method is a native piece of JavaScript that tries to find the element identified by the linkElementId and sets its href property to the specified URL. In order for all of this to work, you must also include your dynamic CSS in your main HTML file. We simply added the following to the <head> section:


Alright, that’s all the client code required for this work. Now let’s hop over to the last piece we need to build: the CSS servlet. This servlet basically just extends the HttpServlet class and implements the service() method to write a standard CSS file. In our app, we use a class called ServerSettings to retrieve the stored colors from the Datastore. Adapt this to your situation as needed:

public class CssThemeServlet extends BaseServlet {

  @Inject
  private ServerSettings settings;

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    res.setContentType("text/css");
    PrintWriter writer = res.getWriter();

    StringValue coreColor1Setting = settings.getStringSetting("core.color1");
    StringValue coreColor2Setting = settings.getStringSetting("core.color2");

    writer.println(".themedGradientBackground {");
    writer.println("    background-image: -moz-linear-gradient(top, "
      + colorStop1 + ", " + colorStop2 + ");");
    writer.println("    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, "
      + colorStop1 + "), color-stop(1, " + colorStop2 + "));");
    writer.println("}");
  }
}

You can write any CSS you want here, however, writing it with println-statements will get crazy fast. So think about a using a template engine, or putting it in a JSP. We are probably going for Velocity.

The last step to make all of this work, is to serve the URL used above (/activeTheme.css) with the CssThemeServlet. You either put it in your web.xml, or in you Guice ServletModule. Choose the appropriate method for your setup:

  <servlet>
    <servlet-name>cssTheme</servlet-name>
    <servlet-class>CssThemeServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>cssTheme</servlet-name>
    <url-pattern>/activeTheme.css</url-pattern>
  </servlet-mapping>
  serve("/activeTheme.css").with(CssThemeServlet.class);

Last but certainly not least, we still need to fire the Event somewhere. This is most likely done from a drop down or list box. Implement the appropriate event handler for your box (probably something like onChange):

public void onChange(ChangeEvent event) {
  // if event.getSource() is our list box
  eventBus.fireEvent(new SettingChangedEvent("theme"));
}

Now you can use the classes from your dynamic CSS in your widgets to provide dynamic themed colors, borders, shadows, backgrounds, whatever your want. If you have any questions, feel free to leave a comment. Happy theming! ^_^

–JH

Posted in GWT, Java, Tutorials.