diff options
Diffstat (limited to 'data/js')
-rw-r--r-- | data/js/dropdownsearch.js | 296 |
1 files changed, 165 insertions, 131 deletions
diff --git a/data/js/dropdownsearch.js b/data/js/dropdownsearch.js index 53d404dd..7d0f82ad 100644 --- a/data/js/dropdownsearch.js +++ b/data/js/dropdownsearch.js @@ -1,162 +1,196 @@ -function dsInit(obj, url, trfunc, serfunc, retfunc, parfunc) { - obj.setAttribute('autocomplete', 'off'); - obj.onkeydown = dsKeyDown; - obj.onblur = function() { setTimeout(function () { setClass(byId('ds_box'), 'hidden', true) }, 500) }; - obj.ds_returnFunc = retfunc; - obj.ds_trFunc = trfunc; - obj.ds_serFunc = serfunc; - obj.ds_parFunc = parfunc; - obj.ds_searchURL = url; - obj.ds_selectedId = 0; - obj.ds_dosearch = null; - if(!byId('ds_box')) - addBody(tag('div', {id: 'ds_box', 'class':'hidden'}, tag('b', mt('_js_loading')))); -} - -function dsKeyDown(ev) { - var c = document.layers ? ev.which : document.all ? event.keyCode : ev.keyCode; - var obj = this; - - if(c == 9) // tab - return true; - - // do some processing when the enter key has been pressed - if(c == 13) { - var frm = obj; - while(frm && frm.nodeName.toLowerCase() != 'form') - frm = frm.parentNode; - if(frm) { - var oldsubmit = frm.onsubmit; - frm.onsubmit = function() { return false }; - setTimeout(function() { frm.onsubmit = oldsubmit }, 100); +/* Interactive drop-down search widget. Usage: + * + * dsInit(obj, url, trfunc, serfunc, retfunc); + * + * obj: An <input type="text"> 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 + * <item id="something" ..>..</item> + * element for each result. + * + * trfunc(item, tr): Function that is given an <item> object given by the XML + * document and an empty <tr> 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. + */ +(function(){ + var boxobj; + + function box() { + if(!boxobj) { + boxobj = tag('div', {id: 'ds_box', 'class':'hidden'}, tag('b', mt('_js_loading'))); + addBody(boxobj); } + return boxobj; + } + + function init(obj, url, trfunc, serfunc, retfunc) { + obj.setAttribute('autocomplete', 'off'); + obj.onkeydown = keydown; + 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; + } + 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; i<l.length; i++) + setClass(l[i], 'selected', id && l[i].id == 'ds_box_'+id); + } + + function setvalue(obj) { if(obj.ds_selectedId != 0) obj.value = obj.ds_serFunc(byId('ds_box_'+obj.ds_selectedId).ds_itemData, obj); if(obj.ds_returnFunc) obj.ds_returnFunc(obj); - setClass(byId('ds_box'), 'hidden', true); - setContent(byId('ds_box'), tag('b', mt('_js_loading'))); - obj.ds_selectedId = 0; + setClass(box(), 'hidden', true); + setContent(box(), tag('b', mt('_js_loading'))); + setselected(obj, 0); if(obj.ds_dosearch) { clearTimeout(obj.ds_dosearch); obj.ds_dosearch = null; } + } + function enter(obj) { + // Make sure the form doesn't submit when enter is pressed. + // This solution is a hack, but it's simple and reliable. + var frm = obj; + while(frm && frm.nodeName.toLowerCase() != 'form') + frm = frm.parentNode; + if(frm) { + var oldsubmit = frm.onsubmit; + frm.onsubmit = function() { return false }; + setTimeout(function() { frm.onsubmit = oldsubmit }, 100); + } + + setvalue(obj); return false; } - // process up/down keys - if(c == 38 || c == 40) { - var l = byName(byId('ds_box'), 'tr'); + function updown(obj, up) { + var i, sel, l = byName(box(), 'tr'); if(l.length < 1) return true; - // get new selected id - if(obj.ds_selectedId == 0) { - if(c == 38) // up - obj.ds_selectedId = l[l.length-1].id.substr(7); - else - obj.ds_selectedId = l[0].id.substr(7); - } else { - var sel = null; - for(var i=0; i<l.length; i++) - if(l[i].id == 'ds_box_'+obj.ds_selectedId) { - if(c == 38) // up - sel = i>0 ? l[i-1] : l[l.length-1]; - else - sel = l[i+1] ? l[i+1] : l[0]; - } - obj.ds_selectedId = sel.id.substr(7); - } + if(obj.ds_selectedId == 0) + sel = up ? l.length-1 : 0; + else + for(i=0; i<l.length; i++) + if(l[i].id == 'ds_box_'+obj.ds_selectedId) + sel = up ? (i>0 ? i-1 : l.length-1) : (l[i+1] ? i+1 : 0); - // set selected class - for(var i=0; i<l.length; i++) - setClass(l[i], 'selected', l[i].id == 'ds_box_'+obj.ds_selectedId); - return true; + setselected(obj, l[sel].id.substr(7)); + return false; } - // perform search after a timeout - if(obj.ds_dosearch) - clearTimeout(obj.ds_dosearch); - obj.ds_dosearch = setTimeout(function() { - dsSearch(obj); - }, 500); + function keydown(ev) { + var c = document.layers ? ev.which : document.all ? event.keyCode : ev.keyCode; + var obj = this; - return true; -} + if(c == 9) // tab + return true; -function dsSearch(obj) { - var box = byId('ds_box'); - var val = obj.ds_parFunc ? obj.ds_parFunc(obj.value) : obj.value; + if(c == 13) // enter + return enter(obj); - clearTimeout(obj.ds_dosearch); - obj.ds_dosearch = null; + if(c == 38 || c == 40) // up / down + return updown(obj, c == 38); - // hide the ds_box div - if(val.length < 2) { - setClass(box, 'hidden', true); - setContent(box, tag('b', mt('_js_loading'))); - obj.ds_selectedId = 0; - return; + // perform search after a timeout + if(obj.ds_dosearch) + clearTimeout(obj.ds_dosearch); + obj.ds_dosearch = setTimeout(function() { + search(obj); + }, 500); + + return true; } - // position the div - var ddx=0; - var ddy=obj.offsetHeight; - var o = obj; - do { - ddx += o.offsetLeft; - ddy += o.offsetTop; - } while(o = o.offsetParent); - - box.style.position = 'absolute'; - box.style.left = ddx+'px'; - box.style.top = ddy+'px'; - box.style.width = obj.offsetWidth+'px'; - setClass(box, 'hidden', false); - - // perform search - ajax(obj.ds_searchURL + encodeURIComponent(val), function(hr) { - dsResults(hr, obj); - }); -} - -function dsResults(hr, obj) { - var lst = hr.responseXML.getElementsByTagName('item'); - var box = byId('ds_box'); - if(lst.length < 1) { - setContent(box, tag('b', mt('_js_ds_noresults'))); - obj.selectedId = 0; - return; + 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', mt('_js_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) }); } - var tb = tag('tbody', null); - for(var i=0; i<lst.length; i++) { - var id = lst[i].getAttribute('id'); - var tr = tag('tr', {id: 'ds_box_'+id, ds_itemData: lst[i]} ); - setClass(tr, 'selected', obj.selectedId == id); - - tr.onmouseover = function() { - obj.ds_selectedId = this.id.substr(7); - var l = byName(box, 'tr'); - for(var j=0; j<l.length; j++) - setClass(l[j], 'selected', l[j].id == 'ds_box_'+obj.ds_selectedId); - }; - tr.onmousedown = function() { - obj.value = obj.ds_serFunc(this.ds_itemData, obj); - if(obj.ds_returnFunc) - obj.ds_returnFunc(); - setClass(box, 'hidden', true); - obj.ds_selectedId = 0; - }; - - obj.ds_trFunc(lst[i], tr); - tb.appendChild(tr); + function results(hr, obj) { + var lst = hr.responseXML.getElementsByTagName('item'); + var b = box(); + if(lst.length < 1) { + setContent(b, tag('b', mt('_js_ds_noresults'))); + setselected(obj, 0); + return; + } + + var tb = tag('tbody', null); + for(var i=0; i<lst.length; i++) { + var id = lst[i].getAttribute('id'); + var tr = tag('tr', {id: 'ds_box_'+id, ds_itemData: lst[i]} ); + + tr.onmouseover = function() { setselected(obj, this.id.substr(7)) }; + tr.onmousedown = function() { setselected(obj, this.id.substr(7)); setvalue(obj) }; + + obj.ds_trFunc(lst[i], tr); + tb.appendChild(tr); + } + setContent(b, tag('table', tb)); + setselected(obj, obj.ds_selectedId != 0 && !byId('ds_box_'+obj.ds_selectedId) ? 0 : obj.ds_selectedId); } - setContent(box, tag('table', tb)); - if(obj.ds_selectedId != 0 && !byId('ds_box_'+obj.ds_selectedId)) - obj.ds_selectedId = 0; -} + window.dsInit = init; +})(); |