Cómo hacer un formulario con campos select dependientes en WordPress

La respuesta rápida es utilizando javascript y un poco de preparación a la hora de crear el formulario desde un plugin de WordPress. Sigue leyendo para la respuesta completa.

Escribo esta entrada a raíz de un comentario que un usuario dejó en el artículo ¿Cómo programar un formulario en WordPress sin utilizar plugins? El problema suele darse cuando un campo de un formulario de tipo select o combo tiene que ser seleccionado de una tabla de la base de datos que a su vez depende de otra tabla. Piensa por ejemplo en curso/asignatura, país/ciudad, provincia/localidad o marca/modelo. Este último es el ejemplo que vas a ver aquí.

Alta de dispositivo donde se muestra un campo select modelo enlazado a un select marca

La solución tiene dos partes, la primera es preparar correctamente el formulario con los dos campos select desde un plugin, utilizando por ejemplo un shortcode. Luego tendrás que crear un script de JavaScript para que ambos select queden enlazados o sincronizados.

En este tutorial se vuelcan todos las opciones de ambas tablas en el formulario que se muestra al usuario y luego con JavaScript se filtran en función de lo que se seleccione en el primer select.

No obstante si la tabla del segundo select tuviera demasiados registros quizás sería más eficiente traerse solamente las opciones correspondientes utilizando AJAX. Intentaré implementar esta segunda estrategia en un próximo tutorial.

Si todavía no has programado un formulario con un campo select que tira de una tabla de la base de datos te recomiendo empezar por aquí, si ya lo has hecho alguna vez puedes ir directo al epígrafe Formulario con campo select enlazado que es la implementación completa.

En cualquier caso tienes el plugin completo en GitHub.

Formulario con campo select simple

Este primer formulario te servirá para dar de alta un nuevo modelo en la marca seleccionada.

Empieza con el archivo principal del plugin que irá cargando el resto de archivos y llamando a las funciones necesarias (funciones que estarán en los archivos cargados)

<?php
/**
 * File: kfp-formulario-mania.php
 * Plugin Name: KFP Formulario Manía
 * Author: KungFuPress
 * Version: 1.0
 * Description: Plugin con distintos ejemplos de formulario
 * Author URI: https://kungfupress.com
 */

//  Aborta si se intenta acceder directamente
if (! defined('ABSPATH')) {
    exit();
}

$ruta_plugin = plugin_dir_path(__FILE__);

require_once $ruta_plugin . 'crear_tablas.php';
require_once $ruta_plugin . 'crear_datos_ejemplo.php';
require_once $ruta_plugin . 'shortcode_select_simple.php';

register_activation_hook(__FILE__, 'Kfp_Form_Mania_Crear_tablas');
register_activation_hook(__FILE__, 'Kfp_Form_Mania_Volcar_Datos_ejemplo');

add_shortcode('kfp_form_mania_select_simple', 'Kfp_Form_Mania_Select_simple');

En el archivo crear_tablas.php se define la función que creará las tablas necesarias en la base de datos, si ya existen no las vuelve a crear.

<?php
/**  Archivo: crear-tablas.php  */

/**
 * Función que crea las tablas necesarias durante la activación del plugin
 *
 * @return void
 */
function Kfp_Form_Mania_Crear_tablas()
{
    global $wpdb;
    $sql = array(); 
    $tabla_dispositivo = $wpdb->prefix . 'dispositivo';
    $tabla_dispositivo_marca = $wpdb->prefix . 'dispositivo_marca';
    $tabla_dispositivo_modelo = $wpdb->prefix . 'dispositivo_modelo';
    $charset_collate = $wpdb->get_charset_collate();
    
    // Consulta para crear las tablas
    // Mas adelante utiliza dbDelta, si la tabla ya existe no la crea sino que la
    // modifica con los posibles cambios y sin pérdida de datos
    $sql[] = "CREATE TABLE $tabla_dispositivo (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        nombre varchar(100) NOT NULL,
        id_modelo mediumint(9),
        created_at datetime NOT NULL,
        PRIMARY KEY (id)
        ) $charset_collate";

    $sql[] = "CREATE TABLE $tabla_dispositivo_marca (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        nombre varchar(100) NOT NULL,
        PRIMARY KEY (id)
        ) $charset_collate";
    
    $sql[] = "CREATE TABLE $tabla_dispositivo_modelo (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        id_marca mediumint(9) NOT NULL,
        nombre varchar(100) NOT NULL,
        PRIMARY KEY (id)
        ) $charset_collate";
    
    include_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
}

Y ahora el archivo crear_datos_ejemplo.php para el volcado de datos.

Nota: esta función lanzará un error si se vuelve a activar el plugin sin borrar los datos de la tabla porque los ID de los registros estarán duplicados, pero el plugin seguirá funcionando sin problema. Es algo que tengo que solucionar.

<?php
/** Archivo: crear_datos_ejemplo.php */

/**
 * Función que inserta datos de ejemplo en las tablas de la base de datos
 *
 * @return void
 */
function Kfp_Form_Mania_Crear_Datos_ejemplo()
{
    global $wpdb;
    $tabla_dispositivo = $wpdb->prefix . 'dispositivo';
    $tabla_dispositivo_marca = $wpdb->prefix . 'dispositivo_marca';
    $tabla_dispositivo_modelo = $wpdb->prefix . 'dispositivo_modelo';

    $wpdb->insert( 
        $tabla_dispositivo_marca,  
        array('id' => 1, 'nombre' => 'Apple')
    );
    $wpdb->insert( 
        $tabla_dispositivo_marca, 
        array('id' => 2, 'nombre' => 'Samsung')
    );
    $wpdb->insert( 
        $tabla_dispositivo_modelo, 
        array('id' => 1, 'id_marca' => 1, 'nombre' => 'iPhone X') 
    );
    $wpdb->insert( 
        $tabla_dispositivo_modelo, 
        array('id' => 2,'id_marca' => 1,'nombre' => 'iPhone 8') 
    );
    $wpdb->insert( 
        $tabla_dispositivo_modelo, 
        array('id' => 3,'id_marca' => 2,'nombre' => 'Galaxy 8') 
    );
    $wpdb->insert( 
        $tabla_dispositivo_modelo, 
        array('id' => 4,'id_marca' => 2,'nombre' => 'Galaxy 9') 
    );

    $wpdb->insert( 
        $tabla_dispositivo, 
        array( 
            'id' => 1,
            'nombre' => 'Dispositivo móvil Antonio', 
            'id_modelo' => 1, 
            'created_at' => current_time('mysql'), 
            ) 
    );
}

Por último el archivo shortcode_select_simple.php que implementa el shortcode encargado de mostrar y capturar los datos del formulario para dar de alta nuevos modelos.

<?php
/** Archivo: shortcode_select_simple.php */

/**
 * Función que muestra y captura los datos del formulario con campo select simple
 *
 * @return void
 */
function Kfp_Form_Mania_Select_simple()
{
    global $wpdb;
    wp_enqueue_style('css_form_mania', plugins_url('style.css', __FILE__));
    // Si el formulario viene relleno graba los datos en la BD
    if (!empty($_POST) &amp;&amp; $_POST['nombre'] != '' &amp;&amp; $_POST['id_marca'] != '') {
        $tabla_dispositivo_modelo = $wpdb->prefix . 'dispositivo_modelo';
        $nombre = sanitize_text_field($_POST['nombre']);
        $id_marca = (int)$_POST['id_marca'];
        $wpdb->insert(
            $tabla_dispositivo_modelo, 
            array(
                'nombre' => $nombre,
                'id_marca' => $id_marca,
            )
        );
    }
    // Trae las marcas de dispositivos de la base de datos
    $tabla_dispositivo_marca = $wpdb->prefix . 'dispositivo_marca';
    $dispositivo_marcas = $wpdb->get_results(
        "SELECT * from $tabla_dispositivo_marca ORDER BY nombre"
    );    
    ob_start();
    ?>
    <form action="<?php get_the_permalink(); ?>" method="post"
        class="kfp-form-mania">
        <div class="form-input">
            <label for="nombre">Modelo</label>
            <input type="text" name="nombre" id="nombre" required>
        </div>
        <div class="form-input">
            <label for="id_marca">Marca</label>
            <select name="id_marca" required>
                <option value="">Selecciona la marca</option>
                <?php
                foreach ($dispositivo_marcas as $marca) {
                    echo("<option value='$marca->id'>$marca->nombre</option>)");
                }
                ?>
            </select>
        </div>
        <div class="form-input">
            <input type="submit" value="Enviar">
        </div>
    </form>
    <?php
    return ob_get_clean();
}

Aquí tienes también el código CSS para que el formulario quede un poco más decente.

/* Archivo: style.css */ 

form.kfp-form-mania {
    max-width: 600px;
}

form.kfp-form-mania label {
    font-weight: bold;
    display: block;
    margin-bottom: 5px;
}

form.kfp-form-mania .form-input {
    padding: 1em;
}

form.kfp-form-mania .form-input input[type='text'],
form.kfp-form-mania .form-input select,
form.kfp-form-mania textarea {
    display: block;
    width: 100%;
}

p.exito {
    font-size: 1.5em;
    padding: 1em;
    border: 2px solid green;
    background: lightgreen;
}

p.error {
    font-size: 1.5em;
    padding: 1em;
    border: 2px solid red;
    background: #ff8787;
}

Para probar que todo funciona activa el plugin en tu instalación local de WordPress y en una nueva entrada o página inserta el shortcode [kfp_form_mania_select_simple]

Formulario con campo select enlazado

Para empezar tienes que añadir otro shortcode al fichero kfp-formulario-mania.php que creaste en el epígrafe anterior.

require_once $ruta_plugin . 'shortcode_select_enlazado.php';
add_shortcode('kfp_form_mania_select_enlazado', 'Kfp_Form_Mania_Select_enlazado');

A continuación tienes el código del shortcode que se encarga de mostrar y procesar un formulario para dar de alta un nuevo dispositivo en la base de datos. Cuando se seleccione la marca del dispositivo aparecerán los modelos de esa marca y el ID de ese modelo es el que se almacenará en el registro del nuevo dispositivo.

<?php
/** Archivo: shortcode_select_enlazado.php */

/**
 * Función que implementa formulario con campos select enlazados
 *
 * @return void
 */
function Kfp_Form_Mania_Select_enlazado()
{
    global $wpdb;
    wp_enqueue_style('css_form_mania', plugins_url('style.css', __FILE__));
    wp_enqueue_script(
        'js_select_enlazado', 
        plugins_url('select-enlazado.js', __FILE__)
    );
    // Si el formulario viene relleno graba los datos en la BD
    if (!empty($_POST) &amp;&amp; $_POST['nombre'] != '' &amp;&amp; $_POST['id_modelo'] != '') {
        $tabla_dispositivo = $wpdb->prefix . 'dispositivo';
        $nombre = sanitize_text_field($_POST['nombre']);
        $id_modelo = (int)$_POST['id_modelo'];
        $created_at = date('Y-m-d H:i:s');
        $wpdb->insert(
            $tabla_dispositivo, 
            array(
                'nombre' => $nombre,
                'id_modelo' => $id_modelo,
                'created_at' => $created_at,
            )
        );
    }
    // Trae marcas y modelos de dispositivos de la base de datos
    $tabla_dispositivo_marca = $wpdb->prefix . 'dispositivo_marca';
    $dispositivo_marcas = $wpdb->get_results(
        "SELECT * FROM $tabla_dispositivo_marca"
    );
    $tabla_dispositivo_modelo = $wpdb->prefix . 'dispositivo_modelo';
    $dispositivo_modelos = $wpdb->get_results(
        "SELECT * FROM $tabla_dispositivo_modelo"
    );
    ob_start();
    ?>
    <form action="<?php get_the_permalink(); ?>" method="post"
        class="kfp-form-mania">
        <div class="form-input">
            <label for="nombre">Nombre</label>
            <input type="text" name="nombre" id="nombre" required>
        </div>
        <div class="form-input">
            <label for="id_marca">Marca</label>
            <select name="id_marca" id="kfp-fm-select-marca" required>
                <option value="">Selecciona la marca del dispositivo</option>
                <?php
                foreach ($dispositivo_marcas as $marca) {
                    echo("<option value='$marca->id'>$marca->nombre</option>)");
                }
                ?>
            </select>
        </div>
        <div class="form-input">
            <label for="id_modelo">Modelo</label>
            <select name="id_modelo" id="kfp-fm-select-modelo" required>
                <option value="">Selecciona el modelo del dispositivo</option>
                <?php
                foreach ($dispositivo_modelos as $modelo) {
                    echo("<option data-marca='$modelo->id_marca' 
                        value='$modelo->id'>$modelo->nombre</option>");
                }
                ?>
            </select>
        </div>
        <div class="form-input">
            <input type="submit" value="Enviar">
        </div>
    </form>

    <?php
    return ob_get_clean();
}

En este formulario es muy importante la propiedad data-marca que se asocia a cada option del segundo select, porque es la que luego te permitirá filtrar solamente los option correspondientes a la marca escogida desde JavaScript.

Y aquí tienes el código JavaScript que hará el resto de la magia. Quizás podría quedar más intuitivo utilizando jQuery, pero como el pobre está tan denostado he preferido hacerlo con Vanila JavaScript (a pelo vamos).

/** Archivo: select-enlazado.js */

var selectMarca = document.querySelector('#kfp-fm-select-marca');
var selectModelo = document.querySelector('#kfp-fm-select-modelo');
var optionsModelo = selectModelo.querySelectorAll('option');

filtraOpcionesModelo(selectMarca.value);

document.addEventListener('input', function (event) {
	if (event.target.id !== 'kfp-fm-select-marca') return;
    filtraOpcionesModelo(event.target.value);
}, false);

// Elimina las options del segundo select y lo rellena con las options filtradas
function filtraOpcionesModelo(selectValue) {
  selectModelo.innerHTML = '';
  for(var i = 0; i < optionsModelo.length; i++) {
    if(optionsModelo[i].dataset.marca === selectValue) {
      selectModelo.appendChild(optionsModelo[i]);
    }
  }
}

Si ya lo tienes todo listo crea una nueva entrada o página con el shortcode [kfp_form_mania_select_enlazado] y debe aparecerte algo parecido al gif animado que aparece al principio de este artículo.

Referencias

8 comentarios en “Cómo hacer un formulario con campos select dependientes en WordPress

      1. Hola Kung Fu Press gracias por responder. Te daré un ejemplo.

        Supongamos que tengo una venta de libros, cuadernos y lápices, en inventario (base de datos) tengo 5 libros y vendo 2 libros en el día, por lo cual me quedarían 3 libros en el inventario.

        ¿Cómo puedo hacer un plugin donde ingrese la venta del día y en el inventario (base de datos) me modifiquen dependiendo de las ventas.

        gracias por tu ayuda

  1. No sabía que eran la misma persona.
    Gracias por el código me funcionó de maravilla, ya que no sabía como hacer operaciones aritméticas con la base de datos

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *