Introduction

There are a number of different reasons why you would want to disable input for sections of a page. Most obviously is the case where information should be readonly, pending certain conditions – perhaps access roles, perhaps something as simple as enabling editing.

There are a couple of different approaches to this – you could build two different copies of the page, one with outputs and one with inputs, and switch between the two. This has the obvious disadvantage of having to maintain two versions of the page. It would be far easier if we could just wrap the page up in some sort of tag that would make them read-only.

For those in a hurry, and who are happy to include an additional library, OmniFaces provides a massAttribute tag that allows you to set an attribute on a group of components.

For those who want to learn how to do it themselves, or perhaps have more specific needs that would require being able to modify the code, read on.

Other approaches

I examined two other approaches (in my ignorance and inclination not to write new components) before settling on using a tag (as it should be done).

First off, I dabbled with a PhaseListener. The idea was that you’d just put a readOnly attribute down, anywhere in your page, and the component (and children thereof) would be set to readOnly.

This (admittedly hacky) idea was sunk on the realization that the view model has not been built up the first time the page is viewed. This would mean that we could only set the page to readOnly after it was too late. Scratch that plan.

The second approach, which has been previously declared by none other than BalusC himself to be a terrible idea, was to do the same approach using a ViewHandler. This actually worked, but given BaslusC’s disrecommendation I decided to just write a component.

Let’s make a Component

Making components in JSF2 is almost as simple as it could be. For some reason we haven’t escaped XML entirely, but it’s kept to a minimal.

First off, let’s put in the basic skeleton of a component. We need to extend UIComponentBase and annotate our component with a name that we’ll tie up to a tag in our XML.

Empty component @FacesComponent(value = "knw.ReadOnlyComponent") public class ReadOnlyComponent extends UIComponentBase { @Override public String getFamily() { return "za.co.knowles"; } @Override public void encodeBegin(FacesContext context) throws IOException { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @FacesComponent ( value = "knw.ReadOnlyComponent" ) public class ReadOnlyComponent extends UIComponentBase { @Override public String getFamily ( ) { return "za.co.knowles" ; } @Override public void encodeBegin ( FacesContext context ) throws IOException { } }

That’s easy enough – we also need to register this new tag so that we can actually use it in a page. This is so quick that you’d almost expect it to do it itself. Oh well.

Facelet Component TagLib XML <facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" version="2.0"> <namespace>http://za.co.knowles/knw-taglib</namespace> <tag> <tag-name>readOnly</tag-name> <component> <component-type>knw.ReadOnlyComponent</component-type> </component> </tag> </facelet-taglib> 1 2 3 4 5 6 7 8 9 10 11 12 <facelet-taglib xmlns = "http://java.sun.com/xml/ns/javaee" xmlns : xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi : schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" version = "2.0" > <namespace> http://za.co.knowles/knw-taglib </namespace> <tag> <tag-name> readOnly </tag-name> <component> <component-type> knw.ReadOnlyComponent </component-type> </component> </tag> </facelet-taglib>

That’s it for the setup – you can put this component into your project if you like. There’d be little point at the moment though, so let’s get some logic into it.

First off, we might not always want to set components to be read only – let’s have an attribute readOnly that we’ll pull from our component. We can do this in the encodeBegin method quite easily:

Boolean readOnly = (Boolean) getAttributes().get("readOnly"); if (readOnly == null) { readOnly = false; } 1 2 3 4 5 Boolean readOnly = ( Boolean ) getAttributes ( ) . get ( "readOnly" ) ; if ( readOnly == null ) { readOnly = false ; }

We attempt to get the attribute from the component, and if it does not exist we default to false. Next we’ll set all the components to read-only.

private void processViewTree(UIComponent component, boolean setTo) { for (UIComponent child : component.getChildren()) { if (UIInput.class.isAssignableFrom(child.getClass())) { UIInput inputText = (UIInput) child; if (!callMethod(inputText, "setReadonly", setTo)) { // second attempt. callMethod(inputText, "setReadOnly", setTo); } } processViewTree(child, setTo); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void processViewTree ( UIComponent component , boolean setTo ) { for ( UIComponent child : component . getChildren ( ) ) { if ( UIInput . class . isAssignableFrom ( child . getClass ( ) ) ) { UIInput inputText = ( UIInput ) child ; if ( ! callMethod ( inputText , "setReadonly" , setTo ) ) { // second attempt. callMethod ( inputText , "setReadOnly" , setTo ) ; } } processViewTree ( child , setTo ) ; } }

We do this by looping through all of the children of the current component. For each one, we check if it is assignable from UIInput – that is, it needs to be set to read-only. Annoyingly, there is no setReadOnly method on UIInput, it exists on children components and occasionally has different spellings. We’re just going to try call setReadonly and setReadOnly using reflection – if you’re using this component and run into issues of components not being set, check if it has a different spelling.

Finally, we recurse on that child component, so that all of its children are set to read-only.

private boolean callMethod(UIInput inputText, String name, boolean setTo) { try { Method method = inputText.getClass().getMethod(name, boolean.class); method.invoke(inputText, setTo); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { return false; } return true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 private boolean callMethod ( UIInput inputText , String name , boolean setTo ) { try { Method method = inputText . getClass ( ) . getMethod ( name , boolean . class ) ; method . invoke ( inputText , setTo ) ; } catch ( NoSuchMethodException | InvocationTargetException | IllegalAccessException e ) { return false ; } return true ; }

The last piece of the puzzle is retrieving and calling the method via reflection. This is fairly standard, apart from catching exceptions that it might throw and returning false. This is so that we can pick up one of the methods not existing and try the alternate spelling.

Putting it all together, it looks like this.