An example defining custom field type, field widget and field formatter in Drupal 8.
Nothing fancy about the module definition as such, except for the fact that it would define a field. A standard info file with inc files for utility functions. Unlike Drupal 7, field types, field widgets and field formatters are defined and detected using Plugin definitions with annotations like @FieldType, @FieldWidget and @FieldFormatter respectively. Hence, we have noo hook_field_xxx() implementations in the .module file.
The field type
The first thing to do is create a FieldType plugin implementation. This class would represent a single item of the Burrito field type and hence, by convention, it has been named the BurritoItem. Notice the @FieldType part of the class documentation - that is where we have all the data which previously used to be provided by a hook_field_info() implementation.
Here is some quick info on some important methods of the class (which previously used to be hook_field_* functions):
- BurritoItem::schema() - Defines storage columns to be created in the database in a format similar to the one used by the Schema API.
- BurritoItem::propertyDefinitions() - Defines additional info about sub-properties of the field.
- BurritoItem::isEmpty() - Defines logic which determines as to when a field should be considered empty (and ignored) and when the field should be considered to be containing data. In short, if this method does not return a TRUE, the field values would not be saved in the database.
- BurritoItem::preSave() - As the name suggests, the method is called before data for a field item is saved to the database. There also exists a similar method called ::postSave().
After you define the field type, you can enable the module and attach an instance of the burrito_maker_burrito field type to any fieldable entity. We are now 33.33333% close to our objective of having our custom burrito data!
NOTE: At times, it may seem tempting to name your field only burrito instead of burrito_maker_burrito. However, it is good practice to define stuff in the namespace of your own module.
The field widget
So, the database tables are ready and your field type appears in the UI. But how would the user enter data for their custom burritos? Drupal doesn't know anything about them burritos! So, we need to implement a FieldWidget plugin to let tell Drupal exactly how it should build the forms for accepting burrito data.
A default widget type for our custom burrito field has already been specified in the @FieldType declaration - the machine name being burrito_default. To define the structure of the widget, we implement BurritoDefaultWidget with the following important method:
- BurritoDefaultWidget::formElement() - This method is responsible for defining the form items which map to various schema columns provided by the field type. In our case, this method looks a bit complicated (though it is quite simple) because we have various checkboxes for the toppings/ingredients of the burrito, placed inside 2 fieldsets - Meat and Toppings - The meat fieldset being hidden if meat is disallowed by settings.
The BurritoDefaultWidget::processToppingsFieldset() method solely exists as a helper. Its only purpose is to help flatten the user input array in such a way that the field names map to various database columns, instead of being nested under meat and toppings indexes in the POST array.
NOTE: One might think that adding the #tree attribute to these fieldsets can help flatten the fieldset's sub-fields, but it does't work that way. If you set #tree as FALSE on the fieldset, though we might expect only the fieldset to be be flattened, what actually happens is every child of the fieldset is submitted as a root element in the main form. Hence, we use the BurritoDefaultWidget::processToppingsFieldset() to do the trick.
The field formatter
Input checked. Storage checked. Now for presentation of data, we define the BurritoDefaultFormatter. It's main purpose is to take a list BurritoItem objects and display them (as per the field's display settings). This is done by the BurritoFormatter::viewElements() method. Rest of the methods in the Formatter are optional but quite useful for implementing certain commonly needed features. A default formatter for our custom burrito field has already been specified in the @FieldType declaration - the machine name being burrito_default.
NOTE: Documentation on other methods of the BurritoDefaultFormatter will be added soon.