template tag plantillas page define ajax jsf jsf-2 navigation uiinclude

ajax - tag - ui define jsf



Navegación dinámica ajax con<ui: incluir> (1)

Supongamos que quiero navegar en mi aplicación e incluir diferentes páginas facelet dinámicamente. Tengo un comando Vínculo como este:

<h:commandLink value="Link" action="#{navigation.goTo(''someTest'')}"> <f:ajax render=":content" /> </h:commandLink>

Y aquí es donde incluyo el facelet:

<h:form id="content"> <ui:include src="#{navigation.includePath}" /> </h:form>

La clase de navegación:

public class Navigation { private String viewName; public void goTo(String viewName) { this.viewName = viewName; } public String getIncludePath() { return resolvePath(viewName); } }

He visto ejemplos similares, pero esto no funciona, por supuesto. Como ui:include es un gestor de etiquetas, la inclusión ocurre mucho antes de que se invoca mi oyente de navegación. El viejo facelet está incluido, en lugar del nuevo. Hasta ahora lo entiendo.

Ahora a la parte del dolor de cabeza: ¿cómo puedo incluir dinámicamente un facelet, basado en un ActionListener? Intenté incluir el facelet en un evento preRender y un phaseListener antes de RENDER_RESPONSE. Ambos funcionan, pero en el caso de que el oyente no pueda incluir un facelet que contenga otro evento preRender, y en el phaseListener obtengo identificaciones duplicadas después de algunos clics en el facelet incluido. Sin embargo, la inspección del árbol de componentes me dice que no hay componentes duplicados. Quizás estas dos ideas no fueron para nada buenas ...

Necesito una solución, donde la página con ui:include , o la clase Java que incluye el facelet, no tiene que conocer las páginas, que se incluirán, ni la ruta exacta. ¿Alguien resolvió este problema antes? ¿Cómo puedo hacerlo?

Estoy usando JSF 2.1 y Mojarra 2.1.15

Todo lo que necesita para reproducir el problema es este frijol:

@Named public class Some implements Serializable { private static final long serialVersionUID = 1L; private final List<String> values = new ArrayList<String>(); public Some() { values.add("test"); } public void setInclude(String include) { } public List<String> getValues() { return values; } }

Esto en su archivo de índice:

<h:head> <h:outputScript library="javax.faces" name="jsf.js" /> </h:head> <h:body> <h:form id="topform"> <h:panelGroup id="container"> <my:include src="/test.xhtml" /> </h:panelGroup> </h:form> </h:body>

Y esto en text.xhtml

<ui:repeat value="#{some.values}" var="val"> <h:commandLink value="#{val}" action="#{some.setInclude(val)}"> <f:ajax render=":topform:container" /> </h:commandLink> </ui:repeat>

Eso es suficiente para producir un error como este:

javax.faces.FacesException: Cannot add the same component twice: topform:j_id-549384541_7e08d92c


Para OmniFaces , también he experimentado con esto creando un <o:include> como UIComponent lugar de un TagHandler que hace un FaceletContext#includeFacelet() en el método encodeChildren() . De esta forma, el recuadro derecho incluido se recuerda durante la fase de vista de restauración y el árbol de componentes incluido solo cambia durante la fase de respuesta al renderizado, que es exactamente lo que queremos lograr con este constructo.

Aquí hay un ejemplo básico de inicio:

@FacesComponent("com.example.Include") public class Include extends UIComponentBase { @Override public String getFamily() { return "com.example.Include"; } @Override public boolean getRendersChildren() { return true; } @Override public void encodeChildren(FacesContext context) throws IOException { getChildren().clear(); ((FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY)).includeFacelet(this, getSrc()); super.encodeChildren(context); } public String getSrc() { return (String) getStateHelper().eval("src"); } public void setSrc(String src) { getStateHelper().put("src", src); } }

Que está registrado en .taglib.xml siguiente manera:

<tag> <tag-name>include</tag-name> <component> <component-type>com.example.Include</component-type> </component> <attribute> <name>src</name> <required>true</required> <type>java.lang.String</type> </attribute> </tag>

Esto funciona bien con la siguiente vista:

<h:outputScript name="fixViewState.js" /> <h:form> <ui:repeat value="#{includeBean.includes}" var="include"> <h:commandButton value="Include #{include}" action="#{includeBean.setInclude(include)}"> <f:ajax render=":include" /> </h:commandButton> </ui:repeat> </h:form> <h:panelGroup id="include"> <my:include src="#{includeBean.include}.xhtml" /> </h:panelGroup>

Y el siguiente bean de respaldo:

@ManagedBean @ViewScoped public class IncludeBean implements Serializable { private List<String> includes = Arrays.asList("include1", "include2", "include3"); private String include = includes.get(0); private List<String> getIncludes() { return includes; } public void setInclude(String include) { return this.include = include; } public String getInclude() { return include; } }

(este ejemplo espera incluir archivos include1.xhtml , include2.xhtml e include3.xhtml en la misma carpeta base que el archivo principal)

El fixViewState.js se puede encontrar en esta respuesta: h: commandButton / h: commandLink no funciona con el primer clic, solo funciona con un segundo clic . Esta secuencia de comandos es obligatoria para solucionar el problema JSF 790 por el cual se pierde el estado de la vista cuando hay múltiples formas ajax que actualizan el padre de la otra.

También tenga en cuenta que de esta forma cada archivo incluido puede tener su propia <h:form> cuando sea necesario, por lo que no necesariamente tiene que ponerlo en el include.

Este enfoque funciona bien en Mojarra, incluso con solicitudes de devolución de datos provenientes de formularios dentro de la inclusión, sin embargo, falla mucho en MyFaces con la siguiente excepción durante la solicitud inicial:

java.lang.NullPointerException at org.apache.myfaces.view.facelets.impl.FaceletCompositionContextImpl.generateUniqueId(FaceletCompositionContextImpl.java:910) at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.generateUniqueId(DefaultFaceletContext.java:321) at org.apache.myfaces.view.facelets.compiler.UIInstructionHandler.apply(UIInstructionHandler.java:87) at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:49) at org.apache.myfaces.view.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:158) at org.apache.myfaces.view.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:57) at org.apache.myfaces.view.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:48) at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:394) at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:448) at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:426) at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:244) at com.example.Include.encodeChildren(Include.java:54)

MyFaces básicamente libera el contexto de Facelet durante el tiempo de compilación de fin de vista, por lo que no está disponible durante el tiempo de visualización de la vista, lo que genera NPE porque el estado interno tiene varias propiedades anuladas. Sin embargo, es posible agregar componentes individuales en lugar de un archivo Facelet durante el tiempo de renderizado. Realmente no tuve tiempo de investigar si esto es culpa mía o de MyFaces. Es por eso que aún no terminó en OmniFaces.

Si usa Mojarra de todos modos, siéntase libre de usarlo. Sin embargo, recomiendo probarlo exhaustivamente con todos los casos de uso posibles en la misma página. Mojarra tiene algunos caprichos relacionados con el ahorro de estado que pueden fallar al usar esta construcción.