* cue Star Wars theme tune *
Return of the JavaScript! - #22519: form_autocomplete(): Ajax based autocompletion. Currently used for user names and folksonomy tags.4.7.x
parent
0de88f50ba
commit
58bddf8abc
|
@ -1256,6 +1256,41 @@ function form_textfield($title, $name, $value, $size, $maxlength, $description =
|
|||
return theme('form_element', $title, '<input type="text" maxlength="'. $maxlength .'" class="'. _form_get_class('form-text', $required, _form_get_error($name)) .'" name="edit['. $name .']" id="edit-'. $name .'"'. $size .' value="'. check_plain($value) .'"'. drupal_attributes($attributes) .' />', $description, 'edit-'. $name, $required, _form_get_error($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a single-line text field that 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, '<input type="text" maxlength="'. $maxlength .'" class="'. _form_get_class('form-text form-autocomplete', $required, _form_get_error($name)) .'" name="edit['. $name .']" id="edit-'. $name .'"'. $size .' value="'. check_plain($value) .'"'. drupal_attributes($attributes) .' />', $description, 'edit-'. $name, $required, _form_get_error($name));
|
||||
$output .= '<input class="autocomplete" type="hidden" id="edit-'. $name .'-autocomplete" value="'. check_url(url($callback_path, NULL, NULL, TRUE)) .'" disabled="disabled" />';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a single-line text field that does not display its contents visibly.
|
||||
*
|
||||
|
@ -1917,6 +1952,43 @@ if (version_compare(phpversion(), '5.0') < 0) {
|
|||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!isset($sent['misc/drupal.js'])) {
|
||||
drupal_set_html_head('<script type="text/javascript" src="misc/drupal.js"></script>');
|
||||
$sent['misc/drupal.js'] = true;
|
||||
}
|
||||
if (!isset($sent[$file])) {
|
||||
drupal_set_html_head('<script type="text/javascript" src="'. check_url($file) .'"></script>');
|
||||
$sent[$file] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implode a PHP array into a string that can be decoded by the autocomplete JS routines.
|
||||
*
|
||||
* Items are separated by double pipes. Each item consists of a key-value pair
|
||||
* separated by single pipes. Entities are used to ensure pipes in the strings
|
||||
* pass unharmed.
|
||||
*
|
||||
* The key is what is filled in in the text-box (plain-text), the value is what
|
||||
* is displayed in the suggestion list (HTML).
|
||||
*/
|
||||
function drupal_implode_autocomplete($array) {
|
||||
$output = array();
|
||||
foreach ($array as $k => $v) {
|
||||
$output[] = str_replace('|', '|', $k) .'|'. str_replace('|', '|', $v);
|
||||
}
|
||||
return implode('||', $output);
|
||||
}
|
||||
|
||||
// Set the Drupal custom error handler.
|
||||
set_error_handler('error_handler');
|
||||
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
// Global Killswitch
|
||||
if (isJsEnabled()) {
|
||||
addLoadEvent(autocompleteAutoAttach);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the autocomplete behaviour to all required fields
|
||||
*/
|
||||
function autocompleteAutoAttach() {
|
||||
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 = autocompleteSubmit;
|
||||
new jsAC(input, acdb[uri]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents the form from submitting if the suggestions popup is open
|
||||
*/
|
||||
function autocompleteSubmit() {
|
||||
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.autocompleteValue;
|
||||
}
|
||||
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.autocompleteValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
div = document.createElement('div');
|
||||
div.innerHTML = matches[i][1];
|
||||
li.appendChild(div);
|
||||
li.autocompleteValue = matches[i][0];
|
||||
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(searchString) {
|
||||
if (this.docache) {
|
||||
this.searchString = searchString;
|
||||
if (this.cache[searchString]) {
|
||||
return this.match(this.cache[searchString]);
|
||||
}
|
||||
}
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
var db = this;
|
||||
this.timer = setTimeout(function() { HTTPGet(db.uri +'/'+ searchString +'/'+ 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);
|
||||
}
|
||||
// Split into array of key->value pairs
|
||||
var matches = string.length > 0 ? string.split('||') : [];
|
||||
for (i in matches) {
|
||||
matches[i] = matches[i].length > 0 ? matches[i].split('|') : [];
|
||||
// Decode textfield pipes back to plain-text
|
||||
matches[i][0] = eregReplace('|', '|', matches[i][0]);
|
||||
}
|
||||
acdb.cache[acdb.searchString] = matches;
|
||||
acdb.owner.found(matches);
|
||||
}
|
|
@ -551,3 +551,28 @@ ul.secondary a {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* 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, callbackFunction, callbackParameter) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
var bAsync = true;
|
||||
if (!callbackFunction)
|
||||
bAsync = false;
|
||||
xmlHttp.open('GET', uri, bAsync);
|
||||
xmlHttp.send(null);
|
||||
|
||||
if (bAsync) {
|
||||
if (callbackFunction) {
|
||||
xmlHttp.onreadystatechange = function() {
|
||||
if (xmlHttp.readyState == 4)
|
||||
callbackFunction(xmlHttp.responseText, xmlHttp, callbackParameter)
|
||||
}
|
||||
}
|
||||
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 sLeft = 0, sTop = 0;
|
||||
var isDiv = /^div$/i.test(el.tagName);
|
||||
if (isDiv && el.scrollLeft) {
|
||||
sLeft = el.scrollLeft;
|
||||
}
|
||||
if (isDiv && el.scrollTop) {
|
||||
sTop = el.scrollTop;
|
||||
}
|
||||
var r = { x: el.offsetLeft - sLeft, y: el.offsetTop - sTop };
|
||||
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 = eregReplace('(^| )'+ 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 eregReplace(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;
|
||||
}
|
||||
}
|
|
@ -1317,7 +1317,7 @@ function node_form($edit) {
|
|||
if (user_access('administer nodes')) {
|
||||
$output .= '<div class="admin">';
|
||||
|
||||
$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 .= '<div class="authored">';
|
||||
|
|
|
@ -1317,7 +1317,7 @@ function node_form($edit) {
|
|||
if (user_access('administer nodes')) {
|
||||
$output .= '<div class="admin">';
|
||||
|
||||
$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 .= '<div class="authored">';
|
||||
|
|
|
@ -82,6 +82,11 @@ function taxonomy_menu($may_cache) {
|
|||
'callback' => 'taxonomy_term_page',
|
||||
'access' => user_access('access content'),
|
||||
'type' => MENU_CALLBACK);
|
||||
|
||||
$items[] = array('path' => 'taxonomy/autocomplete', 'title' => t('autocomplete taxonomy'),
|
||||
'callback' => 'taxonomy_autocomplete',
|
||||
'access' => user_access('access content'),
|
||||
'type' => MENU_CALLBACK);
|
||||
}
|
||||
else {
|
||||
if (is_numeric(arg(2))) {
|
||||
|
@ -519,7 +524,7 @@ function taxonomy_node_form($type, $node = '', $help = NULL, $name = 'taxonomy')
|
|||
}
|
||||
}
|
||||
$typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
|
||||
$result[] = form_textfield($vocabulary->name, "$name][tags][". $vocabulary->vid, $typed_string, 50, 100, t('A comma-separated list of terms describing this content (Example: funny, bungie jumping, "Company, Inc.").'), NULL, ($vocabulary->required ? TRUE : FALSE));
|
||||
$result[] = form_autocomplete($vocabulary->name, "$name][tags][". $vocabulary->vid, $typed_string, 50, 100, 'taxonomy/autocomplete/'. $vocabulary->vid, t('A comma-separated list of terms describing this content (Example: funny, bungie jumping, "Company, Inc.").'), NULL, ($vocabulary->required ? TRUE : FALSE));
|
||||
}
|
||||
else {
|
||||
$ntterms = array_key_exists('taxonomy', $node) ? $terms : array_keys($terms);
|
||||
|
@ -1259,4 +1264,36 @@ function _taxonomy_get_tid_from_term($term) {
|
|||
return $term->tid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for autocompletion
|
||||
*/
|
||||
function taxonomy_autocomplete($vid, $string = '') {
|
||||
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
|
||||
// This regexp allows the following types of user input:
|
||||
// this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
|
||||
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
|
||||
preg_match_all($regexp, $string, $matches);
|
||||
$array = $matches[1];
|
||||
|
||||
// Fetch last tag
|
||||
$last_string = trim(array_pop($array));
|
||||
if ($last_string != '') {
|
||||
$result = db_query_range("SELECT name FROM {term_data} WHERE vid = %d AND LOWER(name) LIKE LOWER('%%%s%%')", $vid, $last_string, 0, 10);
|
||||
|
||||
$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 (preg_match('/,/', $tag->name) || preg_match('/"/', $tag->name)) {
|
||||
$n = '"'. preg_replace('/"/', '""', $tag->name) .'"';
|
||||
}
|
||||
$matches[$prefix . $n] = check_plain($tag->name);
|
||||
}
|
||||
print drupal_implode_autocomplete($matches);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -82,6 +82,11 @@ function taxonomy_menu($may_cache) {
|
|||
'callback' => 'taxonomy_term_page',
|
||||
'access' => user_access('access content'),
|
||||
'type' => MENU_CALLBACK);
|
||||
|
||||
$items[] = array('path' => 'taxonomy/autocomplete', 'title' => t('autocomplete taxonomy'),
|
||||
'callback' => 'taxonomy_autocomplete',
|
||||
'access' => user_access('access content'),
|
||||
'type' => MENU_CALLBACK);
|
||||
}
|
||||
else {
|
||||
if (is_numeric(arg(2))) {
|
||||
|
@ -519,7 +524,7 @@ function taxonomy_node_form($type, $node = '', $help = NULL, $name = 'taxonomy')
|
|||
}
|
||||
}
|
||||
$typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
|
||||
$result[] = form_textfield($vocabulary->name, "$name][tags][". $vocabulary->vid, $typed_string, 50, 100, t('A comma-separated list of terms describing this content (Example: funny, bungie jumping, "Company, Inc.").'), NULL, ($vocabulary->required ? TRUE : FALSE));
|
||||
$result[] = form_autocomplete($vocabulary->name, "$name][tags][". $vocabulary->vid, $typed_string, 50, 100, 'taxonomy/autocomplete/'. $vocabulary->vid, t('A comma-separated list of terms describing this content (Example: funny, bungie jumping, "Company, Inc.").'), NULL, ($vocabulary->required ? TRUE : FALSE));
|
||||
}
|
||||
else {
|
||||
$ntterms = array_key_exists('taxonomy', $node) ? $terms : array_keys($terms);
|
||||
|
@ -1259,4 +1264,36 @@ function _taxonomy_get_tid_from_term($term) {
|
|||
return $term->tid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for autocompletion
|
||||
*/
|
||||
function taxonomy_autocomplete($vid, $string = '') {
|
||||
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
|
||||
// This regexp allows the following types of user input:
|
||||
// this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
|
||||
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
|
||||
preg_match_all($regexp, $string, $matches);
|
||||
$array = $matches[1];
|
||||
|
||||
// Fetch last tag
|
||||
$last_string = trim(array_pop($array));
|
||||
if ($last_string != '') {
|
||||
$result = db_query_range("SELECT name FROM {term_data} WHERE vid = %d AND LOWER(name) LIKE LOWER('%%%s%%')", $vid, $last_string, 0, 10);
|
||||
|
||||
$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 (preg_match('/,/', $tag->name) || preg_match('/"/', $tag->name)) {
|
||||
$n = '"'. preg_replace('/"/', '""', $tag->name) .'"';
|
||||
}
|
||||
$matches[$prefix . $n] = check_plain($tag->name);
|
||||
}
|
||||
print drupal_implode_autocomplete($matches);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -641,6 +641,9 @@ function user_menu($may_cache) {
|
|||
$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);
|
||||
|
@ -1854,4 +1857,17 @@ function _user_forms(&$edit, $account, $category, $hook = 'form') {
|
|||
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] = check_plain($user->name);
|
||||
}
|
||||
print drupal_implode_autocomplete($matches);
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -641,6 +641,9 @@ function user_menu($may_cache) {
|
|||
$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);
|
||||
|
@ -1854,4 +1857,17 @@ function _user_forms(&$edit, $account, $category, $hook = 'form') {
|
|||
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] = check_plain($user->name);
|
||||
}
|
||||
print drupal_implode_autocomplete($matches);
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
Loading…
Reference in New Issue