Este caso es interesante.

Tenemos un texto en base de datos donde definimos ciertos ‘tags‘ que deben ser reemplazados por cajas de texto, con valores pre-llenados. Algo así como un output mezclado con el input, mezcla que puede tomar cualquier orden.

Esto significa parsear estos tags en tiempo de ejecución (runtime) y convertirlos en inputs los cuales, sobre todo, deben seguir aún ligados al contexto de JSF, de modo que sus valores puedan ser modificados – la idea es submitirlos en el próximo request.

  • Podríamos pensar que bastaría con usar una expresión regular >>, la cual desde java reemplacemos con los inputs y sus valores
    public String parsear(String texto){
    return clausula.replaceAll("\\[[a-zA-Z]*\\]", "<input type=\"text\" />");
    }
    

    Lo cual es escapado por JSF, por lo que hay que especificarle escape=»false»

    <h:outputText value="#{bean.parsear(texto)}" escape="false">;
    

    Si bien esto sirve para dibuar los inputs, estos no estarán ligados a jsf, pues si como value les damos un EL (#{..}) éste queda tal cual. De hecho, al input le falta el name=»formulario:0:id» que le da Mojarra y que es el que usa para obtener su valor, etc.

    <input type="text" name="j_idt73:0:j_idt77">
    
  • Lo que hay que hacer es manejar los UIComponent directo desde Java >>. Así, debiera poder declarar en mi clase
    import javax.faces.component.html.HtmlPanelGroup;
    //...
    private HtmlPanelGroup panel;
    
    @PostConstruct
    private void init(){
    panel.getChildren.add(
    //le agrego los output e input
    );
    }
    

    y ligarlo desde JSF

    <h:panelGroup binding="#{bean.panel}">
    
  • Ahora, al hacer el binding, ¿el componente es instanciado por JSF? Porque de hecho incluso la cantidad de paneles que voy a necesitar es variable. Y si intento hacer el binding dinámico con algo como esto
    <ui:repeat var="x" value="#{bean.paneles}" iterationStatusVar="i">
    <h:panelGroup binding="#{x[i.index]}"/>
    </ui:repeat>
    

    se cae con un
    javax.el.PropertyNotFoundException: Objetivo inalcanzable, identificador 'x' resuelto a nulo (Nota. Ahora, esto podría haberse debido a la notación [ ] pues mis paneles eran una lista enlazada y no un arreglo)

  • En todo caso lo más sencillo para resolver esto último es manejar un solo binding, y a éste colgarle un número variable de hijos (de hecho estudiar el componente de Seam para ello >>)
    <h:panelGrid binding="#{bean.grid}"/>
    

    y en java

    private HtmlPanelGrid grid; //tabla
    
    @PostConstruct
    public void init(){
    this.grilla = new HtmlPanelGrid(); //es necesario inicializarlo
    for(MiTexto texto : this.getTextos()){
    HtmlPanelGroup panel = new HtmlPanelGroup();
    // les doy contenido a cada div
    this.grid.getChildren().add(panel);
    }
    }
    /**
    * getters & setters de grid, etc.
    *
    
  • Ahora, para enlazar los inputs con sus valores en el bean, a estos objetos podemos setearles su ValueExpression (javadoc), para esto antes había que instanciar el FacesContext, extraerle Application y a éste el ExpressionFactory, etc.
    Por supuesto que Seam simplifica esto con inyección de dependencias >>:

    @Inject Expressions expressions;
    
    //en el método {
    HtmlInputText inputText = new HtmlInputText();
    ELContext ctx = expressions.getELContext();
    ValueExpression el = expressions.getExpressionFactory().createValueExpression(ctx, "#{bean.propiedad}", Object.class); //Object es el tipo de propiedad
    input.setValueExpression("value", expresion);
    
  • Nótese que Expressions es un Managed Bean no serializable, por lo que si lo estamos inyectando en un bean con scope passivating (cuyo estado JSF guarda entre requests, como SessionScoped o ConversationScoped) habrá que marcar el punto de inyección como transiente.
    @Inject
    private transient Expressions expressions;
    
  • Cuidado que al instanciar nuestros UIComponents, no están realmente siendo cargados por la aplicación, de modo que si necesitan de librerías extra como los javascript de RichFaces, no funcionarán a cabalidad >>
    (Un workaround charcha puede ser meter un calendario invisible en el xhtml

    <rich:calendar rendered="false"/>
    

    así se importan los scripts…)

  • Según este post >> es que no hay que instanciar los componentes con new sino con
    @Inject
    private Application aplicacion;
    UICalendar calendar = (UICalendar)aplicacion.createComponent(UICalendar.COMPONENT_TYPE);
    

    (javadoc)

  • Además de esto, hay que darle el rendererType correcto a la sobrecarga de createComponent >>
    application.createComponent(facesContext, UICalendar.COMPONENT_TYPE, "org.richfaces.CalendarRenderer");
    

    ya que esta clase es la encargada de cargar los scripts y estilos.

  • Otro punto importante a considerar es que cuando un componente se crea programáticamente, no se aplican a él las validaciones por anotaciones >> según JSR303Bean Validation. Sino que hay que aplicarlas programáticamente.
    Vamos a ver esto en un nuevo artículo porque éste ya está kilométrico.
Este sitio utiliza cookies.    Leer más
Privacidad