You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /* adapted from: Timothy Groves - http://www.brandspankingnew.net */
  2. var cur_autosug;
  3. function hide_autosuggest() { if(cur_autosug)cur_autosug.clearSuggestions(); }
  4. function AutoSuggest(id, param) {
  5. this.fld = $i(id);
  6. if (!this.fld) {return 0; alert('AutoSuggest: No ID');}
  7. // init variables
  8. this.init();
  9. // parameters object
  10. this.oP = param ? param : {};
  11. // defaults
  12. var k, def = {
  13. minchars:1, meth:"get", varname:"input", className:"autosuggest", timeout:4000
  14. ,delay:1000, offsety:-5, shownoresults: true, noresults: "No results!", maxheight: 250
  15. ,cache: false, maxentries: 25, fixed_options: false, xdelta: 0, ydelta: 5
  16. }
  17. for (k in def)
  18. {
  19. if (typeof(this.oP[k]) != typeof(def[k]))
  20. this.oP[k] = def[k];
  21. }
  22. // set keyup handler for field
  23. // and prevent autocomplete from client
  24. var p = this;
  25. // NOTE: not using addEventListener because UpArrow fired twice in Safari
  26. //DOM.addEvent( this.fld, 'keyup', function(ev){ return me.onKeyPress(ev); } );
  27. this.fld.onkeypress = function(ev){ if(!(selector && selector.display)) return p.onKeyPress(ev); };
  28. this.fld.onkeyup = function(ev){ if(!(selector && selector.display)) return p.onKeyUp(ev); };
  29. this.fld.setAttribute("autocomplete","off");
  30. };
  31. AutoSuggest.prototype.init = function() {
  32. this.sInp = "";
  33. this.nInpC = 0;
  34. this.aSug = [];
  35. this.iHigh = 0;
  36. }
  37. AutoSuggest.prototype.onKeyPress = function(ev)
  38. {
  39. var key = (window.event) ? window.event.keyCode : ev.keyCode;
  40. //var ev = (window.event) ? window.event : ev;
  41. //var key = ev.keyCode ? ev.keyCode : ev.charCode;
  42. // set responses to keydown events in the field
  43. // this allows the user to use the arrow keys to scroll through the results
  44. // ESCAPE clears the list
  45. // TAB sets the current highlighted value
  46. //
  47. var RETURN = 13;
  48. var TAB = 9;
  49. var ESC = 27;
  50. var bubble = 1;
  51. switch(key)
  52. {
  53. case TAB:
  54. this.setHighlightedValue();
  55. bubble = 0;
  56. break;
  57. case RETURN:
  58. this.setHighlightedValue();
  59. bubble = 0;
  60. break;
  61. case ESC:
  62. this.clearSuggestions();
  63. break;
  64. }
  65. return bubble;
  66. }
  67. AutoSuggest.prototype.onKeyUp = function(ev)
  68. {
  69. var key = (window.event) ? window.event.keyCode : ev.keyCode;
  70. //var ev = (window.event) ? window.event : ev;
  71. //var key = ev.keyCode ? ev.keyCode : ev.charCode;
  72. var ARRUP = 38; var ARRDN = 40;
  73. var bubble = 1;
  74. switch(key) {
  75. case ARRUP:
  76. this.changeHighlight(key);
  77. bubble = 0;
  78. break;
  79. case ARRDN:
  80. this.changeHighlight(key);
  81. bubble = 0;
  82. break;
  83. default:
  84. if(key!=13) {
  85. if(this.oP.fixed_options)
  86. this.find_nearest(key);
  87. else
  88. this.getSuggestions(this.fld.value);
  89. }
  90. }
  91. return bubble;
  92. }
  93. AutoSuggest.prototype.clear_user_inp = function() {
  94. this.user_inp = '';
  95. }
  96. AutoSuggest.prototype.find_nearest = function (key) {
  97. var list = this.ul;
  98. var same_key = 0;
  99. // make the list
  100. if (!list) {
  101. if(this.aSug) {
  102. this.createList(this.aSug);
  103. } if(this.aSug[0].value.substr(0,this.user_inp.length).toLowerCase()==String.fromCharCode(key)) {
  104. this.resetTimeout();
  105. return;
  106. }
  107. }
  108. // values for multiple keystrokes
  109. if((this.user_inp.length==1) && this.user_inp == String.fromCharCode(key).toLowerCase()) {
  110. same_key = 1;
  111. } else {
  112. this.user_inp += String.fromCharCode(key).toLowerCase();
  113. }
  114. // clear user keys
  115. window.clearTimeout(this.clear_timer);
  116. // loop over the next after the current
  117. var st = this.iHigh;
  118. // continuation of typing, also check the current value
  119. if(!same_key) st--;
  120. for(var i = st; i<this.aSug.length; i++) {
  121. if(this.aSug[i].value.substr(0,this.user_inp.length).toLowerCase()==this.user_inp) {
  122. this.setHighlight(i+1);
  123. this.resetTimeout();
  124. return;
  125. }
  126. }
  127. this.clear_timer = window.setTimeout('if(cur_autosug)cur_autosug.clear_user_inp()', 3000);
  128. // begin at the top
  129. for(var i = 0; i<st; i++) {
  130. if(this.aSug[i].value.substr(0,this.user_inp.length).toLowerCase()==this.user_inp) {
  131. this.setHighlight(i+1);
  132. this.resetTimeout();
  133. return;
  134. }
  135. }
  136. }
  137. AutoSuggest.prototype.getSuggestions = function (val)
  138. {
  139. // if input stays the same, do nothing
  140. if (val == this.sInp) return 0;
  141. // kill list
  142. if(this.body && this.body.parentNode)
  143. this.body.parentNode.removeChild(this.body);
  144. this.sInp = val;
  145. // input length is less than the min required to trigger a request
  146. // do nothing
  147. if (val.length < this.oP.minchars)
  148. {
  149. this.aSug = [];
  150. this.nInpC = val.length;
  151. return 0;
  152. }
  153. var ol = this.nInpC; // old length
  154. this.nInpC = val.length ? val.length : 0;
  155. // if caching enabled, and user is typing (ie. length of input is increasing)
  156. // filter results out of aSuggestions from last request
  157. var l = this.aSug.length;
  158. if (this.nInpC > ol && l && l<this.oP.maxentries && this.oP.cache)
  159. {
  160. var arr = [];
  161. for (var i=0;i<l;i++)
  162. {
  163. if (this.aSug[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
  164. arr.push( this.aSug[i] );
  165. }
  166. this.aSug = arr;
  167. this.createList(this.aSug);
  168. return false;
  169. }
  170. else
  171. // do new request
  172. {
  173. var me = this;
  174. var input = this.sInp;
  175. clearTimeout(this.ajID);
  176. this.ajID = setTimeout( function() { me.doAjaxRequest(input) }, this.oP.delay );
  177. }
  178. return false;
  179. };
  180. AutoSuggest.prototype.doAjaxRequest = function (input)
  181. {
  182. // check that saved input is still the value of the field
  183. if (input != this.fld.value)
  184. return false;
  185. var me = this;
  186. var q = '';
  187. this.oP.link_field.set_get_query();
  188. if(this.oP.link_field.get_query) {
  189. if(cur_frm)var doc = locals[cur_frm.doctype][cur_frm.docname];
  190. q = this.oP.link_field.get_query(doc, this.oP.link_field.doctype, this.oP.link_field.docname);
  191. }
  192. // do ajax request
  193. this.fld.old_bg = this.fld.style.backgroundColor;
  194. $y(this.fld, {backgroundColor:'#FFC'});
  195. $c('webnotes.widgets.search.search_link', args={
  196. 'txt': this.fld.value,
  197. 'dt':this.oP.link_field.df.options,
  198. 'query':q }
  199. , function(r,rt) {
  200. $y(me.fld, {backgroundColor:(me.fld.old_bg ? me.fld.old_bg : '#FFF')});
  201. me.setSuggestions(r, rt, input);
  202. });
  203. return;
  204. };
  205. AutoSuggest.prototype.setSuggestions = function (r, rt, input)
  206. {
  207. // if field input no longer matches what was passed to the request
  208. // don't show the suggestions
  209. if (input != this.fld.value)
  210. return false;
  211. this.aSug = [];
  212. if (this.oP.json) {
  213. //var jsondata = eval('(' + req.responseText + ')');
  214. var jsondata = eval('(' + rt + ')');
  215. if(jsondata) {
  216. for (var i=0;i<jsondata.results.length;i++) {
  217. this.aSug.push( { 'id':jsondata.results[i].id, 'value':jsondata.results[i].value, 'info':jsondata.results[i].info } );
  218. }
  219. }
  220. }
  221. this.createList(this.aSug);
  222. };
  223. AutoSuggest.prototype.createList = function(arr) {
  224. if(cur_autosug && cur_autosug!= this)
  225. cur_autosug.clearSuggestions();
  226. this.aSug = arr;
  227. this.user_inp = '';
  228. var me = this;
  229. var pos = objpos(this.fld); pos.y += this.oP.ydelta; pos.x += this.oP.xdelta;
  230. if(pos.x <= 0 || pos.y <= 0) return; // field hidden
  231. // get rid of old list and clear the list removal timeout
  232. if(this.body && this.body.parentNode)
  233. this.body.parentNode.removeChild(this.body);
  234. this.killTimeout();
  235. // if no results, and shownoresults is false, do nothing
  236. if (arr.length == 0 && !this.oP.shownoresults)
  237. return false;
  238. // create holding div
  239. var div = $ce("div", {className:this.oP.className});
  240. top_index++;
  241. div.style.zIndex = 1100;
  242. div.isactive = 1;
  243. // create and populate ul
  244. this.ul = $ce("ul", {id:"as_ul"}); var ul = this.ul;
  245. // loop throught arr of suggestionscreating an LI element for each suggestion
  246. for (var i=0;i<arr.length;i++) {
  247. // format output with the input enclosed in a EM element
  248. // (as HTML, not DOM)
  249. //
  250. var val = arr[i].value;
  251. if(this.oP.fixed_options) {
  252. var output = val;
  253. } else {
  254. var st = val.toLowerCase().indexOf( this.sInp.toLowerCase() );
  255. var output = val.substring(0,st) + "<em>" + val.substring(st, st+this.sInp.length) + "</em>" + val.substring(st+this.sInp.length);
  256. }
  257. var span = $ce("span", {}, output, true);
  258. span.isactive = 1;
  259. if (arr[i].info != "")
  260. {
  261. var small = $ce("small", {}, arr[i].info);
  262. span.appendChild(small);
  263. small.isactive = 1
  264. }
  265. var a = $a(null, "a");
  266. a.appendChild(span);
  267. a.name = i+1;
  268. a.onclick = function (e) {
  269. me.setHighlightedValue();
  270. };
  271. a.onmouseover = function () { me.setHighlight(this.name); };
  272. a.isactive = 1;
  273. var li = $ce( "li", {}, a );
  274. // empty option
  275. if(!val) {
  276. $y(span,{height:'12px'});
  277. }
  278. ul.appendChild( li );
  279. }
  280. // no results
  281. //
  282. if (arr.length == 0 && this.oP.shownoresults) {
  283. var li = $ce( "li", {className:"as_warning"}, this.oP.noresults);
  284. ul.appendChild( li );
  285. }
  286. div.appendChild( ul );
  287. // get position of target textfield
  288. // set width of holding div to width of field
  289. //
  290. var mywid = cint(this.fld.offsetWidth);
  291. if(this.oP.fixed_options) {
  292. mywid += 20;
  293. }
  294. if(cint(mywid) < 100) mywid = 100;
  295. var left = pos.x - ((mywid - this.fld.offsetWidth)/2);
  296. if(left<0) {
  297. mywid = mywid + (left/2); left = 0;
  298. }
  299. div.style.left = left + "px";
  300. div.style.top = ( pos.y + this.fld.offsetHeight + this.oP.offsety ) + "px";
  301. div.style.width = mywid + 'px';
  302. // set mouseover functions for div
  303. // when mouse me leaves div, set a timeout to remove the list after an interval
  304. // when mouse enters div, kill the timeout so the list won't be removed
  305. //
  306. div.onmouseover = function(){ me.killTimeout() };
  307. div.onmouseout = function(){ me.resetTimeout() };
  308. // add DIV to document
  309. //
  310. //document.getElementsByTagName("body")[0].appendChild(div);
  311. popup_cont.appendChild(div);
  312. //height
  313. if(cint(div.clientHeight) >= this.oP.maxheight) {
  314. div.original_height = cint(div.clientHeight);
  315. $y(div,{height: this.oP.maxheight+'px', overflowY:'auto'});
  316. div.scrollbars = true;
  317. }
  318. this.body = div;
  319. if(isIE) {
  320. $y(div,{border:'1px solid #444'});
  321. }
  322. // currently no item is highlighted
  323. //
  324. // if value, then hilight value
  325. this.iHigh = 0;
  326. if(!this.iHigh)
  327. this.changeHighlight(40); // hilight first
  328. // remove list after an interval
  329. //
  330. this.resetTimeout();
  331. };
  332. AutoSuggest.prototype.changeHighlight = function(key)
  333. {
  334. var list = this.ul;
  335. if (!list) {
  336. if(this.aSug)
  337. this.createList(this.aSug);
  338. return false;
  339. }
  340. var n;
  341. if (key == 40)
  342. n = this.iHigh + 1;
  343. else if (key == 38)
  344. n = this.iHigh - 1;
  345. if (n > list.childNodes.length)
  346. n = list.childNodes.length;
  347. if (n < 1)
  348. n = 1;
  349. this.setHighlight(n);
  350. };
  351. AutoSuggest.prototype.setHighlight = function(n)
  352. {
  353. this.resetTimeout();
  354. var list = this.ul;
  355. if (!list)
  356. return false;
  357. if (this.iHigh > 0)
  358. this.clearHighlight();
  359. this.iHigh = Number(n);
  360. var ele = list.childNodes[this.iHigh-1];
  361. ele.className = "as_highlight";
  362. // set scroll
  363. if(this.body.scrollbars) {
  364. var cur_y = 0;
  365. for(var i=0; i<this.iHigh-1; i++)
  366. cur_y += (isIE ? list.childNodes[i].offsetHeight : list.childNodes[i].clientHeight);
  367. // scroll up
  368. if(cur_y < this.body.scrollTop)
  369. this.body.scrollTop = cur_y;
  370. // scroll down
  371. ff_delta = (isFF ? cint(this.iHigh/2) : 0);
  372. var h = (isIE ? ele.offsetHeight : ele.clientHeight);
  373. if(cur_y >= (this.body.scrollTop + this.oP.maxheight - h))
  374. this.body.scrollTop = cur_y - this.oP.maxheight + h + ff_delta;
  375. }
  376. // no values returned
  377. if(!this.aSug[this.iHigh-1]) return;
  378. };
  379. AutoSuggest.prototype.clearHighlight = function()
  380. {
  381. var list = this.ul;
  382. if (!list)
  383. return false;
  384. if (this.iHigh > 0) {
  385. list.childNodes[this.iHigh-1].className = "";
  386. this.iHigh = 0;
  387. }
  388. };
  389. AutoSuggest.prototype.setHighlightedValue = function ()
  390. {
  391. if (this.iHigh) {
  392. this.sInp = this.aSug[ this.iHigh-1 ].value;
  393. // set the value
  394. if(this.set_input_value) {
  395. this.set_input_value(this.sInp);
  396. } else {
  397. this.fld.value = this.sInp;
  398. }
  399. this.clearSuggestions();
  400. this.killTimeout();
  401. if(this.fld.onchange){
  402. cur_autosug = null;
  403. this.fld.onchange();
  404. }
  405. }
  406. };
  407. AutoSuggest.prototype.killTimeout = function() {
  408. cur_autosug = this;
  409. clearTimeout(this.toID);
  410. clearTimeout(this.clear_timer);
  411. };
  412. AutoSuggest.prototype.resetTimeout = function() {
  413. cur_autosug = this;
  414. clearTimeout(this.toID);
  415. clearTimeout(this.clear_timer);
  416. this.toID = setTimeout(function () { if(cur_autosug)cur_autosug.clearSuggestions(1); }, this.oP.timeout);
  417. };
  418. AutoSuggest.prototype.clearSuggestions = function (from_timeout) {
  419. this.killTimeout();
  420. cur_autosug = null;
  421. var me = this;
  422. if (this.body) { $dh(this.body); delete this.body; }
  423. if(!this.ul) return;
  424. if(this.ul)
  425. delete this.ul;
  426. this.iHigh = 0;
  427. // accept the value
  428. if(from_timeout && this.fld.field_object && !this.oP.fixed_options) {
  429. // call onchange
  430. // we do not call onchange from the link field if autosuggest options are open
  431. if(this.fld.onchange)this.fld.onchange();
  432. }
  433. }
  434. /* create element */
  435. $ce = function ( type, attr, cont, html )
  436. {
  437. var ne = document.createElement( type );
  438. if (!ne) return 0;
  439. for (var a in attr) ne[a] = attr[a];
  440. var t = typeof(cont);
  441. if (t == "string" && !html) ne.appendChild( document.createTextNode(cont) );
  442. else if (t == "string" && html) ne.innerHTML = cont;
  443. else if (t == "object") ne.appendChild( cont );
  444. return ne;
  445. };