#526122 by bangpound, yched, and catch: Added an autocomplete widget for taxonomy term fields.
parent
80b4cd6610
commit
ae1d9c577b
|
@ -330,6 +330,7 @@ function theme_node_form($form) {
|
|||
*/
|
||||
function node_preview($node) {
|
||||
if (node_access('create', $node) || node_access('update', $node)) {
|
||||
_field_invoke_multiple('load', 'node', array($node->nid => $node));
|
||||
// Load the user's name when needed.
|
||||
if (isset($node->name)) {
|
||||
// The use of isset() is mandatory in the context of user IDs, because
|
||||
|
|
|
@ -84,6 +84,9 @@ function taxonomy_theme() {
|
|||
'field_formatter_taxonomy_term_plain' => array(
|
||||
'arguments' => array('element' => NULL),
|
||||
),
|
||||
'taxonomy_autocomplete' => array(
|
||||
'arguments' => array('element' => NULL),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -228,6 +231,13 @@ function taxonomy_menu() {
|
|||
'access arguments' => array('access content'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
// TODO: remove with taxonomy_term_node_*
|
||||
$items['taxonomy/autocomplete/legacy'] = array(
|
||||
'title' => 'Autocomplete taxonomy',
|
||||
'page callback' => 'taxonomy_autocomplete_legacy',
|
||||
'access arguments' => array('access content'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
$items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array(
|
||||
'title' => 'Vocabulary', // this is replaced by callback
|
||||
|
@ -689,7 +699,7 @@ function taxonomy_form_alter(&$form, $form_state, $form_id) {
|
|||
'#description' => $help,
|
||||
'#required' => $vocabulary->required,
|
||||
'#default_value' => $typed_string,
|
||||
'#autocomplete_path' => 'taxonomy/autocomplete/' . $vocabulary->vid,
|
||||
'#autocomplete_path' => 'taxonomy/autocomplete/legacy/' . $vocabulary->vid,
|
||||
'#weight' => $vocabulary->weight,
|
||||
'#maxlength' => 1024,
|
||||
);
|
||||
|
@ -1840,6 +1850,35 @@ function taxonomy_field_info() {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement hook_field_widget_info().
|
||||
*
|
||||
* We need custom handling of multiple values because we need
|
||||
* to combine them into a options list rather than display
|
||||
* cardinality elements. We will use the field module's default
|
||||
* handling for default values.
|
||||
*
|
||||
* Callbacks can be omitted if default handing is used.
|
||||
* They're included here just so this module can be used
|
||||
* as an example for custom modules that might do things
|
||||
* differently.
|
||||
*/
|
||||
function taxonomy_field_widget_info() {
|
||||
return array(
|
||||
'taxonomy_autocomplete' => array(
|
||||
'label' => t('Autocomplete term widget (tagging)'),
|
||||
'field types' => array('taxonomy_term'),
|
||||
'settings' => array(
|
||||
'size' => 60,
|
||||
'autocomplete_path' => 'taxonomy/autocomplete',
|
||||
),
|
||||
'behaviors' => array(
|
||||
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement hook_field_widget_info_alter().
|
||||
*/
|
||||
|
@ -1874,6 +1913,19 @@ function taxonomy_field_schema($field) {
|
|||
*/
|
||||
function taxonomy_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
|
||||
$allowed_values = taxonomy_allowed_values($field);
|
||||
$widget = field_info_widget_types($instance['widget']['type']);
|
||||
|
||||
// Check we don't exceed the allowed number of values for widgets with custom
|
||||
// behavior for multiple values (taxonomy_autocomplete widget).
|
||||
if ($widget['behaviors']['multiple values'] == FIELD_BEHAVIOR_CUSTOM && $field['cardinality'] >= 2) {
|
||||
if (count($items) > $field['cardinality']) {
|
||||
$errors[$field['field_name']][0][] = array(
|
||||
'error' => 'taxonomy_term_illegal_value',
|
||||
'message' => t('%name: this field cannot hold more that @count values.', array('%name' => t($instance['label']), '@count' => $field['cardinality'])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
if (!empty($item['value'])) {
|
||||
if (!isset($allowed_values[$item['value']])) {
|
||||
|
@ -2052,3 +2104,146 @@ function _taxonomy_clean_field_cache($term) {
|
|||
function taxonomy_term_title($term) {
|
||||
return check_plain($term->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement hook_field_widget().
|
||||
*/
|
||||
function taxonomy_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = NULL) {
|
||||
$element = array(
|
||||
'#type' => $instance['widget']['type'],
|
||||
'#default_value' => !empty($items) ? $items : array(),
|
||||
);
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement hook_field_widget_error().
|
||||
*/
|
||||
function taxonomy_field_widget_error($element, $error) {
|
||||
$field_key = $element['#columns'][0];
|
||||
form_error($element[$field_key], $error['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual autocomplete widget element.
|
||||
*
|
||||
* Build the form element. When creating a form using FAPI #process, note that
|
||||
* $element['#value'] is already set.
|
||||
*
|
||||
* The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
|
||||
*
|
||||
* @todo For widgets to be actual FAPI 'elements', reusable outside of a 'field'
|
||||
* context, they shoudn't rely on $field and $instance. The bits of information
|
||||
* needed to adjust the behavior of the 'element' should be extracted in
|
||||
* hook_field_widget() above.
|
||||
*/
|
||||
function taxonomy_autocomplete_elements_process($element, &$form_state, $form) {
|
||||
$field = $form['#fields'][$element['#field_name']]['field'];
|
||||
$instance = $form['#fields'][$element['#field_name']]['instance'];
|
||||
$field_key = $element['#columns'][0];
|
||||
|
||||
// See if this element is in the database format or the transformed format,
|
||||
// and transform it if necessary.
|
||||
if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
|
||||
$tags = array();
|
||||
foreach ($element['#default_value'] as $item) {
|
||||
$tags[$item['value']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['value']);
|
||||
}
|
||||
$element['#value'] = taxonomy_implode_tags($tags);
|
||||
}
|
||||
$typed_string = $element['#value'];
|
||||
|
||||
$value = array();
|
||||
$element[$field_key] = array(
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $typed_string,
|
||||
'#autocomplete_path' => 'taxonomy/autocomplete/'. $element['#field_name'] .'/'. $element['#bundle'],
|
||||
'#size' => $instance['widget']['settings']['size'],
|
||||
'#attributes' => array('class' => 'text'),
|
||||
'#title' => $element['#title'],
|
||||
'#description' => $element['#description'],
|
||||
'#required' => $element['#required'],
|
||||
);
|
||||
$element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
|
||||
|
||||
// Set #element_validate in a way that it will not wipe out other validation
|
||||
// functions already set by other modules.
|
||||
if (empty($element['#element_validate'])) {
|
||||
$element['#element_validate'] = array();
|
||||
}
|
||||
array_unshift($element['#element_validate'], 'taxonomy_autocomplete_validate');
|
||||
|
||||
// Make sure field info will be available to the validator which does not get
|
||||
// the values in $form.
|
||||
$form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* FAPI function to validate taxonomy term autocomplete element.
|
||||
*/
|
||||
function taxonomy_autocomplete_validate($element, &$form_state) {
|
||||
// Autocomplete widgets do not send their tids in the form, so we must detect
|
||||
// them here and process them independently.
|
||||
if ($form_state['values'][$element['#field_name']]['value']) {
|
||||
|
||||
// @see taxonomy_node_save
|
||||
$field = $form_state['#fields'][$element['#field_name']]['field'];
|
||||
$field_key = $element['#columns'][0];
|
||||
$vids = array();
|
||||
foreach ($field['settings']['allowed_values'] as $tree) {
|
||||
$vids[] = $tree['vid'];
|
||||
}
|
||||
$typed_terms = drupal_explode_tags($form_state['values'][$element['#field_name']]['value']);
|
||||
$values = array();
|
||||
|
||||
foreach ($typed_terms as $typed_term) {
|
||||
|
||||
// See if the term exists in the chosen vocabulary and return the tid;
|
||||
// otherwise, add a new record.
|
||||
$possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => $vids));
|
||||
$typed_term_tid = NULL;
|
||||
|
||||
// tid match, if any.
|
||||
foreach ($possibilities as $possibility) {
|
||||
$typed_term_tid = $possibility->tid;
|
||||
break;
|
||||
}
|
||||
if (!$typed_term_tid) {
|
||||
$vocabulary = taxonomy_vocabulary_load($vids[0]);
|
||||
$edit = array(
|
||||
'vid' => $vids[0],
|
||||
'name' => $typed_term,
|
||||
'vocabulary_machine_name' => $vocabulary->machine_name,
|
||||
);
|
||||
$term = (object) $edit;
|
||||
if ($status = taxonomy_term_save($term)) {
|
||||
$typed_term_tid = $term->tid;
|
||||
}
|
||||
}
|
||||
$values[$typed_term_tid] = $typed_term_tid;
|
||||
}
|
||||
$results = options_transpose_array_rows_cols(array($field_key => $values));
|
||||
form_set_value($element, $results, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement FAPI hook_elements().
|
||||
*
|
||||
* Any FAPI callbacks needed for individual widgets can be declared here,
|
||||
* and the element will be passed to those callbacks for processing.
|
||||
*
|
||||
* Drupal will automatically theme the element using a theme with
|
||||
* the same name as the hook_elements key.
|
||||
*/
|
||||
function taxonomy_elements() {
|
||||
return array(
|
||||
'taxonomy_autocomplete' => array(
|
||||
'#input' => TRUE,
|
||||
'#columns' => array('value'),
|
||||
'#delta' => 0,
|
||||
'#process' => array('taxonomy_autocomplete_elements_process'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ function taxonomy_term_edit($term) {
|
|||
/**
|
||||
* Helper function for autocompletion
|
||||
*/
|
||||
function taxonomy_autocomplete($vid = 0, $tags_typed = '') {
|
||||
function taxonomy_autocomplete_legacy($vid = 0, $tags_typed = '') {
|
||||
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
|
||||
$tags_typed = drupal_explode_tags($tags_typed);
|
||||
$tag_last = drupal_strtolower(array_pop($tags_typed));
|
||||
|
@ -131,11 +131,64 @@ function taxonomy_autocomplete($vid = 0, $tags_typed = '') {
|
|||
$name = t('Did you mean %suggestion', array('%suggestion' => $name));
|
||||
$synonym_matches[$prefix . $n] = filter_xss($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drupal_json(array_merge($term_matches, $synonym_matches));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for autocompletion
|
||||
*/
|
||||
function taxonomy_autocomplete($field_name, $bundle, $tags_typed = '') {
|
||||
$instance = field_info_instance($field_name, $bundle);
|
||||
$field = field_info_field($field_name);
|
||||
|
||||
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
|
||||
$tags_typed = drupal_explode_tags($tags_typed);
|
||||
$tag_last = drupal_strtolower(array_pop($tags_typed));
|
||||
|
||||
$matches = array();
|
||||
if ($tag_last != '') {
|
||||
|
||||
// Part of the criteria for the query come from the field's own settings.
|
||||
$vids = array();
|
||||
foreach ($field['settings']['allowed_values'] as $tree) {
|
||||
$vids[] = $tree['vid'];
|
||||
}
|
||||
|
||||
$query = db_select('taxonomy_term_data', 't');
|
||||
$query->addTag('term_access');
|
||||
|
||||
// Do not select already entered terms.
|
||||
if (!empty($tags_typed)) {
|
||||
$query->condition('t.name', $tags_typed, 'NOT IN');
|
||||
}
|
||||
$tags_return = $query
|
||||
->fields('t', array('tid', 'name'))
|
||||
->condition('t.vid', $vids)
|
||||
// Select rows that match by term name.
|
||||
->condition(db_or()
|
||||
->where("t.name LIKE :last_string", array(':last_string' => '%' . $tag_last . '%'))
|
||||
)
|
||||
->range(0, 10)
|
||||
->execute()
|
||||
->fetchAllKeyed();
|
||||
|
||||
$prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
|
||||
|
||||
$term_matches = array();
|
||||
foreach ($tags_return as $tid => $name) {
|
||||
$n = $name;
|
||||
// Term names containing commas or quotes must be wrapped in quotes.
|
||||
if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
|
||||
$n = '"' . str_replace('"', '""', $name) . '"';
|
||||
}
|
||||
else {
|
||||
$term_matches[$prefix . $n] = filter_xss($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drupal_json(array_merge($term_matches, $synonym_matches));
|
||||
drupal_json($term_matches);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue