Aprendiendo Vaadin #5: Soporte de navegación

Gestor de proyectosLlevaba tiempo buscando una forma de poder desarrollar una aplicación RIA sin tener que sufrir una dura curva de aprendizaje, aprovechando mis conocimientos de Java y obteniendo unos resultados suficientemente buenos. De primeras, GWT me pareció una buena opción, pero después descubrí Vaadin, que bajo mi punto de vista va un paso más allá y me convenció mucho más. En esta serie de entradas compartiré mis primeras experiencias con este framework.

Las aplicaciones Vaadin por defecto funcionan en modo SPA (Single Page Application), o sea, todo el contenido, páginas web, formularios, etc. de la aplicación son cargados en una sola página web. Algo muy característico de las aplicaciones SPA es que se pierde el soporte de navegación, o lo que es lo mismo, no permiten moverse atrás/adelante mediante el navegador, y además tampoco permiten guardar una página concreta de la aplicación como favorita (bookmark del navegador). Sólo puedes establecer como bookmark la página de entrada a la aplicación.

Puede que este modo de funcionamiento nos interese o no. Por si no fuera así, vamos a ver cómo implementar soporte de navegación a una aplicación Vaadin. Para esto se suele usar la clase Navigator de Vaadin, que será la encargada de gestionar la navegación entre vistas (View), y a cada vista le podremos asignar un fragmento URI, gracias al cual podremos establecer un bookmark a cada vista.

Como primer paso, clonamos el proyecto de mi anterior post sobre Vaadin en un nuevo proyecto al que he llamado ApVaadinURIFragment. El código fuente que voy a explicar en este post se puede descargar aquí.

Una vez clonado el proyecto conviene hacerle unos cambios. He cambiado el nombre del paquete de com.dherrabits.aprendiendovaadin a com.dherrabits.aprendiendovaadin.urifragment. También me ha parecido buena idea cambiar el nombre del archivo de contexto de spring de context.xml a spring-context.xml. Como he cambiado el nombre del paquete también será necesario cambiar el contenido del archivo de contexto de spring, que debe quedar así:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:lang="http://www.springframework.org/schema/lang"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans 
   			http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
			http://www.springframework.org/schema/lang
 			http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
 			http://www.springframework.org/schema/context
			http://www.springframework.org/schema/context/spring-context-2.5.xsd">
 
    <context:annotation-config />
    <context:component-scan base-package="com.dherrerabits.aprendiendovaadin.urifragment" />
</beans>

Por el mismo motivo, es necesario cambiar las referencias a las clases en el archivo web.xml que debe quedar así:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
  <display-name>Vaadin Web Application</display-name>
  <context-param>
    <description>Vaadin production mode</description>
    <param-name>productionMode</param-name>
    <param-value>false</param-value>
  </context-param>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/spring-context.xml</param-value>
  </context-param>
  <listener>       
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>Vaadin Application Servlet</servlet-name>
    <servlet-class>com.vaadin.server.VaadinServlet</servlet-class>
    <init-param>
      <description>Vaadin UI to display</description>
      <param-name>UI</param-name>
      <param-value>com.dherrerabits.aprendiendovaadin.urifragment.MyVaadinUI</param-value>
    </init-param>
    <init-param>
      <description>Application widgetset</description>
      <param-name>widgetset</param-name>
      <param-value>com.dherrerabits.aprendiendovaadin.urifragment.AppWidgetSet</param-value>
    </init-param>
    <init-param>
      <description>Vaadin UI Provider</description>
      <param-name>UIProvider</param-name>
      <param-value>com.dherrerabits.aprendiendovaadin.urifragment.MyUIProvider</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>Vaadin Application Servlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

Crearé dos páginas o vistas, una página de entrada por defecto a la que llamare StartView y una página principal a la que llamaré MainView. Ambas mostrarán un mensaje de bienvenida y un botón para navegar de una a otra. Cada una de ellas será una clase que implementará la interfaz View. Ambas serán clases internas a la clase MyVaadinUI. Esto es así por comodidad, para que las vistas tengan acceso al navegador que se definirá en MyVaadinUI y también para que las vistas tengan acceso a los servicios de Spring inyectados en MyVaadinUI. En un siguiente post explicaré cómo usar Spring y autowiring también en las Vistas de forma independiente, usando para ello el plugin SpringVaadinIntegration.

Bien, según lo explicado anteriormente, el código para la clase de la vista de entrada por defecto será algo así:

    public class StartView extends VerticalLayout implements View,ClickListener {
    	
    	public StartView() {
    		
            setSizeFull();
            Button button = new Button("Ir a la vista principal (MainView)");
            button.addClickListener((Button.ClickListener) this);
            addComponent(button);
            setComponentAlignment(button, Alignment.TOP_LEFT);
            
        }
    	
    	@Override
    	public void enter(ViewChangeEvent event) {
    		Notification.show(helloService.sayHello(this));
    	}

    	@Override
    	public void buttonClick(ClickEvent event) {
    		navigator.navigateTo(MAINVIEW);
    	}

    }

y el código para la vista principal será:

    public class MainView extends VerticalLayout implements View,ClickListener {

    	public MainView() {
    		
            setSizeFull();
            Button button = new Button("Ir a la vista inicial (StartView)");
            button.addClickListener((Button.ClickListener) this);
            addComponent(button);
            setComponentAlignment(button, Alignment.TOP_LEFT);
            
        }
    	
    	@Override
    	public void enter(ViewChangeEvent event) {
    		Notification.show(helloService.sayHello(this));
    	}

    	@Override
    	public void buttonClick(ClickEvent event) {
    		navigator.navigateTo("");
    	}

    }

Recordemos que son clases internas a MyVaadinUI y por tanto tienen acceso al servicio Spring inyectado helloService y a la instancia navigator. He modificado el servicio helloService para que devuelva un mensaje diferente en base a qué vista le esté invocando, quedando así:

package com.dherrerabits.aprendiendovaadin.urifragment;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import com.dherrerabits.aprendiendovaadin.urifragment.MyVaadinUI.MainView;
import com.dherrerabits.aprendiendovaadin.urifragment.MyVaadinUI.StartView;
  
@Service
@Scope("prototype")
public class HelloService {
  
    public String sayHello(Object c) {
    	if (c instanceof StartView)
    		return "Bienvenido a la vista inicial (StartView)";
    	else if (c instanceof MainView)
    		return "Bienvenido a la vista principal (MainView)";
    	else
    		return "Hola desde servicio Spring!!!";
    }
}

Por último, he definido en MyVaadinUI el Navigator que gestionará la navegación entre vistas, he definido el nombre que se empleará para el URI Fragment de la vista principal, y he añadido las vistas al Navigator, y con esto ya estaría todo. El fuente de MyVaadinUI con las clases de vista internas incluidas quedaría finalmente así:

package com.dherrerabits.aprendiendovaadin.urifragment;

import org.springframework.beans.factory.annotation.Autowired;

import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
  
/**
 * The Application's "main" class
 */
@SuppressWarnings("serial")
public class MyVaadinUI extends UI {
      
	@Autowired
    HelloService helloService;
	
    Navigator navigator;
    protected static final String MAINVIEW = "main";

    @Override
    protected void init(VaadinRequest request) {
    	
        getPage().setTitle("Ejemplo de navegación");
        
        // Crear un navegador para controlar las Vistas
        navigator = new Navigator(this, this);
        
        // Crear y registrar las Vistas
        navigator.addView("", new StartView());
        navigator.addView(MAINVIEW, new MainView());
        
    }
  
    public class StartView extends VerticalLayout implements View,ClickListener {
    	
    	public StartView() {
    		
            setSizeFull();
            Button button = new Button("Ir a la vista principal (MainView)");
            button.addClickListener((Button.ClickListener) this);
            addComponent(button);
            setComponentAlignment(button, Alignment.TOP_LEFT);
            
        }
    	
    	@Override
    	public void enter(ViewChangeEvent event) {
    		Notification.show(helloService.sayHello(this));
    	}

    	@Override
    	public void buttonClick(ClickEvent event) {
    		navigator.navigateTo(MAINVIEW);
    	}

    }

    public class MainView extends VerticalLayout implements View,ClickListener {

    	public MainView() {
    		
            setSizeFull();
            Button button = new Button("Ir a la vista inicial (StartView)");
            button.addClickListener((Button.ClickListener) this);
            addComponent(button);
            setComponentAlignment(button, Alignment.TOP_LEFT);
            
        }
    	
    	@Override
    	public void enter(ViewChangeEvent event) {
    		Notification.show(helloService.sayHello(this));
    	}

    	@Override
    	public void buttonClick(ClickEvent event) {
    		navigator.navigateTo("");
    	}

    }

    
}

Y así disponemos de forma sencilla de un proyecto Vaadin con soporte de navegación mediante vistas usando un servicio Spring auto-inyectado. Como he comentado anteriormente, en un siguiente post explicaré cómo usar Spring y autowiring también en las Vistas de forma independiente, usando para ello el plugin SpringVaadinIntegration. De esta forma será posible usar autowiring de Spring en las vistas sin que estas tengan que ser clases internas a nuestra clase UI.

Puedes descargar el código fuente de este ejemplo aquí.

Anuncios