Pasar al contenido principal
Loading...

Campos autocompletables en Drupal 8

Submitted by yen.shun on Lunes, Julio 4, 2016 - 11:42.
blog-post-img

En muchos proyectos solemos necesitar campos que poseen muchas opciones a elegir, estas pueden ser categorías, tags, etc. Y tener una lista de opciones que ocupa la mayor parte de un formulario es un error de diseño. Para estos casos lo más recomendable es un campo autocompletable que se alimente y busque entre opciones específicas.


En drupal la implementación de un campo autocompletable es muy simple y utiliza el formAPI en la siguiente manera:

$form['user'] = array (
  '#type' => 'textfield',
  '#title => t('Test autocomplete'),
  '#required' => TRUE,
  '#autocomplete_path' => 'user/autocomplete',
);

 

Dicho campo es de texto y se autocompleta buscando entre los usuarios que dispone el sitio.

Pero en Drupal 8 la complejidad de este caso da una gran vuelta.

Para este pequeño ejemplo crearemos un campo autocompletable que permita que el usuario escoja un país al llenar el formulario.

En la ruta modules/custom de nuestro proyecto crearemos una carpeta llamada “paises” y crearemos, siguiendo los nuevos estándares de Drupal 8, nuestro archivo paises.info.yml con el siguiente contenido:

name: Paises
type: module
description: Define un campo para paises autocompletable.
package: Field types
core: 8.x
dependencies:
-field

En la estructura de nuestro módulo requerimos una carpeta llamada “src” que almacenará todas las clases de nuestro módulo, en este caso estamos creando un Plugin, por lo tanto crearemos la carpeta “src” y dentro de ella crearemos una carpeta llamada “Plugin”.

Para nuestro campo necesitaremos definir:

  • Tipo de campo

  • Widget

  • Formatter

 

Tipo de Campo:

Primero creamos la carpeta FieldType que almacenará nuestro Tipo de Campo y creamos el archivo que contendrá la clase (src\Plugin\Field\FieldType\PaisesItem.php,y luego definimos nuestro Tipo de Campo:

namespace Dupal\paises\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\FieldStorageDefinitionInterface;

/**
* Plugin implementation of the 'paises' field type
*
*@FieldType(
* id = "paises",
* label = @Translation("Paises"),
* description = @Translation("Almacena el nombre en dos caracteres para un pais."),
* category = @Translation("Custom"),
* default_widget = "paises_default",
* default_formatter = "paises_default"
*)
*/
class PaisesItem extends FieldItemBase {

}

Con el nuevo sistema de plugins, definimos la clase y con un comentario indicamos a Drupal todos los datos pertinentes (nombre máquina, etiqueta, y que widget y formatter usará el campo), además, declaramos que clases de Drupal utilizaremos en la implementación e indicamos el namespace de nuestra clase.

 

Dentro de nuestra clase indicaremos la etiqueta y las propiedades de nuestro campo, en este caso la etiqueta será “Países” y será un campo que contiene un string. con la función schema le indicamos a Drupal como almacenará el dato en la base de datos, será un char de 2 caracteres que no podrá ser NULL:

const PAISES_MAXLENGHT = 2;

/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
 $properties['value'] = DataDefinition::create('string')
 ->setLabel(t('Paises'));
 return $properties;
}

/**
* {@inheritoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
  return array(
    'columns' => array(
      'value' => array(
        'type' => 'char',
        'lenght' => static::PAISES_MAXLENGHT,
        'not null' => FALSE,
      ),
    ),
    'indexes' => array(
      'value' => array('value'),
    ),
  );
}

Con la clase clara, podemos crear nuestro Widget:
Al igual que el Tipo de Campo, crearemos la siguiente estructura de carpetas con el archivo: src\Plugin\Field\FieldWidget\PaisesDefaultWidget.php.

namespace Dupal\paises\Plugin\Field\FieldWidget;

use Drupal;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;

/**
* Plugin implementation of the 'paises_default' widget
*
*@FieldWidget(
*  id = "paises_default",
*  label = @Translation("Selección de país"),
*  field_types = {
*   "paises"
*  }
*)
*/
class PaisesDefaultWidget extends WidgetBase {
  public function formElement(FieldItemListInterface $items, $delta, array $element, array
  $form, FormStateInterface $form_state) {
    $countries = \Drupal::service('paises_manager')->getList();
    $element['value'] = $element + array (
      '#type' => 'select',
      '#options' => $countries,
      '#empty_value' => '',
      'default_value' => (isset($items[$delta]->value) && isset($countries[$items[$delta]
      ->value])) ? $items[$delta]->value : NULL,
    );
  }
}

 

De la misma forma vemos que definimos el namespace y las clases que usaremos, le indicamos a Drupal con un comentario la existencia del widget y dentro de nuestra clase creamos la función de formElement que creará el elemento a retornar cuando Drupal requiera el campo. Definimos el elemento utilizando el Form API y creando un campo de tipo select cuyas opciones serán la lista de países de Drupal tiene por default.

Finalmente, creamos el Widget que nos permitirá añadir la función de autocompletado:
Primero tendremos que crear la siguiente estructura de carpetas y archivos: src\Plugin\Field\FieldWidget\PaisesAutocompleteWidget.php

namespace Dupal\paises\Plugin\Field\FieldWidget;

use Drupal;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;

/**
* Plugin implementation of the 'paises_default' widget
*
*@FieldWidget(
*  id = "paises_autocomplete",
*  label = @Translation("Paises autocomplete widget"),
*  field_types = {
*   "paises"
*  }
*)
*/
class PaisesAutocompleteWidget extends WidgetBase {
  public static function defaultSettings() {
    return array(
      'size' => '60',
      'autocomplete_route_name' => 'paises.autocomplete',
      'placeholder' => '',
    ) + parent::defaultSettings();
  }
  public function formElement(FieldItemListInterface $items, $delta, array $element, array
  $form, FormStateInterface $form_state) {
    $countries = \Drupal::service('country_manager')->getList();
    $element['value'] = $element + array (
      '#type' => 'textfield',
      '#default_value' => (isset($items[$delta]->value) && isset(countries[$items[$delta]
      ->value])) ? $countries[$items[$delta]->value] : '',
      '#autocomplete_route_name' => $this->getSetting('autocomplete_route_name'),
      '#autocompelte_route_parameters' => array(),
      '#size' => $this->getSetting('placeholder'),
      '#maxlength' => 255,
      '#element_validate' => array('paises_autocomplete_validate'),
    );
  }
}

Nuevamente definimos el namespace de nuestra clase, las clases que utilizará y con el comentario señalamos a Drupal la existencia del widget.

Dentro de nuestra clase con la función defaultSettings definimos las características predeterminadas que recibirá el campo.

En la función formElement creamos el widget de autocompletado que no es más que un elemento que será el valor en nuestro previamente creado campo de tipo select. Lo alimentamos con los países por default de drupal y le indicamos la ruta que utilizará para verificar el autocompletado.

Creación de la ruta:
Ahora que tenemos todo nuestro código listo necesitamos crear la ruta que utilizará el autocompletado, para ello creamos el archivo paises.routing.yml en la raíz del módulo:

paises.autocomplete:
  path: '/paises/autocomplete'
  defaults:
    _controller: '\Drupal\paises\Controller\paisesAutocompleteController::autocomplete'
  requirements:
    _permission: 'administer content types'

El nombre de nuestra ruta es paises.autocomplete (el cual llamamos en nuestro widget) este nombre debe ser único y es el nombre por el que Drupal conocerá a la ruta.

En nuestro archivo de rutas mencionamos a un controlador que deberemos crear en: src/Controller/paisesAutocompleteController.php con el siguiente código:

/**
* @file
* Contains \Drupal\paises\Controller\paisesAutocompletController.
*/
namespace Dupal\paises\Controller;

use Drupal;
use Drupal\Component\Utility\Unicode;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

/**
* Returns autocomplete responses for countries.
*/

class paisesAutocompleteController {
  /**
  * Returns response for the paises name autocompletion
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The current request object containing the search string.
  *
  * @return \Symfony\Component\HttpFoundation\JsonResponse
  *   A JSON response containing the autocomplete suggestions for countries.
  */
  public function autocomplete(Request $request) {
    $matches = array();
    $string = $request->query->get('q');
    if ($string) {
      $countries = \Drupal::service('paises_manager')->getList();
      foreach ($countries as $iso2 => $paises) {
         if (strpos(Unicode::strtolower($paises), Unicode::strtolower($string)) !== FALSE) {
           $matches[] = array('value' => $paises, 'label' => $paises);
         }
      }
    }
    return new JsonResponse($matches);
  }
}

Cualquier cosa que escribamos en nuestro widget de autocompletado tendrá que pasar por nuestra función de autocompletado y luego de ello buscaremos ese texto en el array que tomamos desde el servicio de country_manager en Drupal. Si encontramos coincidencias las mostraremos como opciones.

Y ahora lo último que nos hace falta es el formatter que crearemos en: src\Plugin\Field\FieldFormatter\PaisesDefaultFormatter.php:

/**
* @file
* Definition of Drupal\paises\Plugin\field\formatter\paisesDefaultFormatter.
*/

namespace Drupal\paises\Plugin\Field\FieldFormatter;

use Drupal;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;

/**
* Plugin implementation of the 'paises' formatter.
*
* @FieldFormatter(
*   id= "paises_default",
*   module = "paises",
*   label = @Translation("paises"),
*   field_types = {
*     "paises"
*   }
* )
*/
class paisesDefaultFormatter extends FormatterBase {
  /**
  * {@inheritdoc}
  */
  public function viewElements(FieldItemListInterface $items) {
    $elements = array();
    $counties = \Drupal::service('country_manager')->getList();
    foreach ($items as $delta => $item) {
      if (isset($countries[$item->value])) {
        $elements[$delta] = array('#markup' => $countries[$item->value]);
      }
    }
    return $elements;
  }
}

 

El formatter permitirá que el dato que nosotros guardamos (2 caracteres) pueda mostrar el nombre del país cuando carguemos un contenido que utiliza nuestro campo así que básicamente hace un override de la función “viewElements” que Drupal siempre utiliza para mostrar un elemento.

Ahora solo nos queda agregar nuestro campo a un tipo de contenido desde Drupal y listo.

Categoría

Desarrollo Web
Diseño Gráfico
Soporte Web
Estrategias Web