cvs diff: Diffing . cvs diff: Diffing database cvs diff: Diffing includes Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.443 diff -u -r1.443 common.inc --- includes/common.inc 21 May 2005 18:33:59 -0000 1.443 +++ includes/common.inc 24 May 2005 03:33:19 -0000 @@ -1257,6 +1257,41 @@ } /** + * Format a single-line text field that has uses Ajax for autocomplete. + * + * @param $title + * The label for the text field. + * @param $name + * The internal name used to refer to the field. + * @param $value + * The initial value for the field at page load time. + * @param $size + * A measure of the visible size of the field (passed directly to HTML). + * @param $maxlength + * The maximum number of characters that may be entered in the field. + * @param $callback_path + * A drupal path for the Ajax autocomplete callback. + * @param $description + * Explanatory text to display after the form item. + * @param $attributes + * An associative array of HTML attributes to add to the form item. + * @param $required + * Whether the user must enter some text in the field. + * @return + * A themed HTML string representing the field. + */ +function form_autocomplete($title, $name, $value, $size, $maxlength, $callback_path, $description = NULL, $attributes = NULL, $required = FALSE) { + drupal_add_js('misc/autocomplete.js'); + + $size = $size ? ' size="'. $size .'"' : ''; + + $output = theme('form_element', $title, '', $description, 'edit-'. $name, $required, _form_get_error($name)); + $output .= ''; + + return $output; +} + +/** * Format a single-line text field that does not display its contents visibly. * * @param $title @@ -1917,6 +1952,24 @@ '); } +/* +* Add a JavaScript file to the output. +* +* The first time this function is invoked per page request, +* it adds "misc/drupal.js" to the output. Other scripts +* depends on the 'killswitch' inside it. +*/ +function drupal_add_js($file) { + static $sent = array(); + if (!$sent['misc/drupal.js']) { + drupal_set_html_head(''); + } + if (!$sent[$file]) { + drupal_set_html_head(''); + $sent[$file] = true; + } +} + // Set the Drupal custom error handler. set_error_handler('error_handler'); cvs diff: Diffing misc Index: misc/autocomplete.js =================================================================== RCS file: misc/autocomplete.js diff -N misc/autocomplete.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/autocomplete.js 24 May 2005 03:33:19 -0000 @@ -0,0 +1,254 @@ +// Global Killswitch +if (isJSEnabled()) { + addLoadEvent(autocomplete_auto_attach); +} + +/** + * Attaches the autocomplete behaviour to all required fields + */ +function autocomplete_auto_attach() { + var acdb = []; + var inputs = document.getElementsByTagName('input'); + for (i = 0; input = inputs[i]; i++) { + if (input && hasClass(input, 'autocomplete')) { + uri = input.value; + if (!acdb[uri]) { + acdb[uri] = new ACDB(uri); + } + id = input.id.substr(0, input.id.length - 13); + input = document.getElementById(id); + input.setAttribute('autocomplete', 'OFF'); + input.form.onsubmit = autocomplete_submit; + new jsAC(input, acdb[uri]); + } + } +} + +/** + * Prevents the form from submitting if the suggestions popup is open + */ +function autocomplete_submit() { + var popup = document.getElementById('autocomplete'); + if (popup) { + popup.owner.hidePopup(); + return false; + } + return true; +} + + +/** + * An AutoComplete object + */ +function jsAC(input, db) { + var ac = this; + this.input = input; + this.db = db; + this.db.owner = this; + this.input.onkeydown = function (event) { return ac.onkeydown(this, event); }; + this.input.onkeyup = function (event) { ac.onkeyup(this, event) }; + this.input.onblur = function () { ac.hidePopup() }; + this.popup = document.createElement('div'); + this.popup.id = 'autocomplete'; + this.popup.owner = this; +}; + +/** + * Hides the autocomplete suggestions + */ +jsAC.prototype.hidePopup = function (keycode) { + if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) { + this.input.value = this.selected.innerHTML; + } + if (this.popup.parentNode && this.popup.parentNode.tagName) { + removeNode(this.popup); + } + this.selected = false; +} + + +/** + * Handler for the "keydown" event + */ +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 + */ +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 + */ +jsAC.prototype.select = function (node) { + this.input.value = node.innerHTML; +} + +/** + * Highlights the next suggestion + */ +jsAC.prototype.selectDown = function () { + if (this.selected && this.selected.nextSibling) { + this.highlight(this.selected.nextSibling); + } + else { + var lis = this.popup.getElementsByTagName('li'); + if (lis.length > 0) { + this.highlight(lis[0]); + } + } +} + +/** + * Highlights the previous suggestion + */ +jsAC.prototype.selectUp = function () { + if (this.selected && this.selected.previousSibling) { + this.highlight(this.selected.previousSibling); + } +} + +/** + * Highlights a suggestion + */ +jsAC.prototype.highlight = function (node) { + removeClass(this.selected, 'selected'); + addClass(node, 'selected'); + this.selected = node; +} + +/** + * Unhighlights a suggestion + */ +jsAC.prototype.unhighlight = function (node) { + removeClass(node, 'selected'); + this.selected = false; +} + +/** + * Positions the suggestions popup and starts a search + */ +jsAC.prototype.populatePopup = function () { + var ac = this; + var pos = AbsolutePosition(this.input); + this.selected = false; + this.popup.style.top = (pos.y + this.input.offsetHeight) +'px'; + this.popup.style.left = pos.x +'px'; + this.popup.style.width = (this.input.offsetWidth - 4) +'px'; + addClass(this.input, 'throbbing'); + this.db.search(this.input.value); +} + +/** + * Fills the suggestion popup with any matches received + */ +jsAC.prototype.found = function (matches) { + while (this.popup.hasChildNodes()) { + this.popup.removeChild(this.popup.childNodes[0]); + } + if (!this.popup.parentNode || !this.popup.parentNode.tagName) { + document.getElementsByTagName('body')[0].appendChild(this.popup); + } + var ul = document.createElement('ul'); + var ac = this; + if (matches.length > 0) { + for (i in matches) { + li = document.createElement('li'); + li.appendChild(document.createTextNode(matches[i])); + li.onmousedown = function() { ac.select(this); }; + li.onmouseover = function() { ac.highlight(this); }; + li.onmouseout = function() { ac.unhighlight(this); }; + ul.appendChild(li); + } + this.popup.appendChild(ul); + } + else { + this.hidePopup(); + } + removeClass(this.input, 'throbbing'); +} + +/** + * An AutoComplete DataBase object + */ +function ACDB(uri) { + this.uri = uri; + this.max = 15; + this.delay = 300; + this.cache = {}; +} + +/** + * Performs a cached and delayed search + */ +ACDB.prototype.search = function(search_string) { + if (this.docache) { + this.search_string = search_string; + if (this.cache[search_string]) { + return this.match(this.cache[search_string]); + } + } + if (this.timer) { + clearTimeout(this.timer); + } + var db = this; + this.timer = setTimeout(function() { HTTPGet(db.uri +'/'+ search_string +'/'+ db.max, db.receive, db); }, this.delay); +} + +/** + * HTTP callback function. Passes suggestions to the autocomplete object + */ +ACDB.prototype.receive = function(string, xmlhttp, acdb) { + if (xmlhttp.status != 200) { + return alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ acdb.uri); + } + var matches = string.length > 0 ? string.split('|') : []; + acdb.cache[acdb.search_string] = matches; + acdb.owner.found(matches); +} Index: misc/drupal.css =================================================================== RCS file: /cvs/drupal/drupal/misc/drupal.css,v retrieving revision 1.103 diff -u -r1.103 drupal.css --- misc/drupal.css 21 May 2005 11:53:01 -0000 1.103 +++ misc/drupal.css 24 May 2005 03:33:19 -0000 @@ -551,3 +551,28 @@ ul.secondary a.active { border-bottom: 4px solid #999; } + +/* +** Autocomplete styles +*/ +#autocomplete { + position: absolute; + border: 1px solid; + overflow: hidden; +} +#autocomplete ul { + margin: 0; + padding: 0; + list-style: none; +} +#autocomplete li { + background: #fff; + color: #000; + white-space: pre; + cursor: default; +} +#autocomplete li.selected { + background: #0072b9; + color: #fff; +} + Index: misc/drupal.js =================================================================== RCS file: misc/drupal.js diff -N misc/drupal.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/drupal.js 24 May 2005 03:33:19 -0000 @@ -0,0 +1,163 @@ +/** + * Only enable Javascript functionality if all required features are supported. + */ +function isJSEnabled() { + if (document.jsEnabled == undefined) { + // Note: ! casts to boolean implicitly. + document.jsEnabled = !( + !document.getElementsByTagName || + !document.createElement || + !document.createTextNode || + !document.getElementById); + } + return document.jsEnabled; +} + +// Global Killswitch +if (isJSEnabled()) { + +} + +/** + * Make IE's XMLHTTP object accessible through XMLHttpRequest() + */ +if (typeof XMLHttpRequest == 'undefined') { + XMLHttpRequest = function () { + var msxmls = ['MSXML3', 'MSXML2', 'Microsoft'] + for (var i=0; i < msxmls.length; i++) { + try { + return new ActiveXObject(msxmls[i]+'.XMLHTTP') + } + catch (e) { } + } + throw new Error("No XML component installed!") + } +} + +/** + * Creates an HTTP request and sends the response to the callback function + */ +function HTTPGet(uri, callback_function, callback_parameter) { + var xmlhttp = new XMLHttpRequest(); + var bAsync = true; + if (!callback_function) + bAsync = false; + xmlhttp.open('GET', uri, bAsync); + xmlhttp.send(null); + + if (bAsync) { + if (callback_function) { + xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState == 4) + callback_function(xmlhttp.responseText, xmlhttp, callback_parameter) + } + } + return true; + } + else { + return xmlhttp.responseText; + } +} + +/** + * Adds a function to the window onload event + */ +function addLoadEvent(func) { + var oldonload = window.onload; + if (typeof window.onload != 'function') { + window.onload = func; + } + else { + window.onload = function() { + oldonload(); + func(); + } + } +} + +/** + * Retrieves the absolute position of an element on the screen + */ +function AbsolutePosition(el) { + var SL = 0, ST = 0; + var is_div = /^div$/i.test(el.tagName); + if (is_div && el.scrollLeft) { + SL = el.scrollLeft; + } + if (is_div && el.scrollTop) { + ST = el.scrollTop; + } + var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; + if (el.offsetParent) { + var tmp = AbsolutePosition(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; +}; + +/** + * Returns true if an element has a specified class name + */ +function hasClass(node, className) { + if (node.className == className) { + return true; + } + var reg = new RegExp('(^| )'+ className +'($| )') + if (reg.test(node.className)) { + return true; + } + return false; +} + +/** + * Adds a class name to an element + */ +function addClass(node, className) { + if (hasClass(node, className)) { + return false; + } + node.className += ' '+ className; + return true; +} + +/** + * Removes a class name from an element + */ +function removeClass(node, className) { + if (!hasClass(node, className)) { + return false; + } + node.className = ereg_replace('(^| )'+ className +'($| )', '', node.className); + return true; +} + +/** + * Toggles a class name on or off for an element + */ +function toggleClass(node, className) { + if (!removeClass(node, className) && !addClass(node, className)) { + return false; + } + return true; +} + +/** + * Emulate PHP's ereg_replace function in javascript + */ +function ereg_replace(search, replace, subject) { + return subject.replace(new RegExp(search,'g'), replace); +} + +/** + * Removes an element from the page + */ +function removeNode(node) { + if (typeof node == 'string') { + node = document.getElementById(node); + } + if (node && node.parentNode) { + return node.parentNode.removeChild(node); + } + else { + return false; + } +} cvs diff: Diffing modules Index: modules/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node.module,v retrieving revision 1.488 diff -u -r1.488 node.module --- modules/node.module 17 May 2005 20:24:33 -0000 1.488 +++ modules/node.module 24 May 2005 03:33:25 -0000 @@ -1317,7 +1317,7 @@ if (user_access('administer nodes')) { $output .= '
'; - $author = form_textfield(t('Authored by'), 'name', $edit->name, 20, 60); + $author = form_autocomplete(t('Authored by'), 'name', $edit->name, 20, 60, 'user/autocomplete'); $author .= form_textfield(t('Authored on'), 'date', $edit->date, 20, 25, NULL, NULL, TRUE); $output .= '
'; Index: modules/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user.module,v retrieving revision 1.473 diff -u -r1.473 user.module --- modules/user.module 21 May 2005 11:57:59 -0000 1.473 +++ modules/user.module 24 May 2005 03:33:31 -0000 @@ -638,6 +638,9 @@ $items[] = array('path' => 'user', 'title' => t('user account'), 'callback' => 'user_page', 'access' => TRUE, 'type' => MENU_CALLBACK); + $items[] = array('path' => 'user/autocomplete', 'title' => t('user autocomplete'), + 'callback' => 'user_autocomplete', 'access' => $admin_access, 'type' => MENU_CALLBACK); + //registration and login pages. $items[] = array('path' => 'user/login', 'title' => t('log in'), 'type' => MENU_DEFAULT_LOCAL_TASK); @@ -1851,4 +1854,17 @@ return $output; } +/** + * Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function user_autocomplete($string) { + $matches = array(); + $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; + } + print check_plain(implode('|', $matches)); + exit(); +} + ?> cvs diff: Diffing scripts cvs diff: Diffing sites cvs diff: Diffing sites/default cvs diff: Diffing themes cvs diff: Diffing themes/bluemarine cvs diff: Diffing themes/chameleon cvs diff: Diffing themes/chameleon/marvin cvs diff: Diffing themes/engines cvs diff: Diffing themes/engines/phptemplate cvs diff: Diffing themes/pushbutton ***** CVS exited normally with code 1 *****