0

i just done build a custom element based on this tutorial: https://docs.acquia.com/articles/drupal-8-creating-elements-theming-elements-and-managing-frontend-libraries

so let say my element type is "location"

so i have:

$form["location"] = [
      '#type' => 'location',
      '#label' => $this->t('Supplier Location'),
      '#theme' => 'supplier_location',
];

so inside type location there are a couple fields (e.g. textfield, select, etc.)

the problem here is: when i submit my form, i couldn't get those value:

  public function submitForm(array &$form, FormStateInterface $form_state) {
    foreach ($form_state->getValues() as $key => $value) {
        drupal_set_message($key . ': ' . $value); // i cant catch my custom element value here T_T why?
    }    
  }

so apparently my custom element field value is not catched in submitform during submit, any idea why?

here is my custom element class code: actually, below is the sample working code after discussion with @clive

<?php
/**
 * @file
 * Contains \Drupal\module\Element\Location.
 */

namespace Drupal\module\Element;

use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides a location element.
 * @FormElement("location")
 */
class Location extends FormElement {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#element_validate' => [[$class, 'validateLocation']],
      '#process' => [[$class, 'processLocation'],[$class, 'processGroup']],
      '#pre_render' => [[$class, 'preRenderGroup']],
      '#theme' => 'location',
      //'#theme_wrappers' => ???, //what is theme wrappers?
    ];
  }

  /**
   * Prepare the render array for the template.
   */
  public static function processLocation(&$element, FormStateInterface $form_state, &$complete_form) {

    $countries = \Drupal::service('country_manager')->getList();
    $element['address'] = [
        '#type' => 'textfield',
        '#title' => t('Address: '),
        '#description' => t('Your Address'),
        '#default_value' => "",
        '#size' => 136,
        '#required' => FALSE,
        '#attributes' => [
          'id' => 'id',
          'placeholder' => 'Enter your address here',
        ]
    ];

    $element['zip'] = [
        '#type' => 'textfield',
        '#title' => t('Zip: '),
        '#description' => t('Zip Code'),
        '#default_value' => "",
        '#size' => 136,
        '#required' => FALSE,
        '#attributes' => [
          'id' => 'id',
          'placeholder' => 'Enter your Zip Code here',
        ]
    ];

    $element['city'] = [
        '#type' => 'textfield',
        '#title' => t('City: '),
        '#description' => t('Select City'),
        '#default_value' => "",
        '#size' => 136,
        '#required' => FALSE,
        '#attributes' => [
          'id' => 'id',
          'placeholder' => 'Enter your city here',
        ]
    ];

    $element['state'] = [
        '#type' => 'textfield',
        '#title' => t('State: '),
        '#description' => t('Select State'),
        '#default_value' => "",
        '#size' => 136,
        '#required' => FALSE,
        '#attributes' => [
          'id' => 'id',
          'placeholder' => 'Enter your State here',
        ]
    ];

    $element['country'] = [
        '#type' => 'select',
        '#title' => t('Country: '),
        '#options' => $countries,
        '#default_value' => '',
        '#required' => TRUE,
        '#attributes' => [
          'id' => 'id',
        ]
    ];

    $element['latitude'] = [
        '#type' => 'hidden',
        '#title' => t('latitude: '),
        '#default_value' => "",
        '#attributes' => [
          'id' => 'id',
        ]
    ];

    $element['longitude'] = [
        '#type' => 'hidden',
        '#title' => t('longitude: '),
        '#default_value' => "",
        '#attributes' => [
          'id' => 'id',
        ]
    ];

    $element['zoom'] = [
        '#type' => 'hidden',
        '#title' => t('zoom: '),
        '#default_value' => "",
        '#attributes' => [
          'id' => 'id',
        ]
    ];

    // Add the library
    $element['#attached'] = ['library' => ['module/arm_location_field_library']];

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
      //what should i do here?
      return $input;
  }

  /**
   * Validation callback for a location element.
   *
   * @param array $element
   *   The form element whose value is being validated.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   */
  public static function validateLocation(&$element, FormStateInterface $form_state, &$complete_form) {
    //what should i do here?
  }

}
7
  • Without seeing your code it's difficult to say - what does your valueCallback() method look like for the custom element type?
    – Clive
    Jul 22 '16 at 15:52
  • I update the code in question
    – AnD
    Jul 22 '16 at 15:56
  • i dont have valueCallback() method, where should i put it?
    – AnD
    Jul 22 '16 at 16:01
  • It goes in the form element class, you're overriding FormElement::valueCallback(). Pretty sure that's what you're missing
    – Clive
    Jul 22 '16 at 16:02
  • hmm thanks, let me explore that, do you have any reference, if i may? on how to override this?
    – AnD
    Jul 22 '16 at 16:16
2

Your form element needs to extend the FormElement class, rather than RenderElement.

Because of these few lines of code in ElementInfoManager::buildInfo:

// If this is element is to be used exclusively in a form, denote that it
// will receive input, and assign the value callback.
if ($element instanceof FormElementInterface) {
  $element_info['#input'] = TRUE;
  $element_info['#value_callback'] = array($definition['class'], 'valueCallback');
}

along with these in FormBuilder::doBuildForm:

// Handle input elements.
if (!empty($element['#input'])) {
  $this->handleInputElement($form_id, $element, $form_state);
}

more generic render elements won't be considered capable of providing input, and their values won't make it into the form state.

To fix (untested):

  • Replace use Drupal\Core\Render\Element\RenderElement with use Drupal\Core\Render\Element\FormElement
  • Replace @RenderElement("location") with @FormElement("location") in the annotation
  • Change your class to extend FormElement: class Location extends FormElement
  • Rebuild the cache
3
  • Thanks, I changed it into FormElement without valueCallback() and add #input => True in getInfo(); when i try to print, it detect the variable "location" but the value is empty
    – AnD
    Jul 23 '16 at 15:32
  • I mark you as an answer - thank you for the help, i have update my code in question for anyone who like to see it. in general it is worked, i got the value passed down. just a minor thing: if you see in my buildForm(), i use $form['location'] with type 'location', ideally you should receive the value under/inside ['location'] from $form_state->getValues(); however, what i receive is: ['location'] = empty, ['address'] = correct, ['zip'] = correct, ['city'] = correct, etc. as an individual array (not inside ['location'] array) - how to make it inside ['location']? just to make it tidy.
    – AnD
    Jul 24 '16 at 16:02
  • Adding '#tree' => TRUE to the element definition in getInfo makes sure that element values are properly wrapped inside the main element.
    – berliner
    Jan 9 '20 at 23:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.