Componente select de Apache Tapestry con funcionalidades adicionales usando bootstrap-select

Escrito por el , actualizado el .
java javascript planeta-codigo programacion tapestry
Enlace permanente Comentarios

Apache Tapestry

Java

El selector de opciones implementado en los navegadores es muy simple pudiendo seleccionar un elemento de una lista, mostrar los elementos agrupados por categorías o seleccionar múltiples elementos pero mostrándolos en formato de una lista en vez de como un desplegable. bootstrap-select es una librería que utiliza los estilos de Bootstrap y que añade algunas funcionalidades más a los componentes de selección de opciones de los formularios de una página web.

Algunas de estas funcionalidades adicionales está explicadas más detalladamente en los ejemplos, que son:

  • Cuadro de búsqueda
  • Búsqueda por palabras clave
  • Limitar el número de opciones seleccionables
  • Texto personalizado de opción no seleccionada
  • Texto personalizado de opción seleccionada (distinto al texto de la opción)
  • Texto de opciones seleccionadas personalizado
  • Estilos personalizados
  • Marca en la opción seleccionada
  • Flecha hacia el campo del formulario en el desplegable
  • Estilos personalizados en opciones individuales
  • Anchuras personalizables
  • Iconos en las opciones
  • Contenido personalizado en las opciones
  • Subtextos
  • Tamaño de menú personalizado
  • Opciones de selección y deselección
  • Divisores
  • Cabecera
  • Posición desplegable
  • Deshabilitar select, opción o grupo de opciones

Todas estas capacidades de personalización se consiguen bien añadiendo atributos a las etiquetas HTML select o a las etiquetas option y optgroup con lo que usar el componente bootstrap-select consiste básicamente generar el marcado HTML adecuado. Con JavaScript se puede construir el componente donde podemos indicar las mismas opciones adicionales que con los atributos data-, también tiene métodos para manipular su comportamiento de forma programática como por ejemplo recibir eventos cuando cambia la selección.

Usando Apache Tapestry con su concepto de parámetros informales añadiremos las atributos necesarios en la etiqueta select y con el modelo de datos proporcionado en SelectModel, OptionGroupModel y OptionModel podremos proporcionar los atributos adicionales para las etiquetas de los option y optgroup. El modelo de datos de una etiqueta select es una lista de opciones y grupos de opciones, cada opción tiene una etiqueta que se le mostrará al usuario, si está habilitada o no, el valor que se enviará al servidor cuando esté seleccionada y un mapa de atributos a añadir en la etiqueta de la opción.

Este sería el código para crear una instancia de SelectModel para un componente Select de Tapestry para un ficticio selector de país. En el ejemplo en vez de usar un mapa vacío con Collections.EMPTY_MAP se podría sustituir por un mapa con atributos que se añadirían a la opción para usar alguna otra funcionalidad de bootstrap-select.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package io.github.picodotdev.plugintapestry.pages;
...
public class Index {

  ...  
  @Property
  @Persist(value = PersistenceConstants.FLASH)
  private String pais;	
  ...
  @Environmental
  private JavaScriptSupport javascriptSupport;
  ...
  void afterRender() {
    javascriptSupport.require("app/index").invoke("init");
  }
  ...

  public SelectModel getPaisesSelectModel() {
    return new AbstractSelectModel() {
      @Override
      public List<OptionGroupModel> getOptionGroups() {
        OptionModel espana = new AppOptionModel("España", false, "espana", Collections.EMPTY_MAP);
        OptionModel francia = new AppOptionModel("Francia", false, "francia", Collections.EMPTY_MAP);
        OptionModel alemania = new AppOptionModel("Alemania", false, "alemania", Collections.EMPTY_MAP);

        OptionModel eeuu = new AppOptionModel("EEUU", false, "eeuu", Collections.EMPTY_MAP);
        OptionModel mexico = new AppOptionModel("Mexico", false, "mexico", Collections.EMPTY_MAP);
        OptionModel argentina = new AppOptionModel("Argentina", false, "argentina", Collections.EMPTY_MAP);

        OptionModel china = new AppOptionModel("China", false, "china", Collections.EMPTY_MAP);
        OptionModel japon = new AppOptionModel("Japón", false, "japon", Collections.EMPTY_MAP);
        OptionModel india = new AppOptionModel("India", true, "india", Collections.EMPTY_MAP);

        OptionGroupModel europa = new AppOptionGroupModel("Europa", false, Collections.EMPTY_MAP, Arrays.asList(espana, francia, alemania));
        OptionGroupModel america = new AppOptionGroupModel("America", false, Collections.EMPTY_MAP, Arrays.asList(eeuu, mexico, argentina));
        OptionGroupModel asia = new AppOptionGroupModel("Asia", false, Collections.EMPTY_MAP, Arrays.asList(china, japon, india));
        return Arrays.asList(europa, america, asia);
      }

      @Override
      public List<OptionModel> getOptions() {
        return null;
      }
    };
  }
}
Index.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
<div class="form-group">
  <t:select value="pais" model="paisesSelectModel" class="selectpicker" data-size="6" data-none-selected-text="Nada seleccionado"/>
</div>
...
<div class="form-group">
  ...
  País seleccionado: ${pais}
</div>
...
Index.tml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package io.github.picodotdev.plugintapestry.misc;

import org.apache.tapestry5.OptionGroupModel;
import org.apache.tapestry5.OptionModel;

import java.util.List;
import java.util.Map;

public class AppOptionGroupModel implements OptionGroupModel {

    private String label;
    private boolean disabled;
    private Map<String, String> attributes;
    private List<OptionModel> options;

    public AppOptionGroupModel(String label, boolean disabled, Map<String, String> attributes, List<OptionModel> options) {
        this.label = label;
        this.disabled = disabled;
        this.attributes = attributes;
        this.options = options;
    }

    @Override
    public String getLabel() {
        return label;
    }

    @Override
    public boolean isDisabled() {
        return disabled;
    }

    @Override
    public Map<String, String> getAttributes() {
        return attributes;
    }

    @Override
    public List<OptionModel> getOptions() {
        return options;
    }
}
AppOptionGroupModel.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package io.github.picodotdev.plugintapestry.misc;

import org.apache.tapestry5.OptionModel;

import java.util.Map;

public class AppOptionModel implements OptionModel {

    private String label;
    private boolean disabled;
    private Object value;
    private Map<String, String> attributes;

    public AppOptionModel(String label, boolean disabled, Object value, Map<String, String> attributes) {
        this.label = label;
        this.disabled = disabled;
        this.value = value;
        this.attributes = attributes;
    }

    @Override
    public String getLabel() {
        return label;
    }

    @Override
    public boolean isDisabled() {
        return disabled;
    }

    @Override
    public Map<String, String> getAttributes() {
        return attributes;
    }

    @Override
    public Object getValue() {
        return value;
    }
}
AppOptionModel.java

Componente selector con bootstrap-select

Componente selector con bootstrap-select

Al ser enviado el formulario que contiene el select el valor seleccionado los tendremos en la propiedad que hayamos indicado en el parámetro value del componente Select, este caso en la propiedad pais de la clase Index que representa la página.

En la clase PlugInStack hay que especificar los recursos CSS y de JavaScript necesarios para usar bootstrap-select, además de inicializar los selectores con JavaScript.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PlugInStack implements JavaScriptStack {

  ...

  @Override
  public List<Asset> getJavaScriptLibraries() {
    List<Asset> r = new ArrayList<>();
    r.add(assetSource.getClasspathAsset("META-INF/assets/tapestry5/bootstrap/js/dropdown.js"));
    r.add(new UrlAsset("https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.11.2/js/bootstrap-select.min.js", null));
    return r;
  }
	
  ...

  @Override
  public List<StylesheetLink> getStylesheets() {
    List<StylesheetLink> r = new ArrayList<>();
    r.add(new StylesheetLink("https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.11.2/css/bootstrap-select.min.css"));
    r.add(new StylesheetLink(assetSource.getContextAsset("css/app.css", null)));
    return r;
  }

  ...
}
PlugInStack.java
1
2
3
4
5
6
7
8
9
define("app/index", ["jquery"], function($) {
  function init(spec) {
    $('.selectpicker').selectpicker();
  }

  return {
    init: init
  }
});
index.js

En el artículo Componente select múltiple en Apache Tapestry explico como crear un select múltiple que por defecto no incluye Tapestry pero el estándar HTML soporta y en algún caso nos será necesario y en añadir botones selectores de opciones como incluir selectores adicionales además de Todos y Ninguno.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

Portada libro: PlugIn Tapestry

Libro PlugIn Tapestry

Si te interesa Apache Tapestry descarga gratis el libro de más de 300 páginas que he escrito sobre este framework en el formato que prefieras, PlugIn Tapestry: Desarrollo de aplicaciones y páginas web con Apache Tapestry, y el código de ejemplo asociado. En el libro comento detalladamente muchos aspectos que son necesarios en una aplicación web como persistencia, pruebas unitarias y de integración, inicio rápido, seguridad, formularios, internacionalización (i18n) y localización (l10n), AJAX, ... y como abordarlos usando Apache Tapestry.



Comparte el artículo: