Many web applications offer users the option to change the appearance of user interface. One of the easiest ways to implement this is by offering different page color schemes by selecting different css style sheets. This article describes how to implement themes in JSF using CDI backing beans that the user can select as their default theme.

Download project source
Each style sheet implements a different color scheme and when they user picks a theme, you simply have to provide the style sheet assigned to that theme. The application will be based on an existing archetype, so we’ll start by creating a new Maven application using the jee6-sandbox-demo-archetype.

As it is, the application has a stylesheet called screen.css which includes color information for the pages. We want to add another style sheet that overrides those color choices for the elements that we want to change per theme.

Of course, we could change much more css than the colors in the theme style sheet, we could change the layout and sizing information also, but the tradeoff is that the more content we put into the theme stylesheet, the more content we have to duplicate in each theme, and the more we have to change each time there is a minor change. We could resolve this problem by giving the user two different theming option, the first would select the layout stylesheet and the other would select the color stylesheet.

  1. In the package explorer locate the bean package and right click on the package folder.
  2. Select New->Class and in the class dialog, enter StyleBean as the name of the class

    This bean will hold the value of the current theme name and return the style sheet name to our view. It will also hold the list of available style sheets.
  3. We’ll start by annotating the bean with the @Named annotation so we can reference it from our JSF view pages and make the bean @SessionScoped so it will remember the settings between each request.
  4. Since our bean is session scoped, it needs to be passivation capable which means adding the Serializable interface to the implements clause.
  5. We add fields for the currently selected theme, and also a theme map which will contain the name of the themes and the stylesheet associated with it. Getters and setters for the theme have been omitted.
  6. import javax.enterprise.context.ApplicationScoped;
    import javax.enterprise.context.SessionScoped;
    import javax.enterprise.inject.Produces;
    import javax.inject.Named;
    
    @Named("styleBean")
    @SessionScoped
    public class StyleBean implements Serializable {
    
    	private String theme;
    
    	private Map<String, String> themeMap;
    	
    	public StyleBean() {
    		themeMap = new LinkedHashMap<String, String>();
    		// read this list from the db or something
    		themeMap.put("Default", "default.css");
    		themeMap.put("Blue", "blue.css");
    		themeMap.put("Green", "green.css");	
    	}
    
    }
    
  7. Because JSF cannot iterate over a Map or Set we need to convert the list of theme names into a List. We do this with another method that we annotate with the @Produces method to indicate that this produces values. We give it the name themes and make it application scoped so this will be produced only once per application.
    
    	@Produces
    	@Named("themes")
    	@ApplicationScoped
    	public List<String> getThemes() {
    		return new ArrayList<String>(themeMap.keySet());
    	}
    
    
  8. We have one more method to implement and that is the method that returns the name of the CSS stylesheet for the selected theme. We do this by using our theme Map as a lookup to get the filename
  9. 
    	public String getThemeCss() {
    		return themeMap.get(getTheme());
    	}
    
    

    If you get an error at this point you forgot to put in the getter/setters for the theme field.

Now we have put all our code together in one bean, given that bean a name so it can be referenced from JSF, and implemented the list of themes and converting themes to a CSS file name. It’s time to implement our options page that lists the themes available and lets the user select one.

  1. Create a new JSF page called options.xhtml and paste in the following code :
    <?xml version="1.0" encoding="UTF-8"?>
    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
    	xmlns:ui="http://java.sun.com/jsf/facelets"
    	xmlns:f="http://java.sun.com/jsf/core"
    	xmlns:h="http://java.sun.com/jsf/html"
    	xmlns:fc="http://java.sun.com/jsf/composite/fluttercode/component"	
    	template="/WEB-INF/templates/template.xhtml">
    	<ui:define name="content">
    		<h:form>
    			<fc:panel caption="Visual Options">
    				<fc:property caption="Theme">
    					<h:selectOneListbox value="#{styleBean.theme}" style="width : 180px;height : 180px">
    						<f:selectItems value="#{styleBean.themes}" />						
    					</h:selectOneListbox>
    				</fc:property>
    				<h:commandButton value="Update"/>
    			</fc:panel>
    			
    
    		</h:form>
    
    	</ui:define>
    </ui:composition>
    
    

    Most of this code is boilerplate so I’ve highlighted the relevant parts. The list box that is wrapped by the property component, takes the list of themes from our themes producer and is bound to the theme property of our styleBean. When the user selects an item and clicks the Update button the theme property is set to the name of the selected theme.Since this is stateful, the new value holds across page requests so next time the page is rendered we can look at the themeCss property to get the file name of the css file for the selected theme.

  2. To render the css style sheet we need to add it to the style sheets in the header. Open up the template.xhtml file and locate the line with <h:outputStylesheet name="css/screen.css" />. Underneath it, add a new line to include our user selected style sheet.
    <h:outputStylesheet name="css/#{styleBean.themeCss}" />
    
  3. Now the only thing left is to create the additional stylesheets for our themes. In the src/main/webapp/resources/css/ folder create a new stylesheet called default.css but leave it blank.
  4. Create a new stylesheet called blue.css with the following content :
    
    body {
    	background-color: #E0E0FF;
    	color: #102040;
    }
    
    #header {
    	background: #204080;
    	color: #f0f0fF;
    }
    
    .panel {
    	border: 1px solid #D0D0F0;
    	background: #f0f0ff;
    }
    
    .panel h1 {
    	background: #e0e0f0;
    	border-bottom: solid 1px #D0D0F0;
    	color: #102040;
    }
    
    .odd {
    	background: #f0f0fF;
    }
    
    .even {
    	background: #f7f7ff;
    }
    
    .dataTable th {
    	background: #204080;
    	color: #f0f0ff;
    }
    
    
  5. Add one more stylesheet called green.css with the following content :
    body {
    	background-color: #E0FFE0;
    	color: #104020;
    }
    
    #header {
    	background: #208040;
    	color: #f0fFF0;
    }
    
    .panel {
    	border: 1px solid #D0F0D0;
    	background: #f0fff0;
    }
    
    .panel h1 {
    	background: #e0f0e0;
    	border-bottom: solid 1px #D0F0D0;
    	color: #104020;
    }
    
    .odd {
    	background: #f0fFf0;
    }
    
    .even {
    	background: #f7fFf7;
    }
    
    .dataTable th {
    	background: #208040;
    	color: #f0fff0;
    }
    
  6. If you deploy the application now and go to the http://localhost:8080/jsfthemes/options.jsf you can pick a different theme,click update and the application will start rendering pages with the new themes.

    Extending the Implementation

    There are a number of ways we can improve this implementation, especially since we are keeping the list of styles and the selection information in a backing bean in the session. Chances are that the session already has some kind of User entity, and it would be a good idea to store the selected theme, or better yet, the name of the css file as part of the user profile. The list of themes is only needed when the user is on the options page and is only used to provide the list of themes and to convert from the theme name to the css file. It would be easy to re-implement these as request scoped elements once the state is moved to the user entity.