jsf single-page-application facelets uiinclude ajax-update

¿Cómo ajax-refresh dinámico incluye contenido por menú de navegación?(JSF SPA)



single-page-application facelets (3)

Estoy aprendiendo JSF 2 gracias a este sitio que había aprendido mucho en tan poco tiempo.

Mi pregunta es cómo implementar un diseño común para todas mis páginas JSF 2 y hacer que solo el contenido parte de la página actualice no toda la página cada vez que hago clic en un enlace / menú desde un panel diferente. Estoy usando el enfoque Facelets, hace lo que quiero, excepto que cada vez que hago clic en un enlace de un panel (por ejemplo, los elementos del menú del panel izquierdo) se actualiza toda la página. Lo que estoy buscando es una forma de actualizar solo el contenido de mi página. Para ilustrar esto a continuación, está mi página de destino.

No publiqué mi código porque no estoy seguro de si Facelets puede hacer esto. ¿Hay otro enfoque más adecuado para mi requerimiento aparte de Facelets?


Netbeans proporciona un asistente que crea el diseño propuesto con un mínimo esfuerzo usando JSF. Entonces, la mejor manera de comenzar es echarle un vistazo al Facelets Template Wizard y observar la fuente generada.


Si solo desea actualizar parte de la página, solo hay dos maneras de hacerlo (para la web en general, no solo para JSF). Tienes que usar marcos o Ajax. JSF 2 admite ajax de forma nativa, consulte la etiqueta f: ajax para actualizar solo 1 componente sin volver a cargar la página completa.


Un enfoque directo sería la siguiente vista:

<h:panelGroup id="header" layout="block"> <h1>Header</h1> </h:panelGroup> <h:panelGroup id="menu" layout="block"> <h:form> <f:ajax render=":content"> <ul> <li><h:commandLink value="include1" action="#{bean.setPage(''include1'')}" /></li> <li><h:commandLink value="include2" action="#{bean.setPage(''include2'')}" /></li> <li><h:commandLink value="include3" action="#{bean.setPage(''include3'')}" /></li> </ul> </f:ajax> </h:form> </h:panelGroup> <h:panelGroup id="content" layout="block"> <ui:include src="/WEB-INF/includes/#{bean.page}.xhtml" /> </h:panelGroup>

Con este frijol:

@ManagedBean @ViewScoped public class Bean implements Serializable { private String page; @PostConstruct public void init() { page = "include1"; // Default include. } // +getter+setter. }

En este ejemplo, las plantillas de inclusión reales son include1.xhtml , include2.xhtml e include3.xhtml en /WEB-INF/includes folder (la carpeta y la ubicación son totalmente gratuitas a su elección; los archivos se colocan en /WEB-INF en para evitar el acceso directo al adivinar la URL en la barra de direcciones del navegador).

Este enfoque funciona en todas las versiones de MyFaces, pero requiere un mínimo de Mojarra 2.3.0. En caso de que esté utilizando una versión de Mojarra anterior a la 2.3.0, todo esto falla cuando la página <ui:include> a su vez contiene una <h:form> . Cualquier devolución de datos fallará porque falta totalmente el estado de la vista. Puede resolver esto actualizando mínimamente Mojarra 2.3.0 o con una secuencia de comandos encontrada en esta respuesta h: commandButton / h: commandLink no funciona con el primer clic, funciona solo con un segundo clic , o si ya está utilizando la biblioteca de utilidades JSF OmniFaces , use su script FixViewState . O bien, si ya está utilizando PrimeFaces y utiliza exclusivamente <p:xxx> ajax, entonces ya se ha tenido en cuenta de forma transparente.

Además, asegúrese de estar utilizando mínimamente Mojarra 2.1.18 ya que las versiones anteriores fallarán al mantener vivo el bean de ámbito de vista, lo que provocará que se use el include incorrecto durante la devolución de datos. Si no puede actualizar, deberá volver al siguiente enfoque (relativamente torpe) de renderizar condicionalmente la vista en lugar de construir condicionalmente la vista:

... <h:panelGroup id="content" layout="block"> <ui:fragment rendered="#{bean.page eq ''include1''}"> <ui:include src="include1.xhtml" /> </ui:fragment> <ui:fragment rendered="#{bean.page eq ''include2''}"> <ui:include src="include2.xhtml" /> </ui:fragment> <ui:fragment rendered="#{bean.page eq ''include3''}"> <ui:include src="include3.xhtml" /> </ui:fragment> </h:panelGroup>

La desventaja es que la vista se volverá relativamente grande y que todos los beans administrados asociados pueden inicializarse innecesariamente aunque no se usen según la condición procesada.

En cuanto al posicionamiento de los elementos, solo se trata de aplicar el CSS correcto. Eso está más allá del alcance de JSF :) Al menos, <h:panelGroup layout="block"> representa un <div> , así que debería ser lo suficientemente bueno.

Por último, pero no menos importante, este enfoque de SPA (aplicación de una sola página) no es amigable con el SEO. Todas las páginas no son indexables por los robots de búsqueda ni pueden marcarlas los endusers, es posible que tenga que juguetear con el historial de HTML5 en el cliente y proporcionar una alternativa al servidor. Además, en el caso de las páginas con formularios, la misma instancia del bean con ámbito de vista se reutilizaría en todas las páginas, lo que da como resultado un comportamiento de exploración poco intuitivo cuando navega de regreso a una página visitada anteriormente. Sugeriría ir con el enfoque de plantillas en su lugar como se describe en la segunda parte de esta respuesta: ¿Cómo incluir otro XHTML en XHTML usando JSF 2.0 Facelets? Ver también ¿Cómo navegar en JSF? Cómo hacer que la URL refleje la página actual (y no la anterior) .