Occasionally a node reference or entity reference autocomplete widget will not operate as expected, specifically when it is based off a view reference display. Other widgets, the select box, or list of checkboxes, will still function correctly.
This will happen if the view is depending on a contextual filter (an argument), but is not being provided one. Normally a view can try to automatically fill in the argument if one is not provided based on the current page url. If the view fails to receive an argument and is unable to infer its value from the url path then it will fail to provide any results.
Outlined below is a possible scenario that would cause an autocomplete node reference field to fail.
- You are editing an autocomplete node reference field on a taxonomy term edit page.
- The view reference display you have configured is setup to only show content associated with the 'current' taxonomy term.
- In the view, the taxonomy term argument is provided by the current context if no value is available.
Here is how we have configured the view:
Beneath 'Contextual Filters' clicking on 'Context: Has Taxonomy Term ID' will provide more information on this filter (argument received by views):
Note: In this screenshot we are running the openpublish beta2 profile, which currently has not updated to the latest patches.
Notice that the view will try to fill a value into it's contextual filter if none is provided. It will try to do this based on the current url:
If your widget is setup to be list or select box then the view will be able to determine the current context (a taxonomy term) and provide a default value. Views can do this because the context is determined while the form is being loaded. But if you are using an autocomplete field, the json callback to drupal provides no context and the view has no idea what page it is being accessed from.
A solution can be achieved by providing a custom function that handles the autocomplete callback from json. The function will then explicitly set the views argument to the correct taxonomy term id.
- Alter the existing form field to use a different callback path with hook_form_FORM_ID_alter()
- Create the path router in hook_menu()
- Design the callback function itself to invoke views
Alter The Existing Form Field
In this particular case, after viewing the source of our taxonomy form, we find the form tag id is 'taxonomy-form-term'. This translates into taxonomy_form_term as the FORM_ID when declaring the hook_form_FORM_ID_alter() function. The node reference field itself has been named 'field_myfield_nref' and contains up to 3 descrete values.
<?php
/**
* Implements hook_form_FORM_ID_alter().
*/
function mymodule_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) {
// We will get our term id argument from the from build itself.
$term_id = $form['#term']['tid'];
// This is the path we will create in hook_menu().
$new_path = "mymodule/autocomplete/{$term_id}";
// Maximum number of descrete values (deltas) that are present.
$max_delta = $form['field_myfield_nref']['und']['#max_delta'];
// Hijack the autocomplete callback for each of our values.
for($x=0; $x<=$max_delta; $x++) {
$form['field_myfield_nref']['und'][$x]['nid']['#autocomplete_path'] = $new_path;
}
}
?>
The above hook is enough by itself to get the autocomplete widget to begin polling a different path as you type in characters. Make sure that you have flushed all caches, and that your browser is not caching the old javascript. Now Drupal needs to be configured to do something useful when the above path is being requested.
Create the Path Router
<?php
/**
* Implements hook_menu().
*/
function mymodule_menu() {
// The index will specify which path is responded to.
$items['mymodule/autocomplete/%'] = array(
// This is the function to be envoked.
'page callback' => 'mymodule_autocomplete_callback',
// Which url path segments, delineated by the forward slash (/), should be
// sent to our function as arguments. Zero based.
'page arguments' => array(2),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
?>
Now, when the autocomplete field accesses the path /mymodule/autocomplete/{integer_value} Drupal will execute the function mymodule_autocomplete_callback. Next, the function must be configured to Invoke the correct view and return something useful to the requesting javascript.
Design the Callback Function
<?php
/**
* Autocomplete callback.
*
* Recieve a field autocomplete json request from a taxonomy term edit page.
* Returns a list of article nodes whos titles matches what has already
* been typed into the field so far.
*
* @param int $term_id
* Unique taxonomy term identifier. This is the variable that is represented
* by the % sign of the path in hook_menu().
* @param string $string
* Contents of the json submission. This will be what the user has typed into
* the node reference field so far.
*
* @return drupal_son_output()
* A json formated string containing possible matches constructed by a view.
*/
function mymodule_autocomplete_callback($term_id, $string = '') {
// We know the name of this field specifically because this is an edge case
// solution. More flexible code could be put in place so as to not hard code
// this. The field settings will store which view to use.
$field = field_info_field('field_myfield_nref');
// These options will be received by views. Within the result set that views
// will provide, we want to further limit by comparing the field 'title'
// against what was submitted by the javascript ($string). We will compare
// by 'contains', meaning the title must contain $string. The total results
// returned should be no more than 10.
$options = array(
'string' => $string,
'match' => 'contains',
'ids' => array(),
'limit' => 10,
'title_field' => 'title',
);
$settings = $field['settings']['view'];
// This is the important part below. This view requires an argument for the
// contextual filter to operate when the context can not be determined
// automatically
$settings['args'] = array($term_id);
$matches = array();
// This is where the view is run that is reponsible for creating the possible
// selections for autocomplete. Now we can pass in the argument that would have
// otherwise been empty.
$references = references_potential_references_view('node', $settings['view_name'], $settings['display_name'], $settings['args'], $options);
foreach ($references as $id => $row) {
// Markup is fine in autocompletion results (might happen when rendered
// through Views) but we want to remove hyperlinks.
$suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']);
// Add a class wrapper for a few required CSS overrides.
$matches[$row['title'] . " [nid:$id]"] = '<div class="reference-autocomplete">' . $suggestion . '</div>';
}
return drupal_json_output($matches);
}
?>
Success
By crafting our own module and using the above hooks and callback functions we now have modified the autocomplete field to work as desired. While editing our taxonomy term, the node reference field that allows us to select any node that is already associated with this taxonomy term works correctly. The first two values have already been filled out, while the third is in the process of displaying possible options.