Apr 192011
 

The following are things that I think could be improved upon in JSF2.

1) There is no colspan (or rowspan) attribute for panelGrid.

Something like this would be useful, for example if you want full control of the tables JSF creates for you for specific formatting. There are a couple of ways to get around this. One method is to not use a panelGrid and just use a plain old html table. Of course, if you use a plain html then you’ll have to wrap it something (like a panelGrid for instance) if you’d like the table to be rerendered with some AJAX.

The other way around this is knowing that the panelGrid does offer headers and footers for the table it creates for you. This may be acceptable for your design.

<f:facet name="header">
    <h:outputText value="I am header text and I take up many columns."/>
</f:facet>

and

<f:facet name="footer">
    <h:outputText value="I am footer text and I take up many columns."/>
</f:facet>

2) A Boolean value on a bean is set to false instead of being left as null when the form is submitted.

There isn’t much to say about this. It’s “Boolean” not “boolean,” so don’t touch it if it’s null! This may have to do with the javax.faces.VALIDATE_EMPTY_FIELDS setting, but in my mind the two should be unrelated. Storing null instead of “false” is useful when saving form data to a database and capturing whether the user actually chose false or was defaulted into false. The default should be null if there is a “no option” default. The only way around this I’m aware of at the time of this writing is making the backing bean use a String, giving the “no option” option a value of “null,” and then turning that “null” String into an actual null value before inserting into the database (what a pain).

3) selectMany check boxes can only be displayed vertically or horizontally.

What about 2 by 2? 3 by 2? etc. This is another control issue.. The way around this that I found is to create extra methods on the backing bean – one that returns the first half of the check boxes and one that returns the second half of the check boxes. That way the two methods can be used for two separate columns, thus creating a list of checkboxes that are two-by-two. The annoying thing about doing it this way is that returning a simple

list.sublist(0, list.size() / 2);

doesn’t really work. Since JSF wants to set data on the list instead of just looking at it, you may need to actually truly have two lists on the backing bean. Then dealing with it as one list requires some additional workarounds. That’s what I had to do…

 Posted by at 9:56 pm
Apr 022011
 

Sometimes it’s desirable to set focus to the the invalid component instead of only displaying a message about the invalid component. This is especially true when tabs are used and the invalid component may not be visible to the user at all, leaving the submit button apparently unresponsive when it’s actually displaying the message on a different tab. The code works as follows and depends on the following.

…sets focus to invalid input components (empty and required or when an invalid date is entered, for example). It depends on the following things:

 

  1. the component must have an ID.
  2. The h:message component must have an ID exactly the same as the invalid component except with the additional suffix of “Message”.
  3. The h:message component must have the “errorMessage” css class specified as one of it’s classes.

This works in the following way.

  1. look for components with the “errorMessage” class.
  2. removes the “Message” suffix from the ID of the component.
  3. find the component with the new ID.
  4. set focus there.

On the submit button use the “onevent” attribute to trigger javascript.

<h:commandButton value="Submit Claim"
     action="#{controller.storeClaim}" id="saveReport">
      <f:ajax event="action" execute="@form"
     render="allErrorMessages tc1 tc2 tc3 tc4 tc5"
           onevent="setFocusToRequiredOrInvalid" />
</h:commandButton>

In an included javascript file define the “setFocusToRequiredOrInvalid” function as the following.

/** Returns true if the passed in string ends with the passed in suffix. */
function endsWith(str, suffix) {
   return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
 
// set focus to the first required field that is empty.
function setFocusToRequiredOrInvalid(e) {
     var setFocusItem = null;
     var tabIndexForFocus = null;
     
     /* The following sets focus to invalid input components (empty and required or when an invalid date
       * is entered, for example). It depends on the following things:
       * 1) the component must have an ID.
       * 2) The h:message component must have an ID exactly the same as the invalid component except with
       * the additional suffix of "Message".
       * 3) The h:message component must have the "errorMessage" css class specified as one of it's classes.
       * This works in the following way:
       * 1) look for components with the "errorMessage" class.
       * 2) removes the "Message" suffix from the ID of the component.
       * 3) find the component with the new ID
       * 4) set focus there */

     
      // look for error message components
      jQuery.each($(".errorMessage"), function(i, val) {
           var componentID = val.id; // get the component's ID
           
           // if the ID ends in "Message" then we named the ID to correspond with an input component
           if (endsWith(componentID, "Message")) {
                 // get the ID of the component for which this error message refers.
                 var errorredComponentID = componentID.substring(0, componentID.length - "Message".length);
                 
                 // select the errored component
                 var errorredComponent = document.getElementById(errorredComponentID);
                 if ((errorredComponent != null) &&
                              (errorredComponent != undefined) &&
                              (errorredComponent != "")) {
                       
                       // set the focus to this component
                        setFocusItem = errorredComponent;
                  }
                 
                 // figure out which tab to switch to
                 if (setFocusItem != null) {
                       // get the list of classes for the element. copied from
                       // stackoverflow.com/questions/1227286/get-class-list-for-element-with-jquery
                       var classList =$(setFocusItem).attr('class').split(/s+/);
                       // look at each class
                       for(var i = 0; i < classList.length; i++) {
                             var value = classList[i];
                             // if the length is correct and it starts with "tab-" then
                             // extract the tab number
                             if ((value.length >= "tab-0".length) && (value.substr(0,4) == "tab-")) {
                                    tabIndexForFocus = value.substr(4);
                              }
                        }
                       
                       return false; // this means "break;" to jquery
                  }
            }
      });
     
     // look for invalid components by looking for requiredEmpty components.
     // these components are required to be empty
      jQuery.each($(".requiredEmpty"), function(i, val) {
           // get the first child's node value...
           if (val.firstChild != null) {
                  value = val.firstChild.nodeValue;
            }
           
           if ((setFocusItem == null) &&
                  (!
                  ((value == undefined) || (value == null) || (value == ""))
                  )
                  ) {
                 // find the previousSibling which is an input type for setting focus
                 do {
                        value = val.previousSibling;
                       
                       if (value == null) {
                             break;
                        }
                       if ((value.nodeName == "INPUT") && (value.getAttribute('type') != "hidden")) {
                              setFocusItem = value;
                             break;
                        }
                  } while (true);
            }
      });
     
     // switch to the appropriate tab if we should
     if (tabIndexForFocus != null) {
           var $tabs = $('#tabs').tabs(); // first tab selected
            $tabs.tabs('select', parseInt(tabIndexForFocus)); // switch to tab
           //$tabs.tabs('select', 1); // switch to tab
      }
     
     // if we found an empty required item then set the focus there
     if (setFocusItem != null) {
            setFocusItem.focus();
      }
}
Mar 302011
 

things inside my dataTable aren’t displayed at all?
use a <h:column> tag and put the items to display inside of it..

    <h:dataTable value="#{controller.claim.diagnosisCodeMeta}" var="m"
       id="dataTableDiagCodesMeta" rowClasses="color1,color2">
 
        <h:column>
... content ...
</h:column>
 
</h:dataTable>
Mar 302011
 

Don’t reload a page when a commandButton is clicked and the button is only meant to do an ajax request, use immediate=”true” and return null from the action method.

<h:commandButton immediate="true"
    value="Add Charges Section" action="#{controller.claim.addChargesSection}">
   
   <f:ajax render="dataTableDiagCodesMeta dataTableDiagCodes" execute="@form" />
</h:commandButton>