It appears that primefaces <p:tree> is not an EditableValueHolder, even though it offers the ability to make the tree selectable. To me this seems like the very definition of EditableValueHolder as it both holds values (the list of nodes that are selected) and is editable (you can change the selection). In making the tree selectable, it basically turns it into a selectOneXxx/selectManyXxx. This is the fashion in which I use this widget. However, not being an EditableValueHolder, I cannot attach a validator to it directly. I could add validation to the form submission action with an actionListener but then it is out of the appropriate lifecycle phase and is much more difficult to get at the UITree component to check for attributes like the i18n message for failed validation. Has anyone dealt with this before? What do you do?
---------- EDIT ----------
I found an issue posted in the primefaces bug tracker that seems releated:
http://code.google.com/p/primefaces/issues/detail?id=4137
And a forum post:
http://forum.primefaces.org/viewtopic.php?f=3&t=22340
---------- EDIT ----------
This is the solution I came up with. Some of the jQuery is pretty hairy as it uses server side el to generate the client side javascript. But for the most part it works. Just have to figure out why an empty array skips validation... but thats another story.
<h:panelGroup id="pnpCois" styleClass="pnp-input-group pnp-cois">
<h:outputLabel for="inputCois"
value="#{i18n['communities-of-interest']}" />
<p:tree id="inputCois"
value="#{subscriptions.selected.coiTreeRootNode}" var="node"
selectionMode="checkbox"
selection="#{subscriptions.selected.selectedCoiNodes}">
<p:ajax event="select" process="#this :#{component.clientId}_validator" update="#this"
onstart="$('##{component.clientId}_validator'.replace(':','\\:')).val($('##{component.clientId}_selection'.replace(':','\\:')).val());" />
<p:ajax event="unselect" process="#this :#{component.clientId}_validator" update="#this"
onstart="$('##{component.clientId}_validator'.replace(':','\\:')).val($('##{component.clientId}_selection'.replace(':','\\:')).val());" />
<p:treeNode>
<h:outputText value="#{node}" />
</p:treeNode>
</p:tree>
<h:inputHidden id="inputCois_validator">
<f:converter converterId="asias.stringCsvToArray" />
<f:validator validatorId="asias.atLeastOneSelected" />
<f:attribute name="atLeastOneSelectedMessage"
value="#{i18n['at-least-one-coi-must-be-selected']}" />
</h:inputHidden>
</h:panelGroup>
---------- EDIT ----------
After working through some suggestions with BalusC, I think I'm gonna give up on <p:tree> and find another way... :(
You can trick it with a required hidden input field whose value is altered on node click. You can use the selections property of the <p:tree> widget variable to get the available selections as an array.
E.g.
<h:form id="form">
<p:tree widgetVar="tree"
onNodeClick="$('#form\\:treeSelections').val(tree.selections.length != 0 ? 'ok' : '')">
...
</p:tree>
<h:inputHidden id="treeSelections" required="true"
requiredMessage="Please select at least one tree node" />
<p:message for="treeSelections" />
</h:form>
The 'ok' value is purely arbitrary. The point is that the hidden field is filled, so that the required validator doesn't get triggered.
Bear with me, this is a long answer...
Since primefaces tree is not an EditableValueHolder it cannot be validated during the standard process validations phase of the JSF lifecycle (without some major hacking). And to the best of my ability, I was not able to patch the primefaces tree code to make it an EditableValueHolder (the tree does not get rendered according to the selected values, but according to the state of the nodes backing the tree). Given these constraints, the only solutions are create my own tree component (I don't have the time), use a different component (a tree fit best), or validate in the invoke application phase.
I chose the 3rd solution, and in doing so, tried to make it as similar to regular validation as possible. The main idea is, use an actionListener that gets fired first (before any other actionListeners or the main action (save the form) to process validations. If validations failed, I add information about the failure to the component in custom attributes, call facesContext.validationFailed() so I can skip the action, then add a preRenderView system event listener to modify the components according to their validation state before the render response phase. This is done in a fashion that allows you to still specify the validation in the same fashion using a custom component instead of an <f:validator>. Here is the code:
web.xml:
...
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/somenamespace.taglib.xml</param-value>
</context-param>
...
somenamespace.taglib.xml:
<facelet-taglib version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">
<namespace>http://ns.my.com/ui/extensions</namespace>
<tag>
<description><![CDATA[
Add an actionListener validator to a component
]]></description>
<tag-name>actionListenerValidator</tag-name>
<handler-class>com.my.ns.actionlistenervalidator.ActionListenerValidatorHandler</handler-class>
<attribute>
<description><![CDATA[
The validatorId.
]]></description>
<name>validatorId</name>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[
A ValueExpression that evaluates to an instance of Validator.
]]></description>
<name>binding</name>
<type>javax.el.ValueExpression</type>
</attribute>
<attribute>
<description><![CDATA[
The styleClass added to the end of the component style class when a validation error occurs
]]></description>
<name>errorStyleClass</name>
<type>java.lang.String</type>
</attribute>
</tag>
</facelet-taglib>
ActionListenerHandler.java:
package com.my.ns.actionlistenervalidator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributes;
import javax.faces.view.facelets.TagConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.faces.facelets.tag.TagHandlerImpl;
public class ActionListenerValidatorHandler extends TagHandlerImpl {
private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorHandler.class );
public static enum AttributeKeys {
errorStyleClass("hack.jsf.actionlistenervalidator.errorStyleClass"),
messages("hack.jsf.actionlistenervalidator.messages"),
valid("hack.jsf.actionlistenervalidator.valid"),
validators("hack.jsf.actionlistenervalidator.validators");
private String key;
private AttributeKeys( String key ) {
this.key = key;
}
public String getKey() {
return key;
}
}
public ActionListenerValidatorHandler( TagConfig config ) {
super( config );
}
#Override
public void apply( FaceletContext ctx, UIComponent parent ) throws IOException {
ActionListenerValidatorWrapper validator = new ActionListenerValidatorWrapper( ctx.getFacesContext(),
tagAttributesToMap( ctx, this.tag.getAttributes() ) );
logger.trace( "adding actionListener validator {} to {}", validator, parent );
#SuppressWarnings( "unchecked" )
List<ActionListenerValidatorWrapper> validators = (List<ActionListenerValidatorWrapper>) parent.getAttributes().get( AttributeKeys.validators.getKey() );
if ( validators == null ) {
validators = new ArrayList<ActionListenerValidatorWrapper>();
parent.getAttributes().put( AttributeKeys.validators.getKey(), validators );
}
validators.add( validator );
}
private Map<String, Object> tagAttributesToMap( FaceletContext ctx, TagAttributes tagAttributes ) {
Map<String, Object> map = new HashMap<String, Object>();
for ( TagAttribute attribute : tagAttributes.getAll() ) {
map.put( attribute.getLocalName(), attribute.getValue( ctx ) );
}
return map;
}
}
ActionListenerValidatorWrapper.java:
package com.my.ns.actionlistenervalidator;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import javax.faces.view.facelets.FaceletException;
import com.sun.faces.el.ELUtils;
public class ActionListenerValidatorWrapper {
private Validator validator;
private String errorStyleClass;
public ActionListenerValidatorWrapper( FacesContext context, Map<String, Object> attributes ) {
String binding = (String) attributes.get( "binding" );
String validatorId = (String) attributes.get( "validatorId" );
if ( binding != null ) {
ExpressionFactory factory = context.getApplication().getExpressionFactory();
ELContext elContext = context.getELContext();
ValueExpression valueExpression = factory.createValueExpression(
elContext, binding, String.class );
this.validator = (Validator) ELUtils.evaluateValueExpression( valueExpression, context.getELContext() );
}
else if ( validatorId != null ) {
this.validator = context.getApplication().createValidator( validatorId );
this.errorStyleClass = (String) attributes.get( "errorStyleClass" );
// inject all attributes
for ( Method method : validator.getClass().getMethods() ) {
String methodName = method.getName();
Class<?>[] types = method.getParameterTypes();
if ( methodName.startsWith( "set" ) && types.length == 1 ) {
String property = Character.toLowerCase( methodName.charAt( 3 ) ) + methodName.substring( 4 );
if ( attributes.containsKey( property ) ) {
// convert value type
Object value = null;
if ( types[0] == Integer.TYPE ) {
value = intValue( context, attributes.get( property ) );
}
else {
value = attributes.get( property );
}
// call setter
try {
method.invoke( validator, value );
}
catch ( IllegalArgumentException e ) {
throw new FaceletException( e );
}
catch ( IllegalAccessException e ) {
throw new FaceletException( e );
}
catch ( InvocationTargetException e ) {
throw new FaceletException( e );
}
}
}
}
}
else {
throw new FaceletException( "ActionListenerValidator requires either validatorId or binding" );
}
}
#Override
public boolean equals( Object otherObj ) {
if ( !(otherObj instanceof ActionListenerValidatorWrapper) ) {
return false;
}
ActionListenerValidatorWrapper other = (ActionListenerValidatorWrapper) otherObj;
return (this.getValidator().equals( other.getValidator() ))
&& (this.getErrorStyleClass().equals( other.getErrorStyleClass() ));
}
public String getErrorStyleClass() {
return errorStyleClass;
}
public Validator getValidator() {
return validator;
}
#Override
public int hashCode() {
int hashCode = (getValidator().hashCode()
+ getErrorStyleClass().hashCode());
return (hashCode);
}
private Integer intValue( FacesContext context, Object value ) {
ExpressionFactory factory = context.getApplication().getExpressionFactory();
ELContext elContext = context.getELContext();
ValueExpression valueExpression = factory.createValueExpression(
elContext, value.toString(), String.class );
if ( !valueExpression.isLiteralText() ) {
return ((Number) ELUtils.evaluateValueExpression( valueExpression, elContext )).intValue();
}
else {
return Integer.valueOf( valueExpression.getExpressionString() );
}
}
#Override
public String toString() {
return validator.getClass().getName();
}
public void validate( FacesContext context, UIComponent component, Object value ) throws ValidatorException {
validator.validate( context, component, value );
}
}
ActionListenerValidatorManager.java:
package com.my.ns.actionlistenervalidator;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.validator.ValidatorException;
import com.my.ns.controller.MyBean;
import com.my.ns.actionlistenervalidator.ActionListenerValidatorHandler.AttributeKeys;
import org.primefaces.component.tree.Tree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#ManagedBean
#ViewScoped
public class ActionListenerValidatorManager implements Serializable {
private static final long serialVersionUID = -696487579396819893L;
private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorManager.class );
#ManagedProperty( "#{myBean}" )
private MyBean myBean;
private void addValidationToComponent( Map<String, Object> attributes, Collection<FacesMessage> facesMessages, Set<String> errorStyleClasses ) {
attributes.put( AttributeKeys.valid.getKey(), false );
attributes.put( AttributeKeys.messages.getKey(), facesMessages );
StringBuilder builder = new StringBuilder();
if ( errorStyleClasses != null ) {
for ( String styleClass : errorStyleClasses ) {
builder.append( styleClass );
}
attributes.put( AttributeKeys.errorStyleClass.getKey(), builder.toString() );
}
}
public void applyValidationStateToComponentTreeBecausePrimefacesDidntMakeTreeAnEditableValueHolder( ComponentSystemEvent event ) {
applyValidationStateToComponentTree( FacesContext.getCurrentInstance() );
}
private void applyValidationStateToComponentTree( FacesContext context ) {
UIViewRoot viewRoot = context.getViewRoot();
logger.trace( "pre render view for {}", viewRoot );
viewRoot.visitTree( VisitContext.createVisitContext( context ),
new VisitCallback() {
#Override
public VisitResult visit( VisitContext context, UIComponent component ) {
Map<String, Object> attributes = component.getAttributes();
if ( attributes.containsKey( AttributeKeys.valid.getKey() ) &&
!((Boolean) attributes.get( AttributeKeys.valid.getKey() )) ) {
// validation state
if ( component instanceof EditableValueHolder ) {
((EditableValueHolder) component).setValid( false );
}
// validation messages
FacesContext facesContext = context.getFacesContext();
#SuppressWarnings( "unchecked" )
List<FacesMessage> messages = (List<FacesMessage>) attributes.get( AttributeKeys.messages.getKey() );
if ( messages != null ) {
for ( FacesMessage message : messages ) {
facesContext.addMessage( component.getClientId(), message );
}
}
// style class
String errorStyleClass = (String) attributes.get( AttributeKeys.errorStyleClass.getKey() );
if ( errorStyleClass != null ) {
String styleClass = (String) attributes.get( "styleClass" );
styleClass = styleClass == null ? errorStyleClass : styleClass + " " + errorStyleClass;
attributes.put( "styleClass", styleClass );
}
}
return VisitResult.ACCEPT;
}
} );
}
private void clearValidationFromTree( FacesContext context, UIComponent component ) {
component.visitTree( VisitContext.createVisitContext( context ),
new VisitCallback() {
#Override
public VisitResult visit( VisitContext context, UIComponent target ) {
clearValidationFromComponent( target.getAttributes() );
return VisitResult.ACCEPT;
}
} );
}
private void clearValidationFromComponent( Map<String, Object> attributes ) {
if ( attributes.containsKey( AttributeKeys.validators.getKey() ) ) {
String errorStyleClass = (String) attributes.get( AttributeKeys.errorStyleClass.getKey() );
if ( errorStyleClass != null ) {
String styleClass = (String) attributes.get( "styleClass" );
styleClass = styleClass.replace( errorStyleClass, "" );
attributes.put( "styleClass", styleClass );
}
attributes.remove( AttributeKeys.valid.getKey() );
attributes.remove( AttributeKeys.messages.getKey() );
attributes.remove( AttributeKeys.errorStyleClass.getKey() );
}
}
private Object getValue( FacesContext facesContext, UIComponent component ) {
Object value = null;
if ( component instanceof EditableValueHolder ) {
value = ((EditableValueHolder) component).getValue();
}
else if ( component instanceof Tree ) {
value = myBean.getSelectedIds();
}
return value;
}
public void setMyBean( MyBean myBean ) {
this.myBean = myBean;
}
private void validate( FacesContext context ) {
logger.trace( "entering validation" );
final List<String> validationFailed = new ArrayList<String>();
UIViewRoot viewRoot = context.getViewRoot();
viewRoot.visitTree( VisitContext.createVisitContext( context ),
new VisitCallback() {
#Override
public VisitResult visit( VisitContext context, UIComponent component ) {
if ( !component.isRendered() ) {
// remove all validation from subtree as validation
// is not performed unless the component is
// rendered.
clearValidationFromTree( context.getFacesContext(), component );
return VisitResult.REJECT;
}
Map<String, Object> attributes = component.getAttributes();
if ( attributes.containsKey( AttributeKeys.validators.getKey() ) ) {
Object value = getValue( context.getFacesContext(), component );
boolean valid = true;
Collection<FacesMessage> facesMessages = null;
Set<String> errorStyleClasses = null;
#SuppressWarnings( "unchecked" )
List<ActionListenerValidatorWrapper> validators =
(List<ActionListenerValidatorWrapper>) attributes.get( AttributeKeys.validators.getKey() );
for ( ActionListenerValidatorWrapper validator : validators ) {
try {
validator.validate( context.getFacesContext(), component, value );
}
catch ( ValidatorException validatorException ) {
valid = false;
Collection<FacesMessage> innerMessages = validatorException.getFacesMessages();
if ( innerMessages == null ) {
FacesMessage innerMessage = validatorException.getFacesMessage();
if ( innerMessage != null ) {
innerMessages = Arrays.asList( new FacesMessage[] { innerMessage } );
}
}
if ( facesMessages == null ) {
facesMessages = new ArrayList<FacesMessage>();
}
facesMessages.addAll( innerMessages );
String errorStyleClass = validator.getErrorStyleClass();
if ( errorStyleClass != null ) {
if ( errorStyleClasses == null ) {
errorStyleClasses = new TreeSet<String>();
}
errorStyleClasses.add( errorStyleClass );
}
}
}
if ( valid ) {
// remove previous validation state
clearValidationFromComponent( attributes );
}
else {
// add validation state
addValidationToComponent( attributes, facesMessages, errorStyleClasses );
validationFailed.add( "Yes, it did, but cant update final boolean so we use a list" );
}
}
return VisitResult.ACCEPT;
}
} );
if ( validationFailed.size() > 0 ) {
context.validationFailed();
}
}
public void validateThisFormBecausePrimefacesDidntMakeTreeAnEditableValueHolder( ActionEvent event ) {
validate( FacesContext.getCurrentInstance() );
}
}
And finally the page that uses it:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:myns="http://ns.my.com/ui/extensions">
<h:head />
<h:body>
<f:event type="preRenderView"
listener="#{actionListenerValidatorManager.applyValidationStateToComponentTreeBecausePrimefacesDidntMakeTreeAnEditableValueHolder}" />
...
<h:panelGroup id="treeGroup">
<h:outputLabel for="treeInput"
value="#{i18n['my-tree']}" />
<p:tree id="treeInput"
value="#{myBean.treeRootNode}" var="node"
selectionMode="checkbox"
selection="#{myBean.selectedNodes}">
<pe:actionListenerValidator
validatorId="javax.faces.Required"
errorStyleClass="ui-state-error" />
<p:treeNode>
<h:outputText value="#{node}" />
</p:treeNode>
</p:tree>
</h:panelGroup>
...
</h:body>
</html>
I know this is not a cut/paste type answer, but it fully outlines the process. The main benefit of this approach is that it feels the same as standard validation in the way it is used and the way it is processed. Plus it leverages existing validators. If anyone else is stuck using <p:tree> and must validate the selection, I hope this helps...
Related
I migrated from the old Places SDK to the new Places SDK (including writing a new adapter), and now when typing an address into my AutoCompleteTextView it shows only the Place Names in the drop-down list (i.e. addresses but without city, state, country), but I need it to show the full address.
Here is my adapter:
import android.content.Context;
import android.graphics.Typeface;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.libraries.places.api.model.AutocompletePrediction;
import com.google.android.libraries.places.api.model.AutocompleteSessionToken;
import com.google.android.libraries.places.api.model.RectangularBounds;
import com.google.android.libraries.places.api.model.TypeFilter;
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse;
import com.google.android.libraries.places.api.net.PlacesClient;
import java.util.List;
public class PlaceAutocompleteAdapterNew extends ArrayAdapter<AutocompletePrediction> implements Filterable
{
PlacesClient placesClient;
AutocompleteSessionToken token;
private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD);
private List<AutocompletePrediction> mResultList;
private List<AutocompletePrediction> tempResult;
Context context;
private String TAG="PlaceAutoCompleteAdapter";
public PlaceAutocompleteAdapterNew(Context context,PlacesClient placesClient,AutocompleteSessionToken token) {
super(context,android.R.layout.simple_expandable_list_item_1,android.R.id.text1);
this.context=context;
this.placesClient=placesClient;
this.token=token;
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = super.getView(position, convertView, parent);
AutocompletePrediction item = getItem(position);
TextView textView1 = (TextView) row.findViewById(android.R.id.text1);
textView1.setText(item.getPrimaryText(STYLE_BOLD));
return row;
}
#Override
public int getCount() {
return mResultList.size();
}
#Override
public AutocompletePrediction getItem(int position) {
return mResultList.get(position);
}
#Override
public Filter getFilter() {
return new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// Skip the autocomplete query if no constraints are given.
if (constraint != null) {
// Query the autocomplete API for the (constraint) search string.
mResultList = getAutoComplete(constraint);
if (mResultList != null) {
// The API successfully returned results.
results.values = mResultList;
results.count = mResultList.size();
}
}
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
// The API returned at least one result, update the data.
notifyDataSetChanged();
} else {
// The API did not return any results, invalidate the data set.
notifyDataSetInvalidated();
}
}
#Override
public CharSequence convertResultToString(Object resultValue) {
// Override this method to display a readable result in the AutocompleteTextView
// when clicked.
if (resultValue instanceof AutocompletePrediction) {
return ((AutocompletePrediction) resultValue).getFullText(null);
} else {
return super.convertResultToString(resultValue);
}
}
};
}
private List<AutocompletePrediction> getAutoComplete(CharSequence constraint){
// Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,
// and once again when the user makes a selection (for example when calling fetchPlace()).
AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();
// Create a RectangularBounds object.
// Use the builder to create a FindAutocompletePredictionsRequest.
FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
// Call either setLocationBias() OR setLocationRestriction().
//.setLocationBias(bounds)
//.setLocationRestriction(bounds)
.setTypeFilter(TypeFilter.ADDRESS)
.setSessionToken(token)
.setQuery(constraint.toString())
.build();
placesClient.findAutocompletePredictions(request).addOnSuccessListener(new OnSuccessListener<FindAutocompletePredictionsResponse>() {
#Override
public void onSuccess(FindAutocompletePredictionsResponse response) {
for (AutocompletePrediction prediction : response.getAutocompletePredictions()) {
Log.i(TAG, prediction.getPrimaryText(null).toString());
}
tempResult=response.getAutocompletePredictions();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
if (exception instanceof ApiException) {
ApiException apiException = (ApiException) exception;
Log.e(TAG, "Place not found: " + apiException.getStatusCode());
}
}
});
return tempResult;
}
}
How can I show the full addresses in the drop-down list?
getAutoComplete is returning tempResult. This is a List that contains fullText (the full address), primaryText (just the address without city, state, country), and other items. So the fullText is what I want, which is being returned, but the primaryText is what is being displayed in the AutoCompleteTextView. How can I fix this?
I changed this line:
textView1.setText(item.getPrimaryText(STYLE_BOLD));
to this:
textView1.setText(item.getFullText(STYLE_BOLD));
I'm trying to create an HtmlCommandButton programmatically, following the example here
http://javaevangelist.blogspot.ch/2013/01/jsf-21-tip-of-day-programmatically.html
Everything works fine (i.e., the actionListener is called) if I add the ajax behavior, it doesn't work if ajax is turned off.
Backing bean:
#Named
#RequestScoped
public class CommandBean implements Serializable {
public String generateUUID() {
return java.util.UUID.randomUUID().toString();
}
}
Solution 1 (with ajax)
private HtmlCommandButton createCommandButtonWithAjax(final FacesContext context,
final String methodExpression, final String value) {
Application application = context.getApplication();
Class<?>[] clazz = new Class<?>[]{};
HtmlCommandButton htmlCommandButton =
(HtmlCommandButton) application.createComponent(HtmlCommandButton.COMPONENT_TYPE);
htmlCommandButton.setValue(value);
AjaxBehavior ajaxBehavior = (AjaxBehavior) FacesContext.getCurrentInstance().getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
((UIComponentBase)htmlCommandButton).addClientBehavior("click", ajaxBehavior);
MethodExpression actionListener = application.getExpressionFactory().createMethodExpression(FacesContext.getCurrentInstance().getELContext(), action, String.class, clazz);
button.addActionListener(new MethodExpressionActionListener(actionListener));
return htmlCommandButton;
}
Solution 2 (no ajax)
private HtmlCommandButton createCommandButton(final FacesContext context,
final String methodExpression, final String value) {
Application application = context.getApplication();
Class<?>[] clazz = new Class<?>[]{};
HtmlCommandButton htmlCommandButton =
(HtmlCommandButton) application.createComponent(HtmlCommandButton.COMPONENT_TYPE);
htmlCommandButton.setValue(value);
htmlCommandButton.setActionExpression(JSFUtils.createMethodExpression(methodExpression, String.class, clazz));
return htmlCommandButton;
}
Calling code:
createCommandButton(FacesContext.getCurrentInstance(),
"#{commandBean.generateUUID()}", "Generate UUID");
JSFUtils:
public static MethodExpression createMethodExpression(String methodExpression,Class<?> expectedReturnType,Class<?>[] expectedParamTypes) {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().getExpressionFactory()
.createMethodExpression(context.getELContext(), methodExpression, expectedReturnType, expectedParamTypes);
}
Solution 1 is working, solution 2 not: the bean method generateUUID() is not called. I have tried also with htmlCommandButton.setImmediate(true) to exclude validation errors.
Apparently we need a Custom AjaxBehavior, as suggested here:
https://forum.primefaces.org/viewtopic.php?f=3&t=5344 and here
How to programmatically add an AjaxBehavior to a UIComponent with primefaces
Custom Ajax:
import java.util.HashMap;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.BehaviorEvent;
public class MyAjaxBehavior extends AjaxBehavior{
#Override
public Object saveState(FacesContext context) {
HashMap<String, Object> map;
map = new HashMap<String, Object>();
map.put( "update", getUpdate() );
map.put( "process", getProcess() );
map.put( "oncomplete", getOncomplete() );
map.put( "onerror", getOnerror() );
map.put( "onsuccess", getOnsuccess() );
map.put( "onstart", getOnstart() );
map.put( "listener", getListener() );
if (initialStateMarked()) return null;
return UIComponentBase.saveAttachedState(context, map);
}
#SuppressWarnings("unchecked")
#Override
public void restoreState(FacesContext context, Object state) {
if (state != null){
HashMap<String, Object> map;
map = (HashMap<String, Object>) UIComponentBase.restoreAttachedState(context, state);
setUpdate( (String) map.get( "update" ));
setProcess( (String) map.get( "process"));
setOncomplete( (String) map.get( "oncomplete" ));
setOnerror( (String) map.get( "onerror" ));
setOnsuccess( (String) map.get( "onsuccess" ));
setOnstart( (String) map.get( "onstart" ));
setListener( (MethodExpression) map.get( "listener" ));
}
}
#Override
public void broadcast(BehaviorEvent event) throws AbortProcessingException {
ELContext eLContext = FacesContext.getCurrentInstance().getELContext();
//Backward compatible implementation of listener invocation
if(getListener() != null) {
try {
getListener().invoke(eLContext, new Object[]{event});
} catch(IllegalArgumentException exception) {
getListener().invoke(eLContext, new Object[0]);
}
}
}
}
Create Button
private HtmlCommandButton createCommandButtonWithAjax(final FacesContext context,
final String methodExpression, final String value) {
Application application = context.getApplication();
Class<?>[] clazz = new Class<?>[]{};
HtmlCommandButton htmlCommandButton =
(HtmlCommandButton) application.createComponent(HtmlCommandButton.COMPONENT_TYPE);
htmlCommandButton.setValue(value);
addPrimefacesAjaxSupport(htmlCommandButton,"click", methodExpression);
return htmlCommandButton;
}
add AjaxBehavior
private AjaxBehavior addPrimefacesAjaxSupport(UIComponentBase comp, String event, String actionListener){
MyAjaxBehavior ajaxBehavior = new MyAjaxBehavior();
ajaxBehavior.setListener( JSFUtils.createMethodExpression(actionListener, void.class,new Class[]{ ActionEvent.class}) );
ajaxBehavior.setProcess( "#this" );
comp.addClientBehavior( event, ajaxBehavior );
return ajaxBehavior;
}
I have created a custom adapter that extends ArrayAdapter containing Listview with two TextView in a single row. I have SearchView on the ActionBar.In onQueryTextChange(String s) i have tried some methods which i have found, works but result is not correct, in the end i have only first row from ListView. How to make SearchView to work correctly?
Everything else working fine. This app is for minSdkVersion="9" and above. Any suggestion will be OK. Regards.
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.ListView;
import android.widget.TextView;
public class A extends ActionBarActivity{
ListView list;
String[] titl;
String[] opis;
SearchView searchView;
VjuAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
ActionBar aB = getSupportActionBar();
aB.setDisplayHomeAsUpEnabled(true);
Resources res=getResources();
titl=res.getStringArray(R.array.naslov);
opis=res.getStringArray(R.array.podnaslov);
list=(ListView) findViewById(R.id.listView);
VjuAdapter adapter=new VjuAdapter(this, titl, opis);
adapter.getFilter().filter(null);
list.setAdapter(adapter);
list.setTextFilterEnabled(true);
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
finish();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
MenuInflater men = getMenuInflater();
men.inflate(R.menu.main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
MenuItem searchItem = menu.findItem(R.id.trazi);
ComponentName cn = new ComponentName(this, A.class);
searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSearchableInfo(searchManager.getSearchableInfo(cn));
//searchView.setOnQueryTextListener(this);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String arg0) {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean onQueryTextChange(String s) {
// Here i have tried some methods which i have found
// this works but result is not correct, in the end i have
// only first row from ListView
/*if (TextUtils.isEmpty(s)) {
((LayoutInflater) list.getAdapter()).getFilter().filter(s);
} else {
adapter.getFilter().filter(s.toString());
}
return true;
}
});*/
//s.toLowerCase(Locale.getDefault());
VjuAdapter vd = (VjuAdapter)list.getAdapter();
Ll.m("POCETAK VD "+ vd);
Filter filter = vd.getFilter();
Ll.m("SRED VD.GET "+ vd.getFilter());
filter.filter(s);
Ll.m("KRAJ FILTER "+ filter);
/*if (TextUtils.isEmpty(s)) {
list.clearTextFilter();
} else {
//adapter.setSelectionAfterHeaderView();
adapter.getFilter().filter(s.toString());
}*/
return true;
}
});
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
int id = item.getItemId();
/*if (id == R.id.trazi){
onSearchRequested();
}*/
if (id == android.R.id.home){
onBackPressed();
}
if (id == R.id.about){
Intent i = new Intent("com.kanna.sanjarica.ABOUT");
startActivity(i);
}
if (id == R.id.oceni_apl){
Uri uri = Uri.parse("market://details?id=" + getApplicationContext().getPackageName());
Intent goToMarket = new Intent(Intent.ACTION_VIEW, uri);
startActivity(goToMarket);
}
if (id == R.id.kontakt){
String mailTo="kanjah77#gmail.com";
Intent email_intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto",mailTo, null));
email_intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "");
email_intent.putExtra(android.content.Intent.EXTRA_TEXT,"");
startActivity(Intent.createChooser(email_intent, "PoĊĦalji email..."));
}
return true;
}
}
class VjuAdapter extends ArrayAdapter<String>{
Context context;
String [] titlArray;
String [] opisArray;
public VjuAdapter(Context c, String[] naslov, String[] podnaslov ) {
super(c,R.layout.single_row,R.id.textView1,naslov);
this.context=c;
this.titlArray=naslov;
this.opisArray=podnaslov;
}
class MyViewHolder{
TextView textVel;
TextView textMal;
MyViewHolder(View v){
textVel=(TextView) v.findViewById(R.id.textView1);
textMal=(TextView) v.findViewById(R.id.textView2);
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row=convertView;
MyViewHolder holder=null;
if(row==null){
LayoutInflater inf=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row=inf.inflate(R.layout.single_row, parent, false);
holder=new MyViewHolder(row);
row.setTag(holder);
}else{
holder=(MyViewHolder) row.getTag();
}
holder.textVel.setText(titlArray[position]);
holder.textMal.setText(opisArray[position]);
return row;
}
}
This is from http://developer.android.com/:
A concrete BaseAdapter that is backed by an array of arbitrary
objects. By default this class expects that the provided resource id
references a single TextView. If you want to use a more complex
layout, use the constructors that also takes a field id. That field id
should reference a TextView in the larger layout resource.
You have 2 TextView, and constructor class is this:
ArrayAdapter(Context context, int resource, int textViewResourceId)
Since i've asked my last question (which still unanswered) i continued searching for a solution and lastly i found this topic which i think can help achieve what i want.
So , i tried that solution (which itself is a workaround) but it still didn't work for me.
Here is the code, it s all just a test for this issue :
the index.xhtml :
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>dyn add comps test </title>
</h:head>
<body>
<h:form id="form1">
<!-- once this first commandLink is clisked it will generate another commandlink below it -->
<h:commandLink value="cliick me">
<f:ajax event="click" listener="#{myBean.firstcmdLinkListenerHandler}"/>
</h:commandLink>
</h:form>
</body>
</html>
The managed Bean : MyBean.java
package mybeans;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.faces.application.Application;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.BehaviorEvent;
import org.primefaces.component.behavior.ajax.AjaxBehaviorListenerImpl;
import org.primefaces.context.RequestContext;
#ManagedBean
#SessionScoped
public class MyBean {
public void handleClose(AjaxBehaviorEvent abe){
System.out.println("!!!-->>>>> the Ajax Behaviour Works !!!!! ");
}
public void reLoadCityList( BehaviorEvent event ){
System.out.println("!!!-->>>>> the reLoadCityList method Works !!!!! ");
}
public void firstcmdLinkListenerHandler(AjaxBehaviorEvent abe){
System.out.println("firstcmdLinkListenerHandleris running ! ");
FacesContext fc = FacesContext.getCurrentInstance();
Application application = fc.getApplication();
ExpressionFactory ef = fc.getApplication().getExpressionFactory();
UIComponent form1 = fc.getViewRoot().findComponent("form1");
if(form1!=null){
//Creating the commandLink
HtmlCommandLink mynewcmdlink = (HtmlCommandLink)application.createComponent(HtmlCommandLink.COMPONENT_TYPE);
mynewcmdlink.setId("mynewcmdlink");
mynewcmdlink.setValue("clickme2!!");
MyAjaxBehavior pajax = new MyAjaxBehavior();
Class[] par = new Class[1];
par[0] = BehaviorEvent.class;
//par[0] = AjaxBehaviorEvent.class; (*)
//pajax.setListener( myCreateMetExpression( "reLoadCityList", true, //void.class, par ) );
//i tried with both AjaxBehaviorEvent and BehaviorEvent as in (*)
//MethodExpression me = ef.createMethodExpression( //fc.getELContext(), "#{myBean.handleClose}", void.class, par);
MethodExpression me = ef.createMethodExpression( fc.getELContext(), "#{myBean.reLoadCityList}", void.class, par);
//pajax.setListener(me); //i've tried with this too but it wasn't sucesseful
pajax.addAjaxBehaviorListener( new AjaxBehaviorListenerImpl( me ) );
pajax.setProcess( "#this" );
mynewcmdlink.addClientBehavior( "change", pajax );
//adding thecommanLink to the form
form1.getChildren().add(mynewcmdlink);
//Refreshing the form to see the added commandLink :
RequestContext context = RequestContext.getCurrentInstance();
context.update("form1");
context.update("form1:foo");
}else
System.out.println("form1 is null!!");
}
and the MyAjaxBehavior.java used as the workaround in the primefaces forum article:
package mybeans;
import java.util.HashMap;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.BehaviorEvent;
import org.primefaces.component.behavior.ajax.AjaxBehavior;
public class MyAjaxBehavior extends AjaxBehavior {
#Override
public Object saveState(FacesContext context) {
HashMap<String, Object> map;
map = new HashMap<String, Object>();
map.put("update", getUpdate());
map.put("process", getProcess());
map.put("oncomplete", getOncomplete());
map.put("onerror", getOnerror());
map.put("onsuccess", getOnsuccess());
map.put("onstart", getOnstart());
map.put("listener", getListener());
if (initialStateMarked())
return null;
return UIComponentBase.saveAttachedState(context, map);
}
#SuppressWarnings("unchecked")
#Override
public void restoreState(FacesContext context, Object state) {
if (state != null) {
HashMap<String, Object> map;
map = (HashMap<String, Object>) UIComponentBase
.restoreAttachedState(context, state);
setUpdate((String) map.get("update"));
setProcess((String) map.get("process"));
setOncomplete((String) map.get("oncomplete"));
setOnerror((String) map.get("onerror"));
setOnsuccess((String) map.get("onsuccess"));
setOnstart((String) map.get("onstart"));
setListener((MethodExpression) map.get("listener"));
}
}
#Override
public void broadcast(BehaviorEvent event) throws AbortProcessingException {
ELContext eLContext = FacesContext.getCurrentInstance().getELContext();
// Backward compatible implementation of listener invocation
if (getListener() != null) {
try {
getListener().invoke(eLContext, new Object[] { event });
} catch (IllegalArgumentException exception) {
getListener().invoke(eLContext, new Object[0]);
}
}
}
}
Do something like this
Panel tp = new Panel();
FacesContext fc = FacesContext.getCurrentInstance();
ExpressionFactory ef = fc.getApplication().getExpressionFactory();
MethodExpression me = ef.createMethodExpression(fc.getELContext(), "#{myView.closeIt}", null, new Class<?>[]{BehaviorEvent.class});
AjaxBehavior ajaxBehavior = (AjaxBehavior) fc.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
ajaxBehavior.setProcess("#this");
ajaxBehavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(me, me));
tp.addClientBehavior("close", ajaxBehavior);
component.getChildren().add(tp);
myView.closeIt()
public void closeIt(CloseEvent ce){
Panel p = (Panel) ce.getComponent();
System.out.println("Do what ever you want");
}
I have a requirement to create a javascript function that when invoked will save all of the valid components on a JSF 2.0 form. Since the complete form will never be valid as a whole I need to figure out a way to run the lifecycle per component so that if the validation phase is successful the model will be updated and eventually saved.
Ideally, this needs to be a single ajax request as iterating over each component with a separate ajax request would be painfully inefficient.
Has anyone solved the problem before? If not could you give me some pointers on possible implementations?
Edit:
Here's what I have that seems to be working well so far:
#ManagedBean(name = "partialAppSaveBean")
#RequestScoped
public class PartialAppSaveBean implements Serializable {
protected static final Logger LOGGER = LoggerFactory.getLogger(PartialAppSaveBean.class);
private static final long serialVersionUID = 1L;
/**
* Save any valid Application values
*
* #param event
*/
public void saveData(AjaxBehaviorEvent event) {
final FacesContext context = FacesContext.getCurrentInstance();
UIForm form = getParentForm(event.getComponent());
Set<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
form.visitTree(VisitContext.createVisitContext(context, null, hints), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
input.validate(context.getFacesContext());
if (input.isValid() && input.getValue() != null) {
ValueExpression valueExpression = input.getValueExpression("value");
if (valueExpression != null
&& valueExpression.getExpressionString() != null) {
try {
valueExpression.setValue(context.getFacesContext().getELContext(), input.getValue());
} catch (Exception ex) {
LOGGER.error("Expression [ " + valueExpression.getExpressionString() +
"] value could not be set with value [" + input.getValue() + "]", ex);
}
}
}
}
return VisitResult.ACCEPT;
}
});
//Save data here
}
/**
* Returns the parent form for this UIComponent
*
* #param component
* #return form
*/
private static UIForm getParentForm(UIComponent component) {
while (component.getParent() != null) {
if (component.getParent() instanceof UIForm) {
return (UIForm) component.getParent();
} else {
return getParentForm(component.getParent());
}
}
return null;
}
}
Invoked with something like:
<h:commandButton
id="saveData">
<f:ajax listener="#{partialAppSaveBean.saveData}" execute="#form" immediate="true" onevent="onPartialSave" />
</h:commandButton>
You could use UIComponent#visitTree() on the UIForm.
FacesContext context = FacesContext.getCurrentInstance();
UIForm form = getFormSomehow();
Map<String, Object> validValues = new HashMap<String, Object>();
Set<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
form.visitTree(VisitContext.createVisitContext(context, null, hints), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
if (input.isValid()) {
validValues.put(input.getClientId(context.getFacesContext()), input.getValue());
}
}
return VisitResult.ACCEPT;
}
});