jueves, 2 de diciembre de 2021

Struts 2: Formularios con Grid

Esto es un poco antiguo pero tampoco hay mucha información al respecto e igual a alguien le es útil tener un ejemplo completo. En el cual usaremos un grid para visualizar una lista de datos y un formulario en la misma página que permita filtrar por los datos de dicha pantalla. 

Struts fue uno de los primeros frameworks MVC o al menos de los más populares a comienzos del siglo XXI. Y Struts 2 fue una actualización que simplificaba su funcionamiento, cuando ya Spring despuntaba y no solo como framework MVC. 

Empecemos con las librerías, a continuación indicaremos cuales usaremos para el ejemplo:

  • struts2-core que contiene el núcleo del framework MVC. 
  • struts2-convention-plugin que nos permitirá usar anotaciones y evitarnos el fichero de configuración xml. 
  • struts2-json-plugin para habilitar el framework para que permita llamadas y respuestas JSON. 
  • struts2-jquery-plugin para habilitar el uso de tags asociados a jquery. 
  • struts2-jquery-grid-plugin para permitir el uso de grid a través de tags asociados 
El ejemplo será sencillo como siempre y paso a paso veremos como realizarlo para su correcto funcionamiento. Y para empezar, lo primero a tener en cuenta es la configuración de la aplicación para usar Struts 2. Normalmente esta configuración siempre se ha realizado a través del fichero web.xml,  pero al ser una aplicación a desplegar en un contenedor de servlets moderno, como Tomcat 9 con Servlet API 4.01, nos lo podemos ahorrar. Esto lo realizaremos con una configuración del plugin maven-war-plugin. Pero aún así necesitaremos indicar de alguna forma que vamos a utilizar Struts 2. Esto lo podremos realizar a través de la creación de un filtro que pase por filtre todas las URLs y las haga pasar por la clase principal de Struts 2. 

@WebFilter("/*")
public class Struts2Filter extends StrutsPrepareAndExecuteFilter {
}

Y tal y como hemos indicados en uso de librerías anteriormente, intentaremos hacerlo de una forma más actualizada. Haciendo uso de anotaciones y evitando el uso de ficheros de configuración XML. Por lo que también nos podremos ahorrar el fichero XML de configuración por defecto de Struts 2. 

El siguiente paso será crear nuestro Action principal que actuará de controlador. Este contendrá los siguientes componentes:
  • La anotación @ParentPackage que nos permitirá devolver JSON en uno de los métodos.
  • Los atributos searchName y searchAuthor que compondrán el formulario
  • El atributo gridModel que representará la lista a pintar en la pantalla
  • El método search asociado a la URL /searchBook que no hace nada y reenvia directamente a la pantalla books.jsp
  • El método searchJson asociado a la URL /searchBookJson que recibe la invocación desde el grid y que le devuelve un listado de objetos en función de los parámetros de búsqueda del formulario. 
@ParentPackage("json-default")
public class BookAction extends GridPageAction {
  private static final long serialVersionUID = -2206764495660083184L;

  @Getter @Setter
  private String searchName;

  @Getter @Setter
  private String searchAuthor;

  @Getter @Setter
  List<Book> gridModel = new ArrayList<>();

  @Action(value = "/searchBook", results = { @Result(name = "success", location = "/pages/books.jsp") })
  public String search() {
	return SUCCESS;
  }

  @Action(value = "/searchBookJson", results = { @Result(name = "success", type = "json") })
  public String searchJson() {
	if (StringUtils.isNotBlank(searchAuthor) && searchAuthor.contains("Asimov")) {
		gridModel.add(new Book(1, "Isaac Asimov", "Foundation I"));
		gridModel.add(new Book(1, "Isaac Asimov", "Foundation II"));
		gridModel.add(new Book(1, "Isaac Asimov", "Foundation III"));
	} else {
		gridModel = new ArrayList<>();
	}
  	return SUCCESS;
  }
}

Como veis este es un ejemplo dummy donde creamos la lista manualmente. Lo normal es que invocásemos un servicio web o a la capa de acceso a datos para obtener los registros que coincidan con los parámetros de búsqueda. 

Ahora procederemos a realizar la página JSP en la cual mostraremos el formulario y el listado. Entre los componentes de esta página destacan: 
  • Las declaraciones de los taglibs que nos permiten generar componentes fácilmente. Usamos los propios de struts, los del plugin struts-jquery y los del plugin struts-jquery-grid.
  • La invocación del taglib sj:head que nos permite el uso de JQuery en la página. 
  • Un formulario compuesto por dos campos y un botón que nos permite enviar la información de la consulta. 
  • Un grid que muestre el contenido de la lista con los componentes buscados. Y el cual esta enlazado al método searchBookJson del Action.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sj" uri="/struts-jquery-tags" %>
<%@ taglib prefix="sjg" uri="/struts-jquery-grid-tags" %>

<sj:head jqueryui="true" jquerytheme="redmond"/>
<div>
	<s:form id="filterForm" action="searchBook">
		<s:textfield key="global.book.author" name="searchAuthor" />
		<s:textfield key="global.book.name" name="searchName" />
		<sj:submit value="Search" button="true" indicator="indicator" /> 
	</s:form>
    <s:url var="remoteurl" action="searchBookJson"/>
    <sjg:grid id="gridtable"
        dataType="json" href="%{remoteurl}" gridModel="gridModel"
        loadonce="true" reloadTopics="reloadGrid"  formIds="filterForm"> 
        <sjg:gridColumn name="id" index="id" title="id" sortable="false"/>
        <sjg:gridColumn name="author" index="author" title="author" sortable="false"/>
        <sjg:gridColumn name="name" index="name" title="name" sortable="false"/>
     </sjg:grid>
</div>

El componente más complejo es el grid, el cual tiene diversos atributos que ayudan a configurar su comportamiento. Como són:
  • dataType: Permite indicar que la información con la que se rellenará la tabla será JSON. 
  • href: Permite indicar a que URL hay que invocar para recibir la información que rellene el listado. 
  • gridModel: Permite indicar cual es el nombre del atributo asociado al listado. 
  • loadonce: Permite indicarle al componente que después de la primera llamada las invocaciones se realizarán desde el cliente. 
  • reloadTopics: Permite estar atento al evento que se indique para que se recargue el listado cuando sea disparado. 
  • formIds: Permite indicar que atributos del formulario a indicar deben ser enviados también a la dirección indicada en el atributo href. 
De esta forma tan sencilla podremos tener nuestro propio formulario con una tabla que muestre los registros que cumplan su formula dentro de la misma página. 

Pero antes de terminar, indicar que esta solución tiene un pequeño problema, y es que no es el diseño más eficiente. Y esto es debido a que la pantalla se recarga cada vez que se pulsa el botón submit y se realiza una invocación doble. Por un lado se invoca el método searchBook que no hace nada pero recarga la página, y una vez recargada la página se invoca el método searchBookJson y este es el que hace la búsqueda y muestra los datos por pantallas. 

La forma de hacerlo eficiente teóricamente es muy similar a la mostrada en el ejemplo. Con la diferencia de que el botón submit debe ser sustituido por un enlace que inicie el evento reloadGrid y por tanto conlleve la recarga del listado a través de invocaciones Ajax. Sin necesidad de recargar la pantalla. 

<sj:a value="Search" button="true" onClickTopics="reloadGrid" indicator="indicator" />

Desgraciadamente aunque la teoría y la documentación indican que debe de funcionar de esta forma. Al realizarlo así aparece un error asociado a la invocación y carga de datos el cual además no es muy concluyente sobre donde puede estar el origen del mismo: Uncaught RangeError: Maximum call stack size exceeded. Y de aquí que quisiera crear un post que indicase una solución alternativa al problema. 

Espero que al menos esta formo no tan eficiente sea válida para alguien que tenga que realizar un mantenimiento o soporte de proyectos con Struts 2. Como siempre, puedes ver todo el código aquí

No hay comentarios:

Publicar un comentario