mirror of
https://github.com/ExactTarget/fuelux.git
synced 2026-04-26 03:00:10 -04:00
392 lines
9.6 KiB
JavaScript
392 lines
9.6 KiB
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 --
|