Thursday, February 25, 2010

Use ice:commandButton to update UI fields bypassing validation

How to trigger an action that refreshes or updates ui input components bypassing JSF validation? Suppose you have a form with address fields where data can be entered by hand or copied from somewhere (a previously entered screen maybe) at the click of a button. You'd usually want to validate/convert these fields before data gets to its final destination in the backing bean.

You can use a command button to trigger a bean action that would copy data but the problem with this approach is that if there are errors (for example, if the user clicked Save with empty fields) on the screen JSF will prevent the button from submitting. You might try to set the button's immediate attribute to true which will force JSF to skip validation and ignore errors on UI components, but it will also mean that changes to data are not transferred to DOM and hence aren't seen by ICEfaces - you will see the action being triggered but the UI will not reflect the changes.

Using the immediate attribute is the simplest way to bypass validation but we need a way to get the new values from action to the UI. The simple trick is to bind the container where the UI data components reside to a backing bean property and clear it's children (the UI components) in the action method. This forces ICEfaces to fetch all data to rebuild components at which point it will see the changed values.

In short: make the command button immediate and bind the panel grid containing UI components with data to a backing bean property. In the action triggered by the button clear the grid to force ICEfaces to restore data. Here are the code snippets:


JSPX: put a container - a panel grid in this case - with ui fields:

<ice:panelGrid columns="4" binding = "#{backingBean.panelGrid}">
<!-- Add input components inside the grid>
</ice:panelGrid>


Add a command button:

<ice:commandButton value = "Copy Address"
immediate = "true"
action = "#{backingBean.copyAddress}"
actionListener="#{backingBean.copyAddressEvent}" />


Backing bean:

//Bind the grid to HtmlPanelGrid
public HtmlPanelGrid getPanelGrid() {
return panelGrid;
}

public void setPanelGrid(HtmlPanelGrid panelGrid) {
this.panelGrid = panelGrid;
}

//Do data updates and clear the grid
public void copyAddress(ActionEvent ae) {
thisAddress.streetAddress = otherAddress.streetAddress;
thisAddress.city = otherAddress.city;
getPanelGrid().getChildren().clear();
}

Friday, February 5, 2010

ice:panelCollapsible header styling issues

Had a problem this week with styling nested ICEfaces collapsible panels. (ice:panelCollapsible): the top panel would be styled, but the children's headers didn't get the neat open/close buttons from the default rime stylesheet. Custom styles wouldn't get recognized either.
After hours of digging around in CSS and ICE forums, I finally realized (thanks to the JIRA 3209) that the problem was not with the style, but with the layout. The content of header must be contained in a <div> (or an ice:panelGroup), otherwise the style doesn't get recognized. It doesn't have anything to do with nesting, I just happened to copy the top panel from elsewhere, and didn't pay attention to the wrapping panelGroup.


<ice:panelCollapsible expanded="true">
<f:facet name="header">
<div>
<ice:outputText value = "Grocery Panel"> </ice:outputText>
</div>
</f:facet>
<ice:panelGrid columns = "1">
<ice:outputText value = "Food stuffs"> </ice:outputText>
</ice:panelGrid>
<!-- Nested panel 1 -->
<ice:panelCollapsible>
<f:facet name="header">
<ice:panelGroup>
<ice:outputText value = "Fruit Panel"> </ice:outputText>
</ice:panelGroup>
</f:facet>
<ice:panelGrid columns = "2">
<ice:outputText value = "Apple"> </ice:outputText>
<ice:outputText value = "Orange"> </ice:outputText>
</ice:panelGrid>
</ice:panelCollapsible>
<!-- Nested panel 2 -->
<ice:panelCollapsible>
<f:facet name="header">
<ice:panelGroup>
<ice:outputText value = "Vegetable Panel"> </ice:outputText>
</ice:panelGroup>
</f:facet>
<ice:panelGrid columns = "2">
<ice:outputText value = "Cucumber"> </ice:outputText>
<ice:outputText value = "Potato"> </ice:outputText>
</ice:panelGrid>
</ice:panelCollapsible>
</ice:panelCollapsible>


Dev environment: ICEfaces 1.8.2, MyEclipse 7.5, Liferay 5.2.EE

Friday, January 8, 2010

Create new eclipse workspace with old settings

How to create a new eclipse workspace with the old settings:

From "http://eclipse.dzone.com/news/create-new-eclipse-workspace-w


"The short answer

All settings are stored in the .metadata/.plugins/org.eclipse.core.runtime/.settings directory. I mean -- all relevant settings. If you look into .metadata/.plugins directory there are many more directories with settings, but they are too project specific. I've walked trough these configuration files one by one, believe me, nothing useful lies hidden there.

So the short answer is: If you want to create a new eclipse workspace and preserve all your settings, simply copy the .metadata/.plugins/org.eclipse.core.runtime/.settings directory into your new workspace directory.

Do not copy other directories! There are project specific settings and since your old projects are left in your old workspace, the copied settings would not be valid and you would get some nasty exceptions at eclipse startup."

Tuesday, June 2, 2009

Nested JSF DataTable - Refresh issue and workaround

Nested dataTables - JSF refresh issue

Nested dataTables can be very useful in iterating over nested collections, but unfortunately JSF has a bug that prevents the nested table from being updated in certain cases. If you see a discrepancy in the nested table this fix might be what you need.

We use a dataTable to iterate over configurations stored in a list inside which each object nests another list. An ice:dataTable component is used to display a collection on the UI and the user has an option of adding new rows to the table. New rows are added without a page refresh, using ICEfaces Ajax capabilities.
We saw the refresh problem when the nested table had a drop-down component which seemed to be copied from the row that was previously in the position of the added row. Fortunately there is a quick work around to fix the issue.
The basic idea is to bind the parent data table to a backing bean component and then force a refresh when a new row is added. This is how you can do it:

1. Create component property in the backing bean:

private UIData tableBinding;
public UIData getTableBinding() {
return tableBinding;
}

public void setTableBinding(UIData tableBinding) {
this.tableBinding = tableBinding;
}


2. Force refresh in the action method by clearing the children of component:


public void processChanges(){
getUIBean().getTableBinding().getChildren().clear();
}


3. Bind the parent data table in jspx:

< ice:datatable id="parentTable" binding = "#{uiBean.tableBinding}">
</ice:dateTable>
-----
Dev environment: ICEfaces 1.8, MyEclipse 6.5, Liferay 5.1.2

Wednesday, July 9, 2008

Dev environment

IDE: MyEclipse 6.x
Liferay/JBoss server attachment JDK arguments to increase P: -Xms128m -Xmx512m -XX:MaxPermSize=128m -ea
- use separate installation jdk 5 instead of the one that comes with MyEclipse by default.
* If Server not shown under MyEclipse\Servers go to Preferences-> General -> Capabilities and add the server

Wednesday, April 23, 2008

JSF ValueChangeListeners

This is yet another post about JSF value change listeners and how they don't work as expected, intuitively, or logically. As I thought about it I couldn't come up with a real-life scenario when their behaviour would be justified or would make sense.

JSF Value Change Events are called after the Apply Request Values phase which updates the component tree with the request values from the client. For the framework developers (I assume) this is a logical place for it in the lifecycle because it's easy to know what values have changed.

Because they allow you to trigger logic that depends on changed values, the value change events are often used to update something in the model based on the change. For example, suppose you have a page with a country and state/province drop downs and you want to update the list of states/provinces when the user picks a country. So you've bound the values in the drop downs to some backing bean fields, and the selectItems to Lists in backing beans, and then attach a value change listener to the country drop down. In the listener method you put some logic to update the backing bean List of states/provinces. Now you expect the updated list to show up when the page is redisplayed.

But wait. Your listener method will get called after the Apply Request Values phase, at which point the component tree will have the new value but the backing bean still
won't. You can still get the new value out of the event (by calling event.getNewValue()), but that's not the worst of it. If you update the backing bean's list of states/provinces now it will still be overwritten when the lifecycle moves on to Update Model Values phase.

There are plenty of hacks to this problem, and none of them particularly graceful. They include: binding the component you want to update and setting it's submittedValue() - this can cause validation exceptions for combo boxes, and other problems for inputTexts; updating temporary variables that aren't value-bound and then updating the actual binding in other application logic; forwarding the lifecycle to the Render Response phase - not a good idea to circumvent the lifecycle, especially if using ICEfaces; using a phase listener and queuing functions to call at each phase; and, finally, moving the listener method to the appropriate phase. The last solution is the cleanest one I found so far (see References for source). I won't elaborate on the others - they are generally confusing and cause a trail of hacks to patch the holes created by them.

The solution is simple: you want the listener method to be called after the model values are updated? - so move the event to the Update Model Values phase. The value change event allows you to change its PhaseId to whatever you want and re-queue it to be called again. You can check for when the phase is reached and do your application logic using the backing bean values:

public void countryChangedVCE(ValueChangeEvent vce) {
if(vce.getPhaseId().equals(PhaseId.ANY_PHASE)){
vce.setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
vce.queue();
}
else if(vce.getPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES)){
//Your application logic here:
//get the backing bean and update the state/province list based on the
//country value
}
}


References:
Value Change Listeners - What you need to know
 
1.