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 JSR303–Bean Validation. Sino que hay que aplicarlas programáticamente.
Vamos a ver esto en un nuevo artículo porque éste ya está kilométrico.