UNPKG

kist-selectdown

Version:
689 lines (540 loc) 16.7 kB
/*! kist-selectdown 0.2.2 - Select with customizable menu. | Author: Ivan Nikolić <niksy5@gmail.com> (http://ivannikolic.com/), 2016 | License: MIT */ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ (function (global){ require(6); var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); var meta = require(11); var dom = require(2); var events = require(4); var instance = require(8); var getClassSelector = require(5); var emit = require(3)(meta.name); /** * @param {Number|String} val * * @return {Number|String} */ function valCast ( val ) { return $.isNumeric(val) ? Number(val) : val; } /** * @param {Number|String} val * * @return {Function} */ function filterOptions ( val ) { /** * @param {Integer} index * @param {Element} el * * @return {Boolean} */ return function ( index, el ) { return valCast(this.getOptionToggler($(el)).data('val')) === valCast(val); }; } /** * @class * * @param {Element} element * @param {Object} options */ var Selectdown = module.exports = function ( element, options ) { this.element = element; this.options = $.extend(true, {}, this.defaults, options); this.setupInstance(); this.setupDom(); this.setupEvents(); emit(this, 'create', [this.$el, this.$select]); }; $.extend(Selectdown.prototype, { destroy: function () { this.destroyDom(); this.destroyEvents(); this.destroyInstance(); }, /** * @param {Mixed} val */ setValue: function ( val ) { this.$el.val(val); }, /** * @param {jQuery} $el * * @return {Mixed} */ getValue: function ( $el ) { $el = $el || this.$el; return $el.val(); }, /** * @param {jQuery} $el * * @return {String} */ getContent: function ( $el ) { $el = $el || this.$el.children(':selected'); return $el.html(); }, /** * @param {Mixed} val * * @return {jQuery} */ getOriginalOption: function ( val ) { return this.$el.children('option').filter(function ( index, el ) { return valCast(el.value) === valCast(val); }); }, /** * Disable select button * * @param {Boolean} state */ disableSelect: function ( state ) { this.$select.prop('disabled', state); }, /** * Disable option button * * @param {jQuery} $option * @param {Boolean} state */ disableOption: function ( $option, state ) { $option.prop('disabled', state); }, /** * @param {String} content */ renderSelect: function ( content ) { this.$select.html(this.options.templates.select.call(this.element, { content: content })); }, renderOptions: function () { this.$optionItem = this.$el.children().map($.proxy(function ( index, el ) { var $el = $(el); var elId = meta.ns.htmlClass + '-option-' + this.uid + '-' + index; var $optionItem = $('<li />', { 'class': this.options.classes.optionItem }); var $option = $('<button />', { id: elId, tabindex: -1, type: 'button', 'class': this.options.classes.option, role: 'option', html: this.options.templates.option.call(this.element, { content: $el.html(), value: $el.val(), selected: $el.prop('selected'), disabled: $el.prop('disabled') }) }); // Adding this through mapping object cast value to integer $option.data('val', $el.val()); // If this option is disabled, we should render it like that this.disableOption($option, $el.prop('disabled')); $option.appendTo($optionItem); return $optionItem.get(); }, this)); this.$optionList.html(this.$optionItem); }, /** * @param {Number|String} val * * @return {jQuery} */ getOption: function ( val ) { var fn = filterOptions(val); return this.$optionItem.filter($.proxy(fn, this)); }, /** * @param {Number|String} val * @param {Boolean} preventEmit */ setActiveOption: function ( val, preventEmit ) { var classes = [this.options.classes.isActive, this.options.classes.isFocused].join(' '); this.$activeOptionItem = this.getOption(val); this.$optionItem.removeClass(classes); this.$activeOptionItem.addClass(classes); this.$wrapper.attr('aria-activedescendant', this.getOptionToggler(this.$activeOptionItem).attr('id')); if ( !preventEmit ) { emit(this, 'select', [this.$activeOptionItem, val, this.getOriginalOption(val)]); } }, /** * @param {Number|String} val */ setFocusedOption: function ( val ) { var classes = [this.options.classes.isFocused].join(' '); this.$focusedOptionItem = this.getOption(val); this.$optionItem.removeClass(classes); this.$focusedOptionItem.addClass(classes); }, /** * @param {jQuery} $item * * @return {jQuery} */ getOptionToggler: function ( $item ) { return $item.children('button'); }, /** * @param {Boolean} bool */ displayOptions: function ( bool ) { var isHidden = this.$optionList.hasClass(this.options.classes.isHidden); if ( bool ) { if ( isHidden ) { emit(this, 'open', [this.$el, this.$select]); this.$optionList.attr('aria-expanded', true); } } else { if ( !isHidden ) { emit(this, 'close', [this.$el, this.$select]); this.$optionList.attr('aria-expanded', false); } } this.$select[!bool ? 'removeClass' : 'addClass'](this.options.classes.isActive); this.$optionList[bool ? 'removeClass' : 'addClass'](this.options.classes.isHidden); }, /** * @param {String} direction */ navigate: function ( direction ) { var $currentOptionItem = this.$optionList.children(getClassSelector(this.options.classes.isFocused)); var $optionItems = this.$optionList.children(); var position = $optionItems.index($currentOptionItem); var $newOptionItem, $newOptionToggler; position = direction === 'down' ? ++position : --position; // If we are at the top, we should go to the bottom, and vice versa :) if ( position >= $optionItems.length ) { position = 0; } else if ( position < 0 ) { position = $optionItems.length - 1; } $newOptionItem = $optionItems.eq(position); $newOptionToggler = this.getOptionToggler($newOptionItem); this.setFocusedOption($newOptionToggler.data('val')); // If this option is disabled, navigate again in the same direction // until enabled one is found if ( $newOptionToggler.prop('disabled') ) { this.navigate(direction); } }, refresh: function () { this.assignInstanceToChildren(); this.renderOptions(); this.renderSelect(this.getContent()); this.setActiveOption(this.getValue()); }, defaults: { classes: { wrapper: meta.ns.htmlClass, originalSelect: meta.ns.htmlClass + '-originalSelect', select: meta.ns.htmlClass + '-select', optionList: meta.ns.htmlClass + '-optionList', optionItem: meta.ns.htmlClass + '-optionItem', option: meta.ns.htmlClass + '-option', isActive: 'is-active', isHidden: 'is-hidden', isFocused: 'is-focused' }, create: $.noop, open: $.noop, close: $.noop, select: $.noop, templates: { select: function ( data ) { return data.content; }, option: function ( data ) { return data.content; } } } }, dom, events, instance); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"11":11,"2":2,"3":3,"4":4,"5":5,"6":6,"8":8}],2:[function(require,module,exports){ (function (global){ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); var meta = require(11); module.exports = { $doc: $(document), setupDom: function () { var listId = meta.ns.htmlClass + '-list-' + this.uid; this.$el = $(this.element); this.$el .addClass(this.options.classes.originalSelect) .attr({ tabindex: -1, 'aria-autocomplete': 'list', 'aria-owns': listId, 'aria-readonly': true }); // Setup wrapper this.$wrapper = $('<div />', { 'class': this.options.classes.wrapper, role: 'combobox', 'aria-activedescendant': '' }); // Setup select this.$select = $('<button />', { type: 'button', 'class': this.options.classes.select, 'aria-controls': listId }); // Setup option list this.$optionList = $('<ul />', { id: listId, 'class': [this.options.classes.optionList, this.options.classes.isHidden].join(' '), role: 'listbox', 'aria-expanded': false }); this.renderOptions(); this.renderSelect(this.getContent()); this.setActiveOption(this.getValue(), true); this.setFocusedOption(this.getValue()); // If select is disabled at start, we should render it like that this.disableSelect(this.$el.prop('disabled')); // Go! this.$wrapper .insertBefore(this.$el) .append(this.$el, this.$select, this.$optionList); }, destroyDom: function () { this.$el .removeClass(this.options.classes.originalSelect) .insertBefore(this.$wrapper) .removeAttr('aria-autocomplete aria-owns aria-readonly tabindex'); this.$wrapper.remove(); } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"11":11}],3:[function(require,module,exports){ (function (global){ /* jshint maxparams:false */ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); /** * @param {String} name * * @return {Function} */ module.exports = function ( name ) { /** * @param {Object} ctx * @param {String} eventName * @param {Array} data * @param {jQuery} triggerEl */ return function ( ctx, eventName, data, triggerEl ) { var el = (ctx.dom && ctx.dom.el) || ctx.$el || $({}); if ( ctx.options[eventName] ) { ctx.options[eventName].apply((el.length === 1 ? el[0] : el.toArray()), data); } (triggerEl || el).trigger(((name || '') + eventName).toLowerCase(), data); }; }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],4:[function(require,module,exports){ (function (global){ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); var meta = require(11); var key = require(10); var getClassSelector = require(5); /** * @this {Selectdown} * * @param {Object} e */ function globalEventsHandler ( e ) { var keycode = e.which; var $target = $(e.target); if ( keycode === key.escape || ($target.closest(this.$select) && $target.closest(this.$wrapper).length === 0) || (!$target.closest(this.$select) && $target.closest(getClassSelector(this.options.classes.option)).length === 0) ) { this.displayOptions(false); } } /** * @this {Selectdown} * * @param {Object} e */ function keyboardNavigation ( e ) { var keycode = e.which; // Only navigate if list is opened if ( this.$optionList.hasClass(this.options.classes.isHidden) ) { return; } switch ( keycode ) { case key.up: case key.down: this.navigate(keycode === key.down ? 'down' : 'up'); break; case key.enter: this.setValue(this.getOptionToggler(this.$focusedOptionItem).data('val')); break; } } module.exports = { setupEvents: function () { this.$el .on('change' + this.ens, $.proxy(function ( e, type ) { if ( type !== meta.name + 'syntheticChange' ) { this.renderSelect(this.getContent()); this.setActiveOption(this.getValue()); } }, this)); this.$select .on('click' + this.ens, $.proxy(function () { this.displayOptions(this.$optionList.hasClass(this.options.classes.isHidden)); }, this)); this.$wrapper.on('click' + this.ens, getClassSelector(this.options.classes.option), $.proxy(function ( e ) { var $el = $(e.currentTarget); var val = $el.data('val'); this.setValue(val); this.displayOptions(false); this.$el.trigger('change', [meta.name + 'syntheticChange']); }, this)); this.$doc .on('click' + this.ens, $.proxy(globalEventsHandler, this)) .on('keydown' + this.ens, $.proxy(globalEventsHandler, this)) .on('keydown' + this.ens, $.proxy(keyboardNavigation, this)); }, destroyEvents: function () { this.$el.off(this.ens); this.$select.off(this.ens); this.$wrapper.off(this.ens); this.$doc.off(this.ens); } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"10":10,"11":11,"5":5}],5:[function(require,module,exports){ /** * @param {String} className * * @return {String} */ module.exports = function ( className ) { return '.' + className.split(' ').join('.'); }; },{}],6:[function(require,module,exports){ (function (global){ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); var meta = require(11); var valHooks = $.valHooks; var propHooks = $.propHooks; var hooks = { set: function ( el, val, prop ) { var self = $.data(el, meta.name); var $el; if ( self ) { if ( prop === 'disabled' ) { $el = $(el); if ( $el.is('select') ) { self.disableSelect(val); } else { self.disableOption(self.getOptionToggler(self.getOption(el.value)), val); } } else { var $option = self.getOriginalOption(val); self.renderSelect(self.getContent($option)); self.setActiveOption(self.getValue($option)); } } return undefined; } }; valHooks.select = hooks; propHooks.value = hooks; propHooks.disabled = hooks; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"11":11}],7:[function(require,module,exports){ (function (global){ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); var Ctor = require(1); var meta = require(11); var isPublicMethod = require(9)(meta.publicMethods); /** * @param {Object|String} options * * @return {jQuery} */ var plugin = $.fn[meta.name] = module.exports = function ( options ) { options = options || {}; return this.each(function () { var instance = $.data(this, meta.name); if ( isPublicMethod(options) && instance ) { instance[options](); } else if ( typeof(options) === 'object' && !instance ) { $.data(this, meta.name, new Ctor(this, options)); } }); }; plugin.defaults = Ctor.prototype.defaults; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"1":1,"11":11,"9":9}],8:[function(require,module,exports){ (function (global){ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); var meta = require(11); var instance = 0; module.exports = { setupInstance: function () { this.uid = instance++; this.ens = meta.ns.event + '.' + this.uid; this.assignInstanceToChildren(); }, destroyInstance: function () { $.removeData(this.element, meta.name); this.removeInstanceFromChildren(); }, assignInstanceToChildren: function () { $(this.element).children('option').data(meta.name, this); }, removeInstanceFromChildren: function () { $(this.element).children('option').removeData(meta.name); } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"11":11}],9:[function(require,module,exports){ (function (global){ var $ = (typeof window !== "undefined" ? window.$ : typeof global !== "undefined" ? global.$ : null); /** * @param {Array} methods * * @return {Function} */ module.exports = function ( methods ) { /** * @param {String} name * * @return {Boolean} */ return function ( name ) { return typeof(name) === 'string' && $.inArray(name, methods || []) !== -1; }; }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],10:[function(require,module,exports){ module.exports = { enter: 13, escape: 27, up: 38, down: 40 }; },{}],11:[function(require,module,exports){ module.exports = { name: 'selectdown', ns: { htmlClass: 'kist-Selectdown', event: '.kist.selectdown', dataAttr: 'kist-selectdown' }, publicMethods: ['destroy','refresh'] }; },{}]},{},[7]);