in this tuto, I'll show you How to create a custom Autocomplete on text fields using the Drupal 8 Form API.
To use Autocomplete on text fields, just add property '#autocomplete_route_name' to a text field in a form. The route controller for this route must return an array of options for autocomplete, as a \Symfony\Component\HttpFoundation\JsonResponse object.
Step 1 - Define your module
create mymodule.info.yml file like this:
name: 'My Module' description: 'My Module' package: 'Custom' type: module core: 8.x core_version_requirement: ^8 || ^9
Step 2 - Define the routes
create mymodule.routing.yml file like this:
mymodule.autocomplete:
path: '/autocomplete/articles'
defaults:
_controller: '\Drupal\mymodule\Controller\JsonApiArticlesController::handleAutocomplete'
_format: json
requirements:
_permission: 'access content'
mymodule.form_autocomplete:
path: '/autocomplete-form'
defaults:
_form: '\Drupal\mymodule\Form\MyAutocompleteForm'
_title: 'Autocomplete Form'
requirements:
_permission: 'access content'
Step 3 - Define The form and the fileds
create src/Form/MyAutocompleteForm.php file like this:
<?php
namespace Drupal\mymodule\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\Element\EntityAutocomplete;
/**
* Class MyAutocompleteForm
* @package Drupal\mymodule\Form
*/
class MyAutocompleteForm extends FormBase
{
/**
* {@inheritdoc}
*/
public function getFormId()
{
return 'my_autocomplete_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state)
{
$form['article'] = [
'#type' => 'textfield',
'#title' => $this->t('Autocomplete Articles'),
'#autocomplete_route_name' => 'mymodule.autocomplete',
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state)
{
$article_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($form_state->getValue('article'));
\Drupal::messenger()->addMessage('Article ID is ' . $article_id);
}
}
Step 4 - Add Controller and return JSON response
create src/Controller/JsonApiArticlesController.php file like this:
<?php
namespace Drupal\mymodule\Controller;
use Drupal\Core\Entity\Element\EntityAutocomplete;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\Xss;
/**
* Class JsonApiArticlesController
* @package Drupal\mymodule\Controller
*/
class JsonApiArticlesController
{
/**
* @return JsonResponse
*/
public function handleAutocomplete(Request $request)
{
$results = [];
$input = $request->query->get('q');
if (!$input) {
return new JsonResponse($results);
}
$input = Xss::filter($input);
$query = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('title', $input, 'CONTAINS')
->groupBy('nid')
->sort('created', 'DESC')
->range(0, 10);
$ids = $query->execute();
$nodes = $ids ? \Drupal\node\Entity\Node::loadMultiple($ids) : [];
foreach ($nodes as $node) {
$results[] = [
'value' => EntityAutocomplete::getEntityLabels([$node]),
'label' => $node->getTitle().' ('.$node->id().')',
];
}
return new JsonResponse($results);
}
}
And this is my final output:
If you want to select an entity, then there is a way easier way to do that. Drupal 8 has a standard entity_autocomplete field type, just specify your form element like this:
Usage examples:
Provide a basic autocomplete element that matches node titles from all bundles:
$form['my_element'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#default_value' => $entity, // The #default_value can be either an entity object or an array of entity objects.
);
If we want to restrict the matches to a single or a set of bundles, we can use the 'target_bundles' selection setting:
$form['my_element'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#selection_handler' => 'default', // Optional. The default selection handler is pre-populated to 'default'.
'#selection_settings' => array(
'target_bundles' => array('article', 'page'),
),
);
If we want to allow an input of multiple entity labels into the element (commonly known as "tagging" fields), we set the '#tags' property to TRUE (its default value is FALSE):
$form['my_element'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#tags' => TRUE,
);
If we want to allow an input of an entity label that does not exist yet but can be created "on the fly" on form submission, the '#autocreate' property can be used:
$form['my_element'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'taxonomy_term',
'#autocreate' => array(
'bundle' => 'tags', // Required. The bundle name for the new entity.
'uid' => <a valid user ID>, // Optional. The user ID for the new entity, if the target entity type implements \Drupal\user\EntityOwnerInterface. Defaults to the current logged-in user.
),
);
Example:
$entity = \Drupal\user\Entity\User::loadMultiple(); // Load all user entities
foreach ($entity as $key => $user) {
if($user->id() === $member){
$member = $user; //the user entity which I want is now stored in member
break;
}
}
$form['member'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'user',
'#title' => $this->t('Member'),
'#required' => 'true',
'#default_value' => $member,
);