This can be done via Form API callback implementation:
Inside module/src/Element/ElementClass.php
, in public function getInfo() {}
define a callback, e.g. processMyElement
under '#process'
key:
public function getInfo() {
$class = static::class;
return [
// [...] Some other definitions.
'#process' => [
[$class, 'processMyElement'],
],
];
}
And then perform two complementary operations.
(1) Process the user input and return a value which will be provided in the '#form_state'
values in valueCallback()
method:
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
$output = parent::valueCallback($element, $input, $form_state);
if ($input == 'SOMETHING') {
$output = 'SOMETHING_ELSE';
}
return $output;
}
(2) And vice versa, in the processMyElement()
method return the original value:
public static function processMyElement(&$element, FormStateInterface $form_state, &$complete_form) {
if ($element['#value'] == 'SOMETHING_ELSE') {
$element['#value'] = 'SOMETHING';
}
return $element;
}
Usage example
I have used this to extend Drupal\Core\Render\Element\Textfield
class in combination with JS Storage Complete. That gives me Autocomplete (without need for a callback URL). This autocomplete returns Entity ID obtained via entity load based on unique label inside valueCallback()
method, but user is still presented with its text input (obtained by returning $entity->label()
inside processMyElement()
method).