'example_dbxref', // the name of the table linking additional dbxrefs to this node
'base_foreign_key' => 'example_id', // key to link to the chado content created by this node
'base_key_value' => $example_id, // the value of the above key
'fieldset_title' => 'Additional References', // the non-translated title for this fieldset
'additional_instructions' => '' // a non-stranslated string providing additional instructions
);
// Finally, and add the additional form elements to the form
tripal_core_additional_dbxrefs_form($form, $form_state, $details);
return $form;
}
function chado_example_insert($node) {
// if there is an example_id in the $node object then this must be a sync so
// we can skip adding the chado_example as it is already there, although
// we do need to proceed with the rest of the insert
if (!property_exists($node, 'example_id')) {
// Add record to chado example table
// Add to any other tables needed
// Add all additional database references
// This function will create new database references as needed and select existing ones.
// Existing _dbxref links will be cleared and then re-added
tripal_core_additional_dbxrefs_form_update_dbxrefs(
$node, // the node object passed in via hook_insert()
'example_dbxref', // the name of the _dbxref linking table
'example_id', // key to link to the chado content created by this node
$node->example_id // value of the above key
);
}
// Add record to chado_example linking example_id to new node
}
function chado_example_update($node) {
// Update record in chado example table
// Update any other tables needed
// Update all additional database references
// This function will create new database references as needed and select existing ones.
// Existing _dbxref links will be cleared and then re-added
tripal_core_additional_dbxrefs_form_update_dbxrefs(
$node, // the node object passed in via hook_insert()
'example_dbxref', // the name of the _dbxref linking table
'example_id', // key to link to the chado content created by this node
$node->example_id // value of the above key
);
// Don't need to update chado_example linking table since niether example_id or nid can be changed in update
}
* @endcode
*/
/**
* @section
* Additional Database References Form to be added to node forms
*/
/**
* Provides a form for adding to BASE_dbxref and dbxref tables
*
* @param $form
* The Drupal form array into which the dbxref elements will be added
* @param $form_state
* The corresponding form_state array for the form
* @param $details
* An array defining details needed by this form. Required Keys are:
* - linking_table: the name of the dbxref linking table (ie: feature_dbxref)
* - base_foreign_key: the name of the foreign key linking this table to the non-dbxref table (ie: feature_id)
* - base_key_value: the value of the base_foreign_key for the current form (ie: 999 if the feature_id=999)
* Optional keys include:
* - fieldset_title: the non-translated title for this fieldset
* - additional_instructions: a non-translated string providing additional instructions
*/
function tripal_core_additional_dbxrefs_form(&$form, &$form_state, $details) {
// Set Defaults for optional fields
$details['fieldset_title'] = 'Additional Database References';
$details['additional_instructions'] = '';
// the fieldset of the dbxref elements
$form['addtl_dbxrefs'] = array(
'#type' => 'fieldset',
'#title' => t($details['fieldset_title']),
'#description' => t('You may add additional database references by
selecting a database reference type from the dropdown and adding text. You may add
as many database references as desired by clicking the add button on the right. To
remove a database reference, click the remove button. ' . $details['additional_instructions']),
'#prefix' => "
",
'#suffix' => '
'
);
// this form element is a tree, so that we don't puke all of the values into then node variable
// it is set as a tree, and keeps them in the $form_state['values']['dbxref_table'] heading.
$form['addtl_dbxrefs']['dbxref_table'] = array(
'#type' => 'markup',
'#tree' => TRUE,
'#prefix' => '',
'#suffix' => '
',
'#theme' => 'tripal_core_additional_dbxrefs_form_table'
);
/* DBxrefs can come to us in two ways:
*
* 1) In the form state in the $form_state['chado_additional_dbxrefs']. Data is in this field
* when an AJAX call updates the form state or a validation error.
*
* 2) Directly from the database if the record already has dbxref's associated. This
* data is only used the first time the form is loaded. On AJAX calls or validation
* errors the fields on the form are populated from the $form_state['chado_additional_dbxrefs']
* entry.
*/
if (isset($form_state['chado_additional_dbxrefs'])) {
$existing_dbxrefs = $form_state['chado_additional_dbxrefs'];
}
else {
$existing_dbxrefs = chado_query(
"SELECT dbxref.dbxref_id, db.name as db_name, db.db_id as db_id, dbxref.accession as accession, dbxref.description as description, dbxref.version
FROM {dbxref} dbxref
LEFT JOIN {db} db ON db.db_id = dbxref.db_id
LEFT JOIN {".$details['linking_table']."} linking_table ON linking_table.dbxref_id = dbxref.dbxref_id
WHERE linking_table.".$details['base_foreign_key']."= :base_key_value
ORDER BY db.name ASC, dbxref.version ASC",
array(':base_key_value' => $details['base_key_value'])
);
}
/* The format of the $existing_dbxref's array is either:
*
* From the chado_additional_dbxrefs array:
* $form_state['chado_additional_dbxrefs'] = array(
* '[db_id]-[version]' => array(
* 'db_id' => [the db.db_id value]
* 'db_name' => [the db.name value]
* 'dbxref_id' => [the dbxref.dbxref_id value, or NULL if it doesn't yet exists],
* 'version' => [the dbxref.version value],
* 'accession' => [the dbxref.accession value],
* ),
* );
*
* OR
* Populated from the database:
* $existing_dbxref = array(
* 0 => array(
* 'dbxref_id' => [the dbxref.dbxref_id value],
* 'db_name' => [the db.name value],
* 'db_id' => [the db.db_id value],
* 'accession' => [the dbxref.accession value],
* 'description' => [the dbxref.description value],
* 'version' => [the dbxref.versiion value],
* ),
* );
*
* NOTE: The main difference is the key
*
* Loop on the array elements of the $existing_dbxrefs array and add
* an element to the form for each one.
*/
foreach ($existing_dbxrefs as $dbxref) {
// Since the version is part of the key, when it is '' we need to use something
// in the key to indicate this case. Otherwise, you wouldn't be able to select
// those elements from the array (ie: $form['addtl_dbxrefs']['dbxref_table'][9999]['']
// doesn't work as expected whereas $form['addtl_dbxrefs']['dbxref_table'][9999][NONE]
// is much better)
$version = (!empty($dbxref->version)) ? $dbxref->version : 'NONE';
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id] = array(
'#type' => 'markup',
'#value' => ''
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version] = array(
'#type' => 'markup',
'#value' => ''
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['db_id'] = array(
'#type' => 'hidden',
'#value' => $dbxref->db_id
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['accession'] = array(
'#type' => 'hidden',
'#value' => $dbxref->accession
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['dbxref_id'] = array(
'#type' => 'hidden',
'#value' => $dbxref->dbxref_id
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['db'] = array(
'#type' => 'markup',
'#markup' => $dbxref->db_name
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['dbxref_version'] = array(
'#type' => 'markup',
'#markup' => $dbxref->version
);
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['dbxref_accession'] = array(
'#type' => 'markup',
'#markup' => $dbxref->accession
);
// remove button
$form['addtl_dbxrefs']['dbxref_table'][$dbxref->db_id][$version]['dbxref_action'] = array(
'#type' => 'submit',
'#value' => t('Remove'),
'#name' => "dbxref_remove-".$dbxref->db_id.'-'.$version,
'#ajax' => array(
'callback' => "tripal_core_additional_dbxrefs_form_ajax_update",
'wrapper' => 'tripal-generic-edit-addtl_dbxrefs-table',
'effect' => 'fade',
'method' => 'replace',
'prevent' => 'click'
),
// When this button is clicked, the form will be validated and submitted.
// Therefore, we set custom submit and validate functions to override the
// default node form submit. In the validate function we validate only the
// additional dbxref fields and in the submit we remove the indicated dbxref
// from the chado_additional_dbxrefs array. In order to keep validate errors
// from the node form validate and Drupal required errors for non-dbxref fields
// preventing the user from removing dbxrefs we set the #limit_validation_errors below
'#validate' => array('tripal_core_additional_dbxrefs_form_remove_button_validate'),
'#submit' => array('tripal_core_additional_dbxrefs_form_remove_button_submit'),
// Limit the validation of the form upon clicking this button to the dbxref_table tree
// No other fields will be validated (ie: no fields from the main form or any other api
// added form).
'#limit_validation_errors' => array(
array('dbxref_table') // Validate all fields within $form_state['values']['dbxref_table']
)
);
}
// Form elements for adding a new dbxref
//---------------------------------------------
$form['addtl_dbxrefs']['dbxref_table']['new'] = array(
'#type' => 'markup',
'#prefix' => '',
'#suffix' => ''
);
// add in the existing databases
$db_options = array(0 => 'Select a Database');
$select = tripal_core_chado_select('db', array('db_id','name'), array(), array('order_by' => array('name' => 'ASC')));
foreach($select as $db) {
$db_options[$db->db_id] = $db->name;
}
$form['addtl_dbxrefs']['dbxref_table']['new']['db'] = array(
'#type' => 'select',
'#options' => $db_options,
);
$form['addtl_dbxrefs']['dbxref_table']['new']['dbxref_accession'] = array(
'#type' => 'textfield',
);
$form['addtl_dbxrefs']['dbxref_table']['new']['dbxref_version'] = array(
'#type' => 'textfield',
);
// add button
$form['addtl_dbxrefs']['dbxref_table']['new']['dbxref_action'] = array(
'#type' => 'submit',
'#value' => t('Add'),
'#name' => "dbxref-add",
'#ajax' => array(
'callback' => "tripal_core_additional_dbxrefs_form_ajax_update",
'wrapper' => 'tripal-generic-edit-addtl_dbxrefs-table',
'effect' => 'fade',
'method' => 'replace',
'prevent' => 'click'
),
// When this button is clicked, the form will be validated and submitted.
// Therefore, we set custom submit and validate functions to override the
// default node form submit. In the validate function we validate only the
// additional dbxref fields and in the submit we add them to the chado_additional_dbxrefs
// array. In order to keep validate errors from the node form validate and Drupal
// required errors for non-dbxref fields preventing the user from adding dbxrefs we
// set the #limit_validation_errors below
'#validate' => array('tripal_core_additional_dbxrefs_form_add_button_validate'),
'#submit' => array('tripal_core_additional_dbxrefs_form_add_button_submit'),
// Limit the validation of the form upon clicking this button to the dbxref_table tree
// No other fields will be validated (ie: no fields from the main form or any other api
// added form).
'#limit_validation_errors' => array(
array('dbxref_table') // Validate all fields within $form_state['values']['dbxref_table']
)
);
}
/**
* Validate the user input for creating a new dbxref
* Called by the add button in tripal_core_additional_dbxrefs_form
*/
function tripal_core_additional_dbxrefs_form_add_button_validate($form, &$form_state) {
// Ensure the db_id is supplied & Valid
$db = tripal_core_chado_select(
'db',
array('db_id', 'name'),
array('db_id' => $form_state['values']['dbxref_table']['new']['db'])
);
if (!isset($db[0])) {
form_set_error('dbxref_table][new][db', 'Please select a database before attempting to add a new database reference.');
}
else {
$form_state['values']['dbxref_table']['new']['db_name'] = $db[0]->name;
}
// Ensure accession is supplied
if (empty($form_state['values']['dbxref_table']['new']['dbxref_accession'])) {
form_set_error('dbxref_table][new][dbxref_accession','You must enter the accession before attempting to add a new database reference.');
}
}
/**
* Called by the add button in tripal_core_additional_dbxrefs_form
*
* Create an array of additional dbxrefs in the form state. This array will then be
* used to rebuild the form in subsequent builds
*/
function tripal_core_additional_dbxrefs_form_add_button_submit(&$form, &$form_state) {
// if the chado_additional_dbxrefs array is not set then this is the first time modifying the
// dbxref table. this means we need to include all the dbxrefs from the db
if (!isset($form_state['chado_additional_dbxrefs'])) {
tripal_core_additional_dbxrefs_form_create_dbxref_formstate_array($form, $form_state);
}
// get details for the new dbxref
$dbxref = array(
'db_id' => $form_state['values']['dbxref_table']['new']['db'],
'db_name' => $form_state['values']['dbxref_table']['new']['db_name'],
'dbxref_id' => NULL,
'version' => $form_state['values']['dbxref_table']['new']['dbxref_version'],
'accession' => $form_state['values']['dbxref_table']['new']['dbxref_accession'],
);
$version = (!empty($dbxref['version'])) ? $dbxref['version'] : 'NONE';
$key = $dbxref['db_id'] . '-' . $version;
$form_state['chado_additional_dbxrefs'][$key] = (object) $dbxref;
$form_state['rebuild'] = TRUE;
}
/**
* There is no user input for the remove buttons so there is no need to validate
* However, both a submit & validate need to be specified so this is just a placeholder
*
* Called by the many remove buttons in tripal_core_additional_dbxrefs_form
*/
function tripal_core_additional_dbxrefs_form_remove_button_validate($form, $form_state) {
// No Validation needed for remove
}
/**
* Remove the correct dbxref from the form
* Called by the many remove buttons in tripal_core_additional_dbxrefs_form
*/
function tripal_core_additional_dbxrefs_form_remove_button_submit(&$form, &$form_state) {
// if the chado_additional_dbxrefs array is not set then this is the first time modifying the
// dbxref table. this means we need to include all the dbxrefs from the db
if (!isset($form_state['chado_additional_dbxrefs'])) {
tripal_core_additional_dbxrefs_form_create_dbxref_formstate_array($form, $form_state);
}
// remove the specified dbxref from the form dbxref table
if(preg_match('/dbxref_remove-([^-]+-[^-]+)/',$form_state['triggering_element']['#name'],$match)) {
$key = $match[1];
if (array_key_exists($key, $form_state['chado_additional_dbxrefs'])) {
unset($form_state['chado_additional_dbxrefs'][$key]);
}
}
$form_state['rebuild'] = TRUE;
}
/**
* Ajax function which returns the section of the form to be re-rendered
*/
function tripal_core_additional_dbxrefs_form_ajax_update($form, $form_state) {
return $form['addtl_dbxrefs']['dbxref_table'];
}
/**
* Creates an array in form_state containing the existing addtl_dbxrefs. This array is
* then modified by the add/remove buttons and used as a source for rebuilding the form.
* This function get's called at each button (add and remove) button submits the first
* time one of the button's is clicked to instantiates the $form_state['chado_additional_dbxrefs'] array
*
* $form_state['chado_additional_dbxrefs'] = array(
* '[db_id]-[version]' => array(
* 'db_id' => [the db.db_id value]
* 'db_name' => [the db.name value]
* 'dbxref_id' => [the dbxref.dbxref_id value, or NULL if it doesn't yet exists],
* 'version' => [the dbxref.version value],
* 'accession' => [the dbxref.accession value],
* ),
* );
*/
function tripal_core_additional_dbxrefs_form_create_dbxref_formstate_array($form, &$form_state) {
$form_state['chado_additional_dbxrefs'] = array();
foreach (element_children($form['addtl_dbxrefs']['dbxref_table']) as $db_id) {
if ($db_id != 'new') {
foreach (element_children($form['addtl_dbxrefs']['dbxref_table'][$db_id]) as $version) {
$element = $form['addtl_dbxrefs']['dbxref_table'][$db_id][$version];
$dbxref = array(
'db_id' => $element['db_id']['#value'],
'db_name' => $element['db']['#markup'],
'dbxref_id' => $element['dbxref_id']['#value'],
'version' => $element['dbxref_version']['#markup'],
'accession' => $element['dbxref_accession']['#markup'],
);
$version = (!empty($dbxref['version'])) ? $dbxref['version'] : 'NONE';
$key = $dbxref['db_id'] . '-' . $version;
$form_state['chado_additional_dbxrefs'][$key] = (object) $dbxref;
}
}
}
}
/**
* Function to theme the add/remove dbxrefs form into a table
*/
function theme_tripal_core_additional_dbxrefs_form_table($variables) {
$element = $variables['element'];
$header = array(
'db' => t('Database'),
'dbxref_accession' => t('Accession'),
'dbxref_version' => t('Version'),
'dbxref_action' => t('Actions'),
);
$rows = array();
foreach (element_children($element) as $db_id) {
if ($db_id == 'new') {
$row = array();
$row['data'] = array();
foreach ($header as $fieldname => $title) {
$row['data'][] = drupal_render($element[$db_id][$fieldname]);
}
$rows[] = $row;
}
else {
foreach (element_children($element[$db_id]) as $version) {
$row = array();
$row['data'] = array();
foreach ($header as $fieldname => $title) {
$row['data'][] = drupal_render($element[$db_id][$version][$fieldname]);
}
$rows[] = $row;
}
}
}
return theme('table', array(
'header' => $header,
'rows' => $rows
));
}
/**
* This function is used in a hook_insert, hook_update for a node form
* when the additional_dbxrefs form has been added to the form. It retrieves all of the dbxrefs
* and returns them in an array of the format:
*
* $dbxefs[][][] =
*
* This array can then be used for inserting or updating dbxrefs using the API call
* tripal_hook_insert_dbxref()
*
* @param $node
*
* @return
* A dbxref array
*
* @ingroup tripal_databaserefernce_api
*/
function tripal_core_additional_dbxrefs_form_retreive($node) {
$dbxrefs = array();
foreach ($node->dbxref_table as $db_id => $elements) {
if ($db_id != 'new') {
foreach ($elements as $version => $dbxref) {
$dbxref_id = (!empty($dbxref['dbxref_id'])) ? $dbxref['dbxref_id'] : 'NONE';
$dbxrefs[$db_id][$version][$dbxref_id] = $dbxref['accession'];
}
}
}
return $dbxrefs;
}
/**
* This function is used in hook_insert or hook_update and handles inserting of any new
* dbxrefs and creation of links between those dbxrefs and node content
*
* @param $node
* The node passed into hook_insert & hook_update
* @param $linking_table
* The name of the _dbxref linking table (ie: feature_dbxref)
* @param $foreignkey_name
* The name of the foreign key used to link to the node content (ie: feature_id)
* @param $foreignkey_value
* The value of the foreign key (ie: 445, if there exists a feature where feature_id=445)
*/
function tripal_core_additional_dbxrefs_form_update_dbxrefs($node, $linking_table, $foreignkey_name, $foreignkey_value) {
// First remove existing dbxref links
tripal_core_chado_delete($linking_table, array($foreignkey_name => $foreignkey_value));
// Add back in dbxref links and insert dbxrefs as needed
$dbxrefs = tripal_core_additional_dbxrefs_form_retreive($node);
foreach ($dbxrefs as $db_id => $versions) {
foreach ($versions as $version => $elements) {
foreach ($elements as $dbxref_id => $accession) {
// If there is no dbxref then we have to create that first
if ($dbxref_id == 'NONE') {
$version = ($version == 'NONE') ? '' : $version;
$success = tripal_db_add_dbxref(
$db_id,
$accession,
$version,
NULL
);
if ($success) {
$dbxref_id = $success->dbxref_id;
}
else {
$dbxref_id = FALSE;
}
}
// add _dbxref linker
if ($dbxref_id) {
$success_link = tripal_db_add_dbxref_link(
$linking_table,
$dbxref_id,
$foreignkey_name,
$foreignkey_value
);
}
}
}
}
}