When we created our field type, we specified some storage settings and we saw that these are typically linked to underlying storage and cannot be changed once the field has data in it. This is because databases have a hard time making table column changes when there is data present in them. However, apart from storage settings, we also have something called field settings, which are specific to the field instance on a certain entity bundle. Even more, they can (or should) be changeable even after the field has been created and has data in it. An example of such a field setting, which is available from Drupal core on all field types, is the "required" option which marks a field as required or not. So let's see how we can add our own field settings to configure what we said we want to do.
Back in our LicensePlateItem plugin class, we start by adding the default field settings:
/** * {@inheritdoc} */ public static function defaultFieldSettings() { return [ 'codes' => '', ] + parent::defaultFieldSettings(); }
This is the same pattern we've been seeing by which we specify what are the settings and what are their relevant defaults. Then, as expected, we need the form, where users can specify the setting values for each field instance:
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$element = [];
$element['codes'] = [
'#title' => $this->t('License plate codes'),
'#type' => 'textarea',
'#default_value' => $this->getSetting('codes'),
'#description' => t('If you want the field to be have a select list with license plate codes instead of a textfield, please provide the available codes. Each code on a new line.')
];
return $element;
}
So what we provide here is a textarea form element by which the administrator can add multiple license plate codes, one per each line. In our widget, we will use these and turn them into a select list. However, before we do that, we need to provide the configuration schema for this new setting:
field.field_settings.license_plate_type: type: mapping label: 'License plate field settings' mapping: codes: type: string label: 'Codes'
With this in place, we can turn to our field widget and make the necessary changes.
Inside the formElement() method, let's replace the block where we defined the code form element with this:
$this->addCodeField($element, $items, $delta, $placeholder_settings);
Since the logic for determining that element depends on configuration, it's a bit more complicated, so it's best to refactor to its own method. Now let's write it up:
/** * Adds the license plate code field to the form element. * * @param $element * @param \Drupal\Core\Field\FieldItemListInterface $items * @param $delta * @param $placeholder_settings */ protected function addCodeField(&$element, FieldItemListInterface $items, $delta, $placeholder_settings) { $element['details']['code'] = [ '#title' => t('Plate code'), '#default_value' => isset($items[$delta]->code) ? $items[$delta]->code : NULL, '#description' => '', '#required' => $element['#required'], ]; $codes = $this->getFieldSetting('codes'); if (!$codes) { $element['details']['code'] += [ '#type' => 'textfield', '#placeholder' => $placeholder_settings['code'], '#maxlength' => $this->getFieldSetting('code_max_length'), '#size' => $this->getSetting('code_size'), ]; return; } $codes = explode("\r\n", $codes); $element['details']['code'] += [ '#type' => 'select', '#options' => array_combine($codes, $codes), ]; }
We start by defining the code form element defaults, such as title, default, and value. Then, we get the field settings for the codes setting we just created. Note that getFieldSetting() and getFieldSettings() delegate to the actual field type and return both storage and field settings combined. So, we don't need to use separate methods. However, an implication is that you should probably stick to different setting names for the two categories.
Then, if we don't have any codes configured in this particular field instance, we build up our textfield form element as we did before. Otherwise, we break them up into an array and use them in a select list form element. Also, note that in this latter case we no longer need to apply any length limits because of the validation inherent to select lists. Values not present in the original options list will be considered invalid.
That's pretty much it. The field can now be configured to either default to the open textfield for adding a license plate code or to a select list of predefined ones. Also, the same field can be used in these two ways on two different bundles, which is neat.