By Mario Steinitz


2019-03-08 16:56:15 8 Comments

I am using an Entity Browser (2.x-dev in Drupal 8) as form widget for a custom entity's entity reference base field. The entity browser is configured

  • as a modal display,
  • with single widget,
  • and no selection display,
  • using a view with entity browser bulk select field as widget, and
  • to append chosen entities to the current selection of the reference field.

Selecting the entities is working fine. But the entity reference field shall not have any duplicates.

In order to ease selecting of entities without duplicates, I'd like to filter already chosen entities from the entity browser view results. So users will see unselected entities only.

For this purpose, I created a custom views argument_default plugin that exposes the entity browser selection storage as context default argument for the entity ID:

<?php

namespace Drupal\my_module\Plugin\views\argument_default;

use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * The entity browser selection argument default handler.
 *
 * @ViewsArgumentDefault(
 *   id = "entity_browser_selection",
 *   title = @Translation("Entity Browser Selection")
 * )
 */
class EntityBrowserSelection extends ArgumentDefaultPluginBase {

  /**
   * The selection storage.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
   */
  protected $selectionStorage;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, KeyValueStoreExpirableInterface $selection_storage) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->selectionStorage = $selection_storage;
  }

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

  /**
   * {@inheritdoc}
   */
  public function access() {
    return $this->view->getDisplay()->pluginId === 'entity_browser';
  }

  /**
   * {@inheritdoc}
   */
  public function getArgument() {
    $argument = NULL;
    $current_request = $this->view->getRequest();

    // Check if the widget context is available.
    if ($current_request->query->has('uuid')) {
      $uuid = $current_request->query->get('uuid');
      if ($storage = $this->selectionStorage->get($uuid)) {
        if (!empty($storage['selected_entities'])) {
          $argument = $storage['selected_entities'];
        }
      }
    }
    return $argument;
  }

}

The issue I face, is that the current selection within the selection storage is always empty, no matter how many entities have been selected at the entity reference field, and even after I complete the modal selection and open the entity browser again.

What do I have to do to have the current selection exposed in the entity browser's selection storage?

3 comments

@oknate 2019-03-18 05:43:04

The Entity Browser doesn't current pass current default value items field in persistent data, but it's easy to add it.

1) Add persistent data using field_widget_form_alter()

/**
 * Implements hook_field_widget_form_alter().
 */
function mymodule_field_widget_form_alter(&$element, FormStateInterface &$form_state, $context) {
  if (!empty($element['entity_browser'])) {
    $default_value =  $element['entity_browser']['#default_value'];
    $ids = [];
    foreach ($default_value as $entity) {
      $ids[] = $entity->id();
    }
    $element['entity_browser']['#widget_context']['current_ids'] = implode('+', $ids);
  }
}

2) Update your selection so that if blank it shows all:

  /**
   * {@inheritdoc}
   */
  public function getArgument() {
    $argument = NULL;
    $current_request = $this->view->getRequest();

    // Check if the widget context is available.
    if ($current_request->query->has('uuid')) {
      $uuid = $current_request->query->get('uuid');
      if ($storage = $this->selectionStorage->get($uuid)) {
        if (!empty($storage['widget_context']['current_ids'])) {
          $argument = $storage['widget_context']['current_ids'];
        }
        else {
          $argument = 'all';
        }
      }
    }
    return $argument;
  }

3) Make sure you have "exclude" and "allow multiple" checked on your selection.

enter image description here

By the way, if you update to the latest dev release of entity_browser, you don't need your custom plugin. There is a new entity_browser_widget_context default value views plugin that is configurable.

I also added an issue to the entity_browser queue to add this information when in the widget_context.

@Andreas W. Wylach 2019-03-18 02:13:17

I used your default argument class and debugged a little. This is my approach:

The entity browser widget stores selected values in its current property, which is filled, when the entity form is opened with an existing entity/selection. The widget also uses AJAX when the modal closes and the current property is updated accordingly.

So you can get the selected entity IDs using something like the following in your entity form/form alter:

use Drupal\Core\Render\Element;

// Current selection. Replace 'field_references' with the actual
// name of your field.
$selection = [];
if (isset($form['field_references']['widget']['current'])) {
  $current = $form['time_records']['widget']['current'];
  foreach (Element::children($current) as $key) {
    if (isset($current[$key]['target_id']['#value'])) {
      $selection[] = $current[$key]['target_id']['#value'];
    }
  }
}

Another widget property available in the form is the widget context of the used entity browser. You can simply add the current selection to the widget context and use this information with your views default argument (the widget context is updated in the selection storage on each AJAX reload of the widget/form):

$form['field_references']['widget']['entity_browser']['#widget_context']['current_selection'] = $selection;

Then alter your EntityBrowserSelection::getArgument():

  /**
   * {@inheritdoc}
   */
  public function getArgument() {
    $argument = NULL;
    $current_request = $this->view->getRequest();

    // Check if the widget context is available.
    if ($current_request->query->has('uuid')) {
      $uuid = $current_request->query->get('uuid');
      if ($storage = $this->selectionStorage->get($uuid)) {
        if (!empty($storage['widget_context']['current_selection'])) {
          $selection = $storage['widget_context']['current_selection'];
          if (is_string($selection)) {
            $argument = $selection;
          }
          elseif (is_array($selection)) {
            $non_scalar = array_filter($selection, function ($item) {
              return !is_scalar($item);
            });
            if (empty($non_scalar)) {
              // Replace the ',' with '+', if you like to have an
              // OR filter rather than an AND filter.
              $argument = implode(',', $selection);
            }
          }
        }
      }
    }
    return $argument;
  }

With these changes I was able to filter selected items from my view with a contextual filter for the entity IDs, choosing

  • When the filter is not available: Provide a default value, Type "Entity Browser Selection"
  • More: Exclude

Hope it helps!

@Taggart Jensen 2019-03-15 04:43:17

I could not get the default filter to work but I did have some success doing the following scary-ness:

function mymodule_views_pre_render(\Drupal\views\ViewExecutable $view) {
  if ($view->id() == "media_entity_browser" && $view->current_display ==
    'entity_browser_1') {
    $request = \Drupal::request();
    $prams = $request->query->all();
    $is_edit = FALSE;
    if (!empty($prams['original_path'])) {
      // testing with "/node/1/edit"
      $check_explode = explode('/', $prams['original_path']);
      if (in_array('edit', $check_explode)) {
        $edit_key = array_search ( 'edit', $check_explode);
        $entity_id_key = $edit_key - 1;
        $entity_id = $check_explode[$entity_id_key];
        $entity_type_key = $edit_key - 2;
        $entity_type = $check_explode[$entity_type_key];
        $selected_ids = [];
        try {
          $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id);
          // This sucks bacause field name is hardcoded.
          $media_entities = $entity->field_image->referencedEntities();
          if (!empty($media_entities)) {
            foreach ($media_entities as $media_entity) {
              $selected_ids[] = (int) $media_entity->id();
            }
          }
        }
        catch (\Exception $e) {
          // log this.
        }

        $my_results = [];
        // Now need to remove from view.
        if (!empty($selected_ids)) {
          $i = 0;
          foreach ($view->result as $key =>  $item) {
            $id = (int) $item->_entity->id();
            if (!in_array($id, $selected_ids)) {
              $my_results[] = new ResultRow([
                '_entity' => $item->_entity,
                'index' => $i,
                'mid' => $item->_entity->id(),
              ]);
              $i++;
            }
          }
          $view->result = $my_results;
        }
      }
    }
  }
}

This does work. However, there are quite a few assumptions made... Good thing entity browser allows you to select which view.

Related Questions

Sponsored Content

0 Answered Questions

Media & Media Entity Browser upload problems

  • 2019-03-09 13:29:40
  • ice70
  • 52 View
  • 0 Score
  • 0 Answer
  • Tags:   8 media

0 Answered Questions

Entity reference view display is not displaying fields as expected

  • 2018-10-11 12:23:22
  • Elie Masaad
  • 278 View
  • 1 Score
  • 0 Answer
  • Tags:   views 8

3 Answered Questions

3 Answered Questions

[SOLVED] How to give custom function in D8 custom field widget class?

  • 2016-08-16 10:09:40
  • Jitha M Saroj
  • 747 View
  • 0 Score
  • 3 Answer
  • Tags:   8

1 Answered Questions

[SOLVED] How to validate all elements in Custom Widget

  • 2016-08-19 06:18:23
  • Jitha M Saroj
  • 631 View
  • 1 Score
  • 1 Answer
  • Tags:   8

2 Answered Questions

[SOLVED] Entity Browser Widget for Video Embed Media Entity

  • 2017-06-04 23:57:05
  • tdaff
  • 1286 View
  • 1 Score
  • 2 Answer
  • Tags:   8 entities media

0 Answered Questions

Custom Fields not displayed

  • 2016-08-10 11:53:46
  • Jitha M Saroj
  • 720 View
  • 1 Score
  • 0 Answer
  • Tags:   8

0 Answered Questions

How to access the values of custom Widget form fields?

  • 2016-08-25 10:37:31
  • Jitha M Saroj
  • 706 View
  • 0 Score
  • 0 Answer
  • Tags:   8

2 Answered Questions

Sponsored Content