manual_field_creation.rst 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. Manual Field Creation
  2. ======================
  3. To show how a TripalField works we will break down a class implementation section by section. Here we will use the **obi__organism** field that comes with Tripal and which extends the ChadoField class. The ChadoField class is almost identical to the TripalField class except that it provides a few extra settings for working with Chado tables. To create your own class you need to create a new class that implements the necessary functions.
  4. .. note::
  5. Creation of your first field may not seem easy! The following document is a lot to think about and consider. Therefore, when you write your first field, don't try to do everything at once. Take it one piece at a time. The variables and functions described here are in order with the most critical components described first. Take it at an even pace.
  6. Directory Structure for Fields
  7. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  8. Before we create our class we must first create a proper directory structure. Tripal expects that all new Tripal field classes are located inside of a custom module in the following directory structure:
  9. .. code-block:: bash
  10. /sites/all/modules/[your_module]/includes/TripalFields/[field_name]/[field_name].inc
  11. /sites/all/modules/[your_module]/includes/TripalFields/[field_name]/[field_name]_widget.inc
  12. /sites/all/modules/[your_module]/includes/TripalFields/[field_name]/[field_name]_formatter.inc
  13. In the directories above the token [your_module] can be substituted with the name of your module and [field_name] is the name of your field. You can name your field whatever you like, but you must use this name consistently in other locations throughout the modules. Because all fields are defined by vocabulary terms, it is custom to name your fields with the vocabulary **short name** followed by two underscores followed by the **term name**, hence: obi__organism. Here the ChadoField implementation goes in the [field_name].inc file, the ChadoFieldWidget in the [field_name]_widget.inc file and the ChadoFieldFormatter in the [field_name]_formatter.inc. All new fields must implement all three classes. in the case of our obi__organism field the directory structure is as follows:
  14. .. code-block:: bash
  15. /sites/all/modules/tripal/tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc
  16. /sites/all/modules/tripal/tripal_chado/includes/TripalFields/obi__organism/obi__organism_widget.inc
  17. /sites/all/modules/tripal/tripal_chado/includes/TripalFields/obi__organism/obi__organism_formatter.inc
  18. Anatomy of the ChadoField Class
  19. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  20. The following describes a ChadoField class from top to bottom. The code for the obi__organism field is shown in order that it appears in the class with descriptions provided for the meaning of each piece of code. To write your own class, duplicate the variables and function and customize accordingly. First, let's look at the definition of the class. The following line defines the class and indicates that it extends the ChadoField class:
  21. .. code-block:: php
  22. class obi__organism extends ChadoField {
  23. .. note::
  24. In the line above, the class is named obi__organism. This must be the same name as the directory in which the field is located. Otherwise, Tripal won't be able to find the field.
  25. Static Member Variables
  26. ~~~~~~~~~~~~~~~~~~~~~~~~
  27. Next, the TripalField/ChadoField class has a section of public static variables. These are variables that you can customize to describe your field to Tripal. Here you will provide the default label that appears for the field, and a description for the field:
  28. .. code-block:: php
  29. // The default label for this field.
  30. public static $default_label = 'Organism';
  31. // The default description for this field.
  32. public static $description = 'The organism to which this resource is associated.';
  33. As described in the section titled Tripal Data Structures, fields that are attached to Bundles are "instances" of a field. Every field instance can be customized differently on each bundle. The following section of code allows your field to provide custom settings. Here we want to "hard-code" the term that defines this field using the $default_instance_settings variable:
  34. .. code-block:: php
  35. // Provide a list of instance specific settings. These can be access within
  36. // the instanceSettingsForm. When the instanceSettingsForm is submitted
  37. // then Drupal with automatically change these settings for the instance.
  38. // It is recommended to put settings at the instance level whenever possible.
  39. // If you override this variable in a child class be sure to replicate the
  40. // term_name, term_vocab, term_accession and term_fixed keys as these are
  41. // required for all TripalFields.
  42. public static $default_instance_settings = array(
  43. // The short name for the vocabulary (e.g. shcema, SO, GO, PATO, etc.).
  44. 'term_vocabulary' => 'OBI',
  45. // The name of the term.
  46. 'term_name' => 'organism',
  47. // The unique ID (i.e. accession) of the term.
  48. 'term_accession' => '0100026',
  49. // Set to TRUE if the site admin is allowed to change the term
  50. // type. This will create form elements when editing the field instance
  51. // to allow the site admin to change the term settings above.
  52. 'term_fixed' => FALSE,
  53. // The format for display of the organism.
  54. 'field_display_string' => '<i>[organism.genus] [organism.species]</i>',
  55. );
  56. Notice in the code above that the elements **term_vocabulary, term_name** and **term_accession** are used to define the vocabulary term that this field maps to. The term_fixed element allows the term to be changed by the site admin if desired. These elements are required of all TripalFields classes. You must always have these elements. However, the **field_display_string** is a variable unique to this obi__organism field! Because this field is displaying the organism we want to allow the site-admin to customize how the organism name is constructed and displayed. Therefore, the **field_display_string** creates this new setting for us. How this setting is used will be described later.
  57. As you may have noticed, a field requires a widget and a formatter. This is why there are three classes for every field. However, Drupal is flexible and allows fields to be edited or displayed by any number of widgets and formatters. By default, Tripal provides one widget class and one formatter class for every field. When you write a new field you will need to do the same and create a new ChadoFieldWidget and ChadoFieldFormatter class (or the corresponding non-Chado versions if you don't need Chado). The following variables in the class indicate what are the default widget and formatter classes (we have not yet created those, but we know their names!):
  58. .. code-block:: php
  59. // The default widget for this field.
  60. public static $default_widget = 'obi__organism_widget';
  61. // The default formatter for this field.
  62. public static $default_formatter = 'obi__organism_formatter';
  63. Drupal allows new instances of fields to be attached to any Bundle. This is really useful for fields like the built in Image field that Drupal provides. It can be very handy to attache an instance of an Image field to any content type and viola! your content type now supports images. However, there are some fields that should never be added via the online Drupal interface. Our organism field is a good example. We probably don't want to allow end-users to add an organism field to a Person content type... In this case we will programmatically control which fields are attached to which Bundles. We'll show that later. But for now, let's set the no_ui variable to TRUE to prevent users from adding our new field to any Bundle.
  64. .. code-block:: php
  65. // A boolean specifying that users should not be allowed to create
  66. // fields and instances of this field type through the UI. Such
  67. // fields can only be created programmatically with field_create_field()
  68. // and field_create_instance().
  69. public static $no_ui = TRUE;
  70. Sometimes a field is meant to provide a visualization or some other functionality. An example of this might be a small search form or link to an analytical service. In these cases we want the field to show up on the web page but it should not appear anywhere else, such as in Tripal's web service that provides access to all content. We can set the no_data variable to TRUE and this will allow it to be seen on the site, but not anywhere else.
  71. .. code-block:: php
  72. // A boolean specifying that the field will not contain any data. This
  73. // should exclude the field from web services or downloads. An example
  74. // could be a quick search field that appears on the page that redirects
  75. // the user but otherwise provides no data.
  76. public static $no_data = FALSE;
  77. .. note::
  78. Be sure to only set this to TRUE when you are absolutely certain the contents would not be needed in web services. Tripal was designed so that what appears on the page will always appear in web services. Aside form the formatting we see on the website, the content should be the same.
  79. Finally, the last item in our Class variables is the **download_formatters**. Tripal provides an API that allows tools to group entities into data collections. Data collections are like "baskets" or "shopping carts". Entities that are in data collections can be downloaded into files. If your field is compatible with specific file downloaders you can specify those here. A file downloader is a special TripalFieldDownloader class that "speaks" certain file formats. Tripal, by default, provides the TripalTabDownloader (for tab-delimited files), the TripalCSVDownloader (for CSV files), a TripalNucFASTADownloader for creating nucleotide FASTA files and a TripalProteinFASTADownloader for protein FASTA files. If your field is compatible with any of these formatters you can specify them in the following array:
  80. .. .. code-block::
  81. // Indicates the download formats for this field. The list must be the
  82. // name of a child class of the TripalFieldDownloader.
  83. public static $download_formatters = array(
  84. 'TripalTabDownloader',
  85. 'TripalCSVDownloader',
  86. );
  87. If your field is compatible with the TripalTabDownloader, for example, your field will be included as a column in a tab delimited file where each row represents contents for a given entity.
  88. The load() function.
  89. ~~~~~~~~~~~~~~~~~~~~~
  90. The first function we want to implement in our class is the load() function. This function is responsible for querying the database and populating the field value. Data that is loaded into the field must be organized in two ways: 1) a value that is visible to the end-users, and 2) values that are visible to Chado for ensuing update/editing of the correct record in Chado when the field is edited. Our obi__organism field is designed to be used for multiple Bundles therefore the code in our load() function must be able to support any Chado table that has a foreign key relationship with the organism table.
  91. To get started, the load() function receives a single argument. The entity object:
  92. .. code-block:: php
  93. public function load($entity) {
  94. Because this is a ChadoField and the TripalChado module supports this field and maps entities to their "base" record on Chado, we get something extra... we get the record itself
  95. .. code-block:: php
  96. $record = $entity->chado_record;
  97. Having the record helps tremendously. Our **obi__organism** field is meant to be attached to genomic feature content types (e.g. genes, mRNA, etc.), germplasm, etc. Therefore, the entity will be a record of one of those types. In the case of a genomic feature, these come from the **feature** table of Chado. In the case of a germplam, these records come from the **stock** table of Chado. Both of these records have an **organism_id** field which is a foreign key to the organism table where we find out details about the organism.
  98. Before we set the values for our field, we need a little bit more information. Remember that all field instances have settings? The Tripal Chado module also populates for us the name of the Chado table and the column that this field maps to. Our obi__organism field can be used for multiple Bundles. A gene bundle would map to the **feature** table of Chado and a germplasm Bundle would map to the **stock** table. We need to know what table and column this field is mapping to: We can get that from the instance object of the class and its settings:
  99. .. code-block:: php
  100. $settings = $this->instance['settings'];
  101. $field_table = $this->instance['settings']['chado_table'];
  102. $field_column = $this->instance['settings']['chado_column'];
  103. Next, we want to get this field name and its type. We obviously know our field name, it is obi__organism. However, we can get the name programmatically as well. Drupal maintains an "informational" array about our field. Inside of that field array we can find lots of interesting information such as our field name and its type (Bundle). We'll need this when we set our field value. But rather than hard-code it, let's grab it programmatically from the field name. It's best to grab it programmatically because there are cases where the field name could change:
  104. .. code-block:: php
  105. $field_name = $this->field['field_name'];
  106. $field_type = $this->field['type'];
  107. Now, let's plan how we want our values to appear in our field. The organism record of Chado v1.3 has a genus, species, abbreviation, infraspecific name, infraspecific type, and a common name. We want these values exposed to the end user. But, wait... when we discussed fields in the Tripal Data Structures section we learned about a name field that provides names for entities. That field only has one value: the name. Our organism field has multiple values (i.e. genus, species, etc.). A field can provide more than just one value but values have to be qualified. We have to provide values in key/value pairs, and the keys must be controlled vocabulary terms. We must use controlled vocabulary terms because we want our field to be searchable by other Tripal sites. For example, the ontology term for the word 'genus' comes from the TAXRANK vocabulary. Fortunately, almost every column of every table in Chado has been mapped to a controlled vocabulary term so we don't need to go hunting for terms. We can use a Chado API function that Tripal provides for getting the ontology terms associated with every column table in Chado. The following code shows these functions retrieving the ontology terms for our values from the organism table:
  108. .. code-block:: php
  109. // Get the terms for each of the keys for the 'values' property.
  110. $label_term = 'rdfs:label';
  111. $genus_term = tripal_get_chado_semweb_term('organism', 'genus');
  112. $species_term = tripal_get_chado_semweb_term('organism', 'species');
  113. $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
  114. $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
  115. Notice that for our organism fields we can easily get the ontology terms for them using the API function **tripal_get_chado_semweb_term**. You will also notice a **label_term** variable. Sometimes a user may want to see the full name of the organism and not pieces of it in various elements. Therefore, we will provide a label in our list of values that will concatenate the full organism name. This field is not in our organism table so we hard-code the term 'rdfs:lable' which is a term from the Resource Data Framework Schema vocabulary that defines a label.
  116. Next, let's initialize our field's value to be empty. When setting a field value we must do so in the entity object that got passed into our load function. The entity is an object and it stores values using the names of the fields. The following code sets an empty record for our field:
  117. .. code-block:: php
  118. // Set some defaults for the empty record.
  119. $entity->{$field_name}['und'][0] = array(
  120. 'value' => array(),
  121. );
  122. Notice that our field has some sub elements. The first is 'und'. This element corresponds to the "language" of the text. Drupal supports multiple spoken languages and wants to know the language of text we provide. For Tripal fields we always use 'und' meaning 'undefined'. The next element is the delta index number. Field have a cardinality, or in other words they can have multiple values. For every value we add we increment that index, always starting at zero. The last element is our 'value' element and it is here where we put our element. You may notice that our **delta** index is hard coded to 0. This is because an entity can only always have one organism that it is associated with. We will never have more than one.
  123. Now that we've got some preliminary values and we've initialized our value array we can start adding values! Before we do though, let's double check that we have a record. If we don't have a record for this entity, we can't get a value.
  124. .. code-block:: php
  125. if ($record) {
  126. Now if we do have a record we need to get the value The first step is to actually get our organism record. For this we will find the record variable to be really handy. It already comes pre-populated with every Chado record that has a foreign-key relationship with our base record. So, in the case of a gene, the record is stored in the feature table which has an organism_id column which is a foreign key to the organism table. So, we know then that our record object has an organism_id property and we can get our organism from that. The only exception is the biomaterial table which uses a field named taxon_id:
  127. .. code-block:: php
  128. if ($field_table == 'biomaterial') {
  129. $organism = $record->taxon_id;
  130. }
  131. else {
  132. $organism = $record->organism_id;
  133. }
  134. We can easily get all of the values we need from this organism object. We can now access the values for this organism using the Chado organism table column names (e.g. $organism->genus, $organism->species).
  135. .. code-block:: php
  136. $label = tripal_replace_chado_tokens($string, $organism);
  137. $entity->{$field_name}['und'][0]['value'] = array(
  138. $label_term => $label,
  139. $genus_term => $organism->genus,
  140. $species_term => $organism->species,
  141. );
  142. // The infraspecific fields were introduced in Chado v1.3.
  143. if (property_exists($organism, 'infraspecific_name')) {
  144. $entity->{$field_name}['und'][0]['value'][$infraspecific_type_term] = NULL;
  145. $entity->{$field_name}['und'][0]['value'][$infraspecific_name_term] = $organism->infraspecific_name;
  146. if ($organism->type_id) {
  147. $entity->{$field_name}['und'][0]['value'][$infraspecific_type_term] = $organism->type_id->name;
  148. }
  149. }
  150. In the code above we are populating our value array and we're using the controlled vocabulary terms we retrieved earlier as the keys.
  151. Okay, so, we have our values set. However, remember, our fields must support two types of values: 1) those for end users; and 2) those that allow us to save values in Chado if the field is edited. If you look at our value array above you will recognize that the entity to which this field is loading data for is for a feature or stock or library, etc. This field represents the organism for a record from one of those tables. If someone wants to edit the entity and change the organism then effectively we need to change the organism_id of that table. But in our values array we don't have the organism_id we only have data about the organism. How will Tripal know how to change the organism for an entity if edited? To do help Tripal out, we have to create special key/value pair to add to our values. These are values that are not meant to be seen by the end-user. The organism_id is a good example of such a value. To create these values we create a key with a special naming scheme: use "chado-" as a prefix, followed by the table name (e.g. feature), followed by two underscores and finally the column name (e.g. organism_id). The following code shows the creation of this value name:
  152. .. code-block:: php
  153. // Set the linker field appropriately.
  154. if ($field_table == 'biomaterial') {
  155. $linker_field = 'chado-biomaterial__taxon_id';
  156. }
  157. else {
  158. $linker_field = 'chado-' . $field_table . '__organism_id';
  159. }
  160. If our entity were of type "gene" then our **field_table** is feature. Therefore, our **linker_field** variable would be **chado-feature__organism_id**. Next, we need to add this to our value:
  161. .. code-block:: php
  162. $entity->{$field_name}['und'][0][$linker_field] = $organism->organism_id;
  163. Notice, though, that we did not add this value inside the 'value' key like we did above for our end-user, such as the following:
  164. .. code-block:: php
  165. $entity->{$field_name}['und'][0]['value'])
  166. Instead, we put it in at the same level as 'value':
  167. .. code-block:: php
  168. $entity->{$field_name}['und'][0][$linker_field]
  169. We do this because anything in the 'value' element is intended for the end-user. Anything outside of the 'value' is meant for Tripal. Adding the organism ID to this field as a Tripal "hidden" value allows Tripal to recognize where these values really came from. When writing your own fields, you must include any values as "hidden" Tripal values that need to be written to the database table. A good way to remember if you a value should be visible to the end-user or hidden for Tripal is to ask yourself these questions:
  170. 1. Does the user need this value? If yes, put it in the 'value' element.
  171. 2. Does Tripal need the value when writing back to the Chado table? If yes, put it as a hidden element.
  172. 3. Does the user need to see the value an will this same value need to be written to the table? If yes, then you have to put the value in both places.
  173. For our **obi__organism** field it is for entities with records in the **feature, stock, library**, etc. tables. Those tables only have an **organism_id** to represent the organism. So, that's the database column this field is supporting. We therefore, need to put that field as a hidden field, and all the others are just helpful to the user and don't get saved in the feature, stock or library tables. So, those go in the values array.
  174. Now, we're at a good stopping point with our field! We can close out our if($record) statement and the function:
  175. .. code-block:: php
  176. }
  177. }
  178. elementInfo() function
  179. ~~~~~~~~~~~~~~~~~~~~~~
  180. The elementInfo() function is necessary to integrate your new field with Drupal Views and Tripal Web Services. Drupal needs to know what data elements your field provides and Tripal needs to know what vocabulary terms to use for each of the data elements. Related to vocabulary terms, all fields are assigned an ontology term for the field itself. Every field has to have an one. But when a field provides more than just a single data value it must also provide vocabulary terms for any sub elements as well. Our obi__organism field provides the genus, species, etc. sub elements and, therefore, we need to describe these to Drupal and Tripal. The elementInfo() function from the obi_organism field is as follows:
  181. .. code-block:: php
  182. /**
  183. * @see TripalField::elementInfo()
  184. */
  185. public function elementInfo() {
  186. $field_term = $this->getFieldTermID();
  187. $genus_term = chado_get_semweb_term('organism', 'genus');
  188. $species_term = chado_get_semweb_term('organism', 'species');
  189. $infraspecific_name_term = chado_get_semweb_term('organism', 'infraspecific_name');
  190. $infraspecific_type_term = chado_get_semweb_term('organism', 'type_id');
  191. return array(
  192. $field_term => array(
  193. 'operations' => array('eq', 'contains', 'starts'),
  194. 'sortable' => TRUE,
  195. 'searchable' => TRUE,
  196. 'readonly' => FALSE,
  197. 'type' => 'xs:complexType',
  198. 'elements' => array(
  199. 'rdfs:label' => array(
  200. 'searchable' => TRUE,
  201. 'name' => 'scientific_name',
  202. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  203. 'sortable' => FALSE,
  204. 'type' => 'xs:string',
  205. 'readonly' => TRUE,
  206. 'required' => FALSE,
  207. ),
  208. $genus_term => array(
  209. 'searchable' => TRUE,
  210. 'name' => 'genus',
  211. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  212. 'sortable' => TRUE,
  213. 'readonly' => FALSE,
  214. 'type' => 'xs:string',
  215. 'required' => TRUE,
  216. ),
  217. $species_term => array(
  218. 'searchable' => TRUE,
  219. 'name' => 'species',
  220. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  221. 'sortable' => TRUE,
  222. 'readonly' => FALSE,
  223. 'type' => 'xs:string',
  224. 'required' => TRUE,
  225. ),
  226. $infraspecific_name_term => array(
  227. 'searchable' => TRUE,
  228. 'name' => 'infraspecies',
  229. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  230. 'sortable' => TRUE,
  231. 'readonly' => FALSE,
  232. 'type' => 'xs:string',
  233. 'required' => FALSE,
  234. ),
  235. $infraspecific_type_term => array(
  236. 'searchable' => TRUE,
  237. 'name' => 'infraspecific_type',
  238. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  239. 'sortable' => TRUE,
  240. 'readonly' => FALSE,
  241. 'type' => 'xs:integer',
  242. 'required' => FALSE,
  243. ),
  244. 'entity' => array(
  245. 'searchable' => FALSE,
  246. ),
  247. ),
  248. ),
  249. );
  250. }
  251. The code above generates and returns an associative array that provides metadata about the field and its elements. The array is structured such that the first-level key is the term for the field. Details about the field are at the second-level and all sub elements are contained in a 'elements' key. In the following code the terms for the field and sub elements are retrieved using TripalField class functions and Tripal API calls:
  252. .. code-block:: php
  253. $field_term = $this->getFieldTermID();
  254. $genus_term = chado_get_semweb_term('organism', 'genus');
  255. $species_term = chado_get_semweb_term('organism', 'species');
  256. $infraspecific_name_term = chado_get_semweb_term('organism', 'infraspecific_name');
  257. $infraspecific_type_term = chado_get_semweb_term('organism', 'type_id');
  258. return array( $field_term => array(
  259. 'operations' => array('eq', 'contains', 'starts'),
  260. 'sortable' => TRUE,
  261. 'searchable' => TRUE,
  262. 'readonly' => FALSE,
  263. 'type' => 'xs:complexType',
  264. 'elements' => array(
  265. Notice the value for $field_term variable was easily obtained by calling the $this->getFieldTermID function and all of the terms for the elements were obtained using the chado_get_semweb_term function which maps table columns in the Chado database schema to ontology terms. The operations key indicates which search filter operations are supported for the field as a whole. For this example these include 'eq' (for equals), 'contains' and 'starts' (for starts with). The field is sortable and searchable so those values are set to TRUE. Later, we weill learn how to implement the sorting, searching and filtering that the field will support. For now we know we want them so we set the values accordingly. Additionally, the field allows updating so 'readonly' is set to FALSE. By convention, the 'type' of a field follows the XML data types for simple types (https://www.w3schools.com/xml/schema_simple.asp) and Complex types (https://www.w3schools.com/xml/schema_complex.asp) that have multiple elements. Because our obi__organism field has subelements and is not a single value, the field type is 'xs:complexType'.
  266. The array keys just mentioned fully describe our field to Drupal and Tripal. Next we will define the sub elements in the same way, and these go in the 'elements' key. First, we will describe the label. Our obi__oranganism field provides a handly label element that concatenates the genus, species and infraspecific name into one simple string. Therefore, we need to describe this element in the same way we described the field itself. In the code below that the key is set to 'rdfs:label' (which is the controlled vocabulary term for a label) and that the child keys are the same as for the field.
  267. .. code-block:: php
  268. 'elements' => array(
  269. 'rdfs:label' => array(
  270. 'searchable' => TRUE,
  271. 'name' => 'scientific_name',
  272. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  273. 'sortable' => FALSE,
  274. 'type' => 'xs:string',
  275. 'readonly' => TRUE,
  276. 'required' => FALSE,
  277. ),
  278. Notice that our field will allow searching, provides a variety of search filter options, is sortable and defines the type as 'xs:string'. The remaining elements follow the same pattern. As another example, here is the genus element:
  279. .. code-block:: php
  280. $genus_term => array(
  281. 'searchable' => TRUE,
  282. 'name' => 'genus',
  283. 'operations' => array('eq', 'ne', 'contains', 'starts'),
  284. 'sortable' => TRUE,
  285. 'readonly' => FALSE,
  286. 'type' => 'xs:string',
  287. 'required' => TRUE,
  288. ),
  289. The major difference in the code above is that the term is provided by the variable $genus_term.
  290. Finally, our obi__organism field provides an 'entity' element that provides information for a published organism entity. We do not provide any filtering, searching or sorting of those values. So the final element appears as:
  291. .. code-block:: php
  292. 'entity' => array(
  293. 'searchable' => FALSE,
  294. ),
  295. In summary, you will always want to describe your field and every element of your field in the array returned by the elementInfo function. However, you do not need to provide sorting, filtering or querying for every element. If your field is read-only and simply provides values you should still describe these elements but you would set the meta data keys appropriately for the behavior of your field. Also, you only need to describe elements in the values array returned by your load function. Remember, there may be other key/value pairs (such as those used to help coordinate inserts/updates into Chado) but those do not need to be described here because they are never seen by the end-user.
  296. query() function
  297. ~~~~~~~~~~~~~~~~~~
  298. As described above in the elementInfo function section, some fields and elements of fields are searchable. if the elementInfo array indicates that the field is searchable and has operations (i.e. filters) then we must provide a way for those queries to occur. This is where the query() function is needed. The following is example code from the query function of our obi__organism field:
  299. .. code-block:: php
  300. public function query($query, $condition) {
  301. $alias = $this->field['field_name'];
  302. $operator = $condition['operator'];
  303. $field_term_id = $this->getFieldTermID();
  304. $genus_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'genus');
  305. $species_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'species');
  306. $infraspecific_name_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'infraspecific_name');
  307. $infraspecific_type_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'type_id');
  308. // Join to the organism table for this field.
  309. $this->queryJoinOnce($query, 'organism', $alias, "base.organism_id = $alias.organism_id");
  310. // If the column is the field name then we're during a search on the full
  311. // scientific name.
  312. if ($condition['column'] == $field_term_id or
  313. $condition['column'] == $field_term_id . ',rdfs:label') {
  314. if (chado_get_version() <= 1.3) {
  315. $query->where("CONCAT($alias.genus, ' ', $alias.species) $operator :full_name", array(':full_name' => $condition['value']));
  316. }
  317. else {
  318. $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', 'base.infraspecific_type = ' . $alias . '_cvterm.type_id', 'LEFT OUTER');
  319. $query->where("CONCAT($alias.genus, ' ', $alias.species, ' ', " . $alias . "'_cvterm.name', ' ', $alias.infraspecific_name) $operator :full_name", array(':full_name' => $condition['value']));
  320. }
  321. }
  322. // If the column is a subfield.
  323. if ($condition['column'] == $species_term) {
  324. $query->condition("$alias.species", $condition['value'], $operator);
  325. }
  326. if ($condition['column'] == $genus_term) {
  327. $query->condition("$alias.genus", $condition['value'], $operator);
  328. }
  329. if ($condition['column'] == $infraspecific_name_term) {
  330. $query->condition("$alias.infraspecific_name", $condition['value'], $operator);
  331. }
  332. if ($condition['column'] == $infraspecific_type_term) {
  333. $this->queryJoinOnce($query, 'cvterm', 'CVT', "base.type_id = CVT.cvterm_id");
  334. $query->condition("CVT.name", $condition['value'], $operator);
  335. }
  336. }
  337. The code above is how the field tells Drupal and Tripal how to find and filter the records that this field corresponds to. First, we retrieve the field alias and operators:and as with the load and elementInfo functions we get the controlled vocabulary terms for our field and field elements:
  338. .. code-block:: php
  339. $alias = $this->field['field_name'];
  340. $operator = $condition['operator'];
  341. $field_term_id = $this->getFieldTermID();
  342. $genus_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'genus');
  343. $species_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'species');
  344. $infraspecific_name_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'infraspecific_name');
  345. $infraspecific_type_term = $field_term_id . ',' . chado_get_semweb_term('organism', 'type_id');
  346. Next, our knowledge of Chado is needed. We know that our obi__organism field will load data from the organism table. Therefore, our search must occur there.