public final class Genesis extends Object
All documentation is kept in Javadocs because it guarantees consistency between what's on the web and what's in the source code. Also, it makes possible to access documentation straight from the IDE even if you work offline.
Modularity
Background image generation
Variables and expressions
Type safety
Theming
Sprites
CSS3 and cross-browser compatibility
Compiling modules
When a generic widget is intended to be used in different projects, it usually comes with a piece of CSS that the user is expected to link to, or copy into the main style sheet (example). This method has some disadvantages:
Genesis offers a solution to these issues, by introducing the concept of CSS
module. A module is a Java object with a
CssModule.configure(CssBuilder)
method that builds a part of the
final CSS code. Example:
static final CssModule MY_BUTTON_CSS_MODULE = new CssModule() {
@Override public void configure(CssBuilder out) {
out.addRule(".myButton", Properties.builder()
.setWidthPx(80)
.setHeightPx(20)
.setBackground(Color.ALUMINIUM_3)
.set(CursorValue.POINTER)
.setBorderRadiusPx(2));
out.addRule(".myButton:hover", Properties.builder()
.setBackground(Color.ALUMINIUM_2)
.set(FontWeightValue.BOLD));
out.addRule("div.myBox", Properties.builder()
.set(FloatValue.LEFT)
.setWidthPct(50));
}
};
This generates the CSS:
.myButton {
width: 80px;
height: 20px;
background: #babdb6;
cursor: pointer:
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
.myButton:hover {
color: #d3d7cf;
font-weight: bold;
}
div.myBox {
float: left;
width: 50%;
}
Widget developers expose a CSS module instead of a piece of CSS. Users only
have to add a dependency to this module to have the piece of CSS inserted
into the main style sheet. Let's see how this solves the issues described
above:
Genesis comes will a small library that allows you to quickly generate background images without using Photoshop or GIMP. See the examples below.
// Linear gradient
Color from = Color.of(114, 159, 207);
Color to = Color.of(52, 101, 164);
Image im = Images.canvas(40, 5, from)
.fill(to, Gradients.linear(Direction.RIGHT));
Produces this image - // Square button
Image im = Images.canvas(25, 25, Color.ALUMINIUM_1)
.fill(Color.ALUMINIUM_2, Gradients.circular(0.5, 1))
.mask(Shapers.roundedBox(2), new Border(1, Color.ALUMINIUM_3));
Produces this image - // Diagonal stripes
Image im = Images.canvas(40, 40, Color.CHOCOLATE_1)
.mask(Shapes.diagonalStripes(20, 20, 0.5, true),
new Border(1, Color.CHOCOLATE_3))
.frame(Color.CHOCOLATE_2);
Produces this image - // Comic book balloon tail
Image im = Images.canvas(40, 40, Color.PLUM_1)
.mask(Shapers.tail(10, 30, 20, 20).pad(0, 0, 4, 0),
new Border(1, Color.PLUM_2))
.frame(Color.PLUM_3.mix(Color.WHITE, 0.5));
Produces this image - This library can save you a lot of time because it is often required to re-generate background images during the web design process, with slightly different dimensions or colors. Going through these iterations with Photoshop can be very laborious. Vector graphics editors such as Inkscape make it easier but it takes a lot of time and motivation to master them.
Using generated images in the CSS is very simple:
@Override public void configure(CssBuilder out) {
int width = 25;
int height = 25;
Image im = Images.canvas(width, height, Color.ALUMINIUM_1)
.fill(Color.ALUMINIUM_2, Gradients.circular(0.5, 1))
.mask(Shapers.roundedBox(2), new Border(1, Color.ALUMINIUM_3));
out.addRule(".squareButton", Properties.builder()
.setWidthPx(width)
.setHeightPx(height)
.setBackground(BackgroundLayer.of(im));
}
CSS does not support variables (or named constants) and expressions, although this is a feature many developers request. The justification is absolutely understandable but the fact remains that in many situations it would make the life of CSS writers better. Several languages extending CSS have been created to solve this issue: LESS, ZUSS, Saas.
With Genesis you write Java code, so you can take advantage of all the features of the Java language. Below are examples of things you can easily do, and the implementation:
// Constants
static final int LEFT_WIDTH = 120;
static final int MIDDLE_WIDTH = 10;
static final int RIGHT_WIDTH = 220;
static final Color LEFT_BG_COLOR = Color.BLUE;
static final Color RIGHT_BG_COLOR = Color.SILVER;
static final Color ICON_BG_COLOR = Color.ALUMINIUM_1;
@Override public void configure(CssBuilder out) {
out.addRule(".left", Properties.builder()
.set(FloatValue.LEFT)
.setWidthPx(LEFT_WIDTH)
.setBackground(LEFT_BG_COLOR));
out.addRule(".right", Properties.builder()
.set(FloatValue.LEFT)
.setWidthPx(RIGHT_WIDTH)
.setBackground(RIGHT_BG_COLOR));
// Separates left and right
out.addRule(".middle", Properties.builder()
.setWidthPx(MIDDLE_WIDTH));
Image grad = Images.canvas(MIDDLE_WIDTH, 4, LEFT_BG_COLOR)
.fill(RIGHT_BG_COLOR, Gradients.linear(Direction.RIGHT));
out.addRule(".container", Properties.builder()
.setWidthPx(LEFT_WIDTH + MIDDLE_WIDTH + RIGHT_WIDTH)
.setBackground(BackgroundLayer.of(grad)));
Image icon = createIconImage();
out.addRule(".icon", Properties.builder()
.setWidthPx(icon.width())
.setHeightPx(icon.height())
.setBackground(BackgroundLayer.of(icon), ICON_BG_COLOR)
.setBorder(1, ICON_BG_COLOR.mix(Color.BLACK, 0.4),
BorderStyleValue.SOLID));
}
One advantage of generating CSS code from Java is that Java is a compiled language with static typing. It removes the risk of making typos in the CSS, setting properties that don't exist, or setting properties to illegal values, as those errors will be detected at compile-time. For example:
// COMPILE-TIME ERROR
// Should be FontStyle and not TextStyle
Properties.builder().set(TextStyle.ITALIC);
// COMPILE-TIME ERROR
// An int is required
Properties.builder().setBorderWidthPx("solid");
// RUNTIME ERROR
// Padding can't be negative
// Not as good as a compile-time error, but better than no error
Properties.builder().setPaddingPx(-2);
With Genesis, it is the widget developer who defines the high-level style
properties the user can set, and not the user who hacks into the CSS to
replace some targeted CSS property values.
High-level properties are parameters, of any type, that are passed to a
module through setters or through a class constructor.
They will be converted to CSS rules in the
CssModule.configure(CssBuilder)
method although the user does not
have to know how. For example:
// CSS module for an online code editor with syntactic highlighting
public class CodeEditorCssModule implements CssModule {
// High-level style properties
private final Map<Section, Color> sectionToColor;
private Color backgroundColor;
public CodeEditorCssModule() {
sectionToColor = Maps.newEnumMap(Section.class);
// Default style
sectionToColor.put(Section.COMMENT, Color.RED);
sectionToColor.put(Section.KEYWORD, Color.BLUE);
sectionToColor.put(Section.NUMBER, Color.GREEN);
sectionToColor.put(Section.STRING_LITERAL, Color.GREEN);
backgroundColor = Color.WHITE;
}
public void setSectionColor(Section section, Color color) {
sectionToColor.put(section, color);
}
public void setBackgroundColor(BackgroundColor backgroundColor) {
this.backgroundColor = backgroundColor;
}
@Override public void configure(CssBuilder out) {
out.put(".codeEditor", Properties.builder()
.setBackground(backgroundColor));
for (Section section : Section.values()) {
String selector = "." + section.className;
Color color = sectionToColor.get(section);
out.put(selector, Properties.builder()
.setColor(color));
// Invert the background color and the text color when the
// 'invert' class name is on
out.put(selector + ".invert", Properties.builder()
.setBackground(color)
.setColor(backgroundColor));
}
}
public enum Section {
COMMENT("comment"),
KEYWORD("keyword"),
NUMBER("number"),
STRING_LITERAL("string-literal");
final String className;
Section(String className) {
this.className = className;
}
}
}
See CssModule
for how to enable multiple themes for different
instances of a widget appearing in a page.
To reduce the number of requests the browser makes to the server, a technique consists of combining numerous small images or icons into a larger image called a sprite. The background-position CSS property is used to select the part of the composite image to display at different points in the page. If a page has ten 1 kB images, they can be combined into one 10 kB image, downloaded with a single HTTP request, and then positioned with CSS. Reducing the number of HTTP requests can make a Web page load much faster.
With Genesis, the generation of sprites is automatic and transparent.
The only thing you have to do is call BackgroundLayer.fill()
. This
method informs Genesis that the background-image entirely fills the
background painting area, and thus, that it can be put into a sprite. It is a
required condition because if the image does not fill the painting area but
is put into a sprite, other images from the sprite may appear unexpectedly
next to it.
Spriting example:
@Override public void configure(CssBuilder out) throws IOException {
// Load icons from Java resources
Image cutIcon = Images.load(getClass().getResource("cut-32x32.png"));
Image copyIcon = Images.load(getClass().getResource("copy-32x32.png"));
Image pasteIcon = Images.load(getClass().getResource("paste-32x32.png"));
out.addRule(".cutButton", Properties.builder()
.setBackground(BackgroundLayer.of(cutIcon).fill()));
out.addRule(".copyButton", Properties.builder()
.setBackground(BackgroundLayer.of(copyIcon).fill()));
out.addRule(".pasteButton", Properties.builder()
.setBackground(BackgroundLayer.of(pasteIcon).fill()));
}
The generated CSS is:
.cutButton {
background: url('w7rdyh4tmi.png') no-repeat;
}
.copyButton {
background: url('w7rdyh4tmi.png') no-repeat -32px 0;
}
.pasteButton{
background: url('w7rdyh4tmi.png') no-repeat -64px 0;
}
And the unique image file referred to in it is - CSS3 is a standard in construction, but Genesis has setters for the most commonly-accepted CSS3 properties. This includes animations, transitions, colons, border radiuses, border shadows and geometric transformations (rotate, scale, etc.). Note that it is always possible to set a property with the non-typesafe setter(String, String), so you will never be blocked because Genesis does not support a property yet.
As of 2013, many CSS3 properties are supported by main browsers through what
is called vendor-specific properties. Each browser that is concerned requires
a particular prefix to be inserted before the name of the property. Genesis
takes care of setting vendor-specific properties for you, so you don't have
to care about it. For example,
setTransform(TranformFunction.rotateDeg(7))
translates into:
transform:rotate(7deg);
-ms-transform:rotate(7deg);
-moz-transform:rotate(7deg);
-webkit-transform:rotate(7deg);
-o-transform:rotate(7deg);
Besides CSS3 properties, Genesis supports CSS3 grammar, the at-rules keyframes and font-face, CSS3 selectors, and media features. See a complete example below:
static final FontFace MY_GENTIUM = FontFace.forLocalSource("myGentium")
.sourceAdded("../Gentium.ttf")
.withFamilyName("myGentium");
static final Keyframes KEYFRAMES = Keyframes.from("top: 0;left; 0")
.through(30, "top: 50%")
.through(68, Properties.builder().setLeftPct(50))
.to("top: 100%;left:100%");
@Override public void configure(CssBuilder out) {
out.use(MY_GENTIUM);
out.use(KEYFRAMES);
out.addRule("#header", Properties.builder()
.setTransform(TransformFunction.rotateDeg(7))
.setBorderRadiusPx(5)
.setAnimation(Animation.on(KEYFRAMES.name())
.durationSeconds(12)
.timingFunction(TimingFunction.EASE_OUT)));
MediaQuery screenQuery = MediaQuery.SCREEN;
MediaQuery tvQuery = MediaQuery.TV
.and(MediaFeature.progressiveScan())
.and(MediaFeature.minWidth(1024, LengthUnit.PX));
out.addRule(
":not(#footer)",
Properties.builder().setFontFamily("my-gentium"),
screenQuery.or(tvQuery.not()));
}
Use the Genesis class to compile a set of CSS modules into a style sheet. There are two possible ways to do so. You can create an instance of Genesis with a builder, for example during the construction of the server or a servlet:
Genesis genesis = Genesis.builder()
.install(MY_CSS_MODULE)
.install(new DatePickerCssModule())
.setImageFolderUri("/media")
.build();
You can also execute the main method of Genesis from the command line, as an
offline process. The parameters are:
Genesis \
--entry_class my.java.package.Foobar \
--image_folder /home/john/mysite/img \
--image_folder_url img \
--out /home/john/mysite/style.css
Modifier and Type | Class and Description |
---|---|
static class |
Genesis.Builder
A builder for creating Genesis instances.
|
Modifier and Type | Method and Description |
---|---|
static Genesis.Builder |
builder()
Returns a new builder for creating Genesis instances.
|
String |
getCss()
Returns the generated CSS as a string.
|
String |
getCssPart(CssModule... installedModules)
Returns the part of the CSS that gets generated from
the given installed modules and their dependencies.
|
String |
getCssPart(Iterable<? extends CssModule> installedModules)
Returns the part of the CSS that gets generated from
the given installed modules and their dependencies.
|
static void |
main(String[] args) |
void |
writeCssFile(File out)
Writes the CSS to the given file.
|
void |
writeImages(File folder)
Writes all background images used in the CSS to the given folder.
|
public static void main(String[] args) throws ClassNotFoundException, IOException
ClassNotFoundException
IOException
public void writeImages(File folder) throws IOException
It is safe for several threads and even processes to write images to the same folder at the same time. If the images to write are already present in the folder, this method figures it out and returns very fast. Feel free to call this method during the initialization of a servlet.
IllegalArgumentException
- if the given file is not a folderIOException
public void writeCssFile(File out) throws IOException
IllegalArgumentException
- if there is a conflict between rules
defined in two installed modules; see CssModule
IOException
public String getCss()
A servlet can call this method and insert the CSS into a <style> element. Compared to linking to an external style sheet, this is one less RPC. Note that if the document is a XHTML, the CSS needs to be escaped.
IllegalArgumentException
- if there is a conflict between rules
defined in two installed modules; see CssModule
public String getCssPart(Iterable<? extends CssModule> installedModules)
A servlet can call this method and insert the CSS into a <style> element. Compared to linking to an external style sheet, this is one less RPC. Note that if the document is a XHTML, the CSS needs to be escaped.
IllegalArgumentException
- if one of the module has not been
installedpublic String getCssPart(CssModule... installedModules)
A servlet can call this method and insert the CSS into a <style> element. Compared to linking to an external style sheet, this is one less RPC. Note that if the document is a XHTML, the CSS needs to be escaped.
IllegalArgumentException
- if one of the module has not been
installedpublic static Genesis.Builder builder()
Copyright © 2013. All Rights Reserved.