? balloon ? select.html ? misc/theme.js ? modules/user/access_control.js ? sites/all/modules ? sites/all/modules_repository Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.194 diff -u -d -F^\s*function -r1.194 form.inc --- includes/form.inc 7 May 2007 10:15:57 -0000 1.194 +++ includes/form.inc 12 May 2007 12:09:03 -0000 @@ -1421,13 +1421,12 @@ 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'); + drupal_add_js(array('autocomplete' => array($element['#id'] => url($element['#autocomplete_path']))), 'setting'); $class[] = 'form-autocomplete'; - $extra = ''; } _form_set_class($element, $class); @@ -1441,7 +1440,7 @@ function theme_textfield($element) { $output .= ' '. $element['#field_suffix'] .''; } - return theme('form_element', $element, $output) . $extra; + return theme('form_element', $element, $output); } /** 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 12 May 2007 12:09:03 -0000 @@ -1,54 +1,63 @@ // $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(); }); + .blur(function () { + var selected = ac.selected; + ac.hidePopup(); + ac.db.cancel(); + if (selected) { + ac.select(); + } + }); }; /** * Handler for the "keydown" event */ -Drupal.jsAC.prototype.onkeydown = function (input, e) { - if (!e) { - e = window.event; - } +Drupal.autocomplete.field.prototype.onkeydown = function (input, e) { switch (e.keyCode) { case 40: // down arrow this.selectDown(); @@ -64,10 +73,7 @@ /** * Handler for the "keyup" event */ -Drupal.jsAC.prototype.onkeyup = function (input, e) { - if (!e) { - e = window.event; - } +Drupal.autocomplete.field.prototype.onkeyup = function (input, e) { switch (e.keyCode) { case 16: // shift case 17: // ctrl @@ -101,14 +107,22 @@ /** * Puts the currently highlighted suggestion into the autocomplete field */ -Drupal.jsAC.prototype.select = function (node) { - this.input.value = node.autocompleteValue; +Drupal.autocomplete.field.prototype.select = function (node) { + if (node) { + this.input.value = node.autocompleteValue; + } + + if (this.input.selectionStart) { + this.input.select(); + this.input.focus(); + 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 +137,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 +146,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 +157,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 +165,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 +182,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 +204,43 @@ /** * 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); }) + .click(function() { field.select(this); return false; }); + 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'); @@ -238,9 +254,9 @@ } /** - * An AutoComplete DataBase object + * The default autocomplete handler with AJAX callback */ -Drupal.ACDB = function (uri) { +Drupal.autocomplete.handler = function (uri) { this.uri = uri; this.delay = 300; this.cache = {}; @@ -249,7 +265,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 +307,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 +315,5 @@ // Global Killswitch if (Drupal.jsEnabled) { - $(document).ready(Drupal.autocompleteAutoAttach); + $(Drupal.autocomplete.attach); } Index: modules/profile/profile.module =================================================================== RCS file: /cvs/drupal/drupal/modules/profile/profile.module,v retrieving revision 1.200 diff -u -d -F^\s*function -r1.200 profile.module --- modules/profile/profile.module 30 Apr 2007 17:03:27 -0000 1.200 +++ modules/profile/profile.module 12 May 2007 12:09:03 -0000 @@ -730,16 +730,17 @@ 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); } - - print drupal_to_js($matches); } - exit(); + + // 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)); } /** Index: modules/system/system.css =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.css,v retrieving revision 1.26 diff -u -d -F^\s*function -r1.26 system.css --- modules/system/system.css 10 May 2007 19:55:23 -0000 1.26 +++ modules/system/system.css 12 May 2007 12:09:04 -0000 @@ -279,6 +279,10 @@ border: 1px solid; overflow: hidden; z-index: 100; + background: #fff; + color: #000; + cursor: default; + line-height: normal; } #autocomplete ul { margin: 0; @@ -286,15 +290,24 @@ list-style: none; } #autocomplete li { - background: #fff; - color: #000; - white-space: pre; + padding: 0.25em 0; + clear: both; cursor: default; } #autocomplete li.selected { background: #0072b9; color: #fff; } +#autocomplete li small { + font-size: 0.85em; + color: #555; +} +#autocomplete li.selected * { + color:#fff; +} +#autocomplete .autocomplete-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.354 diff -u -d -F^\s*function -r1.354 taxonomy.module --- modules/taxonomy/taxonomy.module 4 May 2007 08:38:34 -0000 1.354 +++ modules/taxonomy/taxonomy.module 12 May 2007 12:09:04 -0000 @@ -21,6 +21,9 @@ function taxonomy_theme() { 'taxonomy_term_select' => array( 'arguments' => array('element' => NULL), ), + 'taxonomy_autocomplete_item' => array( + 'arguments' => array('title', 'description'), + ), ); } @@ -1495,25 +1498,61 @@ function taxonomy_autocomplete($vid, $st // The user enters a comma-separated list of tags. We only autocomplete the last tag. $array = taxonomy_explode_tags($string); + $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); + if (!empty($last_string)) { + $result = db_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'), $vid, $last_string); - $prefix = count($array) ? implode(', ', $array) .', ' : ''; + // The rest of the autocomplete string (i.e. not modified tags). + $prefix = count($array) ? taxonomy_implode_tags($array) .', ' : ''; - $matches = array(); + // Loop through the result set. 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) .'"'; + // Check if the tag is not yet in the list of already entered tags. + 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) .'"'; + } + + // Highlight the search string in the result name. + $title = preg_replace('%('. $last_string .')%i', '$1', check_plain($tag->name)); + + // Add the tag to the list of tags. + $matches[$prefix . $id .', '] = theme('taxonomy_autocomplete_item', $title, check_plain($tag->description)); } - $matches[$prefix . $n] = check_plain($tag->name); } - print drupal_to_js($matches); - exit(); + + // Add info text on certain occasions. + $total = db_num_rows($result); + if (count($matches) == 0) { + $info = t('There are no matching terms.'); + } + elseif ($total > 10) { + $info = format_plural($total - 10, 'There is one further matching term.', 'There are @count further matching terms.'); + } } + + // 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' => $info)); +} + +/** + * Themes a taxonomy term list item for an autocomplete dropdown. + * + * @ingroup themeable + */ +function theme_taxonomy_autocomplete_item($title, $description = '') { + $output = ''. $title .''; + if (!empty($description)) { + $output .= ' '. $description .''; + } + return $output; } /** @@ -1542,19 +1581,29 @@ function taxonomy_explode_tags($tags) { /** * Implode a list of tags of a certain vocabulary into a string. + * + * @param $tags + * An array of taxonomy term objects or an array of taxonomy term strings. + * @param $vid + * (Optional). If specified, only taxonomy terms with this vocabulary id are + * imploded together. Terms not belonging to this vocabulary are simply omitted. */ function taxonomy_implode_tags($tags, $vid = NULL) { $typed_tags = array(); foreach ($tags as $tag) { // Extract terms belonging to the vocabulary in question. - if (is_null($vid) || $tag->vid == $vid) { + if (!isset($vid) || (isset($tag->vid) && $tag->vid == $vid)) { + // If we have a list of term objects, flatten the list to only the name. + if (is_object($tag)) { + $tag = $tag->name; + } // Commas and quotes in tag names are special cases, so encode 'em. - if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { - $tag->name = '"'. str_replace('"', '""', $tag->name) .'"'; + if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) { + $tag = '"'. str_replace('"', '""', $tag) .'"'; } - $typed_tags[] = $tag->name; + $typed_tags[] = $tag; } } return implode(', ', $typed_tags); Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.779 diff -u -d -F^\s*function -r1.779 user.module --- modules/user/user.module 10 May 2007 19:55:23 -0000 1.779 +++ modules/user/user.module 12 May 2007 12:09:06 -0000 @@ -56,6 +56,9 @@ function user_theme() { 'user_signature' => array( 'arguments' => array('signature' => NULL), ), + 'user_autocomplete_item' => array( + 'arguments' => array('account' => NULL), + ), ); } @@ -727,6 +730,21 @@ function theme_user_list($users, $title return theme('item_list', $items, $title); } +/** + * Themes a user autocomplete list item for display in the dropdown. + * + * @param $account + * A user object containing only the user id and the name of the user. + * @param $search + * The search term the user entered. + * + * @ingroup themeable + */ +function theme_user_autocomplete_item($account, $search) { + // Highlight the search string in the result name. + return preg_replace('%('. $search .')%i', '$1', check_plain($account->name)); +} + function user_is_anonymous() { return !$GLOBALS['user']->uid; } @@ -2609,14 +2627,29 @@ function _user_forms(&$edit, $account, $ */ function user_autocomplete($string = '') { $matches = array(); - if ($string) { - $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10); - while ($user = db_fetch_object($result)) { - $matches[$user->name] = check_plain($user->name); - } + $info = ''; + + if (!empty($string)) { + $result = db_query("SELECT uid, name FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", $string); + + $i = 0; + while ($i < 10 && $user = db_fetch_object($result)) { + $matches[$user->name] = theme('user_autocomplete_item', $user, $string); + $i++; + } + + $total = db_num_rows($result); + if (count($matches) == 0) { + $info = t('No matching users were found.'); + } + elseif ($total > 10) { + $info = format_plural($total - 10, 'The ten best matches are listed. One further user has been found.', 'The ten best matches are listed. @count further users have been found.'); + } } - print drupal_to_js($matches); - exit(); + + // 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' => $info)); } /** Index: themes/garland/style.css =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/style.css,v retrieving revision 1.19 diff -u -d -F^\s*function -r1.19 style.css --- themes/garland/style.css 13 Apr 2007 07:33:24 -0000 1.19 +++ themes/garland/style.css 12 May 2007 12:09:07 -0000 @@ -806,8 +806,9 @@ */ #autocomplete li { cursor: default; - padding: 2px; + padding: 0.25em; margin: 0; + background: none; } /**