Showing posts with label jsf. Show all posts
Showing posts with label jsf. Show all posts

10 August 2009

Disable days on rich:Calendar

This article covers how you'd disable some days on the Richfaces Calendar component. For a recent project, I needed to only allow users to pick Saturdays.

Here's the XHTML Facelets code for the tag:
<rich:calendar value="#{oc.overtimeDate}" requiredMessage="Date 1 is required."
id="#{oc.overtimeDateId}" isDayEnabled="isDayEnabled"
dayStyleClass="getDisabledStyle" datePattern="MM-dd-yyyy"
required="#{oc.id == 1 ? true : false}" firstWeekDay="0"/>
Note two items: the isDayEnabled and dayStyleClass attributes. Both have their values set via the following JavaScript functions:
<script type="text/javascript">
function isDayEnabled(day){
   var date = new Date(day.date);
   return (date.getDay() == 6); // 6 is Saturday, the only day that should be enabled.
}
function getDisabledStyle(day){
   if (!isDayEnabled(day)) return 'rich-calendar-boundary-dates disabledDay';
}
</script>
The isDayEnabled() function uses the day argument to get the date and then the day of the week. It returns true if the day of the week is Saturday, false otherwise. This took some trial-and-error, because I didn't know how to get to the day object's date.

The getDisabledStyle() function again receives the day object, which it uses to determine the CSS style class to use. It's returning two classes for that element in this case. The second style is a custom one and is defined here:
<style>
.disabledDay{
 background-color:gray;
}
</style>
Update: Jerick Osiel asked for help on how to highlight/select a week on the calendar. The answer was thanks to Makhiel on Stackoverflow.com:
<h:outputStylesheet>
    .highlightWeek {
        color: red;
        background-color: black;
    }
</h:outputStylesheet>
<h:outputScript>
    var currentWeekNumber = … // determine current week number
    chooseDay = function(day) {
        if (day.weekNumber == currentWeekNumber) return 'highlight';
            return '';
        }
</h:outputScript>
<h:form>
    <rich:calendar dayClassFunction="chooseDay" />
</h:form>

03 August 2009

Add JSF controls dynamically with c:forEach

Sometimes you need to add controls to a page based on user action at run-time. For this purpose, we'll use the c:forEach tag; the RichFaces rich:repeat and a4j:repeat tags won't do the job because they work during the page render time and do not create components. The c:forEach works at view build phase; Roger Keays has an article to describe the difference between these controls in JSF Facelets. This blog post is based on Ilya Shaikovsky's great article.

I needed to generate some RichFaces Calendar components with associated JSF HtmlTextInput boxes, to enable users to enter a date and overtime hours. Users could add an arbitrary number of these "rows" of controls. Some early experimentation with an a4j:outputPanel revealed the impossibility to programmatically add controls to the panel via a backing bean. Instead, I needed to use the JSF c:forEach tag.

We start by creating a custom class for our calendars, text input boxes, and their related controls. The custom class will generate the IDs for the various components and hold the Date and hours values, along with getter/setter methods (properties):
package org.unctv.controls;

import java.util.Date;
import java.util.Random;
import java.io.Serializable;

public class OvertimeComponent implements Serializable {

private static final long serialVersionUID = -2233263449692354678L;

private Integer Id;
private Date overtimeDate;
private Number overtimeHours;
private String overtimeDateId;
private String overtimeHoursId;
private String dateLabel;
private String overtimeHoursOutputId;
private Integer randomId;

public OvertimeComponent(Integer Id){
this.Id = Id;

String strId = Integer.toString(this.Id);

this.overtimeDateId = "calOvertimeDate_" + strId;
this.overtimeHoursId = "txtOvertimeHours_" + strId;
this.overtimeHoursOutputId = "txtOvertimeHoursOutput_" + strId;
this.dateLabel = "Date " + strId;

randomId = new Random(System.currentTimeMillis()).nextInt();
}

public String getOvertimeHoursOutputId(){
return this.overtimeHoursOutputId;
}
public void setOvertimeHoursOutputId(String overtimeHoursOutputId){
this.overtimeHoursOutputId = overtimeHoursOutputId;
}

public Integer getRandomId(){
return this.randomId;
}
public void setRandomId(Integer randomId){
this.randomId = randomId;
}

public String getOvertimeDateId(){
return this.overtimeDateId;
}
public void setOvertimeDateId(String overtimeDateId){
this.overtimeDateId = overtimeDateId;
}

public String getOvertimeHoursId(){
return this.overtimeHoursId;
}
public void setOvertimeHoursId(String overtimeHoursId){
this.overtimeHoursId = overtimeHoursId;
}

public String getDateLabel(){
return this.dateLabel;
}
public void setDateLabel(String dateLabel){
this.dateLabel = dateLabel;
}

public Integer getId(){
return this.Id;
}
public void setId(Integer Id){
this.Id = Id;
}

public Date getOvertimeDate(){
return this.overtimeDate;
}
public void setOvertimeDate(Date overtimeDate){
this.overtimeDate = overtimeDate;
}

public Number getOvertimeHours(){
return this.overtimeHours;
}
public void setOvertimeHours(Number overtimeHours){
this.overtimeHours = overtimeHours;
}
}
Note that we're generating a random ID for each control; this will serve as a unique identifier for each "row" and will be used for deleting the controls from the page later. In the constructor, we set the IDs of the various controls.

Next, in our backing action class for the Facelet, we have code to initialize the ArrayList of components and handle the adding and removing of elements from the ArrayList. We're using the JBoss Seam framework, so we just need to add some annotations to be able to reference this backing bean from our XHTML; if we use just plain JSF, we'd need to add a <managed-bean> to the faces-config.xml file. Here's the Seam action component:
package org.unctv.requestforms.overtime;

import java.util.ArrayList;
import javax.faces.context.FacesContext;
import org.jboss.seam.annotations.Name;
import org.unctv.controls.OvertimeComponent;
import static org.jboss.seam.ScopeType.SESSION;
import org.jboss.seam.annotations.Scope;
import javax.faces.model.SelectItem;
import org.unctv.util.Utilities;

@Name("overtime")
@Scope(SESSION)
public class OvertimeAction {
private ArrayList<OvertimeComponent> overtimeItems;
private Integer componentCount;

public OvertimeAction(){
this.overtimeItems = new ArrayList<OvertimeComponent>();
componentCount = 1;
this.addOvertimeComponent();
}

public String getOvertimeValues(){
StringBuilder sb = new StringBuilder();
Utilities util = new Utilities();

for (int i=0; i < overtimeItems.size(); i++){
if ((overtimeItems.get(i).getOvertimeDate() != null &&
!overtimeItems.get(i).getOvertimeDate().toString().equalsIgnoreCase("")) &&
(overtimeItems.get(i).getOvertimeHours() != null )){
sb.append(Integer.toString(i+1) + ") Date: " + util.formatDate(overtimeItems.get(i).getOvertimeDate(),"MM-dd-yyyy"));
sb.append("; Hours: " + (overtimeItems.get(i).getOvertimeHours()));
sb.append(" ***** ");
}
}
return sb.toString();
}

public void addOvertimeComponent(){
overtimeItems.add(new OvertimeComponent(componentCount++));
}

public OvertimeComponent getOvertimeComponentByRandomId(Integer randomId){
for (OvertimeComponent currentOC : overtimeItems){
if (currentOC.getRandomId().equals(randomId)){
return currentOC;
}
}
return null;
}

public void removeOvertimeComponent(){
OvertimeComponent currentOC = null;
Integer currentRandomId;
String current = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("current");

if (current != null){
currentRandomId = Integer.parseInt(current);
currentOC = this.getOvertimeComponentByRandomId(currentRandomId);
this.overtimeItems.remove(currentOC);
}
}

public ArrayList<OvertimeComponent> getOvertimeItems(){
return this.overtimeItems;
}
public void setOvertimeItems(ArrayList<OvertimeComponent> overtimeItems){
this.overtimeItems = overtimeItems;
}
}
We use a custom class (org.unctv.util.Utilities) with a method that returns a formatted date. In the action class' constructor, we instantiate an ArrayList of OvertimeComponent objects and add the first calendar and text input box.

Users can delete a "row" of controls by clicking a "Remove" button; the removeOvertimeComponent() method handles this process. As we'll see in a moment in the XHTML, an a4j:jsFunction tag calls the handler and passes the randomId of the current control. The getOvertimeComponentByRandomId() returns the current OvertimeComponent using the randomId. We then call the remove() method on the ArrayList to delete this element.

To get the values from the ArrayList as a string, we use getOvertimeValues(). This method loops the list and, if the date and overtime hours aren't blank, appends their values to a StringBuilder object.

The XHTML page contains the c:forEach tag:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
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:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:c="http://java.sun.com/jstl/core"
template="../global/layout/template.xhtml">

<ui:define name="body">
<a4j:outputPanel id="output" rendered="#{not empty overtime.overtimeItems}">
<c:forEach items="#{overtime.overtimeItems}" var="oc">
<h:outputLabel value="#{oc.dateLabel}"
style="font-weight:#{(oc.id == 1) ? 'bold' : 'normal'}"
for="#{oc.overtimeDateId}" />
<rich:calendar value="#{oc.overtimeDate}" requiredMessage="Date 1 is required."
id="#{oc.overtimeDateId}"
datePattern="MM-dd-yyyy" required="#{oc.id == 1 ? true : false}">
<a4j:support event="onchanged" reRender="#{oc.overtimeHoursId}, #{oc.overtimeHoursOutputId}" ajaxSingle="true"/>
</rich:calendar>

<h:outputText value="OT Hours" id="#{oc.overtimeHoursOutputId}"
style="padding-left:9px; font-weight:#{oc.overtimeDate == null || oc.overtimeDate.toString() == '' ? 'normal' : 'bold'}"/>
<h:inputText value="#{oc.overtimeHours}" id="#{oc.overtimeHoursId}"
converterMessage="OT Hours for Date #{oc.id} must be numeric and in this format: 99.9"
requiredMessage="OT Hours is required if Date #{oc.id} has a value." maxlength="4" style="width:50px;"
required="#{oc.overtimeDate == null || oc.overtimeDate.toString() == '' ? false : true}" >
<f:convertNumber maxFractionDigits="1" maxIntegerDigits="2" type="number"/>
</h:inputText>
<a4j:commandButton action="#{overtime.addOvertimeComponent()}"
rendered="#{oc.id == 1 ? true : false}" style="margin-left:9px"
reRender="output" type="submit" ajaxSingle="false" value="Add Date Row" >
<rich:toolTip followMouse="true" direction="top-right"
showDelay="300" styleClass="tooltip tooltip_custom">
<span style="white-space:nowrap">
Click to add another row for date and overtime hours.
</span>
</rich:toolTip>
</a4j:commandButton>
<h:commandButton onclick="removeOvertime('#{oc.randomId}'); Event.stop(event);" value="Remove"
rendered="#{oc.id == 1 ? false : true}" style="margin-left:9px" >
<rich:toolTip followMouse="true" direction="top-right" showDelay="300" styleClass="tooltip tooltip_custom">
<span style="white-space:nowrap">
Click to remove this row of date and overtime hours.
</span>
</rich:toolTip>
</h:commandButton>
<br />
</c:forEach>
</a4j:outputPanel>

<a4j:jsFunction action="#{overtime.removeOvertimeComponent()}" name="removeOvertime"
ajaxSingle="true" reRender="output">
<a4j:actionparam name="current"/>
</a4j:jsFunction>

<label for="kluge" class="buttons" />
<a4j:commandButton action="/new-employee/new_employee.xhtml"
type="submit" value="Submit"
reRender="status" eventsQueue="foo" status="status"
onclick="this.disabled=true" id="btnSubmit"
oncomplete="#{facesContext.maximumSeverity == null ? 'Richfaces.hideModalPanel(\'mpErrors\');' : 'Richfaces.showModalPanel(\'mpErrors\'); this.disabled=false'}"
/>
<input id="btnReset" value="Reset" type="reset" />
<br />
<label for="kludge" class="buttons" />
<a4j:status id="status" startText="Processing request. Please wait..."
stopText="#{utilities.status}" startStyleClass="startStyle"
stopStyleClass="stopStyle" /><br />
</ui:define>
</ui:composition>
We set the items attribute of the c:forEach tag to the overtimeItems ArrayList; our loop variable is called oc. Note that the "Remove" button calls the a4j:jsFunction to delete the row by passing it the randomId of the current control. A rich:modalPanel (referenced in the oncomplete of one of the buttons) has been omitted for brevity.

The form uses CSS for layout; each control has a preceding label tag. Blank label tags have their for attribute set to "kluge" to indicate that their simply there for formatting. Also note that the first "row" in the c:forEach doesn't have a "Remove" button; we require the first set of controls and don't want users to delete them. That first set also has a button to add controls.

If users set the date on a rich:Calendar, we use AJAX (a4j:support) to update the required attribute of the associated h:inputText tag and its style.

21 July 2009

Referencing an object in JSF/Richfaces

With JSF 1.2, referencing objects in the component tree isn't as straightforward as it used to be. The findComponent() method has been deprecated, so we must use other means to find an object. Jacob Hookom and Ric Smith provide slight variations on how to accomplish this task. Both involve using the ContextCallback object; here's a snippet to give you an idea:
public static final ContextCallback RENDER = new ContextCallback() {
public void invokeContextCallback(FacesContext ctx, UIComponent c) {
c.renderAll(ctx);
}
};

boolean found = root.invokeOnComponent(faces, clientId, RENDER);

if (!found) throw new FacesException(clientId + " not found!");

Note in the above code, you use the invokeOnComponent() method to (what else?) invoke a particular action on the component in question. Note that you need to use the client ID for the object, which in JSF looks something like this: myForm:myComponentID

In turn, the RENDER method calls invokeContextCallback(), which carries out some action on the component. This can be handy for programmatically adding controls to the page at a specific location; c.renderAll(ctx) can be replaced with whatever you need.

You can also add objects to the page using Max Katz' technique, though I'm not sure if that way you're allowed to put them at a specific location in the component tree.

06 June 2009

Populate and validate JSF selectOneMenu

Using a backing bean or Seam component (on JBoss), you can easily populate a JSF selectOneMenu (JSF dropdown box server-side control):

<h:selectonemenu id="lstRating" value="#{asset.rating}" required="true" requiredMessage="Rating is required.">
<f:selectitems value="#{asset.ratingItems}" />
</h:selectonemenu>
<rich:message for="lstRating" styleclass="errors" />

In this example, you have a selectOneMenu with id of lstRating. It gets its SelectItem elements from the asset.ratingItems property on the backing component. The selected item is stored in asset.rating.

  private String rating;
private ArrayList<SelectItem> ratingItems = null;

public ArrayList<SelectItem> getRatingItems(){
ratingItems = new ArrayList<SelectItem>();
ratingItems.add(new SelectItem(null,"Select One..."));
ratingItems.add(new SelectItem("TV-Y", "TV-Y"));
ratingItems.add(new SelectItem("TV-Y7", "TV-Y7"));
ratingItems.add(new SelectItem("TV-G", "TV-G"));
ratingItems.add(new SelectItem("TV-PG", "TV-PG"));
ratingItems.add(new SelectItem("TV-14", "TV-14"));
ratingItems.add(new SelectItem("TV-MA", "TV-MA"));

return ratingItems;
}

public String getRating(){
return this.rating;
}

public void setRating(String rating){
this.rating = rating;
}

In the above code, we declare an ArrayList of SelectItem object type. The getRatingItems() property instantiates the ratingItems ArrayList and populates it with SelectItem objects. Note the first SelectItem has a null value; if the user leaves the dropdown box to this item, the required validator will detect it as a blank value and throw a validation error.