diff --git a/core/includes/form.inc b/core/includes/form.inc
index a12fb34..ee0ad3c 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -3698,14 +3698,7 @@ function theme_textfield($variables) {
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
-
- $attributes = array();
- $attributes['type'] = 'hidden';
- $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
- $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
- $attributes['disabled'] = 'disabled';
- $attributes['class'][] = 'autocomplete';
- $extra = '';
+ $element['#attributes']['data-autocomplete-path'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
}
$output = '';
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index f91d58e..a071bb8 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -5,318 +5,65 @@
*/
Drupal.behaviors.autocomplete = {
attach: function (context, settings) {
- var acdb = [];
- $('input.autocomplete', context).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))
- .attr('autocomplete', 'OFF')
- .attr('aria-autocomplete', 'list');
- $($input[0].form).submit(Drupal.autocompleteSubmit);
- $input.parent()
- .attr('role', 'application')
- .append($('')
- .attr('id', $input.attr('id') + '-autocomplete-aria-live')
- );
- new Drupal.jsAC($input, acdb[uri]);
- });
- }
-};
-
-/**
- * Prevents the form from submitting if the suggestions popup is open
- * and closes the suggestions popup when doing so.
- */
-Drupal.autocompleteSubmit = function () {
- return $('#autocomplete').each(function () {
- this.owner.hidePopup();
- }).size() == 0;
-};
-
-/**
- * An AutoComplete object.
- */
-Drupal.jsAC = function ($input, db) {
- var ac = this;
- this.input = $input[0];
- this.ariaLive = $('#' + $input.attr('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;
- }
- switch (e.keyCode) {
- case 40: // down arrow.
- this.selectDown();
- return false;
- case 38: // up arrow.
- this.selectUp();
- return false;
- default: // All other keys.
- return true;
- }
-};
-
-/**
- * Handler for the "keyup" event.
- */
-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)
- this.populatePopup();
- else
- this.hidePopup(e.keyCode);
- return true;
- }
-};
-
-/**
- * Puts the currently highlighted suggestion into the autocomplete field.
- */
-Drupal.jsAC.prototype.select = function (node) {
- this.input.value = $(node).data('autocompleteValue');
-};
-
-/**
- * Highlights the next suggestion.
- */
-Drupal.jsAC.prototype.selectDown = function () {
- if (this.selected && this.selected.nextSibling) {
- this.highlight(this.selected.nextSibling);
- }
- else if (this.popup) {
- var lis = $('li', this.popup);
- if (lis.size() > 0) {
- this.highlight(lis.get(0));
- }
- }
-};
-
-/**
- * Highlights the previous suggestion.
- */
-Drupal.jsAC.prototype.selectUp = function () {
- if (this.selected && this.selected.previousSibling) {
- this.highlight(this.selected.previousSibling);
- }
-};
-
-/**
- * Highlights a suggestion.
- */
-Drupal.jsAC.prototype.highlight = function (node) {
- if (this.selected) {
- $(this.selected).removeClass('selected');
- }
- $(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();
-};
-
-/**
- * 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');
- }
- // Hide popup.
- var popup = this.popup;
- if (popup) {
- this.popup = null;
- $(popup).fadeOut('fast', function () { $(popup).remove(); });
- }
- this.selected = false;
- $(this.ariaLive).empty();
-};
-
-/**
- * Positions the suggestions popup and starts a search.
- */
-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 = $('
')[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);
-};
-
-/**
- * Fills the suggestion popup with any matches received.
- */
-Drupal.jsAC.prototype.found = function (matches) {
- // If no value in the textfield, do not show the popup.
- if (!this.input.value.length) {
- return false;
- }
-
- // Prepare matches.
- var ul = $('');
- var ac = this;
- for (key in matches) {
- $('')
- .html($('').html(matches[key]))
- .mousedown(function () { ac.select(this); })
- .mouseover(function () { ac.highlight(this); })
- .mouseout(function () { ac.unhighlight(this); })
- .data('autocompleteValue', key)
- .appendTo(ul);
- }
-
- // Show popup with matches, if any.
- if (this.popup) {
- if (ul.children().size()) {
- $(this.popup).empty().append(ul).show();
- $(this.ariaLive).html(Drupal.t('Autocomplete popup'));
- }
- else {
- $(this.popup).css({ visibility: 'hidden' });
- this.hidePopup();
- }
- }
-};
-
-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.
- */
-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]);
- }
-
- // Initiate delayed search.
- if (this.timer) {
- clearTimeout(this.timer);
- }
- this.timer = setTimeout(function () {
- db.owner.setStatus('begin');
-
- // Ajax GET request for autocompletion. We use Drupal.encodePath instead of
- // encodeURIComponent to allow autocomplete search terms to contain slashes.
- $.ajax({
- type: 'GET',
- url: db.uri + '/' + Drupal.encodePath(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);
+ // Act on textfields with the "form-autocomplete" class.
+ $('input.form-autocomplete', context).once('autocomplete', function() {
+ // Use jQuery UI Autocomplete on the textfield.
+ $(this).autocomplete({
+ // The autcomplete source is provided by the data-autocomplete-path
+ // attribute within the element itself.
+ source: function(request, response) {
+ // Get the desired term and construct the autocomplete URL for it.
+ var term = Drupal.autocompleteextractLast(request.term);
+ var url = $(this.element).data('autocompletePath') + '/' + encodeURIComponent(term);
+ // Make the AJAX request.
+ $.getJSON(url, function(data) {
+ // Drupal returns an object array, but we need a string array.
+ var terms = new Array();
+ $.each(data, function(key, value) {
+ terms.push(value);
+ });
+ // Send the new string array of terms to the jQuery UI list.
+ response(terms);
+ });
+ },
+ search: function() {
+ // Only search if there has been more then two characters in there.
+ var term = Drupal.autocompleteextractLast(this.value);
+ if (term.length < 1) {
+ return false;
}
- db.owner.setStatus('found');
+ },
+ focus: function() {
+ // Prevent value inserted on focus.
+ return false;
+ },
+ // It will select the search term based on the last item in the list.
+ select: function(event, ui) {
+ var terms = Drupal.autocompletesplit(this.value);
+ // Remove the current input.
+ terms.pop();
+ // Add the selected item.
+ terms.push(ui.item.value);
+ this.value = terms.join(", ");
+ return false;
}
- },
- error: function (xmlhttp) {
- alert(Drupal.ajaxError(xmlhttp, db.uri));
+ });
+ });
+
+ // Pop up the autocomplete if tab was entered.
+ $(this).bind('keydown', function(event) {
+ if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
+ event.preventDefault();
}
});
- }, 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.autocompletesplit = function(val) {
+ return val.split(/,\s*/);
+};
+Drupal.autocompleteextractLast = function(term) {
+ return Drupal.autocompletesplit(term).pop();
};
})(jQuery);
diff --git a/core/modules/system/system.base-rtl.css b/core/modules/system/system.base-rtl.css
index 9099c9d..984f329 100644
--- a/core/modules/system/system.base-rtl.css
+++ b/core/modules/system/system.base-rtl.css
@@ -11,7 +11,7 @@
html.js input.form-autocomplete {
background-position: 0% 2px;
}
-html.js input.throbbing {
+html.js input.form-autocomplete.ui-autocomplete-loading {
background-position: 0% -18px;
}
diff --git a/core/modules/system/system.base.css b/core/modules/system/system.base.css
index 99fcfcb..df6bf3d 100644
--- a/core/modules/system/system.base.css
+++ b/core/modules/system/system.base.css
@@ -4,37 +4,13 @@
* Generic theme-independent base styles.
*/
-/**
- * Autocomplete.
- *
- * @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 */
html.js input.form-autocomplete {
background-image: url(../../misc/throbber.gif);
background-position: 100% 2px; /* LTR */
background-repeat: no-repeat;
}
-html.js input.throbbing {
+html.js input.form-autocomplete.ui-autocomplete-loading {
background-position: 100% -18px; /* LTR */
}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index f16b73f..6f85207 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1165,6 +1165,9 @@ function system_library_info() {
'js' => array(
'core/misc/autocomplete.js' => array('group' => JS_DEFAULT),
),
+ 'dependencies' => array(
+ array('system', 'ui.autocomplete'),
+ ),
);
// jQuery.
diff --git a/core/themes/bartik/css/style-rtl.css b/core/themes/bartik/css/style-rtl.css
index d25006f..c10e54a 100644
--- a/core/themes/bartik/css/style-rtl.css
+++ b/core/themes/bartik/css/style-rtl.css
@@ -222,7 +222,7 @@ ul.action-links li a {
html.js input.form-autocomplete {
background-position: 1% 4px;
}
-html.js input.throbbing {
+html.js input.form-autocomplete.ui-autocomplete-loading {
background-position: 1% -16px;
}
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index a66b95b..53cc527 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -1330,7 +1330,7 @@ input.form-button-disabled:active,
html.js input.form-autocomplete {
background-position: 100% 4px; /* LTR */
}
-html.js input.throbbing {
+html.js input.form-autocomplete.ui-autocomplete-loading {
background-position: 100% -16px; /* LTR */
}
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index e5869d3..1dbad23 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -607,7 +607,6 @@ div.teaser-checkbox .form-item,
.form-item label.option input {
vertical-align: middle;
}
-.form-disabled input.form-autocomplete,
.form-disabled input.form-text,
.form-disabled input.form-file,
.form-disabled textarea.form-textarea,
@@ -694,7 +693,6 @@ input.form-button-disabled:active {
text-shadow: none;
color: #999;
}
-input.form-autocomplete,
input.form-text,
input.form-file,
textarea.form-textarea,
@@ -712,12 +710,6 @@ select.form-select:focus {
color: #000;
border-color: #ace;
}
-html.js input.form-autocomplete {
- background-position: 100% 4px;
-}
-html.js input.throbbing {
- background-position: 100% -16px;
-}
ul.action-links {
margin: 1em 0;
padding: 0 20px 0 20px; /* LTR */