Taxonomy list dependent on choice from another tax

2019-08-24 07:11发布

问题:

I have a taxonomy option list make where I choose say Toyota.

  1. I want the second taxonomy option list with the models of Toyota only (Eg. Corolla, hilux etc...).
  2. When I choose Benz the second list will then contains C-Class, ML, etc...

I have created the entity vehicle from google examples on xampp localhost, windows 10.

In my vehicle form I'm able to populate the first list. But the second appears empty.

Here is my code. Please help:

public function buildForm(array $form, FormStateInterface $form_state, $params = NULL) {
  $options = array(); 
  $tax = "make";  
  $terms = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($tax, $parent = 0, $max_depth = NULL, $load_entities = FALSE);
  foreach ($terms as $term) {
    $options[] = $term->name;
  } 

  $form['make'] = array(
    '#type' => 'select',
    '#title' => t('Make'),
    'weight' => 0,
    '#options' => $options,
    '#ajax' => array(
      'callback' => [$this, 'changeOptionsAjax'],
      'wrapper' => 'model_wrapper',
    ),
  );

  $form['model'] = array(
    '#type' => 'select',
    '#title' => t('Model'),
    'weight' => 1,
    '#options' => $this->getOptions($form_state),
    '#prefix' => '<div id="model_wrapper">',
    '#suffix' => '</div>',
  );

  return $form;
}

public function getOptions(FormStateInterface $form_state) {
  $options = array();
  if ($form_state->getValue('make') == "Benz") { 
    $tax="benz";
  }
  elseif ($form_state->getValue('make') == "BMW") {
    $tax="bmw";
  } 
  elseif ($form_state->getValue('make') == "Toyota") {
    $tax="toyota";
  } 
  else {
    $tax="title";
    // title is just another taxonomy list I'm using as default if make is not found
  }

  $terms = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($tax, $parent = 0, $max_depth = NULL, $load_entities = FALSE);
  foreach ($terms as $term) {
    $options[] = $term->name;
  }  
  return $options;
}

public function changeOptionsAjax(array &$form, FormStateInterface $form_state) {
  return $form['model'];
}

回答1:

Here I give you a working sample based on your example VehiculesForm.php:

I took the liberty to rename some variable for better readability.

<?php

namespace Drupal\example\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * VehiculesForm.
 */
class VehiculesForm extends FormBase {
  /**
   * The term Storage.
   *
   * @var \Drupal\taxonomy\TermStorageInterface
   */
  protected $termStorage;

  /**
   * {@inheritdoc}
   */
  public function __construct(EntityTypeManagerInterface $entity) {
    $this->termStorage = $entity->getStorage('taxonomy_term');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    // Instantiates this form class.
    return new static(
    // Load the service required to construct this class.
    $container->get('entity_type.manager')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'vehicules_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $params = NULL) {
    $brands = $this->termStorage->loadTree('make', 0, NULL, TRUE);
    $options = [];
    if ($brands) {
      foreach ($brands as $brand) {
        $options[$brand->getName()] = $brand->getName();
      }
    }
    $form['brand'] = array(
      '#type'    => 'select',
      '#title'   => $this->t('brand'),
      '#options' => $options,
      '#ajax'    => array(
        'callback' => [$this, 'selectModelsAjax'],
        'wrapper'  => 'model_wrapper',
      ),
    );

    $form['model'] = array(
      '#type'      => 'select',
      '#title'     => $this->t('Model'),
      '#options'   => ['_none' => $this->t('- Select a brand before -')],
      '#prefix'    => '<div id="model_wrapper">',
      '#suffix'    => '</div>',
      '#validated' => TRUE,
    );

    $form['actions']['submit'] = [
      '#type'  => 'submit',
      '#value' => $this->t('Send'),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * Called via Ajax to populate the Model field according brand.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The form model field structure.
   */
  public function selectModelsAjax(array &$form, FormStateInterface $form_state) {
    $options = [];

    $vocabulary = 'title';
    switch ($form_state->getValue('brand')) {
      case 'Benz':
        $vocabulary = 'benz';
        break;
      case 'BMW':
        $vocabulary = 'bmw';
        break;
      case 'Toyota':
        $vocabulary = 'toyota';
        break;
    }

    $models = $this->termStorage->loadTree($vocabulary, 0, NULL, TRUE);
    if ($models) {
      foreach ($models as $model) {
        $options[$model->id()] = $model->getName();
      }
    }
    $form['model']['#options'] = $options;

    return $form['model'];
  }
}

Also, I suggest you to make some improvments on you code such:

  • Don't use a switch but link your taxonomies with a reference fields.
  • Add validation to ensure security (check we don't spoof your field for example) !!
  • Don't use the brandname but the ID. Avoid $options[$brand->getName()] = $brand->getName(); and use something like $options[$brand->id()] = $brand->getName();.

Hope it will help you !