Friday, March 26, 2010

Immediate, Autosubmit, and Validation

Often users understand how to use immediate on buttons and form controls to avoid validation, however they sometimes have problems using immediate and autoSubmit. So what's different about using autoSubmit and immediate?

Let's start at the beginning, components that implement the interfaces below support an attribute called "immediate". In both cases processing is moved up to the APPLY REQUEST VALUES phase.

  • ActionSource: (an example is commandButton.) An ActionEvent is normally delivered in INVOKE APPLICATION. When immediate is true the action event delivery is moved up to the APPLY REQUEST VALUES phase. An ActionEvent always results in renderResponse() being called implicitly in the default ActionListener (Application.getActionListener()), whether or not an "action" is attached.
  • EditableValueHolder: the validation and valueChangeEvent usually take place in the PROCESS VALIDATIONS phase. When immediate is true the component's value will be converted and validated in the APPLY REQUEST VALUES phase, and ValueChangeEvents will be delivered in that phase as well.


So in JSF there's a default actionListener which calls renderResponse. That means that when a button is immediate renderResponse will be called during apply request values, and the validation phase will be skipped.

Let's look at the following example, note that when you press submit you don't get a required field error:



<af:selectBooleanRadio valueChangeListener="#{validate.toggle}" id="show3"
text="Show" group="group3"
value="#{validate.show3}" immediate="true"/>
<af:selectBooleanRadio id="hide3" text="Hide" group="group3"
value="#{validate.hide3}" immediate="true"/>
<af:panelGroupLayout partialTriggers="cb2" id="pgl8">
<af:inputText label="Required Field" required="true"
rendered="#{validate.show3}" id="it3"/>
</af:panelGroupLayout>
<af:commandButton text="submit" id="cb2" immediate="true"/>


and in the validate bean here's the toggle method

  
public void toggle(ValueChangeEvent vce)
{
setShow3(Boolean.TRUE.equals(vce.getNewValue()));
}



Adf Faces adds the concept of "autoSubmit" to form controls, when "autoSubmit" is true the component will automatically submit when the value is changed. When people combine autoSubmit and immediate on a form control, they often expect to skip the validation phase like they would if they had used an immediate button, but there is no default listener for valueChangeEvents.

You might think you can just change the jspx to this without changing the toggle method.


<af:selectBooleanRadio autoSubmit="true"
valueChangeListener="#{validate.toggle}" id="show3"
text="Show" group="group3" value="#{validate.show3}"
immediate="true"/>
<af:selectBooleanRadio autoSubmit="true" id="hide3" text="Hide" group="group3"
value="#{validate.hide3}" immediate="true"/>
<af:panelGroupLayout partialTriggers="show3 hide3" id="pgl8">
<af:inputText label="Required Field" required="true"
rendered="#{validate.show3}" id="it3"/>
</af:panelGroupLayout>


However you also have to change the toggle method to call FacesContext.renderResponse, like so:

  
public void toggle(ValueChangeEvent vce)
{
setShow3(Boolean.TRUE.equals(vce.getNewValue()));
FacesContext.getCurrentInstance().renderResponse();
}

Tuesday, March 2, 2010

Clearing Properties Set on the Client in ADF Faces

Oracle Adf Faces is based on JSF, which has a server-side component model. One of the things ADF Faces adds is a component model on the client. This means you can get/set component attributes on the client using javascript.

When you set a value on the client, that value is actually sent to the server and set on the component, so that when it is rerendered the new value will be shown. Sometimes this isn't the behavior a user is expecting. This blog will suggest some coding workarounds.

Let's look at an example. An ADF Faces inputText component has a 'changed' attribute. When the 'changed' attribute is true, a blue circle is shown to the left of the label, indicating that the value has changed. Here's what an input looks like when 'changed' is set to true:



So let's say I want to show the changed indicator as soon as the user changes the value. Then when I save the page I re-render, the changed indicator should not be shown since the value has now been saved. You might start with a page like the one below. In this code we are listening for a valueChangeEvent on the client, and when we get it we set 'changed' to true using javascript.

<?xml version='1.0' encoding='utf-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich" version="1.2">
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document id="d1" inlineStyle="padding:20px">
<af:form>
<af:resource type="javascript">
function changed(event) {
var inputComponent = event.getSource();
inputComponent.setChanged(true);
}
</af:resource>
<af:inputText id="it" label="label">
<af:clientListener method="changed" type="valueChange"/>
</af:inputText>
<af:commandButton text="Submit"/>
</af:form>
</af:document>
</f:view>
</jsp:root>



If you run this page in Jdev 11.1.1.2.0 you'll find that you can enter a value in the input, tab out, and the changed indicator does in fact show up. So we're done, right? Well not quite, because you'll also find that when you hit submit the changed indicator does not disappear like we want it to.

Why is the value of changed saved? Under the covers client side convenience setters like setChanged() are really calling AdfUIComponent.setProperty. The doc for setProperty says "Changing the value of a property supported by the component will normally result in the new property value being synchronized with the server automatically." In other words when you call setProperty on the client, the new property value will be sent to the server, and the new value is saved on the component. This means the component will rerender based on the new attribute value. In our example this means that when we submit and the component is rerendered it still thinks changed is true, and thus the blue changed icon is still visible.

When is the new value sent to the server? The new value will be sent to the server the next time there's traffic to the server. In some cases that happens right away, in other cases not.

So what follows are some coding alternatives to consider. Note that depending on how your app is written, some of these may work better than others if validation fails on the server.

Coding Alternative 1



The first coding alternative moves the logic to the server. In this example:
  • in the input tag bind changed to a request scoped value
  • in the input tag set autoSubmit to true
  • in the valueChangeListener for the input component
    • set the request scoped value that the 'changed' attribute is bound to to true
    • call context.renderResponse
    • set the input component as a partial target so that the input component rerenders itself.




<?xml version='1.0' encoding='utf-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich" version="1.2">
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document id="d1" inlineStyle="padding:20px">
<af:form>
<af:inputText label="label"
autoSubmit="true"
changed="#{test.changed}"
valueChangeListener="#{test.valueChange}"/>
<af:commandButton text="Submit" />
</af:form>
</af:document>
</f:view>
</jsp:root>



And the test backing bean code, which is request scope looks like:



import javax.faces.event.ValueChangeEvent;
import oracle.adf.view.rich.context.AdfFacesContext;

public class TestBean {
public TestBean() {}

public void valueChange(ValueChangeEvent valueChangeEvent)
{
setChanged(true);
AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
adfFacesContext.addPartialTarget(valueChangeEvent.getComponent());
FacesContext.getCurrentInstance().renderResponse();
}

public void setChanged(boolean changed)
{
_changed = changed;
}

public boolean isChanged()
{
return _changed;
}
private boolean _changed;
}



Coding Alternative 2


Coding alternative 1 relies on having autoSubmit and a valueChangeListener. In this particular example that works, but in other examples you may not have an event that is going to the server. In those cases you might want to use a serverListener, here's the example:


<?xml version='1.0' encoding='utf-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich" version="1.2">
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document id="d1" inlineStyle="padding:20px">
<af:form>
<af:resource type="javascript">
function changed(event)
{
var inputComponent = event.getSource();
AdfCustomEvent.queue(inputComponent, "myCustomEvent", null, true);
}
</af:resource>
<af:inputText label="label" changed="#{test2.changed}">
<af:serverListener type="myCustomEvent"
method="#{test2.doCustomEvent}"/>
<af:clientListener method="changed" type="valueChange"/>
</af:inputText>
<af:commandButton text="Submit"/>
</af:form>
</af:document>
</f:view>
</jsp:root>



And the test backing bean code, which is request scope looks like:


package test;

import javax.faces.context.FacesContext;

import oracle.adf.view.rich.context.AdfFacesContext;
import oracle.adf.view.rich.render.ClientEvent;

public class Test2Bean
{
public Test2Bean()
{
}

public void doCustomEvent(ClientEvent event)
{
setChanged(true);
AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
adfFacesContext.addPartialTarget(event.getComponent());
FacesContext.getCurrentInstance().renderResponse();
}

public void setChanged(boolean changed)
{
_changed = changed;
}

public boolean isChanged()
{
return _changed;
}
private boolean _changed;

}




Coding Alternative 3


Another option is to call setChanged on the client, which will cause the changed attribute to be set to true on the server, but then we will set changed back to false in the submit button's action listener. Here's the code:


<?xml version='1.0' encoding='utf-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich" version="1.2">
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document id="d1" inlineStyle="padding:20px">
<af:form>
<af:resource type="javascript">
function changed(event) {
var inputComponent = event.getSource();
inputComponent.setChanged(true);
}
</af:resource>
<af:inputText binding="#{test3.input}" label="label">
<af:clientListener method="changed" type="valueChange"/>
</af:inputText>
<af:commandButton text="Submit" actionListener="#{test3.clear}"/>
</af:form>
</af:document>
</f:view>
</jsp:root>




And the test backing bean code, which is request scope looks like:

package test;

import javax.faces.event.ActionEvent;

import oracle.adf.view.rich.component.rich.input.RichInputText;

public class Test3Bean
{
public Test3Bean()
{
}

public void clear(ActionEvent actionEvent)
{
_input.setChanged(false);
}

public void setInput(RichInputText input)
{
_input = input;
}

public RichInputText getInput()
{
return _input;
}

private RichInputText _input;
}

Friday, February 12, 2010

Connect Google Calendar Data to ADF Faces Calendar Front End

Oracle ADF Faces has a calendar component. This blog post discusses how to attach an ADF Faces front end to the google calendar back end. I have written a demo which you can download and run, this demo has been tested in JDev 11.1.1.2.0.


In this blog


Before getting into the details, here's some images to give you an idea of what the demo does. First here's the google calendar:




And here's the running demo, which shows the google data with the adf faces front end:


Running the Demo

The demo was tested using Jdev 11.1.1.2.0

Proxy

If you have a proxy you will need to setup the proxy in jdev:

  • Go to Tools->Preferences
  • Click on "Web Browser and Proxy" on the left pane
  • Check "Use HTTP Proxy Server"
  • Add the host and port number and click ok

JCE

To get google calendar data you will need to upgrade to Java Cryptography Extension (JCE) Unlimited Strength. Without the upgrade I was getting an error like this: java.security.InvalidKeyException: Illegal key size

  • Download JCE Unlimited Strength for jdk 1.6
  • You will find local_policy.jar and US_export_policy.jar in the zip. Copy these two jars to your jdk directory.
    • $JDEV_HOME/jdk160_14_R27.6.5-32/jre/lib/security
    • Note that these jars will already be in the directory, but you should replace them.
    • Also note that when you unzip, the jars are in a directory 'jce', so if you blindly unzip into the jdk directory above, you will add a jce directory rather than replacing the jars, so try unzipping somewhere like the temp directory, and then copy the jars into the jdk directory above, replacing the jars that are already in there.

Getting the Google Jars


You will need some jars from google in order to run the demos. To get these jars go to the google download page and open gdata-src.java-[VERSION].zip. I used version 1.39.0, but the latest version should also work. From the zip get these jars
  • gdata-calendar-2.0.jar
  • gdata-client-1.0.jar
  • gdata-core-1.0.jar
  • google-collect-1.0-rc1.jar
  • jsr305.jar

Opening the Project

  • Download the workspace
  • Open JDeveloper 11.1.1.2.0
  • Go to File -> Open and select "ADFFacesGoogleCalendar.jws"
  • Under "Projects" double click on "Calendar" to open the project properties dialog. Go to "Libraries and Classpath" and click on "Add JAR/Directory", then add the google jars you downloaded in the previous section.
  • go to calendar.view.bean.calendar.Calendar.java and in the _initialize() method change the params "userName" and "userPassword" to the google calendar you want to to see.
  • right click on the "Calendar" project and choose "Rebuild Calendar.jpr"
  • go to calendar.jspx, right click, and choose run

Documentation Links


Here are the docs I used from Google:
.

Oracle ADF Faces Docs


API Overview


Generally speaking here's the mapping of Google api to ADF Faces api:

I'm not going to go into details about the demo, because the blog would be painfully long, but in the code you can look at GoogleCalendarActivity to see how to get and/or set the following information from a Google CalendarEventEntry object (some of these you can only get but not set, ran out of time).

  • time info (and handle timezone issues)
  • id
  • location
  • description
  • all day vs time based
  • readonly
  • recurring
  • reminders

And you can look at GoogleCalendarProvider, which contains a Google CalendarEntry object, for the following

  • getting events - see "Getting Events" section below for more about that
  • get calendar name
  • add and delete an activity
  • toggle showing the calendar on and off
  • change calendar colors

Google Issues


For the most part you can look at the code to see details of how the demo was implemented. However here are the things I wish Google would add or change about their documentation.

Need JCE


As discussed in the JCE section, without upgrading to Java Cryptography Extension (JCE) Unlimited Strength I was getting an error like this:
java.security.InvalidKeyException: Illegal key size

Maybe I missed it, but I didn't see any mention of this in the developer's guide nor on the getting started pages.

See JCE section on what to do if you see this.

Deleting Events


Initially I followed the developer's guide section on deleting events, and ran into this google issue, so just call delete on the CalendarEventEntry itself rather than following the developer guide.

Changing Providers


I couldn't find any doc on how to move an event from one calendar to the other. Maybe this is obvious to everyone else, but this actually took me a minute to figure out, so it would be nice if google could add doc about this. What I did was to take the event and
  • delete it from the current provider (under the covers this is calling delete() on the google CalendarEventEntry)
  • add it to the new provider (under the covers this is calling CalendarService.insert(_eventFeedUrl, calendarEventEntry))

Getting Events


The "Retrieving Events" of the Google Calendar Developer Guide tells you how to create an event feed url. Unfortunately, it only tells you how to do that for the main calendar, it does not tell you how to get the url for secondary calendars.

It doesn't seem possible that getting the event feed url for the calendar entry is this esoteric, but so far the way I've found to get the event feed url is to ask for the link of link relation type"http://schemas.google.com/gCal/2005#eventFeed"


Link eventFeedLink = calendarEntry.getLink("http://schemas.google.com/gCal/2005#eventFeed", null);
_eventFeedUrl = new URL(eventFeedLink.getHref());


The other way I could have created this is to parse the id of the calendar, so the calendar event feed url should be:

http://www.google.com/calendar/feeds/dtonb2emggsuv20o3j95bvup7s%40group.calendar.google.com/private/full


and the calendar id might be:

http://www.google.com/calendar/feeds/user.name%40gmail.com/calendars/dtonb2emggsuv20o3j95bvup7s%40group.calendar.google.com


so to create the event feed url take the id and remove the user name (in this case user.name%40gmail.com) and "/calendars/" from the middle, and then append "/private/full" to the end

If anyone knows an easier way please add a comment to this blog and let me know.