Commit 02a10b31 authored by webchick's avatar webchick

Issue #675446 by mgifford, RobLoach, amateescu, nod_, longwave, oxyc,...

Issue #675446 by mgifford, RobLoach, amateescu, nod_, longwave, oxyc, rteijeiro, tomyouds, Jelle_S, mcrittenden, Sutharsan, hansyg, Angry Dan, clemens.tolboom, droplet | Dave Reid: Change notice: Use jQuery UI Autocomplete.
parent fcea2f9e
...@@ -2158,20 +2158,8 @@ function form_process_autocomplete($element, &$form_state) { ...@@ -2158,20 +2158,8 @@ function form_process_autocomplete($element, &$form_state) {
if ($access) { if ($access) {
$element['#attributes']['class'][] = 'form-autocomplete'; $element['#attributes']['class'][] = 'form-autocomplete';
$element['#attached']['library'][] = array('system', 'drupal.autocomplete'); $element['#attached']['library'][] = array('system', 'drupal.autocomplete');
// Provide a hidden element for the JavaScript behavior to bind to. Since // Provide a data attribute for the JavaScript behavior to bind to.
// this element is for client-side functionality only, do not process input. $element['#attributes']['data-autocomplete-path'] = $path;
// @todo Refactor autocomplete.js to accept drupalSettings instead of
// requiring extraneous markup.
$element['autocomplete'] = array(
'#type' => 'hidden',
'#input' => FALSE,
'#value' => $path,
'#disabled' => TRUE,
'#attributes' => array(
'class' => array('autocomplete'),
'id' => $element['#id'] . '-autocomplete',
),
);
} }
return $element; return $element;
} }
......
This diff is collapsed.
...@@ -100,7 +100,7 @@ public function getMatches($field, $instance, $entity_type, $entity_id = '', $pr ...@@ -100,7 +100,7 @@ public function getMatches($field, $instance, $entity_type, $entity_id = '', $pr
if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
$key = '"' . str_replace('"', '""', $key) . '"'; $key = '"' . str_replace('"', '""', $key) . '"';
} }
$matches[$prefix . $key] = $label; $matches[] = array('value' => $prefix . $key, 'label' => $label);
} }
} }
} }
......
...@@ -79,21 +79,24 @@ function testEntityReferenceAutocompletion() { ...@@ -79,21 +79,24 @@ function testEntityReferenceAutocompletion() {
// We should get both entities in a JSON encoded string. // We should get both entities in a JSON encoded string.
$input = '10/'; $input = '10/';
$data = $this->getAutocompleteResult('single', $input); $data = $this->getAutocompleteResult('single', $input);
$this->assertIdentical($data[$entity_1->name->value . ' (1)'], check_plain($entity_1->name->value), 'Autocomplete returned the first matching entity'); $this->assertIdentical($data[0]['label'], check_plain($entity_1->name->value), 'Autocomplete returned the first matching entity');
$this->assertIdentical($data[$entity_2->name->value . ' (2)'], check_plain($entity_2->name->value), 'Autocomplete returned the second matching entity'); $this->assertIdentical($data[1]['label'], check_plain($entity_2->name->value), 'Autocomplete returned the second matching entity');
// Try to autocomplete a entity label that matches the first entity. // Try to autocomplete a entity label that matches the first entity.
// We should only get the first entity in a JSON encoded string. // We should only get the first entity in a JSON encoded string.
$input = '10/16'; $input = '10/16';
$data = $this->getAutocompleteResult('single', $input); $data = $this->getAutocompleteResult('single', $input);
$target = array($entity_1->name->value . ' (1)' => check_plain($entity_1->name->value)); $target = array(
$this->assertIdentical($data, $target, 'Autocomplete returns only the expected matching entity.'); 'value' => $entity_1->name->value . ' (1)',
'label' => check_plain($entity_1->name->value),
);
$this->assertIdentical(reset($data), $target, 'Autocomplete returns only the expected matching entity.');
// Try to autocomplete a entity label that matches the second entity, and // Try to autocomplete a entity label that matches the second entity, and
// the first entity is already typed in the autocomplete (tags) widget. // the first entity is already typed in the autocomplete (tags) widget.
$input = $entity_1->name->value . ' (1), 10/17'; $input = $entity_1->name->value . ' (1), 10/17';
$data = $this->getAutocompleteResult('tags', $input); $data = $this->getAutocompleteResult('tags', $input);
$this->assertIdentical($data[$entity_1->name->value . ' (1), ' . $entity_2->name->value . ' (2)'], check_plain($entity_2->name->value), 'Autocomplete returned the second matching entity'); $this->assertIdentical($data[0]['label'], check_plain($entity_2->name->value), 'Autocomplete returned the second matching entity');
// Try to autocomplete a entity label with both a comma and a slash. // Try to autocomplete a entity label with both a comma and a slash.
$input = '"label with, and / t'; $input = '"label with, and / t';
...@@ -103,8 +106,11 @@ function testEntityReferenceAutocompletion() { ...@@ -103,8 +106,11 @@ function testEntityReferenceAutocompletion() {
if (strpos($entity_3->name->value, ',') !== FALSE || strpos($entity_3->name->value, '"') !== FALSE) { if (strpos($entity_3->name->value, ',') !== FALSE || strpos($entity_3->name->value, '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $entity_3->name->value) . ' (3)"'; $n = '"' . str_replace('"', '""', $entity_3->name->value) . ' (3)"';
} }
$target = array($n => check_plain($entity_3->name->value)); $target = array(
$this->assertIdentical($data, $target, 'Autocomplete returns an entity label containing a comma and a slash.'); 'value' => $n,
'label' => check_plain($entity_3->name->value),
);
$this->assertIdentical(reset($data), $target, 'Autocomplete returns an entity label containing a comma and a slash.');
} }
/** /**
......
...@@ -131,7 +131,7 @@ public function testAuthorAutocomplete() { ...@@ -131,7 +131,7 @@ public function testAuthorAutocomplete() {
$this->drupalGet('node/add/page'); $this->drupalGet('node/add/page');
$result = $this->xpath('//input[@id = "edit-name-autocomplete"]'); $result = $this->xpath('//input[@id="edit-name" and contains(@data-autocomplete-path, "user/autocomplete")]');
$this->assertEqual(count($result), 0, 'No autocompletion without access user profiles.'); $this->assertEqual(count($result), 0, 'No autocompletion without access user profiles.');
$admin_user = $this->drupalCreateUser(array('administer nodes', 'create page content', 'access user profiles')); $admin_user = $this->drupalCreateUser(array('administer nodes', 'create page content', 'access user profiles'));
...@@ -139,8 +139,7 @@ public function testAuthorAutocomplete() { ...@@ -139,8 +139,7 @@ public function testAuthorAutocomplete() {
$this->drupalGet('node/add/page'); $this->drupalGet('node/add/page');
$result = $this->xpath('//input[@id = "edit-name-autocomplete"]'); $result = $this->xpath('//input[@id="edit-name" and contains(@data-autocomplete-path, "user/autocomplete")]');
$this->assertEqual((string) $result[0]['value'], url('user/autocomplete'));
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion'); $this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
} }
......
...@@ -8,25 +8,6 @@ ...@@ -8,25 +8,6 @@
* *
* @see autocomplete.js * @see autocomplete.js
*/ */
/* Suggestion list */
#autocomplete {
border: 1px solid;
overflow: hidden;
position: absolute;
z-index: 100;
}
#autocomplete ul {
list-style: none;
list-style-image: none;
margin: 0;
padding: 0;
}
#autocomplete li {
background: #fff;
color: #000;
cursor: default;
white-space: pre;
}
/* Animated throbber */ /* Animated throbber */
.js input.form-autocomplete { .js input.form-autocomplete {
...@@ -37,10 +18,10 @@ ...@@ -37,10 +18,10 @@
.js[dir="rtl"] input.form-autocomplete { .js[dir="rtl"] input.form-autocomplete {
background-position: 0% 2px; background-position: 0% 2px;
} }
.js input.throbbing { .js input.form-autocomplete.ui-autocomplete-loading {
background-position: 100% -18px; /* LTR */ background-position: 100% -18px; /* LTR */
} }
.js[dir="rtl"] input.throbbing { .js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
background-position: 0% -18px; background-position: 0% -18px;
} }
......
...@@ -192,9 +192,10 @@ label button.link { ...@@ -192,9 +192,10 @@ label button.link {
* @see autocomplete.js * @see autocomplete.js
*/ */
/* Suggestion list */ /* Suggestion list */
#autocomplete li.selected { .ui-autocomplete li.ui-menu-item a.ui-state-focus, .autocomplete li.ui-menu-item a.ui-state-hover {
background: #0072b9; background: #0072b9;
color: #fff; color: #fff;
margin: 0;
} }
/** /**
......
...@@ -133,20 +133,18 @@ function testGroupElements() { ...@@ -133,20 +133,18 @@ function testGroupElements() {
public function testFormAutocomplete() { public function testFormAutocomplete() {
$this->drupalGet('form-test/autocomplete'); $this->drupalGet('form-test/autocomplete');
$result = $this->xpath('//input[@id = "edit-autocomplete-1-autocomplete"]'); $result = $this->xpath('//input[@id="edit-autocomplete-1" and contains(@data-autocomplete-path, "form-test/autocomplete-1")]');
$this->assertEqual(count($result), 0, 'Ensure that the user does not have access to the autocompletion');
$result = $this->xpath('//input[@id="edit-autocomplete-2" and contains(@data-autocomplete-path, "form-test/autocomplete-2/value")]');
$this->assertEqual(count($result), 0, 'Ensure that the user does not have access to the autocompletion'); $this->assertEqual(count($result), 0, 'Ensure that the user does not have access to the autocompletion');
$result = $this->xpath('//input[@id = "edit-autocomplete-2-autocomplete"]');
$this->assertEqual(count($result), 0, 'Ensure that the user did not had access to the autocompletion');
$user = $this->drupalCreateUser(array('access autocomplete test')); $user = $this->drupalCreateUser(array('access autocomplete test'));
$this->drupalLogin($user); $this->drupalLogin($user);
$this->drupalGet('form-test/autocomplete'); $this->drupalGet('form-test/autocomplete');
$result = $this->xpath('//input[@id = "edit-autocomplete-1-autocomplete"]'); $result = $this->xpath('//input[@id="edit-autocomplete-1" and contains(@data-autocomplete-path, "form-test/autocomplete-1")]');
$this->assertEqual((string) $result[0]['value'], url('form-test/autocomplete-1'));
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion'); $this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
$result = $this->xpath('//input[@id = "edit-autocomplete-2-autocomplete"]'); $result = $this->xpath('//input[@id="edit-autocomplete-2" and contains(@data-autocomplete-path, "form-test/autocomplete-2/value")]');
$this->assertEqual((string) $result[0]['value'], url('form-test/autocomplete-2/value'));
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion'); $this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
} }
......
...@@ -1096,7 +1096,9 @@ function system_library_info() { ...@@ -1096,7 +1096,9 @@ function system_library_info() {
'dependencies' => array( 'dependencies' => array(
array('system', 'jquery'), array('system', 'jquery'),
array('system', 'drupal'), array('system', 'drupal'),
array('system', 'drupalSettings'),
array('system', 'drupal.ajax'), array('system', 'drupal.ajax'),
array('system', 'jquery.ui.autocomplete'),
), ),
); );
......
...@@ -200,7 +200,7 @@ protected function getMatchingTerms($tags_typed, array $vids, $tag_last) { ...@@ -200,7 +200,7 @@ protected function getMatchingTerms($tags_typed, array $vids, $tag_last) {
if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) { if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
$name = '"' . str_replace('"', '""', $name) . '"'; $name = '"' . str_replace('"', '""', $name) . '"';
} }
$matches[$prefix . $name] = String::checkPlain($term->label()); $matches[] = array('value' => $prefix . $name, 'label' => String::checkPlain($term->label()));
} }
return $matches; return $matches;
} }
......
...@@ -220,13 +220,13 @@ function testNodeTermCreationAndDeletion() { ...@@ -220,13 +220,13 @@ function testNodeTermCreationAndDeletion() {
// The term will be quoted, and the " will be encoded in unicode (\u0022). // The term will be quoted, and the " will be encoded in unicode (\u0022).
$input = substr($term_objects['term3']->label(), 0, 3); $input = substr($term_objects['term3']->label(), 0, 3);
$json = $this->drupalGet('taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id(), array('query' => array('q' => $input))); $json = $this->drupalGet('taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id(), array('query' => array('q' => $input)));
$this->assertEqual($json, '{"\u0022' . $term_objects['term3']->label() . '\u0022":"' . $term_objects['term3']->label() . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term3']->label()))); $this->assertEqual($json, '[{"value":"\u0022' . $term_objects['term3']->label() . '\u0022","label":"' . $term_objects['term3']->label() . '"}]', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term3']->label())));
// Test autocomplete on term 4 - it is alphanumeric only, so no extra // Test autocomplete on term 4 - it is alphanumeric only, so no extra
// quoting. // quoting.
$input = substr($term_objects['term4']->label(), 0, 3); $input = substr($term_objects['term4']->label(), 0, 3);
$this->drupalGet('taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id(), array('query' => array('q' => $input))); $this->drupalGet('taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id(), array('query' => array('q' => $input)));
$this->assertRaw('{"' . $term_objects['term4']->label() . '":"' . $term_objects['term4']->label() . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term4']->label()))); $this->assertRaw('[{"value":"' . $term_objects['term4']->label() . '","label":"' . $term_objects['term4']->label() . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term4']->label())));
// Test taxonomy autocomplete with a nonexistent field. // Test taxonomy autocomplete with a nonexistent field.
$field_name = $this->randomName(); $field_name = $this->randomName();
...@@ -261,15 +261,18 @@ function testTermAutocompletion() { ...@@ -261,15 +261,18 @@ function testTermAutocompletion() {
// The result order is not guaranteed, so check each term separately. // The result order is not guaranteed, so check each term separately.
$result = $this->drupalGet($path, array('query' => array('q' => $input))); $result = $this->drupalGet($path, array('query' => array('q' => $input)));
$data = drupal_json_decode($result); $data = drupal_json_decode($result);
$this->assertEqual($data[$first_term->label()], check_plain($first_term->label()), 'Autocomplete returned the first matching term.'); $this->assertEqual($data[0]['label'], check_plain($first_term->label()), 'Autocomplete returned the first matching term');
$this->assertEqual($data[$second_term->label()], check_plain($second_term->label()), 'Autocomplete returned the second matching term.'); $this->assertEqual($data[1]['label'], check_plain($second_term->label()), 'Autocomplete returned the second matching term');
// Try to autocomplete a term name that matches first term. // Try to autocomplete a term name that matches first term.
// We should only get the first term in a json encoded string. // We should only get the first term in a json encoded string.
$input = '10/16'; $input = '10/16';
$path = 'taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id(); $path = 'taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id();
$this->drupalGet($path, array('query' => array('q' => $input))); $this->drupalGet($path, array('query' => array('q' => $input)));
$target = array($first_term->label() => check_plain($first_term->label())); $target = array(array(
'value' => check_plain($first_term->label()),
'label' => $first_term->label(),
));
$this->assertRaw(drupal_json_encode($target), 'Autocomplete returns only the expected matching term.'); $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns only the expected matching term.');
// Try to autocomplete a term name with both a comma and a slash. // Try to autocomplete a term name with both a comma and a slash.
...@@ -281,7 +284,10 @@ function testTermAutocompletion() { ...@@ -281,7 +284,10 @@ function testTermAutocompletion() {
if (strpos($third_term->label(), ',') !== FALSE || strpos($third_term->label(), '"') !== FALSE) { if (strpos($third_term->label(), ',') !== FALSE || strpos($third_term->label(), '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $third_term->label()) . '"'; $n = '"' . str_replace('"', '""', $third_term->label()) . '"';
} }
$target = array($n => check_plain($third_term->label())); $target = array(array(
'value' => $n,
'label' => check_plain($third_term->label()),
));
$this->assertRaw(drupal_json_encode($target), 'Autocomplete returns a term containing a comma and a slash.'); $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns a term containing a comma and a slash.');
} }
......
...@@ -101,8 +101,8 @@ public function testFilterUI() { ...@@ -101,8 +101,8 @@ public function testFilterUI() {
$display['display_options']['filters']['tid']['type'] = 'textfield'; $display['display_options']['filters']['tid']['type'] = 'textfield';
$view->save(); $view->save();
$this->drupalGet('admin/structure/views/nojs/config-item/test_filter_taxonomy_index_tid/default/filter/tid'); $this->drupalGet('admin/structure/views/nojs/config-item/test_filter_taxonomy_index_tid/default/filter/tid');
$result = $this->xpath('//input[@id = "edit-options-value-autocomplete"]'); $result = $this->xpath('//input[@id="edit-options-value"]/@data-autocomplete-path');
$this->assertEqual((string) $result[0]['value'], url('taxonomy/autocomplete_vid/tags')); $this->assertEqual((string) $result[0], url('taxonomy/autocomplete_vid/tags'));
} }
} }
...@@ -53,8 +53,8 @@ function testUserAutocomplete() { ...@@ -53,8 +53,8 @@ function testUserAutocomplete() {
// Test that anonymous username is in the result when requested and escaped // Test that anonymous username is in the result when requested and escaped
// with check_plain(). // with check_plain().
$users = $this->drupalGetJSON('user/autocomplete/anonymous', array('query' => array('q' => drupal_substr($anonymous_name, 0, 4)))); $users = $this->drupalGetJSON('user/autocomplete/anonymous', array('query' => array('q' => drupal_substr($anonymous_name, 0, 4))));
$this->assertTrue(in_array(check_plain($anonymous_name), $users), 'The anonymous name found in autocompletion results.'); $this->assertEqual(check_plain($anonymous_name), $users[0]['label'], 'The anonymous name found in autocompletion results.');
$users = $this->drupalGetJSON('user/autocomplete', array('query' => array('q' => drupal_substr($anonymous_name, 0, 4)))); $users = $this->drupalGetJSON('user/autocomplete', array('query' => array('q' => drupal_substr($anonymous_name, 0, 4))));
$this->assertFalse(isset($users[$anonymous_name]), 'The anonymous name not found in autocompletion results without enabling anonymous username.'); $this->assertTrue(empty($users), 'The anonymous name not found in autocompletion results without enabling anonymous username.');
} }
} }
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\user; namespace Drupal\user;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Database\Connection; use Drupal\Core\Database\Connection;
...@@ -62,12 +63,12 @@ public function getMatches($string, $include_anonymous = FALSE) { ...@@ -62,12 +63,12 @@ public function getMatches($string, $include_anonymous = FALSE) {
$anonymous_name = $this->configFactory->get('user.settings')->get('anonymous'); $anonymous_name = $this->configFactory->get('user.settings')->get('anonymous');
// Allow autocompletion for the anonymous user. // Allow autocompletion for the anonymous user.
if (stripos($anonymous_name, $string) !== FALSE) { if (stripos($anonymous_name, $string) !== FALSE) {
$matches[$anonymous_name] = check_plain($anonymous_name); $matches[] = array('value' => $anonymous_name, 'label' => String::checkPlain($anonymous_name));
} }
} }
$result = $this->connection->select('users')->fields('users', array('name'))->condition('name', db_like($string) . '%', 'LIKE')->range(0, 10)->execute(); $result = $this->connection->select('users')->fields('users', array('name'))->condition('name', db_like($string) . '%', 'LIKE')->range(0, 10)->execute();
foreach ($result as $account) { foreach ($result as $account) {
$matches[$account->name] = check_plain($account->name); $matches[] = array('value' => $account->name, 'label' => String::checkPlain($account->name));
} }
} }
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace Drupal\views\Tests; namespace Drupal\views\Tests;
use Drupal\views\Tests\ViewTestBase; use Drupal\views\Tests\ViewTestBase;
use Drupal\Component\Utility\MapArray;
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
/** /**
...@@ -81,7 +80,10 @@ public function testTaxonomyAutocomplete() { ...@@ -81,7 +80,10 @@ public function testTaxonomyAutocomplete() {
// Test a with whole name term. // Test a with whole name term.
$label = $this->term1->label(); $label = $this->term1->label();
$expected = MapArray::copyValuesToKeys((array) $label); $expected = array(array(
'value' => $label,
'label' => check_plain($label),
));
$this->assertIdentical($expected, $this->drupalGetJSON($base_autocomplete_path, array('query' => array('q' => $label)))); $this->assertIdentical($expected, $this->drupalGetJSON($base_autocomplete_path, array('query' => array('q' => $label))));
// Test a term by partial name. // Test a term by partial name.
$partial = substr($label, 0, 2); $partial = substr($label, 0, 2);
......
...@@ -1434,10 +1434,10 @@ input.form-submit:focus { ...@@ -1434,10 +1434,10 @@ input.form-submit:focus {
.js[dir="rtl"] input.form-autocomplete { .js[dir="rtl"] input.form-autocomplete {
background-position: 1% 4px; background-position: 1% 4px;
} }
.js input.throbbing { .js input.form-autocomplete.ui-autocomplete-loading {
background-position: 100% -16px; /* LTR */ background-position: 100% -16px; /* LTR */
} }
.js[dir="rtl"] input.throbbing { .js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
background-position: 1% -16px; background-position: 1% -16px;
} }
......
...@@ -730,7 +730,6 @@ label { ...@@ -730,7 +730,6 @@ label {
.form-item label.option input { .form-item label.option input {
vertical-align: middle; vertical-align: middle;
} }
.form-disabled input.form-autocomplete,
.form-disabled input.form-text, .form-disabled input.form-text,
.form-disabled input.form-tel, .form-disabled input.form-tel,
.form-disabled input.form-email, .form-disabled input.form-email,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment