You are here

public function ParagraphsWidget::formMultipleElements in Paragraphs 8

Special handling to create form elements for multiple values.

Handles generic features for multiple fields:

  • number of widgets
  • AHAH-'add more' button
  • table display and drag-n-drop value reordering

Overrides WidgetBase::formMultipleElements

File

src/Plugin/Field/FieldWidget/ParagraphsWidget.php, line 1012

Class

ParagraphsWidget
Plugin implementation of the 'entity_reference_revisions paragraphs' widget.

Namespace

Drupal\paragraphs\Plugin\Field\FieldWidget

Code

public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
  $field_name = $this->fieldDefinition
    ->getName();
  $cardinality = $this->fieldDefinition
    ->getFieldStorageDefinition()
    ->getCardinality();
  $this->fieldParents = $form['#parents'];
  $field_state = static::getWidgetState($this->fieldParents, $field_name, $form_state);
  $max = $field_state['items_count'];
  $entity_type_manager = \Drupal::entityTypeManager();

  // Consider adding a default paragraph for new host entities.
  if ($max == 0 && $items
    ->getEntity()
    ->isNew()) {
    $default_type = $this
      ->getDefaultParagraphTypeMachineName();

    // Checking if default_type is not none and if is allowed.
    if ($default_type) {

      // Place the default paragraph.
      $target_type = $this
        ->getFieldSetting('target_type');

      /** @var \Drupal\paragraphs\ParagraphInterface $paragraphs_entity */
      $paragraphs_entity = $entity_type_manager
        ->getStorage($target_type)
        ->create([
        'type' => $default_type,
      ]);
      $paragraphs_entity
        ->setParentEntity($items
        ->getEntity(), $field_name);
      $field_state['selected_bundle'] = $default_type;
      $display = EntityFormDisplay::collectRenderDisplay($paragraphs_entity, $this
        ->getSetting('form_display_mode'));
      $field_state['paragraphs'][0] = [
        'entity' => $paragraphs_entity,
        'display' => $display,
        'mode' => 'edit',
        'original_delta' => 1,
      ];
      $max = 1;
      $field_state['items_count'] = $max;
    }
  }
  $this->realItemCount = $max;
  $is_multiple = $this->fieldDefinition
    ->getFieldStorageDefinition()
    ->isMultiple();
  $field_title = $this->fieldDefinition
    ->getLabel();
  $description = FieldFilteredMarkup::create(\Drupal::token()
    ->replace($this->fieldDefinition
    ->getDescription()));
  $elements = array();
  $tabs = '';
  $this->fieldIdPrefix = implode('-', array_merge($this->fieldParents, array(
    $field_name,
  )));
  $this->fieldWrapperId = Html::getId($this->fieldIdPrefix . '-add-more-wrapper');

  // If the parent entity is paragraph add the nested class if not then add
  // the perspective tabs.
  $field_prefix = strtr($this->fieldIdPrefix, '_', '-');
  if (count($this->fieldParents) == 0) {
    if ($items
      ->getEntity()
      ->getEntityTypeId() != 'paragraph') {
      $tabs = '<ul class="paragraphs-tabs tabs primary clearfix"><li class="tabs__tab paragraphs_content_tab"><a href="#' . $field_prefix . '-values">' . $this
        ->t('Content', [], [
        'context' => 'paragraphs',
      ]) . '</a></li><li class="tabs__tab paragraphs_behavior_tab"><a href="#' . $field_prefix . '-values">' . $this
        ->t('Behavior', [], [
        'context' => 'paragraphs',
      ]) . '</a></li></ul>';
    }
  }
  if (count($this->fieldParents) > 0) {
    if ($items
      ->getEntity()
      ->getEntityTypeId() === 'paragraph') {
      $form['#attributes']['class'][] = 'paragraphs-nested';
    }
  }
  $elements['#prefix'] = '<div class="is-horizontal paragraphs-tabs-wrapper" id="' . $this->fieldWrapperId . '">' . $tabs;
  $elements['#suffix'] = '</div>';
  $field_state['ajax_wrapper_id'] = $this->fieldWrapperId;

  // Persist the widget state so formElement() can access it.
  static::setWidgetState($this->fieldParents, $field_name, $form_state, $field_state);
  if (!empty($field_state['dragdrop'])) {
    $elements['header_actions']['actions']['complete_button'] = $this
      ->expandButton([
      '#type' => 'submit',
      '#name' => $this->fieldIdPrefix . '_dragdrop_mode',
      '#value' => $this
        ->t('Complete drag & drop'),
      '#attributes' => [
        'class' => [
          'field-dragdrop-mode-submit',
        ],
      ],
      '#submit' => [
        [
          get_class($this),
          'dragDropModeSubmit',
        ],
      ],
      '#ajax' => [
        'callback' => [
          get_class($this),
          'dragDropModeAjax',
        ],
        'wrapper' => $this->fieldWrapperId,
      ],
      '#limit_validation_errors' => [
        array_merge($this->fieldParents, [
          $field_name,
        ]),
      ],
      '#button_type' => 'primary',
    ]);
    $elements['#attached']['library'][] = 'paragraphs/paragraphs-dragdrop';

    //$elements['dragdrop_mode']['#button_type'] = 'primary';
    $elements['dragdrop'] = $this
      ->buildNestedParagraphsFoDragDrop($form_state, NULL, []);
    return $elements;
  }
  if ($max > 0) {
    for ($delta = 0; $delta < $max; $delta++) {

      // Add a new empty item if it doesn't exist yet at this delta.
      if (!isset($items[$delta])) {
        $items
          ->appendItem();
      }

      // For multiple fields, title and description are handled by the wrapping
      // table.
      $element = array(
        '#title' => $is_multiple ? '' : $field_title,
        '#description' => $is_multiple ? '' : $description,
      );
      $element = $this
        ->formSingleElement($items, $delta, $element, $form, $form_state);
      if ($element) {

        // Input field for the delta (drag-n-drop reordering).
        if ($is_multiple) {

          // We name the element '_weight' to avoid clashing with elements
          // defined by widget.
          $element['_weight'] = array(
            '#type' => 'weight',
            '#title' => $this
              ->t('Weight for row @number', array(
              '@number' => $delta + 1,
            )),
            '#title_display' => 'invisible',
            // Note: this 'delta' is the FAPI #type 'weight' element's property.
            '#delta' => $max,
            '#default_value' => $items[$delta]->_weight ?: $delta,
            '#weight' => 100,
          );
        }

        // Access for the top element is set to FALSE only when the paragraph
        // was removed. A paragraphs that a user can not edit has access on
        // lower level.
        if (isset($element['#access']) && !$element['#access']) {
          $this->realItemCount--;
        }
        else {
          $elements[$delta] = $element;
        }
      }
    }
  }
  $field_state = static::getWidgetState($this->fieldParents, $field_name, $form_state);
  $field_state['real_item_count'] = $this->realItemCount;
  $field_state['add_mode'] = $this
    ->getSetting('add_mode');
  static::setWidgetState($this->fieldParents, $field_name, $form_state, $field_state);
  $elements += [
    '#element_validate' => [
      [
        $this,
        'multipleElementValidate',
      ],
    ],
    '#required' => $this->fieldDefinition
      ->isRequired(),
    '#field_name' => $field_name,
    '#cardinality' => $cardinality,
    '#max_delta' => $max - 1,
  ];
  $elements += [
    '#theme' => 'field_multiple_value_form',
    '#field_name' => $field_name,
    '#cardinality' => $cardinality,
    '#cardinality_multiple' => TRUE,
    '#required' => $this->fieldDefinition
      ->isRequired(),
    '#title' => $field_title,
    '#description' => $description,
    '#max_delta' => $max - 1,
  ];
  $host = $items
    ->getEntity();
  $this
    ->initIsTranslating($form_state, $host);
  $header_actions = $this
    ->buildHeaderActions($field_state, $form_state);
  if ($header_actions) {
    $elements['header_actions'] = $header_actions;

    // Add a weight element so we guaranty that header actions will stay in
    // first row. We will use this later in
    // paragraphs_preprocess_field_multiple_value_form().
    $elements['header_actions']['_weight'] = [
      '#type' => 'weight',
      '#default_value' => -100,
    ];
  }
  if (($this->realItemCount < $cardinality || $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) && !$form_state
    ->isProgrammed() && $this
    ->allowReferenceChanges()) {
    $elements['add_more'] = $this
      ->buildAddActions();

    // Add the class to hide the add actions in the Behavior perspective.
    $elements['add_more']['#attributes']['class'][] = 'paragraphs-add-wrapper';

    // Hidden field is provided for additional integrations, where also
    // position of addition can be specified. It should be used by sub-modules
    // or other paragraphs integration. CSS class is used to support easier
    // element selecting in JavaScript.
    $elements['add_more']['add_more_delta'] = [
      '#type' => 'hidden',
      '#attributes' => [
        'class' => [
          'paragraph-type-add-delta',
          $this
            ->getSetting('add_mode'),
        ],
      ],
    ];
  }
  $elements['#allow_reference_changes'] = $this
    ->allowReferenceChanges();
  $elements['#paragraphs_widget'] = TRUE;
  $elements['#attached']['library'][] = 'paragraphs/drupal.paragraphs.widget';
  if (\Drupal::theme()
    ->getActiveTheme()
    ->getName() == 'seven') {
    $elements['#attached']['library'][] = 'paragraphs/paragraphs.seven';
  }
  return $elements;
}