toolshed-8.x-1.x-dev/src/Element/Autocomplete.php
src/Element/Autocomplete.php
<?php
namespace Drupal\toolshed\Element;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element\Textfield;
use Drupal\Core\Url;
/**
* Create an autocomplete element, which uses the Toolshed autocomplete widget.
*
* The autocomplete widget from Toolshed handles the situation where the
* display and the value differs. Allows passing key and value as autocomplete
* results, and some additional flexibility with separating the autocomplete
* display of suggestions from the input value.
*
* @FormElement("toolshed_autocomplete")
*
* @see /assets/widgets/Autocomplete.es6.js
*/
class Autocomplete extends Textfield {
/**
* {@inheritdoc}
*/
public static function processAutocomplete(&$element, FormStateInterface $form_state, &$complete_form): array {
$url = NULL;
$access = FALSE;
if (!empty($element['#autocomplete_route_name'])) {
$params = @$element['#autocomplete_route_parameters'] ?: [];
$url = Url::fromRoute($element['#autocomplete_route_name'], $params)->toString(TRUE);
/** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */
$access_manager = \Drupal::service('access_manager');
$access = $access_manager->checkNamedRoute(
$element['#autocomplete_route_name'],
$params,
\Drupal::currentUser(),
TRUE
);
}
if ($access) {
$metadata = BubbleableMetadata::createFromRenderArray($element);
if (TRUE === $access || $access->isAllowed()) {
$metadata->addAttachments(['library' => ['toolshed/autocomplete']]);
$element['#attributes']['class'][] = 'toolshed-autocomplete';
$settings = $element['#autocomplete_settings'] ?? [];
$settings['uri'] = $url->getGeneratedUrl();
if (!empty($element['#autocomplete_min_length'])) {
$settings['minLength'] = intval($element['#autocomplete_min_length']);
}
if (!empty($element['#autocomplete_delay'])) {
$settings['delay'] = intval($element['#autocomplete_delay']);
}
// Provide a data attribute for the JavaScript behavior to bind to.
$element['#attributes']['data-autocomplete'] = json_encode($settings);
if (!empty($element['#autocomplete_params'])) {
$formattedParams = static::formatAutocompleteParams($element['#autocomplete_params'], $complete_form);
$element['#attributes']['data-params'] = json_encode($formattedParams);
}
$metadata = $metadata->merge($url);
}
$metadata->merge(BubbleableMetadata::createFromObject($access))->applyTo($element);
}
// Apply display text for the separate value display.
if (!empty($element['#text'])) {
$element['#attributes']['data-text'] = Html::escape($element['#text']);
}
return $element;
}
/**
* Transform the passed autocomplete parameters into a format for the JS data.
*
* @return array
* Convert autocomplete routing parameters into a format, that can be
* JSON encoded for inclusion to the element attributes.
*/
protected static function formatAutocompleteParams(array $params, $form): array {
$formatted = [];
foreach ($params as $key => $target) {
$hasKey = FALSE;
$source = NestedArray::getValue($form, $target, $hasKey);
if ($hasKey && !empty($source['#id'])) {
$formatted[$key] = $source['#id'];
}
}
return $formatted;
}
}
