You are here

class MultiStepDisplay in Entity Browser 8

Same name and namespace in other branches
  1. 8.2 src/Plugin/EntityBrowser/SelectionDisplay/MultiStepDisplay.php \Drupal\entity_browser\Plugin\EntityBrowser\SelectionDisplay\MultiStepDisplay

Show current selection and delivers selected entities.

Plugin annotation


@EntityBrowserSelectionDisplay(
  id = "multi_step_display",
  label = @Translation("Multi step selection display"),
  description = @Translation("Shows the current selection display, allowing to mix elements selected through different widgets in several steps."),
  acceptPreselection = TRUE,
  js_commands = TRUE
)

Hierarchy

Expanded class hierarchy of MultiStepDisplay

File

src/Plugin/EntityBrowser/SelectionDisplay/MultiStepDisplay.php, line 27

Namespace

Drupal\entity_browser\Plugin\EntityBrowser\SelectionDisplay
View source
class MultiStepDisplay extends SelectionDisplayBase {

  /**
   * Field widget display plugin manager.
   *
   * @var \Drupal\entity_browser\FieldWidgetDisplayManager
   */
  protected $fieldDisplayManager;

  /**
   * Constructs widget plugin.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   Event dispatcher service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
   *   Field widget display plugin manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, FieldWidgetDisplayManager $field_display_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $event_dispatcher, $entity_type_manager);
    $this->fieldDisplayManager = $field_display_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('event_dispatcher'), $container
      ->get('entity_type.manager'), $container
      ->get('plugin.manager.entity_browser.field_widget_display'));
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'entity_type' => 'node',
      'display' => 'label',
      'display_settings' => [],
      'select_text' => 'Use selected',
      'selection_hidden' => 0,
    ] + parent::defaultConfiguration();
  }

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

    // Check if trigger element is dedicated to handle front-end commands.
    if (($triggering_element = $form_state
      ->getTriggeringElement()) && $triggering_element['#name'] === 'ajax_commands_handler' && !empty($triggering_element['#value'])) {
      $this
        ->executeJsCommand($form_state);
    }
    $selected_entities = $form_state
      ->get([
      'entity_browser',
      'selected_entities',
    ]);
    $form = [];
    $form['#attached']['library'][] = 'entity_browser/multi_step_display';
    $form['selected'] = [
      '#theme_wrappers' => [
        'container',
      ],
      '#attributes' => [
        'class' => [
          'entities-list',
        ],
      ],
      '#tree' => TRUE,
    ];
    if ($this->configuration['selection_hidden']) {
      $form['selected']['#attributes']['class'][] = 'hidden';
    }
    foreach ($selected_entities as $id => $entity) {
      $display_plugin = $this->fieldDisplayManager
        ->createInstance($this->configuration['display'], $this->configuration['display_settings'] + [
        'entity_type' => $this->configuration['entity_type'],
      ]);
      $display = $display_plugin
        ->view($entity);
      if (is_string($display)) {
        $display = [
          '#markup' => $display,
        ];
      }
      $form['selected']['items_' . $entity
        ->id() . '_' . $id] = [
        '#theme_wrappers' => [
          'container',
        ],
        '#attributes' => [
          'class' => [
            'item-container',
          ],
          'data-entity-id' => $entity
            ->id(),
        ],
        'display' => $display,
        'remove_button' => [
          '#type' => 'submit',
          '#value' => $this
            ->t('Remove'),
          '#submit' => [
            [
              get_class($this),
              'removeItemSubmit',
            ],
          ],
          '#name' => 'remove_' . $entity
            ->id() . '_' . $id,
          '#attributes' => [
            'class' => [
              'entity-browser-remove-selected-entity',
            ],
            'data-row-id' => $id,
            'data-remove-entity' => 'items_' . $entity
              ->id(),
          ],
        ],
        'weight' => [
          '#type' => 'hidden',
          '#default_value' => $id,
          '#attributes' => [
            'class' => [
              'weight',
            ],
          ],
        ],
      ];
    }

    // Add hidden element used to make execution of front-end commands.
    $form['ajax_commands_handler'] = [
      '#type' => 'hidden',
      '#name' => 'ajax_commands_handler',
      '#id' => 'ajax_commands_handler',
      '#attributes' => [
        'id' => 'ajax_commands_handler',
      ],
      '#ajax' => [
        'callback' => [
          get_class($this),
          'handleAjaxCommand',
        ],
        'wrapper' => 'edit-selected',
        'event' => 'execute_js_commands',
        'progress' => [
          'type' => 'fullscreen',
        ],
      ],
    ];
    $form['use_selected'] = [
      '#type' => 'submit',
      '#value' => $this->configuration['select_text'],
      '#name' => 'use_selected',
      '#attributes' => [
        'class' => [
          'entity-browser-use-selected',
          'button--primary',
        ],
      ],
      '#access' => empty($selected_entities) ? FALSE : TRUE,
    ];
    $form['show_selection'] = [
      '#type' => 'button',
      '#value' => $this
        ->t('Show selected'),
      '#attributes' => [
        'class' => [
          'entity-browser-show-selection',
        ],
      ],
      '#access' => empty($selected_entities) ? FALSE : TRUE,
    ];
    return $form;
  }

  /**
   * Execute command generated by front-end.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state object.
   */
  protected function executeJsCommand(FormStateInterface $form_state) {
    $triggering_element = $form_state
      ->getTriggeringElement();
    $commands = json_decode($triggering_element['#value'], TRUE);

    // Process Remove command.
    if (isset($commands['remove'])) {
      $entity_ids = $commands['remove'];

      // Remove weight of entity being removed.
      foreach ($entity_ids as $entity_info) {
        $entity_id_info = explode('_', $entity_info['entity_id']);
        $form_state
          ->unsetValue([
          'selected',
          $entity_info['entity_id'],
        ]);

        // Remove entity itself.
        $selected_entities =& $form_state
          ->get([
          'entity_browser',
          'selected_entities',
        ]);
        unset($selected_entities[$entity_id_info[2]]);
      }
      static::saveNewOrder($form_state);
    }

    // Process Add command.
    if (isset($commands['add'])) {
      $entity_ids = $commands['add'];
      $entities_to_add = [];
      $added_entities = [];

      // Generate list of entities grouped by type, to speed up loadMultiple.
      foreach ($entity_ids as $entity_pair_info) {
        $entity_info = explode(':', $entity_pair_info['entity_id']);
        if (!isset($entities_to_add[$entity_info[0]])) {
          $entities_to_add[$entity_info[0]] = [];
        }
        $entities_to_add[$entity_info[0]][] = $entity_info[1];
      }

      // Load Entities and add into $added_entities, so that we have list of
      // entities with key - "type:id".
      foreach ($entities_to_add as $entity_type => $entity_type_ids) {
        $indexed_entities = $this->entityTypeManager
          ->getStorage($entity_type)
          ->loadMultiple($entity_type_ids);
        foreach ($indexed_entities as $entity_id => $entity) {
          $added_entities[implode(':', [
            $entity_type,
            $entity_id,
          ])] = $entity;
        }
      }

      // Array is accessed as reference, so that changes are propagated.
      $selected_entities =& $form_state
        ->get([
        'entity_browser',
        'selected_entities',
      ]);

      // Fill list of selected entities in correct order with loaded entities.
      // In this case, order is preserved and multiple entities with same ID
      // can be selected properly.
      foreach ($entity_ids as $entity_pair_info) {
        $selected_entities[] = $added_entities[$entity_pair_info['entity_id']];
      }
    }
  }

  /**
   * Handler to generate Ajax response, after command is executed.
   *
   * @param array $form
   *   Form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state object.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   Return Ajax response with commands.
   */
  public static function handleAjaxCommand(array $form, FormStateInterface $form_state) {
    $ajax = new AjaxResponse();
    if (($triggering_element = $form_state
      ->getTriggeringElement()) && $triggering_element['#name'] === 'ajax_commands_handler' && !empty($triggering_element['#value'])) {
      $commands = json_decode($triggering_element['#value'], TRUE);

      // Entity IDs that are affected by this command.
      if (isset($commands['add'])) {

        /** @var \Drupal\Core\Render\RendererInterface $renderer */
        $renderer = \Drupal::service('renderer');
        $entity_ids = $commands['add'];
        $selected_entities =& $form_state
          ->get([
          'entity_browser',
          'selected_entities',
        ]);

        // Get entities added by this command and generate JS commands for them.
        $selected_entity_keys = array_keys($selected_entities);
        $key_index = count($selected_entity_keys) - count($entity_ids);
        foreach ($entity_ids as $entity_pair_info) {
          $last_entity_id = $selected_entities[$selected_entity_keys[$key_index]]
            ->id();
          $html = $renderer
            ->render($form['selection_display']['selected']['items_' . $last_entity_id . '_' . $selected_entity_keys[$key_index]]);
          $ajax
            ->addCommand(new ReplaceCommand('div[id="' . $entity_pair_info['proxy_id'] . '"]', static::trimSingleHtmlTag($html)));
          $key_index++;
        }

        // Check if action buttons should be added to form. When number of added
        // entities is equal to number of selected entities. Then form buttons
        // should be also rendered: use_selected and show_selection.
        if (count($selected_entities) === count($entity_ids)) {

          // Order is important, since commands are executed one after another.
          $ajax
            ->addCommand(new AfterCommand('.entities-list', static::trimSingleHtmlTag($renderer
            ->render($form['selection_display']['show_selection']))));
          $ajax
            ->addCommand(new AfterCommand('.entities-list', static::trimSingleHtmlTag($renderer
            ->render($form['selection_display']['use_selected']))));
        }
      }

      // Add Invoke command to trigger loading of entities that are queued
      // during execution of current Ajax request.
      $ajax
        ->addCommand(new InvokeCommand('[name=ajax_commands_handler]', 'trigger', [
        'execute-commands',
      ]));
    }
    return $ajax;
  }

  /**
   * Make HTML with single tag suitable for Ajax response.
   *
   * Comments will be removed and also whitespace characters, because Ajax JS
   * "insert" command handling checks number of base elements in response and
   * wraps it in a "div" tag if there are more then one base element.
   *
   * @param string $html
   *   HTML content.
   *
   * @return string
   *   Returns cleaner HTML content, suitable for Ajax responses.
   */
  protected static function trimSingleHtmlTag($html) {
    $clearHtml = trim($html);

    // Remove comments around main single HTML tag. RegEx flag 's' is there to
    // allow matching on whitespaces too. That's needed, because generated HTML
    // contains a lot newlines.
    if (preg_match_all('/(<(?!(!--)).+((\\/)|(<\\/[a-z]+))>)/is', $clearHtml, $matches)) {
      if (!empty($matches) && !empty($matches[0])) {
        $clearHtml = $matches[0][0];
      }
    }
    return $clearHtml;
  }

  /**
   * Submit callback for remove buttons.
   *
   * @param array $form
   *   Form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   */
  public static function removeItemSubmit(array &$form, FormStateInterface $form_state) {
    $triggering_element = $form_state
      ->getTriggeringElement();

    // Remove weight of entity being removed.
    $form_state
      ->unsetValue([
      'selected',
      $triggering_element['#attributes']['data-remove-entity'] . '_' . $triggering_element['#attributes']['data-row-id'],
    ]);

    // Remove entity itself.
    $selected_entities =& $form_state
      ->get([
      'entity_browser',
      'selected_entities',
    ]);
    unset($selected_entities[$triggering_element['#attributes']['data-row-id']]);
    static::saveNewOrder($form_state);
    $form_state
      ->setRebuild();
  }

  /**
   * {@inheritdoc}
   */
  public function submit(array &$form, FormStateInterface $form_state) {
    $this
      ->saveNewOrder($form_state);
    if ($form_state
      ->getTriggeringElement()['#name'] == 'use_selected') {
      $this
        ->selectionDone($form_state);
    }
  }

  /**
   * Saves new ordering of entities based on weight.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   */
  public static function saveNewOrder(FormStateInterface $form_state) {
    $selected = $form_state
      ->getValue('selected');
    if (!empty($selected)) {
      $weights = array_column($selected, 'weight');
      $selected_entities = $form_state
        ->get([
        'entity_browser',
        'selected_entities',
      ]);

      // If we added new entities to the selection at this step we won't have
      // weights for them so we have to fake them.
      $diff_selected_size = count($selected_entities) - count($weights);
      if ($diff_selected_size > 0) {
        $max_weight = max($weights) + 1;
        for ($new_weight = $max_weight; $new_weight < $max_weight + $diff_selected_size; $new_weight++) {
          $weights[] = $new_weight;
        }
      }
      $ordered = array_combine($weights, $selected_entities);
      ksort($ordered);
      $form_state
        ->set([
        'entity_browser',
        'selected_entities',
      ], $ordered);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $entity_browser = $form_state
      ->getFormObject()
      ->getEntity();
    $defaults = $entity_browser
      ->getSelectionDisplay()
      ->getConfiguration();
    $default_entity_type = $defaults['entity_type'];
    $default_display = $defaults['display'];
    $default_display_settings = $defaults['display_settings'];
    $default_display_settings += [
      'entity_type' => $default_entity_type,
    ];
    $entity_types = [];
    foreach ($this->entityTypeManager
      ->getDefinitions() as $entity_type_id => $entity_type) {

      /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
      $entity_types[$entity_type_id] = $entity_type
        ->getLabel();
    }
    $form['multi_step_form_wrapper'] = [
      '#type' => 'container',
    ];
    $form['entity_type'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Entity type'),
      '#description' => $this
        ->t("Entity browser itself does not need information about entity type being selected. It can actually select entities of different type. However, some of the display plugins need to know which entity type they are operating with. Display plugins that do not need this info will ignore this configuration value."),
      '#default_value' => $default_entity_type,
      '#options' => $entity_types,
      '#ajax' => [
        'callback' => [
          $this,
          'updateSettingsAjax',
        ],
        'wrapper' => 'selection-display-config-ajax-wrapper',
      ],
    ];
    $displays = [];
    foreach ($this->fieldDisplayManager
      ->getDefinitions() as $display_plugin_id => $definition) {
      $entity_type = $this->entityTypeManager
        ->getDefinition($default_entity_type);
      if ($this->fieldDisplayManager
        ->createInstance($display_plugin_id)
        ->isApplicable($entity_type)) {
        $displays[$display_plugin_id] = $definition['label'];
      }
    }
    $form['display'] = [
      '#title' => $this
        ->t('Entity display plugin'),
      '#type' => 'select',
      '#default_value' => $default_display,
      '#options' => $displays,
      '#ajax' => [
        'callback' => [
          $this,
          'updateSettingsAjax',
        ],
        'wrapper' => 'selection-display-config-ajax-wrapper',
      ],
    ];
    $form['display_settings'] = [
      '#type' => 'container',
      '#title' => $this
        ->t('Entity display plugin configuration'),
      '#tree' => TRUE,
    ];
    if ($default_display_settings) {
      $display_plugin = $this->fieldDisplayManager
        ->createInstance($default_display, $default_display_settings);
      $form['display_settings'] += $display_plugin
        ->settingsForm($form, $form_state);
    }
    $form['select_text'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Select button text'),
      '#default_value' => $defaults['select_text'],
      '#description' => $this
        ->t('Text to display on the entity browser select button.'),
    ];
    $form['selection_hidden'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Selection hidden by default'),
      '#default_value' => $defaults['selection_hidden'],
      '#description' => $this
        ->t('Whether or not the selection should be hidden by default.'),
    ];
    return $form;
  }

  /**
   * Ajax callback that updates multi-step plugin configuration form.
   */
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
    $form['selection_display_wrapper']['selection_display_configuration']['#open'] = TRUE;
    return $form['selection_display_wrapper']['selection_display_configuration'];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
MultiStepDisplay::$fieldDisplayManager protected property Field widget display plugin manager.
MultiStepDisplay::buildConfigurationForm public function Implements PluginFormInterface::buildConfigurationForm(). Overrides PluginConfigurationFormTrait::buildConfigurationForm
MultiStepDisplay::create public static function Creates an instance of the plugin. Overrides SelectionDisplayBase::create
MultiStepDisplay::defaultConfiguration public function Gets default configuration for this plugin. Overrides SelectionDisplayBase::defaultConfiguration
MultiStepDisplay::executeJsCommand protected function Execute command generated by front-end.
MultiStepDisplay::getForm public function Returns selection display form. Overrides SelectionDisplayInterface::getForm
MultiStepDisplay::handleAjaxCommand public static function Handler to generate Ajax response, after command is executed.
MultiStepDisplay::removeItemSubmit public static function Submit callback for remove buttons.
MultiStepDisplay::saveNewOrder public static function Saves new ordering of entities based on weight.
MultiStepDisplay::submit public function Submits form. Overrides SelectionDisplayBase::submit
MultiStepDisplay::trimSingleHtmlTag protected static function Make HTML with single tag suitable for Ajax response.
MultiStepDisplay::updateSettingsAjax public function Ajax callback that updates multi-step plugin configuration form.
MultiStepDisplay::__construct public function Constructs widget plugin. Overrides SelectionDisplayBase::__construct
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginConfigurationFormTrait::submitConfigurationForm public function Implements PluginFormInterface::submitConfigurationForm(). 3
PluginConfigurationFormTrait::validateConfigurationForm public function Implements PluginFormInterface::validateConfigurationForm(). 2
SelectionDisplayBase::$entityTypeManager protected property Entity manager service.
SelectionDisplayBase::$eventDispatcher protected property Event dispatcher service.
SelectionDisplayBase::$label protected property Plugin label.
SelectionDisplayBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 1
SelectionDisplayBase::checkPreselectionSupport public function Check does selection display support preselection. Overrides SelectionDisplayInterface::checkPreselectionSupport
SelectionDisplayBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
SelectionDisplayBase::label public function Returns the selection display label. Overrides SelectionDisplayInterface::label
SelectionDisplayBase::selectionDone protected function Marks selection as done - sets value in form state and dispatches event.
SelectionDisplayBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
SelectionDisplayBase::supportsJsCommands public function Returns true if selection display supports selection over javascript. Overrides SelectionDisplayInterface::supportsJsCommands
SelectionDisplayBase::supportsPreselection public function Check if the plugin supports preselection. Overrides SelectionDisplayInterface::supportsPreselection
SelectionDisplayBase::validate public function Validates form. Overrides SelectionDisplayInterface::validate
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.