Bug 91840 - Autocomplete for users

git-svn-id: svn://svn.office.custis.ru/3rdparty/bugzilla.org/trunk@1489 6955db30-a419-402b-8a0d-67ecbb4d7f56
custis
vfilippov 2012-01-11 16:47:30 +00:00
parent f4a155bf2f
commit 0fc18407dd
14 changed files with 651 additions and 163 deletions

View File

@ -74,9 +74,9 @@ sub ready_list
my $self = shift;
$self->{r} ||= [
map { {
login => $_->[0]->login,
identity => $_->[1] . ': ' . $_->[0]->identity,
visible => 1
email => $_->[0]->login,
real_name => $_->[1] . ': ' . $_->[0]->name,
visible => 1,
} }
sort { ($roleindex{$b->[1]} <=> $roleindex{$a->[1]})
|| ($a->[0]->login cmp $b->[0]->login) }

View File

@ -130,7 +130,6 @@ sub create {
return { id => $self->type('int', $user->id) };
}
# function to return user information by passing either user ids or
# login names or both together:
# $call = $rpc->call( 'User.get', { ids => [1,2,3],
@ -138,6 +137,17 @@ sub create {
sub get {
my ($self, $params) = validate(@_, 'names', 'ids');
# Make them arrays if they aren't
if ($params->{names} && !ref $params->{names}) {
$params->{names} = [ $params->{names} ];
}
if ($params->{ids} && !ref $params->{ids}) {
$params->{ids} = [ $params->{ids} ];
}
if ($params->{match} && !ref $params->{match}) {
$params->{match} = [ $params->{match} ];
}
my @user_objects;
@user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }
if $params->{names};
@ -145,7 +155,7 @@ sub get {
# start filtering to remove duplicate user ids
my %unique_users = map { $_->id => $_ } @user_objects;
@user_objects = values %unique_users;
my @users;
# If the user is not logged in: Return an error if they passed any user ids.
@ -187,7 +197,7 @@ sub get {
userid => $obj->id});
}
}
# User Matching
my $limit;
if ($params->{'maxusermatches'}) {
@ -202,9 +212,9 @@ sub get {
}
}
}
my $in_group = $self->_filter_users_by_group(
\@user_objects, $params);
\@user_objects, $params);
if (Bugzilla->user->in_group('editusers')) {
@users =
map {filter $params, {
@ -216,8 +226,7 @@ sub get {
email_enabled => $self->type('boolean', $_->email_enabled),
login_denied_text => $self->type('string', $_->disabledtext),
}} @$in_group;
}
}
else {
@users =
map {filter $params, {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

View File

@ -50,7 +50,7 @@ function showEditableField(e, ContainerInputArray)
}
addClass(ContainerInputArray[0], 'bz_default_hidden');
removeClass(inputArea, 'bz_default_hidden');
if (inputArea.tagName.toLowerCase() == "input")
if (inputArea.nodeName.toLowerCase() == "input")
inputs.push(inputArea);
else
inputs = inputArea.getElementsByTagName('input');
@ -265,3 +265,55 @@ function _value_id(field_name, id)
{
return 'v' + id + '_' + field_name;
}
// Data loader for user autocomplete
function userAutocomplete(hint, emptyOptions)
{
if (!hint.input.value)
{
if (emptyOptions)
hint.replaceItems(emptyOptions);
else
hint.replaceItems(null);
return;
}
var x;
if (window.XMLHttpRequest)
x = new XMLHttpRequest();
else
{
try { return new ActiveXObject("Msxml2.XMLHTTP"); }
catch (e) { return new ActiveXObject("Microsoft.XMLHTTP"); }
}
x.onreadystatechange = function()
{
if (x.readyState == 4)
{
var r = {};
try { eval('r = '+x.responseText+';'); } catch (e) { return; }
if (r.status == 'ok')
{
var data = [];
for (var i = 0; i < r.users.length; i++)
data.push([
'<span class="hintRealname">' + htmlspecialchars(r.users[i].real_name) +
'</span><br /><span class="hintEmail">' + htmlspecialchars(r.users[i].email) + '</span>',
r.users[i].email
]);
// FIXME 3 letters: remove hardcode, also in Bugzilla::User::match()
if (data.length == 0 && hint.input.value.length < 3)
hint.emptyText = 'Type at least 3 letters';
else
hint.emptyText = 'No users found';
hint.replaceItems(data);
}
}
};
var u = window.location.href.replace(/[^\/]+$/, '');
u += 'xml.cgi?method=User.get&output=json&maxusermatches=20';
var l = hint.input.value.split(/[\s,]*,[\s,]*/);
for (var i = 0; i < l.length; i++)
u += '&match='+encodeURI(l[i]);
x.open('GET', u);
x.send(null);
}

View File

@ -116,92 +116,3 @@ function set_language(value)
});
window.location.reload();
}
/* template/en/global/menuforusers.html.tmpl */
function menuforusers_initcombo(id, multi)
{
var sel = document.getElementById(id+"_s");
var ed = document.getElementById(id);
if (!sel || !ed)
return;
var p = sel.parentNode;
/* We must move <select> out of its parent element temporarily,
because it can be invisible in some cases (i.e. New Bug form),
and have offsetWidth=offsetHeight=0 */
document.body.appendChild(sel);
var w = (sel.offsetWidth-sel.offsetHeight+2);
if (w > 400)
{
w = 400;
sel.style.width = (w+sel.offsetHeight-2)+'px';
}
ed.style.width = w+'px';
p.appendChild(sel);
ed.style.borderWidth = 0;
menuforusers_tocombo(id);
if (multi)
{
addListener(document.body, "click", function(ev) {
t = eventTarget(ev);
if (t.id != id && t.id != id+'_b' && t.id != id+'_s' && t.parentNode.id != id+'_s')
menuforusers_showmulti(id, false)
});
addListener(document.getElementById(id+'_b'), "click", function(ev) { menuforusers_showmulti(id); });
}
}
function menuforusers_tocombo(id, multi)
{
var sel = document.getElementById(id+"_s");
var ed = document.getElementById(id);
if (!sel || !ed)
return;
var nv = [];
var v = ed.value.split(/[\s,]+/);
var i, j;
for (i = 0; i < v.length; i++)
{
for (j = 0; j < sel.options.length; j++)
{
if (sel.options[j].value.toLowerCase().indexOf(v[i].toLowerCase()) >= 0 ||
sel.options[j].text.toLowerCase().indexOf(v[i].toLowerCase()) >= 0)
{
sel.options[j].selected = true;
nv.push(sel.options[j].value);
break;
}
}
if (j >= sel.options.length)
nv.push(v[i]);
if (!multi)
break;
}
ed.value = nv.join(', ');
}
function menuforusers_fromcombo(id, multi)
{
var sel = document.getElementById(id+"_s");
var ed = document.getElementById(id);
if (!sel || !ed)
return;
v = [];
for (var i = 0; i < sel.options.length; i++)
if (sel.options[i].selected)
v.push(sel.options[i].value);
ed.value = v.join(', ');
}
function menuforusers_showmulti(id, wha)
{
var sel = document.getElementById(id+"_s");
var btn = document.getElementById(id+"_b");
if (!sel || !btn)
return;
var show = sel.style.visibility == 'hidden' && !sel.disabled;
if (typeof(wha) != 'undefined')
show = wha;
sel.style.visibility = show ? '' : 'hidden';
btn.src = 'images/dn' + (show ? 'push' : '') + '.gif';
}

482
js/hinter.js Normal file
View File

@ -0,0 +1,482 @@
/* Simple autocomplete for text inputs, with the support for multiple selection.
Homepage: http://yourcmc.ru/wiki/SimpleAutocomplete
(c) Vitaliy Filippov 2011
Usage:
Include hinter.css, hinter.js on your page. Then write:
var hint = new SimpleAutocomplete(input, dataLoader, multipleDelimiter, onChangeListener, maxHeight, emptyText, allowHTML);
Parameters:
input
The input, either id or DOM element reference (the input must have an id anyway).
dataLoader(hint, value)
Callback which should load autocomplete options and then call
hint.replaceItems([ [ name, value ], [ name, value ], ... ])
'hint' parameter will be this autocompleter object, and the guess
should be done based on 'value' parameter (string).
Optional parameters:
multipleDelimiter
Pass a delimiter string (for example ',' or ';') to enable multiple selection.
Item values cannot have leading or trailing whitespace. Input value will consist
of selected item values separated by this delimiter plus single space.
dataLoader should handle it's 'value' parameter accordingly in this case,
because it will be just the raw value of the input, probably with incomplete
item or items, typed by the user.
onChangeListener(hint, index)
Callback which is called when input value is changed using this dropdown.
index is the number of element which selection is changed, starting with 0.
It must be used instead of normal 'onchange' event.
maxHeight
Maximum hint dropdown height in pixels
emptyText
Text to show when dataLoader returns no options.
If emptyText === false, the hint will be hidden instead of showing text.
allowHTML
If true, HTML code will be allowed in option names.
*/
var SimpleAutocomplete = function(input, dataLoader, multipleDelimiter, onChangeListener, maxHeight, emptyText, allowHTML)
{
if (typeof(input) == 'string')
input = document.getElementById(input);
if (emptyText === undefined)
emptyText = 'No items found';
// Parameters
var self = this;
self.input = input;
self.multipleDelimiter = multipleDelimiter;
self.dataLoader = dataLoader;
self.onChangeListener = onChangeListener;
self.maxHeight = maxHeight;
self.emptyText = emptyText;
self.allowHTML = allowHTML;
// Variables
self.items = [];
self.skipHideCounter = 0;
self.selectedIndex = -1;
self.id = input.id;
self.disabled = false;
// Initialiser
var init = function()
{
var e = self.input;
var p = getOffset(e);
// Create hint layer
var t = self.hintLayer = document.createElement('div');
t.className = 'hintLayer';
t.style.display = 'none';
t.style.position = 'absolute';
t.style.top = (p.top+e.offsetHeight) + 'px';
t.style.zIndex = 1000;
t.style.left = p.left + 'px';
if (self.maxHeight)
{
t.style.overflowY = 'scroll';
try { t.style.overflow = '-moz-scrollbars-vertical'; } catch(exc) {}
t.style.maxHeight = self.maxHeight+'px';
if (!t.style.maxHeight)
self.scriptMaxHeight = true;
}
document.body.appendChild(t);
// Remember instance
e.SimpleAutocomplete_input = self;
t.SimpleAutocomplete_layer = self;
SimpleAutocomplete.SimpleAutocompletes.push(self);
// Set event listeners
var msie = navigator.userAgent.match('MSIE') && !navigator.userAgent.match('Opera');
if (msie)
addListener(e, 'keydown', self.onKeyPress);
else
{
addListener(e, 'keydown', self.onKeyDown);
addListener(e, 'keypress', self.onKeyPress);
}
addListener(e, 'keyup', self.onKeyUp);
addListener(e, 'change', self.onChange);
addListener(e, 'focus', self.onInputFocus);
addListener(e, 'blur', self.onInputBlur);
self.onChange();
};
// obj = [ [ name, value, disabled ], [ name, value ], ... ]
self.replaceItems = function(items)
{
self.hintLayer.innerHTML = '';
self.hintLayer.scrollTop = 0;
self.items = [];
if (!items || items.length == 0)
{
if (self.emptyText)
{
var d = document.createElement('div');
d.className = 'hintEmptyText';
d.innerHTML = self.emptyText;
self.hintLayer.appendChild(d);
}
else
self.disable();
return;
}
self.enable();
var h = {};
if (self.multipleDelimiter)
{
var old = self.input.value.split(self.multipleDelimiter);
for (var i = 0; i < old.length; i++)
h[old[i].trim()] = true;
}
for (var i in items)
self.hintLayer.appendChild(self.makeItem(items[i][0], items[i][1], h[items[i][1]]));
if (self.maxHeight)
{
self.hintLayer.style.height =
(self.hintLayer.scrollHeight > self.maxHeight
? self.maxHeight : self.hintLayer.scrollHeight) + 'px';
}
};
// Create a drop-down list item, include checkbox if self.multipleDelimiter is true
self.makeItem = function(name, value, checked)
{
var d = document.createElement('div');
d.id = self.id+'_item_'+self.items.length;
d.className = 'hintItem';
d.title = value;
if (self.allowHTML)
d.innerHTML = name;
if (self.multipleDelimiter)
{
var c = document.createElement('input');
c.type = 'checkbox';
c.id = self.id+'_check_'+self.items.length;
c.checked = checked && true;
c.value = value;
if (d.childNodes.length)
d.insertBefore(c, d.firstChild);
else
d.appendChild(c);
addListener(c, 'click', self.preventCheck);
}
if (!self.allowHTML)
d.appendChild(document.createTextNode(name));
addListener(d, 'mouseover', self.onItemMouseOver);
addListener(d, 'mousedown', self.onItemClick);
self.items.push([name, value, checked]);
return d;
};
// Prevent default action on checkbox
self.preventCheck = function(ev)
{
ev = ev||window.event;
return stopEvent(ev, false, true);
};
// Handle item mouse over
self.onItemMouseOver = function()
{
return self.highlightItem(this);
};
// Handle item clicks
self.onItemClick = function(ev)
{
self.selectItem(parseInt(this.id.substr(self.id.length+6)));
return true;
};
// Move highlight forward or back by 'by' items (integer)
self.moveHighlight = function(by)
{
var n = self.selectedIndex+by;
if (n < 0)
n = 0;
var elem = document.getElementById(self.id+'_item_'+n);
if (!elem)
return true;
return self.highlightItem(elem);
};
// Make item 'elem' active (highlighted)
self.highlightItem = function(elem)
{
if (self.selectedIndex >= 0)
{
var c = self.getItem();
if (c)
c.className = 'hintItem';
}
self.selectedIndex = parseInt(elem.id.substr(self.id.length+6));
elem.className = 'hintActiveItem';
return false;
};
// Get index'th item, or current when index is null
self.getItem = function(index)
{
if (index == null)
index = self.selectedIndex;
if (index < 0)
return null;
return document.getElementById(self.id+'_item_'+self.selectedIndex);
};
// Select index'th item - change the input value and hide the hint if not a multi-select
self.selectItem = function(index)
{
if (!self.multipleDelimiter)
{
self.input.value = self.items[index][1];
self.hide();
}
else
{
document.getElementById(self.id+'_check_'+index).checked = self.items[index][2] = !self.items[index][2];
var old = self.input.value.split(self.multipleDelimiter);
for (var i = 0; i < old.length; i++)
old[i] = old[i].trim();
if (!self.items[index][2])
{
for (var i = old.length-1; i >= 0; i--)
if (old[i] == self.items[index][1])
old.splice(i, 1);
self.input.value = old.join(self.multipleDelimiter+' ');
}
else
{
var h = {};
for (var i = 0; i < self.items.length; i++)
if (self.items[i][2])
h[self.items[i][1]] = true;
var nl = [];
for (var i = 0; i < old.length; i++)
{
if (h[old[i]])
{
delete h[old[i]];
nl.push(old[i]);
}
}
for (var i = 0; i < self.items.length; i++)
if (self.items[i][2] && h[self.items[i][1]])
nl.push(self.items[i][1]);
self.input.value = nl.join(self.multipleDelimiter+' ');
}
}
if (self.onChangeListener)
self.onChangeListener(self, index);
};
// Handle user input, load new items
self.onChange = function()
{
var v = self.input.value.trim();
if (v != self.curValue)
{
self.curValue = v;
self.dataLoader(self, v);
}
return true;
};
// Handle Enter key presses, cancel handling of arrow keys
self.onKeyUp = function(ev)
{
ev = ev||window.event;
if (ev.keyCode != 10 && ev.keyCode != 13)
self.show();
if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 10 || ev.keyCode == 13)
return stopEvent(ev, true, true);
self.onChange();
return true;
};
// Cancel handling of Enter key
self.onKeyDown = function(ev)
{
ev = ev||window.event;
if (ev.keyCode == 10 || ev.keyCode == 13)
return stopEvent(ev, true, true);
return true;
};
// Handle arrow keys and Enter
self.onKeyPress = function(ev)
{
ev = ev||window.event;
if (ev.keyCode == 38) // up
self.moveHighlight(-1);
else if (ev.keyCode == 40) // down
self.moveHighlight(1);
else if (ev.keyCode == 10 || ev.keyCode == 13) // enter
{
if (self.selectedIndex >= 0)
self.selectItem(self.selectedIndex);
return stopEvent(ev, true, true);
}
else
return true;
// scrolling
if (self.selectedIndex >= 0)
{
var c = self.getItem();
var t = self.hintLayer;
var ct = getOffset(c).top + t.scrollTop - t.style.top.substr(0, t.style.top.length-2);
var ch = c.scrollHeight;
if (ct+ch-t.offsetHeight > t.scrollTop)
t.scrollTop = ct+ch-t.offsetHeight;
else if (ct < t.scrollTop)
t.scrollTop = ct;
}
return stopEvent(ev, true, true);
};
// Called when input receives focus
self.onInputFocus = function()
{
self.show();
self.hasFocus = true;
return true;
};
// Called when input loses focus
self.onInputBlur = function()
{
self.hide();
self.hasFocus = false;
return true;
};
// Hide hinter
self.hide = function()
{
if (!self.skipHideCounter)
self.hintLayer.style.display = 'none';
else
self.skipHideCounter = 0;
};
// Show hinter
self.show = function()
{
var p = getOffset(self.input);
self.hintLayer.style.top = (p.top+self.input.offsetHeight) + 'px';
self.hintLayer.style.left = p.left + 'px';
if (!self.disabled)
self.hintLayer.style.display = '';
};
// Disable hinter, for the case when there is no items and no empty text
self.disable = function()
{
self.disabled = true;
self.hide();
};
// Enable hinter
self.enable = function()
{
var show = self.disabled;
self.disabled = false;
if (show)
self.show();
}
// *** Call initialise ***
init();
};
// Global variable
SimpleAutocomplete.SimpleAutocompletes = [];
// Global mousedown handler, hides dropdowns when clicked outside
SimpleAutocomplete.GlobalMouseDown = function(ev)
{
var target = ev.target || ev.srcElement;
var esh;
while (target)
{
esh = target.SimpleAutocomplete_input;
if (esh)
break;
else if (target.SimpleAutocomplete_layer)
{
if (target.SimpleAutocomplete_layer.hasFocus)
target.SimpleAutocomplete_layer.skipHideCounter++;
return true;
}
target = target.parentNode;
}
for (var i in SimpleAutocomplete.SimpleAutocompletes)
if (SimpleAutocomplete.SimpleAutocompletes[i] != esh)
SimpleAutocomplete.SimpleAutocompletes[i].hide();
return true;
};
//// UTILITY FUNCTIONS ////
// You can delete this section if you already have them somewhere in your scripts //
// Cancel event bubbling and/or default action
var stopEvent = function(ev, cancelBubble, preventDefault)
{
if (cancelBubble)
{
if (ev.stopPropagation)
ev.stopPropagation();
else
ev.cancelBubble = true;
}
if (preventDefault && ev.preventDefault)
ev.preventDefault();
ev.returnValue = !preventDefault;
return !preventDefault;
};
// Get element position, relative to the top-left corner of page
var getOffset = function(elem)
{
if (elem.getBoundingClientRect)
return getOffsetRect(elem);
else
return getOffsetSum(elem);
};
// Get element position using getBoundingClientRect()
var getOffsetRect = function(elem)
{
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
};
// Get element position using sum of offsetTop/offsetLeft
var getOffsetSum = function(elem)
{
var top = 0, left = 0;
while(elem)
{
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
}
return { top: top, left: left };
};
//// END UTILITY FUNCTIONS ////
// Set global mousedown listener
addListener(window, 'load', function() { addListener(document, 'mousedown', SimpleAutocomplete.GlobalMouseDown) });

View File

@ -23,6 +23,16 @@ function findPos(obj)
return [ obj.x, obj.y ];
}
// Escape special HTML/XML characters
function htmlspecialchars(s)
{
s = s.replace(/&/g, '&amp;'); //&
s = s.replace(/</g, '&lt;'); //<
s = s.replace(/>/g, '&gt;'); //>
s = s.replace(/"/g, '&quot;'); //"
return s;
}
// Checks if a specified value 'val' is in the specified array 'arr'
function bz_isValueInArray(arr, val)
{

View File

@ -546,3 +546,44 @@ fieldset.chart { margin-bottom: 8px; padding: 0 8px 4px; }
.chart_not_c { border: 1px inset gray; background-color: white; font-weight: bold; width: 3.5em; }
div.chart._neg { border: 1px solid black; background-image: url(../../images/neg.gif); margin: 1px; padding: 2px 1px 0 1px; }
fieldset.chart._neg { background-image: url(../../images/neg.gif); }
/* SimpleAutocomplete styles */
.hintLayer {
border: 1px solid gray;
color: gray;
width: 300pt;
background-color: white;
font-size: 80%;
max-height: 300pt;
overflow-y: scroll;
overflow: -moz-scrollbars-vertical;
}
.hintEmptyText {
padding: 3px;
}
.hintItem {
color: black;
cursor: pointer;
padding: 1px 3px;
vertical-align: middle;
}
.hintActiveItem {
color: white;
background-color: #008;
cursor: pointer;
padding: 1px 3px;
}
.hintItem input, .hintActiveItem input {
cursor: pointer;
vertical-align: middle;
margin-right: 3px;
}
.hintRealName {
vertical-align: bottom;
}
.hintEmail {
color: #808080;
}
.hintActiveItem .hintEmail {
color: #c0c0c0;
}

View File

@ -26,55 +26,22 @@
# custom_userlist: optional, specify a limited list of users to use
#%]
<div style="position: relative">
[% SET id="userselect_" _ name IF !id %]
[% SET emptyok=emptyok ? 1 : 0 %]
[% SET custom_userlist=user.get_userlist UNLESS custom_userlist %]
[% UNLESS multiple %]
<div class="selectfree_ie" style="position:absolute;top:1px;left:1px;z-index:20;overflow:hidden">
<input
name="[% name | html %]"
value="[% value | html %]"
autocomplete="off"
[% IF disabled %] disabled="[% disabled | html %]" [% END %]
[% IF accesskey %] accesskey="[% accesskey | html %]" [% END %]
id="[% id | html %]"
onchange="menuforusers_tocombo('[% id | js %]', 0); [% onchange | html %]" />[% IF multiple %]<img style="margin-bottom:-4px" src="images/dn.gif" alt="dropdown list" title="dropdown list" onclick="menuforusers_showmulti('[% id | js %]')" />[% END %]
[%# http://anton-egorov.blogspot.com/2007/11/ie-select-z-index.html %]
<!--[if lte IE 6.5]><iframe style="width: 800px; height: 200px;"></iframe><![endif]-->
</div>
<select style="top: 0; left: 0; position: relative; z-index: 10"
id="[% id | html %]_s"
[% IF disabled %] disabled="[% disabled | html %]" [% END %]
onchange="menuforusers_fromcombo('[% id | js %]', 0)"
>
[% IF emptyok %]<option value=""></option>[% END %]
[% FOREACH tmpuser = custom_userlist %]
<option value="[% tmpuser.login | html %]">[% tmpuser.identity | html %]</option>
[% END %]
</select>
[% ELSE %]
<span style="border: 1px solid gray; display: inline-block">
<input
name="[% name | html %]"
value="[% value | html %]"
[% IF disabled %] disabled="[% disabled | html %]" [% END %]
[% IF accesskey %] accesskey="[% accesskey | html %]" [% END %]
id="[% id | html %]" style="height: 19px; padding: 0; margin: 0"
onchange="menuforusers_tocombo('[% id | js %]', 1); [% onchange | html %]" /><img id="[% id | html %]_b" style="margin-bottom:-4px" src="images/dn.gif" alt="dropdown list" title="dropdown list" />
<div class="selectfree_ie" style="position:relative:z-index:30;overflow:hidden">
<select id="[% id | html %]_s"
[% IF disabled %] disabled="[% disabled | html %]" [% END %]
onchange="menuforusers_fromcombo('[% id | js %]', 1)" multiple="multiple" size="[% custom_userlist.size+emptyok < 4 ? custom_userlist.size+emptyok : 4 %]"
style="position:absolute;visibility:hidden;z-index:30">
[% IF emptyok %]<option value=""></option>[% END %]
[% FOREACH tmpuser = custom_userlist %]
<option value="[% tmpuser.login | html %]">[% tmpuser.identity | html %]</option>
[% END %]
</select>
</div>
</span>
[% END %]
id="[% id | html %]" />
<script language="JavaScript">
addListener(window, "load", function() { menuforusers_initcombo("[% id | js %]", [% multiple ? 1 : 0 %]) }, null);
addListener(window, 'load', function() {
[% cl = []; FOREACH tmpuser = custom_userlist; cl.push([ tmpuser.real_name, tmpuser.email ]); END %]
var emptyOptions = [% json(cl) %];
new SimpleAutocomplete("[% id | js %]",
function(h) { userAutocomplete(h, emptyOptions); },
[% multiple ? '","' : 'null' %], null, null, null, true);
});
</script>
</div>

View File

@ -24,10 +24,10 @@
%]
[% param_descs = {
usemenuforusers => "If this option is set, $terms.Bugzilla will offer you a list " _
"to select from (instead of a text entry field) where a user " _
"needs to be selected. This option should not be enabled on " _
"sites where there are a large number of users.",
usemenuforusers => "If this option is set, a selectbox with all registered users will be shown " _
"always instead of a text entry field.<br /><span style='color:red'>WARNING:</span> " _
"As $terms.Bugzilla now has an autocomplete feature for users, you most probably " _
"don't need this option and you definitely should not turn it on sites with more than 10 users.",
maxusermatches => "Search for no more than this many matches.<br> " _
"If set to '1', no users will be displayed on ambiguous matches. " _

View File

@ -37,9 +37,6 @@
# atomlink: Atom link URL, May contain HTML
#%]
[% SET svn_rev = "$Revision$" %]
[% SET svn_rev = svn_rev.split(' ').1 %]
[% IF message %]
[% PROCESS global/messages.html.tmpl %]
[% END %]
@ -83,7 +80,7 @@
[% IF user.settings.skin.value != 'standard' %]
[% user_skin = user.settings.skin.value %]
[% END %]
[% style_urls.unshift('skins/standard/global.css?rev=' _ svn_rev) %]
[% style_urls.unshift('skins/standard/global.css') %]
[%# CSS cascade, part 1: Standard Bugzilla stylesheet set (persistent).
# Always present.
@ -195,8 +192,9 @@
type="text/css" />
<![endif]-->
<script src="js/util.js?rev=[% svn_rev %]" type="text/javascript"></script>
<script src="js/global.js?rev=[% svn_rev %]" type="text/javascript"></script>
<script src="js/util.js" type="text/javascript"></script>
<script src="js/global.js" type="text/javascript"></script>
<script src="js/hinter.js" type="text/javascript"></script>
<script type="text/javascript">
<!--
[%# The language selector needs javascript to set its cookie,

View File

@ -67,13 +67,18 @@
<input
name="[% name FILTER html %]"
value="[% value FILTER html %]"
autocomplete="off"
[% IF tabindex %] tabindex="[% tabindex FILTER html %]" [% END %]
[% IF onchange %] onchange="[% onchange FILTER html %]" [% END %]
[% IF disabled %] disabled="[% disabled FILTER html %]" [% END %]
[% IF accesskey %] accesskey="[% accesskey FILTER html %]" [% END %]
[% IF size %] size="[% size FILTER html %]" [% END %]
[% IF id %] id="[% id FILTER html %]" [% END %]
>
[% IF id %] id="[% id FILTER html %]" [% ELSE %] id="[% name | html %]"[% END %]
/>
<script language="JavaScript">
addListener(window, 'load', function() {
[%# FIXME: remove hardcoded i18n message, also from js/field.js::userAutocomplete() %]
new SimpleAutocomplete("[% (id || name) | js %]", userAutocomplete, null, null, null, 'No users found', true);
});
</script>
[% END %]

27
xml.cgi
View File

@ -1,9 +1,12 @@
#!/usr/bin/perl -wT
# REST-XML RPC interface (input as query parameters, output as xml)
# REST-(XML/JSON) RPC interface (input as query parameters, output as xml or json)
# Catches all errors and reports them correctly in output!
# License: Dual-license GPL 3.0+ or MPL 1.1+
# Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
# USAGE: xml.cgi?method={{method}}&output=(xml|json)&...
# FIXME: rename to rest.cgi
use strict;
use lib qw(. lib);
@ -14,7 +17,7 @@ use Bugzilla::WebService::Server::XMLSimple;
my $cgi = Bugzilla->cgi;
my $args = $cgi->Vars;
my $args = { %{ $cgi->Vars } }; # throw away the tied hash
my $method = $args->{method};
sub addmsg
@ -115,9 +118,19 @@ else
}
}
# Send response
Bugzilla->send_header(-type => 'text/xml'.(Bugzilla->params->{utf8} ? '; charset=utf-8' : ''));
print '<?xml version="1.0"'.(Bugzilla->params->{utf8} ? ' encoding="UTF-8"' : '').' ?>';
print '<response>';
print xml_dump_simple($result);
print '</response>';
if (!$args->{output} || lc $args->{output} ne 'json')
{
# XML output format
Bugzilla->send_header(-type => 'text/xml'.(Bugzilla->params->{utf8} ? '; charset=utf-8' : ''));
print '<?xml version="1.0"'.(Bugzilla->params->{utf8} ? ' encoding="UTF-8"' : '').' ?>';
print '<response>';
print xml_dump_simple($result);
print '</response>';
}
else
{
# JSON output format
Bugzilla->send_header(-type => 'application/json'.(Bugzilla->params->{utf8} ? '; charset=utf-8' : ''));
print bz_encode_json($result);
}
}