diff --git a/docs/dev_guide/module_dev.rst b/docs/dev_guide/module_dev.rst index bd76eae..30b5da3 100644 --- a/docs/dev_guide/module_dev.rst +++ b/docs/dev_guide/module_dev.rst @@ -11,7 +11,7 @@ Custom Module Development module_dev/file_structure module_dev/routing module_dev/entities - module_dev/fields + module_dev/tripal_fields module_dev/forms module_dev/jobs module_dev/logging diff --git a/docs/dev_guide/module_dev/fields/auto_attach.rst b/docs/dev_guide/module_dev/fields/auto_attach.rst new file mode 100644 index 0000000..7d8396d --- /dev/null +++ b/docs/dev_guide/module_dev/fields/auto_attach.rst @@ -0,0 +1,13 @@ +Add Fields to Content Types +============================ + +In order for a field to be used, it needs to be added to a specific content type. This can be done through the "Manage Fields" interface for a given Content Type. More specifically, go to Tripal > Page Structure and then choose "Manage Fields" for the Content Type you want to add a field to. + +This can also be done programmatically, however documentation for this is still being developed. + +.. warning:: + + This documentation is still being developed. In the meantime there are + examples for programmatically adding TripalFields in the Tripal core codebase. + Specifically, look in the Chado Preparer class in + `tripal_chado/src/Task/ChadoPreparer.php`. diff --git a/docs/dev_guide/module_dev/fields/custom_field_backend.rst b/docs/dev_guide/module_dev/fields/custom_field_backend.rst new file mode 100644 index 0000000..c450bb7 --- /dev/null +++ b/docs/dev_guide/module_dev/fields/custom_field_backend.rst @@ -0,0 +1,10 @@ +Non-Chado Field Storage +========================= + +The TripalStorage plugin supports providing custom backends for fields. You can have fields with different storage backends on the same Content Type. + +.. warning:: + + This documentation is still being developed. Currently ChadoStorage provides + an example for implementing the TripalStorage data store extension. It can be + found in `tripal_chado/src/Plugin/TripalStorage/ChadoStorage.php`. diff --git a/docs/dev_guide/module_dev/fields/formatters.rst b/docs/dev_guide/module_dev/fields/formatters.rst new file mode 100644 index 0000000..5535133 --- /dev/null +++ b/docs/dev_guide/module_dev/fields/formatters.rst @@ -0,0 +1,266 @@ +Field Formatters +================== + +Implementing a ChadoFormatterBase Class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When creating a new Tripal field, the class responsible for displaying +information to the site users is the "Formatter" class. +This class extends the `ChadoFormatterBase` class. + +To illustrate with a simple example, we will create a simple field to display the +organism's scientific name on a germplasm page. +We earlier created a property to store this, ``organism_scientific_name``, +so the formatter will use the value from that property. + +Formatter Class Setup +``````````````````````` +To create a new field, we will extend the `ChadoFormatterBase` class. +For a new field named `MyField` we would create a new file in our module here: +`src/Plugin/Field/FieldFormatter/MyfieldFormatter.php` +The following is a simple class example: + +.. code-block:: php + + $item) { + $values = [ + 'entity_id' => $item->get('entity_id')->getString(), + 'scientific_name' => $item->get('organism_scientific_name')->getString(), + ]; + + // Create a clickable link to the corresponding entity when one exists. + $renderable_item = $lookup_manager->getRenderableItem($values['scientific_name'], $values['entity_id']); + + $list[$delta] = $renderable_item; + } + + // If only one element has been found, don't make into a list. + if (count($list) == 1) { + $elements = $list; + } + // If more than one value has been found, display all values in an + // unordered list. + elseif (count($list) > 1) { + $elements[0] = [ + '#theme' => 'item_list', + '#list_type' => 'ul', + '#items' => $list, + '#wrapper_attributes' => ['class' => 'container'], + ]; + } + + return $elements; + } + + } + +Below is a line-by-line explanation of each section of the code snippet above. + +Formatter Namespace and Use Statements +```````````````````````````````````````` + +The following should always be present and specifies the namespace for this +field. + +.. code-block:: php + + namespace Drupal\mymodule\Plugin\Field\FieldFormatter; + + +.. note:: + + Be sure to change `mymodule` in the `namespace` to the name of your module. + +.. warning:: + + If you misspell the `namespace` your field will not work properly. + +The following "use" statement is required for all Chado fields. + +.. code-block:: php + + use Drupal\tripal_chado\TripalField\ChadoFormatterBase; + +Unless you are sure that your field will only handle a single value in all +cases, you will likely want the following use statement to allow the +formatter to handle a list of multiple items. + +.. code-block:: php + + use Drupal\Core\Field\FieldItemListInterface; + +Formatter Annotation Section +`````````````````````````````` +The annotation section in the class file is the set of in-line comments for the class. +This annotation is required. +This section is similar to the annotation section in the previous Type class. +Note the ``field_types`` annotation, which specifies what field types this formatter can be used +for. This should match the ``id`` annotation in the Type class. + +.. code-block:: php + + /** + * Plugin implementation of an organism scientific name formatter. + * + * @FieldFormatter( + * id = "my_field_formatter", + * label = @Translation("Organism scientific name formatter"), + * description = @Translation("A chado organism scientific name formatter"), + * field_types = { + * "my_field" + * }, + * ) + */ + +.. note:: + + Multiple formatters can exist for a given field, and the site + administrator can select which formatter is appropriate for a + particular content type. + +.. warning:: + + If the annotation section is not present, has misspellings, or is not + complete, the field will not be recognized by Drupal. + +Formatter Class Definition +```````````````````````````` + +Next, the class definition line must extend the `ChadoFormatterBase` class. You +must name your class the same as the filename in which it is contained (minus +the `.php` extension). + +.. code-block:: php + + class MyFieldFormatter extends ChadoFormatterBase { + +.. warning:: + + If you misspell the class name such that it is not the same as the filename + of the file in which it is contained, then the field will not be recognized by + Drupal. + +The defaultSettings() Function +```````````````````````````````` +This is an optional function. If your field requires some additional settings +for content display. An example might be how many decimal places to display +for a real number. This example does not add any settings. + +.. code-block:: php + + public static function defaultSettings() { + $settings = parent::defaultSettings(); + return $settings; + } + +The viewElements() Function +````````````````````````````` + +The `viewElements()` function is used to retrieve one or more property types +managed by this field, and prepare them for display. Our example will just display +a single value, but some fields can be configured for a cardinality greater than +one, meaning multiple values may be present, so the values can be made into a +list or a table as appropriate. + +In the example code block below you can see the steps where the property values +are retrieved from the ``$items`` array.. + +.. code-block:: php + + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $list = []; + $lookup_manager = \Drupal::service('tripal.tripal_entity.lookup'); + + foreach ($items as $delta => $item) { + $values = [ + 'entity_id' => $item->get('entity_id')->getString(), + 'scientific_name' => $item->get('organism_scientific_name')->getString(), + ]; + +Then for each retrieved item, we convert the returned value into a +`render array `_ +item using the `tripal.tripal_entity.lookup` service. When possible, the item +includes a link to the corresponding organism entity page, and if not, then +plain markup is generated. Either way, it is then added to the array +``$list`` of values to display. + +.. code-block:: php + + // Create a clickable link to the corresponding entity when one exists. + $renderable_item = $lookup_manager->getRenderableItem($values['scientific_name'], + $values['entity_id']); + $list[$delta] = $renderable_item; + } + +The last step is to populate the ``$elements`` array. If there is only one value, +as will be the case for this example, it becomes the only value in the array. +However, when multiple values are present, we can present them as a list. +Alternatively, you might want to display them in a table format. +For an example of a table formatter, see the `ChadoSequenceCoordinatesFormatterTable` formatter. + +.. code-block:: php + + // If only one element has been found, don't make into a list. + if (count($list) == 1) { + $elements = $list; + } + // If more than one value has been found, display all values in an + // unordered list. + elseif (count($list) > 1) { + $elements[0] = [ + '#theme' => 'item_list', + '#list_type' => 'ul', + '#items' => $list, + '#wrapper_attributes' => ['class' => 'container'], + ]; + } + + return $elements; + } + +This completes your field formatter! + +The next section :ref:`Field Widgets` will describe how to create a widget +for this new field to allow editing content. + +.. note:: + + A good way to learn about fields is to look at examples of fields in the Tripal + core codebase. Specifically, look in the + `tripal_chado/src/Plugin/Field/FieldFormatter` directory. diff --git a/docs/dev_guide/module_dev/fields/overview.rst b/docs/dev_guide/module_dev/fields/overview.rst new file mode 100644 index 0000000..c1c3351 --- /dev/null +++ b/docs/dev_guide/module_dev/fields/overview.rst @@ -0,0 +1,228 @@ + +Tripal Field Overview +======================= + +Field Classes +--------------- + +Anyone who wants to implement a new field in Drupal must implement three +different classes: + +- `FieldItemBase `_: + the class that defines a new field. This class interacts directly with the + data storage plugin to load and save the data managed by this field. +- `WidgetBase `_: + the class that defines the form elements (widgets) provided to the end-user + to supply or change the data managed by this field. +- `FormatterBase `_: + the class that defines how the field is rendered on the page. + +These classes were extended by Tripal to provide additional +functionality that allows Tripal-based fields to communicate with additional +data stores housing biological data. There is support for a number of +types of datastores (e.g. MySQL, PostgreSQL, SQLite) in core Drupal but you are +required to choose a single data store for your site. The extension to support +multiple data stores provided by Tripal allows you to keep your biological data +separate from the website and still available to scientific analysis and +visualization tools. + +Chado is the default data store implemented within Tripal as it offers flexible +support for a wide breadth of biological data types, ontology-focused metadata, +and robust data integrity. The following documentation will demonstrate how to +develop custom fields with data stored in Chado. However, the Tripal data storage +plugin and Tripal Fields are designed to work with additional data stores and +documentation showing how to take advantage of this will be written in the future. + +Custom module developers who wish to add new fields to Tripal whose data are +stored in Chado should implement the following three classes for every new field: + +- **ChadoFieldItemBase**: extends the Tripal class `TripalFieldItemBase` + which extends the Drupal class `FieldItemBase`. The `TripalFieldItemBase` + must be used for all fields attached to Tripal content types, and the + `ChadoFieldItemBase` adds Chado-specific support. +- **ChadoWidgetBase**: extends the Tripal class `TripalWidgetBase` + which extends the Drupal class `WidgetBase`. +- **ChadoFormatterBase**: extends the Tripal class `TripalFormatterBase` + which extends the Drupal class `FormatterBase`. + +Directory Setup +----------------- + +Drupal manages fields using its `Plugin API `_. +This means that as long as new field classes are placed in the correct directory +and have the correct "annotations" in the class comments, then Drupal will find them +and make the field available. All new fields must be placed in the custom +extension module inside of the `src/Plugin/Field` directory. There are three +subdirectories, one each for the three elements of a field: +`FieldType`, `FieldWidget`, `FieldFormatter`. For a new field named `MyField` +the directory structure would look like the following: + + +.. code:: + + mymodule + ├── config + ├── src + │   └── Plugin + │    └── Field + │         ├── FieldFormatter + | | └── MyFieldFormatter.php + │         ├── FieldType + | | └── MyFieldType.php + │         └── FieldWidget + | └── MyFieldWidget.php + | + ├── tests + └── templates + +Note that the file name must match the class name. + +Naming conventions +------------------- + +The filename for your new field should adhere to the following schema. Please +note the casing used. In addition, for fields that will be included in Tripal +Core, note the 'Default' designation, any fields added by extension modules +should **not** use 'Default': + + .. table:: Tripal Core modules: + + +------------------+-----------------------------+ + | File | Filename | + +==================+=============================+ + | Type | MyFieldTypeDefault.php | + +------------------+-----------------------------+ + | Formatter | MyFieldFormatterDefault.php | + +------------------+-----------------------------+ + | Widget | MyFieldWidgetDefault.php | + +------------------+-----------------------------+ + + .. table:: Extension modules: + + +------------------+-----------------------------+ + | File | Filename | + +==================+=============================+ + | Type | MyFieldType.php | + +------------------+-----------------------------+ + | Formatter | MyFieldFormatter.php | + +------------------+-----------------------------+ + | Widget | MyFieldWidget.php | + +------------------+-----------------------------+ + +Within the individual files, in the annotation section, the ID also has to follow +a specific format, and would look like the following: + + .. table:: Tripal Core modules: + + +------------------+----------------------------+ + | File | ID within annotation | + +==================+============================+ + | Type | my_field_type_default | + +------------------+----------------------------+ + | Formatter | my_field_formatter_default | + +------------------+----------------------------+ + | Widget | my_field_widget_default | + +------------------+----------------------------+ + + .. table:: Extension modules: + + +------------------+----------------------------+ + | File | ID within annotation | + +==================+============================+ + | Type | my_field_type | + +------------------+----------------------------+ + | Formatter | my_field_formatter | + +------------------+----------------------------+ + | Widget | my_field_widget | + +------------------+----------------------------+ + +About the Storage Backend +-------------------------- + +Default Drupal Behavior +^^^^^^^^^^^^^^^^^^^^^^^^^ +By default, all built-in fields provided by Drupal store their data in the +Drupal database. This is provided by Drupal's +`SqlContentEntityStorage `_ +storage plugin. This storage plugin will create a database table for every field. +For example, if you explore the Drupal database tables you will see the +following for the body field attached to the node content type: + +.. code:: + + Table "public.node__body" + Column | Type | Collation | Nullable | Default + --------------+------------------------+-----------+----------+----------------------- + bundle | character varying(128) | | not null | ''::character varying + deleted | smallint | | not null | 0 + entity_id | bigint | | not null | + revision_id | bigint | | not null | + langcode | character varying(32) | | not null | ''::character varying + delta | bigint | | not null | + body_value | text | | not null | + body_summary | text | | | + body_format | character varying(255) | | | + Indexes: + "node__body____pkey" PRIMARY KEY, btree (entity_id, deleted, delta, langcode) + "node__body__body_format__idx" btree (body_format) + "node__body__bundle__idx" btree (bundle) + "node__body__revision_id__idx" btree (revision_id) + Check constraints: + "node__body_delta_check" CHECK (delta >= 0) + "node__body_entity_id_check" CHECK (entity_id >= 0) + "node__body_revision_id_check" CHECK (revision_id >= 0) + +The values provided by the user for the body of a node type are housed in this +table. The following describes the columns of the table. + +These columns are present for all fields + +- `bundle`: the machine name of the content type (e.g. node) +- `deleted`: a value of 1 indicates the field is marked for deletion +- `entity_id`: the unique ID of the node that this field belongs to. +- `revision_id`: the node revision ID. +- `langcode`: for fields that are translatable, this indicates the language + of the saved value. +- `delta`: for fields that support multiple values, this is the index (starting + at zero) for the order of the values. + +These columns are specific to the field: + +- `body_value`: stores the value for the body +- `body_summary`: stores the body summary +- `body_format`: instructions for how the body should be rendered (e.g. plain + text, HTML, etc.) + + +Support for Chado +^^^^^^^^^^^^^^^^^^ +For fields storing biological data in something other than Drupal tables, +Tripal provides its own plugin named `TripalStorage`. If a custom module wants to +store data in a data backend other than in Drupal tables, it must create an implementation +of this plugin. By default, Tripal provides the `ChadoStorage` implementation +that allows a field to interact with a Chado database. + +The `ChadoStorage` backend extends the `SqlContentEntityStorage` and +will create a table in the Drupal schema for every Tripal field that is +added to a content type. The table columns will include the same default +columns as a Drupal field, and will also include a set of additional columns +for every property the field wants to manage. + +The `ChadoStorage` backend is different from `SqlContentEntityStorage` +in that it will not store the values of the properties in the table. This is +because those values need to be stored in Chado--we do not want to duplicate +the data in the Drupal schema and the Chado schema. The `ChadoStorage` +backend is also different in that it requires a set of property settings that +help it control how properties of a field are stored, edited and loaded from +Chado. Instructions for working with properties and storing data in Chado are +described in the following sections. + +.. note:: + + The `ChadoStorage` backend will not store biological data in the Drupal + tables--only in the Chado tables. The only exceptions are record IDs that + associate the field with data in Chado. + + +See the next section :ref:`Field Types` to learn how to define the +properties for a new field. diff --git a/docs/dev_guide/module_dev/fields.rst b/docs/dev_guide/module_dev/fields/types.rst similarity index 53% rename from docs/dev_guide/module_dev/fields.rst rename to docs/dev_guide/module_dev/fields/types.rst index a41265b..d2ae061 100644 --- a/docs/dev_guide/module_dev/fields.rst +++ b/docs/dev_guide/module_dev/fields/types.rst @@ -1,254 +1,13 @@ -Fields (content building blocks) -================================== - -Fields are the building blocks of content in Drupal. For example, all content -types (e.g. "Article", or "Basic Page") provide content to the end-user via -fields that are bundled with them. For example, when adding a basic -page (a default Drupal content type), the end-user is provided with form -elements (or widgets) that allow the user to set the title and the body text -for the page. The "Body" is a field. When a basic page is -viewed, the body is rendered on the page using formatters and -Drupal stores the values for the body in the database. Every -field, therefore, provides three types of functionality: instructions -for storage, widgets for allowing input, and formatters for rendering. - -Drupal provides a variety of built-in fields, and extension module developers -have created a multitude of new fields that can be added by the site admin -to add new functionality and support new types of data. Tripal follows this -model, but adds a variety of new object oriented classes to support storage -of data in biological databases such as Chado. - -.. note:: - - Not every custom module will require fields. But if you need a new way - to store and retrieve data, or if you need data to appear on an existing - Tripal content type then you will want to create a new field for your - custom module. - -Field Classes ---------------- -Anyone who wants to implement a new field in Drupal must implement three -different classes: - -- `FieldItemBase `_: - the class that defines a new field. This class interacts directly with the - data storage plugin to load and save the data managed by this field. -- `WidgetBase `_: - the class that defines the form elements (widgets) provided to the end-user - to supply or change the data managed by this field. -- `FormatterBase `_: - the class that defines how the field is rendered on the page. - -These classes were extended by Tripal to provide additional -functionality that allows Tripal-based fields to communicate with additional -data stores housing biological data. There is support for a number of -types of datastores (e.g. MySQL, PostgreSQL, SQLite) in core Drupal but you are -required to choose a single data store for your site. The extension to support -multiple data stores provided by Tripal allows you to keep your biological data -separate from the website and still available to scientific analysis and -visualization tools. - -Chado is the default data store implemented within Tripal as it offers flexible -support for a wide breadth of biological data types, ontology-focused metadata, -and robust data integrity. The following documentation will demonstrate how to -develop custom fields with data stored in Chado. However, the Tripal data storage -plugin and Tripal Fields are designed to work with additional data stores and -documentation showing how to take advantage of this will be written in the future. - -Custom module developers who wish to add new fields to Tripal whose data are -stored in Chado should implement the following three classes for every new field: - -- **ChadoFieldItemBase**: extends the Tripal class `TripalFieldItemBase` - which extends the Drupal class `FieldItemBase`. The `TripalFieldItemBase` - must be used for all fields attached to Tripal content types and the - `ChadoFieldItemBase` adds Chado-specific support. -- **TripalWidgetBase**: a class that extends the Drupal class `WidgetBase`. -- **TripalFormatterBase**: a class that extends the Drupal class `FormatterBase`. - - -How to Write a New Field for Chado ------------------------------------- - -Directory Setup -^^^^^^^^^^^^^^^^ -Drupal manages fields using its `Plugin API `_. -this means that as long as new field classes are placed in the correct directory -and have the correct "annotations" in the class comments then Drupal will find them -and make the field available. All new fields must be placed in the custom -extension module inside of the `src/Plugin/Field` directory. There are three -subdirectories, one each for the three elements of a field: -`FieldType`, `FieldWidget`, `FieldFormatter`. For a new field named `MyField` -the directory structure would look like the following: - - -.. code:: - - mymodule - ├── config - ├── src - │   └── Plugin - │    └── Field - │         ├── FieldFormatter - | | └── MyFieldFormatter.php - │         ├── FieldType - | | └── MyFieldType.php - │         └── FieldWidget - | └── MyFieldWidget.php - | - ├── tests - └── templates - -Note that the file name must match the class name. - -Naming convention -^^^^^^^^^^^^^^^^^ - -The filename for your new field should adhere to the following schema. Please note the casing used. In addition, for fields that will be included in Tripal Core, note the 'Default' designation, any fields added by extension modules should **not** use 'Default': - - .. table:: Tripal Core modules: - - +------------------+-----------------------------+ - | File | Filename | - +==================+=============================+ - | Type | MyFieldTypeDefault.php | - +------------------+-----------------------------+ - | Formatter | MyFieldFormatterDefault.php | - +------------------+-----------------------------+ - | Widget | MyFieldWidgetDefault.php | - +------------------+-----------------------------+ - - .. table:: Extension modules: - - +------------------+-----------------------------+ - | File | Filename | - +==================+=============================+ - | Type | MyFieldType.php | - +------------------+-----------------------------+ - | Formatter | MyFieldFormatter.php | - +------------------+-----------------------------+ - | Widget | MyFieldWidget.php | - +------------------+-----------------------------+ - -Within the individual files, in the annotation section, the ID also has to follow -a specific format, and would look like the following: - - .. table:: Tripal Core modules: - - +------------------+----------------------------+ - | File | ID within annotation | - +==================+============================+ - | Type | my_field_type_default | - +------------------+----------------------------+ - | Formatter | my_field_formatter_default | - +------------------+----------------------------+ - | Widget | my_field_widget_default | - +------------------+----------------------------+ - - .. table:: Extension modules: - - +------------------+----------------------------+ - | File | ID within annotation | - +==================+============================+ - | Type | my_field_type | - +------------------+----------------------------+ - | Formatter | my_field_formatter | - +------------------+----------------------------+ - | Widget | my_field_widget | - +------------------+----------------------------+ - -About the Storage Backend -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Default Drupal Behavior -```````````````````````` -By default, all built-in fields provided by Drupal store their data in the -Drupal database. This is provided by Drupal's -`SqlContentEntityStorage `_ -storage plugin. This storage plugin will create a database table for every field. -For example, if you explore the Drupal database tables you will see the -following for the body field attached to the node content type: - -.. code:: - - Table "public.node__body" - Column | Type | Collation | Nullable | Default - --------------+------------------------+-----------+----------+----------------------- - bundle | character varying(128) | | not null | ''::character varying - deleted | smallint | | not null | 0 - entity_id | bigint | | not null | - revision_id | bigint | | not null | - langcode | character varying(32) | | not null | ''::character varying - delta | bigint | | not null | - body_value | text | | not null | - body_summary | text | | | - body_format | character varying(255) | | | - Indexes: - "node__body____pkey" PRIMARY KEY, btree (entity_id, deleted, delta, langcode) - "node__body__body_format__idx" btree (body_format) - "node__body__bundle__idx" btree (bundle) - "node__body__revision_id__idx" btree (revision_id) - Check constraints: - "node__body_delta_check" CHECK (delta >= 0) - "node__body_entity_id_check" CHECK (entity_id >= 0) - "node__body_revision_id_check" CHECK (revision_id >= 0) - -The values provided by the user for the body of a node type are housed in this -table. The following describes the columns of the table. - -These columns are present for all fields - -- `bundle`: the machine name of the content type (e.g. node) -- `deleted`: a value of 1 indicates the field is marked for deletion -- `entity_id`: the unique ID of the node that this field belongs to. -- `revision_id`: the node revision ID. -- `langcode`: for fields that are translatable, this indicates the language - of the saved value. -- `delta`: for fields that support multiple values, this is the index (starting - at zero) for the order of the values. - -These columns are specific to the field: - -- `body_value`: stores the value for the body -- `body_summary`: stores the body summary -- `body_format`: instructions for how the body should be rendered (e.g. plain - text, HTML, etc.) - - -Support for Chado -``````````````````` -For fields storing biological data in something other than Drupal tables, -Tripal provides its own plugin named `TripalStorage`. If a custom module wants to -store data in a data backend other than in Drupal tables, it must create an implementation -of this plugin. By default, Tripal provides the `ChadoStorage` implementation -that allows a field to interact with a Chado database. - -The `ChadoStorage` backend extends the `SqlContentEntityStorage` and -will create a table in the Drupal schema for every Tripal field that is -added to a content type. The table columns will have the same default columns. -It will also have a set of additional columns for every property the field wants -to manage. - -The `ChadoStorage` backend is different from the `SqlContentEntityStorage` -in that it will not store the values of the properties in the table. This is -because those values need to be stored in Chado--we do not want to duplicate -the data in the Drupal schema and the Chado schema. The `ChadoStorage` -backend is also different in that it requires a set of property settings that -help it control how properties of a field are stored, edited and loaded from -Chado. Instructions for working with properties and storing data in Chado are -described in the following sections. - -.. note:: - - The `ChadoStorage` backend will not store biological data in the Drupal - tables--only in the Chado tables. The only exceptions are record IDs that - associate the field with data in Chado. - +Field Types +============= Implementing a ChadoFieldItemBase Class ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When creating a new Tripal field, the first class that must be created is the -"type" class. This must extend the `ChadoFieldItemBase` class. +"Type" class. This class extends the `ChadoFieldItemBase` class. +This "Type" class will specify the columns of one or more Chado tables that +the field needs to display its content. Single-Value Fields ````````````````````` @@ -256,21 +15,21 @@ A single-value field is the simplest Chado field. This is a field that manages a data value from a single column in a single Chado table. For example, the `genus` column of the `organism` table of Chado stores the genus of an organism. For the organism pages provided by Tripal, a single-value -field is used to provide the genus. +field could be used to provide the genus. Tripal provides some ready-to-use field classes for single-values. These are: -- **ChadoIntegerTypeItem**: for integer data. -- **ChadoStringTypeItem**: for string data with a max length. -- **ChadoTextTypeItem**: for string data with unlimited length. -- **ChadoRealTypeItem**: for real (floating point) numberic data. -- **ChadoBoolTypeItem**: for boolean data. -- **ChadoDateTimeTypeItem**: for data/time data. +- **ChadoIntegerTypeDefault**: for integer data. +- **ChadoStringTypeDefault**: for string data with a maximum length. +- **ChadoTextTypeDefault**: for string data with unlimited length. +- **ChadoRealTypeDefault**: for real (floating point) numeric data. +- **ChadoBoolTypeDefault**: for boolean data. +- **ChadoDateTimeTypeDefault**: for data/time data. .. warning:: - The alpha v1 version of Tripal v4 does not yet implement these fields: - `ChadoRealTypeItem`, `ChadoBoolTypeItem`, `ChadoDateTimeTypeItem` + The alpha v2 version of Tripal v4 does not yet implement these fields: + `ChadoRealTypeDefault`, `ChadoDateTimeTypeDefault` If you need to add a single-value field for your custom module then you do not need to write your own field! You can use one of these existing field types. @@ -283,11 +42,11 @@ A complex field is one that manages multiple properties (or multiple values) wit of a complex field is one that stores/loads the organism of a germplasm content type. Within Chado, a record in the `stock` table is used to store germplasm data. The `stock` table has a foreign key constraint with the `organism` table. Therefore, -a germplasm page must provide a field that allows the user to specify an organism +a germplasm page will usually include a field that allows the user to specify an organism for saving. It should also format the organism name for display. In practice, the `stock` table stores the numeric `organism_id` when saving -a germplasm. We could use a single-value `ChadoIntegerTypeItem` to allow the +a germplasm record. We could use a single-value `ChadoIntegerTypeDefault` to allow the user to provide the numeric ID for the organism. But, this is not practical. Users should not be required to use a look-up table of numeric organism IDs. @@ -296,17 +55,19 @@ Instead what we need is: - A field that will store and load a numeric organism ID value that the user will never see. - A field that has access to the genus, species, infraspecific type, - infraspecific name, etc., of the organism. -- A widget (form element) that allows the user to select an existing organism. -- A formatter that prints the full scientific name of the organism. + infraspecific name, etc. of the organism. +- A widget (form element) that allows the user to select an existing + organism when adding or editing a `stock` record. +- A formatter that displays the full scientific name of the organism, and + might include other information such as the organism's common name. -Class Setup -````````````` -To create a new field, we will extend the `ChadoFieldItemBase`. For a new -field named `MyField` we would create a new file in our module here: -`src/Plugin/Field/FieldType/MyfieldType.php`. The following is an empty -class example: +Type Class Setup +`````````````````` +To create a new field, we will extend the `ChadoFieldItemBase` class. +For a new field named `MyField` we would create a new file in our module here: +`src/Plugin/Field/FieldType/MyfieldType.php` +The following is a simple class example: .. code-block:: php @@ -324,7 +85,7 @@ class example: * Plugin implementation of Tripal string field type. * * @FieldType( - * id = "MyField", + * id = "my_field", * label = @Translation("MyField Field"), * description = @Translation("An example field"), * default_widget = "MyFieldWidget", @@ -333,7 +94,7 @@ class example: */ class MyField extends ChadoFieldItemBase { - public static $id = "MyField"; + public static $id = "my_field"; /** * {@inheritdoc} @@ -385,6 +146,12 @@ class example: $settings = $field_definition->getSetting('storage_plugin_settings'); $base_table = $settings['base_table']; + // If we don't have a base table then we're not ready to specify the + // properties for this field. + if (!$base_table) { + return; + } + // Determine the primary key of the base table. $chado = \Drupal::service('tripal_chado.database'); $schema = $chado->schema(); @@ -393,11 +160,10 @@ class example: // Return the array of property types. return [ - new ChadoIntStoragePropertyType($entity_type_id, self::$id,'record_id', [ + new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'record_id', [ 'action' => 'store_id', 'drupal_store' => TRUE, - 'chado_table' => $base_table, - 'chado_column' => $base_pkey_col + 'path' => $base_table . '.' . $base_pkey_col, ]), ]; } @@ -405,8 +171,8 @@ class example: Below is a line-by-line explanation of each section of the code snippet above. -Namespace and Use Statements -`````````````````````````````` +Type Namespace and Use Statements +``````````````````````````````````` The following should always be present and specifies the namespace for this field. @@ -443,10 +209,10 @@ classes you could import if needed. use Drupal\tripal_chado\TripalStorage\ChadoTextStoragePropertyType; -Annotation Section -```````````````````` +Type Annotation Section +````````````````````````` -The annotation section in the class file is the in-line comments for the class. +The annotation section in the class file is the set of in-line comments for the class. Note the @FieldType stanza in the comments. Drupal uses these annotations to recognize the new field. It provides information such as the field ID, label and description. It also indicates the default widget @@ -468,12 +234,12 @@ and formatter class. This annotation is required. .. warning:: - If the annotation section is not present, has misspellings or is not + If the annotation section is not present, has misspellings, or is not complete, the field will not be recognized by Drupal. -Class Definition -`````````````````` +Type Class Definition +``````````````````````` Next, the class definition line must extend the `ChadoFieldItemBase` class. You must name your class the same as the filename in which it is contained (minus @@ -528,7 +294,7 @@ As an example, the Tripal organism field sets the term ID space and accession: Not all fields will need the `termIdSpace` and `termAccession` hardcoded like in the example above. A field can be re-used for different terms and those -can be set with the field is added automatically. See the +can be set when the field is added automatically. See the :ref:`Automate Adding a Field to a Content Type` section. The defaultStorageSettings() Function @@ -551,15 +317,15 @@ between field settings and field storage settings. In the example above the first line calls ``parent::defaultStorageSettings()``. this will retrieve the default settings for all Chado fields. This includes a setting named ``base_table`` in the ``storage_plugin_settings`` array. -The ``ChadoStorage`` backend requires a ``base_table`` setting to tell it what table +The ``ChadoStorage`` backend always requires a ``base_table`` setting to tell it what table of Chado this field works with. Tripal will pass to the storage backend any settings in the ``storage_plugin_settings`` array. But you are free to add any additional settings you would like to help manage your field, especially if those settings help the field define how it will interact with Chado. -An example where a storage settings is needed is in the ``ChadoStringTypeItem`` field -that gets used for any single-value string mapped to a Chado table column. Here -we must set the maximum length of the string. Here is the corresonding ``defaultStorageSettings`` +An example where a storage setting is needed is in the ``ChadoStringTypeDefault`` field +that gets used for any single-value string mapped to a Chado table column of type ``character varying``. +Here we must set the maximum length of the string. Here is the corresonding ``defaultStorageSettings`` function from this field: .. code:: php @@ -578,7 +344,7 @@ If a field needs input from the user to provide values for settings, then the `storageSettingsForm()` function can be implemented. Add the form elements needed for the user to provide values. -For example, the `ChadoStringTypeItem` field wants to allow the site admin to +For example, the `ChadoStringTypeDefault` field wants to allow the site admin to set the maximum string length. .. code:: php @@ -613,7 +379,7 @@ The site admin will be able to change the storage settings if they: .. note:: Site admins can change storage settings for a field only before it is used. - Once the field is used to store data on a live entity, storage settings are + Once the field is used to store data on a live entity, storage settings become fixed. The fieldSettingsForm() Function @@ -629,7 +395,7 @@ ensure that values provided to fields are appropriate. You can read more about defining validation contraints for fields `here `_. -For following code example, is from the `ChadoStringTypeItem` field. It wants +The following code example is from the `ChadoStringTypeDefault` field. It wants to ensure that that max length of the string is not exceeded. .. code:: php @@ -668,10 +434,15 @@ A property type is actually an object, thus, this function returns an array of p objects. See the :ref:`Property Types` section below for more information about these object classes. -In the code block below you can see the steps where the field settings are +In the example code block below you can see the steps where the field settings are retrieved, and then used to create an array containing a single property. More about properties is described in the next section. +Note that we return from this function early if we do not yet have a base table +defined. This will happen during manual addition of a field through the GUI. +One of the first steps during this process is to select the base table, so +before that is selected, this function should return without doing anything. + .. code-block:: php public static function tripalTypes($field_definition) { @@ -681,6 +452,12 @@ More about properties is described in the next section. $settings = $field_definition->getSetting('storage_plugin_settings'); $base_table = $settings['base_table']; + // If we don't have a base table then we're not ready to specify the + // properties for this field. + if (!$base_table) { + return; + } + // Determine the primary key of the base table. $chado = \Drupal::service('tripal_chado.database'); $schema = $chado->schema(); @@ -713,14 +490,16 @@ properties. These are named after PostgreSQL column types: - **ChadoDateTimeStoragePropertyType**: a date/time property. - **ChadoIntStoragePropertyType**: an integer property. - **ChadoRealStoragePropertyType**: a floating point property. -- **ChadoTextStoragePropertyType**: an unlimited string property. +- **ChadoTextStoragePropertyType**: a string property with unlimited length. - **ChadoVarCharStoragePropertyType**: a string property with a maximum length. -All of these classes can be instantiated with four arguments: +All of these classes can be instantiated with the following arguments: - The entity type ID: the unique ID for the entity type. - The field ID: the unique ID of the field this property belongs to. - The property "key": a unique key for this property. +- The property controlled vocabulary term: namespace and accession. +- (For `ChadoVarCharStoragePropertyType` only): The maximum length of the string. - The property settings: an array of settings for this property. See the :ref:`Property Settings` section below for more information on how to specify the property settings array. @@ -729,34 +508,44 @@ Property Settings ``````````````````` The :ref:`Property Types` section above indicated that each property type class -has a fourth argument that provides settings for the property. These settings +has a fourth argument (fifth argument in the case of `ChadoVarCharStoragePropertyType`) +that provides settings for the property. These settings are critical for describing how the property is managed by the ``ChadoStorage`` backend. The settings are an associative array of key-value pairs that specify an -"action" to perform for each property and corresponding helper information. The -following actions can be used: +"action" to perform for each property and corresponding helper information. + +Several of the actions use a **path** specification, which is a sequence of join +actions to reach the desired column in the desired table. For example if the base +table for the record is ``feature`` and we want to retrieve the organism species, +then the path would be: ``feature.organism_id>organism.organism_id;species``. +Separate multiple joins with a semicolon. For example to get the infraspecific +type name of an organism: ``feature.organism_id>organism.organism_id;organism.type_id>cvterm.cvterm_id;name``. +If we want to specify a column in the base table, then the path might be as simple as: ``feature.feature_id``. + +The following actions can be used: - **store_id**: indicates that the value of this property will hold the record ID (or primary key ID) of the record in the base table of Chado. Common - base tables include: analysis, feature, stock, pub, organism. This action - uses the following key/value pairs: + base tables include: analysis, feature, stock, pub, organism. There will only + be a single `store_id` action in any given field. This action uses the following + key/value pairs: - - **chado_table**: (required) the name of the table that this property will - get stored in. This will always be the base table name (e.g. feature). - - **chado_column**: (required) the name of the column in the table where This - property value will get stored. This will always be the primary key of the - base table (e.g., feature_id). + - **path**: (required) this path specifies the primary key of the base table, + and is composed of the base table name, a period, and the primary key of + the base table (e.g., ``feature.feature_id``). + - **drupal_store**: (required) this setting should always be TRUE for this action. - **store_link**: indicates that the value of this property will hold the - value of a foreign key ID to the base table. A property with this action + value of a foreign key ID to the base table. A property with this action is required for fields that provide ancillary information about a record but that information is not stored in a column of the base table, but instead - in a linked table. Examples for such a situation would be - values from property table: e.g., analysisprop, featureprop, stockprop, etc. + in a linked table. Examples for such a situation would be + values from property table: e.g., analysisprop, featureprop, feature_synonym, etc. This action uses the following key/value pairs: - - **chado_table**: (required) the name of the linked table (e.g. analysisprop) - - **chado_column**: (required) the name of the foreign key column that - links to the base table (e.g. analysis_id) + - **path**: (required) this path specifies the primary key of the linked table, + and specifies a join to the linked table's foreign key, for example + ``$base_table . '.' . $base_pkey_col . '>' . $linker_table . '.' . $linker_fkey_col``. - **drupal_store**: (required) this setting should always be TRUE for this action. This forces Tripal to store this value in the Drupal field tables. Without this, Tripal cannot link the fields in Drupal with a base record. @@ -766,27 +555,26 @@ following actions can be used: property with this action is required for fields that provide ancillary information about a record but that information is not stored in a column of the base table, but instead in a linked table. Examples for such a situation would be - values from property table: e.g., analysisprop, featureprop, stockprop, etc. + values from property table: e.g., analysisprop, featureprop, feature_synonym, etc. This action uses the following key/value pairs: - - **chado_table**: (required) the name of the linked table (e.g. analysisprop) - - **chado_column**: (required) the name of the primary key column that - links to the base table (e.g. analysisprop_id) + - **path**: (required) this path specifies the primary key of the linked table, + and specifies a join to the linked table's primary key, for example + ``$base_table . '.' . $base_pkey_col . '>' . $linker_table . '.' . $linker_table_pkey``. - **drupal_store**: (required) this setting should always be TRUE for this action. This forces Tripal to store this value in the Drupal field tables. Without this, Tripal cannot link the fields in Drupal with a base record. -- **store**: indicates that the value of this property should be stored in the +- **store**: indicates that the value of this property should be stored in a Chado table. This action uses the following key/value pairs: - - **chado_table**: (required) the name of the table that this property will - get stored in. - - **chado_column**: (required) the name of the column in the table where this - property value will get stored. + - **path**: (required) this path specifies the table and column that the + value will be stored in, for example + ``$linker_table . '.synonym_id'``. - **delete_if_empty**: (optional) if TRUE and this field is for ancillary data then the ancillary record should be removed if this value is empty. - **empty_value**: (optional) the value that indicates an empty state. This - could be ``0``, an empty string or NULL. Whichever is appropriate for the + could be ``0``, an empty string, or NULL, whichever is appropriate for the property. This value is used in conjunction with the **delete_if_empty** setting. @@ -808,32 +596,66 @@ following actions can be used: `ChadoStorage` backend will generate, you can rename the `chado_column` with a different name. -- **replace**: indicates that the value of this property is a tokenized string +- **read_value**: this is almost the same as join, but there will be no modification + to the value if we edit a content type, we only look up an existing value. + +- **replace**: indicates that the value of this property is a tokenized string and should be replaced with values from other properties. - **template**: (required) a string containing the value of the field. The string should contain tokens that will be replaced by values of other properties. Tokens are - surrounded by square brackets and contain the keys of other properties. For example. - if the keys for other properties are "genus", "species", "iftype", "ifname" you can - create a property that builds the full scientific name of an organism with the - following template string: - "[genus] [species] [iftype] [ifname]". + surrounded by square brackets and contain the keys of other properties. For example, + in the `ChadoOrganismTypeDefault` field, a property is created that builds + the full scientific name of an organism with the following template string: + "[organism_genus] [organism_species] [organism_infraspecific_type] [organism_infraspecific_name]". + +- **function**: indicates that the value of this property will be set by a + callback function. You will need to pass the namespace for the + callback function, and the function name. Chado storage will generate a + context array that the function can access to calculate its value. + The callback function then returns a single value which becomes the value + for this property. + + - **namespace**: (required) the namespace of the callback function, which may be the + namespace of the field itself, or the namespace of a parent class. + - **function**: (required) the name of the callback function + +.. note:: -- **function**: indicates that the value of this property will be set by a - callback function. + All of the core tripal fields that link to another content type define a `function` property to + retrieve the Drupal entity corresponding to that linked content. We also pass through the + foreign key which is needed to look up the Chado entity. + For an example, see any of the linking fields such as the `ChadoContactTypeDefault` field. - - *Currently not implemented in Alpha release v1* As an example, let's look at the ``tripalTypes()`` function of the field that -allows an end-user to add an organism to content. This code is found -in the ``tripal_chado\src\Plugin\Field\FieldType\obi__organism.php`` file of -Tripal: +allows an end-user to add an organism to content. This is a simplified version of the +code that is found in the +``tripal_chado\src\Plugin\Field\FieldType\ChadoOrganismTypeDefault.php`` +file of Tripal. + +.. warning:: + + This is just an example, the full version of this field is significantly more complicated. + In the full version we store all of the organism table columns, and in addition to + supporting base tables with an `organism_id` column, we also handle organisms connected + through a linking table such as `organism_pub` or `featuremap_organism`. .. code:: php public static function tripalTypes($field_definition) { $entity_type_id = $field_definition->getTargetEntityTypeId(); + // Get the settings for this field. + $settings = $field_definition->getSetting('storage_plugin_settings'); + $base_table = $settings['base_table']; + + // If we don't have a base table then we're not ready to specify the + // properties for this field. + if (!$base_table) { + return; + } + // Get the length of the database fields so we don't go over the size limit. $chado = \Drupal::service('tripal_chado.database'); $schema = $chado->schema(); @@ -843,52 +665,69 @@ Tripal: $species_len = $organism_def['fields']['species']['size']; $iftype_len = $cvterm_def['fields']['name']['size']; $ifname_len = $organism_def['fields']['infraspecific_name']['size']; - $label_len = $genus_len + $species_len + $iftype_len + $ifname_len; + $scientific_name_len = $genus_len + $species_len + $iftype_len + $ifname_len + 3; // Get the base table columns needed for this field. - $settings = $field_definition->getSetting('storage_plugin_settings'); - $base_table = $settings['base_table']; $base_schema_def = $schema->getTableDef($base_table, ['format' => 'Drupal']); $base_pkey_col = $base_schema_def['primary key']; $base_fk_col = array_keys($base_schema_def['foreign keys']['organism']['columns'])[0]; + // Get the CV terms used for each of the properties + $storage = \Drupal::entityTypeManager()->getStorage('chado_term_mapping'); + $mapping = $storage->load('core_mapping'); + $record_id_term = 'SIO:000729'; + $drupal_entity_term = 'schema:ItemPage'; + $organism_id_term = $mapping->getColumnTermId($base_table, 'organism_id'); + $genus_term = $mapping->getColumnTermId('organism', 'genus'); + $species_term = $mapping->getColumnTermId('organism', 'genus'); + $infraspecific_type_term = $mapping->getColumnTermId('cvterm', 'name'); + $infraspecific_name_term = $mapping->getColumnTermId('organism', 'infraspecific_name'); + $scientific_name_term = 'NCBITaxon:scientific_name'; + // Return the properties for this field. return [ - new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'record_id', [ + new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'record_id', $record_id_term, [ 'action' => 'store_id', 'drupal_store' => TRUE, - 'chado_table' => $base_table, - 'chado_column' => $base_pkey_col + 'path' => $base_table . '.' . $base_pkey_col, ]), - new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'organism_id', [ + new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'entity_id', $drupal_entity_term, [ + 'action' => 'function', + 'drupal_store' => TRUE, + 'namespace' => self::$chadostorage_namespace, // the namespace of the parent class Drupal\tripal_chado\Plugin\TripalStorage\ChadoStorage + 'function' => self::$drupal_entity_callback, // i.e. drupalEntityIdLookupCallback + 'fkey' => 'organism_id', + ]), + new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'organism_id', $organism_id_term, [ 'action' => 'store', - 'chado_table' => $base_table, - 'chado_column' => $base_fk_col, + 'drupal_store' => TRUE, + 'path' => $base_table . '.' . $base_fk_col, ]), - new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'label', $label_len, [ - 'action' => 'replace', - 'template' => "[genus] [species] [infraspecific_type] [infraspecific_name]", + new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'organism_genus', $genus_term, $genus_len, [ + 'action' => 'read_value', + 'drupal_store' => FALSE, + 'path' => $base_table . '.organism_id>organism.organism_id;genus', ]), - new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'genus', $genus_len, [ - 'action' => 'join', - 'path' => $base_table . '.organism_id>organism.organism_id', - 'chado_column' => 'genus' + new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'organism_species', $species_term, $species_len, [ + 'action' => 'read_value', + 'drupal_store' => FALSE, + 'path' => $base_table . '.organism_id>organism.organism_id;species', ]), - new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'species', $species_len, [ - 'action' => 'join', - 'path' => $base_table . '.organism_id>organism.organism_id', - 'chado_column' => 'species' + new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'organism_infraspecific_type', $infraspecific_type_term, [ + 'action' => 'read_value', + 'drupal_store' => FALSE, + 'path' => $base_table . '.organism_id>organism.organism_id;organism.type_id>cvterm.cvterm_id;name', + 'as' => 'infraspecific_type' ]), - new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'infraspecific_name', $ifname_len, [ - 'action' => 'join', - 'path' => $base_table . '.organism_id>organism.organism_id', - 'chado_column' => 'infraspecific_name', + new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'organism_infraspecific_name', $infraspecific_name_term, $ifname_len, [ + 'action' => 'read_value', + 'drupal_store' => FALSE, + 'path' => $base_table . '.organism_id>organism.organism_id;infraspecific_name', ]), - new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'infraspecific_type', [ - 'action' => 'join', - 'path' => $base_table . '.organism_id>organism.organism_id;organism.type_id>cvterm.cvterm_id', - 'chado_column' => 'name', - 'as' => 'infraspecific_type_name' + new ChadoVarCharStoragePropertyType($entity_type_id, self::$id, 'organism_scientific_name', $scientific_name_term, $scientific_name_len, [ + 'action' => 'replace', + 'drupal_store' => FALSE, + 'template' => "[organism_genus] [organism_species] [organism_infraspecific_type] [organism_infraspecific_name]", ]) ]; } @@ -900,52 +739,29 @@ end-user to enter a numeric organism is not ideal. Also we want our formatter to print a nicely formatted scientific name for the organism. We need more properties. -In the code above, we create seven properties for this field. As required we +In the code above, we create eight properties for this field. As required, we must have a property that uses the action ``store_id`` that will house the record ID (e.g., feature.feature_id). Because this field is supposed to store the ``organism_id`` for the feature, stock, etc., we have a property that uses the action ``store`` and maps to the ``organism_id`` column of the table. -We also have a variety of properties with a join action. These are used to -join on the base table to get information such as the genus, species, -and infraspecific type. Lastly, we have a property with the action ``replace`` -that uses a tokenized string to create a nicely formatted scientific name for -the organism. - +Because when the field displays the organism on a page, we would like to be able +to click on the organism name, and go to the corresponding organism page, we need +to store the Drupal entity ID for that organism. This is handled by the `entity_id` +property. The field formatter can use this value to create a URI to the organism +page. -Implementing a TripalWidgetBase Class -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. warning:: - - This documentation is still being developed. In the meantime there are examples - in the Tripal core codebase. Specifically, look in the - `tripal_chado/src/Plugin/Field/FieldWidget` directory. - -Implementing a TripalFormatterBase Class -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. warning:: - - This documentation is still being developed. In the meantime there are examples - in the Tripal core codebase. Specifically, look in the - `tripal_chado/src/Plugin/Field/FieldFormatter` directory. - -Automate Adding a Field to a Content Type ------------------------------------------- - -.. warning:: - - This documentation is still being developed. In the meantime there are - examples for programmatically adding TripalFields in the Tripal core codebase. - Specifically, look in the Chado Preparer class in - `tripal_chado/src/Task/ChadoPreparer.php`. +We also have a variety of properties with a `read_value` action. These are used to +join on the base table to get information such as the genus, species, +and infraspecific type, but these are read-only, the values stored in Chado will +not be modified by this field. Lastly, we have a property with the action ``replace`` +that uses a tokenized string to create the full scientific name for the organism. -What About Fields not for Chado? ---------------------------------- +.. note:: -.. warning:: + A good way to learn about fields is to look at examples of fields in the Tripal + core codebase. Specifically, look in the + `tripal_chado/src/Plugin/Field/FieldType` directory. - This documentation is still being developed. Currently ChadoStorage provides - an example for implementing the TripalStorage data store extension. It can be - found in `tripal_chado/src/Plugin/TripalStorage/ChadoStorage.php`. +The next section :ref:`Field Formatters` will describe how to create a formatter +for this new field. diff --git a/docs/dev_guide/module_dev/fields/widgets.rst b/docs/dev_guide/module_dev/fields/widgets.rst new file mode 100644 index 0000000..519fbd4 --- /dev/null +++ b/docs/dev_guide/module_dev/fields/widgets.rst @@ -0,0 +1,258 @@ +Field Widgets +=============== + +Implementing a ChadoWidgetBase Class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When creating a new Tripal field, the class responsible for allowing +entry or editing of content by a site administrator is the "Widget" class. +This class extends the `ChadoWidgetBase` class. + +Class Setup +````````````` +To create a new field, we will extend the `ChadoWidgetBase` class. +For a new field named `MyField` we would create a new file in our module here: +`src/Plugin/Field/FieldWidget/MyfieldWidget.php` +The following is a simple class example: + +.. code-block:: php + + getValue(); + $record_id = $item_vals['record_id'] ?? 0; + $organism_id = $item_vals['organism_id'] ?? 0; + + $elements = []; + $elements['record_id'] = [ + '#type' => 'value', + '#default_value' => $record_id, + ]; + $elements['organism_id'] = $element + [ + '#type' => 'select', + '#options' => $organisms, + '#default_value' => $organism_id, + '#empty_option' => '-- Select --', + ]; + + return $elements; + } + + /** + * {@inheritDoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + // Remove any empty values that don't have an organism + foreach ($values as $delta => $item) { + if ($item['organism_id'] == '') { + unset($values[$delta]); + } + } + + // Reset the weights + $i = 0; + foreach ($values as $val_key => $value) { + $values[$val_key]['_weight'] = $i; + $i++; + } + return $values; + } + + } + +Below is a line-by-line explanation of each section of the code snippet above. + +Widgets Namespace and Use Statements +`````````````````````````````````````` + +The following should always be present and specifies the namespace for this +field. + +.. code-block:: php + + namespace Drupal\mymodule\Plugin\Field\FieldWidget; + +.. note:: + + Be sure to change `mymodule` in the `namespace` to the name of your module. + +.. warning:: + + If you misspell the `namespace` your field will not work properly. + +The following "use" statements are required for all Chado fields. + +.. code-block:: php + + use Drupal\Core\Field\FieldItemListInterface; + use Drupal\Core\Form\FormStateInterface; + use Drupal\tripal_chado\TripalField\ChadoWidgetBase; + +Widget Annotation Section +`````````````````````````````` +The annotation section in the class file is the set of in-line comments for the class. +This annotation is required. +This section is similar to the annotation section in the previous Type and Formatter classes. +Note the ``field_types`` annotation, which specifies what field types this formatter can be used +for. This should match the ``id`` annotation in the Type class. + +.. code-block:: php + + /** + * Plugin implementation of organism name widget. + * + * @FieldWidget( + * id = "my_field_widget", + * label = @Translation("Organism Scientific Name Widget"), + * description = @Translation("A chado organism scientific name widget."), + * field_types = { + * "my_field" + * } + * ) + */ + +.. warning:: + + If the annotation section is not present, has misspellings, or is not + complete, the field will not be recognized by Drupal. + +Widget Class Definition +```````````````````````````` + +Next, the class definition line must extend the `ChadoWidgetBase` class. You +must name your class the same as the filename in which it is contained (minus +the `.php` extension). + +.. code-block:: php + + class MyFieldWidget extends ChadoWidgetBase { + +.. warning:: + + If you misspell the class name such that it is not the same as the filename + of the file in which it is contained, then the field will not be recognized by + Drupal. + +The formElement() Function +````````````````````````````` +The `formElement()` function is used to define how the form for editing the +field content is constructed. +For our example, a site administrator will want to select one of the existing +organisms to attach to the current content type. + +In the example code block below you can see the steps where the list of existing +organisms is retrieved using a Chado API function. + +.. code-block:: php + + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + + // Get the list of organisms. Second parameter true includes common names. + $organisms = chado_get_organism_select_options(FALSE, TRUE); + +Next we retrieve the values for the existing organism, or zero if one is not set. + +.. code-block:: php + + $item_vals = $items[$delta]->getValue(); + $record_id = $item_vals['record_id'] ?? 0; + $organism_id = $item_vals['organism_id'] ?? 0; + +Next we define two form elements. The first one, ``record_id`` is not +shown on the form, it is the value of the Drupal entity. This is the "host" +page on which this field is being displayed + +.. code-block:: php + + $elements = []; + $elements['record_id'] = [ + '#type' => 'value', + '#default_value' => $record_id, + ]; + +The second form element is an organism select. This select list contains all +organisms currently defined, and the site administrator can select the +appropriate organism. If editing an existing record, then the current +organism is presented as the default. + +.. code-block:: php + + $elements['organism_id'] = $element + [ + '#type' => 'select', + '#options' => $organisms, + '#default_value' => $organism_id, + '#empty_option' => '-- Select --', + ]; + + return $elements; + } + +.. note:: + + For fields with a large number of possible items, it may be more appropriate + to use an autocomplete field. + A working example of this can be found in the additional type field in + `tripal_chado/src/Plugin/Field/FieldWidget/ChadoAdditionalTypeWidgetDefault.php`. + Additional documentation about adding an autocomplete to a form can be found in + :ref:`The form() function`. + +The massageFormValues() function +`````````````````````````````````` + +This function is called afther the form is submitted by the user, and takes care of +removing any records that may have been present earlier, but were removed before saving. +This is a simple example, but your field may need to do more, particularly if the field +is referencing a linked table. + +.. code-block:: php + + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + // Remove any empty values that don't have an organism + foreach ($values as $delta => $item) { + if ($item['organism_id'] == '') { + unset($values[$delta]); + } + } + + // Reset the weights + $i = 0; + foreach ($values as $val_key => $value) { + $values[$val_key]['_weight'] = $i; + $i++; + } + return $values; + } + + +.. note:: + + A good way to learn about fields is to look at examples of fields in the Tripal + core codebase. Specifically, look in the + `tripal_chado/src/Plugin/Field/FieldWidget` directory. diff --git a/docs/dev_guide/module_dev/tripal_fields.rst b/docs/dev_guide/module_dev/tripal_fields.rst new file mode 100644 index 0000000..be928c0 --- /dev/null +++ b/docs/dev_guide/module_dev/tripal_fields.rst @@ -0,0 +1,38 @@ + +Fields (Content building blocks) +======================================== + +Fields are the building blocks of content in Drupal. For example, all content +types (e.g. "Article", or "Basic Page") provide content to the end-user via +fields that are bundled with the content type. For example, when adding a basic +page (a default Drupal content type), the end-user is provided with form +elements (or widgets) that allow the user to set the title and the body text +for the page. The "Body" is a field. When a basic page is +viewed, the body is rendered on the page using formatters, and +Drupal stores the values for the body in the database. Every +field, therefore, provides three types of functionality: instructions +for storage, widgets for allowing input, and formatters for rendering. + +Drupal provides a variety of built-in fields, and extension module developers +have created a multitude of new fields that can be added by the site admin +to add new functionality and support new types of data. Tripal follows this +model, but adds a variety of new object oriented classes to support storage +of data in biological databases such as Chado. + +.. note:: + + Not every custom module will require fields. But if you need a new way + to store and retrieve data, or if you need data to appear on an existing + Tripal content type, then you will want to create a new field for your + custom module. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + fields/overview + fields/types + fields/formatters + fields/widgets + fields/auto_attach + fields/custom_field_backend