custom_widget.rst 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. Creating a Custom Widget
  2. ========================
  3. In Drupal/Tripal terminology, **widget** refers to the form elements for a specific Tripal Field on the "Edit" form of a piece of Tripal Content. For example, the ``obi__organism`` field widget creates the "Organism" drop-down on the edit form of a gene. All fields come with a default widget; however, you can create a custom widget if the default one doesn't meet your needs.
  4. .. note::
  5. This guide assumes you already have your widget class file created. For more information, see :doc:`manual_field_creation` or, :doc:`tripal_field_generator`.
  6. .. note::
  7. If you are only creating a widget and not the whole field, you still need to follow the expected directory structure. For example, if your widget is going to be named ``obi__organism_fancy`` then your file would be ``[your_module]/includes/TripalField/obi__organism_fancy/obi__organism_fancy_widget.inc``.
  8. The Form
  9. --------
  10. The form elements of your widget are defined in the ``form()`` method of your widget according to the `Drupal Form API <https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7.x>`_. As such the ``$widget`` variable is actually a nested associative array describing what the widget portion of the form should look like. For example, the following is how the ``obi__organism`` widget creates the drop-down.
  11. .. code-block:: php
  12. /**
  13. * @see TripalFieldWidget::form()
  14. */
  15. public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
  16. $field_name = $this->field['field_name'];
  17. $field_table = $this->instance['settings']['chado_table'];
  18. $linker_field = 'chado-' . $field_table . '__organism_id';
  19. // The value presented to the user via load.
  20. // If $items['delta']['value'] is set then we are updating and already have this
  21. // information. As such, simply save it again.
  22. $widget['value'] = array(
  23. '#type' => 'value',
  24. '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
  25. );
  26. // Pull out the value previously saved to be used as the default.
  27. $organism_id = 0;
  28. if (count($items) > 0 and array_key_exists($linker_field, $items[0])) {
  29. $organism_id = $items[0][$linker_field];
  30. }
  31. // Define a drop-down form element where the options are organisms retrieved using
  32. // the Tripal API, the default is what we looked up above, and the title and
  33. // description are those set when defining the field.
  34. $widget[$linker_field] = array(
  35. '#type' => 'select',
  36. '#title' => $element['#title'],
  37. '#description' => $element['#description'],
  38. '#options' => chado_get_organism_select_options(FALSE),
  39. '#default_value' => $organism_id,
  40. '#required' => $element['#required'],
  41. '#delta' => $delta,
  42. );
  43. }
  44. At a minimum, the form must have a ``value`` element. For Tripal, the ``value`` element of a field always corresponds to the value that is presented to the end-user either directly on the page (with formatting) or via web services, or some other mechanism. Convention is to store the value of the field as a hidden ``value`` form element as is shown in the above example.
  45. .. note::
  46. For more information on how to use the Drupal Form API, check out the `official Drupal Documentation <https://www.drupal.org/docs/7/api/form-api>`_.
  47. .. note::
  48. The current item is saved in ``$items[$delta]`` as an array where the keys will match those set by the field ``load()`` function.
  49. Validation
  50. ----------
  51. The ``validate()`` function of your widget allows you to confirm that the values entered by the user are valid. It is recommended to consider each form element you created above and consider what is required for that element to be entered "correctly". For example, for an organism drop-down, the organism chosen must exist in our chado database (since this is a ``ChadoFieldWidget``). Luckily this doesn't need to be validated since Drupal ensures only elements in our select list are chosen.
  52. .. warning::
  53. The ``value`` key of this field must be set in the ``$form_state['values']`` array to a **TRUE** value (e.g. a string or non-zero integer) anytime data is entered by the user.
  54. .. note::
  55. For more information on how to validate your data, see the official `Drupal Form Validation Documentation <https://www.drupal.org/docs/7/creating-custom-modules/validating-the-data>`_
  56. Saving the Data
  57. ---------------
  58. The Drupal Storage Backend handles saving of your widget data. As such, **you do not and should not insert, update or delete the data yourself**. It should happen automatically, assuming you've followed the conventions of the specific storage backend.
  59. Chado Fields utilize the chado storage backend to save your data. Thus to ensure your data is saved, you set the columns of your chado table to the values you want them set via the ``$form_state['values']`` array using the ``chado-[table]__[column]`` convention. This should be done at the end of the validation function above, if the data submitted is valid.
  60. For our ``obi__organism`` example, the drop-down returns the chado organism_id of the record chosen by the user. We would like to save that as the organism_id of the chado table the field references, which the following code specifies.
  61. .. code-block:: php
  62. /**
  63. * @see TripalFieldWidget::validate()
  64. */
  65. public function validate($element, $form, &$form_state, $langcode, $delta) {
  66. $field_name = $this->field['field_name'];
  67. $field_table = $this->instance['settings']['chado_table'];
  68. $linker_field = 'chado-' . $field_table . '__organism_id';
  69. //...
  70. // Validate your data here
  71. //...
  72. // In this case, if you have an organism_id, then your user selected this field.
  73. $organism_id = $form_state['values'][$field_name]['und'][0][$linker_field];
  74. if ($organism_id > 0) {
  75. $form_state['values'][$field_name]['und'][0]['value'] = $organism_id;
  76. // This is where we tell the storage backend what we want to save.
  77. // Specifically, that we want to save $organism_id to $field_table.organism_id
  78. $form_state['values'][$field_name]['und'][$delta][$linker_field] = $organism_id;
  79. }
  80. }
  81. But what do you do if the record you want to link to via foreign key constraint doesn't yet exist? Luckily the Chado Storage API has a solution for this as well. Consider the example of the ``sbo__relationship_widget``. When this widget is on the create form for a given content type, we will first need to create the base record before we can create a relationship to it. This is done by setting the values you do know (e.g. ``chado-feature__type_id`` and ``chado-feature__object_id``) but not setting the column mapping to the base record. The Chado Storage API will then fill it in automatically once the base record is created.
  82. .. code-block:: php
  83. /**
  84. * @see TripalFieldWidget::validate()
  85. */
  86. public function validate($element, $form, &$form_state, $langcode, $delta) {
  87. $field_name = $this->field['field_name'];
  88. $field_table = $this->instance['settings']['chado_table'];
  89. $linker_field = 'chado-' . $field_table . '__organism_id';
  90. //...
  91. // Validate your data here
  92. //...
  93. //...
  94. // Determine the subject_id, object_id and type_id based on user input.
  95. // User input is found in $form_state['values'].
  96. //...
  97. // If we have all the keys then set the columns as in the obi__organism ex.
  98. if ($subject_id && $object_id && $type_id) {
  99. // Set all chado fields to their values.
  100. }
  101. // Otherwise, maybe we are creating the entity...
  102. // The storage API should handle this case and automagically add the key in // once the chado record is created... so all we need to do is set the
  103. // other columns.
  104. elseif ($subject_name && $object_id && $type_id) {
  105. $form_state['values'][$field_name][$langcode][$delta]['value'] = 'value must be set but is not used';
  106. $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__' . $object_id_key] = $object_id;
  107. $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__type_id'] = $type_id;
  108. // Notice that the subject_id is not set here.
  109. }
  110. // Otherwise, we don't have a value to insert so leave them blank.
  111. else {
  112. // Set all chado fields to empty string.
  113. }
  114. Drupal typically does not provide a submit hook for fields because, as mentioned above, saving should be done by the storage backend. However, the TripalField provides a ``TripalFieldWidget::submit()`` to allow for behind-the-scenes actions to occur. This function should never be used for updates, deletes or inserts for the Chado table associated with the field as these actions should be handled by the storage backend.
  115. However, it is permissible to perform inserts, updates or deletions within Chado using this function. Those operations can be performed if needed but on other tables not directly associated with the field. An example is the ``chado.feature_synonym`` table. The ``chado_linker__synonym`` field allows the user to provide a brand new synonym and it must add it to the chado.synonym table prior to the record in the chado.feature_synonym table.