Browse Source

Chado Node API: Added API for setting node titles using a generic form. Also added tripal token support including automatic generation of tokens for a given base table

Lacey Sanderson 11 years ago
parent
commit
ef00a82355

+ 5 - 0
tripal_core/api/tripal_core.chado_nodes.api.inc

@@ -93,6 +93,11 @@ function chado_get_nid_from_id($table, $id, $linking_table = NULL) {
   return db_query($sql, array(":" . $table . "_id" => $id))->fetchField();
 }
 
+/**
+ * @section
+ * Sync Form
+ */
+
 /**
  * Generic Sync Form to aid in sync'ing (create drupal nodes linking to chado content)
  * any chado node type.

+ 520 - 0
tripal_core/api/tripal_core.chado_nodes.title_and_path.inc

@@ -0,0 +1,520 @@
+<?php
+
+/**
+ * @file
+ * Contains API functions to set titles and paths for all chado nodes
+ *
+ * TITLES
+ * ====================================
+ * There are two steps to implement the ability to set custom titles for your node type:
+ * 1) Add the "Set Page Titles" Form to your admin settings form
+ * @code
+    // If your module is using the Chado Node: Title & Path API to allow custom titles
+    // for your node type then you need to add the configuration form for this functionality.
+    $details = array(
+      'module' => 'tripal_example',       // the name of the MODULE implementing the content type
+        // An array of options to use under "Page Titles"
+        // the key should be the token and the value should be the human-readable option
+      'options' => array(
+        '[example.name]' => 'Germplasm Name Only',
+        '[example.uniquename]' => 'Germplasm Unique Name Only',
+          // there should always be one options matching the unique constraint.
+          // If you have a more human-readable constraint, then that is preferrable.
+          // See the tripal feature module for a good example of this.
+        '[example.example_id]' => 'Unique Contraint: The Chado ID for Examples'
+      ),
+      // the token indicating the unique constraint in the options array
+      'unique_option' => '[example.example_id]'
+    );
+    // This call adds the configuration form to your current form
+    // This sub-form handles it's own validation & submit
+    chado_add_admin_form_set_title($form, $form_state, $details);
+ * @endcode
+ *
+ * 2) Use chado_get_node_title($node) where ever you want the title for your node. This
+ *    should be done in hook_load(), hook_node_insert(), hook_node_update(). The reason you
+ *    set the title in the node_action hooks, which act on all nodes, is because at that
+ *    point you have the generic loaded node.
+ * @code
+  function tripal_example_load($nodes) {
+
+    $new_nodes = array();
+    foreach ($nodes as $nid => $node) {
+
+      // Add all of the custom content for your node type.
+      // See tripal_example.chado_node.api: chado_example_load()
+
+      // Now get the title
+      $node->title = chado_get_node_title($node);
+
+      $new_nodes[$nid] = $node;
+    }
+
+    return $new_nodes;
+  }
+ * @endcode
+ */
+
+/**
+ * @section
+ * Set Titles
+ */
+
+/**
+ * Get the title of a node based on the Title Format set in the admin section of the module
+ *
+ * @param $node
+ *   The node whose title you want
+ */
+function chado_get_node_title($node) {
+  $content_type = $node->type;
+
+  $format_record = db_select('tripal_token_formats','t')
+    ->fields('t')
+    ->condition('content_type', $content_type,'=')
+    ->condition('application', 'title','=')
+    ->execute()
+    ->fetchObject();
+
+  $title = $format_record->format;
+  $tokens = unserialize($format_record->tokens);
+
+  // Determine which tokens were used in the format string
+  if (preg_match_all('/\[[^]]+\]/',$format_record->format,$used_tokens)) {
+
+    // Get the value for each token used
+    foreach ($used_tokens[0] as $token) {
+      $token_info = $tokens[$token];
+      $value = chado_get_token_value($token_info, $node);
+
+      $title = str_replace($token,$value,$title);
+    }
+  }
+  else {
+    return $format_record->format;
+  }
+
+  return $title;
+}
+
+/**
+ * Generic "Set Node Title" sub-form for setting the title of any chado node
+ *
+ * @param $form
+ *   The Drupal form array into which the property form elements will be added
+ * @param $form_state
+ *   The corresponding form_state array for the form
+ * @param $details
+ *   An array defining details used by this form.
+ *   Required keys that are always required:
+ *     -module: the name of the module implementing the node. For example, for features the module is tripal_feature.
+ *   Optional keys include:
+ *     -fieldset_title: the title to use for the fieldset. Defaults to "Set Page Title".
+ */
+function chado_add_admin_form_set_title(&$form, &$form_state, $details) {
+
+  // Defaults
+  $details['fieldset_title'] = (isset($details['fieldset_title'])) ? $details['fieldset_title'] : 'Set Page Titles';
+  $details['additional_instructions'] = (isset($details['additional_instructions'])) ? $details['additional_instructions'] : '';
+
+  // Get Node Info
+  if (isset($details['module'])) {
+    $node_info = call_user_func($details['module'] . '_node_info');
+    $chado_node_api = $node_info[ $details['module'] ]['chado_node_api'];
+  }
+  else {
+    tripal_report_error(
+      'chado_node_api',
+      TRIPAL_ERROR,
+      "Set Titles API: When calling chado_add_admin_form_set_title, you \$details array must include 'module' => [name of your module] in order to pull out all the information provided in your implementation of hook_node_info"
+    );
+  }
+
+
+  // Determine what the tokens for the custom option should be using the chado schema api
+  $tokens = chado_node_generate_tokens($chado_node_api['base_table']);
+  $token_list = chado_node_format_tokens($tokens);
+
+  // FORM PROPER
+  $msg = t(
+    'Each synced %singular must have a unique page title, however, %plural may have the
+      same name if they are of different types or from different organisms. Therefore,
+      we must be sure that the page titles can uniquely identify the %singular being viewed.
+      Select an option below that will uniquely identify all %plural on your site.'
+      . $details['additional_instructions'],
+    array('%singular' => $chado_node_api['record_type_title']['singular'],
+      '%plural' => $chado_node_api['record_type_title']['plural'])
+  );
+  $form['set_titles'] = array(
+    '#type' => 'fieldset',
+    '#title' => t($details['fieldset_title']),
+    '#description' => $msg,
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+    '#prefix' => "<div id='set_titles-fieldset'>",
+    '#suffix' => '</div>',
+    '#weight'      => 8
+  );
+
+  $form['set_titles']['content_type'] = array(
+    '#type' => 'hidden',
+    '#value' => $node_info[ $details['module'] ]['base'],
+  );
+
+  $details['options']['custom'] = 'Custom: See the text field below.';
+  $form['set_titles']['title_option'] = array(
+    '#title'         => t('%singular Page Titles', array('%singular' => $chado_node_api['record_type_title']['singular'])),
+    '#type'          => 'radios',
+    '#description'   => t("Choose a title type from the list above that is
+      guaranteed to be unique for all %plural. If in doubt it is safest to choose
+      the 'Unique Constaint' option as that guarantees uniqueness.",
+      array('%plural' => $chado_node_api['record_type_title']['plural'])),
+    '#required'      => FALSE,
+    '#options'       => $details['options'],
+    '#default_value' => chado_node_get_token_format('title', $node_info[ $details['module'] ]['base']),
+  );
+
+  $form['set_titles']['title_format_variable'] = array(
+    '#type' => 'hidden',
+    '#value' => $details['module'] . '_title_format'
+  );
+
+  $form['set_titles']['custom_title'] = array(
+    '#type' => 'textarea',
+    '#title' => 'Custom Page Title',
+    '#description' => 'You may rearrange elements in this text box to customize the page
+      titles. The available tokens are listed below. You can separate or include any text
+      between the tokens. <strong>Important: be sure that whatever you choose
+      will always be unique even considering future data that may be added. If in doubt,
+      please select the unique constraint title option above.</strong>',
+    '#default_value' => chado_node_get_token_format('title', $node_info[ $details['module'] ]['base']),
+    '#rows' => 1
+  );
+
+  $form['set_titles']['token_display'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Available Tokens',
+    '#description' => 'Copy the token and paste it into the "Custom Page Title" text field above.',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE
+  );
+
+  $form['set_titles']['token_display']['content'] = array(
+    '#type' => 'item',
+    '#markup' => $token_list
+  );
+
+  $form['set_titles']['tokens'] = array(
+    '#type' => 'hidden',
+    '#value' => serialize($tokens)
+  );
+
+  $form['set_titles']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Set Titles',
+		'#validate' => array('chado_add_admin_form_set_title_form_validate'),
+ 		'#submit' => array('chado_add_admin_form_set_title_form_submit')
+  );
+
+}
+
+/**
+ * Implements hook_form_validate().
+ * VALIDATE: validate the format.
+ */
+function chado_add_admin_form_set_title_form_validate($form, $form_state) {
+
+  // Ensure that all tokens used in the format are in the tokens list
+  if (preg_match_all('/\[[^]]+\]/',$form_state['values']['custom_title'],$used_tokens)) {
+    $token_list = unserialize($form_state['values']['tokens']);
+    foreach ($used_tokens[0] as $token) {
+      if (!array_key_exists($token,$token_list)) {
+        form_set_error('custom_title', 'All tokens used must be in the "Available Tokens" list. Please make sure not to use [ or ] unless it\'s denoting a token');
+      }
+    }
+  }
+
+}
+
+/**
+ * Implements hook_form_submit().
+ * SUBMIT: Actually add the format specified by chado_add_admin_form_set_title()
+ */
+function chado_add_admin_form_set_title_form_submit($form, $form_state) {
+
+  if ($form_state['values']['title_option'] == 'custom') {
+    $format =  $form_state['values']['custom_title'];
+  }
+  else {
+    $format = $form_state['values']['title_option'];
+  }
+
+  chado_node_add_token_format('title', $form_state['values']['content_type'], $format, $form_state['values']['tokens']);
+}
+
+/**
+ * @section
+ * Set Paths
+ */
+
+/**
+ * @section
+ * Tokens
+ */
+
+/**
+ * Save a format to be used by chado_get_node_title() or chado_get_node_path()
+ *
+ * @param $application
+ *   What the format is to be applied to. For example 'title' for generating node titles
+ *   and 'path' for generating node paths
+ * @param $content_type
+ *   The name of the content type this format applies to (ie: $node->type)
+ * @param $format
+ *   A string including tokens used to generate the title/path (which is based on $application)
+ * @param $tokens
+ *   An array of tokens generated by chado_node_generate_tokens(). This is saved to ensure the
+ *   tokens that are available when the format is created are still available when it's used
+ */
+function chado_node_add_token_format($application, $content_type, $format, $tokens) {
+
+  if (is_array($tokens)) {
+    $tokens = serialize($tokens);
+  }
+
+  $record = array(
+    'content_type' => $content_type,
+    'application' => $application,
+    'format' => $format,
+    'tokens' => $tokens
+  );
+
+  // Check if it already exists
+  $id = db_query('SELECT tripal_format_id FROM {tripal_token_formats} WHERE content_type=:type AND application=:application', array(':type'=>$record['content_type'], ':application'=>$record['application']))->fetchField();
+  if ($id) {
+    drupal_write_record('tripal_token_formats',$record,array('content_type','application'));
+  }
+  else {
+    drupal_write_record('tripal_token_formats',$record);
+  }
+
+}
+
+/**
+ * Get the format for the given application of a given content type (ie: the feature title)
+ *
+ * @param $application
+ *   What the format is to be applied to. For example 'title' for generating node titles
+ *   and 'path' for generating node paths
+ * @param $content_type
+ *   The name of the content type this format applies to (ie: $node->type)
+ *
+ * @return
+ *   A string specifying the format
+ */
+function chado_node_get_token_format($application, $content_type) {
+
+  $format_record = db_select('tripal_token_formats','t')
+    ->fields('t')
+    ->condition('content_type', $content_type,'=')
+    ->condition('application', $application,'=')
+    ->execute()
+    ->fetchObject();
+
+  if (is_object($format_record)) {
+    return $format_record->format;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Generate tokens for a particular base table
+ *
+ * @param $base_table
+ *   The name of the chado table you would like to generate tokens for
+ * @param $token_prefix
+ *   RECURSIVE ARG: Used to determine the generic token based on previous interations.
+ *   For example, when adding cvterm fields to a feature token, the token_prefix is "feature.type_id"
+ *   so that resulting tokens can be "feature.type_id>cvterm.*" (ie: [feature.type_id>cvterm.name] )
+ * @param $location_prefix
+ *   RECURSIVE ARG: Used to keep track of the location of the value based on previous interations.
+ *   For example, when adding cvterm fields to a feature token, the location_prefix is "feature > type_id"
+ *   so that resulting tokens can be "feature > type_id > *" (ie: feature > type_id > name)
+ * @return
+ *   An array of available tokens where the key is the table.field and the value is an array
+ *   with the following keys:
+ *    -table: the name of the chado table
+ *    -field: the name of the field in the above table
+ *    -token: the token string (ie: [stock.stock_id])
+ *    -description: a very short description of the token (displayed when tokens are listed)
+ *    -location: the location of the value in a chado node variable with each level
+ *     separated by an arrow (->) symbol. For example, the location for $node->feature->type_id->name
+ *     is feature>type_id>name
+ */
+function chado_node_generate_tokens($base_table, $token_prefix = FALSE, $location_prefix = FALSE) {
+
+  $tokens = array();
+  $table_descrip = chado_get_schema($base_table);
+  foreach ($table_descrip['fields'] as $field_name => $field_details) {
+
+    if (empty($token_prefix)) {
+      $token = '[' . $base_table . '.' . $field_name . ']';
+      $location = implode(' > ',array($base_table, $field_name));
+    }
+    else {
+      $token = '[' . $token_prefix . '>' . $base_table . '.' . $field_name . ']';
+      $location = $location_prefix . ' > ' . $field_name;
+    }
+
+
+    $tokens[$token] = array(
+      'name' => ucwords(str_replace('_',' ',$base_table)) . ': ' . ucwords(str_replace('_',' ',$field_name)),
+      'table' => $base_table,
+      'field' => $field_name,
+      'token' => $token,
+      'description' => $field_details['description'],
+      'location' => $location
+    );
+
+    if (preg_match('/TODO/',$field_details['description'])) {
+      $tokens[$token]['description'] = 'The '.$field_name.' field of the '.$base_table.' table.';
+    }
+  }
+
+  // RECURSION:
+  // Follow the foreign key relationships recursively
+  foreach ($table_descrip['foreign keys'] as $table => $details) {
+    foreach ($details['columns'] as $left_field => $right_field) {
+      if (empty($token_prefix)) {
+        $sub_token_prefix = $base_table . '.' . $left_field;
+        $sub_location_prefix = implode(' > ',array($base_table, $left_field));
+      }
+      else {
+        $sub_token_prefix = $token_prefix . '>' . $base_table . ':' . $left_field;
+        $sub_location_prefix = $location_prefix . ' > ' . implode(' > ',array($base_table, $left_field));
+      }
+
+      $sub_tokens = chado_node_generate_tokens($table, $sub_token_prefix, $sub_location_prefix);
+      if (is_array($sub_tokens)) {
+        $tokens = array_merge($tokens, $sub_tokens);
+      }
+    }
+  }
+
+  return $tokens;
+}
+
+/**
+ * Retrieve the value of the token from the node based on the $token_info['location']
+ *
+ * @param $token_info
+ *   An array of information about the token including:
+ *    -table: the name of the chado table
+ *    -field: the name of the field in the above table
+ *    -token: the token string (ie: [stock.stock_id])
+ *    -description: a very short description of the token (displayed when tokens are listed)
+ *    -location: the location of the value in a chado node variable with each level
+ *     separated by an arrow (>) symbol. For example, the location for $node->feature->type_id->name
+ *     is feature>type_id>name
+ *  @param $node
+ *   The node to get the value of the token from
+ *
+ * @return
+ *   The value of the token
+ */
+function chado_get_token_value($token_info, $node) {
+
+  $location = explode('>',$token_info['location']);
+
+  $var = $node;
+  foreach ($location as $index) {
+    $index = trim($index);
+    if (is_object($var)) {
+      $var = $var->{$index};
+    }
+    elseif (is_array($var)) {
+      $var = $var[$index];
+    }
+    else {
+      tripal_report_error(
+        'chado_node_api',
+        TRIPAL_WARNING,
+        'Tokens: Unable to determine the value of %token. Things went awry when trying
+        to access %index for the following %var',
+        array(
+          '%token' => $token,
+          '%index' => $index,
+          '%var' => print_r($var,TRUE)
+        )
+      );
+      return;
+    }
+  }
+  return $var;
+}
+
+/**
+ * Format a set of tokens for consistent display
+ *
+ * @param $tokens
+ *   An array of tokens from chado_node_generate_tokens()
+ *
+ * @return
+ *   HTML displaying the token list
+ */
+function chado_node_format_tokens($tokens) {
+
+  $header = array('name' => 'Name','token' => 'Token','description' => 'Description');
+  $rows = array();
+
+  usort($tokens, 'chado_sort_tokens_by_location');
+  foreach ($tokens as $token) {
+    $rows[] = array(
+      'name' => $token['name'],
+      'token' => $token['token'],
+      'description' => $token['description']
+    );
+  }
+
+  $table = array(
+    'header' => $header,
+    'rows' => $rows,
+    'attributes' => array(
+      'id' => 'tripal_tokens',
+      'class' => 'tripal-data-table'
+    ),
+    'sticky' => FALSE,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => '',
+  );
+  return theme_table($table);
+}
+
+/**
+ * This sorts tokens first by depth (ie: stock.* is before stock.*>subtable.*) and
+ * then alphabetically within a level (ie: stock.name comes before stock.type_id)
+ *
+ * This is a usort callback and shouldn't be called directly. To use:
+ *    usort($tokens, 'chado_sort_tokens_by_location');
+ */
+function chado_sort_tokens_by_location($tokenA, $tokenB) {
+
+  // First check if they're the same
+  if ($tokenA['location'] == $tokenB['location']) {
+      return 0;
+  }
+
+  // Then check if there's a difference in depth
+  // For example, "stock > type_id" comes before "stock > type_id > name"
+  $tokenA_depth = substr_count($tokenA['location'],'>');
+  $tokenB_depth = substr_count($tokenB['location'],'>');
+  if ($tokenA_depth != $tokenB_depth) {
+    return ($tokenA_depth < $tokenB_depth) ? -1 : 1;
+  }
+
+  // If the depth is equal then just use alphabetical basic string compare
+  return ($tokenA['location'] < $tokenB['location']) ? -1 : 1;
+}

+ 91 - 3
tripal_core/tripal_core.install

@@ -63,7 +63,7 @@ function tripal_core_schema() {
  * @ingroup tripal_core
  */
 function tripal_core_uninstall() {
-  
+
   // drop the foreign key between tripal_custom_tables and tripal_mviews
   // so that Drupal can then drop the tables
   db_query('
@@ -96,6 +96,10 @@ function tripal_core_get_schemas() {
   foreach ($temp as $table => $arr) {
     $schema[$table] = $arr;
   }
+  $temp = tripal_core_tripaltoken_schema();
+  foreach ($temp as $table => $arr) {
+    $schema[$table] = $arr;
+  }
 
   return $schema;
 }
@@ -344,6 +348,47 @@ function tripal_core_custom_tables_schema() {
   return $schema;
 }
 
+function tripal_core_tripaltoken_schema() {
+  $schema = array();
+  $schema['tripal_token_formats'] = array(
+    'fields' => array(
+      'tripal_format_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not NULL' => TRUE
+      ),
+      'content_type' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not NULL' => TRUE
+      ),
+      'application' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not NULL' => TRUE
+      ),
+      'format' => array(
+        'type' => 'text',
+        'not NULL' => TRUE
+      ),
+      'tokens' => array(
+        'type' => 'text',
+        'not NULL' => TRUE
+      ),
+    ),
+    'indexes' => array(
+      'tripal_format_id' => array('tripal_format_id'),
+      'type_application' => array('content_type', 'application'),
+    ),
+    'unique keys' => array(
+      'type_application' => array('content_type', 'application'),
+    ),
+    'primary key' => array('tripal_format_id'),
+  );
+
+  return $schema;
+}
+
 /**
  * Adds an mview_id column to the tripal_custom_tables table and makes an assocation between the mview and the custom table
  *
@@ -368,7 +413,7 @@ function tripal_core_update_7200() {
         ),
       );
       db_add_field('tripal_custom_tables', 'mview_id', $spec, $keys);
-      
+
       // the foreign key specification doesn't really add one to the
       // Drupal schema, it is just used internally, but we want one
       db_query('
@@ -378,7 +423,7 @@ function tripal_core_update_7200() {
         ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
       ');
     }
-    
+
     // now link the materialized view to it's custom table entry
     $mviews = db_select('tripal_mviews', 'tmv')
       ->fields('tmv', array('mview_id', 'mv_table'))
@@ -414,4 +459,47 @@ function tripal_core_update_7201() {
     $error = $e->getMessage();
     throw new DrupalUpdateException('Could not reset language for nodes and url aliases: '. $error);
   }
+}
+
+/**
+ * Adds in tripal token formats table to handle page title and path rewrites
+ */
+function tripal_core_update_7202() {
+  $schema = array();
+  $schema['tripal_token_formats'] = array(
+    'fields' => array(
+      'tripal_format_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not NULL' => TRUE
+      ),
+      'content_type' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not NULL' => TRUE
+      ),
+      'application' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not NULL' => TRUE
+      ),
+      'format' => array(
+        'type' => 'text',
+        'not NULL' => TRUE
+      ),
+      'tokens' => array(
+        'type' => 'text',
+        'not NULL' => TRUE
+      ),
+    ),
+    'indexes' => array(
+      'tripal_format_id' => array('tripal_format_id'),
+      'type_application' => array('content_type', 'application'),
+    ),
+    'unique keys' => array(
+      'type_application' => array('content_type', 'application'),
+    ),
+    'primary key' => array('tripal_format_id'),
+  );
+  db_create_table('tripal_token_formats', $schema['tripal_token_formats']);
 }

+ 1 - 0
tripal_core/tripal_core.module

@@ -20,6 +20,7 @@ require_once 'api/tripal_core.chado_query.api.inc';
 require_once 'api/tripal_core.chado_variables.api.inc';
 require_once 'api/tripal_core.chado_schema.api.inc';
 require_once 'api/tripal_core.chado_nodes.api.inc';
+require_once 'api/tripal_core.chado_nodes.title_and_path.inc';
 require_once 'api/tripal_core.chado_nodes.properties.api.inc';
 require_once 'api/tripal_core.chado_nodes.dbxrefs.api.inc';
 require_once 'api/tripal_core.chado_nodes.relationships.api.inc';

+ 24 - 3
tripal_example/includes/tripal_example.admin.inc

@@ -17,7 +17,7 @@ function tripal_example_admin_examples_listing() {
   $breadcrumb[] = l('Examples', 'admin/tripal/chado/tripal_example');
   drupal_set_breadcrumb($breadcrumb);
 
-  
+
   // EXPLANATION:  Typically for all Tripal modules the home administrative
   // page for the module contains a search form to help the adminstrator
   // locate records.  The following example code adds a default View to
@@ -38,7 +38,7 @@ function tripal_example_admin_examples_listing() {
     $output .= '</ul>';
   }
   */
-  $output = 'Typically a search view goes here';  
+  $output = 'Typically a search view goes here';
 
   return $output;
 }
@@ -55,6 +55,27 @@ function tripal_example_admin() {
     '#markup' => t('There are currently no settings to configure.')
   );
 
+  // If your module is using the Chado Node: Title & Path API to allow custom titles
+  // for your node type then you need to add the configuration form for this functionality.
+  $details = array(
+    'module' => 'tripal_example',       // the name of the MODULE implementing the content type
+      // An array of options to use under "Page Titles"
+      // the key should be the token and the value should be the human-readable option
+    'options' => array(
+      '[example.name]' => 'Germplasm Name Only',
+      '[example.uniquename]' => 'Germplasm Unique Name Only',
+        // there should always be one options matching the unique constraint.
+        // If you have a more human-readable constraint, then that is preferrable.
+        // See the tripal feature module for a good example of this.
+      '[example.example_id]' => 'Unique Contraint: The Chado ID for Examples'
+    ),
+    // the token indicating the unique constraint in the options array
+    'unique_option' => '[example.example_id]'
+  );
+  // This call adds the configuration form to your current form
+  // This sub-form handles it's own validation & submit
+  chado_add_admin_form_set_title($form, $form_state, $details);
+
   return system_settings_form($form);
 }
 
@@ -65,6 +86,6 @@ function tripal_example_admin() {
  * @ingroup tripal_example
  */
 function tripal_example_admin_validate($form, &$form_state) {
-  
+
 }
 

+ 16 - 0
tripal_example/includes/tripal_example.chado_node.inc

@@ -575,6 +575,11 @@ function chado_example_load($nodes) {
     // be included by using this function in the templates.
     $example = chado_expand_var($example, 'field', 'example.description');
 
+    // If your module is using the Chado Node: Title & Path API to allow custom titles
+    // for your node type. Every time you want the title of the node, you need to use the
+    // following API function:
+    $example->title = chado_get_node_title($node);
+
     // add the new example object to this node.
     $nodes[$nid]->example = $example;
   }
@@ -653,6 +658,11 @@ function tripal_example_node_insert($node) {
         $node->example_id = chado_get_id_from_nid('example', $node->nid);
       }
 
+      // If your module is using the Chado Node: Title & Path API to allow custom titles
+      // for your node type. Every time you want the title of the node, you need to use the
+      // following API function:
+      $example->title = chado_get_node_title($node);
+
       // set the URL for this example page
       // see the code in the tripal_feature/includes/tripal_feature.chado_node.inc file
       // in the function tripal_feature_node_insert for an example of how that
@@ -688,6 +698,12 @@ function tripal_example_node_update($node) {
   // add items to other nodes, build index and search results
   switch ($node->type) {
     case 'chado_example':
+
+      // If your module is using the Chado Node: Title & Path API to allow custom titles
+      // for your node type. Every time you want the title of the node, you need to use the
+      // following API function:
+      $example->title = chado_get_node_title($node);
+
       // set the URL for this example page
       // see the code in the tripal_feature/includes/tripal_feature.chado_node.inc file
       // in the function tripal_feature_node_insert for an example of how that