Friday, February 1, 2013

CSS Resources: @Import and @ImportedWithPrefix

CSS Resources

@Import and @ImportedWithPrefix

A CSS Resource can import other CSS resources. GWT supplies two annotations for this: @Import and @ImportedWithPrefix.
Unfortunately, the online examples and tutorials that I have found for this are woefully incomplete. They all suffer from what I have found to be an ever-increasing approach to programming discussions and documentation: they have either been written hypothetically (i.e., the code they list was never actually run), or, if they did come from a working example, the small details crucial to understanding and implementing the concept have been left out.
Plus, to make things even more confusing, the @Import annotation does not produce the same effect as using @import within a CSS file. When used in a CSS file associated with a CssResource, @import doesn't seem to do anything.
So, below is my attempt at describing these two annotations and how they are used.
A CssResource sub-interface can have a prefix automatically prepended to its style class names by annotating it with the @ImportedWithPrefix("prefix") annotation. As a result, all the style class names will have the prefix prepended, regardless of whether the CSS resource is imported into another, of just used without importing it anywhere(a better name for the annotation might have been @PrefixedWith). In the associated CSS file, however, you should write the class names without the prefix (which would match the method names in the interface).
Another CSS resource can import one or more additional CssResource classes by creating a method that returns a CssResource, annotated with @Import. The value supplied for the annotation can be a single class object for another CssResource, or an array of those class objects. Given that the imported class names will have the prefix regardless of whether they're imported anywhere, the real benefit of this is that the associated stylesheet can modify the rules involving the imported class name (but, they would now use the prefix).
For any class names that the importing CSS does want to use, there must be an associated method in the interface (but, without any prefix). It appears that the GWT compiler, as of GWT 2.5, will allow the CSS file to have any prefixed class names as long as the end part of the name is declared as a method in the interface, even if the specific prefix applied doesn't match anything that was imported.
public interface CssResourceNameToBePrefixed extends CssResource {
 public String accessorMethods();
Within the associated CSS file, all the style class names used in the interface must be defined, but without the prefix. When the resource is imported into another, the resulting class definitions will have the prefix (of course, you won't be able to tell, since they will be obfuscated).
Note that by doing this, you are locking this resource into always using the specified prefix.
public interface BundleClass extends ClientBundle {

 public CssResourceNameToBePrefixed accessorMethods();
 @Import(value=Class<? extends CssResource>)
 public ImportingCssResourceClassName accessorMethods();
The CSS file associated with the new CSS resource can also contain rules involving the imported classes, but now those rules must include the prefix.
But, to use the prefixed rules, your client bundle must have an instance of the CSS resource that originally defined them, not just the one that imported them. So, the client bundle containing the code snippet above, you must also define a method to retrieve the original CSS resource (the one with @ImportedWithPrefix). In fact, a CSS resource that uses the @ImportedWithPrefix annotation doesn't even have to be imported into any other - you can just invoke the accessor methods to retrieve the obfuscated name, which will be different from the same name defined in a different CSS resource with a different prefix or no prefix.
But, if this resource is imported into another, any rules added by the importing resource's CSS file will be applied also, since that caused the CSS to be generated and injected.

A Working Example

The Eclipse Project
Note that in order to minimize the file size, the gwt-servlet.jar file is not included. After unzipping and importing into an Eclipse workspace, you can usually reinstate it by going to the project's properties, under the Google ... Web Toolkit sections, removing the check from Use Google Web Toolkit, then OK. Then go back through the same process to put the check back and OK again.
You can try the code below to see all three CSS resources in action. There are embedded notes regarding code to comment out to try it without any instance of the Styles resource - they must be done as a group.
/* Styles.css */
@external .gwt-HTML;

.special { font-size: 14pt; }

/* Try commenting out the two x-special rules below */
.a-special { font-size: 18pt; }

.b-special { font-size: 22pt; }

/* Not related to the example, just to make things look nicer */
html .gwt-HTML {
 margin-top: 12px;
 border: 1px solid blue;
 padding: 12px;
 border-radius: 6px;
package whatever.client;
public interface Styles extends CssResource {
 public String special();
/* AStyles.css */
.special { background-color: #ffaaaa; }
package whatever.client;


public interface AStyles
//extends Styles  // would also work with this instead of below
extends CssResource {
 public String special();
/* BStyles.css */
.special { background-color: #aaaaff; }
package whatever.client;


public interface BStyles
//extends Styles  // would also work with this instead of below
extends CssResource {
 public String special();
package whatever.client;


public interface SomeResources extends ClientBundle {
 /* Try commenting out the three lines below, after
  * commenting out the prefixed lines in the Styles.css file
 @Import({AStyles.class, BStyles.class})
 public Styles styles();
 public AStyles aStyles();
 public BStyles bStyles();
 public static final SomeResources INSTANCE =
In the code below, the lines marked with /**/ are to be commented out to try it without any instance of the Styles class.
package whatever.client;


public class CssResourceImports implements EntryPoint {
 private static final SomeResources resources
  = SomeResources.INSTANCE;
 private static final AStyles aStyles = resources.aStyles();
 private static final BStyles bStyles = resources.bStyles();
 private static final Styles styles = resources.styles();    /**/

 public void onModuleLoad() {
  styles.ensureInjected();                                  /**/
  FlowPanel panel = new FlowPanel();
  HTML special = new HTML("Special");                       /**/
  special.addStyleName(styles.special());                   /**/
  panel.add( special );                                     /**/
  HTML aSpecial = new HTML("A Special");
  panel.add( aSpecial );
  HTML bSpecial = new HTML("B Special");
  panel.add( bSpecial );
  RootPanel.get().add( panel );