Нет описания

jquery.editable-select.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /**
  2. * Copyright (c) 2009 Anders Ekdahl (http://coffeescripter.com/)
  3. * var select = $('.editable-select:first');
  4. * var instances = select.editableSelectInstances();
  5. * instances[0].addOption('Germany', 'value added programmatically');
  6. *
  7. * Version: 1.3.2
  8. * yingxian modify
  9. * Demo and documentation: http://coffeescripter.com/code/editable-select/
  10. */
  11. (function($) {
  12. var instances = [];
  13. $.fn.editableSelect = function(options) {
  14. var defaults = { bg_iframe: false,
  15. onSelect: false,
  16. items_then_scroll: 10,
  17. case_sensitive: false
  18. };
  19. var settings = $.extend(defaults, options);
  20. // Only do bg_iframe for browsers that need it
  21. /*if(settings.bg_iframe && !$.browser.msie) {
  22. settings.bg_iframe = false;
  23. };*/
  24. var instance = false;
  25. $(this).each(function() {
  26. var i = instances.length;
  27. if($(this).data('editable-selecter') !== null) {
  28. instances[i] = new EditableSelect(this, settings);
  29. $(this).data('editable-selecter', i);
  30. };
  31. });
  32. return $(this);
  33. };
  34. $.fn.editableSelectInstances = function() {
  35. var ret = [];
  36. $(this).each(function() {
  37. if($(this).data('editable-selecter') !== null) {
  38. ret[ret.length] = instances[$(this).data('editable-selecter')];
  39. };
  40. });
  41. return ret;
  42. };
  43. var EditableSelect = function(select, settings) {
  44. this.init(select, settings);
  45. };
  46. EditableSelect.prototype = {
  47. settings: false,
  48. text: false,
  49. select: false,
  50. select_width: 0,
  51. wrapper: false,
  52. list_item_height: 20,
  53. list_height: 0,
  54. list_is_visible: false,
  55. hide_on_blur_timeout: false,
  56. bg_iframe: false,
  57. current_value: '',
  58. init: function(select, settings) {
  59. this.settings = settings;
  60. this.wrapper = $(document.createElement('div'));
  61. this.wrapper.addClass('editable-select-options');
  62. this.select = $(select);
  63. var name = this.select.attr('name');
  64. if(!name) {
  65. name = 'editable-select'+ instances.length;
  66. };
  67. var id = this.select.attr('id');
  68. if(!id) {
  69. id = 'editable-select'+ instances.length;
  70. };
  71. this.text = $('<input type="text">');
  72. this.text_submit = $('<input type="hidden">');
  73. this.text.attr('name', name + "_sele");
  74. this.text_submit.attr('name', name);
  75. this.text.data('editable-selecter', this.select.data('editable-selecter'));
  76. this.text_submit.data('editable-selecter', this.select.data('editable-selecter'));
  77. // Because we don't want the value of the select when the form
  78. // is submitted
  79. this.select.attr('disabled', 'disabled');
  80. this.text[0].className = this.select[0].className;
  81. this.text_submit[0].className = this.select[0].className;
  82. this.text.attr('id', id + "_sele");
  83. this.text_submit.attr('id', id);
  84. this.wrapper.attr('id',id+'_editable-select-options');
  85. this.text.attr('autocomplete', 'off');
  86. this.text.attr('autocomplete', 'off');
  87. this.text.addClass('editable-select');
  88. this.text_submit.addClass('editable-select');
  89. this.select.attr('id', id +'_hidden_select');
  90. this.select.attr('name', name +'_hidden_select');
  91. this.select.after(this.text);
  92. this.select.after(this.text_submit);
  93. if(this.select.css('display') == 'none') {
  94. //this.text.css('display', 'none');
  95. this.text_submit.css('display', 'none');
  96. }
  97. if(this.select.css('visibility') == 'hidden') {
  98. //this.text.css('visibility', 'visibility');
  99. this.text_submit.css('visibility', 'visibility');
  100. }
  101. // Set to hidden, because we want to call .show()
  102. // on it to get it's width but not having it display
  103. // on the screen
  104. this.select.css('visibility', 'hidden');
  105. this.select.hide();
  106. this.initInputEvents(this.text);
  107. this.duplicateOptions();
  108. this.setWidths();
  109. $(document.body).append(this.wrapper);
  110. if(this.settings.bg_iframe) {
  111. this.createBackgroundIframe();
  112. };
  113. if(typeof this.settings.success == "function") {
  114. this.settings.success.call(this,this.text_submit[0]);
  115. };
  116. },
  117. /**
  118. * Take the select lists options and
  119. * populate an unordered list with them
  120. */
  121. duplicateOptions: function() {
  122. var context = this,text,val;
  123. var option_list = $(document.createElement('ul'));
  124. this.wrapper.empty();
  125. this.wrapper.append(option_list);
  126. var options = this.select.find('option');
  127. this.dataList = [];
  128. options.each(function(i) {
  129. text = $(this).text();
  130. val = $(this).val();
  131. if($(this).attr('selected') /*|| i == 0*/) {
  132. context.text.val(text);
  133. context.text_submit.val(val);
  134. context.current_value = text;
  135. };
  136. if(context.trim(text) != "") context.dataList.push(text);
  137. var li = $('<li value="'+ val +'">'+ text +'</li>');
  138. li.hide();
  139. context.initListItemEvents(li);
  140. option_list.append(li);
  141. });
  142. this.setWidths();
  143. this.checkScroll();
  144. },in_array:function(e,arr)
  145. {
  146. for(i=0,len = arr.length;i < len;i++)
  147. {
  148. if(arr[i] == e)
  149. {
  150. return true;
  151. }
  152. }
  153. return false;
  154. },trim:function(str){
  155. return typeof str == "string" ? str.replace(/^\s*|\s*$/g,"") : str;
  156. },
  157. /**
  158. * Check if the list has enough items to display a scroll
  159. */
  160. checkScroll: function() {
  161. var options = this.wrapper.find('li');
  162. if(options.length > this.settings.items_then_scroll) {
  163. this.list_height = this.list_item_height * this.settings.items_then_scroll;
  164. this.wrapper.css('height', this.list_height +'px');
  165. this.wrapper.css('overflow', 'auto');
  166. } else {
  167. this.wrapper.css('height', 'auto');
  168. this.wrapper.css('overflow', 'visible');
  169. };
  170. },
  171. addOption: function(value,text) {
  172. var li = $('<li value="' + value + '">'+ text +'</li>');
  173. var option = $('<option value="' + value + '">'+ text +'</option>');
  174. this.select.append(option);
  175. this.initListItemEvents(li);
  176. this.wrapper.find('ul').append(li);
  177. this.setWidths();
  178. this.checkScroll();
  179. },
  180. /**
  181. * Init the different events on the input element
  182. */
  183. initInputEvents: function(text) {
  184. var context = this;
  185. var timer = false;
  186. $(document.body).click(
  187. function() {
  188. context.clearSelectedListItem();
  189. context.hideList();
  190. }
  191. );
  192. text.blur(
  193. function(e) {
  194. var val = context.trim(this.value);
  195. var isInArr = context.in_array(val,context.dataList);
  196. if( val == "")
  197. {
  198. context.text_submit.val("");
  199. }else if(val != "" && !isInArr)
  200. {
  201. context.text_submit.val("-1");
  202. }
  203. var list_item = typeof context.settings.onSelect == 'function' && isInArr ? context.findItem(val) : null;
  204. if(typeof context.settings.onSelect == 'function' && list_item != null) {
  205. context.text.val(list_item.text());
  206. context.text_submit.val(list_item.attr("value"));
  207. context.current_value = context.text.val();
  208. context.settings.onSelect.call(context, list_item,context.text_submit[0]);
  209. };
  210. context.hideList();
  211. e.preventDefault();
  212. e.stopPropagation();
  213. }
  214. );
  215. text.focus(
  216. function(e) {
  217. // Can't use the blur event to hide the list, because the blur event
  218. // is fired in some browsers when you scroll the list
  219. context.showList();
  220. context.highlightSelected();
  221. e.stopPropagation();
  222. }
  223. ).click(
  224. function(e) {
  225. e.stopPropagation();
  226. context.showList();
  227. context.highlightSelected();
  228. }
  229. ).keydown(
  230. // Capture key events so the user can navigate through the list
  231. function(e) {
  232. switch(e.keyCode) {
  233. // Down
  234. case 40:
  235. if(!context.listIsVisible()) {
  236. context.showList();
  237. context.highlightSelected();
  238. } else {
  239. e.preventDefault();
  240. context.selectNewListItem('down');
  241. };
  242. break;
  243. // Up
  244. case 38:
  245. e.preventDefault();
  246. context.selectNewListItem('up');
  247. break;
  248. // Tab
  249. case 9:
  250. context.pickListItem(context.selectedListItem());
  251. break;
  252. // Esc
  253. case 27:
  254. e.preventDefault();
  255. context.hideList();
  256. return false;
  257. break;
  258. // Enter, prevent form submission
  259. case 13:
  260. e.preventDefault();
  261. context.pickListItem(context.selectedListItem());
  262. return false;
  263. };
  264. }
  265. ).keyup(
  266. function(e) {
  267. // Prevent lots of calls if it's a fast typer
  268. if(timer !== false) {
  269. clearTimeout(timer);
  270. timer = false;
  271. };
  272. timer = setTimeout(
  273. function() {
  274. // If the user types in a value, select it if it's in the list
  275. if(context.text.val() != context.current_value) {
  276. context.current_value = context.text.val();
  277. context.highlightSelected();
  278. //context.showList();
  279. };
  280. },
  281. 200
  282. );
  283. // if input text change,list show.yingxian add hack by 2013-09-08
  284. (e.keyCode == 13) ? context.hideList() : context.showList();
  285. e.stopPropagation();
  286. }
  287. ).keypress(
  288. function(e) {
  289. if(e.keyCode == 13) {
  290. // Enter, prevent form submission
  291. e.preventDefault();
  292. return false;
  293. };
  294. }
  295. );
  296. },
  297. initListItemEvents: function(list_item) {
  298. var context = this;
  299. list_item.mouseover(
  300. function() {
  301. context.clearSelectedListItem();
  302. context.selectListItem(list_item);
  303. }
  304. ).mousedown(
  305. // Needs to be mousedown and not click, since the inputs blur events
  306. // fires before the list items click event
  307. function(e) {
  308. e.stopPropagation();
  309. context.pickListItem(context.selectedListItem());
  310. }
  311. );
  312. },
  313. selectNewListItem: function(direction) {
  314. var li = this.selectedListItem();
  315. if(!li.length) {
  316. li = this.selectFirstListItem();
  317. };
  318. if(direction == 'down') {
  319. var sib = this.selectNextItem(li);
  320. } else {
  321. var sib = this.selectPrevItem(li);
  322. };
  323. if(sib.length) {
  324. this.selectListItem(sib);
  325. this.scrollToListItem(sib);
  326. this.unselectListItem(li);
  327. };
  328. },selectNextItem:function(el){
  329. var e = el.next();
  330. if(e && e[0].display == "none")
  331. {
  332. return el;
  333. }
  334. return e;
  335. },selectPrevItem:function(el){
  336. var e = el.prev();
  337. if(e && e[0].display == "none")
  338. {
  339. return el;
  340. }
  341. return e;
  342. },
  343. selectListItem: function(list_item) {
  344. this.clearSelectedListItem();
  345. list_item.addClass('selected');
  346. },
  347. selectFirstListItem: function() {
  348. this.clearSelectedListItem();
  349. var first = this.wrapper.find('li:first');
  350. //this.wrapper.find('li').hide();
  351. first.addClass('selected');
  352. //first.show();
  353. return first;
  354. },
  355. unselectListItem: function(list_item) {
  356. list_item.removeClass('selected');
  357. },
  358. selectedListItem: function() {
  359. return this.wrapper.find('li.selected');
  360. },
  361. clearSelectedListItem: function() {
  362. this.wrapper.find('li.selected').removeClass('selected');
  363. },
  364. /**
  365. * The difference between this method and selectListItem
  366. * is that this method also changes the text field and
  367. * then hides the list
  368. */
  369. pickListItem: function(list_item) {
  370. if(list_item.length) {
  371. this.text.val(list_item.text());
  372. this.text_submit.val(list_item.attr("value"));
  373. this.current_value = this.text.val();
  374. };
  375. if(typeof this.settings.onSelect == 'function') {
  376. this.settings.onSelect.call(this, list_item,this.text_submit[0]);
  377. };
  378. this.hideList();
  379. },
  380. listIsVisible: function() {
  381. return this.list_is_visible;
  382. },
  383. showList: function() {
  384. this.positionElements();
  385. this.setWidths();
  386. this.wrapper.show();
  387. this.hideOtherLists();
  388. this.list_is_visible = true;
  389. if(this.settings.bg_iframe) {
  390. this.bg_iframe.show();
  391. };
  392. },
  393. findItem: function(text1) {
  394. var context = this;
  395. var current_value = context.trim(text1);
  396. var list_items = context.wrapper.find('li');
  397. var best_candiate = false;
  398. var value_found = false;
  399. list_items.each(
  400. function() {
  401. var text = context.trim($(this).text());
  402. if(!value_found) {
  403. if(!context.settings.case_sensitive) {
  404. text = text.toLowerCase();
  405. };
  406. if(text == current_value) {
  407. value_found = true;
  408. best_candiate = $(this);
  409. return false;
  410. }
  411. };
  412. }
  413. );
  414. if(value_found) {
  415. return best_candiate;
  416. }else if(!best_candiate && !value_found) {
  417. return null;
  418. };
  419. },
  420. highlightSelected: function() {
  421. var context = this;
  422. var current_value = context.trim(this.text.val());
  423. if(current_value.length < 0) {
  424. if(highlight_first) {
  425. this.selectFirstListItem();
  426. };
  427. return;
  428. };
  429. var list_items = this.wrapper.find('li');
  430. if(current_value.length == 0) {
  431. list_items.show();
  432. this.selectFirstListItem();
  433. return;
  434. };
  435. if(!context.settings.case_sensitive) {
  436. current_value = current_value.toLowerCase();
  437. };
  438. var best_candiate = false;
  439. var value_found = false;
  440. list_items.each(
  441. function() {
  442. var text = $(this).text();
  443. if(!value_found) {
  444. if(!context.settings.case_sensitive) {
  445. text = text.toLowerCase();
  446. };
  447. if(text == current_value) {
  448. value_found = true;
  449. context.clearSelectedListItem();
  450. context.selectListItem($(this));
  451. context.scrollToListItem($(this));
  452. //return false;
  453. } else if(text.search(current_value) > -1 && !best_candiate) {
  454. // Can't do return false here, since we still need to iterate over
  455. // all list items to see if there is an exact match
  456. best_candiate = $(this);
  457. };
  458. };
  459. if(context.settings.isFilter && text.search(current_value) > -1 && current_value != "")
  460. {
  461. $(this).show();
  462. }else if(context.settings.isFilter)
  463. {
  464. $(this).hide();
  465. }
  466. }
  467. );
  468. if(best_candiate && !value_found) {
  469. context.clearSelectedListItem();
  470. context.selectListItem(best_candiate);
  471. context.scrollToListItem(best_candiate);
  472. }else if(!best_candiate && !value_found) {
  473. this.selectFirstListItem();
  474. };
  475. },
  476. scrollToListItem: function(list_item) {
  477. if(this.list_height) {
  478. this.wrapper.scrollTop(list_item[0].offsetTop - (this.list_height / 2));
  479. };
  480. },
  481. hideList: function() {
  482. this.wrapper.hide();
  483. this.list_is_visible = false;
  484. if(this.settings.bg_iframe) {
  485. this.bg_iframe.hide();
  486. };
  487. },
  488. hideOtherLists: function() {
  489. for(var i = 0; i < instances.length; i++) {
  490. if(i != this.select.data('editable-selecter')) {
  491. instances[i].hideList();
  492. };
  493. };
  494. },
  495. positionElements: function() {
  496. var offset = this.text.offset();
  497. offset = { top: offset.top, left: offset.left };
  498. offset.top += this.text[0].offsetHeight;
  499. this.wrapper.css({top: offset.top +'px', left: offset.left +'px'});
  500. // Need to do this in order to get the list item height
  501. this.wrapper.css('visibility', 'hidden');
  502. this.wrapper.show();
  503. this.list_item_height = this.wrapper.find('li')[0] ? this.wrapper.find('li')[0].offsetHeight : 0;
  504. this.wrapper.css('visibility', 'visible');
  505. this.wrapper.hide();
  506. },
  507. setWidths: function() {
  508. // The text input has a right margin because of the background arrow image
  509. // so we need to remove that from the width
  510. this.select.show();
  511. var width = this.select.width() + 2 + 20;
  512. this.select.hide();
  513. var padding_right = parseInt(this.text.css('padding-right').replace(/px/, ''), 10);
  514. this.text.width(width - padding_right + 18);
  515. this.wrapper.width(width + 2 + 20);
  516. if(this.bg_iframe) {
  517. this.bg_iframe.width(width + 4 + 20);
  518. };
  519. },
  520. createBackgroundIframe: function() {
  521. var bg_iframe = $('<iframe frameborder="0" class="editable-select-iframe" src="about:blank;"></iframe>');
  522. $(document.body).append(bg_iframe);
  523. bg_iframe.width(this.select.width() + 2);
  524. bg_iframe.height(this.wrapper.height());
  525. bg_iframe.css({top: this.wrapper.css('top'), left: this.wrapper.css('left')});
  526. this.bg_iframe = bg_iframe;
  527. }
  528. };
  529. })(jQuery);