Drupal 8: Creación de campos con Field API (Parte III) Ajax

Imagen Drupal 8: Creación de campos con Field API (Parte III) Ajax

Drupal 8: Creación de campos con Field API (Parte III) Ajax

  • Autor: fjavimartin

  • Fecha de Creación: 04/07/2017

  • Categorías:

    • Drupal,
    • Drupal 8,
    • Field,
    • Field api,
    • Ajax,
    • Google,
    • Webservice

Utilizaremos ajax para actualizar los campos latitud y longitud en función de la dirección introducida previamente para la que hemos utilizado autocomplete.

Continuamos introduciendo mejoras en el formulario de nuestro widget. En esta ocasión configuraremos ajax en el campo dirección para que una vez introducida la dirección se actualicen los valores de latitud/longitud.

1. Modificación del formulario de configuración del widget

Dado que vamos a necesitar obtener latitud/longitud de una dirección introducida añadiremos un campo más al formulario de configuración del widget y este deberá contener la clave para consultar al servicio web google geocode.

/**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return [
      'gplaceapikey' => 'keyapigooglepalces',
      'ggeocodeapikey' => 'keyapigooglegeocode'
    ] + parent::defaultSettings();
  }
/**
   * {@inheritdoc}
   */
  
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $elements = [];
    
    $elements['gplaceapikey'] = [
      '#type' => 'textfield',
      '#title' => t('Google Place Key Api'),
      '#default_value' => $this->getSetting('gplaceapikey'),
      '#description' => t('Intro Google Key Api for Google Places Web Service'),
      '#required' => TRUE,
    ];
    
    $elements['ggeocodeapikey'] = [
        '#type' => 'textfield',
        '#title' => t('Google Geocode Key Api'),
        '#default_value' => $this->getSetting('ggeocodeapikey'),
        '#description' => t('Intro Google Key Api for Google Geocode Web Service'),
        '#required' => TRUE,
    ];
    
    return $elements;
  }

/**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = [];

    $summary[] = t('Google Place Api Key: @gplaceapikey', 
        ['@gplaceapikey' => $this->getSetting('gplaceapikey')]);
    
    $summary[] = t('Google Geocode Api Key: @ggeocodeapikey', 
        ['@ggeocodeapikey' => $this->getSetting('ggeocodeapikey')]);

    return $summary;
  }

Simplemente hemos añadido un campo más al formulario de configuración y esto también implica añadir una línea más al resumen de configuración.

2. Modificación del widget

Cada vez que un usuario termine de introducir una dirección, cuando este campo pierda el foco, se ejecutará una función que devolverá latitud/longitud de la dirección introducida.

/**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $key = $this->getSetting('gplaceapikey');
    $fieldclass = isset($items[$delta]) ? $items[$delta] : NULL;
    $idajax = 'field-coordinates-' . $delta;
    
    $element['address'] = [
        '#type' => 'textfield',
        '#title' => t('Address'),
        '#prefix' => '<div id="field-address">',
        '#suffix' => '</div>',
        '#default_value' => 
          isset($fieldclass) ? $fieldclass->get('address')->getValue() : NULL,       
        '#autocomplete_route_name' => 'address_field.autocomplete_address',
        '#autocomplete_route_parameters' => array('key' => $key),
        '#ajax' => [
            'callback' => [ $this, 'geocodeAddress'],
            'wrapper' => $idajax,
            'disable-refocus' => TRUE,
        ],
        '#delta' => $delta,
    ];
    
    $element['latitude'] = [
        '#type' => 'textfield',
        '#title' => t('Address Latitude'),
        '#prefix' => '<div id="'. $idajax .'">',
        '#default_value' => 
          isset($fieldclass) ? $fieldclass->get('latitude')->getValue() : NULL,
    ];
    
    $element['longitude'] = [
        '#type' => 'textfield',
        '#title' => t('Address Longitude'),
        '#suffix' => '</div>',
        '#default_value' => 
          isset($fieldclass) ? $fieldclass->get('longitude')->getValue() : NULL,
    ];
    
    return $element;
  }

Siempre tenemos que tener muy presente que podemos tener múltiples instancias de nuestro campo en el mismo contenido por lo que será muy importante el valor de la variable $delta que nos dirá exactamente cuál es el campo que estamos modificando en cada momento. Aprovecharemos el momento de creación del formulario para introducir este valor ya que luego nos vendrá muy bien capturarlo en la función ajax que veremos más adelante.

Como podéis ver en el código, lo primero que hacemos es determinar el identificador del elemento html que modificará nuestra función ajax y lo guardaremos en la variable $idajax.

Configuramos ajax en el elemento address:

'#ajax' => [
            'callback' => [ $this, 'geocodeAddress'],
            'wrapper' => $idajax,
            'disable-refocus' => TRUE,
        ],

Simplemente indicamos el nombre de la función que se ejecutará cuando se lance el evento correspondiente, el identificador del elemento que sustituiremos y del que hemos hablado previamente y fijaremos a TRUE el valor de la propiedad disable-refocus. Está última propiedad eliminará un comportamiento un tanto extraño de drupal, hace que cuando se pierda el foco y se lance el evento ajax, el foco vuelva al elemento que ha lanzado el evento haciendo que estemos en bucle sin salida.

Deberemos marcar los elementos que sobrescribirá nuestra función ajax por lo que introduciremos en los elementos latitud/longitud las propiedades prefix en el primero y suffix en el segundo.

Vamos a echar un ojo a la función que lanzará el evento ajax:

public function geocodeAddress(array &$form, FormStateInterface $form_state) {    
    $triggerelement = $form_state->getTriggeringElement();
    $address = $triggerelement['#value'];
    $delta = $triggerelement['#delta'];
    
    $key = $this->getSetting('ggeocodeapikey');
    $url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' . urlencode($address) . 
      '&key=' . $key;
     
    $response = file_get_contents($url);
    $json = json_decode($response);
    
    if ($json->status != 'OK') {
      $latitude = 0;
      $longitude = 0;
    } else {
      $results = $json->results;
      $latitude = $results[0]->geometry->location->lat;
      $longitude = $results[0]->geometry->location->lng;
    }
    
    $element = [];
    $element['latitude'] = $form['field_direccion']['widget'][$delta]['latitude'];
    $element['longitude'] = $form['field_direccion']['widget'][$delta]['longitude'];
    
    $element['latitude']['#value'] = $latitude;
    $element['longitude']['#value'] = $longitude;
     
    return $element;
  }

La función $form_state→getTriggeringElement() nos devolverá el elemento que ha lanzado el evento ajax y dado que hemos introducido el valor $delta en este último sabremos exactamente que parte del widget vamos a sobrescribir.

La siguiente parte de nuestro método simplemente consultará al servicio web de google con la dirección que introduce el usuario y capturamos latitud/longitud si todo ha ido bien.

Por último capturamos los elementos del formulario cuyos valores vamos a actualizar y los devolvemos. Siempre tendremos que restacar esta parte del formulario y nunca crearlos de nuevo porque entonces cuando guardemos el contenido drupal sabrá en que parte del formulario están los valores que debe guardar en la base de datos. Para verlo con más claridad simplemente tendréis que echar un ojo al código html de los elementos que drupal genera.

Conclusiones

Con esta modificación en nuestro widget actualizamos los valores de latitud/longitud correctamente, pero no es la solución definitiva. Si una vez introducida la dirección pulsamos directamente en “Guardar y mantener publicado” no se guardarán los valores que actualizamos con la función ajax.

El motivo de este comportamiento es bien sencillo, ajax funciona de manera asíncrona y cuando pulsamos en guardar todavía no ha terminado de ejecutarse la función por lo que siempre se guardará en la base de datos los valores anteriores o lo que haya configurados por defecto.

En la próxima entrega veremos como resolver este problema. No os la perdáis.

Recursos

http://www.e-quipos.es/blog/drupal-8-creaci%C3%B3n-de-campos-con-field-api

http://www.e-quipos.es/blog/drupal-8-creaci%C3%B3n-de-campos-con-field-api-parte-ii-autocomplete

https://api.drupal.org/api/drupal/core!core.api.php/group/ajax/8.2.x

Todos los Derechos Reservados © 2016

Funciona con Drupal