PageSourceSearch

https://www.geneticmedjournal.com/public/dropdown/jquery.dropdown.js

js geneticmedjournal.com collected 2026-10-07 22:31:04 UTC 21,717 bytes, 643 lines download raw bytes

1;
2(function ($) {
3  'use strict';
4
5  function noop() { }
6
7  function throttle(func, wait, options) {
8    var context, args, result;
9    var timeout = null;
10    // 上次执行时间点
11    var previous = 0;
12    if (!options) options = {};
13    // 延迟执行函数
14    var later = function () {
15      // 若设定了开始边界不执行选项,上次执行时间始终为0
16      previous = options.leading === false ? 0 : new Date().getTime();
17      timeout = null;
18      result = func.apply(context, args);
19      if (!timeout) context = args = null;
20    };
21    return function () {
22      var now = new Date().getTime();
23      // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。
24      if (!previous && options.leading === false) previous = now;
25      // 延迟执行时间间隔
26      var remaining = wait - (now - previous);
27      context = this;
28      args = arguments;
29      // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口
30      // remaining大于时间窗口wait,表示客户端系统时间被调整过
31      if (remaining <= 0 || remaining > wait) {
32        clearTimeout(timeout);
33        timeout = null;
34        previous = now;
35        result = func.apply(context, args);
36        if (!timeout) context = args = null;
37        //如果延迟执行不存在,且没有设定结尾边界不执行选项
38      } else if (!timeout && options.trailing !== false) {
39        timeout = setTimeout(later, remaining);
40      }
41      return result;
42    };
43  }
44
45  var isSafari = function () {
46    var ua = navigator.userAgent.toLowerCase();
47    if (ua.indexOf('safari') !== -1) {
48      return ua.indexOf('chrome') > -1 ? false : true;
49    }
50  }();
51
52  var settings = {
53    readonly: false,
54    minCount: 0,
55    minCountErrorMessage: '',
56    limitCount: Infinity,
57    limitCountErrorMessage: '',
58    input: '<input type="text" maxLength="20" placeholder="Search...">',
59    data: [],
60    searchable: true,
61    searchNoData: '<li style="color:#ddd">No Results.</li>',
62    init: noop,
63    choice: noop,
64    extendProps: []
65  };
66
67  var KEY_CODE = {
68    up: 38,
69    down: 40,
70    enter: 13
71  };
72
73  var EVENT_SPACE = {
74    click: 'click.iui-dropdown',
75    focus: 'focus.iui-dropdown',
76    keydown: 'keydown.iui-dropdown',
77    keyup: 'keyup.iui-dropdown'
78  };
79
80  var ALERT_TIMEOUT_PERIOD = 1000;
81
82  // 创建模板
83  function createTemplate() {
84    var isLabelMode = this.isLabelMode;
85    var searchable = this.config.searchable;
86    var templateSearch = searchable ? '<span class="dropdown-search">' + this.config.input + '</span>' : '';
87
88    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 href="javascript:;"  class="dropdown-clear-all" tabindex="0">\xD7</a></a><div class="dropdown-main">' + templateSearch + '{{ul}}</div>';
89  }
90
91  // 小于minCount提示的元素
92  function minItemsAlert() {
93    var _dropdown = this;
94    var _config = _dropdown.config;
95    var $el = _dropdown.$el;
96    var $alert = $el.find('.dropdown-minItem-alert');
97    var alertMessage = _config.minCountErrorMessage;
98    clearTimeout(_dropdown.itemCountAlertTimer);
99
100    if ($alert.length === 0) {
101      if (!alertMessage) {
102        alertMessage = '\u6700\u4f4e\u9009\u62e9' + _config.minCount + '\u4E2A';
103      }
104      $alert = $('<div class="dropdown-minItem-alert">' + alertMessage + '</div>');
105    }
106
107    $el.append($alert);
108    _dropdown.itemCountAlertTimer = setTimeout(function () {
109      $el.find('.dropdown-minItem-alert').remove();
110    }, ALERT_TIMEOUT_PERIOD);
111  }
112
113  // 超出限制提示
114  function maxItemAlert() {
115    var _dropdown = this;
116    var _config = _dropdown.config;
117    var $el = _dropdown.$el;
118    var $alert = $el.find('.dropdown-maxItem-alert');
119    var alertMessage = _config.limitCountErrorMessage;
120    clearTimeout(_dropdown.itemLimitAlertTimer);
121
122    if ($alert.length === 0) {
123      if (!alertMessage) {
124        alertMessage = '\u6700\u591A\u53EF\u9009\u62E9' + _config.limitCount + '\u4E2A';
125      }
126      $alert = $('<div class="dropdown-maxItem-alert">' + alertMessage + '</div>');
127    }
128
129    $el.append($alert);
130    _dropdown.itemLimitAlertTimer = setTimeout(function () {
131      $el.find('.dropdown-maxItem-alert').remove();
132    }, ALERT_TIMEOUT_PERIOD);
133  }
134
135  // select-option 转 ul-li
136  function selectToDiv(str) {
137    var result = str || '';
138    // 移除select标签
139    result = result.replace(/<select[^>]*>/gi, '').replace('</select>', '');
140    // 移除 optgroup 结束标签
141    result = result.replace(/<\/optgroup>/gi, '');
142    result = result.replace(/<optgroup[^>]*>/gi, function (matcher) {
143      var groupName = /label="(.[^"]*)"(\s|>)/.exec(matcher);
144      var groupId = /data\-group\-id="(.[^"]*)"(\s|>)/.exec(matcher);
145      return '<li class="dropdown-group" data-group-id="' + (groupId ? groupId[1] : '') + '">' + (groupName ? groupName[1] : '') + '</li>';
146    });
147    result = result.replace(/<option(.*?)<\/option>/gi, function (matcher) {
148      // var value = /value="?([\w\u4E00-\u9FA5\uF900-\uFA2D]+)"?/.exec(matcher);
149      var value = $(matcher).val();
150      var name = />(.*)<\//.exec(matcher);
151      // 强制要求html中使用selected/disabled,而不是selected="selected","disabled="disabled"
152      var isSelected = matcher.indexOf('selected') > -1 ? true : false;
153      var isDisabled = matcher.indexOf('disabled') > -1 ? true : false;
154      var extendAttr = ''
155      var extendProps = matcher.replace(/data-(\w+)="?(.[^"]+)"?/g, function ($1) {
156        extendAttr += $1 + ' '
157      });
158      return '<li ' + (isDisabled ? ' disabled' : ' tabindex="0"') + ' data-value="' + (value || '') + '" class="dropdown-option ' + (isSelected ? 'dropdown-chose' : '') + '" ' + extendAttr + '>' + (name ? name[1] : '') + '</li>';
159    });
160
161    return result;
162  }
163
164  // object-data 转 select-option
165  function objectToSelect(data) {
166    var dropdown = this;
167    var map = {};
168    var result = '';
169    var name = [];
170    var selectAmount = 0;
171    var extendProps = dropdown.config.extendProps;
172
173    if (!data || !data.length) {
174      return false;
175    }
176
177    $.each(data, function (index, val) {
178      // disable 权重高于 selected
179      var hasGroup = val.groupId;
180      var isDisabled = val.disabled ? ' disabled' : '';
181      var isSelected = val.selected && !isDisabled ? ' selected' : '';
182      var extendAttr = ''
183      $.each(extendProps, function (index, value) {
184        if (val[value]) {
185          extendAttr += 'data-' + value + '="' + val[value] + '" '
186        }
187      })
188      var temp = '<option' + isDisabled + isSelected + ' value="' + val.id + '" ' + extendAttr + '>' + val.name + '</option>';
189      if (isSelected) {
190        name.push('<span class="dropdown-selected">' + val.name + '<i class="del" data-id="' + val.id + '"></i></span>');
191        selectAmount++;
192      }
193      // 判断是否有分组
194      if (hasGroup) {
195        if (map[val.groupId]) {
196          map[val.groupId] += temp;
197        } else {
198          //  &janking& just a separator
199          map[val.groupId] = val.groupName + '&janking&' + temp;
200        }
201      } else {
202        map[index] = temp;
203      }
204    });
205
206    $.each(map, function (index, val) {
207      var option = val.split('&janking&');
208      // 判断是否有分组
209      if (option.length === 2) {
210        var groupName = option[0];
211        var items = option[1];
212        result += '<optgroup label="' + groupName + '" data-group-id="' + index + '">' + items + '</optgroup>';
213      } else {
214        result += val;
215      }
216    });
217
218    return [result, name, selectAmount];
219  }
220
221  // select-option 转 object-data
222  //
223  function selectToObject(el) {
224    var $select = el;
225    var result = [];
226
227    function readOption(key, el) {
228      var $option = $(el);
229      this.id = $option.prop('value');
230      this.name = $option.text();
231      this.disabled = $option.prop('disabled');
232      this.selected = $option.prop('selected');
233    }
234
235    $.each($select.children(), function (key, el) {
236      var tmp = {};
237      var tmpGroup = {};
238      var $el = $(el);
239      if (el.nodeName === 'OPTGROUP') {
240        tmpGroup.groupId = $el.data('groupId');
241        tmpGroup.groupName = $el.attr('label');
242        $.each($el.children(), $.proxy(readOption, tmp));
243        $.extend(tmp, tmpGroup);
244      } else {
245        $.each($el, $.proxy(readOption, tmp));
246      }
247      result.push(tmp);
248    });
249
250    return result;
251  }
252
253  var action = {
254    show: function (event) {
255      event.stopPropagation();
256      var _dropdown = this;
257      $(document).trigger('click.dropdown');
258      _dropdown.$el.addClass('active');
259    },
260    search: throttle(function (event) {
261      var _dropdown = this;
262      var _config = _dropdown.config;
263      var $el = _dropdown.$el;
264      var $input = $(event.target);
265      var intputValue = $input.val();
266      var data = _dropdown.config.data;
267      var result = [];
268      if (event.keyCode > 36 && event.keyCode < 41) {
269        return;
270      }
271      $.each(data, function (key, value) {
272        if ((value.groupName && value.groupName.toLowerCase().indexOf(intputValue.toLowerCase()) > -1) || value.name.toLowerCase().indexOf(intputValue.toLowerCase()) > -1 || '' + value.id === '' + intputValue) {
273          result.push(value);
274        }
275      });
276      $el.find('ul').html(selectToDiv(objectToSelect.call(_dropdown, result)[0]) || _config.searchNo
276Data);
277    }, 300),
278    control: function (event) {
279      var keyCode = event.keyCode;
280      var KC = KEY_CODE;
281      var index = 0;
282      var direct;
283      var itemIndex;
284      var $items;
285      if (keyCode === KC.down || keyCode === KC.up) {
286        // 方向
287        direct = keyCode === KC.up ? -1 : 1;
288        $items = this.$el.find('[tabindex]');
289        itemIndex = $items.index($(document.activeElement));
290        // 初始
291        if (itemIndex === -1) {
292          index = direct + 1 ? -1 : 0;
293        } else {
294          index = itemIndex;
295        }
296        // 确认位序
297        index = index + direct;
298        // 最后位循环
299        if (index === $items.length) {
300          index = 0;
301        }
302        $items.eq(index).focus();
303        event.preventDefault();
304      }
305    },
306    multiChoose: function (event, status) {
307      var _dropdown = this;
308      var _config = _dropdown.config;
309      var $select = _dropdown.$select;
310      var $target = $(event.target);
311      var value = $target.attr('data-value');
312      var hasSelected = $target.hasClass('dropdown-chose');
313      var selectedName = [];
314      var selectedProp;
315
316      if ($target.hasClass('dropdown-display')) {
317        return false;
318      }
319
320      if (hasSelected) {
321        $target.removeClass('dropdown-chose');
322        _dropdown.selectAmount--;
323      } else {
324        if (_dropdown.selectAmount < _config.limitCount) {
325          $target.addClass('dropdown-chose');
326          _dropdown.selectAmount++;
327        } else {
328          maxItemAlert.call(_dropdown);
329          return false;
330        }
331      }
332
333      _dropdown.name = [];
334
335      $.each(_config.data, function (key, item) {
336        if ('' + item.id === '' + value) {
337          selectedProp = item;
338          item.selected = hasSelected ? false : true;
339        }
340        if (item.selected) {
341          selectedName.push(item.name);
342          _dropdown.name.push('<span class="dropdown-selected">' + item.name + '<i class="del" data-id="' + item.id + '"></i></span>');
343        }
344      });
345
346      $select.find('option[value="' + value + '"]').prop('selected', hasSelected ? false : true);
347
348      if (hasSelected && _dropdown.selectAmount < _config.minCount) {
349        minItemsAlert.call(_dropdown);
350      }
351
352      _dropdown.$choseList.find('.dropdown-selected').remove();
353      _dropdown.$choseList.prepend(_dropdown.name.join(''));
354      _dropdown.$el.find('.dropdown-display').attr('title', selectedName.join(','));
355      _config.choice.call(_dropdown, event, selectedProp);
356    },
357    singleChoose: function (event) {
358      var _dropdown = this;
359      var _config = _dropdown.config;
360      var $el = _dropdown.$el;
361      var $select = _dropdown.$select;
362      var $target = $(event.target);
363      var value = $target.attr('data-value');
364      var hasSelected = $target.hasClass('dropdown-chose');
365
366      if ($target.hasClass('dropdown-chose') || $target.hasClass('dropdown-display')) {
367        return false;
368      }
369
370      _dropdown.name = [];
371
372
373      $el.removeClass('active').find('li').not($target).removeClass('dropdown-chose');
374
375      $target.toggleClass('dropdown-chose');
376      $.each(_config.data, function (key, item) {
377        // id 有可能是数字也有可能是字符串,强制全等有弊端 2017-03-20 22:19:21
378        item.selected = false;
379        if ('' + item.id === '' + value) {
380          item.selected = hasSelected ? 0 : 1;
381          if (item.selected) {
382            _dropdown.name.push('<span class="dropdown-selected">' + item.name + '<i class="del" data-id="' + item.id + '"></i></span>');
383          }
384        }
385      });
386
387      $select.find('option[value="' + value + '"]').prop('selected', true);
388
389      _dropdown.name.push('<span class="placeholder">' + _dropdown.placeholder + '</span>');
390      _dropdown.$choseList.html(_dropdown.name.join(''));
391      _config.choice.call(_dropdown, event);
392    },
393    del: function (event) {
394      var _dropdown = this;
395      var _config = _dropdown.config;
396      var $target = $(event.target);
397      var id = $target.data('id');
398      // 2017-03-23 15:58:50 测试
399      // 10000条数据测试删除,耗时 ~3ms
400      $.each(_dropdown.name, function (key, value) {
401        if (value.indexOf('data-id="' + id + '"') !== -1) {
402          _dropdown.name.splice(key, 1);
403          return false;
404        }
405      });
406
407      $.each(_dropdown.config.data, function (key, item) {
408        if ('' + item.id == '' + id) {
409          item.selected = false;
410          return false;
411        }
412      });
413
414      _dropdown.selectAmount--;
415      _dropdown.$el.find('[data-value="' + id + '"]').removeClass('dropdown-chose');
416      _dropdown.$el.find('[value="' + id + '"]').prop('selected', false).removeAttr('selected');
417      $target.closest('.dropdown-selected').remove();
418      _config.choice.call(_dropdown, event);
419
420      return false;
421    },
422    clearAll: function (event) {
423      var _dropdown = this;
424      var _config = _dropdown.config;
425      event && event.preventDefault();
426      console.log(this)
427      this.$choseList.find('.del').each(function (index, el) {
428        $(el).trigger('click');
429      });
430
431      if (_config.minCount > 0) {
432        minItemsAlert.call(_dropdown);
433      }
434
435      this.$el.find('.dropdown-display').removeAttr('title');
436      return false;
437    }
438  };
439
440  function Dropdown(options, el) {
441    this.$el = $(el);
442    this.$select = this.$el.find('select');
443    this.placeholder = this.$select.attr('placeholder');
444    this.config = options;
445    this.name = [];
446    this.isSingleSelect = !this.$select.prop('multiple');
447    this.selectAmount = 0;
448    this.itemLimitAlertTimer = null;
449    this.isLabelMode = this.config.multipleMode === 'label';
450    this.init();
451  }
452
453  Dropdown.prototype = {
454    init: function () {
455      var _this = this;
456      var _config = _this.config;
457      var $el = _this.$el;
458      _this.$select.hide();
459      //  判断dropdown是否单选,是否token模式
460      $el.addClass(_this.isSingleSelect ? 'dropdown-single' : _this.isLabelMode ? 'dropdown-multiple-label' : 'dropdown-multiple');
461
462      if (_config.data.length === 0) {
463        _config.data = selectToObject(_this.$select);
464      }
465
466      var processResult = objectToSelect.call(_this, _config.data);
467
468      _this.name = processResult[1];
469      _this.selectAmount = processResult[2];
470      _this.$select.html(processResult[0]);
471      _this.renderSelect();
472      // disabled权重高于readonly
473      _this.changeStatus(_config.disabled ? 'disabled' : _config.readonly ? 'readonly' : false);
474
475      _this.config.init();
476    },
477    // 渲染 select 为 dropdown
478    renderSelect: function (isUpdate, isCover) {
479      var _this = this;
480      var $el = _this.$el;
481      var $select = _this.$select;
482      var elemLi = selectToDiv($select.prop('outerHTML'));
483      var template;
484
485      if (isUpdate) {
486        $el.find('ul')[isCover ? 'html' : 'append'](elemLi);
487      } else {
488        template = createTemplate.call(_this).replace('{{ul}}', '<ul>' + elemLi + '</ul>');
489        $el.append(template).find('ul').removeAttr('style class');
490      }
491
492      if (isCover) {
493        _this.name = [];
494        _this.$el.find('.dropdown-selected').remove();
495        _this.$select.val('');
496      }
497
498      _this.$choseList = $el.find('.dropdown-chose-list');
499
500      if (!_this.isLabelMode) {
501        _this.$choseList.html($('<span class="placeholder"></span>').text(_this.placeholder));
502      }
503
504      _this.$choseList.prepend(_this.name ? _this.name.join('') : []);
505    },
506    bindEvent: function () {
507      var _this = this;
508      var $el = _this.$el;
509      var openHandle = isSafari ? EVENT_SPACE.click : EVENT_SPACE.focus;
510
511      $el.on(EVENT_SPACE.click, function (event) {
512        event.stopPropagation();
513      });
514
515      $el.on(EVENT_SPACE.click, '.del', $.proxy(action.del, _this));
516
517      // show
518      if (_this.isLabelMode) {
519        $el.on(EVENT_SPACE.click, '.dropdown-display-label', function () {
520          $el.find('input').focus();
521        });
522        if (_this.config.searchable) {
523          $el.on(EVENT_SPACE.focus, 'input', $.proxy(action.show, _this));
524        } else {
525          $el.on(EVENT_SPACE.click, $.proxy(action.show, _this));
526        }
527        $el.on(EVENT_SPACE.keydown, 'input', function (event) {
528          if (event.keyCode === 8 && this.value === '' && _this.name.length) {
529            $el.find('.del').eq(-1).trigger('click');
530          }
531        });
532      } else {
533        $el.on(openHandle, '.dropdown-display', $.proxy(action.show, _this));
534        $el.on(openHandle, '.dropdown-clear-all', $.proxy(action.clearAll, _this));
535      }
536
537      // 搜索
538      $el.on(EVENT_SPACE.keyup, 'input', $.proxy(action.search, _this));
539
540      // 按下enter键设置token
541      $el.on(EVENT_SPACE.keyup, function (event) {
542        var keyCode = event.keyCode;
543        var KC = KEY_CODE;
544        if (keyCode === KC.enter) {
545          $.proxy(_this.isSingleSelect ? action.singleChoose : action.multiChoose, _this, event)();
546        }
547      });
548
549      // 按下上下键切换token
550      $el.on(EVENT_SPACE.keydown, $.proxy(action.control, _this));
551
552      $el.on(EVENT_SPACE.click, 'li[tabindex]', $.proxy(_this.isSingleSelect ? action.singleChoose : action.multiChoose, _this));
553    },
554    unbindEvent: function () {
555      var _this = this;
556      var $el = _this.$el;
557      var openHandle = isSafari ? EVENT_SPACE.click : EVENT_SPACE.focus;
558
559      $el.off(EVENT_SPACE.click);
560      $el.off(EVENT_SPACE.click, '.del');
561
562      // show
563      if (_this.isLabelMode) {
564        $el.off(EVENT_SPACE.click, '.dropdown-display-label');
565        $el.off(EVENT_SPACE.focus, 'input');
566        $el.off(EVENT_SPACE.keydown, 'input');
567      } else {
568        $el.off(openHandle, '.dropdown-display');
569        $el.off(openHandle, '.dropdown-clear-all');
570      }
571      // 搜索
572      $el.off(EVENT_SPACE.keyup, 'input');
573      // 按下enter键设置token
574      $el.off(EVENT_SPACE.keyup);
575      // 按下上下键切换token
576      $el.off(EVENT_SPACE.keydown);
577      $el.off(EVENT_SPACE.click, '[tabindex]');
578    },
579    changeStatus: function (status) {
580      var _this = this;
581      if (status === 'readonly') {
582        _this.unbindEvent();
583      } else if (status === 'disabled') {
584        _this.$select.prop('disabled', true);
585        _this.unbindEvent();
586      } else {
587        _this.$select.prop('disabled', false);
588        _this.bindEvent();
589      }
590    },
591    update: function (data, isCover) {
592      var _this = this;
593      var _config = _this.config;
594      var $el = _this.$el;
595      var _isCover = isCover || false;
596
597      if (Object.prototype.toString.call(data) !== '[object Array]') {
598        return;
599      }
600
601      _config.data = _isCover ? data.slice(0) : _config.data.concat(data);
602
603      var processResult = objectToSelect.call(_this, _config.data);
604
605      _this.name = processResult[1];
606      _this.selectAmount = processResult[2];
607      _this.$select.html(processResult[0]);
608      _this.renderSelect(true, _isCover);
609    },
610    destroy: function () {
611      this.unbindEvent();
612      this.$el.children().not('select').remove();
613      this.$el.removeClass('dropdown-single dropdown-multiple-label dropdown-multiple');
614      this.$select.show();
615    },
616    choose: function (values, status) {
617      var valArr = Object.prototype.toString.call(values) === '[object Array]' ? values : [values];
618      var _this = this;
619      var _status = status !== void 0 ? !!status : true
620      $.each(valArr, function (index, value) {
621        var $target = _this.$el.find('[data-value="' + value + '"]');
622        var targetStatus = $target.hasClass('dropdown-chose');
623        if (targetStatus !== _status) {
624          $target.trigger(EVENT_SPACE.click, status || true)
625        }
626      });
627    },
628    reset: function () {
629      action.clearAll.call(this)
630    }
631  };
632
633  $(document).on('click.dropdown', function () {
634    $('.dropdown-single,.dropdown-multiple,.dropdown-multiple-label').removeClass('active');
635  });
636
637  $.fn.dropdown = function (options) {
638    this.each(function (index, el) {
639      $(el).data('dropdown', new Dropdown($.extend(true, {}, settings, options), el));
640    });
641    return this;
642  }
643})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.