Client Bundle
The resources in a deployed GWT application can be roughly categorized into resources to never cache (.nocache.js
), to cache forever (.cache.html
), and everything else (myapp.css
). The ClientBundle interface moves entries from the everything-else category into the cache-forever category.
- Overview
- DataResource
- TextResource and ExternalTextResource
- ImageResource
- GwtCreateResource
- CssResource
- CssResourceCookbook
Overview
Goals
- No more uncertainty about whether your application is getting the right contents for program resources.
- Decrease non-determinism caused by intermediate proxy servers.
- Enable more aggressive caching headers for program resources.
- Eliminate mismatches between physical filenames and constants in Java code by performing consistency checks during the compile.
- Use 'data:' URLs, JSON bundles, or other means of embedding resources in compiled JS when browser- and size-appropriate to eliminate unneeded round trips.
- Provide an extensible design for adding new resource types.
- Ensure there is no penalty for having multiple
ClientBundle
resource functions refer to the same content.
Non-Goals
- To provide a file-system abstraction
Examples
To use ClientBundle, add an inherits
tag to your gwt.xml
file:
<inherits name="com.google.gwt.resources.Resources" />
If you write this interface:
public interface MyResources extends ClientBundle {
public static final MyResources INSTANCE = GWT.create(MyResources.class);
@Source("my.css")
public CssResource css();
@Source("config.xml")
public TextResource initialConfiguration();
@Source("manual.pdf")
public DataResource ownersManual();
}
You can then say:
MyResources.INSTANCE.css().ensureInjected();
// Display the manual file in an iframe
new Frame(MyResources.INSTANCE.ownersManual().getSafeUri().asString());
I18N
ClientBundle
is compatible with GWT's I18N module.
Suppose you defined a resource:
@Source("default.txt")
public TextResource defaultText();
For each possible value of the locale
deferred-binding property, the ClientBundle
generator will look for variations of the specified filename in a manner similar to that of Java's ResourceBundle
.
Suppose the locale
were set to fr_FR
. The generator would look for files in the following order:
- default_fr_FR.txt
- default_fr.txt
- default.txt
This will work equally well with all resource types, which can allow you to provide localized versions of other resources, say ownersManual_en.pdf
versus ownersManual_fr.pdf
.
Pluggable Resource Generation
Each subtype of ResourcePrototype
must define a @ResourceGeneratorType
annotation whose value is a concrete Java class that extends ResourceGenerator
. The instance of the ResourceGenerator
is responsible for accumulation (or bundling) of incoming resource data as well as a small degree of code generation to assemble the concrete implementation of the ClientBundle
class. Implementors of ResourceGenerator
subclasses can expect that only one ResourceGenerator
will be created for a given type of resource within a ClientBundle
interface.
The methods on a ResourceGenerator
are called in the following order
init
to provide theResourceGenerator
with aResourceContext
prepare
is called for eachJMethod
theResourceGenerator
is expected to handlecreateFields
allows theResourceGenerator
to add code at the class levelcreateAssignment
is called for eachJMethod
. The generated code should be suitable for use as the right-hand side of an assignment expression.finish
is called after all assignments should have been written.
ResourceGenerators
are expected to make use of the ResourceGeneratorUtil
class.
Levers and knobs
ClientBundle.enableInlining
is a deferred-binding property that can be used to disable the use ofdata:
URLs in browsers that would otherwise support inlining resource data into the compiled JS.*ClientBundle.enableRenaming
is a configuration property that will disable the use of strongly-named cache files.
Resource types
These resource types are valid return types for methods defined in a ClientBundle:
- ImageResource
- CssResource
- DataResource
- ExternalTextResource
- GwtCreateResource
- TextResource
- Subclasses of ClientBundle (for nested bundles)
DataResource
A DataResource is the simplest of the resource types, offering a URL by which the contents of a file can be retrieved at runtime. The main optimization offered is to automatically rename files based on their contents in order to make the resulting URL strongly-cacheable by the browser. Very small files may be converted into data:
URLs on those browsers that support them.
interface Resources extends ClientBundle {
Resources INSTANCE = GWT.create(Resources.class);
@Source("mycursor.cur")
DataResource customCursor();
}
// Elsewhere
someDiv.getStyle().setProperty("cursor", "url(" + Resources.INSTANCE.customCursor().getUrl() + ")");
Resources that are not appropriate for being inlined into the compiled JavaScript as data:
URLs will be emitted into the compilation output with strong names, based on the contents of the file. For example, foo.pdf
will be given a name similar to ABC1234.cache.pdf
. The webserver should be configured to serve any files matching the *.cache.*
glob with publicly-cacheable headers and a far-future Expires
header.
TextResource and ExternalTextResource
The related resource types TextResource and ExternalTextResource provide access to static text content. The main difference between these two types is that the former interns the text into the compiled JavaScript, while the latter bundles related text resources into a single file, which is accessed asynchronously.
interface Resources extends ClientBundle {
Resources INSTANCE = GWT.create(Resources.class);
@Source("a.txt")
TextResource synchronous();
@Source("b.txt")
ExternalTextResource asynchronous();
}
// Using a TextResource
myTextArea.setInnerText(Resources.INSTANCE.synchronous().getText());
// Using an ExternalTextResource
Resources.INSTANCE.asynchronous().getText(new ResourceCallback<TextResource>() {
public void onError(ResourceException e) { ... }
public void onSuccess(TextResource r) {
myTextArea.setInnerText(r.getText());
}
});
ImageResource
This section describes how ImageResource can tune the compile-time processing of image data and provide efficient access to image data at runtime.
Goal
- Provide access to image data at runtime in the most efficient way possible* Do not bind the
ImageResource
API to a particular widget framework
Non-Goals
- To provide a general-purpose image manipulation framework.
Overview
- Define a ClientBundle with one or more ImageResource accessors. For each accessor method, add an @Source annotation with the path of the new image you want to add to your program. The ClientBundle generator combines all of the images defined in your interface into a single, optimized image.
interface Resources extends ClientBundle {
@Source("logo.png")
ImageResource logo();
@Source("arrow.png")
@ImageOptions(flipRtl = true)
ImageResource pointer();
}
- Instantiate the
ClientBundle
via a call toGWT.create()
. - Instantiate one or more Image widget or use with the CssResource @sprite directive.
For example, the code:
Resources resources = GWT.create(Resources.class);
Image img = new Image(resources.logo());
causes GWT to load the composite image generated for Resources and then creates an Image that is the correct subregion for just the logo image.
ImageOptions
The @ImageOptions
annotation can be applied to the ImageResource
accessor method in order to tune the compile-time processing of the image data:
flipRtl
is a boolean value that will cause the image to be mirrored about its Y-axis whenLocaleInfo.isRTL()
returnstrue
.repeatStyle
is an enumerated value that is used in combination with the@sprite
directive to indicate that the image is intended to be tiled.
Supported formats
ImageBundleBuilder
uses the Java ImageIO framework to read image data. This includes support for all common web image formats.
Animated GIF files are supported by ImageResource
. While the image data will not be incorporated into an image strip, the resource is still served in a browser-optimal fashion by the larger ClientBundle
framework.
Converting from ImageBundle
Only minimal changes are required to convert existing code to use ImageResource
.
ImageBundle
becomesClientBundle
AbstractImagePrototype
becomesImageResource
AbstractImagePrototype.createImage()
becomesnew Image(imageResource)
- Other methods on
AbstractImagePrototype
can continue to be used by callingAbstractImagePrototype.create(imageResource)
to provide an API wrapper.
GwtCreateResource
The GwtCreateResource is a bridge type between ClientBundle
and any other (resource) type that is default-instantiable. The instance of the GwtCreateResource
acts as a factory for some other type.
interface Resources extends ClientBundle {
Resources INSTANCE = GWT.create(Resources.class);
@ClassType(SomeClass.class)
GwtCreateResource<ReturnType> factory();
}
// Elsewhere
ReturnType obj = Resources.INSTANCE.factory().create();
While the above is equivalent to
ReturnType obj = GWT.<ReturnType> create(SomeClass.class);
it allows the consuming classes to be ignorant of the specific class literal passed into GWT.create()
. It is not necessary for there to be a specific deferred-binding rule in place for SomeClass
as long as that type is default-instantiable.
CssResource
This section describes CssResource and the compile-time processing of CSS.
See also the CssResourceCookbook and StyleInjector.
Goals
- Primary
- Compatibility with non-GWT-aware CSS parsers (i.e. any extensions should be valid CSS syntax)
- This does not imply that the stylesheet would necessarily make sense if you just displayed it in a browser
- Syntax validation
- Minification
- Leverage GWT compiler
- Different CSS for different browsers, automatically
- Static evaluation of content
- Secondary
- Basic CSS Modularization
- Via dependency-injection API style
- Widgets can inject their own CSS only when it's needed
- BiDi (Janus-style?)
- CSS image strips
- "Improve CSS"
- Constants
- Simple expressions
- Basic CSS Modularization
- Tertiary
- Runtime manipulation (StyleElement.setEnabled() handles many cases)
- Compile-time class-name checking (Java/CSS)
- Obfuscation
Non-Goals
- Server-side manipulation
- All features in CssResource must be implemented with compile-time and runtime code only. No features may depend on runtime support from server-side code.
Overview
- Write a CSS file, with or without GWT-specific extensions
- If GWT-specific extensions are used, define a custom subtype of CssResource
- Declare a method that returns CssResource or a subtype in an ClientBundle
- When the bundle type is generated with
GWT.create()
a Java expression that evaluates to the contents of the stylesheets will be created- Except in the simplest case where the Java expression is a string literal, it is generally not the case that a CSS file could be generated into the module output
- At runtime, call
CssResource.ensureInjected()
to inject the contents of the stylesheet- This method is safe to call multiple times, as subsequent invocations will be a no-op
- The recommended pattern is to call
ensureInjected()
in the static initializer of your various widget types
Features
Constants
@def small 1px;
@def black #000;
border: small solid black;
- The parse rules make it difficult to use delimiting tokens for substitutions
- Redefining built-in sizes allows users to write plain CSS to draft a style and then tweak it.
- Suggest that users use upper-case names, similar to static final members.
- Any legal property value or expression may be used with
@def
@def
rules that define a single numeric value may be accessed in a manner similar to obfuscated class names by defining an accessor method on the CssResource type that returns a primitive numeric value.
interface MyResources extends CssResource {
int small();
}
-
Calling
small()
would return the value1
. -
@def` rules can be accessed as a String as well. You can retrieve the two definitions above with:
interface MyResources extends CssResource {
String small();
String black();
}
- Calling
small()
returns "1px" - Calling
black()
returns "#000" - The Generator will not allow you to declare an
@def
rule with the same name as a class, unless you annotate method to retrieve the class with the@ClassName
annotation.
@def myIdent 10px;
.myIdent {
...
}
interface MyResources extends CssResource {
String myIdent();
@ClassName("myIdent")
String myIdentClass();
}
- Calling
myIdent()
returns @def value "10px" - Calling
myIdentClass()
returns the obfuscated class name for .myIdent
Runtime substitution
@eval userBackground com.module.UserPreferences.getUserBackground();
div {
background: userBackground;
}
-
Provides runtime support for evaluating static methods when the stylesheet is injected. Triggered / dynamic updates could be added in the future if we allow programmatic manipulation of the style elements.
-
If the user-defined function can be statically evaluated by the compiler, then the implementation of the specific CssResource should collapse to just a string literal.
-
This allows easy support for non-structural skinning changes.
Value function
.myDiv {
offset-left: value('imageResource.getWidth', 'px');
}
- The
value()
function takes a sequence of dot-separated identifiers and an optional suffix. The identifiers are interpreted as zero-arg method invocations, using the interface passed to GWT.create() as the root namespace. By only allowing zero-arg methods, there's no need to attempt to perform type checking in the Generator. The only validation necessary is to ensure that the sequence of methods exists. There may be arbitrarily many identifiers in the chain. - The
value()
function may be combined with@def
@def SPRITE_WIDTH value('imageResource.getWidth', 'px')
.selector {
width: SPRITE_WIDTH;
}
Literal function
Some user agents make use of property values that do not conform to the CSS grammar. The literal()
function exists to allow these non-standard property values to be used.
div-with-literal {
top: literal("expression(document.compatMode==\"CSS1Compat\" ? documentElement.scrollTop : document.body.scrollTop \\ 2)");
}
Note that it is necessary to escape the backslash (\
) and double-quote ("
) characters.
Conditional CSS
/* Runtime evaluation in a static context */
@if (com.module.Foo.staticBooleanFunction()) {
... css rules ...
}
/* Compile-time evaluation */
@if <deferred-binding-property> <space-separated list of values> {
... css rules ...
}
@if user.agent safari gecko1_8 { ... }
@if locale en { ... }
/* Negation is supported */
@if !user.agent ie6 opera {
...
}
/* Chaining is also supported */
@if (true) {
} @elif (false) {
} @else {
}
- This allows for more advanced skinning / theming / browser quirk handling by allowing for structural changes in the CSS.
- The contents of an @if block can be anything that would be a top-level rule in a CSS stylesheet.
- @if blocks can be arbitrarily nested.
- What does it mean to have an @def or @eval in an @if block? Easy to make this work for property-based @if statements; would have to generate pretty gnarly runtime code to handle the expression-based @if statement. Could have block-level scoping; but this seems like a dubious use-case.
- If the function in the first form can be statically evaluated by the compiler in a permutation, there is no runtime cost. The second form will never have a runtime cost because it is evaluated during compilation.
Image Sprites
@sprite .mySpriteClass {gwt-image: "imageAccessor"; other: property;} => generates =>
.mySpriteClass {
background-image: url(gen.png);
clip: ...;
width: 27px;
height: 42px;
other: property;
}
interface MyCssResource extends CssResource {
String mySpriteClass();
}
class MyResources extends ClientBundle {
@Source("my.css")
MyCssResource css();
@Source("some.png")
ImageResource imageAccessor();
@Source("some.png")
@ImageOptions(repeatStyle=RepeatStyle.Horizontal)
ImageResource repeatingImage();
}
- @sprite is sensitive to the FooBundle in which the CSSResource is declared; a sibling ImageResource method named in the @sprite declaration will be used to compose the background sprite.
- @sprite entries will be expanded to static CSS rules, possibly with data: urls.
- The expansion is sensitive to any RepeatStyle value defined on the
ImageResource
accessor function. The appropriaterepeat-x
orrepeat-y
properties will be added to the @sprite selector. - Any CSS selector can be specified for @sprite.
- Support for IE6 isn't feasible in this format, because structural changes to the DOM are necessary to implement a "windowing" effect. Once it's possible to distinguish ie6 and ie7 in user.agent, we could revisit support for ie6. In the current implementation, the ie6 code won't render correctly, although is a purely cosmetic issue.
References to Data Resources
/* @url <constant name> <DataResource method name> */
@url myCursorUrl fancyCursorResource;
.myClass {
cursor: myCursorUrl, pointer;
}
interface MyResources extends ClientBundle {
@Source("myCursor.cur")
DataResource fancyCursorResource();
@Source("my.css")
CssResource css();
}
- The identifier will be expanded to
url('some_url')
based on the return value ofDataResource.getUrl()
.
RTL support
- CssResource supports automatic transformations of CSS code into a right-to-left variant at compile time.
- The use of the RTL variant is keyed by
com.google.gwt.i18n.client.LocaleInfo.getCurrentLocale().isRTL()
- Transformations applied:
- The
left
andright
properties are flipped.
- The
- Any properties that have values
left
orright
are flipped:clear float text-align page-break-before page-break-after
- The
background
/background-position
property is flipped. Attachments expressed in percentage points are mirrored: 40% becomes 60% margin padding border-color border-style
andborder-width
four-valued properties are flipped:1px 2px 3px 4px
becomes1px 4px 3px 2px
- Any
xyz-right
orxzy-right-abc
property is flipped toxzy-left
orxzy-left-abc
- The
direction
property on abody
selector will be flipped fromltr
tortl
; on any other selectors, thedirection
property is unchanged - When the cursor property has an
resize
value, it will be flipped:ne-resize
becomesnw-resize
@noflip
block:@noflip {
.selector {
left: 10;
}
}
-
A
background
property value that uses pixel-based offsets, such asbackground-position: 4px 10px;
will not be transformed automatically.-
The four-valued CSS3
background-position
property will be automatically flipped by the RTL supportbackground-position: left 4px top 10px;
-
For CSS2 browsers, it will be necessary to use an
@sprite
rule:
@sprite .bgImage { gwt-image: 'background-image'; position: absolute; left: 4px; top: 10px; }
-
-
ImageResources
can be automatically flipped in RTL contexts via the use of the@ImageOptions
annotation:
@Source("icon128.png")
@ImageOptions(flipRtl = true)
ImageResource logo();
Selector obfuscation
java:
class Resources {
MyCSSResource myCSSResource();
}
class MyCSSResource extends CSSResource {
Sprite mySpriteClass();
String someOtherClass();
String hookClass();
}
myWidget.addStyleName(resource.mySpriteClass());
css:
@sprite mySpriteClass mySpriteImage;
.someOtherClass {
/* ... */
}
.hookClass{} /* Empty and stripped, but left for future expansion */
-
The function just returns the CSS class name, but verifies that the CSS class exists in the stylesheet.
-
Accessing class names through the interface ensures that there can be no typos in code that consumes the
CssResource
. -
For obfuscation, we'll use a Adler32 checksum of the source css file expressed in base36 as a prefix (7 chars). The developer can override this with the
CssResource.obfuscationPrefix
deferred-binding property. -
<set-configuration-property name="CssResource.obfuscationPrefix" value="empty" />
can be used for minimal-length selector names, but this is only recommended when the GWT module has total control over the page. -
The
@external
at-rule can be used to selectively disable obfuscation for named selectors; see external and legacy scopes for additional detail.
Optimizations
Basic minification
Basic minification of the CSS input results in the minimum number of bytes required to retain the original structure of the input. In general, this means that comments, unnecessary whitespace, and empty rules are removed.
.div {
/* This is the default background color */
background: blue;
}
.empty {}
would be transformed into
.div{background:blue;}
Selector merging
Rules with identical selectors can be merged together.
.div {prop: value;}
.div {foo: bar;}
becomes
.div {prop:value;foo:bar;}
However, it is necessary that the original semantic ordering of the properties within the CSS is preserved. To ensure that all selector merges are correct, we impose the restriction that no rule can be promoted over another if the two rules define a common property. We consider border
and border-top
to be equivalent properties, however padding-left
and padding-right
are not equivalent.
Thus
.a {background: green;}
.b {border: thin solid blue;}
.a {border-top: thin solid red;}
cannot be merged because an element whose CSS class matches both .a
and .b
would be rendered differently based on the exactly order of the CSS rules.
When working with @if
statements, it is preferable to work with the form that operates on deferred-binding properties because the CSS compiler can evaluate these rules statically, before the merge optimizations. Consider the following:
.a {
background: red;
}
@if user.agent safari {
.a {
\-webkit-border-radius: 5px;
}
} @else {
.a {
background: url('picture_of_border.png');
}
}
In the safari permutation, the rule becomes .a{background:red;\-webkit-border-radius:5px;}
while in other permutations, the background
property is merged.
Property merging
Rules with identical properties can be merged together.
.a {background: blue;}
.b {background: blue;}
can be transformed into
.a,.b{background:blue;}
Promotion of rules follows the previously-established rule of not promoting a rule over other rules with common properties.
Levers and Knobs
- The configuration property
CssResource.style
may be set topretty
which will disable class-name obfuscation as well as pretty-print the CSS content. Combine this with aClientBundle.enableInlining
value offalse
to produce a CSS expression which is amenable to client-side editing. - The configuration property
CssResource.mergeEnabled
can be set tofalse
to disable modifications that re-order rules. This should be considered a temporary measure until the merge logic has been fully vetted. - To allow for client-side tweaking of the effective (i.e. permutation-specific) style rules, you can store the value of
CssResource
.getText() into a TextArea. Wire some UI action to pass the contents of the TextArea intoStyleInjector.setContents()
to overwrite the original, injected stylesheet.
Selector obfuscation details
Strict scoping
In the normal case, any class selectors that do not match String accessor functions is an error. This behavior can be disabled by adding a @NotStrict
annotation to the CSS accessor method. Enabling @NotStrict
behavior is only recommended for applications that are transitioning from external CSS files to CssResource
.
interface MyCssResource extends CssResource {
String foo();
}
interface Resources {
@Source("my.css")
@CssResource.NotStrict
MyCssResource css();
}
/* This is ok */
.foo {}
/* This would normally generate a compile error in strict mode */
.other {}
Scope
Scoping of obfuscated class names is defined by the return type of the CssResource
accessor method in the resource bundle. Each distinct return type will return a wholly separate collection of values for String accessor methods.
interface A extends CssResource {
String foo();
}
interface B extends A {
String foo();
}
interface C extends A {
String foo();
}
interface D extends C {
// Intentionally not defining foo()
}
interface Resources {
A a();
A a2();
B b();
C c();
D d();
D d2();
It will be true that a().foo() != b().foo() != c().foo() != d().foo(). However, a().foo() == a2().foo() and d().foo() == d2().foo().
Shared scopes
In the case of "stateful" CSS classes like focused
or enabled
, it is convenient to allow for certain String accessor functions to return the same value, regardless of the CssResource
type returned from the accessor method.
@Shared
interface FocusCss extends CssResource {
String focused();
String unfocused();
}
interface A extends FocusCss {
String widget();
}
interface B extends FocusCss {
String widget();
}
interface C extends B {
// Intentionally empty
}
interface Resources {
A a();
B b();
C c();
FocusCss f();
}
In this example, a().focused() == b().focused() == c().focused == f().focused(). However, a().widget() != b().widget != c.widget(), as in the previous example.
The short version is that if distinct CSS types need to share obfuscated class names, the CssResource
subtypes to which they are attached must share a common supertype that defines accessors for those names and has the @Shared
annotation.
Imported scopes
The Java type system can be somewhat ambiguous when it comes to multiple inheritance of interfaces that define methods with identical signatures, although there exist a number of cases where it is necessary to refer to multiple, unrelated CssResource
types. Consider the case of a Tree that contains Checkboxes.
@ImportedWithPrefix("tree")
interface TreeCss extends CssResource {
String widget();
}
@ImportedWithPrefix("checkbox")
interface CbCss extends CssResource {
String widget();
}
interface MyCss extends CssResource {
String other();
}
interface Resources extends ClientBundle {
@Import({TreeCss.class, CbCss.class})
MyCss css();
}
/* Now we can write a descendant selector using the prefixes defined on the CssResource types */
.tree-widget .checkbox-widget {
color: red;
}
.other {
something: else;
}
Composing a "TreeCbCss" interface would be insufficient because consumers of the TreeCss interface and CbCss interface would receive the same value from the widget method. Moreover, the use of just .widget
in the associated CSS file would also be insufficient without the use of some kind of class selector prefix. The prefix is defined on the CssResource
type (instead of on the CssResource
accessor method) In the interest of uniformity across all CSS files that import a given scope. It is a compile-time error to import multiple classes that have the same prefix or simple name.
The case of shared scopes could be handled solely with importing scopes, however this form is somewhat more verbose and relationships between unrelated scopes is less common than the use of stateful selectors.
Example: StackPanel inside a StackPanel
This is a use-case that is currently impossible to style correctly in GWT.
// Assume this interface is provided by the UI library
interface StackPanelCss extends CssResource {
String widget();
// and many more class names
}
// App code defines the following interfaces:
@ImportedWithPrefix("inner")
interface StackPanelInner extends StackPanelCss {
// Empty interface
}
interface StackPanelOuter extends StackPanelCss {
// Empty interface
}
interface Resources {
@Source("stackPanel.css")
StackPanelInner inner();
@Import(StackPanelInner.class)
@Source("stackPanel.css", "outer.css")
StackPanelOuter outer();
}
The file stackPanel.css
defines the basic structure of any given stackPanel:
.widget .title {}
.widget .content {}
/* Other stuff to make a StackPanel work */
The outer()
method can continue to use the base stackPanel.css
file, because the accessor methods defined in StackPanelCss
are mapped into the default (no-prefix) namespace. The inner StackPanel's style members are also available, but in the inner
prefix. Here's what outer.css
might contain:
.widget {color: red;}
.inner-widget {
color: blue;
font-size: smaller;
}
In many cases, newly-developed CSS will need to be combined with external or legacy CSS. The @external
at-rule can be used to suppress selector obfuscation while still allowing programmatic access to the selector name.
interface MyCssResource extends CssResource {
String obfuscated();
String legacySelectorA();
}
interface Resource extends ClientBundle {
@Source("my.css")
MyCssResource css();
}
@external legacySelectorA, legacySelectorB;
.obfuscated .legacySelectorA { .... }
.obfuscated .legacySelectorB { .... }
In the above example, the .obfuscated
class selector will be obfuscated, and the obfuscated()
method will return the replaced name. Neither of the legacy selectors will be obfuscated and the legacySelectorA()
method will return the unobfuscated value. Furthermore, because the legacySelectorB
is explicitly defined in the @external
declaration, the inaccessible class name will not trigger an error.
Automatically generating CssResource
interfaces
A utility is included in the GWT distribution which will analyze a CssResource
-compatible CSS file and create a corresponding Java interface for accessing the classnames used in the file.
java -cp gwt-dev.jar:gwt-user.jar com.google.gwt.resources.css.InterfaceGenerator \
-standalone -typeName some.package.MyCssResource -css input.css
The generated interface will be emitted to System.out
. The -standalone
option will add the necessary package
and import
statements to the output so that it can be used as part of a build process.
CssResourceCookbook
This section contains examples showing how to use CssResource.
Browser-specific css
.foo {
background: green;
}
@if user.agent ie6 {
/* Rendering fix */
.foo {
position: relative;
}
} @elif user.agent safari {
.foo {
\-webkit-border-radius: 4px;
}
} @else {
.foo {
font-size: x-large;
}
}
Obfuscated CSS class names
CssResource
will use method names as CSS class names to obfuscate at runtime.
interface MyCss extends CssResource {
String className();
}
interface MyResources extends ClientBundle {
@Source("my.css")
MyCss css();
}
All instances of a selector with .className
will be replaced with an obfuscated symbol when the CSS is compiled. To use the obfuscated name:
MyResources resources = GWT.create(MyResources.class);
Label l = new Label("Some text");
l.addStyleName(resources.css().className());
If you have class names in your css file that are not legal Java identifiers, you can use the @ClassName
annotation on the accessor method:
interface MyCss extends CssResource {
@ClassName("some-other-name")
String someOtherName();
}
Background images / Sprites
CssResource
reuses the ImageResource
bundling techniques and applies them to CSS background images. This is generally known as "spriting" and a special @sprite
rule is used in CssResource
.
interface MyResources extends ClientBundle {
@Source("image.png")
ImageResource image();
@Source("my.css");
CssResource css();
}
In my.css
, sprites are defined using the @sprite
keyword, followed by an arbitrary CSS selector, and the rule block must include a gwt-image
property. The gwt-image
property should name the ImageResource
accessor function.
@sprite .myImage {
gwt-image: 'image';
}
The elements that match the given selection will display the named image and have their heights and widths automatically set to that of the image.
Tiled images
If the ImageResource
is decorated with an @ImageOptions
annotation, the source image can be tiled along the X- or Y-axis. This allows you to use 1-pixel wide (or tall) images to define borders, while still taking advantage of the image bundling optimizations afforded by ImageResource
.
interface MyResources extends ClientBundle {
@ImageOptions(repeatStyle = RepeatStyle.Horizontal)
@Source("image.png")
ImageResource image();
}
The elements that match the @sprite
's selector will only have their height or width set, based on the direction in which the image is to be repeated.
9-boxes
In order to make the content area of a 9-box have the correct size, the height and widths of the border images must be taken into account. Instead of hard-coding the image widths into your CSS file, you can use the value()
CSS function to insert the height or width from the associated ImageResource
.
public interface Resources extends ClientBundle {
Resources INSTANCE = GWT.create(Resources.class);
@Source("bt.png")
@ImageOptions(repeatStyle = RepeatStyle.Horizontal)
ImageResource bottomBorder();
@Source("btl.png")
ImageResource bottomLeftBorder();
@Source("btr.png")
ImageResource bottomRightBorder();
@Source("StyleInjectorDemo.css")
CssResource css();
@Source("lr.png")
@ImageOptions(repeatStyle = RepeatStyle.Vertical)
ImageResource leftBorder();
@Source("rl.png")
@ImageOptions(repeatStyle = RepeatStyle.Vertical)
ImageResource rightBorder();
@Source("tb.png")
@ImageOptions(repeatStyle = RepeatStyle.Horizontal)
ImageResource topBorder();
@Source("tbl.png")
ImageResource topLeftBorder();
@Source("tbr.png")
ImageResource topRightBorder();
}
.contentArea {
padding: value('topBorder.getHeight', 'px') value('rightBorder.getWidth', 'px')
value('bottomBorder.getHeight', 'px') value('leftBorder.getWidth', 'px');
}
@sprite .contentAreaTopLeftBorder {
gwt-image: 'topLeftBorder';
position: absolute;
top:0;
left: 0;
}
@sprite .contentAreaTopBorder {
gwt-image: 'topBorder';
position: absolute;
top: 0;
left: value('topLeftBorder.getWidth', 'px');
right: value('topRightBorder.getWidth', 'px');
}
@sprite .contentAreaTopRightBorder {
gwt-image: 'topRightBorder';
position: absolute;
top:0;
right: 0;
}
@sprite .contentAreaBottomLeftBorder {
gwt-image: 'bottomLeftBorder';
position: absolute;
bottom: 0;
left: 0;
}
@sprite .contentAreaBottomBorder {
gwt-image: 'bottomBorder';
position: absolute;
bottom: 0;
left: value('bottomLeftBorder.getWidth', 'px');
right: value('bottomRightBorder.getWidth', 'px');
}
@sprite .contentAreaBottomRightBorder {
gwt-image: 'bottomRightBorder';
position: absolute;
bottom: 0;
right: 0;
}
@sprite .contentAreaLeftBorder {
gwt-image: 'leftBorder';
position: absolute;
top: 0;
left: 0;
height: 100%;
}
@sprite .contentAreaRightBorder {
gwt-image: 'rightBorder';
position: absolute;
top: 0;
right: 0;
height: 100%;
}
<div class="contentArea">
<div class="contentAreaTopLeftBorder"></div>
<div class="contentAreaTopBorder"></div>
<div class="contentAreaTopRightBorder"></div>
<div class="contentAreaBottomLeftBorder"></div>
<div class="contentAreaBottomBorder"></div>
<div class="contentAreaBottomRightBorder"></div>
<div class="contentAreaLeftBorder"></div>
<div class="contentAreaRightBorder"></div>
</div>