/* Interactive drop-down search widget. Usage:
*
* dsInit(obj, url, trfunc, serfunc, retfunc);
*
* obj: An object.
*
* url: The base URL of the XML API, e.g. "/xml/tags.xml?q=", the search query is appended to this URL.
* The resource at the URL should return an XML document with a
* ..
* element for each result.
*
* trfunc(item, tr): Function that is given an object given by the XML
* document and an empty
object. The function should format the data of
* the item to be shown in the tr.
*
* serfunc(item, obj): Called whenever a user selects an item from the search
* results. Should return a string, which will be used as the new value of the
* input object.
*
* retfunc(obj): Called whenever the user selects an item from the search
* results (after setfunc()) or when enter is pressed (even if nothing is
* selected).
*
* setfunc and retfunc can be null.
*
* TODO: Some users of this widget consider serfunc() as their final "apply
* this selection" function, whereas others use retfunc() for this. Might be
* worth investigating whether the additional flexibility offered by
* retfunc() is actually necessary, and remove the callback if not.
*/
var boxobj;
function box() {
if(!boxobj) {
boxobj = tag('div', {id: 'ds_box', 'class':'hidden'}, tag('b', 'Loading...'));
addBody(boxobj);
}
return boxobj;
}
function init(obj, url, trfunc, serfunc, retfunc) {
obj.setAttribute('autocomplete', 'off');
obj.onkeydown = keydown;
obj.onclick = obj.onchange = obj.oninput = function() { return textchanged(obj); };
obj.onblur = blur;
obj.ds_returnFunc = retfunc;
obj.ds_trFunc = trfunc;
obj.ds_serFunc = serfunc;
obj.ds_searchURL = url;
obj.ds_selectedId = 0;
obj.ds_dosearch = null;
obj.ds_lastVal = obj.value;
}
function blur() {
setTimeout(function () {
setClass(box(), 'hidden', true)
}, 500)
}
function setselected(obj, id) {
obj.ds_selectedId = id;
var l = byName(box(), 'tr');
for(var i=0; i0 ? i-1 : l.length-1) : (l[i+1] ? i+1 : 0);
setselected(obj, l[sel].id.substr(7));
return false;
}
function textchanged(obj) {
// Ignore this event if the text hasn't actually changed.
if(obj.ds_lastVal == obj.value)
return true;
obj.ds_lastVal = obj.value;
// perform search after a timeout
if(obj.ds_dosearch)
clearTimeout(obj.ds_dosearch);
obj.ds_dosearch = setTimeout(function() {
search(obj);
}, 500);
return true;
}
function keydown(ev) {
var c = document.layers ? ev.which : document.all ? event.keyCode : ev.keyCode;
var obj = this;
if(c == 9) // tab
return true;
if(c == 13) // enter
return enter(obj);
if(c == 38 || c == 40) // up / down
return updown(obj, c == 38);
return textchanged(obj);
}
function search(obj) {
var b = box();
var val = obj.value;
clearTimeout(obj.ds_dosearch);
obj.ds_dosearch = null;
// hide the ds_box div if the search string is too short
if(val.length < 2) {
setClass(b, 'hidden', true);
setContent(b, tag('b', 'Loading...'));
setselected(obj, 0);
return;
}
// position the div
var ddx=0;
var ddy=obj.offsetHeight;
var o = obj;
do {
ddx += o.offsetLeft;
ddy += o.offsetTop;
} while(o = o.offsetParent);
b.style.position = 'absolute';
b.style.left = ddx+'px';
b.style.top = ddy+'px';
b.style.width = obj.offsetWidth+'px';
setClass(b, 'hidden', false);
// perform search
ajax(obj.ds_searchURL + encodeURIComponent(val), function(hr) { results(hr, obj) });
}
function results(hr, obj) {
var lst = hr.responseXML.getElementsByTagName('item');
var b = box();
if(lst.length < 1) {
setContent(b, tag('b', 'No results...'));
setselected(obj, 0);
return;
}
var tb = tag('tbody', null);
for(var i=0; i