You are here

protected function ComponentSectionForm::buildFormElement in Module Builder 8.3

Builds the form element for a data item.

This is called recursively for complex and multi-valued data items.

Parameters

array &$form: The parent form element (or the entire form), passed by reference. The data item's element is placed with an array key that is its machine name.

\Drupal\Core\Form\FormStateInterface $form_state: The form state.

\MutableTypedData\Data\DataItem $data: The data item.

3 calls to ComponentSectionForm::buildFormElement()
ComponentSectionForm::buildComplexFormElement in src/Form/ComponentSectionForm.php
Builds a form element with multiple child elements.
ComponentSectionForm::buildMultipleDeltaFormElement in src/Form/ComponentSectionForm.php
Builds a multi-valued form element.
ComponentSectionForm::componentPropertiesForm in src/Form/ComponentSectionForm.php
Add form elements for the specified component properties.

File

src/Form/ComponentSectionForm.php, line 162

Class

ComponentSectionForm
Generic form for entering a section of data for a component.

Namespace

Drupal\module_builder\Form

Code

protected function buildFormElement(&$form, FormStateInterface $form_state, DataItem $data) {
  $element = [];

  // Determine whether to handle multiple data as a single element or a set
  // of deltas.
  // Multiple-valued data gets a set of items that can be added and removed
  // with AJAX buttons...
  $use_multiple_deltas = $data
    ->isMultiple();

  // ... with exceptions: simple data with options that is multiple-
  // valued is just a SELECT element.
  if (!$data
    ->isComplex() && $data
    ->hasOptions()) {
    $use_multiple_deltas = FALSE;
  }

  // ... multiple simple data without options is shown as a text area.
  if (!$data
    ->isComplex()) {
    $use_multiple_deltas = FALSE;
  }

  // Case 1: multiple deltas each handled as a separate form element, within
  // a details wrapper.
  if ($use_multiple_deltas) {
    $element = $this
      ->buildMultipleDeltaFormElement($form, $form_state, $data);
  }
  elseif ($data
    ->getDefinition()
    ->isComplex()) {
    $element = $this
      ->buildComplexFormElement($form, $form_state, $data);
  }
  else {

    // Case 3A: element with options.
    if ($data
      ->hasOptions()) {
      $options = [];
      $options_have_descriptions = FALSE;
      foreach ($data
        ->getOptions() as $value => $option) {
        $options[$value] = $option
          ->getLabel();
        if ($description = $option
          ->getDescription()) {

          // Set the description on each value. This is a not-terribly-well
          // documented feature in FormAPI. This relies on us not clobbering
          // $element further on!
          $element[$value]['#description'] = $description;
          $options_have_descriptions = TRUE;
        }
      }
      $options_count = count($options);
      if ($data
        ->isMultiple()) {
        $element_type = 'checkboxes';

        // Build up a default value array for the checkboxes from the data's
        // delta items.
        $default_value = [];
        foreach ($data as $delta => $delta_item) {
          $default_value[$delta_item->value] = $delta_item->value;
        }
      }
      else {
        if ($options_count > 8 && !$options_have_descriptions) {
          $element_type = 'select';
          $default_value = $data->value;
        }
        else {
          $element_type = 'radios';
          $default_value = $data->value;
        }
      }
      natcasesort($options);
      $element += [
        '#type' => $element_type,
        '#title' => $data
          ->getLabel(),
        '#description' => $data
          ->getDescription(),
        '#default_value' => $default_value,
        '#options' => $options,
        // ARGH why isn't this happening automatically like it's supposed to?
        '#empty_option' => $data
          ->isRequired() ? $this
          ->t('- Select -') : $this
          ->t('- None -'),
        '#empty_value' => NULL,
      ];

      // Special handling for injected services: textfield with autocomplete.
      if (in_array($data
        ->getName(), [
        'injected_services',
        'container_services',
        'mocked_services',
      ])) {
        $element['#type'] = 'textfield';

        // This needs to be massive to allow lots of services!
        $element['#maxlength'] = 512;
        $element['#description'] .= ' ' . $this
          ->t("Enter a comma-separated list of names.");
        $element['#default_value'] = implode(', ', $default_value);
        $element['#autocomplete_route_name'] = 'module_builder.autocomplete';
        $element['#autocomplete_route_parameters'] = [
          'property_address' => $data
            ->getAddress(),
        ];

        // Remove the options, as it makes FormAPI think the value must be
        // compared against them.
        unset($element['#options']);
      }
      if ($data
        ->isVariantProperty()) {

        // Put this above the 'Update variant properties' button; compare
        // with the weight set on that.
        $element['#weight'] = -20;
        $wrapper_id = Html::getId($data
          ->getParent()
          ->getAddress() . '-mutable-wrapper');
        $variant_property_form_address = explode(':', $data
          ->getAddress());
        $variant_property_address = array_merge($variant_property_form_address);
        $values = $form_state
          ->getValues();
        $variant_value = NestedArray::getValue($values, $variant_property_address);
        if (isset($variant_value)) {
          $data
            ->set($variant_value);
        }
      }
    }
    elseif ($data
      ->getType() == 'boolean') {
      $element += [
        '#type' => 'checkbox',
        '#title' => $data
          ->getLabel(),
        '#description' => $data
          ->getDescription(),
        '#default_value' => $data->value,
      ];
    }
    elseif ($data
      ->isMultiple()) {
      $element += [
        '#type' => 'textarea',
        '#title' => $data
          ->getLabel(),
        '#description' => $data
          ->getDescription(),
        '#default_value' => implode("\n", $data
          ->export()),
      ];
    }
    else {
      $element += [
        '#type' => 'textfield',
        '#title' => $data
          ->getLabel(),
        '#description' => $data
          ->getDescription(),
        '#default_value' => $data->value,
      ];
    }

    // Make form elements required if the data is required, unless there is
    // a default, in which case, either the JS will set it, or data validation
    // will set it on submission, so there's no need to force the user to
    // enter something.
    if ($data
      ->isRequired() && !$data
      ->getDefault()) {
      $element['#required'] = TRUE;
    }
    $element['#attributes']['data-typed-data-address'] = $data
      ->getAddress();

    // dsm($data->getDefault());
    // Note need parentheses around the assignment because of precedence
    // relative to &&.
    if (($default = $data
      ->getDefault()) && $default
      ->getType() == 'expression') {
      $expression = $default
        ->getExpressionWithAbsoluteAddresses($data);

      // Prefix custom EL functions with the JS namespace.
      // TODO: Would be nice to get the names from the EL rather than
      // hardcode them!
      $expression = str_replace('get(', 'DataAddressExpressionLanguage.get(', $expression);
      $expression = str_replace('machineToClass(', 'DataAddressExpressionLanguage.machineToClass(', $expression);
      $expression = str_replace('machineToLabel(', 'DataAddressExpressionLanguage.machineToLabel(', $expression);
      $expression = str_replace('stripBefore(', 'DataAddressExpressionLanguage.stripBefore(', $expression);
      $dependencies = $default
        ->getDependencies();
      if (!empty($dependencies)) {

        // CHEAT; for now only ever one dependency!
        // TODO: this only works for addresses that go up only one level!
        $dependencies[0] = str_replace('..:', $data
          ->getParent()
          ->getAddress() . ':', $dependencies[0]);
      }
      $element['#attached']['drupalSettings']['moduleBuilder']['typedDataDefaults']['defaults'][$this
        ->getFormElementNameFromData($data)] = [
        'dependencies' => $dependencies,
        'expression' => $expression,
      ];
      foreach ($dependencies as $dependency) {
        $element['#attached']['drupalSettings']['moduleBuilder']['typedDataDefaults']['reactions'][$dependency] = $this
          ->getFormElementNameFromData($data);
      }
    }
  }
  $element['#tree'] = TRUE;
  $form_key = $data
    ->getName();
  $form[$form_key] = $element;
}