Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.617
diff -u -d -F^\s*function -r1.617 common.inc
--- includes/common.inc 15 Feb 2007 11:40:17 -0000 1.617
+++ includes/common.inc 6 Mar 2007 14:45:01 -0000
@@ -1753,6 +1753,24 @@ function drupal_to_js($var) {
}
/**
+ * Outputs a properly structured autocomplete object and sets the right header.
+ *
+ * @param $matches
+ * An indexed array with the items found for the keyword. The value of the
+ * textfield will be replaced with the array's key when an item is selected.
+ * The value is shown to the user.
+ * @param $info
+ * (Optional) If set, the info text will be displayed together with the
+ * items, but will not be selectable.
+ */
+function drupal_autocomplete($matches, $info = '') {
+ // Set the correct content type for JSON output.
+ drupal_set_header('Content-Type: text/javascript; charset=utf-8');
+
+ echo drupal_to_js(array('matches' => $matches, 'info' => theme('autocomplete_info', $info)));
+}
+
+/**
* Wrapper around urlencode() which avoids Apache quirks.
*
* Should be used when placing arbitrary data in an URL. Note that Drupal paths
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.179
diff -u -d -F^\s*function -r1.179 form.inc
--- includes/form.inc 27 Feb 2007 12:45:13 -0000 1.179
+++ includes/form.inc 6 Mar 2007 14:45:04 -0000
@@ -876,6 +876,14 @@ function _form_set_value(&$form_values,
}
/**
+ * Handles autocomplete functionality for an element.
+ */
+function form_autocomplete($element) {
+ drupal_add_js('misc/autocomplete.js');
+ drupal_add_js(array('autocomplete' => array($element['#id'] => url($element['#autocomplete_path']))), 'setting');
+}
+
+/**
* Retrieve the default properties for the defined element type.
*/
function _element_info($type, $refresh = NULL) {
@@ -1411,13 +1419,11 @@ function theme_token($element) {
function theme_textfield($element) {
$size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
$class = array('form-text');
- $extra = '';
$output = '';
if ($element['#autocomplete_path']) {
- drupal_add_js('misc/autocomplete.js');
+ form_autocomplete($element);
$class[] = 'form-autocomplete';
- $extra = '';
}
_form_set_class($element, $class);
@@ -1431,7 +1437,7 @@ function theme_textfield($element) {
$output .= ' '. $element['#field_suffix'] .'';
}
- return theme('form_element', $element, $output). $extra;
+ return theme('form_element', $element, $output);
}
/**
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.343
diff -u -d -F^\s*function -r1.343 theme.inc
--- includes/theme.inc 2 Mar 2007 09:40:13 -0000 1.343
+++ includes/theme.inc 6 Mar 2007 14:45:06 -0000
@@ -1122,6 +1122,22 @@ function theme_progress_bar($percent, $m
return $output;
}
+function theme_autocomplete_item($primary, $secondary = NULL, $description = NULL) {
+ $output .= '
'. $primary .'
';
+ if (!empty($secondary)) {
+ $output .= ''. $secondary .'
';
+ }
+ if (!empty($description)) {
+ $output .= ''. $description .'';
+ }
+ return $output;
+}
+
+function theme_autocomplete_info($info) {
+ if (!empty($info)) {
+ return ''. $info .'
';
+ }
+}
/**
* @} End of "defgroup themeable".
*/
Index: misc/autocomplete.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/autocomplete.js,v
retrieving revision 1.17
diff -u -d -F^\s*function -r1.17 autocomplete.js
--- misc/autocomplete.js 9 Jan 2007 07:31:04 -0000 1.17
+++ misc/autocomplete.js 6 Mar 2007 14:45:07 -0000
@@ -1,51 +1,55 @@
// $Id: autocomplete.js,v 1.17 2007/01/09 07:31:04 drumm Exp $
-/**
- * Attaches the autocomplete behaviour to all required fields
- */
-Drupal.autocompleteAutoAttach = function () {
- var acdb = [];
- $('input.autocomplete').each(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')[0];
- $(input.form).submit(Drupal.autocompleteSubmit);
- new Drupal.jsAC(input, acdb[uri]);
- });
-}
+Drupal.autocomplete = { handlers: {} };
/**
* Prevents the form from submitting if the suggestions popup is open
* and closes the suggestions popup when doing so.
*/
-Drupal.autocompleteSubmit = function () {
+Drupal.autocomplete.submit = function () {
return $('#autocomplete').each(function () {
this.owner.hidePopup();
}).size() == 0;
}
/**
+ * Attaches the autocomplete behaviour to all required fields
+ */
+Drupal.autocomplete.attach = function () {
+ if (!Drupal.settings || !Drupal.settings.autocomplete) return;
+
+ var handlers = Drupal.autocomplete.handlers;
+ for (id in Drupal.settings.autocomplete) {
+ var url = Drupal.settings.autocomplete[id];
+ if (!handlers[url]) {
+ handlers[url] = new Drupal.autocomplete.handler(url);
+ }
+
+ new Drupal.autocomplete.field($('#' + id)[0], handlers[url]);
+ }
+}
+
+/**
* An AutoComplete object
*/
-Drupal.jsAC = function (input, db) {
+Drupal.autocomplete.field = function (input, db) {
var ac = this;
this.input = input;
this.db = db;
+ $(this.input.form).submit(Drupal.autocomplete.submit);
+
$(this.input)
+ .attr('autocomplete', 'OFF')
.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) {
+Drupal.autocomplete.field.prototype.onkeydown = function (input, e) {
if (!e) {
e = window.event;
}
@@ -64,7 +68,7 @@
/**
* Handler for the "keyup" event
*/
-Drupal.jsAC.prototype.onkeyup = function (input, e) {
+Drupal.autocomplete.field.prototype.onkeyup = function (input, e) {
if (!e) {
e = window.event;
}
@@ -101,14 +105,17 @@
/**
* Puts the currently highlighted suggestion into the autocomplete field
*/
-Drupal.jsAC.prototype.select = function (node) {
+Drupal.autocomplete.field.prototype.select = function (node) {
this.input.value = node.autocompleteValue;
+ if (this.input.selectionStart) {
+ this.input.selectionStart = this.input.value.length
+ }
}
/**
* Highlights the next suggestion
*/
-Drupal.jsAC.prototype.selectDown = function () {
+Drupal.autocomplete.field.prototype.selectDown = function () {
if (this.selected && this.selected.nextSibling) {
this.highlight(this.selected.nextSibling);
}
@@ -123,7 +130,7 @@
/**
* Highlights the previous suggestion
*/
-Drupal.jsAC.prototype.selectUp = function () {
+Drupal.autocomplete.field.prototype.selectUp = function () {
if (this.selected && this.selected.previousSibling) {
this.highlight(this.selected.previousSibling);
}
@@ -132,7 +139,7 @@
/**
* Highlights a suggestion
*/
-Drupal.jsAC.prototype.highlight = function (node) {
+Drupal.autocomplete.field.prototype.highlight = function (node) {
if (this.selected) {
$(this.selected).removeClass('selected');
}
@@ -143,7 +150,7 @@
/**
* Unhighlights a suggestion
*/
-Drupal.jsAC.prototype.unhighlight = function (node) {
+Drupal.autocomplete.field.prototype.unhighlight = function (node) {
$(node).removeClass('selected');
this.selected = false;
}
@@ -151,10 +158,10 @@
/**
* Hides the autocomplete suggestions
*/
-Drupal.jsAC.prototype.hidePopup = function (keycode) {
+Drupal.autocomplete.field.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.autocompleteValue;
+ this.select(this.selected);
}
// Hide popup
var popup = this.popup;
@@ -168,20 +175,18 @@
/**
* Positions the suggestions popup and starts a search
*/
-Drupal.jsAC.prototype.populatePopup = function () {
+Drupal.autocomplete.field.prototype.populatePopup = function () {
// Show popup
if (this.popup) {
$(this.popup).remove();
}
this.selected = false;
- this.popup = document.createElement('div');
- this.popup.id = 'autocomplete';
- this.popup.owner = this;
- $(this.popup).css({
+ this.popup = $('').css({
marginTop: this.input.offsetHeight +'px',
width: (this.input.offsetWidth - 4) +'px',
display: 'none'
});
+ this.popup[0].owner = this;
$(this.input).before(this.popup);
// Do search
@@ -192,39 +197,42 @@
/**
* Fills the suggestion popup with any matches received
*/
-Drupal.jsAC.prototype.found = function (matches) {
+Drupal.autocomplete.field.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 = document.createElement('ul');
- var ac = this;
- for (key in matches) {
- var li = document.createElement('li');
- $(li)
- .html(''+ matches[key] +'
')
- .mousedown(function () { ac.select(this); })
- .mouseover(function () { ac.highlight(this); })
- .mouseout(function () { ac.unhighlight(this); });
- li.autocompleteValue = key;
- $(ul).append(li);
+ var ul = $('');
+ var field = this;
+ for (key in matches.matches) {
+ var li = $('')
+ .html(matches.matches[key])
+ .mouseover(function () { field.highlight(this); })
+ .mouseout(function () { field.unhighlight(this); });
+ li[0].autocompleteValue = key;
+ ul.append(li);
}
// Show popup with matches, if any
if (this.popup) {
- if (ul.childNodes.length > 0) {
- $(this.popup).empty().append(ul).show();
+ this.popup.empty();
+ if ($(ul).children().size()) {
+ this.popup.append(ul).show();
}
- else {
- $(this.popup).css({visibility: 'hidden'});
+ if (matches.info) {
+ this.popup.append($('').html(matches.info)).show();
+ }
+
+ if (!$(this.popup).children().size()) {
+ this.popup.css({visibility: 'hidden'});
this.hidePopup();
}
}
}
-Drupal.jsAC.prototype.setStatus = function (status) {
+Drupal.autocomplete.field.prototype.setStatus = function (status) {
switch (status) {
case 'begin':
$(this.input).addClass('throbbing');
@@ -240,7 +248,7 @@
/**
* An AutoComplete DataBase object
*/
-Drupal.ACDB = function (uri) {
+Drupal.autocomplete.handler = function (uri) {
this.uri = uri;
this.delay = 300;
this.cache = {};
@@ -249,7 +257,7 @@
/**
* Performs a cached and delayed search
*/
-Drupal.ACDB.prototype.search = function (searchString) {
+Drupal.autocomplete.handler.prototype.search = function (searchString) {
var db = this;
this.searchString = searchString;
@@ -291,7 +299,7 @@
/**
* Cancels the current autocomplete request
*/
-Drupal.ACDB.prototype.cancel = function() {
+Drupal.autocomplete.handler.prototype.cancel = function() {
if (this.owner) this.owner.setStatus('cancel');
if (this.timer) clearTimeout(this.timer);
this.searchString = '';
@@ -299,5 +307,5 @@
// Global Killswitch
if (Drupal.jsEnabled) {
- $(document).ready(Drupal.autocompleteAutoAttach);
+ $(document).ready(Drupal.autocomplete.attach);
}
Index: modules/profile/profile.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/profile/profile.module,v
retrieving revision 1.194
diff -u -d -F^\s*function -r1.194 profile.module
--- modules/profile/profile.module 11 Feb 2007 09:30:51 -0000 1.194
+++ modules/profile/profile.module 6 Mar 2007 14:45:10 -0000
@@ -705,16 +705,14 @@ function profile_form_profile($edit, $us
* Callback to allow autocomplete of profile text fields.
*/
function profile_autocomplete($field, $string) {
+ $matches = array();
if (db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE fid = %d AND autocomplete = 1", $field))) {
- $matches = array();
$result = db_query_range("SELECT value FROM {profile_values} WHERE fid = %d AND LOWER(value) LIKE LOWER('%s%%') GROUP BY value ORDER BY value ASC", $field, $string, 0, 10);
while ($data = db_fetch_object($result)) {
- $matches[$data->value] = check_plain($data->value);
+ $matches[$data->value] = theme('autocomplete_item', check_plain($data->value));
}
-
- print drupal_to_js($matches);
}
- exit();
+ drupal_autocomplete($matches);
}
/**
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.23
diff -u -d -F^\s*function -r1.23 system.css
--- modules/system/system.css 2 Mar 2007 09:40:13 -0000 1.23
+++ modules/system/system.css 6 Mar 2007 14:45:10 -0000
@@ -273,6 +273,10 @@
border: 1px solid;
overflow: hidden;
z-index: 100;
+ background: #fff;
+ color: #000;
+ cursor: default;
+ line-height: normal;
}
#autocomplete ul {
margin: 0;
@@ -280,15 +284,32 @@
list-style: none;
}
#autocomplete li {
- background: #fff;
- color: #000;
- white-space: pre;
- cursor: default;
+ padding: 0.25em 0;
+ clear: both;
}
#autocomplete li.selected {
background: #0072b9;
color: #fff;
}
+#autocomplete li .primary {
+ font-weight: bold;
+ float: left;
+}
+#autocomplete li .secondary {
+ float: right;
+}
+#autocomplete li small {
+ font-size: 0.85em;
+ color: #555;
+ display: block;
+ clear: both;
+}
+#autocomplete li.selected * {
+ color:#fff;
+}
+#autocomplete div.info {
+ padding:0.25em 0.5em;
+}
/* Animated throbber */
html.js input.form-autocomplete {
background-image: url(../../misc/throbber.gif);
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.341
diff -u -d -F^\s*function -r1.341 taxonomy.module
--- modules/taxonomy/taxonomy.module 27 Feb 2007 12:48:33 -0000 1.341
+++ modules/taxonomy/taxonomy.module 6 Mar 2007 14:45:14 -0000
@@ -1475,24 +1475,35 @@ function taxonomy_autocomplete($vid, $st
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
preg_match_all($regexp, $string, $matches);
$array = $matches[1];
+ $matches = array();
+ $info = '';
// Fetch last tag
$last_string = trim(array_pop($array));
if ($last_string != '') {
- $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_string, 0, 10);
+ $result = pager_query(db_rewrite_sql("SELECT t.tid, t.name, t.description FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), 10, 'taxonomy_autocomplete', NULL, $vid, $last_string);
$prefix = count($array) ? implode(', ', $array) .', ' : '';
- $matches = array();
while ($tag = db_fetch_object($result)) {
- $n = $tag->name;
- // Commas and quotes in terms are special cases, so encode 'em.
- if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
- $n = '"'. str_replace('"', '""', $tag->name) .'"';
+ if (!in_array($tag->name, $array)) {
+ $id = $tag->name;
+ // Commas and quotes in terms are special cases, so encode them.
+ if (strpos($id, ',') !== FALSE || strpos($id, '"') !== FALSE) {
+ $n = '"'. str_replace('"', '""', $id) .'"';
+ }
+ $matches[$prefix . $id .', '] = theme('autocomplete_item', check_plain($tag->name), l(t('List'), 'taxonomy/term/'. $tag->tid), check_plain($tag->description));
}
- $matches[$prefix . $n] = check_plain($tag->name);
}
- print drupal_to_js($matches);
- exit();
}
+
+ global $pager_total_items;
+ if (count($matches) == 0) {
+ $info = t('There are no matches.');
+ }
+ elseif (isset($pager_total_items) && $pager_total_items['taxonomy_autocomplete'] > 10) {
+ $info = format_plural($pager_total_items['taxonomy_autocomplete'] - 10, 'There is one further match.', 'There are @count further matches.');
+ }
+
+ drupal_autocomplete($matches, $info);
}
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.756
diff -u -d -F^\s*function -r1.756 user.module
--- modules/user/user.module 15 Feb 2007 11:40:18 -0000 1.756
+++ modules/user/user.module 6 Mar 2007 14:45:20 -0000
@@ -684,6 +684,13 @@ function theme_user_list($users, $title
return theme('item_list', $items, $title);
}
+function theme_user_autocomplete_item($user) {
+ $output = ''. check_plain($user->name) .'
';
+ $output .= ''. l('('. t('View profile') .')', 'user/'. $user->uid) .'
';
+
+ return $output;
+}
+
function user_is_anonymous() {
return !$GLOBALS['user']->uid;
}
@@ -2541,14 +2548,24 @@ function _user_forms(&$edit, $account, $
*/
function user_autocomplete($string = '') {
$matches = array();
+ $info = '';
if ($string) {
- $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10);
+ $result = pager_query("SELECT uid FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", 10, 'user_autocomplete', NULL, $string);
while ($user = db_fetch_object($result)) {
- $matches[$user->name] = check_plain($user->name);
- }
+ $user = user_load($user->uid);
+ $matches[$user->name] = theme('user_autocomplete_item', $user);
+ }
}
- print drupal_to_js($matches);
- exit();
+
+ global $pager_total_items;
+ if (count($matches) == 0) {
+ $info = t('No matching users were found.');
+ }
+ elseif (isset($pager_total_items) && $pager_total_items['user_autocomplete'] > 10) {
+ $info = format_plural($pager_total_items['user_autocomplete'] - 10, 'The ten best matches are listed. One further user was found.', 'The ten best matches are listed. @count further users were found.');
+ }
+
+ drupal_autocomplete($matches, $info);
}
/**
Index: themes/garland/style.css
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/style.css,v
retrieving revision 1.17
diff -u -d -F^\s*function -r1.17 style.css
--- themes/garland/style.css 2 Mar 2007 09:40:14 -0000 1.17
+++ themes/garland/style.css 6 Mar 2007 14:45:22 -0000
@@ -804,8 +804,9 @@
*/
#autocomplete li {
cursor: default;
- padding: 2px;
+ padding: 0.25em;
margin: 0;
+ background: none;
}
/**