I use the primefaces extensions documentViewer to show pdfs. The files are streamed, this means the backing bean provides an instance of org.primefaces.model.DefaultStreamedContent. If a big pdf is shown, it took some time until the viewer is showing something. In the showcase of the PF extensions website, the documentViewer shows a loading bar under the viewers button bar. Unfortunately this is not shown in my case. In the showcase there is no DefaultStreamContent used. It is url to file. Maybe I have to set the total size for the streamed content? Is that possible with DefaultStreamedContent?
Maybe I have to set the total size for the streamed content
Yes! The client can only calculate the progress if the response content length is known beforehand. In JSF, you can set the response content length via ExternalContext#setResponseContentLength().
Provided that you want to return a StreamedContent, here's how you could do it (based on a.o. Display dynamic image from database with p:graphicImage and StreamedContent):
#ManagedBean
#ApplicationScoped
public class PdfManager {
#EJB
private PdfService service;
public StreamedContent getContent() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
return new DefaultStreamedContent();
} else {
Pdf pdf = service.generateItSomehow();
context.getExternalContext().setResponseContentLength(pdf.getLength());
return new DefaultStreamedContent(pdf.getInputStream());
}
}
}
Related
I'm trying to display image bytes which is saved in database as a StreamedContent in the <p:graphicImage> as follows:
<p:graphicImage value="#{item.imageF}" width="50" id="grpImage" height="80"/>
private StreamedContent content; // getter and setter
public StreamedContent getImageF() {
if (student.getImage() != null) {
InputStream is = new ByteArrayInputStream(student.getImage());
System.out.println("Byte :"+student.getImage());
content = new DefaultStreamedContent(is, "", student.getStuID());
System.out.println("ddd ------------------------------- " + content);
return content;
}
return content;
}
This returns a blank image. How is this caused and how can I solve it?
The stdout prints the following:
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#b0887b
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1d06a92
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#39a60
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#8c3daa
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1dbe05b
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#66a266
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1293976
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#17b7399
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1e245a5
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#4a7153
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1561bfd
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#47a8c2
The <p:graphicImage> requires a special getter method. It will namely be invoked twice per generated image, each in a completely different HTTP request.
The first HTTP request, which has requested the HTML result of a JSF page, will invoke the getter for the first time in order to generate the HTML <img> element with the right unique and auto-generated URL in the src attribute which contains information about which bean and getter exactly should be invoked whenever the webbrowser is about to request the image. Note that the getter does at this moment not need to return the image's contents. It would not be used in any way as that's not how HTML works (images are not "inlined" in HTML output, but they are instead requested separately).
Once the webbrowser retrieves the HTML result as HTTP response, it will parse the HTML source in order to present the result visually to the enduser. Once the webbrowser encounters an <img> element during parsing the HTML source, then it will send a brand new HTTP request on the URL as specified in its src attribute in order to download the content of that image and embed it in the visual presentation. This will invoke the getter method for the second time which in turn should return the actual image content.
In your particular case PrimeFaces was apparently either unable to identify and invoke the getter in order to retrieve the actual image content, or the getter didn't return the expected image content. The usage of #{item} variable name and the lot of calls in the log suggests that you were using it in an <ui:repeat> or a <h:dataTable>. Most likely the backing bean is request scoped and the datamodel isn't properly preserved during the request for the image and JSF won't be able to invoke the getter during the right iteration round. A view scoped bean would also not work as the JSF view state is nowhere available when the browser actually requests the image.
To solve this problem, your best bet is to rewrite the getter method as such so that it can be invoked on a per-request basis wherein you pass the unique image identifier as a <f:param> instead of relying on some backing bean properties which may go "out of sync" during subsequent HTTP requests. It would make completely sense to use a separate application scoped managed bean for this which doesn't have any state. Moreover, an InputStream can be read only once, not multiple times.
In other words: never declare StreamedContent nor any InputStream or even UploadedFile as a bean property; only create it brand-new in the getter of a stateless #ApplicationScoped bean when the webbrowser actually requests the image content.
E.g.
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<p:graphicImage value="#{studentImages.image}">
<f:param name="studentId" value="#{student.id}" />
</p:graphicImage>
</p:column>
</p:dataTable>
Where the StudentImages backing bean can look like this:
#Named // Or #ManagedBean
#ApplicationScoped
public class StudentImages {
#EJB
private StudentService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String studentId = context.getExternalContext().getRequestParameterMap().get("studentId");
Student student = studentService.find(Long.valueOf(studentId));
return new DefaultStreamedContent(new ByteArrayInputStream(student.getImage()));
}
}
}
Please note that this is a very special case wherein performing business logic in a getter method is completely legit, considering how the <p:graphicImage> works under the covers. Invoking business logic in getters is namely usually frowned upon, see also Why JSF calls getters multiple times. Don't use this special case as excuse for other standard (non-special) cases. Please also note that you can't make use of EL 2.2 feature of passing method arguments like so #{studentImages.image(student.id)} because this argument won't end up in the image URL. Thus you really need to pass them as <f:param>.
If you happen to use OmniFaces 2.0 or newer, then consider using its <o:graphicImage> instead which can be used more intuitively, with an application scoped getter method directly delegating to the service method and supporting EL 2.2 method arguments.
Thus so:
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<o:graphicImage value="#{studentImages.getImage(student.id)}" />
</p:column>
</p:dataTable>
With
#Named // Or #ManagedBean
#ApplicationScoped
public class StudentImages {
#EJB
private StudentService service;
public byte[] getImage(Long studentId) {
return studentService.find(studentId).getImage();
}
}
See also the blog on the subject.
Try including a mime type. In your posted example, you have it as "". The blank image may be because it doesn't recognize the stream as a image file since you made that field an empty string. So add a mime type of image/png or image/jpg and see if that works:
String mimeType = "image/jpg";
StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);
There's a couple possibilities here (and please post the entire class if this isn't it).
1) You're not initializing the image properly
2) Your stream is empty so you're getting nothing
I'm assuming student.getImage() has a signature of byte[] so first make sure that that data is actually intact and represents an image. Secondly--you're not specifying a content-type which should be "image/jpg" or whatever you're using.
Here's some boilerplate code to check it with, I'm using Primefaces 2 for this.
/** 'test' package with 'test/test.png' on the path */
#RequestScoped
#ManagedBean(name="imageBean")
public class ImageBean
{
private DefaultStreamedContent content;
public StreamedContent getContent()
{
if(content == null)
{
/* use your database call here */
BufferedInputStream in = new BufferedInputStream(ImageBean.class.getClassLoader().getResourceAsStream("test/test.png"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
int val = -1;
/* this is a simple test method to double check values from the stream */
try
{
while((val = in.read()) != -1)
out.write(val);
}
catch(IOException e)
{
e.printStackTrace();
}
byte[] bytes = out.toByteArray();
System.out.println("Bytes -> " + bytes.length);
content = new DefaultStreamedContent(new ByteArrayInputStream(bytes), "image/png", "test.png");
}
return content;
}
}
and some markup...
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui"
>
<h:head>
</h:head>
<h:body>
<p:graphicImage value="#{imageBean.content}" />
</h:body>
</html>
If that code works then you're set up properly. Despite the fact it is garbage code for the streams (don't use it in production) it should give you a point to troubleshoot from. My guess is that you might have something happening in your JPA or other Database framework where you're byte[] is empty or it is formatted wrong. Alternatively you could just have a content-type problem.
Lastly, I would clone the data from the bean so that student.getImage() would only be copied into a new array and then used. This way if you have something unknown going on (something else moving the object or changing the byte[] you're not messing with your streams.
Do something like:
byte[] data = new byte[student.getImage().length]
for(int i = 0; i < data.length; i++)
data[i] = student.getImage()[i];
so that your bean has a copy (or Arrays.copy()--whatever floats your boat). I can't stress enough how something simple like this/content type is usually what's wrong. Good luck with it.
The answer from BalusC is (as usual) the correct one.
But keep one thing (as already stated by him) in mind. The final request is done from the browser to get the URL from the constructed <img> tag. This is not done in a 'jsf context'.
So if you try to e.g. access the phaseId (logging or whatever reason)
context.getCurrentPhaseId().getName()
This will result in a NullPointerException and the somehow misleading error message you will get is:
org.primefaces.application.resource.StreamedContentHandler () - Error in streaming dynamic resource. Error reading 'image' on type a.b.SomeBean
It took me quite some time to figure out what was the problem.
How can I change favicon of my pages in Vaadin ? I would like to change favicon of my pages but I have no idea where is the place to change it ? Has somebody experience on it ?
First, create a theme directory: /WebContent/VAADIN/themes/mynewtheme
Then, put your custom favicon.ico in this directory. You also need to set theme property in your application :
public class MyNewApplication extends Application {
#Override
public void init() {
...
...
setTheme("mynewtheme");
}
}
Here is a more detailed version of the similar Answer posted by Greg Ballot. My Answer here relates to Vaadin 7, current as of 7.5.3.
Custom Theme
In Vaadin 7.5, you can drop your favicon graphics image file into your own custom theme. If using the Vaadin plugin for various IDEs (NetBeans, Eclipse) or the Maven archetypes, a custom theme named mytheme should have already been created for you. Drop your image file into that mytheme folder.
The main part of your Vaadin 7 app, your subclass of UI, must specify that it uses your custom theme. Again, if using the IDE plugins and/or Maven archetype, this should have already been configured for you. The easiest way is an Java Annotation on the UI subclass.
#Theme ( "mytheme" ) // Tell Vaadin to apply your custom theme, usually a subclass of the Valo or Reindeer theme.
#Title ( "PowerWrangler" ) // Statically specify the title to appear in web browser window/tab.
#SuppressWarnings ( "serial" ) // If not serializing such as "sticky sessions" and such, disable compiler warnings about serialization.
#Push ( PushMode.AUTOMATIC ) // If using Push technology.
public class MyVaadinUI extends UI
{
…
Favicon Usage/Behavior Not Standard
Remember that favicon behavior is not standardized. Favicons developed haphazardly, mostly out of a sense of fun. The exact behavior depends on the particular browser and particular server. Other than the particular folder location, none of this is special to Vaadin.
Image File Formats
Originally the ICO file format was used exclusively. Since then most browsers have evolved to accept any of several formats including JPEG, TIFF, and PNG.
Image Size/Resolution
Originally favicons were intended to be very small bitmap icons. Some browsers have made various uses of the favicon in situations where you may want to provide a higher-resolution image. But remember that smaller files load faster without keeping your users waiting.
Favicon File Name
Some browsers or servers may handle other file names or name extensions, but I've found it easiest to name my file exactly favicon.ico -- even if using a different format! I usually use a PNG file but name it with the .ico extension. While I cannot guarantee this practice works one every server and browser, I’ve not encountered any problem.
Existing Favicon File
Recent versions of Vaadin have included a Vaadin-related icon in a favicon.ico file in a configured project. So you must replace that file with your own. In Vaadin 7.5.3 the file contains four sizes, the largest looking like this:
Older versions did not add a file, so you drop in your own.
IDE Screen Shots
Here are a pair of screen shots. One is the project (logical) view in NetBeans 8, while the other is a files (physical) view.
In case of custom icon name (Vaadin 7):
public class MyServlet extends VaadinServlet implements SessionInitListener {
#Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(this);
}
#Override
public void sessionInit(SessionInitEvent event) throws ServiceException {
event.getSession().addBootstrapListener(new BootstrapListener() {
#Override
public void modifyBootstrapPage(BootstrapPageResponse response) {
response.getDocument().head()
.getElementsByAttributeValue("rel", "shortcut icon")
.attr("href", "./VAADIN/themes/mynewtheme/custom.ico");
response.getDocument().head()
.getElementsByAttributeValue("rel", "icon")
.attr("href", "./VAADIN/themes/mynewtheme/custom.ico");
}
#Override
public void modifyBootstrapFragment(BootstrapFragmentResponse response) {
}
});
}
}
EDIT
It is better to use the BootstrapListener as a static nested class: link
Vaadin 23.x (plain spring/war application, no springboot!):
Derive an implementation of com.vaadin.flow.component.page.AppShellConfigurator:
#Theme(value = "mytheme")
#PWA(name = "My application", shortName = "MyApp", iconPath = "icons/favicon.ico" )
public class AppShellConfiguratiorImpl implements AppShellConfigurator {
#Override
public void configurePage(AppShellSettings settings) {
settings.addFavIcon("icon", "icons/favicon.ico", "16x16");
}
}
And put your favicon.ico into src\main\webapp\icons (in order that it is encluded in <war-root>/icons/favicon.ico)
A servlet container (3.0 plus, e.g. Tomcat 8.5) will pick up this class automagically and load it.
I'm translating a web application and things are generally going smoothly with wicket:message and properties files. But Wicket always wants to have a component for looking up strings.
How can I translate converters and renderers (i.e. implementations of IConverter and IChoiceRenderer) which don't have access to any Wicket component in their methods?
So far I found one way - Application.get().getResourceSettings().getLocalizer().getString(key, null) - but I have to make the strings "global", i.e. associated with the application class. That's not nice for separation and reuse. How can I do it better?
I think you should invent you own way how to achieve this. Here in my current project we registered our own IStringResourceLoader like this:
IStringResourceLoader stringResourceLoader = new OurOwnResourceLoaderImpl();
Application.get().getResourceSettings().getStringResourceLoaders().add(stringResourceLoader);
Then for example in IChoiceRenderer we just call Application.get().getLocalizer().getString("key", null).
Inside our IStringResourceLoader we are looking for bundles (property files) with some string pattern according our own conventions.
Or you can just register localization bundle (ie. properties file) distributed inside your library's jar in Application#init through org.apache.wicket.resource.loader.BundleStringResourceLoader.
Afaik there is no standard way to do that so it's up to you what path you choose.
Updated:
I found another solution how your library/extension can register it's own localization by itself so you needn't to touch Application#init or create your own IStringResourceLoaders.
There is preregistered string resource loader org.apache.wicket.resource.loader.InitializerStringResourceLoader (see wickets default IResourceSetting implementation ie. ResourceSetting and it's constructor) which uses wicket's Initializer mechanism - see IInitializer javadoc - basically you add wicket.properties file in your jar class root (ie. it is in default/none package) and inside file there is:
initializer=i.am.robot.MyInitilizer
then i.am.robot.MyInitilizer:
public class MyInitializer implements IInitializer {
/**
* #param application
* The application loading the component
*/
void init(Application application) {
// do whatever want
}
/**
* #param application
* The application loading the component
*/
void destroy(Application application) {
}
}
and now you create your localization bundles in same package and same name as IInitializer implementation (in our example MyInitializer)
I think I found another way...
I noticed that IStringResourceLoader also has a method String loadStringResource(Class<?> clazz, String key, Locale locale, String style); (and one more parameter for variation in newer Wicket versions) which does not require a component. clazz is supposed to be a component class, but... it doesn't actually have to be :)
I was able to implement my own class MyLocalizer extends Localizer with a new method
getString(String key, Class<?> cl, IModel<?> model, Locale locale, String defaultValue)
which works in a similar way to
getString(String key, Component component, IModel<?> model, String defaultValue)
but uses the class directly instead of a component. It still uses the same properties cache and resource loaders.
Then I wrote an abstract class MyConverter implements IConverter which has a MyLocalizer getLocalizer() and a few getString methods like the Component class. Basically it does getLocalizer().getString(key, getClass(), model, locale, defaultValue), so the properties can now be attached to the converter class.
Seems to work :)
If I understand your question...
You can use package based properties that means if you put your keys/values into a property file 'package.properties' in a package. Each localized resource of any subpackage under that package returns the value associated to the requested key until you override it in another property file.
The file name is 'package.properties' in Wicket prior to 1.6.x and 'wicket-package.properties' in Wicket 1.6+
See
https://cwiki.apache.org/confluence/display/WICKET/Migration+to+Wicket+6.0#MigrationtoWicket6.0-package.propertiesrenamedtowicket-package.properties
However it works just for componet, outside the componet (when component argument is null), it is possible to use:
WicketApplication.properties (the WebApplication class is WicketApplication.class, this property file is in the same package).
applicationGlobalProperty=My Global Localized Property
wicket-package.properties (package based, place it in the same package as the page)
localText=Localized text: A local component text based on wicket-package.properties
LocalizedPage.html (markup template)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Localized Page</title>
</head>
<body xmlns:wicket="http://wicket.apache.org">
<div>
<div>
<h2>Texts</h2>
<div>
<wicket:message key="localText"/> <br/>
<span wicket:id="localizedLabel"></span>
</div>
</div>
</div>
</body>
</html>
LocalizePage.java (code)
public class LocalizedPage extends WebPage {
private static final long serialVersionUID = 1L;
public LocalizedPage() {
super();
}
#Override
protected void onInitialize() {
super.onInitialize();
add(new Label("localizedLabel", new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
#Override
public String getObject() {
return WicketApplication.get().getResourceSettings().getLocalizer().getString("applicationGlobalProperty", null);
}
}));
}
}
See the full example on https://repo.twinstone.org/projects/WISTF/repos/wicket-examples-6.x/browse
We are currently facing one problem in our portlet-environment using JSF2.
The application is creating dynamic portal-pages for the actual user session...think of it as Eclipse editor-views where the user can edit entities. So for now I call the dynamic-views editors :-)
The problem we are facing now, is following. The user navigates to a editor and works on the portlet(s). The displayed views in each portlet change over time of course. Now he wants to have a look on another entity which is displayed in another editor. But when he navigates back to the first editor, the state of portlets changes back to the default views.
In the portlet-world each portlet each portlet gets the view it should display via a parameter which is stored in the PortletSession and I can easily change that parameter as well. I know that this parameter is causing the trouble because when the changes the editors, the portlets will always check this parameter to decide which view to show.
request.getPortletSession().setAttribute("com.ibm.faces.portlet.page.view", "/MyPage.xhtml");
My idea was, to somehow add a callback to each JSF-navigation which will set this parameter to the view the navigation is going to display (possibly including view-params).
Is it possible to have a standard callback? If not, would be possible to execute some kind of EL in the navigation-rule that will set this parameter?
somehow add a callback to each JSF-navigation
You could perform the job in a custom ConfigurableNavigationHandler. Here's a kickoff example:
public class MyNavigationHandler extends ConfigurableNavigationHandler {
private NavigationHandler parent;
public MyNavigationHandler(NavigationHandler parent) {
this.parent = parent;
}
#Override
public void handleNavigation(FacesContext context, String from, String outcome) {
// TODO: Do your job here.
// Keep the following line untouched. This will perform the actual navigation.
parent.handleNavigation(context, from, outcome);
}
#Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return (parent instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) parent).getNavigationCase(context, fromAction, outcome)
: null;
}
#Override
public Map<String, Set<NavigationCase>> getNavigationCases() {
return (parent instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) parent).getNavigationCases()
: null;
}
}
To get it to run, register it as follows in faces-config.xml:
<application>
<navigation-handler>com.example.MyNavigationHandler</navigation-handler>
</application>
I'm having an issue.
I've implemented a PhaseListener, which is meant to add a style class to any UIInput components in the tree that have messages attached to them, and removes the style class if it doesn't have any messages attached to them.
The PhaseListener runs in the RENDER_RESPONSE phase, and does it's work in both the beforePhase and afterPhase methods while debugging. While debugging, I found that beforePhase doesn't have access to the full component tree, but afterPhase does. Any changes done in afterPhase aren't rendered though.
How do I go about this? I want this to be completely server side.
Thanks,
James
The JSF component tree is only available after the view build time. The RENDER_RESPONSE phase is not necessarily a good moment to have access to the full JSF component tree before it gets rendered. During an initial GET request without any <f:viewAction>, the full component tree is only available in the afterPhase as it's being built during the RENDER_RESPONSE. During a postback the full component tree is available in the beforePhase, however, when a navigation to a different view has taken place, then it would stil be changed during the RENDER_RESPONSE phase, so any modifications would get lost.
To learn what exactly the view build time is, head to the question What's the view build time?
You basically want to hook on "view render time" rather than beforePhase of RENDER_RESPONSE phase. JSF offers several ways to hook on it:
In some master template, attach a preRenderView listener to <f:view>.
<f:view ...>
<f:event type="preRenderView" listener="#{bean.onPreRenderView}" />
...
</f:view>
public void onPreRenderView(ComponentSystemEvent event) {
UIViewRoot view = (UIViewRoot) event.getSource();
// The view is the component tree. Just modify it here accordingly.
// ...
}
Or, implement a global SystemEventListener for PreRenderViewEvent.
public class YourPreRenderViewListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
UIViewRoot view = (UIViewRoot) event.getSource();
// The view is the component tree. Just modify it here accordingly.
// ...
}
}
To get it to run, register it as below in faces-config.xml:
<application>
<system-event-listener>
<system-event-listener-class>com.example.YourPreRenderViewListener</system-event-listener-class>
<system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class>
</system-event-listener>
</application>
Or, provide a custom ViewHandler wherein you do the job in renderView().
public class YourViewHandler extends ViewHandlerWrapper {
private ViewHandler wrapped;
public YourViewHandler(ViewHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public void renderView(FacesContext context, UIViewRoot view) {
// The view is the component tree. Just modify it here accordingly.
// ...
// Finally call super so JSF can do the rendering job.
super.renderView(context, view);
}
#Override
public ViewHandler getWrapped() {
return wrapped;
}
}
To get it to run, register as below in faces-config.xml:
<application>
<view-handler>com.example.YourViewHandler</view-handler>
</application>
Or, hook on ViewDeclarationLanguage#renderView(), but this is a bit on the edge as it isn't really intented to manipulate the component tree, but to manipulate how to render the view.
Unrelated to the concrete problem, this all is not the right solution for the concrete functional requirement as stated in your question:
which is meant to add a style class to any UIInput components in the tree that have messages attached to them, and removes the style class if it doesn't have any messages attached to them
You'd really better head for a client side solution rather than manipulating the component tree (which would end up in JSF component state!). Imagine the case of inputs in iterating components such as <ui:repeat><h:inputText>. There's physically only one input component in the tree, not multiple! Manipulating the style class via UIInput#setStyleClass() would get presented in every iteration round.
You'd best visit the component tree using UIViewRoot#visitTree() as below and collect all client IDs of invalid input components (this visitTree() approach will transparently take iterating components into account):
Set<String> invalidInputClientIds = new HashSet<>();
view.visitTree(VisitContext.createVisitContext(context, null, EnumSet.of(VisitHint.SKIP_UNRENDERED)), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
if (!input.isValid()) {
invalidInputClientIds.add(input.getClientId(context.getFacesContext()));
}
}
return VisitResult.ACCEPT;
}
});
And then thereafter pass invalidInputClientIds in flavor of a JSON array to JavaScript which will then grab them via document.getElementById() and alter the className attribute.
for (var i = 0; i < invalidInputClientIds.length; i++) {
var invalidInput = document.getElementById(invalidInputClientIds[i]);
invalidInput.className += ' error';
}
The JSF utility library OmniFaces has a <o:highlight> component which does exactly this.
Implemented using a ViewHandler, however it's not efficient. PhaseListener in Render Response phase doesn't have access to the component tree.