summaryrefslogtreecommitdiff
path: root/data/js
diff options
context:
space:
mode:
Diffstat (limited to 'data/js')
-rw-r--r--data/js/dropdownsearch.js296
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;
+})();