search_dropdown.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. ;
  2. (function ($) {
  3. 'use strict';
  4. function noop() { }
  5. function throttle(func, wait, options) {
  6. var context, args, result;
  7. var timeout = null;
  8. var previous = 0;
  9. if (!options) options = {};
  10. var later = function () {
  11. previous = options.leading === false ? 0 : new Date().getTime();
  12. timeout = null;
  13. result = func.apply(context, args);
  14. if (!timeout) context = args = null;
  15. };
  16. return function () {
  17. var now = new Date().getTime();
  18. if (!previous && options.leading === false) previous = now;
  19. var remaining = wait - (now - previous);
  20. context = this;
  21. args = arguments;
  22. if (remaining <= 0 || remaining > wait) {
  23. clearTimeout(timeout);
  24. timeout = null;
  25. previous = now;
  26. result = func.apply(context, args);
  27. if (!timeout) context = args = null;
  28. } else if (!timeout && options.trailing !== false) {
  29. timeout = setTimeout(later, remaining);
  30. }
  31. return result;
  32. };
  33. }
  34. var isSafari = function () {
  35. var ua = navigator.userAgent.toLowerCase();
  36. if (ua.indexOf('safari') !== -1) {
  37. return ua.indexOf('chrome') > -1 ? false : true;
  38. }
  39. }();
  40. var settings = {
  41. readonly: false,
  42. minCount: 0,
  43. minCountErrorMessage: '',
  44. limitCount: Infinity,
  45. limitCountErrorMessage: '',
  46. input: '<input type="text" maxLength="20" placeholder="Search...">',
  47. data: [],
  48. searchable: true,
  49. searchNoData: '<li style="color:#ddd">검색결과가 없습니다.</li>',
  50. init: noop,
  51. choice: noop,
  52. extendProps: []
  53. };
  54. var KEY_CODE = {
  55. up: 38,
  56. down: 40,
  57. enter: 13
  58. };
  59. var EVENT_SPACE = {
  60. click: 'click.iui-dropdown',
  61. focus: 'focus.iui-dropdown',
  62. keydown: 'keydown.iui-dropdown',
  63. keyup: 'keyup.iui-dropdown'
  64. };
  65. var ALERT_TIMEOUT_PERIOD = 1000;
  66. function createTemplate() {
  67. var isLabelMode = this.isLabelMode;
  68. var searchable = this.config.searchable;
  69. var templateSearch = searchable ? '<span class="dropdown-search">' + this.config.input + '</span>' : '';
  70. return isLabelMode ? '<div class="dropdown-display-label"><div class="dropdown-chose-list">' + templateSearch + '</div></div><div class="dropdown-main">{{ul}}</div>' : '<a href="javascript:;" class="dropdown-display" tabindex="0"><span class="dropdown-chose-list"></span></a><div class="dropdown-main">' + templateSearch + '{{ul}}</div>';
  71. }
  72. function minItemsAlert() {
  73. var _dropdown = this;
  74. var _config = _dropdown.config;
  75. var $el = _dropdown.$el;
  76. var $alert = $el.find('.dropdown-minItem-alert');
  77. var alertMessage = _config.minCountErrorMessage;
  78. clearTimeout(_dropdown.itemCountAlertTimer);
  79. if ($alert.length === 0) {
  80. if (!alertMessage) {
  81. alertMessage = '\u6700\u4f4e\u9009\u62e9' + _config.minCount + '\u4E2A';
  82. }
  83. $alert = $('<div class="dropdown-minItem-alert">' + alertMessage + '</div>');
  84. }
  85. $el.append($alert);
  86. _dropdown.itemCountAlertTimer = setTimeout(function () {
  87. $el.find('.dropdown-minItem-alert').remove();
  88. }, ALERT_TIMEOUT_PERIOD);
  89. }
  90. function maxItemAlert() {
  91. var _dropdown = this;
  92. var _config = _dropdown.config;
  93. var $el = _dropdown.$el;
  94. var $alert = $el.find('.dropdown-maxItem-alert');
  95. var alertMessage = _config.limitCountErrorMessage;
  96. clearTimeout(_dropdown.itemLimitAlertTimer);
  97. if ($alert.length === 0) {
  98. if (!alertMessage) {
  99. alertMessage = '\u6700\u591A\u53EF\u9009\u62E9' + _config.limitCount + '\u4E2A';
  100. }
  101. $alert = $('<div class="dropdown-maxItem-alert">' + alertMessage + '</div>');
  102. }
  103. $el.append($alert);
  104. _dropdown.itemLimitAlertTimer = setTimeout(function () {
  105. $el.find('.dropdown-maxItem-alert').remove();
  106. }, ALERT_TIMEOUT_PERIOD);
  107. }
  108. function selectToDiv(str) {
  109. var result = str || '';
  110. result = result.replace(/<select[^>]*>/gi, '').replace('</select>', '');
  111. result = result.replace(/<\/optgroup>/gi, '');
  112. result = result.replace(/<optgroup[^>]*>/gi, function (matcher) {
  113. var groupName = /label="(.[^"]*)"(\s|>)/.exec(matcher);
  114. var groupId = /data\-group\-id="(.[^"]*)"(\s|>)/.exec(matcher);
  115. return '<li class="dropdown-group" data-group-id="' + (groupId ? groupId[1] : '') + '">' + (groupName ? groupName[1] : '') + '</li>';
  116. });
  117. result = result.replace(/<option(.*?)<\/option>/gi, function (matcher) {
  118. var value = $(matcher).val();
  119. var name = />(.*)<\//.exec(matcher);
  120. var isSelected = matcher.indexOf('selected') > -1 ? true : false;
  121. var isDisabled = matcher.indexOf('disabled') > -1 ? true : false;
  122. var extendAttr = ''
  123. var extendProps = matcher.replace(/data-(\w+)="?(.[^"]+)"?/g, function ($1) {
  124. extendAttr += $1 + ' '
  125. });
  126. return '<li ' + (isDisabled ? ' disabled' : ' tabindex="0"') + ' data-value="' + (value || '') + '" class="dropdown-option ' + (isSelected ? 'dropdown-chose' : '') + '" ' + extendAttr + '>' + (name ? name[1] : '') + '</li>';
  127. });
  128. return result;
  129. }
  130. function objectToSelect(data) {
  131. var dropdown = this;
  132. var map = {};
  133. var result = '';
  134. var name = [];
  135. var selectAmount = 0;
  136. var extendProps = dropdown.config.extendProps;
  137. if (!data || !data.length) {
  138. return false;
  139. }
  140. $.each(data, function (index, val) {
  141. var hasGroup = val.groupId;
  142. var isDisabled = val.disabled ? ' disabled' : '';
  143. var isSelected = val.selected && !isDisabled ? ' selected' : '';
  144. var extendAttr = ''
  145. $.each(extendProps, function (index, value) {
  146. if (val[value]) {
  147. extendAttr += 'data-' + value + '="' + val[value] + '" '
  148. }
  149. })
  150. var temp = '<option' + isDisabled + isSelected + ' value="' + val.id + '" ' + extendAttr + '>' + val.name + '</option>';
  151. if (isSelected) {
  152. name.push('<span class="dropdown-selected">' + val.name + '<i class="del" data-id="' + val.id + '"></i></span>');
  153. selectAmount++;
  154. }
  155. if (hasGroup) {
  156. if (map[val.groupId]) {
  157. map[val.groupId] += temp;
  158. } else {
  159. map[val.groupId] = val.groupName + '&janking&' + temp;
  160. }
  161. } else {
  162. map[index] = temp;
  163. }
  164. });
  165. $.each(map, function (index, val) {
  166. var option = val.split('&janking&');
  167. if (option.length === 2) {
  168. var groupName = option[0];
  169. var items = option[1];
  170. result += '<optgroup label="' + groupName + '" data-group-id="' + index + '">' + items + '</optgroup>';
  171. } else {
  172. result += val;
  173. }
  174. });
  175. return [result, name, selectAmount];
  176. }
  177. function selectToObject(el) {
  178. var $select = el;
  179. var result = [];
  180. function readOption(key, el) {
  181. var $option = $(el);
  182. this.id = $option.prop('value');
  183. this.name = $option.text();
  184. this.disabled = $option.prop('disabled');
  185. this.selected = $option.prop('selected');
  186. }
  187. $.each($select.children(), function (key, el) {
  188. var tmp = {};
  189. var tmpGroup = {};
  190. var $el = $(el);
  191. if (el.nodeName === 'OPTGROUP') {
  192. tmpGroup.groupId = $el.data('groupId');
  193. tmpGroup.groupName = $el.attr('label');
  194. $.each($el.children(), $.proxy(readOption, tmp));
  195. $.extend(tmp, tmpGroup);
  196. } else {
  197. $.each($el, $.proxy(readOption, tmp));
  198. }
  199. result.push(tmp);
  200. });
  201. return result;
  202. }
  203. var action = {
  204. show: function (event) {
  205. event.stopPropagation();
  206. var _dropdown = this;
  207. $(document).trigger('click.dropdown');
  208. _dropdown.$el.addClass('active');
  209. },
  210. search: throttle(function (event) {
  211. var _dropdown = this;
  212. var _config = _dropdown.config;
  213. var $el = _dropdown.$el;
  214. var $input = $(event.target);
  215. var intputValue = $input.val();
  216. var data = _dropdown.config.data;
  217. var result = [];
  218. if (event.keyCode > 36 && event.keyCode < 41) {
  219. return;
  220. }
  221. $.each(data, function (key, value) {
  222. if ((value.groupName && value.groupName.toLowerCase().indexOf(intputValue.toLowerCase()) > -1) || value.name.toLowerCase().indexOf(intputValue.toLowerCase()) > -1 || '' + value.id === '' + intputValue) {
  223. result.push(value);
  224. }
  225. });
  226. $el.find('ul').html(selectToDiv(objectToSelect.call(_dropdown, result)[0]) || _config.searchNoData);
  227. }, 300),
  228. control: function (event) {
  229. var keyCode = event.keyCode;
  230. var KC = KEY_CODE;
  231. var index = 0;
  232. var direct;
  233. var itemIndex;
  234. var $items;
  235. if (keyCode === KC.down || keyCode === KC.up) {
  236. direct = keyCode === KC.up ? -1 : 1;
  237. $items = this.$el.find('[tabindex]');
  238. itemIndex = $items.index($(document.activeElement));
  239. if (itemIndex === -1) {
  240. index = direct + 1 ? -1 : 0;
  241. } else {
  242. index = itemIndex;
  243. }
  244. index = index + direct;
  245. if (index === $items.length) {
  246. index = 0;
  247. }
  248. $items.eq(index).focus();
  249. event.preventDefault();
  250. }
  251. },
  252. multiChoose: function (event, status) {
  253. var _dropdown = this;
  254. var _config = _dropdown.config;
  255. var $select = _dropdown.$select;
  256. var $target = $(event.target);
  257. var value = $target.attr('data-value');
  258. var hasSelected = $target.hasClass('dropdown-chose');
  259. var selectedName = [];
  260. var selectedProp;
  261. if ($target.hasClass('dropdown-display')) {
  262. return false;
  263. }
  264. if (hasSelected) {
  265. $target.removeClass('dropdown-chose');
  266. _dropdown.selectAmount--;
  267. } else {
  268. if (_dropdown.selectAmount < _config.limitCount) {
  269. $target.addClass('dropdown-chose');
  270. _dropdown.selectAmount++;
  271. } else {
  272. maxItemAlert.call(_dropdown);
  273. return false;
  274. }
  275. }
  276. _dropdown.name = [];
  277. $.each(_config.data, function (key, item) {
  278. if ('' + item.id === '' + value) {
  279. selectedProp = item;
  280. item.selected = hasSelected ? false : true;
  281. }
  282. if (item.selected) {
  283. selectedName.push(item.name);
  284. _dropdown.name.push('<span class="dropdown-selected">' + item.name + '<i class="del" data-id="' + item.id + '"></i></span>');
  285. }
  286. });
  287. $select.find('option[value="' + value + '"]').prop('selected', hasSelected ? false : true);
  288. if (hasSelected && _dropdown.selectAmount < _config.minCount) {
  289. minItemsAlert.call(_dropdown);
  290. }
  291. _dropdown.$choseList.find('.dropdown-selected').remove();
  292. _dropdown.$choseList.prepend(_dropdown.name.join(''));
  293. _dropdown.$el.find('.dropdown-display').attr('title', selectedName.join(','));
  294. _config.choice.call(_dropdown, event, selectedProp);
  295. },
  296. singleChoose: function (event) {
  297. var _dropdown = this;
  298. var _config = _dropdown.config;
  299. var $el = _dropdown.$el;
  300. var $select = _dropdown.$select;
  301. var $target = $(event.target);
  302. var value = $target.attr('data-value');
  303. var hasSelected = $target.hasClass('dropdown-chose');
  304. if ($target.hasClass('dropdown-chose') || $target.hasClass('dropdown-display')) {
  305. return false;
  306. }
  307. _dropdown.name = [];
  308. $el.removeClass('active').find('li').not($target).removeClass('dropdown-chose');
  309. $target.toggleClass('dropdown-chose');
  310. $.each(_config.data, function (key, item) {
  311. item.selected = false;
  312. if ('' + item.id === '' + value) {
  313. item.selected = hasSelected ? 0 : 1;
  314. if (item.selected) {
  315. _dropdown.name.push('<span class="dropdown-selected">' + item.name + '<i class="del" data-id="' + item.id + '"></i></span>');
  316. }
  317. }
  318. });
  319. $select.find('option[value="' + value + '"]').prop('selected', true);
  320. _dropdown.name.push('<span class="placeholder">' + _dropdown.placeholder + '</span>');
  321. _dropdown.$choseList.html(_dropdown.name.join(''));
  322. _config.choice.call(_dropdown, event);
  323. },
  324. del: function (event) {
  325. var _dropdown = this;
  326. var _config = _dropdown.config;
  327. var $target = $(event.target);
  328. var id = $target.data('id');
  329. $.each(_dropdown.name, function (key, value) {
  330. if (value.indexOf('data-id="' + id + '"') !== -1) {
  331. _dropdown.name.splice(key, 1);
  332. return false;
  333. }
  334. });
  335. $.each(_dropdown.config.data, function (key, item) {
  336. if ('' + item.id == '' + id) {
  337. item.selected = false;
  338. return false;
  339. }
  340. });
  341. _dropdown.selectAmount--;
  342. _dropdown.$el.find('[data-value="' + id + '"]').removeClass('dropdown-chose');
  343. _dropdown.$el.find('[value="' + id + '"]').prop('selected', false).removeAttr('selected');
  344. $target.closest('.dropdown-selected').remove();
  345. _config.choice.call(_dropdown, event);
  346. return false;
  347. },
  348. clearAll: function (event) {
  349. var _dropdown = this;
  350. var _config = _dropdown.config;
  351. event && event.preventDefault();
  352. console.log(this)
  353. this.$choseList.find('.del').each(function (index, el) {
  354. $(el).trigger('click');
  355. });
  356. if (_config.minCount > 0) {
  357. minItemsAlert.call(_dropdown);
  358. }
  359. this.$el.find('.dropdown-display').removeAttr('title');
  360. return false;
  361. }
  362. };
  363. function Dropdown(options, el) {
  364. this.$el = $(el);
  365. this.$select = this.$el.find('select');
  366. this.placeholder = this.$select.attr('placeholder');
  367. this.config = options;
  368. this.name = [];
  369. this.isSingleSelect = !this.$select.prop('multiple');
  370. this.selectAmount = 0;
  371. this.itemLimitAlertTimer = null;
  372. this.isLabelMode = this.config.multipleMode === 'label';
  373. this.init();
  374. }
  375. Dropdown.prototype = {
  376. init: function () {
  377. var _this = this;
  378. var _config = _this.config;
  379. var $el = _this.$el;
  380. _this.$select.hide();
  381. $el.addClass(_this.isSingleSelect ? 'dropdown-single' : _this.isLabelMode ? 'dropdown-multiple-label' : 'dropdown-multiple');
  382. if (_config.data.length === 0) {
  383. _config.data = selectToObject(_this.$select);
  384. }
  385. var processResult = objectToSelect.call(_this, _config.data);
  386. _this.name = processResult[1];
  387. _this.selectAmount = processResult[2];
  388. _this.$select.html(processResult[0]);
  389. _this.renderSelect();
  390. _this.changeStatus(_config.disabled ? 'disabled' : _config.readonly ? 'readonly' : false);
  391. _this.config.init();
  392. },
  393. renderSelect: function (isUpdate, isCover) {
  394. var _this = this;
  395. var $el = _this.$el;
  396. var $select = _this.$select;
  397. var elemLi = selectToDiv($select.prop('outerHTML'));
  398. var template;
  399. if (isUpdate) {
  400. $el.find('ul')[isCover ? 'html' : 'append'](elemLi);
  401. } else {
  402. template = createTemplate.call(_this).replace('{{ul}}', '<ul>' + elemLi + '</ul>');
  403. $el.append(template).find('ul').removeAttr('style class');
  404. }
  405. if (isCover) {
  406. _this.name = [];
  407. _this.$el.find('.dropdown-selected').remove();
  408. _this.$select.val('');
  409. }
  410. _this.$choseList = $el.find('.dropdown-chose-list');
  411. if (!_this.isLabelMode) {
  412. _this.$choseList.html($('<span class="placeholder"></span>').text(_this.placeholder));
  413. }
  414. _this.$choseList.prepend(_this.name ? _this.name.join('') : []);
  415. },
  416. bindEvent: function () {
  417. var _this = this;
  418. var $el = _this.$el;
  419. var openHandle = isSafari ? EVENT_SPACE.click : EVENT_SPACE.focus;
  420. $el.on(EVENT_SPACE.click, function (event) {
  421. event.stopPropagation();
  422. });
  423. $el.on(EVENT_SPACE.click, '.del', $.proxy(action.del, _this));
  424. // show
  425. if (_this.isLabelMode) {
  426. $el.on(EVENT_SPACE.click, '.dropdown-display-label', function () {
  427. $el.find('input').focus();
  428. });
  429. if (_this.config.searchable) {
  430. $el.on(EVENT_SPACE.focus, 'input', $.proxy(action.show, _this));
  431. } else {
  432. $el.on(EVENT_SPACE.click, $.proxy(action.show, _this));
  433. }
  434. $el.on(EVENT_SPACE.keydown, 'input', function (event) {
  435. if (event.keyCode === 8 && this.value === '' && _this.name.length) {
  436. $el.find('.del').eq(-1).trigger('click');
  437. }
  438. });
  439. } else {
  440. $el.on(openHandle, '.dropdown-display', $.proxy(action.show, _this));
  441. $el.on(openHandle, '.dropdown-clear-all', $.proxy(action.clearAll, _this));
  442. }
  443. $el.on(EVENT_SPACE.keyup, 'input', $.proxy(action.search, _this));
  444. $el.on(EVENT_SPACE.keyup, function (event) {
  445. var keyCode = event.keyCode;
  446. var KC = KEY_CODE;
  447. if (keyCode === KC.enter) {
  448. $.proxy(_this.isSingleSelect ? action.singleChoose : action.multiChoose, _this, event)();
  449. }
  450. });
  451. $el.on(EVENT_SPACE.keydown, $.proxy(action.control, _this));
  452. $el.on(EVENT_SPACE.click, 'li[tabindex]', $.proxy(_this.isSingleSelect ? action.singleChoose : action.multiChoose, _this));
  453. },
  454. unbindEvent: function () {
  455. var _this = this;
  456. var $el = _this.$el;
  457. var openHandle = isSafari ? EVENT_SPACE.click : EVENT_SPACE.focus;
  458. $el.off(EVENT_SPACE.click);
  459. $el.off(EVENT_SPACE.click, '.del');
  460. // show
  461. if (_this.isLabelMode) {
  462. $el.off(EVENT_SPACE.click, '.dropdown-display-label');
  463. $el.off(EVENT_SPACE.focus, 'input');
  464. $el.off(EVENT_SPACE.keydown, 'input');
  465. } else {
  466. $el.off(openHandle, '.dropdown-display');
  467. $el.off(openHandle, '.dropdown-clear-all');
  468. }
  469. $el.off(EVENT_SPACE.keyup, 'input');
  470. $el.off(EVENT_SPACE.keyup);
  471. $el.off(EVENT_SPACE.keydown);
  472. $el.off(EVENT_SPACE.click, '[tabindex]');
  473. },
  474. changeStatus: function (status) {
  475. var _this = this;
  476. if (status === 'readonly') {
  477. _this.unbindEvent();
  478. } else if (status === 'disabled') {
  479. _this.$select.prop('disabled', true);
  480. _this.unbindEvent();
  481. } else {
  482. _this.$select.prop('disabled', false);
  483. _this.bindEvent();
  484. }
  485. },
  486. update: function (data, isCover) {
  487. var _this = this;
  488. var _config = _this.config;
  489. var $el = _this.$el;
  490. var _isCover = isCover || false;
  491. if (Object.prototype.toString.call(data) !== '[object Array]') {
  492. return;
  493. }
  494. _config.data = _isCover ? data.slice(0) : _config.data.concat(data);
  495. var processResult = objectToSelect.call(_this, _config.data);
  496. _this.name = processResult[1];
  497. _this.selectAmount = processResult[2];
  498. _this.$select.html(processResult[0]);
  499. _this.renderSelect(true, _isCover);
  500. },
  501. destroy: function () {
  502. this.unbindEvent();
  503. this.$el.children().not('select').remove();
  504. this.$el.removeClass('dropdown-single dropdown-multiple-label dropdown-multiple');
  505. this.$select.show();
  506. },
  507. choose: function (values, status) {
  508. var valArr = Object.prototype.toString.call(values) === '[object Array]' ? values : [values];
  509. var _this = this;
  510. var _status = status !== void 0 ? !!status : true
  511. $.each(valArr, function (index, value) {
  512. var $target = _this.$el.find('[data-value="' + value + '"]');
  513. var targetStatus = $target.hasClass('dropdown-chose');
  514. if (targetStatus !== _status) {
  515. $target.trigger(EVENT_SPACE.click, status || true)
  516. }
  517. });
  518. },
  519. reset: function () {
  520. action.clearAll.call(this)
  521. }
  522. };
  523. $(document).on('click.dropdown', function () {
  524. $('.dropdown-single,.dropdown-multiple,.dropdown-multiple-label').removeClass('active');
  525. });
  526. $.fn.dropdown = function (options) {
  527. this.each(function (index, el) {
  528. $(el).data('dropdown', new Dropdown($.extend(true, {}, settings, options), el));
  529. });
  530. return this;
  531. }
  532. })(jQuery);