fuelux
Version:
Base Fuel UX styles and controls
392 lines (315 loc) • 9.87 kB
JavaScript
/* global jQuery:true */
/*
* Fuel UX Combobox
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN UMD WRAPPER PREFACE --
// For more information on UMD visit:
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
(function umdFactory (factory) {
if (typeof define === 'function' && define.amd) {
// if AMD loader is available, register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// OR use browser globals if AMD is not present
factory(jQuery);
}
}(function ComboboxWrapper ($) {
// -- END UMD WRAPPER PREFACE --
// -- BEGIN MODULE CODE HERE --
var old = $.fn.combobox;
// COMBOBOX CONSTRUCTOR AND PROTOTYPE
var Combobox = function (element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.combobox.defaults, options);
this.$dropMenu = this.$element.find('.dropdown-menu');
this.$input = this.$element.find('input');
this.$button = this.$element.find('.btn');
this.$inputGroupBtn = this.$element.find('.input-group-btn');
this.$element.on('click.fu.combobox', 'a', $.proxy(this.itemclicked, this));
this.$element.on('change.fu.combobox', 'input', $.proxy(this.inputchanged, this));
this.$element.on('shown.bs.dropdown', $.proxy(this.menuShown, this));
this.$input.on('keyup.fu.combobox', $.proxy(this.keypress, this));
// set default selection
this.setDefaultSelection();
// if dropdown is empty, disable it
var items = this.$dropMenu.children('li');
if( items.length === 0) {
this.$button.addClass('disabled');
}
// filter on load in case the first thing they do is press navigational key to pop open the menu
if (this.options.filterOnKeypress) {
this.options.filter(this.$dropMenu.find('li'), this.$input.val(), this);
}
};
Combobox.prototype = {
constructor: Combobox,
destroy: function () {
this.$element.remove();
// remove any external bindings
// [none]
// set input value attrbute in markup
this.$element.find('input').each(function () {
$(this).attr('value', $(this).val());
});
// empty elements to return to original markup
// [none]
return this.$element[0].outerHTML;
},
doSelect: function ($item) {
if (typeof $item[0] !== 'undefined') {
// remove selection from old item, may result in remove and
// re-addition of class if item is the same
this.$element.find('li.selected:first').removeClass('selected');
// add selection to new item
this.$selectedItem = $item;
this.$selectedItem.addClass('selected');
// update input
this.$input.val(this.$selectedItem.text().trim());
} else {
// this is a custom input, not in the menu
this.$selectedItem = null;
this.$element.find('li.selected:first').removeClass('selected');
}
},
clearSelection: function () {
this.$selectedItem = null;
this.$input.val('');
this.$dropMenu.find('li').removeClass('selected');
},
menuShown: function () {
if (this.options.autoResizeMenu) {
this.resizeMenu();
}
},
resizeMenu: function () {
var width = this.$element.outerWidth();
this.$dropMenu.outerWidth(width);
},
selectedItem: function () {
var item = this.$selectedItem;
var data = {};
if (item) {
var txt = this.$selectedItem.text().trim();
data = $.extend({
text: txt
}, this.$selectedItem.data());
} else {
data = {
text: this.$input.val().trim(),
notFound: true
};
}
return data;
},
selectByText: function (text) {
var $item = $([]);
this.$element.find('li').each(function () {
if ((this.textContent || this.innerText || $(this).text() || '').trim().toLowerCase() === (text || '').trim().toLowerCase()) {
$item = $(this);
return false;
}
});
this.doSelect($item);
},
selectByValue: function (value) {
var selector = 'li[data-value="' + value + '"]';
this.selectBySelector(selector);
},
selectByIndex: function (index) {
// zero-based index
var selector = 'li:eq(' + index + ')';
this.selectBySelector(selector);
},
selectBySelector: function (selector) {
var $item = this.$element.find(selector);
this.doSelect($item);
},
setDefaultSelection: function () {
var selector = 'li[data-selected=true]:first';
var item = this.$element.find(selector);
if (item.length > 0) {
// select by data-attribute
this.selectBySelector(selector);
item.removeData('selected');
item.removeAttr('data-selected');
}
},
enable: function () {
this.$element.removeClass('disabled');
this.$input.removeAttr('disabled');
this.$button.removeClass('disabled');
},
disable: function () {
this.$element.addClass('disabled');
this.$input.attr('disabled', true);
this.$button.addClass('disabled');
},
itemclicked: function (e) {
this.$selectedItem = $(e.target).parent();
// set input text and trigger input change event marked as synthetic
this.$input.val(this.$selectedItem.text().trim()).trigger('change', {
synthetic: true
});
// pass object including text and any data-attributes
// to onchange event
var data = this.selectedItem();
// trigger changed event
this.$element.trigger('changed.fu.combobox', data);
e.preventDefault();
// return focus to control after selecting an option
this.$element.find('.dropdown-toggle').focus();
},
keypress: function (e) {
var ENTER = 13;
//var TAB = 9;
var ESC = 27;
var LEFT = 37;
var UP = 38;
var RIGHT = 39;
var DOWN = 40;
var IS_NAVIGATIONAL = (
e.which === UP ||
e.which === DOWN ||
e.which === LEFT ||
e.which === RIGHT
);
if(this.options.showOptionsOnKeypress && !this.$inputGroupBtn.hasClass('open')){
this.$button.dropdown('toggle');
this.$input.focus();
}
if (e.which === ENTER) {
e.preventDefault();
var selected = this.$dropMenu.find('li.selected').text().trim();
if(selected.length > 0){
this.selectByText(selected);
}else{
this.selectByText(this.$input.val());
}
this.$inputGroupBtn.removeClass('open');
} else if (e.which === ESC) {
e.preventDefault();
this.clearSelection();
this.$inputGroupBtn.removeClass('open');
} else if (this.options.showOptionsOnKeypress) {
if (e.which === DOWN || e.which === UP) {
e.preventDefault();
var $selected = this.$dropMenu.find('li.selected');
if ($selected.length > 0) {
if (e.which === DOWN) {
$selected = $selected.next(':not(.hidden)');
} else {
$selected = $selected.prev(':not(.hidden)');
}
}
if ($selected.length === 0){
if (e.which === DOWN) {
$selected = this.$dropMenu.find('li:not(.hidden):first');
} else {
$selected = this.$dropMenu.find('li:not(.hidden):last');
}
}
this.doSelect($selected);
}
}
// Avoid filtering on navigation key presses
if (this.options.filterOnKeypress && !IS_NAVIGATIONAL) {
this.options.filter(this.$dropMenu.find('li'), this.$input.val(), this);
}
this.previousKeyPress = e.which;
},
inputchanged: function (e, extra) {
var val = $(e.target).val();
// skip processing for internally-generated synthetic event
// to avoid double processing
if (extra && extra.synthetic) {
this.selectByText(val);
return;
}
this.selectByText(val);
// find match based on input
// if no match, pass the input value
var data = this.selectedItem();
if (data.text.length === 0) {
data = {
text: val
};
}
// trigger changed event
this.$element.trigger('changed.fu.combobox', data);
}
};
Combobox.prototype.getValue = Combobox.prototype.selectedItem;
// COMBOBOX PLUGIN DEFINITION
$.fn.combobox = function (option) {
var args = Array.prototype.slice.call(arguments, 1);
var methodReturn;
var $set = this.each(function () {
var $this = $(this);
var data = $this.data('fu.combobox');
var options = typeof option === 'object' && option;
if (!data) {
$this.data('fu.combobox', (data = new Combobox(this, options)));
}
if (typeof option === 'string') {
methodReturn = data[option].apply(data, args);
}
});
return (methodReturn === undefined) ? $set : methodReturn;
};
$.fn.combobox.defaults = {
autoResizeMenu: true,
filterOnKeypress: false,
showOptionsOnKeypress: false,
filter: function filter (list, predicate, self) {
var visible = 0;
self.$dropMenu.find('.empty-indicator').remove();
list.each(function (i) {
var $li = $(this);
var text = $(this).text().trim();
$li.removeClass();
if (text === predicate) {
$li.addClass('text-success');
visible++;
} else if (text.substr(0, predicate.length) === predicate) {
$li.addClass('text-info');
visible++;
} else {
$li.addClass('hidden');
}
});
if (visible === 0) {
self.$dropMenu.append('<li class="empty-indicator text-muted"><em>No Matches</em></li>');
}
}
};
$.fn.combobox.Constructor = Combobox;
$.fn.combobox.noConflict = function () {
$.fn.combobox = old;
return this;
};
// DATA-API
$(document).on('mousedown.fu.combobox.data-api', '[data-initialize=combobox]', function (e) {
var $control = $(e.target).closest('.combobox');
if (!$control.data('fu.combobox')) {
$control.combobox($control.data());
}
});
// Must be domReady for AMD compatibility
$(function () {
$('[data-initialize=combobox]').each(function () {
var $this = $(this);
if (!$this.data('fu.combobox')) {
$this.combobox($this.data());
}
});
});
// -- BEGIN UMD WRAPPER AFTERWORD --
}));
// -- END UMD WRAPPER AFTERWORD --