Helpers 2/2 – Select Box

Continuando este tema que lo dejé para continuar viendo otras cosas, me gustaría seguir hablando y creando helpers que ayuden a las personas que no están usando algún framework que ya disponga de algo parecido como Symfony.

Ya habíamos visto en el artículo de introducción llamado Helpers – Funciones genéricas la idea de crear un helper para crear controles input para formularios, ya sean de tipo text, button, submit, checkbox, radio, etc. Hoy me gustaría tratar un helper un poco más complejo para los selects (combo box) ya que si no lo tenemos hay que ir creando componentes de tipo <select> cada vez que obtenemos los datos de la base de datos y suele resultar un poco cansador.

Definamos la base

Tomemos como base crear una función a la cual llamaremos select_helper() que tomará por argumento los datos necesarios para la creación del combo. En un principio necesitaremos los argumentos más principales como el name del componente y las opciones (<option>) que se desplegarán. Este último será tratado como un array asociativo en donde tendremos como clave el value de nuestros options, y como el valor de la clave el texto a mostrar por cada opción. Haciéndolo bien básico podríamos tener el siguiente código

function select_helper($name, $selectOptions)
{
    //-- Cargaermos el texto HTML generado dentro de
    //   la variable $ret para finalmente hacer un return
    $ret = '<select name="' . $name . '">';
 
    //-- Por cada par (clave => valor) crearemos un <option]> que
    //   iremos concatenando a la variable $ret
    foreach($selectOptions as $key => $value)
    {
        $ret .= '<option value="' . $key . '">' . $value . '</option>';
    }
 
    //-- Finamente concatenamos el cierre del combo
    $ret .= '</select>';
 
    //-- Retornamos el HTML para usarlo con un echo
    return $ret;
}
 
//-- Esto lo podríamos utilizar así
//-- Creamos un array (lo podríamos haber obtenido de la base de datos
//   por ejemplo de una tabla parametrica como por ejemplo lo explicado
//   este <a href="/2010/10/18/parametrizando-nuestro-sistema-con-symfony-i/">artículo</a>)
$paises = array(
    'PY' => 'Paraguay',
    'AR' => 'Argentina',
    'UR' => 'Uruguay'
);
 
echo select_helper('pais', $paises);

Con el código anterior se generaría el texto HTML necesario para mostrar el siguiente combo box

<select name="pais">
    <option value="PY">Paraguay</option>
    <option value="AR">Argentina</option>
    <option value="UR">Uruguay</option>
</select>

Agreguemos ahora un parámetro opcional a nuestro helper que nos permita recibir la opción de deberá estar marcada por defecto. Esta funcionalidad es muy útil para los formularios de modificación de registros. En caso de no tener la necesidad de tener un valor por defecto marcado podríamos enviar null, el cual será el valor por defecto.

function select_helper($name, $selectOptions, $defaultValue=null)
{
    $ret = '<select name="' . $name . '">';
 
    foreach($selectOptions as $key => $value)
    {
        //-- Evalúo si el defaultValue enviado es igual a la clave y agrego el atributo
        //   selected="selected"
        if($defaultValue == $key)
            $ret .= '<option value="' . $key . '" selected="selected">' . $value . '</option>';
        else
            $ret .= '<option value="' . $key . '">' . $value . '</option>';
    }
 
    $ret .= '</select>';
 
    return $ret;
}
 
//-- Esto lo podríamos utilizar así 
 
$paises = array(
    'PY' => 'Paraguay',
    'AR' => 'Argentina',
    'UR' => 'Uruguay'
);
 
echo select_helper('pais', $paises, 'AR');

El código HTML sería el siguiente

<select name="pais">
    <option value="PY">Paraguay</option>
    <option value="AR" selected="selected">Argentina</option>
    <option value="UR">Uruguay</option>
</select>

Todavía muy sencillo como para el gran aporte que nos puede brindar este helper al momento de tener que hacer formularios. Ahora compliquemos más las cosas. Agreguemos unos argumentos opcionales más. Uno para atributos de la etiqueta select como por ejemplo class, size, multiple, etc y otro de opciones que nos permita controlar ciertas opciones específicas a fin de darle mayor funcionalidad a nuestro helper.

Agregando atributos a la etiqueta < select >

Esta funcionalidad ya la habíamos analizado en el artículo anterior sobre la introducción a Helpers – Funciones genéricas. Nos fue de gran ayuda usarla como un Array ya que nos permitía agregar todos los atributos que necesitemos. Veamos los cambios necesarios para esto.

function select_helper($name, $selectOptions, $defaultValue=null, $attrs=array())
{
    //-- usamos como name el primer parámetro pasado a la función
    //   así tenemos todos los atributos del select en un mismo lugar
    //   Dejo como primer argumento el $name porque será obligatorio.
    $attrs['name'] = $name;
 
    //-- recorremos el array $attrs y vamos generando los atributos
    //   en forma de texto separados por coma
    $attributes = '';
    foreach ($attrs as $key => $value)
        $attributes .= ' ' . $key . '="' . $value . '"';
 
    //-- de una vez cargamos todos los atributos. Hay que notar que
    //   las comillas ahora son dobles para que interprete la variable
    $ret = "<select{$attributes}>";
 
    foreach($selectOptions as $key => $value)
    {
        if($defaultValue == $key)
            $ret .= '<option value="' . $key . '" selected="selected">' . $value . '</option>';
        else
            $ret .= '<option value="' . $key . '">' . $value . '</option>';
    }
 
    $ret .= '</select>';
 
    return $ret;
}
 
//-- Esto lo podríamos utilizar así
 
$paises = array(
    'PY' => 'Paraguay',
    'AR' => 'Argentina',
    'UR' => 'Uruguay'
);
 
echo select_helper('pais', $paises, 'AR', array('class' => 'estilo1'));
echo select_helper('pais', $paises, null, array('size' => '3', 'multiple' => 'multiple'));

Esto arrojaría el siguiente código HTML

<select name="pais" class="estilo1">
    <option value="PY">Paraguay</option>
    <option selected="selected" value="AR">Argentina</option>
    <option value="UR">Uruguay</option>
</select>
 
<select name="pais" multiple="multiple" size="3">
    <option value="PY">Paraguay</option>
    <option value="AR">Argentina</option>
    <option value="UR">Uruguay</option>
</select>

Si recordamos en el artículo anterior donde vimos la creación del helper para los componentes de tipo input, vemos que las líneas 10-12, del ejemplo de arriba, las usamos también ahí. Esas líneas se encargan de recibir el array $attrs de atributos que queremos agregar y los convierte en una cadena de texto que contiene todo separado por espacios a fin de usarlo directamente dentro de la etiqueta como vemos en la línea 16. Ahorremos código y creemos una función que haga solo eso de esta manera.

function _get_attributes($attrs)
{
    //-- recorremos el array y vamos generando los atributos
    $attributes = '';
    foreach ($attrs as $key => $value)
        $attributes .= ' ' . $key . '="' . $value . '"';
 
    return $attributes;
}

Agreguémosle un guión bajo al comienzo del nombre de esta función para simular que es una función privada, que será utilizada por los demás helpers. De esta manera refactoricemos nuestro código utilizado esta nueva función.

function select_helper($name, $selectOptions, $defaultValue=null, $attrs=array())
{
    //-- usamos como name el primer parámetro pasado a la función
    $attrs['name'] = $name;
 
    //-- Obtenemos un string de los atributos del select pasados por el argumento
    //   $attrs ya que es un array
    $attributes = _get_attributes($attrs);
 
    $ret = "<select{$attributes}>";
 
    foreach($selectOptions as $key => $value)
    {
        if($defaultValue == $key)
            $ret .= '<option value="' . $key . '" selected="selected">' . $value . '</option>';
        else
            $ret .= '<option value="' . $key . '">' . $value . '</option>';
    }
 
    $ret .= '</select>';
 
    return $ret;
}

Desglosemos funciones y refactoricemos más el código

Sería muy útil dividir más el código, para lo que vamos a crear dos funciones (privadas) más. Una para crear un <option> específico y otro para crear el grupo de opciones completas que reutilizará a la función anterior. La que crea una sola opción se llamará _select_option_helper en singular y la que crea el grupo _select_options_helper en plural.

/**
 * Recibiremos la clave y el valor como argumentos obligatorios, un valor por
 * defecto para que aquí mismo evaluemos si es el que se está creando y lo
 * pongamos por defecto. Un parámetro más opción $attrs que permitirá pasar
 * atributos adicionales a la etiqueta <option>
 */
function _select_option_helper($key, $value, $defaultValue=null, $attrs=array())
{
    //-- Cargamos la clave dentro del array $attrs
    $attrs['value'] = $key;
 
    //-- En caso de que el valor por defecto enviado coincida con la clave que
    //   estamos creando agrega al array de atributos el selected="selected"
    if ($defaultValue == $key)
            $attrs['selected'] = 'selected';
 
    //-- Reutilizamos nuestra función privada para crear el string
    $attributes = _get_attributes($attrs);
 
    //-- creamos la opción
    $ret = "<option{$attributes}>$value</option>";
 
    return $ret;
}
 
/**
 * Recibiremos el array asociativo de opciones, el valor por defecto opcionalmente
 * ya que vamos a reutilizar el helper anterior dentro de este y también así los
 * atributos para las opciones.
 */
function _select_options_helper($selectOptions, $defaultValue=null, $attrs=array())
{
    $ret = '';
 
    //-- Recorremos el array
    foreach ($selectOptions as $key => $value)
    {
        //-- Reutilizamos el helper para crear opciones
        $ret .= _select_option_helper($key, $value, $defaultValue, $attrs);
    }
 
    return $ret;
}

Ahora que tenemos estos helper “privados” refactoricemos nuestro helper principal

function select_helper($name, $selectOptions, $defaultValue=null, $attrs=array(), $opts=array(), $optionsAttrs=array())
{
    //-- usamos como name el primer parámetro pasado a la función
    $attrs['name'] = $name;
 
    //-- Obtenemos un string de los atributos del select pasados por el argumento
    //   $attrs ya que es un array
    $attributes = _get_attributes($attrs);
 
    $ret = "<select{$attributes}>";
 
    $ret .= _select_options_helper($selectOptions, $defaultValue, $optionsAttrs);
 
    $ret .= '</select>';
 
    return $ret;
}

Notemos que en la definición de la función agregué dos últimos argumentos opcionales, el primero que lo veremos a continuación y el último que podríamos, con la funcionalidad de nuestros nuevos helpers privados, enviar atributos extras para las etiquetas <option> como por ejemplo algún class de estilo CSS.

Agregando opciones especiales

A continuación utilizando el quinto argumento opcional del que hablamos más arriba para permitir agregar opciones especiales a nuestro helper. Se me ocurre una opción para decir si el select va a tener un primer <option> opcional ya que ahora si se marcó una opción ya no la puede desmarcar más. Por defecto, en caso de que esa opción haya sido activada se pondrá la clave de la opción como vacía (value=””) y el texto mostrado será — por defecto. También podríamos tener otra opción que permita enviar el texto a mostrar para reemplazar el – por ejemplo por [Elija una opción].

function select_helper($name, $selectOptions, $defaultValue=null, $attrs=array(), $opts=array(), $optionsAttrs=array())
{
    //-- usamos como name el primer parámetro pasado a la función
    $attrs['name'] = $name;
 
    //-- La opción defaultText nos permite cambiar el texto por defecto
    //   que tendrá el primer option en caso de hacer declarado como
    //   optional = true. Por defecto será -- si no se envía
    if (!isset($opts['defaultText']))
        $opts['defaultText'] = '--';
 
    //-- Obtenemos un string de los atributos del select pasados por el argumento
    //   $attrs ya que es un array
    $attributes = _get_attributes($attrs);
 
    $ret = "<select{$attributes}>";
 
    //-- Controlamos si existe la opctión "optional" que permitirá agregar
    //   automáticamente un primer option vacío
    $options = '';
    if (isset($opts['optional']) && $opts['optional'] === true)
    {
        //-- En caso de que no se haya enviado un valor por defecto, haremos que
        //   el valor por defecto sea un string vacío para que esta opción con el
        //   value="" sea marcada por defecto
        if(is_null($defaultValue))
            $defaultValue = '';
 
        //-- Finalmente llamamos al helper privado para crear nuestro <option>
        $options .= _select_option_helper('', $opts['defaultText'], $defaultValue, $optionsAttrs);
    }
 
    $options .= _select_options_helper($selectOptions, $defaultValue, $optionsAttrs);
 
    $ret .= $options;
    $ret .= '</select>';
 
    return $ret;
}
 
//-- Esto lo podríamos utilizar así
 
$paises = array(
 'PY' => 'Paraguay',
 'AR' => 'Argentina',
 'UR' => 'Uruguay'
);
 
echo select_helper('pais', $paises, 'AR', array(), array('optional' => true));
echo select_helper('pais', $paises, null, array(), array(
    'optional' => true,
    'defaultText' => '[Elija una opción]')
);

Esto nos arrojaría el siguiente HTML

<select name="pais">
    <option value="">--</option>
    <option value="PY">Paraguay</option>
    <option selected="selected" value="AR">Argentina</option>
    <option value="UR">Uruguay</option>
</select>
 
<select name="pais">
    <option selected="selected" value="">[Elija una opción]</option>
    <option value="PY">Paraguay</option>
    <option value="AR">Argentina</option>
    <option value="UR">Uruguay</option>
</select>

Ahora que vimos como agregar opciones especiales vayamos al último tema que sería fantástico tenerlo solucionado.

Combo con secciones optgroup

Hasta ahora vimos como pasamos en el segundo argumento un Array con las opciones que queremos crear dentro de nuestro combo. Este Array enviado, debe ser de 1 dimensión. Enviando un Array de 2 dimensiones podríamos decirle que nos cree un combo con secciones, es decir, que haya como una separatoria entre algunas opciones y otras permitiendo clasificarlas usando la etiqueta <optgroup label="nombre_seccion">. Hagamos los cambios necesarios para lograr esta nueva funcionalidad.

function select_helper($name, $selectOptions, $defaultValue=null, $attrs=array(),
    $opts=array(), $optionsAttrs=array())
{
    //-- usamos como name el primer parámetro pasado a la función
    $attrs['name'] = $name;
 
    //-- La opción defaultText nos permite cambiar el texto por defecto
    //   que tendrá el primer option en caso de hacer declarado como
    //   optional = true. Por defecto será -- si no se envía
    if (!isset($opts['defaultText']))
        $opts['defaultText'] = '--';
 
    //-- Obtenemos un string de los atributos del select pasados por el argumento
    //   $attrs ya que es un array
    $attributes = _get_attributes($attrs);
 
    $ret = "<select{$attributes}>";
 
    //-- Controlamos si existe la opctión "optional" que permitirá agregar
    //   automáticamente un primer option vacío
    $options = '';
    if (isset($opts['optional']) && $opts['optional'] === true)
    {
        //-- En caso de que no se haya enviado un valor por defecto, haremos que
        //   el valor por defecto sea un string vacío para que esta opción con el
        //   value="" sea marcada por defecto
        if (is_null($defaultValue))
            $defaultValue = '';
 
        //-- Finalmente llamamos al helper privado para crear nuestro <option>
        $options .= _select_option_helper('', $opts['defaultText'], $defaultValue, $optionsAttrs);
    }
 
    //-- Recorremos el array de opciones que recibimos por argumento. Hasta el
    //   momento no sabemos si el array tiene una sola dimensión o 2.
    foreach ($selectOptions as $key => $value)
    {
        //-- En el caso que notemos que el value sea nuevamente un array,
        //   tendremos un array de dos dimensiones y permitiremos
        //   crear un select con secciones utilizando el helper privado
        //   _select_options_helper. De lo contrario continuará normalmente
        //   llamando al helper _select_option_helper para crear una opción.
        if (is_array($value))
        {
            $options .= '<optgroup label="' . $key . '">';
            $options .= _select_options_helper($value, $defaultValue, $optionsAttrs);
            $options .= '</optgroup>';
        }
        else
        {
            $options .= _select_option_helper($key, $value, $defaultValue, $optionsAttrs);
        }
    }
 
    $ret .= $options;
    $ret .= '</select>';
 
    return $ret;
}
 
//-- Esto lo podríamos utilizar así
 
$paises = array(
    'América Latina' => array(
        'PY' => 'Paraguay',
        'AR' => 'Argentina',
        'UR' => 'Uruguay'
    ),
    'América del Norte' => array(
        'US' => 'Estados Unidos',
        'CA' => 'Canadá'
    ),
);
 
echo select_helper('pais', $paises, null, array(), array(
    'optional' => true,
    'defaultText' => '[Elija una opción]')
);

El HTML generado para estos ejemplos será

<select name="pais">
 
    <option value="">[Elija una opción]</option>
 
    <optgroup label="América Latina">
        <option value="PY">Paraguay</option>
        <option selected="selected" value="AR">Argentina</option>
        <option value="UR">Uruguay</option>
    </optgroup>
 
    <optgroup label="América del Norte">
        <option value="US">Estados Unidos</option>
        <option value="CA">Canadá</option>
    </optgroup>
 
</select>

Bueno, creo que hemos dado un montón de funcionalidad para este helper, lógicamente se puede agregar mucha más pero dejémoslo hasta aquí por el momento ya que estamos cubriendo lo más utilizado. Si tienen alguna idea sobre alguna otra función a agregar espero sus comentarios. Ya nos veremos la próxima con otro artículo sobre creación de helpers.

Los archivos de este artículos los puedes descargar aquí.

Comenta este artículo