JSF2 + IceFaces 2 - Retrieve UIComponent from View

2019-08-28 19:34发布

问题:

I've got hard time resolving the following. My problem is quite simple : I would like to highlight in red the forms fields that triggered validation errors. The error messages are placed correctly in the FacesContext using a context.addMessage(...) line.

I'd like my system to be generic. All form fields having a message attached are automatically highlighted.

I've found on this site a link to this excellent article : http://www.jroller.com/mert/entry/how_to_find_a_uicomponent

With it, I did implement a PhaseListener for the RENDER_RESPONSE phase, which do the following :

@Override
  public void beforePhase(PhaseEvent event) {
    // get context
    FacesContext context = event.getFacesContext();

    // iterate on all the clientIds which have messages
    Iterator<String> clientIdsWithMessages = context.getClientIdsWithMessages();
    while (clientIdsWithMessages.hasNext()) {

      // get the clientId for the field component
      String clientIdWithMessage = clientIdsWithMessages.next();
      // split on ":"
      String[] splitted = clientIdWithMessage.split(":");

      UIComponent component = findComponentInRoot(splitted[splitted.length - 1]);
      if (component != null) {
        Map<String, Object> attributes = component.getAttributes();

        if (attributes.containsKey("style")) {
          attributes.remove("style");
        }
        attributes.put("style", "background-color: #FFE1E1;");
      }
    }
  }

This perform perfectly well for almost all my usage.

Now, where it becomes a bit tricky, is that some of my forms have such code :

<ice:dataTable id="revisionDocuments" value="#{agendaBean.agenda.revisionsDocuments}" var="revision">
    <ice:column>
        <ice:inputText value="#{revision.sequenceAdresse}" id="revisionSequenceAdresse" />
    </ice:column>
    ....

The generated form has several lines (one for each object of the revisionsDocuments list), and each element has a unique identifier (clientId) which looks like :

contentForm:revisionDocuments:0:revisionSequenceAdresse

With 0 changed for 1, 2, ... for each iteration. Consequently, the code provided to search the UIComponent from ViewRoot does not work properly. All forms fields have the same "id". What surprise me more is : they have the same "clientId" in FacesContext too :

contentForm:revisionDocuments:revisionSequenceAdresse

I cannot distinguish, while going through the tree, if I do see the right form field or any of the others.

Does anyone have a hint to solve this ? Or another suggestion to implement the highlight of my fields ? I have to admit, I dont really like my code, I consider dirty to manipulate the viewRoot like I'm doing, but I could not figure out a better solution to have a generic highlight of my fields.

I'm running IceFaces 2.0.2 with JSF-Impl 2.1.1-b04 on JBOss AS 7.0.2.Final.

Thank you in advance for the answers. Best regards, Patrick

回答1:

You should apply this in the client side instead. You've got a collection of client IDs with messages. One of the ways is to pass this information to JavaScript and let it do the job. You can find an example of such a PhaseListener in this article: Set focus and highlight in JSF.

Since JSF 2.0 there is however another way without the need for a PhaseListener. There's a new implicit EL variable, #{component} which refers to the UIComponent instance of the current component. In case of UIInput components, there's an isValid() method. This allows you to do something like:

<h:inputText styleClass="#{component.valid ? '' : 'error'}" />

with this in a CSS file:

.error {
    background: #ffe1e1;
}

(yes, you can also do this in a style attribute, but mingling style with markup is a poor practice)

To abstract this away (so that you don't need to repeat it in every input), you can just create a composite component for this, something like <my:input>.



回答2:

For completeness, here is the solution I finally found to highlight the fields that do have error messages with IceFaces 2.0.2 :

The basic idea is strictly the same than proposed by BalusC on http://balusc.blogspot.com/2007/12/set-focus-in-jsf.html

The piece of code I had to change with IceFaces is the small Javascript call :

<script>
    setHighlight('${highlight}');
</script>

I could not find any IceFaces component which is re-rendered at each JS call. I found that placing the script into a panelGroup works most of the time. However, there was a case that did not work :

submitting the form with errors do trigger the JS. then, re-submitting the form with errors on the same field than previous validation do NOT trigger the JS. then, re-submitting the form with any error field having no more errors do trigger JS. then, re-submitting the form with any non-errored field having an error do trigger JS.

For some reason, IceFaces do not render the panelGroup that contains the JS when the set of fields having errors is the same between two calls.

I tried to use the Javascript API with code like Ice.onAsynchronousReceive(), using Prototype library to attach an event to the AJAX completion of the commandButton, but had not much success with it. Some of my tests could run (with errors but did the job) and I could observe similar behavior.

Here is the trick I finally used (ugly but working) :

<ice:panelGroup>
    <script type="text/javascript">
        var useless = '#{testBean.time}';
        setHighlight('${highlight}');
    </script>
</ice:panelGroup>

The getTime() function simply return the current timestamp. The value is then always different and trigger the JS execution at any AJAX request.

Sadly, IceFaces do not have the RichFaces useful "oncomplete" attribute, which I do regret highly for this case.

Ugly solution, but funny and working.