summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2013-11-24 20:26:30 (GMT)
committerwebchick2013-11-24 20:30:09 (GMT)
commit02a10b31610fa1ef576057947e5c275f517fe908 (patch)
tree462d1361a99559a4e4299cacf5a104276e9de7cc
parentfcea2f9e3f083fc7d8c7fef1b13345586c4d0a15 (diff)
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.
-rw-r--r--core/includes/form.inc16
-rw-r--r--core/misc/autocomplete.js442
-rw-r--r--core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php2
-rw-r--r--core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php20
-rw-r--r--core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php5
-rw-r--r--core/modules/system/css/system.module.css23
-rw-r--r--core/modules/system/css/system.theme.css3
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Form/ElementTest.php12
-rw-r--r--core/modules/system/system.module2
-rw-r--r--core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TermAutocompleteController.php2
-rw-r--r--core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php18
-rw-r--r--core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php4
-rw-r--r--core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php4
-rw-r--r--core/modules/user/lib/Drupal/user/UserAutocomplete.php5
-rw-r--r--core/modules/views/lib/Drupal/views/Tests/ViewsTaxonomyAutocompleteTest.php6
-rw-r--r--core/themes/bartik/css/style.css4
-rw-r--r--core/themes/seven/style.css1
17 files changed, 207 insertions, 362 deletions
diff --git a/core/includes/form.inc b/core/includes/form.inc
index c82124a..d12a9b9 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2158,20 +2158,8 @@ function form_process_autocomplete($element, &$form_state) {
if ($access) {
$element['#attributes']['class'][] = 'form-autocomplete';
$element['#attached']['library'][] = array('system', 'drupal.autocomplete');
- // Provide a hidden element for the JavaScript behavior to bind to. Since
- // this element is for client-side functionality only, do not process input.
- // @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',
- ),
- );
+ // Provide a data attribute for the JavaScript behavior to bind to.
+ $element['#attributes']['data-autocomplete-path'] = $path;
}
return $element;
}
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index 0378b95..ad9bb1c 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -1,338 +1,200 @@
-(function ($) {
+(function ($, Drupal) {
"use strict";
-/**
- * Attaches the autocomplete behavior to all required fields.
- */
-Drupal.behaviors.autocomplete = {
- attach: function (context, settings) {
- var acdb = [];
- $(context).find('input.autocomplete').once('autocomplete', function () {
- var uri = this.value;
- if (!acdb[uri]) {
- acdb[uri] = new Drupal.ACDB(uri);
- }
- var $input = $('#' + this.id.substr(0, this.id.length - 13))
- .prop('autocomplete', 'OFF')
- .attr('aria-autocomplete', 'list');
- $($input[0].form).submit(Drupal.autocompleteSubmit);
- $input.parent()
- .attr('role', 'application')
- .append($('<span class="visually-hidden" aria-live="assertive"></span>')
- .attr('id', $input[0].id + '-autocomplete-aria-live')
- );
- new Drupal.jsAC($input, acdb[uri]);
- });
- }
-};
+var autocomplete;
/**
- * Prevents the form from submitting if the suggestions popup is open
- * and closes the suggestions popup when doing so.
+ * Helper splitting terms from the autocomplete value.
+ *
+ * @param {String} value
+ *
+ * @return {Array}
*/
-Drupal.autocompleteSubmit = function () {
- var $autocomplete = $('#autocomplete');
- if ($autocomplete.length !== 0) {
- $autocomplete[0].owner.hidePopup();
- }
- return $autocomplete.length === 0;
-};
+function autocompleteSplitValues (value) {
+ // We will match the value against comma-seperated terms.
+ var result = [];
+ var quote = false;
+ var current = '';
+ var valueLength = value.length;
+ var i, character;
-/**
- * An AutoComplete object.
- */
-Drupal.jsAC = function ($input, db) {
- var ac = this;
- this.input = $input[0];
- this.ariaLive = $('#' + this.input.id + '-autocomplete-aria-live');
- this.db = db;
-
- $input
- .keydown(function (event) { return ac.onkeydown(this, event); })
- .keyup(function (event) { ac.onkeyup(this, event); })
- .blur(function () { ac.hidePopup(); ac.db.cancel(); });
-};
-
-/**
- * Handler for the "keydown" event.
- */
-Drupal.jsAC.prototype.onkeydown = function (input, e) {
- if (!e) {
- e = window.event;
+ for (i = 0; i < valueLength; i++) {
+ character = value.charAt(i);
+ if (character === '"') {
+ current += character;
+ quote = !quote;
+ }
+ else if (character === ',' && !quote) {
+ result.push(current.trim());
+ current = '';
+ }
+ else {
+ current += character;
+ }
}
- switch (e.keyCode) {
- case 40: // down arrow.
- e.preventDefault();
- this.selectDown();
- break;
- case 38: // up arrow.
- e.preventDefault();
- this.selectUp();
- break;
- default: // All other keys.
- return true;
+ if (value.length > 0) {
+ result.push($.trim(current));
}
-};
+
+ return result;
+}
/**
- * Handler for the "keyup" event.
+ * Returns the last value of an multi-value textfield.
+ *
+ * @param {String} terms
+ *
+ * @return {String}
*/
-Drupal.jsAC.prototype.onkeyup = function (input, e) {
- if (!e) {
- e = window.event;
- }
- switch (e.keyCode) {
- case 16: // Shift.
- case 17: // Ctrl.
- case 18: // Alt.
- case 20: // Caps lock.
- case 33: // Page up.
- case 34: // Page down.
- case 35: // End.
- case 36: // Home.
- case 37: // Left arrow.
- case 38: // Up arrow.
- case 39: // Right arrow.
- case 40: // Down arrow.
- return true;
-
- case 9: // Tab.
- case 13: // Enter.
- case 27: // Esc.
- this.hidePopup(e.keyCode);
- return true;
-
- default: // All other keys.
- if (input.value.length > 0 && !input.readOnly) {
- this.populatePopup();
- }
- else {
- this.hidePopup(e.keyCode);
- }
- return true;
- }
-};
+function extractLastTerm (terms) {
+ return autocomplete.splitValues(terms).pop();
+}
/**
- * Puts the currently highlighted suggestion into the autocomplete field.
+ * The search handler is called before a search is performed.
+ *
+ * @param {Object} event
+ *
+ * @return {Boolean}
*/
-Drupal.jsAC.prototype.select = function (node) {
- this.input.value = $(node).data('autocompleteValue');
-};
+function searchHandler (event) {
+ // Only search when the term is two characters or larger.
+ var term = autocomplete.extractLastTerm(event.target.value);
+ return term.length >= autocomplete.minLength;
+}
/**
- * Highlights the next suggestion.
+ * jQuery UI autocomplete source callback.
+ *
+ * @param {Object} request
+ * @param {Function} response
*/
-Drupal.jsAC.prototype.selectDown = function () {
- if (this.selected && this.selected.nextSibling) {
- this.highlight(this.selected.nextSibling);
+function sourceData (request, response) {
+ var elementId = this.element.attr('id');
+
+ if (!(elementId in autocomplete.cache)) {
+ autocomplete.cache[elementId] = {};
}
- else if (this.popup) {
- var lis = $(this.popup).find('li');
- if (lis.length > 0) {
- this.highlight(lis.get(0));
+
+ /**
+ * Filter through the suggestions removing all terms already tagged and
+ * display the available terms to the user.
+ *
+ * @param {Object} suggestions
+ */
+ function showSuggestions (suggestions) {
+ var tagged = autocomplete.splitValues(request.term);
+ for (var i = 0, il = tagged.length; i < il; i++) {
+ var index = suggestions.indexOf(tagged[i]);
+ if (index >= 0) {
+ suggestions.splice(index, 1);
+ }
}
+ response(suggestions);
}
-};
-/**
- * Highlights the previous suggestion.
- */
-Drupal.jsAC.prototype.selectUp = function () {
- if (this.selected && this.selected.previousSibling) {
- this.highlight(this.selected.previousSibling);
- }
-};
+ /**
+ * Transforms the data object into an array and update autocomplete results.
+ *
+ * @param {Object} data
+ */
+ function sourceCallbackHandler (data) {
+ autocomplete.cache[elementId][term] = data;
-/**
- * Highlights a suggestion.
- */
-Drupal.jsAC.prototype.highlight = function (node) {
- // Unhighlights a suggestion for "keyup" and "keydown" events.
- if (this.selected !== false) {
- $(this.selected).removeClass('selected');
+ // Send the new string array of terms to the jQuery UI list.
+ showSuggestions(data);
}
- $(node).addClass('selected');
- this.selected = node;
- $(this.ariaLive).html($(this.selected).html());
-};
-/**
- * Unhighlights a suggestion.
- */
-Drupal.jsAC.prototype.unhighlight = function (node) {
- $(node).removeClass('selected');
- this.selected = false;
- $(this.ariaLive).empty();
-};
+ // Get the desired term and construct the autocomplete URL for it.
+ var term = autocomplete.extractLastTerm(request.term);
-/**
- * Hides the autocomplete suggestions.
- */
-Drupal.jsAC.prototype.hidePopup = function (keycode) {
- // Select item if the right key or mousebutton was pressed.
- if (this.selected && ((keycode && keycode !== 46 && keycode !== 8 && keycode !== 27) || !keycode)) {
- this.input.value = $(this.selected).data('autocompleteValue');
+ // Check if the term is already cached.
+ if (autocomplete.cache[elementId].hasOwnProperty(term)) {
+ showSuggestions(autocomplete.cache[elementId][term]);
}
- // Hide popup.
- var popup = this.popup;
- if (popup) {
- this.popup = null;
- $(popup).fadeOut('fast', function () { $(popup).remove(); });
+ else {
+ var options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax);
+ /*jshint validthis:true */
+ $.ajax(this.element.attr('data-autocomplete-path'), options);
}
- this.selected = false;
- $(this.ariaLive).empty();
-};
+}
/**
- * Positions the suggestions popup and starts a search.
+ * Handles an autocompletefocus event.
+ *
+ * @return {Boolean}
*/
-Drupal.jsAC.prototype.populatePopup = function () {
- var $input = $(this.input);
- var position = $input.position();
- // Show popup.
- if (this.popup) {
- $(this.popup).remove();
- }
- this.selected = false;
- this.popup = $('<div id="autocomplete"></div>')[0];
- this.popup.owner = this;
- $(this.popup).css({
- top: parseInt(position.top + this.input.offsetHeight, 10) + 'px',
- left: parseInt(position.left, 10) + 'px',
- width: $input.innerWidth() + 'px',
- display: 'none'
- });
- $input.before(this.popup);
-
- // Do search.
- this.db.owner = this;
- this.db.search(this.input.value);
-};
+function focusHandler () {
+ return false;
+}
/**
- * Fills the suggestion popup with any matches received.
+ * Handles an autocompleteselect event.
+ *
+ * @param {Object} event
+ * @param {Object} ui
+ *
+ * @return {Boolean}
*/
-Drupal.jsAC.prototype.found = function (matches) {
- // If no value in the textfield, do not show the popup.
- if (!this.input.value.length) {
- return false;
+function selectHandler (event, ui) {
+ var terms = autocomplete.splitValues(event.target.value);
+ // Remove the current input.
+ terms.pop();
+ // Add the selected item.
+ if (ui.item.value.search(",") > 0) {
+ terms.push('"' + ui.item.value + '"');
}
-
- // Prepare matches.
- var ac = this;
- var ul = $('<ul></ul>')
- .on('mousedown', 'li', function (e) { ac.select(this); })
- .on('mouseover', 'li', function (e) { ac.highlight(this); })
- .on('mouseout', 'li', function (e) { ac.unhighlight(this); });
- for (var key in matches) {
- if (matches.hasOwnProperty(key)) {
- $('<li></li>')
- .html($('<div></div>').html(matches[key]))
- .data('autocompleteValue', key)
- .appendTo(ul);
- }
+ else {
+ terms.push(ui.item.value);
}
+ event.target.value = terms.join(', ');
+ // Return false to tell jQuery UI that we've filled in the value already.
+ return false;
+}
- // Show popup with matches, if any.
- if (this.popup) {
- if (ul.children().length) {
- $(this.popup).empty().append(ul).show();
- $(this.ariaLive).html(Drupal.t('Autocomplete popup'));
+/**
+ * Attaches the autocomplete behavior to all required fields.
+ */
+Drupal.behaviors.autocomplete = {
+ attach: function (context) {
+ // Act on textfields with the "form-autocomplete" class.
+ var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
+ if ($autocomplete.length) {
+ // Use jQuery UI Autocomplete on the textfield.
+ $autocomplete.autocomplete(autocomplete.options);
}
- else {
- $(this.popup).css({ visibility: 'hidden' });
- this.hidePopup();
+ },
+ detach: function (context, settings, trigger) {
+ if (trigger === 'unload') {
+ $(context).find('input.form-autocomplete')
+ .removeOnce('autocomplete')
+ .autocomplete('destroy');
}
}
};
-Drupal.jsAC.prototype.setStatus = function (status) {
- switch (status) {
- case 'begin':
- $(this.input).addClass('throbbing');
- $(this.ariaLive).html(Drupal.t('Searching for matches...'));
- break;
- case 'cancel':
- case 'error':
- case 'found':
- $(this.input).removeClass('throbbing');
- break;
- }
-};
-
-/**
- * An AutoComplete DataBase object.
- */
-Drupal.ACDB = function (uri) {
- this.uri = uri;
- this.delay = 300;
- this.cache = {};
-};
-
/**
- * Performs a cached and delayed search.
+ * Autocomplete object implementation.
*/
-Drupal.ACDB.prototype.search = function (searchString) {
- var db = this;
- this.searchString = searchString;
-
- // See if this string needs to be searched for anyway.
- searchString = searchString.replace(/^\s+|\s+$/, '');
- if (searchString.length <= 0 ||
- searchString.charAt(searchString.length - 1) === ',') {
- return;
- }
-
- // See if this key has been searched for before.
- if (this.cache[searchString]) {
- return this.owner.found(this.cache[searchString]);
+autocomplete = {
+ cache: {},
+ // Exposes methods to allow overriding by contrib.
+ minLength: 1,
+ splitValues: autocompleteSplitValues,
+ extractLastTerm: extractLastTerm,
+ // jQuery UI autocomplete options.
+ options: {
+ source: sourceData,
+ focus: focusHandler,
+ search: searchHandler,
+ select: selectHandler
+ },
+ ajax: {
+ dataType: 'json'
}
-
- // Initiate delayed search.
- if (this.timer) {
- clearTimeout(this.timer);
- }
- this.timer = setTimeout(function () {
- db.owner.setStatus('begin');
-
- // Ajax GET request for autocompletion.
- $.ajax({
- type: 'GET',
- url: db.uri,
- data: {
- q: searchString
- },
- dataType: 'json',
- success: function (matches) {
- if (typeof matches.status === 'undefined' || matches.status !== 0) {
- db.cache[searchString] = matches;
- // Verify if these are still the matches the user wants to see.
- if (db.searchString === searchString) {
- db.owner.found(matches);
- }
- db.owner.setStatus('found');
- }
- },
- error: function (xmlhttp) {
- throw new Drupal.AjaxError(xmlhttp, db.uri);
- }
- });
- }, this.delay);
};
-/**
- * Cancels the current autocomplete request.
- */
-Drupal.ACDB.prototype.cancel = function () {
- if (this.owner) {
- this.owner.setStatus('cancel');
- }
- if (this.timer) {
- clearTimeout(this.timer);
- }
- this.searchString = '';
-};
+Drupal.autocomplete = autocomplete;
-})(jQuery);
+})(jQuery, Drupal);
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php
index 56ce263..96909a9 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php
@@ -100,7 +100,7 @@ class EntityReferenceAutocomplete {
if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
$key = '"' . str_replace('"', '""', $key) . '"';
}
- $matches[$prefix . $key] = $label;
+ $matches[] = array('value' => $prefix . $key, 'label' => $label);
}
}
}
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php
index 45844ce..9895368 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php
@@ -79,21 +79,24 @@ class EntityReferenceAutocompleteTest extends EntityUnitTestBase {
// We should get both entities in a JSON encoded string.
$input = '10/';
$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[$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_1->name->value), 'Autocomplete returned the first 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.
// We should only get the first entity in a JSON encoded string.
$input = '10/16';
$data = $this->getAutocompleteResult('single', $input);
- $target = array($entity_1->name->value . ' (1)' => check_plain($entity_1->name->value));
- $this->assertIdentical($data, $target, 'Autocomplete returns only the expected matching entity.');
+ $target = array(
+ '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
// the first entity is already typed in the autocomplete (tags) widget.
$input = $entity_1->name->value . ' (1), 10/17';
$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.
$input = '"label with, and / t';
@@ -103,8 +106,11 @@ class EntityReferenceAutocompleteTest extends EntityUnitTestBase {
if (strpos($entity_3->name->value, ',') !== FALSE || strpos($entity_3->name->value, '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $entity_3->name->value) . ' (3)"';
}
- $target = array($n => check_plain($entity_3->name->value));
- $this->assertIdentical($data, $target, 'Autocomplete returns an entity label containing a comma and a slash.');
+ $target = array(
+ '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.');
}
/**
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php
index 090a27e..2e69cde 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php
@@ -131,7 +131,7 @@ class NodeCreationTest extends NodeTestBase {
$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.');
$admin_user = $this->drupalCreateUser(array('administer nodes', 'create page content', 'access user profiles'));
@@ -139,8 +139,7 @@ class NodeCreationTest extends NodeTestBase {
$this->drupalGet('node/add/page');
- $result = $this->xpath('//input[@id = "edit-name-autocomplete"]');
- $this->assertEqual((string) $result[0]['value'], url('user/autocomplete'));
+ $result = $this->xpath('//input[@id="edit-name" and contains(@data-autocomplete-path, "user/autocomplete")]');
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
}
diff --git a/core/modules/system/css/system.module.css b/core/modules/system/css/system.module.css
index eaa3b85..ef4cfae 100644
--- a/core/modules/system/css/system.module.css
+++ b/core/modules/system/css/system.module.css
@@ -8,25 +8,6 @@
*
* @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 */
.js input.form-autocomplete {
@@ -37,10 +18,10 @@
.js[dir="rtl"] input.form-autocomplete {
background-position: 0% 2px;
}
-.js input.throbbing {
+.js input.form-autocomplete.ui-autocomplete-loading {
background-position: 100% -18px; /* LTR */
}
-.js[dir="rtl"] input.throbbing {
+.js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
background-position: 0% -18px;
}
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index bb9a347..e5af971 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -192,9 +192,10 @@ label button.link {
* @see autocomplete.js
*/
/* 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;
color: #fff;
+ margin: 0;
}
/**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/ElementTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/ElementTest.php
index 18c8770..32120b2 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Form/ElementTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/ElementTest.php
@@ -133,20 +133,18 @@ class ElementTest extends WebTestBase {
public function testFormAutocomplete() {
$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');
- $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'));
$this->drupalLogin($user);
$this->drupalGet('form-test/autocomplete');
- $result = $this->xpath('//input[@id = "edit-autocomplete-1-autocomplete"]');
- $this->assertEqual((string) $result[0]['value'], url('form-test/autocomplete-1'));
+ $result = $this->xpath('//input[@id="edit-autocomplete-1" and contains(@data-autocomplete-path, "form-test/autocomplete-1")]');
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
- $result = $this->xpath('//input[@id = "edit-autocomplete-2-autocomplete"]');
- $this->assertEqual((string) $result[0]['value'], url('form-test/autocomplete-2/value'));
+ $result = $this->xpath('//input[@id="edit-autocomplete-2" and contains(@data-autocomplete-path, "form-test/autocomplete-2/value")]');
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 11bef84..78905da 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1096,7 +1096,9 @@ function system_library_info() {
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
+ array('system', 'drupalSettings'),
array('system', 'drupal.ajax'),
+ array('system', 'jquery.ui.autocomplete'),
),
);
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TermAutocompleteController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TermAutocompleteController.php
index 616091e..1c12516 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TermAutocompleteController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Controller/TermAutocompleteController.php
@@ -200,7 +200,7 @@ class TermAutocompleteController implements ContainerInjectionInterface {
if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
$name = '"' . str_replace('"', '""', $name) . '"';
}
- $matches[$prefix . $name] = String::checkPlain($term->label());
+ $matches[] = array('value' => $prefix . $name, 'label' => String::checkPlain($term->label()));
}
return $matches;
}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
index a468bed..dcae350 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
@@ -220,13 +220,13 @@ class TermTest extends TaxonomyTestBase {
// The term will be quoted, and the " will be encoded in unicode (\u0022).
$input = substr($term_objects['term3']->label(), 0, 3);
$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
// quoting.
$input = substr($term_objects['term4']->label(), 0, 3);
$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.
$field_name = $this->randomName();
@@ -261,15 +261,18 @@ class TermTest extends TaxonomyTestBase {
// The result order is not guaranteed, so check each term separately.
$result = $this->drupalGet($path, array('query' => array('q' => $input)));
$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[$second_term->label()], check_plain($second_term->label()), 'Autocomplete returned the second matching term.');
+ $this->assertEqual($data[0]['label'], check_plain($first_term->label()), 'Autocomplete returned the first 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.
// We should only get the first term in a json encoded string.
$input = '10/16';
$path = 'taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id();
$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.');
// Try to autocomplete a term name with both a comma and a slash.
@@ -281,7 +284,10 @@ class TermTest extends TaxonomyTestBase {
if (strpos($third_term->label(), ',') !== FALSE || strpos($third_term->label(), '"') !== FALSE) {
$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.');
}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php
index b95759d..6b59ca6 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyIndexTidUiTest.php
@@ -101,8 +101,8 @@ class TaxonomyIndexTidUiTest extends UITestBase {
$display['display_options']['filters']['tid']['type'] = 'textfield';
$view->save();
$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"]');
- $this->assertEqual((string) $result[0]['value'], url('taxonomy/autocomplete_vid/tags'));
+ $result = $this->xpath('//input[@id="edit-options-value"]/@data-autocomplete-path');
+ $this->assertEqual((string) $result[0], url('taxonomy/autocomplete_vid/tags'));
}
}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php
index e6fad9a..de8062e 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php
@@ -53,8 +53,8 @@ class UserAutocompleteTest extends WebTestBase {
// Test that anonymous username is in the result when requested and escaped
// with check_plain().
$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))));
- $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.');
}
}
diff --git a/core/modules/user/lib/Drupal/user/UserAutocomplete.php b/core/modules/user/lib/Drupal/user/UserAutocomplete.php
index 4d36923..a7a20b2 100644
--- a/core/modules/user/lib/Drupal/user/UserAutocomplete.php
+++ b/core/modules/user/lib/Drupal/user/UserAutocomplete.php
@@ -7,6 +7,7 @@
namespace Drupal\user;
+use Drupal\Component\Utility\String;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Database\Connection;
@@ -62,12 +63,12 @@ class UserAutocomplete {
$anonymous_name = $this->configFactory->get('user.settings')->get('anonymous');
// Allow autocompletion for the anonymous user.
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();
foreach ($result as $account) {
- $matches[$account->name] = check_plain($account->name);
+ $matches[] = array('value' => $account->name, 'label' => String::checkPlain($account->name));
}
}
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewsTaxonomyAutocompleteTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewsTaxonomyAutocompleteTest.php
index ef5f98d..df73f40 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewsTaxonomyAutocompleteTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewsTaxonomyAutocompleteTest.php
@@ -8,7 +8,6 @@
namespace Drupal\views\Tests;
use Drupal\views\Tests\ViewTestBase;
-use Drupal\Component\Utility\MapArray;
use Drupal\Core\Language\Language;
/**
@@ -81,7 +80,10 @@ class ViewsTaxonomyAutocompleteTest extends ViewTestBase {
// Test a with whole name term.
$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))));
// Test a term by partial name.
$partial = substr($label, 0, 2);
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index 6314e93..0768ddf 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -1434,10 +1434,10 @@ input.form-submit:focus {
.js[dir="rtl"] input.form-autocomplete {
background-position: 1% 4px;
}
-.js input.throbbing {
+.js input.form-autocomplete.ui-autocomplete-loading {
background-position: 100% -16px; /* LTR */
}
-.js[dir="rtl"] input.throbbing {
+.js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
background-position: 1% -16px;
}
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index e1f8f5c..5700ea0 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -730,7 +730,6 @@ label {
.form-item label.option input {
vertical-align: middle;
}
-.form-disabled input.form-autocomplete,
.form-disabled input.form-text,
.form-disabled input.form-tel,
.form-disabled input.form-email,