Archive for jsf

Acción por defecto de un form JSF

Al rellenar un formulario, si le das al enter cuando el foco está en uno de los input, el fomulario se envía.

En la mayoría de los casos esto está bien, ¿pero qué ocurre si queremos modificar ese comportamiento?
Concretamente, ¿como podemos modificar ese comportamiento si trabajamos con JSF? Lamentablemente, la especificación no contempla este caso.

Si estás utilizando Seam, esto lo puedes hacer con la etiqueta <s:defaultAction>, que permite cambiar la acción que se ejecuta por defecto al presionar el enter. Si no estás utilizando Seam, puedes usar la libreria javascript4jsf , y su tag <j4j:defaultAction>, que tiene el mismo comportamiento.

Pero si quieres algo rápido, lo más sencillo es incluir un h:commandButton oculto y desactivado. Así al presionar el enter, lanzará la acción del commandButton en vez de enviar el form.

<h :commandButton action="#{mi.accion}" style="display:none;" disabled="true" />

Comments (2)

Cómo limitar el numero de caracteres que muestra un h:outputText

En alguna ocasión, trabajando con JSF, he necesitado limitar el número de caracteres que se muestran en un elemento de salida de texto, como un outuputText.Una forma fácil de hacerlo es implementar un Converter personalizado.

En primer lugar necesitas declarar una clase que implemente la interfaz javax.faces.convert.Converter e implementar los métodos getAsObject y getAsString.
Aquí tienes la implementación del Converter: StringLimiterConverter

package es.neodoo.control.jsf;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class StringLimiterConverter implements Converter {
    private static final String LIMIT_PARAMETER_NAME = "limit";
    private static final int DEFAULT_LIMIT = 5;

    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        return limit(value, getLimitAttribute(component));
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (! (value instanceof String))
            return null;
        else {
            return limit(value.toString(), getLimitAttribute(component));
        }
    }

    private int getLimitAttribute(UIComponent component) {
        Object att = component.getAttributes().get(LIMIT_PARAMETER_NAME);
        if (att == null)
            return DEFAULT_LIMIT;
        else
            return Integer.parseInt((String)component.getAttributes().get(LIMIT_PARAMETER_NAME));
    }

    private String limit(String s, int limit) {
        String limited = s;
        if (! (s.length() &lt;= limit))
            limited = s.substring(0, limit);
        return limited;
    }
}

Después registra el converter en el archivo faces-config.xml

<faces -config>
          <converter>
               </converter><converter -id>stringLimiterConverter</converter>
               <converter -class>tes.jsf.converter.StringLimiterConverter</converter>
         
     </faces>

Y ya puedes usarlo a través de la etiqueta de JSF

<h :o utputText value="En un lugar de la Mancha...">
          <f :converter converterId="stringLimiterConverter" />
          <f :attribute name="limit" value="6" />
     </h>

Con el atributo <f:attribute name="limit" value="6" /> podemos indicarle el número de caracteres que queremos que nos muestre

Leave a Comment

Cómo iterar sobre un Set en un dataTable

El otro día iba a crear un h:dataTable con los valores almacenados en un java.util.Set, y me llevé una sopresa al comprobar que no se podía.

La razón es que el componente UIData (que es vital para el funcionamiento de las etiquetas h:dataTable y ui:repeat entre otras ) está basado en índices numéricos, al igual que la interfaz java.util.List.
La interfaz java.util.Set está basada en parejas de elementos clave/valor, por lo que no se lleva bien con el componente UIData.

Una solución para poder usar nuestro Set con dataTables es convertirlo previamenta a un List.
Para ello podemos usar un ELResolver. Ya expliqué cómo hacerlo en otro post

Leave a Comment

Cómo implementar un ELResolver personalizado

El Expression Language de JSF nos permite acceder a propiedades de los objetos siempre que estas esten disponibles mediante getters.

Esto está muy bien, pero en ocasiones lo que queremos obtener es un objeto que solo está accesible a través de un método que no es un getter, como por ejemplo el tamaño de una java.util.Collection disponible a través de su método collection.size(), o los valores de un java.util.Map disponibles a través del método values().

Para ello podemos realizar nuestra propia implementación de la clase javax.el.ELResolver.
Esta clase es la encargada de evaluar las ELs (las expresiones que van entre #{}) y realizar las acciones correspondientes.
Sustiye a los antiguos PropertyResolver y VariableResolver, que están marcados como obsoletos (deprecated) en la especificación 1.2 de JSF (que es la usada en la versión 5 de Java EE).

Para usar nuestro propio ELResolver solo deboemos seguir tres sencillos pasos:

  • 1) Implementar nuestra clase que extienda de javax.el.ELResolver
    Puedes descargarla aquí: MyELResolver
    Necesitarás añadir al classpath de compilación el archivo el-api.jar
  • package tes.jsf;

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;

    import javax.el.ELContext;

    public class MyELResolver extends javax.el.ELResolver {

        @Override
        public Class getCommonPropertyType(ELContext context, Object base) {
            return null;
        }

        @Override
        public Iterator getFeatureDescriptors(ELContext context, Object base) {
            return null;
        }

        @Override
        public Class getType(ELContext context, Object base, Object property) {
            return null;
        }

        @Override
        public Object getValue(ELContext context, Object base, Object property) {
            if (base instanceof Collection) {
                return resolveInCollection(context, (Collection)base, property);
            } else if (base instanceof Map) {
                return resolveInMap(context, (Map)base, property);
            } else {
                return null;
            }
        }

        @Override
        public boolean isReadOnly(ELContext arg0, Object arg1, Object arg2) {
            return true;
        }

        @Override
        public void setValue(ELContext arg0, Object arg1, Object arg2, Object arg3) {
        }

        private Object resolveInCollection(ELContext context, Collection base, Object property) {
            if (property.equals("size")) {
                context.setPropertyResolved(true);
                return base.size();
            } else if (property.equals("toMap")) {
                context.setPropertyResolved(true);
                return collectionToMap(base);
            } else if (property.equals("toList")) {
                context.setPropertyResolved(true);
                return new ArrayList(base);
            }else {
                return null;
            }
        }

        private Object resolveInMap(ELContext context, Map base, Object property) {
            if (property.equals("size")) {
                context.setPropertyResolved(true);
                return base.size();
            } else if (property.equals("values")) {
                context.setPropertyResolved(true);
                return base.values();
            } else if (property.equals("keySet")) {
                context.setPropertyResolved(true);
                return base.keySet();
            } else if (property.equals("entrySet")) {
                context.setPropertyResolved(true);
                return base.entrySet();
            } else {
                return null;
            }
        }

        private Map<object> collectionToMap(Collection col) {
            Map</object><object> map = new HashMap</object><object>();
            for (Object obj : col) {
                map.put(obj, obj);
            }
            return map;
        }
    }

  • 2) Declarar el ELResolver en el archivo faces-config.xml
    La declaración debe hacerse dentro de la seccion <application>.
    <el-resolver>tes.jsf.MyELResolver</el-resolver>
  • 3) Usar las nuevas posibilidades del Language Expression
    • El tamaño de mi Collection es: #{myCollection.size}
    • Puedes usarlo para rellenar tus combos:
    •      <h:selectOneMenu>
                <f:selectItems value="#{myCollection.toMap}" />
           <h:selectOneMenu>
    • Para asociar tu java.util.Set con un dataTable
           <h:dataTable value="#{mySet.toList}" var="entry">
                ...
           </h:dataTable>
    • Para iterar sobre las entradas de un Map
    •      <ui:repeat value="#{myMap.entrySet.toList}" var="entry">
                #{entry.key} : #{entry.value} <br />
           </ui:repeat>
    • Y para todo lo que se te ocurra añadir

Comments (3)