Drupal 8: Textfield con autocomplete desde Google Place

Cabecera Drupal 8 - Textfield con autocomplete desde Google Place

Drupal 8: Textfield con autocomplete desde Google Place

  • Autor: fjavimartin

  • Fecha de Creación: 15/06/2017

  • Categorías:

    • Drupal,
    • Drupal 8,
    • Formularios,
    • Autocomplete,
    • Webservice,
    • Google

En este artículo modificaremos nuestro formulario básico para incluir un campo textfield que tenga configurado el autocompletado.

Una de las características más útiles que tienen los textfield es la de poder configurar una función de autocompletado que nos permita mostrar sugerencias al mismo tiempo que el usuario teclea. Para esta ocasión vamos a utilizar el web service de Google Place para ofrecer sugerencias de direcciones según va escribiendo el usuario.

1. Google Api Place

No es objetivo de este artículo ahondar mucho en los webservice de google, pero para este artículo necesitamos una clave para acceder al servicio de google place por lo que no nos queda más remedio.

En la siguiente url encontraremos toda la información necesaria para obtener una clave. Tendremos que abrir sesión en google console, abrir un proyecto en el que habilitar la api que vamos a utilizar y en credentials solicitar una clave que será una cadena parecida a la siguiente: AIzbSyAzFohLrhJU8iQTmXX7KXl4cVD9-KviEPU.

Una vez tengamos la clave podremos recibir sugerencias del servicio con una llamada web como la siguiente:

https://maps.googleapis.com/maps/api/place/autocomplete/json?input=velázquez&types=geocode&language=es&key=AIzbSyAzFohLrhJU8iQTmXX7KXl4cVD9-KviEPU

Una vez tengamos la clave podremos recibir sugerencias del servicio con una llamada web como la siguiente:

https://maps.googleapis.com/maps/api/place/autocomplete/json?input=velázquez&types=geocode&language=es&key=AIzbSyAzFohLrhJU8iQTmXX7KXl4cVD9-KviEPU

que nos devolverá un resultado parecido a lo siguiente:

{
   "predictions" : [
      {
         "description" : "Calle de Velázquez, Madrid, España",
         "id" : "ba765dc2bbbcc991a802fbbd16a30cf99e9f1cf6",
         "matched_substrings" : [
            {
               "length" : 18,
               "offset" : 0
            }
         ],
         "place_id" : "EiRDYWxsZSBkZSBWZWzDoXpxdWV6LCBNYWRyaWQsIEVzcGHDsWE",
         "reference" : "CjQoAAAAtOuKwJJHWtLnjrd1GAKWGxyxIB53g-JSXzccjHlGIODdOIc5wTvJ_D0PDONuggJLEhCdErLKLn5fUATo9sxB-w3oGhScVHsPA--uCEs2rsYPFPFiSsCzdQ",
         "structured_formatting" : {
            "main_text" : "Calle de Velázquez",
            "main_text_matched_substrings" : [
               {
                  "length" : 18,
                  "offset" : 0
               }
            ],
            "secondary_text" : "Madrid, España"
         },
         "terms" : [
            {
               "offset" : 0,
               "value" : "Calle de Velázquez"
            },
            {
               "offset" : 20,
               "value" : "Madrid"
            },
            {
               "offset" : 28,
               "value" : "España"
            }
         ],
         "types" : [ "route", "geocode" ]
      },
      {
         "description" : "Velázquez, Rocha, Uruguay",
         "id" : "4db009373b612e97ec713b049d1e9eee6a87eea8",
         "matched_substrings" : [
            {
               "length" : 9,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJocvbXsREC5URiixtf8yO9XI",
         "reference" : "CkQyAAAAut6rrDWifbSnm7y9DCzneo0QzTo3SpSF3yf1CZDPrBRHiyFgnFdRvkINoDa_yZsyOGF09xSG4w6rOB14Px9XwRIQNnlILVcy-85jRtrKhsUPGBoUMQgmzPLjbRquXZn39swIJkDiirQ",
         "structured_formatting" : {
            "main_text" : "Velázquez",
            "main_text_matched_substrings" : [
               {
                  "length" : 9,
                  "offset" : 0
               }
            ],
            "secondary_text" : "Rocha, Uruguay"
         },
         "terms" : [
            {
               "offset" : 0,
               "value" : "Velázquez"
            },
            {
               "offset" : 11,
               "value" : "Rocha"
            },
            {
               "offset" : 18,
               "value" : "Uruguay"
            }
         ],
         "types" : [ "locality", "political", "geocode" ]
      },
      {
         "description" : "Velazquez - Juan Bravo, Madrid, España",
         "id" : "beb147e53283b5b81818d4e8018233590e6e34ea",
         "matched_substrings" : [
            {
               "length" : 9,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJczOrb5UoQg0RcK3uxgVO7RM",
         "reference" : "CkQ1AAAALGLzisnY1dSHIDjEMw3kRRKC_RweF7vMpZwh3OSvXP3E6GhWD4fd0MWMIwRJlaNrd7L8mQzf8lTfr014XQ80zxIQUvvQ7jTcE0i6UwpPjwOpZRoURZfmUa9k-x9oE61ga2i43TadRn0",
         "structured_formatting" : {
            "main_text" : "Velazquez - Juan Bravo",
            "main_text_matched_substrings" : [
               {
                  "length" : 9,
                  "offset" : 0
               }
            ],
            "secondary_text" : "Madrid, España"
         },
         "terms" : [
            {
               "offset" : 0,
               "value" : "Velazquez - Juan Bravo"
            },
            {
               "offset" : 24,
               "value" : "Madrid"
            },
            {
               "offset" : 32,
               "value" : "España"
            }
         ],
         "types" : [ "transit_station", "point_of_interest", "establishment", "geocode" ]
      },
      {
         "description" : "Velazquéz, Los Polvorines, Buenos Aires, Argentina",
         "id" : "939e4bfaccd0030f15411da47c3eaea9527e34a0",
         "matched_substrings" : [
            {
               "length" : 9,
               "offset" : 0
            }
         ],
         "place_id" : "EjNWZWxhenF1w6l6LCBMb3MgUG9sdm9yaW5lcywgQnVlbm9zIEFpcmVzLCBBcmdlbnRpbmE",
         "reference" : "CkQ3AAAA5KpPor1t7meWcmdGqp2vjen0mNOPaC-dGV58lfP8HKZ1eOtOHh9vc73iEDNuyN52Cz47MFcJs5Z1m2TzBFMp_BIQqU7kulLnfSmbT8CcAJX9VRoU8lQYLxutUFIpL5Dm8YzTvx5SoGU",
         "structured_formatting" : {
            "main_text" : "Velazquéz",
            "main_text_matched_substrings" : [
               {
                  "length" : 9,
                  "offset" : 0
               }
            ],
            "secondary_text" : "Los Polvorines, Buenos Aires, Argentina"
         },
         "terms" : [
            {
               "offset" : 0,
               "value" : "Velazquéz"
            },
            {
               "offset" : 11,
               "value" : "Los Polvorines"
            },
            {
               "offset" : 27,
               "value" : "Buenos Aires"
            },
            {
               "offset" : 41,
               "value" : "Argentina"
            }
         ],
         "types" : [ "route", "geocode" ]
      },
      {
         "description" : "Velázquez de La Cadena, Pocitos y Rivera, Veracruz, México",
         "id" : "8a4ca1a00e43f561b7f5475410543af068ae36ca",
         "matched_substrings" : [
            {
               "length" : 9,
               "offset" : 0
            }
         ],
         "place_id" : "EjxWZWzDoXpxdWV6IGRlIExhIENhZGVuYSwgUG9jaXRvcyB5IFJpdmVyYSwgVmVyYWNydXosIE3DqXhpY28",
         "reference" : "CkRAAAAAg6KPdpkvzFeu4rdWIETSHRgPkn2-VHdxcFTzub5nCPsxwMeYq8TGdsES-R8Qt7ILJ1Ccgyybqq-FRsTYFJCxExIQ3r-IpVYNz8Vj38ceoahYyRoUdxSraab7NVXG1UO-O-4v8HeVAyo",
         "structured_formatting" : {
            "main_text" : "Velázquez de La Cadena",
            "main_text_matched_substrings" : [
               {
                  "length" : 9,
                  "offset" : 0
               }
            ],
            "secondary_text" : "Pocitos y Rivera, Veracruz, México"
         },
         "terms" : [
            {
               "offset" : 0,
               "value" : "Velázquez de La Cadena"
            },
            {
               "offset" : 24,
               "value" : "Pocitos y Rivera"
            },
            {
               "offset" : 42,
               "value" : "Veracruz"
            },
            {
               "offset" : 52,
               "value" : "México"
            }
         ],
         "types" : [ "route", "geocode" ]
      }
   ],
   "status" : "OK"
}

De esta respuesta nos interesa el valor de ‘status’ donde sabremos si todo a ido bien o no y ‘predictions’ donde cada una de las entradas será una de las sugerencias que nos envía el web service.

2. Modificación core/misc/autocomplete.js

Tanto las solicitudes de nuestros usuarios como las respuestas del servicio web serán cadenas de texto con cada uno de los componentes de la dirección separados por ‘,’. Esta coma no le sienta demasiado bien a drupal haciendo que solamente envíe a la función de autocompletado el último elemento de la cadena.

Para que todo quede más claro, para una solicitud de un cliente como la siguiente “ Calle de Velázquez, Madrid, España” solamente llegará a la función de autocompletado el último componente de la cadena, es decir, “España”, haciendo que los resultados devueltos por el servicio de google no sean los apropiados.

El encargado de gestionar este envío de información es el fichero ‘core/misc/autocomplete.js’ y la única manera de resolver este problema es modificarlo:

  • Modificaremos la línea 26 del fichero y cambiaremos ‘var quote = false;’ por ‘ var quote = true; ‘ en la función ‘ function autocompleteSplitValues(value) ‘.
  • Modificaremos en la línea 176 la función ‘function selectHandler(event, ui)’ para que quede como podéis ver aquí:
function selectHandler(event, ui) {
    var terms = autocomplete.splitValues(event.target.value);
    // Remove the current input.
    terms.pop();
    // Add the selected item.
    /*
    if (ui.item.value.search(',') > 0) {
      terms.push('"' + ui.item.value + '"');
    }
    else {
      terms.push(ui.item.value);
    }
   */
    terms.push(ui.item.value);
    event.target.value = terms.join(', ');
    // Return false to tell jQuery UI that we've filled in the value already.
    return false;
  }

Una vez realizadas todas estas modificaciones no olvidéis vaciar la caché de drupal para que todo vuelva a funcionar:

$ drush8 cache-rebuild

3. Modificación formulario

Hemos llegado al punto en el que retomamos nuestro antiguo formulario básico que nos vendrá que ni pintado para esta práctica y añadimos nuestro campo dirección:

$key = 'AIzbSyAzFohLrhJU8iQTmXX7KXl4cVD9-KviEPU';
    
    $form['direccion'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Dirección'),
        '#description' => $this->t('Añade una dirección'),
        '#autocomplete_route_name' => 'address_field.autocomplete_address',
        '#autocomplete_route_parameters' => array('key' => $key),
    ];

Solamente necesitamos dos propiedades para configurar el autocompletado:

  • autocomplete_route_name: Definiremos el identificador de la ruta que tendremos que configurar en nuestro fichero routing.yml y que veremos más adelante.
  • autocomplete_route_parameters: Añadiermos un array con los parámetros que deseamos pasar a la función de autocompletado. En nuestro caso pasaremos la clave que necesitará la llamada al web service de google.

4. Añadir ruta a .routing.yml

Añadimos otra entrada a nuestro fichero routing.yml donde definimos la ruta que tendrá nuestra función de autocompletado y que funcionará exactamente igual que el webservice de google, devolviendo un json con los resultados de la consulta:

equipos_form.autocomplete_address:
  path: '/equipos/autocomplete_address/{key}'
  defaults:
    _controller: '\Drupal\equipos_form\Controller\AutoCompleteController::address'
    _format: json
  requirements:
    _access: 'TRUE'

5. Crear controlador con función de autocompletado

Ahora vamos a crear un controlador para nuestro módulo que contendrá la función de autocompletado y que será la que finalmente conecte con el web service de google para finalmente devolver los resultados que se mostrarán en el campo de autocompletado.

Este controlador será muy parecido al que implementamos en el artículo donde creamos nuestro primer módulo en drupal 8 y mostramos un “Hola Mundo”.

namespace Drupal\equipos_form\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

class AutoCompleteController extends ControllerBase {
  public function address(Request $request, $key) {
    // Input contiene el texto que introduce el usuario
    $input = $request->query->get('q');
    $results = [];
    $url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json?' .
        'input=' . urlencode($input) .
        '&types=geocode' .
        '&language=es' .
        '&key=' . $key;
    
    // Realizamos la consulta al webservice de google place
    $response = file_get_contents($url);
    $json = json_decode($response);
    
    // Chequeamos si la consulta ha funcionado, en caso contrario
    // devolvemos un array vacío
    if ($json->status == 'OK') {
      foreach ($json->predictions as $key => $item) {
        $results[] = [
            'value' => $item->description,
            'label' => $item->description
        ];
      }
    }
    
    // Codificamos el array de resultado en json y lo devolvemos
    return new JsonResponse($results);
  }
}

Como veréis, el código del controlador es muy sencillo. Recogemos el input del textfield, componemos la url para la llamada al webservices, recogemos el json, componemos el array con los valores a devolver y lo devolvemos en formato json de nuevo.

Formulario con campo textfield y autocomplete funcionando

Drupal 8 - Formulario con campo textfield y autocomplete funcionando

6. Conclusiones

Partiendo de un formulario básico con un par de campos hemos aprendido a configurar la actualización de otro textfield mediante ajax y hemos configurado otro textfield con el autocomplete.

Proximamente seguiremos avanzando en esta línea, pero esta vez abordaremos la creación de fields mediante la nueva API.

Disfrutar!!!!!

Recursos

http://www.e-quipos.es/blog/drupal-8-creaci%C3%B3n-de-formularios

http://www.e-quipos.es/blog/drupal-8-actualizar-textfield-en-formulario-con-ajax

https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Controller!ControllerBase.php/class/ControllerBase/8.2.x

http://php.net/manual/es/function.json-decode.php

https://geekytheory.com/json-iv-ejemplo-practico-de-uso-de-json-con-openweathermap/

https://developers.google.com/places/web-service/?hl=es-419

Todos los Derechos Reservados © 2016

Funciona con Drupal