-
Notifications
You must be signed in to change notification settings - Fork 17
Adding custom CSS
There are several ways to add custom CSS to one's WComponents application. Under most circumstances this should be unnecessary as the theme should provide a common look and feel for the application suite.
- Some reasons for custom CSS
- Order of precedence
- Adding a cacheable stylesheet
- Adding inline CSS
- Building CSS selectors
- Related topics
Usually the theme should provide all the necessary styling, the following are some reasons why one may need to add custom CSS for a view:
- responsive design as this is not really possible in an abstract framework as it requires knowledge of the application's layout to determine where items can be compressed, re-ordered or removed;
- local changes to individual instances of a control;
- user choice.
The CSS applied in a WComponents application is attached in the HTML output stream in the following order:
- base theme CSS is added to the head element; then
- platform and browser specific theme CSS is added to the head element; then
- CSS links created using
WApplication.addCssUrl
orWApplication.addCssFile
are added to the head element; then - HTML link elements (with rel='stylesheet') added to any container component in the application in their order in the source tree are output into the head element; then
- HTML style elements added to any container component in the application in their order in the source tree are output in-situ (at the moment) wherever they are in the input stream.
Note that #5 above is under eternal review as there are competing interests of XSLT performance and a preference to have all style elements which do not have a scoped attribute set placed into the head element where they belong. If we eventually hoist unscoped style elements to the head they will be placed after the application specific link elements so will remain last in order.
The most efficient way to add CSS is usually using a cacheable stylesheet. It is not difficult to add one of these, what it should contain is another matter and is dealt with below.
New in WComponents 1.0.3
WApplication has these methods which may be used to add a link element to the page head element:
-
addCssUrl(String url)
; and -
addCssFile(String fileName)
.
Each of these methods has the same net effect: a HTML link element will be added to the head linking to the url or file resource.
// Get the WApplication from the current object'
WApplication app = WebUtilities.getAncestorOfClass(WApplication.class, this);
// Then use this WApplication to add CSS from a resource.
app.addCssFile("/path/to/resource/my_css_file.css");
// Adding from a URL is similar. Use '//'' to avoid defining the protocol.
app.addCssUrl("//example.com/path/to/static_css_file.css");
WARNING this could be considered a dirty hack and your particular theme may not support it, but it will work in the default theme.
If the stylesheet is at a fixed URL it may be able to add it to the application by building a HTML link
element. To do this one could use WText as per the example below:
// NOTE that the link element must be XML compliant so must be self closing.
private static final String CSS_LINK_CONTENT =
"<link type='text/css' rel='stylesheet'" +
" href='//example.com/my_custom_stylesheet.css' />";
private final WText cssLink = new WText(CSS_LINK_CONTENT);
// ...
// Somewhere we need to turn off text encoding and add the WText.
cssLink.setEncodeText(false);
add(cssLink);
This will write a HTML link element to the output stream. The default theme XSLT will hoist this link into the HTML head element.
The main downside of this method (and why it is a dirty hack is that one has to be aware of what the consequences will be of adding a WText with no UI artefact to any given container. If the WText is added to a WPanel layout it will reserve a cell for the WText. This may result in unexpected alignment or spacing. If, for example, the WText is added to a ColumnLayout then it will result in an empty cell in the row and column into which the WText is added. There are simple techniques for avoiding this.
Sometimes a little bit of CSS has to be added to particular renderings or user contexts. We are also working on ways to make this easier but the current method is to use WText or a WTemplate.
An example of using WText is shown below. A much more convoluted example is in the wcomponents-examples com.github.bordertech.wcomponents.examples.theme.WRowExample.addAppLevelCSSExample()
.
// ...
// in amongst the class members
// Something to hold the custom CSS
final private cssText = new WText();
// Something to style - this can be any component with a UI artefact
// but some are easier than others. It can be initialised later.
private WStyledText inverseText;
// ASSUME we have a specific CSS requirement for ease of example
private static final String CSS_BLOCK
= "{background-color:white;color:black;padding:0.25em;}";
// ...
// Make sure we turn of encoding - e.g. in the constructor.
cssText.cssText.setEncodeText(false);
// and if we do not add we do not get ...
add(cssText);
//...
// later, in preparePaintComponent
// this could be in an override of preparePaintComponent for inverseText
// if necessary.
@Override
protected void preparePaintComponent(final Request request) {
super.preparePaintComponent(request);
if (!isInitialised()) { // do this only once.
// A better Java developer would use StringBuilder or StringBuffer:
// I am a lazy JS hack!
cssText.setText("<style type='text/css' scoped='scoped'>#"
+ inverseText.getId()
+ CSS_BLOCK + "</style>");
setInitialised(true); // do this only once.
}
}
If the component being styled has has its htmlClass property set then this can be used as a selector instead of an ID. This has a slight advantage over using an ID in that the htmlClass is not changed by inclusion in a naming context whereas a components ID is. The most significant disadvantage of using getHtmlClass()
as the only CSS selector is that it may not be specific enough to change theme CSS.
The simplest selector is an ID selector as it will always be more specific than any selector in the WComponents theme.
The main problem with ID selectors is that a WComponent's ID is not necessarily fixed. Even if a static ID is set using setId(String id)
it cannot be guaranteed that the CSS selector #SOME_ID
will work as IDs are modified if the component is nested within a naming context. There are two ways around this issue. One, which is guaranteed to always work but which is less efficient is to write the CSS inline and use getId()
during preparePaintComponent
to build the selector. This is illustrated in the section on inline CSS.
An alternative which allows for cacheable style sheets depends on the full structure of all uses of a component being known. This is extremely fragile and is not recommended.
If this structure is known and the component is either never in a naming context (other than the default WApplication) or is always in a known nesting of naming context(s) where the context ID prefixes are known then the ID will not change.
Given a WButton, for example, where the button has been given the ID "fancySubmitButton" we have two possible fixed ID selectors:
- if the button is never in a naming context then the selector will be
#fancySubmitButton
; - if the button is always in a known naming context "someContext" and a nested naming context "someSubContext" then the selector will be
#someContext-someSubContext-fancySubmitButton
.
I told you it was fragile! Maybe go look at WComponents ID property and WNamingContext: it might help.
WComponents default theme uses a number of attribute selectors. Most of these selectors are tied to WAI-ARIA roles, states and properties. These WAI-ARIA based attribute selectors are as safe as any for use in custom styles.
Given a WTab, for example, it is safer to create a CSS selector using the attribute selector [role='tab']
than to rely on the XML element local name tab
to build a class selector .wc-tab
. Wherever possible WComponents will use WAI-ARIA roles, states and properties as the basis for JavaScript functionality and default styling so if something loses its role of tab
it will stop behaving like a tab as well as no longer looking like a tab.
Class selectors should only be used to add additional styles to a component. Class selectors are much easier to deal with than ID selectors but are much less specific. One may have to use some cunning to get a selector specific enough to override theme determined styles. Knowledge of the CSS selectors used in the default WComponents theme may (should) help see Theme class name cpnverntions for more information..
There are a few groups of inbuilt classes; these are discussed at some length in Theme class name conventions. It must be noted that there is no guarantee any extension theme will support any of these, even support for the first is recommended and never enforced.
WComponents which extend AbstractWComponent
have methods setHtmlClass(String htmlClass)
and addHtmlClass(String className)
to add a value to the class attribute of the root output element. More information may be found here.
It is possible to add several values the class attribute using addHtmlClass
giving one the option to create quite specific selectors but if one needs to go to these lengths it may be worthwhile revisiting one's theme.