(function($) { window.CustomSelect = function(select) { if (!(this instanceof CustomSelect)) return new CustomSelect(select); if (!select) return false; if (typeof select === 'string') { if (select === 'close') return this.close(); else if (!select.indexOf('CustomSelect-')) return this._cache[select]; select = $(select); } if (!select.size()) return false; this._initOnce(); if (select.size() > 1) { select.each(function () { new CustomSelect($(this)); }); } var ID = select.attr('data-custom-select-id'); if (ID) return this._cache[ID]; this._select = select; this._ID = 'CustomSelect-' + (new Date()).getTime() + String.fromCharCode(Math.floor(Math.random() * 26) + 65) + String.fromCharCode(Math.floor(Math.random() * 26) + 97); this._select.attr('data-custom-select-id', this._ID); this._cache[this._ID] = this; this._draw(); } CustomSelect.prototype = { _cache: {}, _inited: false, _open: false, _select: null, _replacer: null, _value: null, _list: null, _ID: null, _multiple: false, _multiple_lastItem: null, _multiple_dragStart: false, _multiple_maxItems: -1, _editable: false, _editableInput: null, _editableEmptyMsg: null, _initOnce: function () { if (this._inited) return; CustomSelect.prototype._inited = true; $(document).on('mousedown', function () { CustomSelect('close'); }).on('mouseup', function () { $('.b-select-multiple').each(function () { var ID = $(this).attr('id'), s = CustomSelect(ID); if (s._multiple) s._multiple_dragStart = false; }); }).on('keydown', function (e) { if (e.which == 27) { CustomSelect('close'); } }); }, _draw: function () { this._editable = !!(this._select.attr('data-editable') === 'true'); this._multiple = !!this._select.attr('multiple'); this._select.after(this._getHTML()) .on('change', this._listeners.select.change); if (this._editable) { this._select.attr('data-default-tabindex', this._select.attr('tabindex') || '').attr('tabindex', '-1'); } else { this._select .on('keydown', this._listeners.select.keydown) .on('focus', this._listeners.select.focus) .on('blur', this._listeners.select.blur); } this._replacer = $('#' + this._ID); this._replacer.append(this._select); this._value = $('#' + this._ID + '-value'); this._list = $('#' + this._ID + '-list'); this._list.find('.b-select-option') .on('mousedown', this._listeners.replacer.options.mousedown) .on('mouseup', this._listeners.replacer.options.mouseup) .on('mouseenter', this._listeners.replacer.options.mouseenter); if (!this._multiple || this._editable) { this._replacer.append(this._select).on('mousedown', this._listeners.replacer.mousedown); } if (this._editable) { this._editableInput = $('#' + this._ID + '-value-input'); this._editableInput .on('focus', this._listeners.replacer.editableInput.focus) .on('blur', this._listeners.replacer.editableInput.blur) .on('keydown', this._listeners.replacer.editableInput.keydown) .on('keyup', this._listeners.replacer.editableInput.keyup); this._value .on('click', '.b-select-value-tag-remove', this._listeners.replacer.tags.click) .on('mousedown', '.b-select-value-tag-remove', this._listeners.stopPropagation); if (!isSupported('placeholder')) this._editableInput.blur(); } this._list.on('mousedown', this._listeners.stopPropagation) this._multiple_lastItem = this._select.find(':selected:first'); this._multiple_maxItems = parseInt(this._select.attr('data-max')); if (isNaN(this._multiple_maxItems)) { this._multiple_maxItems = -1; } this.updateValue(); }, destroy: function () { this._select.off('change', this._listeners.select.change); if (this._editable) { this._select.attr('tabindex', this._select.attr('data-default-tabindex')); } else { this._select .off('keydown', this._listeners.select.keydown) .off('focus', this._listeners.select.focus) .off('blur', this._listeners.select.blur); } this._list.find('.b-select-option') .off('mousedown', this._listeners.replacer.options.mousedown) .off('mouseup', this._listeners.replacer.options.mouseup) .off('mouseenter', this._listeners.replacer.options.mouseenter); if (!this._multiple || this._editable) { this._replacer.off('mousedown', this._listeners.replacer.mousedown); } if (this._editable) { this._editableInput .off('focus', this._listeners.replacer.editableInput.focus) .off('blur', this._listeners.replacer.editableInput.blur) .off('keydown', this._listeners.replacer.editableInput.keydown) .off('keyup', this._listeners.replacer.editableInput.keyup); this._value .off('click', '.b-select-value-tag-remove', this._listeners.replacer.tags.click) .off('mousedown', '.b-select-value-tag-remove', this._listeners.stopPropagation); } this._list.off('mousedown', this._listeners.stopPropagation) this._replacer.after(this._select).remove(); var ID = this._select.attr('data-custom-select-id'); this._select.removeAttr('data-custom-select-id'); delete CustomSelect.prototype._cache[ID]; }, _getHTML: function () { var self = this, tabindex = this._select.attr('tabindex') || null, placeholder = this._select.attr('data-placeholder') || 'Click to see options…'; return '
' + (this._multiple && !this._editable ? '' : '') + (this._editable ? '
' + '' + '
' : this._multiple ? '' : '
' + this._select.find(':selected').text() + '
') + '' + '
'; }, _getTagHTML: function (value, caption) { return '' + caption + ' ' + ''; }, open: function () { this.close(); this._replacer.addClass('b-select-open'); this._open = true; this._editable || this._select.focus(); var self = this, fontSize = this._replacer.css('font-size'), minWidth = Math.floor(this._replacer.innerWidth()), selectTop = this._replacer.offset().top, scrollTop = $(window).scrollTop(), selectScrollTop = this._list.scrollTop(), // keep scroll position after moving list in DOM lineHeight = parseInt(fontSize) * 2, rowsNumber = this._list.find('.b-select-list-item').size(), listHeight = 0; rowsNumber = rowsNumber > 12 ? 12 : rowsNumber; listHeight = lineHeight * rowsNumber; // if no space below select if (selectTop + this._replacer.height() + listHeight > scrollTop + $(window).height()) { // and enough space above select if (selectTop - listHeight > scrollTop) { // then the list should turn up this._replacer.addClass('b-select-upside-down'); this._list.addClass('b-select-upside-down'); } } var left = this._list.offset().left, top = this._list.offset().top; this._list.appendTo('body').css({ bottom: 'auto', left: left + 'px', top: top + 'px', minWidth: minWidth + 'px', fontSize: fontSize }).find('.b-select-option-active').removeClass('b-select-option-active'); if (typeof this._select.val() === 'string') { this._list.find('.b-select-option[data-value="' + this._select.val().addSlashes() + '"]').addClass('b-select-option-active'); } // timeout is for css3 animation setTimeout(function () { self._list.addClass('b-select-open-list').scrollTop(selectScrollTop); }, 0); this._updateScroll(); }, _updatePosition: function () { var selectOffset = this._replacer.offset(), left = selectOffset.left, top = selectOffset.top; if (this._replacer.hasClass('b-select-upside-down')) { top -= this._list.outerHeight() - 1; //1 is for border } else { top += this._replacer.outerHeight() - 1; // 1 is for border } this._list.css({ left: left + 'px', top: top + 'px' }); }, close: function () { $('.b-select-open-list').each(function () { var $this = $(this), ID = $this.attr('data-custom-select-id'), replacer = $('#' + ID), scrollTop = $this.scrollTop(); // keep scroll position after moving list in DOM replacer.removeClass('b-select-open b-select-upside-down'); CustomSelect(ID)._open = false; $this.css({ bottom: '', left: '', top: '', minWidth: '' }).removeClass('b-select-open-list b-select-upside-down').appendTo(replacer).scrollTop(scrollTop); }); }, toggle: function () { if (this._open) this.close(); else this.open(); }, updateValue: function (isChangedUp) { if (!this._multiple) { var selected = this._select.find(':selected'); if (this._editable) { if (selected.size()) { this._editableInput.val(selected.text()); } } else { var img = selected.attr('data-image'), val = (img ? '' : '') + selected.text(); this._value.html(val); } this._list.find('.b-select-option-active').removeClass('b-select-option-active'); if (this._select.val() != null) this._list.find('.b-select-option[data-value="' + this._select.val().addSlashes() + '"]').addClass('b-select-option-active'); } else { var self = this; if (this._editable) { var tags = this._value.find('.b-select-value-tag'); // remove tag if value is not selected anymore tags.each(function () { var $this = $(this), val = $this.attr('data-value'); if (!self._select.find('option[value=' + val.addSlashes() + ']:selected').size()) { $this.remove(); } }); // add new tags this._select.find('option').each(function () { if (!this.selected) return; if (tags.filter('[data-value=' + this.value.addSlashes() + ']').size()) return; var $this = $(this), img = $this.attr('data-image'), val = (img ? '' : '') + $this.text(); self._editableInput.before(self._getTagHTML(this.value, val)); }); // adjust input width var lastTag = this._value.find('.b-select-value-tag:last'); this._editableInput.width('5px'); if (!lastTag[0] || lastTag[0].offsetTop < this._editableInput[0].offsetTop) { this._editableInput.width(''); } else { var valueWidth = this._value.innerWidth(), lastTagWidth = Math.ceil(lastTag.outerWidth(true)), inputWidth = valueWidth - lastTagWidth - lastTag.offset().left + this._value.offset().left - Math.ceil(parseFloat(this._value.css('paddingLeft'))); this._editableInput.width(inputWidth + 'px'); } } else { this._list.find('.b-select-option-active').removeClass('b-select-option-active'); this._select.find('option').each(function () { if (!this.selected) return; var $this = $(this), img = $this.attr('data-image'), val = (img ? '' : '') + $this.text(); self._list.find('.b-select-option[data-value="' + $this.val().addSlashes() + '"]').addClass('b-select-option-active'); }); } } this._updateScroll(isChangedUp); }, setValue: function (val, ctrlKey, shiftKey) { var changed = false; if (this._multiple_maxItems >= 0 && this._select.find(':selected').size() >= this._multiple_maxItems) { return; } if (!this._multiple || this._editable) { this.close(); if (this._editable) { var options = this._select.find('option'); if (val === false) { // then user wishes to leave a field empty; remove all `selected` attributes options.each(function () { if (this.selected) { changed = true; this.selected = false; } }); } else { var target = options.filter('[value=' + val.addSlashes() + ']'); if (!target[0].selected) { changed = true; target[0].selected = true; } } if (this._multiple) this._editableInput.val(''); } else { if (this._select.val() != val) { changed = true; this._select.val(val); } } } else { var options = this._select.find('option'), target = options.filter('[value=' + val.addSlashes() + ']'), lastVal = this._select.val(); if (ctrlKey) { target[0].selected = !target[0].selected; this._multiple_lastItem = target; } else if (shiftKey) { var lastIndex = options.index(this._multiple_lastItem), index = options.index(target); this._select.find('option').each(function (i) { if (lastIndex <= index) { this.selected = i >= lastIndex && i <= index ? true : false; } else { this.selected = i <= lastIndex && i >= index ? true : false; } }); } else { this._select.find('option').each(function () { this.selected = false; }); target[0].selected = true; this._multiple_lastItem = target; } var newVal = this._select.val(); if (lastVal && newVal) { if (lastVal.length != newVal.length) changed = true; else for (var i = lastVal.length; i--;) if (lastVal[i] != newVal[i]) { changed = true; break; } } else if (lastVal != newVal) changed = true; } this._editable || this._select.focus(); changed && this._select.trigger('change'); }, removeVal: function (val) { if (!this._multiple) return false; var options = this._select.find('option'), target = options.filter('[value=' + val.addSlashes() + ']'); if (target[0].selected) { target[0].selected = false; if (this._open) this._updatePosition(); // adjust dropdown list position if neccessary this._select.trigger('change'); } }, filterOptions: function (query) { query = query.split(' '); var options = this._list.find('.b-select-option'), defOptions = this._select.find('option'), reg = [], text, $this, match, val; for (var i = query.length; i--;) reg.push(new RegExp(query[i], 'i')); options.each(function () { $this = $(this); text = $this.text(); val = $this.attr('data-value'); match = !defOptions.filter('[value=' + val.addSlashes() + ']')[0].selected; if (match) { for (var i = reg.length; i--;) match = match && reg[i].test(text); } if (match) $this.show(); else $this.hide().removeClass('b-select-option-active'); }); if (!options.filter(':visible').size()) { if (!this._editableEmptyMsg) { this._editableEmptyMsg = $('
  • ', { id: this._ID + '-emptyMsg', text: 'No mathces found' }).addClass('b-select-list-item b-select-optgroup'); this._list.append(this._editableEmptyMsg); } } else { if (this._editableEmptyMsg) { this._editableEmptyMsg.remove(); this._editableEmptyMsg = null; } if (!options.filter('.b-select-option-active:visible').size()) { this._moveSelection(); } } if ($.browser.msie) { // prevent items disappear, even in ie10. =___= CSS changes just for repaint options.css('z-index', '10'); setTimeout(function () { options.css('z-index', '') }, 5); } if (this._open) this._updatePosition(); // adjust dropdown list position if neccessary }, setTabindex: function (i) { if (this._editable) { this._select.attr('data-default-tabindex', i); this._editableInput.attr('tabindex', i); } else { this._select.attr('tabindex', i); } }, _updateScroll: function (isChangedUp) { var selected = isChangedUp ? this._list.find('.b-select-option-active:first') : this._list.find('.b-select-option-active:last'); if (!selected.size()) return; var itemTop = selected.offset().top, itemBottom = itemTop + selected.height(), listTop = this._list.offset().top, listBottom = listTop + this._list.outerHeight(), listScroll = this._list.scrollTop(); if (itemBottom > listBottom) { this._list.scrollTop(listScroll + itemBottom - listBottom); } else if (itemTop < listTop) { this._list.scrollTop(listScroll + itemTop - listTop); } }, _moveSelection: function (isChangedUp) { var selected = this._list.find('.b-select-option-active:visible'), target; if (!selected.size()) { target = this._list.find('.b-select-option:visible:' + (isChangedUp ? 'last' : 'first')); } else { target = isChangedUp ? selected.prev() : selected.next(); while (target.size() && !target.is(':visible')) { target = isChangedUp ? target.prev() : target.next(); } } if (target.size()) { selected.removeClass('b-select-option-active'); target.addClass('b-select-option-active') this._updateScroll(); } }, _listeners: { select: { change: function (e) { var ID = $(this).attr('data-custom-select-id'); CustomSelect(ID).updateValue(); }, keydown: function (e) { if (e.which >= 33 && e.which <= 40) { // arrows, Home, End, PgUp, PgDwn var ID = $(this).attr('data-custom-select-id'); setTimeout(function () { CustomSelect(ID).updateValue(e.which == 33 || e.which == 36 || e.which == 37 || e.which == 38); }, 0); } else if (e.which == 13) { // Enter CustomSelect('close'); } }, focus: function () { var ID = $(this).attr('data-custom-select-id'); $('#' + ID).addClass('b-select-focused'); }, blur: function () { var ID = $(this).attr('data-custom-select-id'); $('#' + ID).removeClass('b-select-focused'); CustomSelect('close'); } }, replacer: { mousedown: function (e) { e.preventDefault(); e.stopPropagation(); if (e.which == 1) { var ID = $(this).attr('id'), s = CustomSelect(ID); if (s._editable) s._editableInput.focus(); else s.toggle(); } }, tags: { click: function (e) { var $this = $(this), ID = $this.attr('data-custom-select-id'), val = $this.attr('data-value'); CustomSelect(ID).removeVal(val); } }, editableInput: { focus: function (e) { var $this = $(this), ID = $this.attr('data-custom-select-id'), s = CustomSelect(ID); s.filterOptions($this.val()); s.open(); }, blur: function (e) { var $this = $(this), ID = $this.attr('data-custom-select-id'), s = CustomSelect(ID); if (!$.trim($this.val()) && !s._multiple) s.setValue(false); s.close(); }, keydown: function (e) { var $this = $(this), ID = $this.attr('data-custom-select-id'), s = CustomSelect(ID); if (e.which == 27) { e.stopImmediatePropagation(); $this.blur(); } else if (e.which == 38 || e.which == 40) { // arrow up || arrow down e.preventDefault(); if (!s._open) s.open(); s._moveSelection(e.which == 38); } else if (e.which == 13) { // Enter e.preventDefault(); var selected = s._list.find('.b-select-option-active'); if (selected.size()) { s.setValue(selected.attr('data-value')); } } else if (e.which == 8) { // backspace var tags = s._value.find('.b-select-value-tag'); // delete last tag if backspace was pressed in empty field if ($.trim($this.val()) === '' && tags.size()) s.removeVal(tags.filter(':last').attr('data-value')); } }, keyup: function (e) { if (e.which == 13) return; // Enter var $this = $(this), ID = $(this).attr('data-custom-select-id'), s = CustomSelect(ID); if (!s._open) s.open(); s.filterOptions($this.val()); } }, options: { mouseenter: function (e) { var $this = $(this), ID = $this.attr('data-custom-select-id'), s = CustomSelect(ID); if (!s._multiple || s._editable) { s._list.find('.b-select-option-active').removeClass('b-select-option-active'); $this.addClass('b-select-option-active'); } else if (s._multiple_dragStart) { var val = $this.attr('data-value'); s.setValue(val, e.ctrlKey, true); } }, mousedown: function (e) { e.preventDefault(); e.stopPropagation(); if (e.which == 1) { var $this = $(this), ID = $this.attr('data-custom-select-id'), s = CustomSelect(ID); if (s._multiple && !s._editable) { s._multiple_dragStart = true; var val = $this.attr('data-value'); s.setValue(val, e.ctrlKey, e.shiftKey); } } }, mouseup: function (e) { e.stopPropagation(); if (e.which == 1) { var $this = $(this), ID = $this.attr('data-custom-select-id'), s = CustomSelect(ID); if (s._multiple && !s._editable) s._multiple_dragStart = false; else { var val = $this.attr('data-value'); s.setValue(val, e.ctrlKey, e.shiftKey); } } } } }, stopPropagation: function (e) { e.stopPropagation() } } } })( jQuery );