seitime-frappe/js/widgets/autosuggest.js
2011-06-08 14:24:18 +05:30

562 lines
No EOL
13 KiB
JavaScript

/* adapted from: Timothy Groves - http://www.brandspankingnew.net */
var cur_autosug;
function hide_autosuggest() { if(cur_autosug)cur_autosug.clearSuggestions(); }
function AutoSuggest(id, param) {
this.fld = $i(id);
if (!this.fld) {return 0; alert('AutoSuggest: No ID');}
// init variables
this.init();
// parameters object
this.oP = param ? param : {};
// defaults
var k, def = {
minchars:1, meth:"get", varname:"input", className:"autosuggest", timeout:4000
,delay:1000, offsety:-5, shownoresults: true, noresults: "No results!", maxheight: 250
,cache: false, maxentries: 25, fixed_options: false, xdelta: 0, ydelta: 5
}
for (k in def)
{
if (typeof(this.oP[k]) != typeof(def[k]))
this.oP[k] = def[k];
}
// set keyup handler for field
// and prevent autocomplete from client
var p = this;
// NOTE: not using addEventListener because UpArrow fired twice in Safari
//DOM.addEvent( this.fld, 'keyup', function(ev){ return me.onKeyPress(ev); } );
this.fld.onkeypress = function(ev){ if(!(selector && selector.display)) return p.onKeyPress(ev); };
this.fld.onkeyup = function(ev){ if(!(selector && selector.display)) return p.onKeyUp(ev); };
this.fld.setAttribute("autocomplete","off");
};
AutoSuggest.prototype.init = function() {
this.sInp = "";
this.nInpC = 0;
this.aSug = [];
this.iHigh = 0;
}
AutoSuggest.prototype.onKeyPress = function(ev)
{
var key = (window.event) ? window.event.keyCode : ev.keyCode;
//var ev = (window.event) ? window.event : ev;
//var key = ev.keyCode ? ev.keyCode : ev.charCode;
// set responses to keydown events in the field
// this allows the user to use the arrow keys to scroll through the results
// ESCAPE clears the list
// TAB sets the current highlighted value
//
var RETURN = 13;
var TAB = 9;
var ESC = 27;
var bubble = 1;
switch(key)
{
case TAB:
this.setHighlightedValue();
bubble = 0;
break;
case RETURN:
this.setHighlightedValue();
bubble = 0;
break;
case ESC:
this.clearSuggestions();
break;
}
return bubble;
}
AutoSuggest.prototype.onKeyUp = function(ev)
{
var key = (window.event) ? window.event.keyCode : ev.keyCode;
//var ev = (window.event) ? window.event : ev;
//var key = ev.keyCode ? ev.keyCode : ev.charCode;
var ARRUP = 38; var ARRDN = 40;
var bubble = 1;
switch(key) {
case ARRUP:
this.changeHighlight(key);
bubble = 0;
break;
case ARRDN:
this.changeHighlight(key);
bubble = 0;
break;
default:
if(key!=13) {
if(this.oP.fixed_options)
this.find_nearest(key);
else
this.getSuggestions(this.fld.value);
}
}
return bubble;
}
AutoSuggest.prototype.clear_user_inp = function() {
this.user_inp = '';
}
AutoSuggest.prototype.find_nearest = function (key) {
var list = this.ul;
var same_key = 0;
// make the list
if (!list) {
if(this.aSug) {
this.createList(this.aSug);
} if(this.aSug[0].value.substr(0,this.user_inp.length).toLowerCase()==String.fromCharCode(key)) {
this.resetTimeout();
return;
}
}
// values for multiple keystrokes
if((this.user_inp.length==1) && this.user_inp == String.fromCharCode(key).toLowerCase()) {
same_key = 1;
} else {
this.user_inp += String.fromCharCode(key).toLowerCase();
}
// clear user keys
window.clearTimeout(this.clear_timer);
// loop over the next after the current
var st = this.iHigh;
// continuation of typing, also check the current value
if(!same_key) st--;
for(var i = st; i<this.aSug.length; i++) {
if(this.aSug[i].value.substr(0,this.user_inp.length).toLowerCase()==this.user_inp) {
this.setHighlight(i+1);
this.resetTimeout();
return;
}
}
this.clear_timer = window.setTimeout('if(cur_autosug)cur_autosug.clear_user_inp()', 3000);
// begin at the top
for(var i = 0; i<st; i++) {
if(this.aSug[i].value.substr(0,this.user_inp.length).toLowerCase()==this.user_inp) {
this.setHighlight(i+1);
this.resetTimeout();
return;
}
}
}
AutoSuggest.prototype.getSuggestions = function (val)
{
// if input stays the same, do nothing
if (val == this.sInp) return 0;
// kill list
if(this.body && this.body.parentNode)
this.body.parentNode.removeChild(this.body);
this.sInp = val;
// input length is less than the min required to trigger a request
// do nothing
if (val.length < this.oP.minchars)
{
this.aSug = [];
this.nInpC = val.length;
return 0;
}
var ol = this.nInpC; // old length
this.nInpC = val.length ? val.length : 0;
// if caching enabled, and user is typing (ie. length of input is increasing)
// filter results out of aSuggestions from last request
var l = this.aSug.length;
if (this.nInpC > ol && l && l<this.oP.maxentries && this.oP.cache)
{
var arr = [];
for (var i=0;i<l;i++)
{
if (this.aSug[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
arr.push( this.aSug[i] );
}
this.aSug = arr;
this.createList(this.aSug);
return false;
}
else
// do new request
{
var me = this;
var input = this.sInp;
clearTimeout(this.ajID);
this.ajID = setTimeout( function() { me.doAjaxRequest(input) }, this.oP.delay );
}
return false;
};
AutoSuggest.prototype.doAjaxRequest = function (input)
{
// check that saved input is still the value of the field
if (input != this.fld.value)
return false;
var me = this;
var q = '';
this.oP.link_field.set_get_query();
if(this.oP.link_field.get_query) {
if(cur_frm)var doc = locals[cur_frm.doctype][cur_frm.docname];
q = this.oP.link_field.get_query(doc, this.oP.link_field.doctype, this.oP.link_field.docname);
}
// do ajax request
this.fld.old_bg = this.fld.style.backgroundColor;
$y(this.fld, {backgroundColor:'#FFC'});
$c('webnotes.widgets.search.search_link', args={
'txt': this.fld.value,
'dt':this.oP.link_field.df.options,
'query':q }
, function(r,rt) {
$y(me.fld, {backgroundColor:(me.fld.old_bg ? me.fld.old_bg : '#FFF')});
me.setSuggestions(r, rt, input);
});
return;
};
AutoSuggest.prototype.setSuggestions = function (r, rt, input)
{
// if field input no longer matches what was passed to the request
// don't show the suggestions
if (input != this.fld.value)
return false;
this.aSug = [];
if (this.oP.json) {
//var jsondata = eval('(' + req.responseText + ')');
var jsondata = eval('(' + rt + ')');
if(jsondata) {
for (var i=0;i<jsondata.results.length;i++) {
this.aSug.push( { 'id':jsondata.results[i].id, 'value':jsondata.results[i].value, 'info':jsondata.results[i].info } );
}
}
}
this.createList(this.aSug);
};
AutoSuggest.prototype.createList = function(arr) {
if(cur_autosug && cur_autosug!= this)
cur_autosug.clearSuggestions();
this.aSug = arr;
this.user_inp = '';
var me = this;
var pos = objpos(this.fld); pos.y += this.oP.ydelta; pos.x += this.oP.xdelta;
if(pos.x <= 0 || pos.y <= 0) return; // field hidden
// get rid of old list and clear the list removal timeout
if(this.body && this.body.parentNode)
this.body.parentNode.removeChild(this.body);
this.killTimeout();
// if no results, and shownoresults is false, do nothing
if (arr.length == 0 && !this.oP.shownoresults)
return false;
// create holding div
var div = $ce("div", {className:this.oP.className});
top_index++;
div.style.zIndex = 1100;
div.isactive = 1;
// create and populate ul
this.ul = $ce("ul", {id:"as_ul"}); var ul = this.ul;
// loop throught arr of suggestionscreating an LI element for each suggestion
for (var i=0;i<arr.length;i++) {
// format output with the input enclosed in a EM element
// (as HTML, not DOM)
//
var val = arr[i].value;
if(this.oP.fixed_options) {
var output = val;
} else {
var st = val.toLowerCase().indexOf( this.sInp.toLowerCase() );
var output = val.substring(0,st) + "<em>" + val.substring(st, st+this.sInp.length) + "</em>" + val.substring(st+this.sInp.length);
}
var span = $ce("span", {}, output, true);
span.isactive = 1;
if (arr[i].info != "")
{
var small = $ce("small", {}, arr[i].info);
span.appendChild(small);
small.isactive = 1
}
var a = $a(null, "a");
a.appendChild(span);
a.name = i+1;
a.onclick = function (e) {
me.setHighlightedValue();
};
a.onmouseover = function () { me.setHighlight(this.name); };
a.isactive = 1;
var li = $ce( "li", {}, a );
// empty option
if(!val) {
$y(span,{height:'12px'});
}
ul.appendChild( li );
}
// no results
//
if (arr.length == 0 && this.oP.shownoresults) {
var li = $ce( "li", {className:"as_warning"}, this.oP.noresults);
ul.appendChild( li );
}
div.appendChild( ul );
// get position of target textfield
// set width of holding div to width of field
//
var mywid = cint(this.fld.offsetWidth);
if(this.oP.fixed_options) {
mywid += 20;
}
if(cint(mywid) < 100) mywid = 100;
var left = pos.x - ((mywid - this.fld.offsetWidth)/2);
if(left<0) {
mywid = mywid + (left/2); left = 0;
}
div.style.left = left + "px";
div.style.top = ( pos.y + this.fld.offsetHeight + this.oP.offsety ) + "px";
div.style.width = mywid + 'px';
// set mouseover functions for div
// when mouse me leaves div, set a timeout to remove the list after an interval
// when mouse enters div, kill the timeout so the list won't be removed
//
div.onmouseover = function(){ me.killTimeout() };
div.onmouseout = function(){ me.resetTimeout() };
// add DIV to document
//
//document.getElementsByTagName("body")[0].appendChild(div);
popup_cont.appendChild(div);
//height
if(cint(div.clientHeight) >= this.oP.maxheight) {
div.original_height = cint(div.clientHeight);
$y(div,{height: this.oP.maxheight+'px', overflowY:'auto'});
div.scrollbars = true;
}
this.body = div;
if(isIE) {
$y(div,{border:'1px solid #444'});
}
// currently no item is highlighted
//
// if value, then hilight value
this.iHigh = 0;
if(!this.iHigh)
this.changeHighlight(40); // hilight first
// remove list after an interval
//
this.resetTimeout();
};
AutoSuggest.prototype.changeHighlight = function(key)
{
var list = this.ul;
if (!list) {
if(this.aSug)
this.createList(this.aSug);
return false;
}
var n;
if (key == 40)
n = this.iHigh + 1;
else if (key == 38)
n = this.iHigh - 1;
if (n > list.childNodes.length)
n = list.childNodes.length;
if (n < 1)
n = 1;
this.setHighlight(n);
};
AutoSuggest.prototype.setHighlight = function(n)
{
this.resetTimeout();
var list = this.ul;
if (!list)
return false;
if (this.iHigh > 0)
this.clearHighlight();
this.iHigh = Number(n);
var ele = list.childNodes[this.iHigh-1];
ele.className = "as_highlight";
// set scroll
if(this.body.scrollbars) {
var cur_y = 0;
for(var i=0; i<this.iHigh-1; i++)
cur_y += (isIE ? list.childNodes[i].offsetHeight : list.childNodes[i].clientHeight);
// scroll up
if(cur_y < this.body.scrollTop)
this.body.scrollTop = cur_y;
// scroll down
ff_delta = (isFF ? cint(this.iHigh/2) : 0);
var h = (isIE ? ele.offsetHeight : ele.clientHeight);
if(cur_y >= (this.body.scrollTop + this.oP.maxheight - h))
this.body.scrollTop = cur_y - this.oP.maxheight + h + ff_delta;
}
// no values returned
if(!this.aSug[this.iHigh-1]) return;
};
AutoSuggest.prototype.clearHighlight = function()
{
var list = this.ul;
if (!list)
return false;
if (this.iHigh > 0) {
list.childNodes[this.iHigh-1].className = "";
this.iHigh = 0;
}
};
AutoSuggest.prototype.setHighlightedValue = function ()
{
if (this.iHigh) {
this.sInp = this.aSug[ this.iHigh-1 ].value;
// set the value
if(this.set_input_value) {
this.set_input_value(this.sInp);
} else {
this.fld.value = this.sInp;
}
this.clearSuggestions();
this.killTimeout();
if(this.fld.onchange){
cur_autosug = null;
this.fld.onchange();
}
}
};
AutoSuggest.prototype.killTimeout = function() {
cur_autosug = this;
clearTimeout(this.toID);
clearTimeout(this.clear_timer);
};
AutoSuggest.prototype.resetTimeout = function() {
cur_autosug = this;
clearTimeout(this.toID);
clearTimeout(this.clear_timer);
this.toID = setTimeout(function () { if(cur_autosug)cur_autosug.clearSuggestions(1); }, this.oP.timeout);
};
AutoSuggest.prototype.clearSuggestions = function (from_timeout) {
this.killTimeout();
cur_autosug = null;
var me = this;
if (this.body) { $dh(this.body); delete this.body; }
if(!this.ul) return;
if(this.ul)
delete this.ul;
this.iHigh = 0;
// accept the value
if(from_timeout && this.fld.field_object && !this.oP.fixed_options) {
// call onchange
// we do not call onchange from the link field if autosuggest options are open
if(this.fld.onchange)this.fld.onchange();
}
}
/* create element */
$ce = function ( type, attr, cont, html )
{
var ne = document.createElement( type );
if (!ne) return 0;
for (var a in attr) ne[a] = attr[a];
var t = typeof(cont);
if (t == "string" && !html) ne.appendChild( document.createTextNode(cont) );
else if (t == "string" && html) ne.innerHTML = cont;
else if (t == "object") ne.appendChild( cont );
return ne;
};