UNPKG

hierarchy-select

Version:

Hierarchy Select jQuery Plugin for Twitter Bootstrap 4

325 lines (316 loc) 13 kB
(function($){ 'use strict'; var HierarchySelect = function(element, options) { this.$element = $(element); this.options = $.extend({}, $.fn.hierarchySelect.defaults, options); this.$button = this.$element.children('button'); this.$menu = this.$element.children('.dropdown-menu'); this.$menuInner = this.$menu.children('.hs-menu-inner'); this.$searchbox = this.$menu.find('input'); this.$hiddenField = this.$element.children('input'); this.previouslySelected = null; this.init(); }; HierarchySelect.prototype = { constructor: HierarchySelect, init: function() { this.setWidth(); this.setHeight(); this.initSelect(); this.clickListener(); this.buttonListener(); this.searchListener(); }, initSelect: function() { var hiddenFieldValue = this.$hiddenField.val(); if (this.options.initialValueSet && hiddenFieldValue && hiddenFieldValue.length > 0) { this.setValue(hiddenFieldValue); } else { var item = this.$menuInner.find('a[data-default-selected]:first'); if (item.length) { this.setValue(item.data('value')); } else { var firstItem = this.$menuInner.find('a:first'); this.setValue(firstItem.data('value')); } } }, setWidth: function() { this.$searchbox.attr('size', 1); // Fix min-width if (this.options.width === 'auto') { var width = this.$menu.width(); this.$element.css('min-width', width + 2 + 'px'); } else if (this.options.width) { this.$element.css('width', this.options.width); this.$menu.css('min-width', this.options.width); this.$button.css('width', '100%'); } else { this.$element.css('min-width', '42px'); } }, setHeight: function() { if (this.options.height) { this.$menu.css('overflow', 'hidden'); this.$menuInner.css({ 'max-height': this.options.height, 'overflow-y': 'auto' }); } }, getText: function() { return this.$button.text(); }, getValue: function() { return this.$hiddenField.val(); }, setValue: function(value) { var a = this.$menuInner.children('a[data-value="' + value + '"]:first'); this.setSelected(a); }, enable: function() { this.$button.removeAttr('disabled'); }, disable: function() { this.$button.attr('disabled', 'disabled'); }, setSelected: function(a) { if (a.length && this.previouslySelected !== a) { var text = a.text(); var value = a.data('value'); this.previouslySelected = a; this.$button.html(text); this.$hiddenField.val(value); this.$menu.find('.active').removeClass('active'); if (this.options.onChange) this.options.onChange(value, text); if (this.options.resetSearchOnSelection) this.resetSearch(); a.addClass('active'); } }, moveUp: function () { var items = this.$menuInner.find('a:not(.d-none,.disabled)'); var active = this.$menuInner.find('.active'); var index = items.index(active); if (typeof items[index - 1] !== 'undefined') { this.$menuInner.find('.active').removeClass('active'); items[index - 1].classList.add('active'); processElementOffset(this.$menuInner[0], items[index - 1]); } }, moveDown: function () { var items = this.$menuInner.find('a:not(.d-none,.disabled)'); var active = this.$menuInner.find('.active'); var index = items.index(active); if (typeof items[index + 1] !== 'undefined') { this.$menuInner.find('.active').removeClass('active'); if (items[index + 1]) { items[index + 1].classList.add('active'); processElementOffset(this.$menuInner[0], items[index + 1]); } } }, resetSearch: function() { this.$searchbox.val('').trigger('propertychange'); }, selectItem: function () { var that = this; var selected = this.$menuInner.find('.active'); if (selected.hasClass('d-none') || selected.hasClass('disabled')) { return; } setTimeout(function() { that.$button.focus(); }, 5); selected && this.setSelected(selected); this.$button.dropdown('toggle'); }, clickListener: function() { var that = this; this.$element.on('show.bs.dropdown', function() { var selected = that.$menuInner.find('.active'); selected && setTimeout(function() { var el = selected[0]; var p = selected[0].parentNode; if (!(p.scrollTop <= el.offsetTop - p.offsetTop && (p.scrollTop + p.clientHeight) > el.offsetTop + el.clientHeight)) { el.parentNode.scrollTop = el.offsetTop - el.parentNode.offsetTop; } }, 0); }); this.$element.on('hide.bs.dropdown', function() { that.previouslySelected && that.setSelected(that.previouslySelected); }); this.$element.on('shown.bs.dropdown', function() { that.previouslySelected = that.$menuInner.find('.active'); that.$searchbox.focus(); }); this.$menuInner.on('click', 'a', function (e) { e.preventDefault(); var $this = $(this); if ($this.hasClass('disabled')) { e.stopPropagation(); } else { that.setSelected($this); } }); }, buttonListener: function () { var that = this; if (this.options.search) { return; } this.$button.on('keydown', function (e) { switch (e.keyCode) { case 9: // Tab if (that.$element.hasClass('show')) { e.preventDefault(); } break; case 13: // Enter if (that.$element.hasClass('show')) { e.preventDefault(); that.selectItem(); } break; case 27: // Esc if (that.$element.hasClass('show')) { e.preventDefault(); e.stopPropagation(); that.$button.focus(); that.previouslySelected && that.setSelected(that.previouslySelected); that.$button.dropdown('toggle'); } break; case 38: // Up if (that.$element.hasClass('show')) { e.preventDefault(); e.stopPropagation(); that.moveUp(); } break; case 40: // Down if (that.$element.hasClass('show')) { e.preventDefault(); e.stopPropagation(); that.moveDown(); } break; default: break; } }); }, searchListener: function() { var that = this; if (!this.options.search) { this.$searchbox.parent().toggleClass('d-none', true); return; } function disableParents(element) { var item = element; var level = item.data('level'); while (typeof item === 'object' && item.length > 0 && level > 1) { level--; item = item.prevAll('a[data-level="' + level + '"]:first'); if (item.hasClass('d-none')) { item.toggleClass('disabled', true); item.removeClass('d-none'); } } } this.$searchbox.on('keydown', function (e) { switch (e.keyCode) { case 9: // Tab e.preventDefault(); e.stopPropagation(); that.$menuInner.click(); that.$button.focus(); break; case 13: // Enter that.selectItem(); break; case 27: // Esc e.preventDefault(); e.stopPropagation(); that.$button.focus(); that.previouslySelected && that.setSelected(that.previouslySelected); that.$button.dropdown('toggle'); break; case 38: // Up e.preventDefault(); that.moveUp(); break; case 40: // Down e.preventDefault(); that.moveDown(); break; default: break; } }); this.$searchbox.on('input propertychange', function (e) { e.preventDefault(); var searchString = that.$searchbox.val().toLowerCase(); var items = that.$menuInner.find('a'); if (searchString.length === 0) { items.each(function() { var item = $(this); item.toggleClass('disabled', false); item.toggleClass('d-none', false); }); } else { items.each(function() { var item = $(this); var text = item.text().toLowerCase(); if (text.indexOf(searchString) !== -1) { item.toggleClass('disabled', false); item.toggleClass('d-none', false); if (that.options.hierarchy) { disableParents(item); } } else { item.toggleClass('disabled', false); item.toggleClass('d-none', true); } }); } }); } }; var Plugin = function(option) { var args = Array.prototype.slice.call(arguments, 1); var method; var chain = this.each(function() { var $this = $(this); var data = $this.data('HierarchySelect'); var options = typeof option === 'object' && option; if (!data) { $this.data('HierarchySelect', (data = new HierarchySelect(this, options))); } if (typeof option === 'string') { method = data[option].apply(data, args); } }); return (method === undefined) ? chain : method; }; var old = $.fn.hierarchySelect; $.fn.hierarchySelect = Plugin; $.fn.hierarchySelect.defaults = { width: 'auto', height: '256px', hierarchy: true, search: true, initialValueSet: false, resetSearchOnSelection: false }; $.fn.hierarchySelect.Constructor = HierarchySelect; $.fn.hierarchySelect.noConflict = function () { $.fn.hierarchySelect = old; return this; }; function processElementOffset(parent, element) { if (parent.offsetHeight + parent.scrollTop < element.offsetTop + element.offsetHeight) { parent.scrollTop = element.offsetTop + element.offsetHeight - parent.offsetHeight; } else if (parent.scrollTop >= element.offsetTop - parent.offsetTop) { parent.scrollTop = element.offsetTop - parent.offsetTop; } } })(jQuery);