;
(function ($, window, document, undefined){
"use strict";
window = (typeof window != 'undefined' && window.Math == Math)? window: (typeof self != 'undefined' && self.Math == Math)? self: Function('return this')();
$.fn.dropdown = function (parameters){
var $allModules = $(this), $document = $(document), moduleSelector = $allModules.selector || '', hasTouch = ('ontouchstart' in document.documentElement), time = new Date().getTime(), performance = [] , query = arguments[0], methodInvoked = (typeof query == 'string'), queryArguments = [] .slice.call(arguments, 1), returnedValue;
$allModules.each(function (elementIndex){
var settings = ($.isPlainObject(parameters))? $.extend(true , {
}
, $.fn.dropdown.settings, parameters): $.extend({
}
, $.fn.dropdown.settings), className = settings.className, message = settings.message, fields = settings.fields, keys = settings.keys, metadata = settings.metadata, namespace = settings.namespace, regExp = settings.regExp, selector = settings.selector, error = settings.error, templates = settings.templates, eventNamespace = '.' + namespace, moduleNamespace = 'module-' + namespace, $module = $(this), $context = $(settings.context), $text = $module.find(selector.text), $search = $module.find(_AN_Read_search('search', selector)), $sizer = $module.find(selector.sizer), $input = $module.find(selector.input), $icon = $module.find(selector.icon), $combo = (_AN_Read_length('length', $module.prev().find(selector.text)) > 0)? $module.prev().find(selector.text): $module.prev(), $menu = $module.children(selector.menu), $item = $menu.find(selector.item), activated = false , itemActivated = false , internalChange = false , element = this, instance = $module.data(moduleNamespace), initialLoad, pageLostFocus, willRefocus, elementNamespace, id, selectObserver, menuObserver, module;
module = {
initialize: function (){
module.debug('Initializing dropdown', settings);
if (module.is.alreadySetup()) {
module.setup.reference();
}
else {
module.setup.layout();
if (settings.values) {
module.change.values(settings.values);
}
module.refreshData();
module.save.defaults();
module.restore.selected();
module.create.id();
module.bind.events();
module.observeChanges();
module.instantiate();
}
}
,
instantiate: function (){
module.verbose('Storing instance of dropdown', module);
instance = module;
$module.data(moduleNamespace, module);
}
,
destroy: function (){
module.verbose('Destroying previous dropdown', $module);
module.remove.tabbable();
$module.off(eventNamespace).removeData(moduleNamespace);
$menu.off(eventNamespace);
$document.off(elementNamespace);
module.disconnect.menuObserver();
module.disconnect.selectObserver();
}
,
observeChanges: function (){
if ('MutationObserver' in window) {
selectObserver = new MutationObserver(module.event.select.mutation);
menuObserver = new MutationObserver(module.event.menu.mutation);
module.debug('Setting up mutation observer', selectObserver, menuObserver);
module.observe.select();
module.observe.menu();
}
}
,
disconnect: {
menuObserver: function (){
if (menuObserver) {
menuObserver.disconnect();
}
}
,
selectObserver: function (){
if (selectObserver) {
selectObserver.disconnect();
}
}
}
,
observe: {
select: function (){
if (module.has.input()) {
selectObserver.observe($module[0], {
childList: true ,
subtree: true }
);
}
}
,
menu: function (){
if (module.has.menu()) {
menuObserver.observe($menu[0], {
childList: true ,
subtree: true }
);
}
}
}
,
create: {
id: function (){
id = (Math.random().toString(16) + '000000000').substr(2, 8);
elementNamespace = '.' + id;
module.verbose('Creating unique id for element', id);
}
,
userChoice: function (values){
var $userChoices, $userChoice, isUserValue, html;
values = values || module.get.userValues();
if (!values) {
return false ;
}
values = $.isArray(values)? values: [values] ;
$.each(values, function (index, value){
if (module.get.item(value) === false ) {
html = settings.templates.addition(module.add.variables(message.addResult, value));
$userChoice = $('
').html(html).attr('data-' + metadata.value, value).attr('data-' + metadata.text, value).addClass(className.addition).addClass(className.item);
if (settings.hideAdditions) {
$userChoice.addClass(className.hidden);
}
$userChoices = ($userChoices === undefined)? $userChoice: $userChoices.add($userChoice);
module.verbose('Creating user choices for value', value, $userChoice);
}
}
);
return $userChoices;
}
,
userLabels: function (value){
var userValues = module.get.userValues();
if (userValues) {
module.debug('Adding user labels', userValues);
$.each(userValues, function (index, value){
module.verbose('Adding custom user value');
module.add.label(value, value);
}
);
}
}
,
menu: function (){
$menu = $('').addClass(className.menu).appendTo($module);
}
,
sizer: function (){
$sizer = $('').addClass(className.sizer).insertAfter($search);
}
}
,
search: function (query){
query = (query !== undefined)? query: module.get.query();
module.verbose('Searching for query', query);
if (module.has.minCharacters(query)) {
module.filter(query);
}
else {
module.hide();
}
}
,
select: {
firstUnfiltered: function (){
module.verbose('Selecting first non-filtered element');
module.remove.selectedItem();
$item.not(selector.unselectable).not(selector.addition + selector.hidden).eq(0).addClass(className.selected);
}
,
nextAvailable: function ($selected){
$selected = $selected.eq(0);
var $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), hasNext = (_AN_Read_length('length', $nextAvailable) > 0);
if (hasNext) {
module.verbose('Moving selection to', $nextAvailable);
$nextAvailable.addClass(className.selected);
}
else {
module.verbose('Moving selection to', $prevAvailable);
$prevAvailable.addClass(className.selected);
}
}
}
,
setup: {
api: function (){
var apiSettings = {
debug: settings.debug,
urlData: {
value: module.get.value(),
query: module.get.query()}
,
on: false }
;
module.verbose('First request, initializing API');
$module.api(apiSettings);
}
,
layout: function (){
if ($module.is('select')) {
module.setup.select();
module.setup.returnedObject();
}
if (!module.has.menu()) {
module.create.menu();
}
if (module.is.search() && !module.has.search()) {
module.verbose('Adding search input');
$search = $('').addClass(_AN_Read_search('search', className)).prop('autocomplete', 'off').insertBefore($text);
}
if (module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) {
module.create.sizer();
}
if (settings.allowTab) {
module.set.tabbable();
}
}
,
select: function (){
var selectValues = module.get.selectValues();
module.debug('Dropdown initialized on a select', selectValues);
if ($module.is('select')) {
$input = $module;
}
if (_AN_Read_length('length', $input.parent(selector.dropdown)) > 0) {
module.debug('UI dropdown already exists. Creating dropdown menu only');
$module = $input.closest(selector.dropdown);
if (!module.has.menu()) {
module.create.menu();
}
$menu = $module.children(selector.menu);
module.setup.menu(selectValues);
}
else {
module.debug('Creating entire dropdown from select');
$module = $('').attr('class', $input.attr('class')).addClass(className.selection).addClass(className.dropdown).html(templates.dropdown(selectValues)).insertBefore($input);
if ($input.hasClass(className.multiple) && $input.prop('multiple') === false ) {
module.error(error.missingMultiple);
$input.prop('multiple', true );
}
if ($input.is('[multiple]')) {
module.set.multiple();
}
if ($input.prop('disabled')) {
module.debug('Disabling dropdown');
$module.addClass(className.disabled);
}
$input.removeAttr('class').detach().prependTo($module);
}
_AN_Call_refresh('refresh', module);
}
,
menu: function (values){
$menu.html(templates.menu(values, fields));
$item = $menu.find(selector.item);
}
,
reference: function (){
module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
$module = $module.parent(selector.dropdown);
instance = $module.data(moduleNamespace);
element = $module.get(0);
_AN_Call_refresh('refresh', module);
module.setup.returnedObject();
}
,
returnedObject: function (){
var $firstModules = $allModules.slice(0, elementIndex), $lastModules = $allModules.slice(elementIndex + 1);
$allModules = $firstModules.add($module).add($lastModules);
}
}
,
refresh: function (){
module.refreshSelectors();
module.refreshData();
}
,
refreshItems: function (){
$item = $menu.find(selector.item);
}
,
refreshSelectors: function (){
module.verbose('Refreshing selector cache');
$text = $module.find(selector.text);
$search = $module.find(_AN_Read_search('search', selector));
$input = $module.find(selector.input);
$icon = $module.find(selector.icon);
$combo = (_AN_Read_length('length', $module.prev().find(selector.text)) > 0)? $module.prev().find(selector.text): $module.prev();
$menu = $module.children(selector.menu);
$item = $menu.find(selector.item);
}
,
refreshData: function (){
module.verbose('Refreshing cached metadata');
$item.removeData(metadata.text).removeData(metadata.value);
}
,
clearData: function (){
module.verbose('Clearing metadata');
$item.removeData(metadata.text).removeData(metadata.value);
$module.removeData(metadata.defaultText).removeData(metadata.defaultValue).removeData(metadata.placeholderText);
}
,
toggle: function (){
module.verbose('Toggling menu visibility');
if (!module.is.active()) {
_AN_Call_show('show', module);
}
else {
module.hide();
}
}
,
show: function (callback){
callback = $.isFunction(callback)? callback: function (){
}
;
if (!_AN_Call_show('show', module.can) && module.is.remote()) {
module.debug('No API results retrieved, searching before show');
module.queryRemote(module.get.query(), module.show);
}
if (_AN_Call_show('show', module.can) && !module.is.active()) {
module.debug('Showing dropdown');
if (module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered())) {
module.remove.message();
}
if (module.is.allFiltered()) {
return true ;
}
if (settings.onShow.call(element) !== false ) {
_AN_Call_show('show', module.animate, function (){
if (module.can.click()) {
module.bind.intent();
}
if (module.has.menuSearch()) {
module.focusSearch();
}
module.set.visible();
callback.call(element);
}
);
}
}
}
,
hide: function (callback){
callback = $.isFunction(callback)? callback: function (){
}
;
if (module.is.active()) {
module.debug('Hiding dropdown');
if (settings.onHide.call(element) !== false ) {
module.animate.hide(function (){
module.remove.visible();
callback.call(element);
}
);
}
}
}
,
hideOthers: function (){
module.verbose('Finding other dropdowns to hide');
$allModules.not($module).has(selector.menu + '.' + className.visible).dropdown('hide');
}
,
hideMenu: function (){
module.verbose('Hiding menu instantaneously');
module.remove.active();
module.remove.visible();
$menu.transition('hide');
}
,
hideSubMenus: function (){
var $subMenus = $menu.children(selector.item).find(selector.menu);
module.verbose('Hiding sub menus', $subMenus);
$subMenus.transition('hide');
}
,
bind: {
events: function (){
if (hasTouch) {
module.bind.touchEvents();
}
module.bind.keyboardEvents();
module.bind.inputEvents();
module.bind.mouseEvents();
}
,
touchEvents: function (){
module.debug('Touch device detected binding additional touch events');
if (module.is.searchSelection()) {
}
else if (module.is.single()) {
$module.on('touchstart' + eventNamespace, module.event.test.toggle);
}
$menu.on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter);
}
,
keyboardEvents: function (){
module.verbose('Binding keyboard events');
$module.on('keydown' + eventNamespace, module.event.keydown);
if (module.has.search()) {
$module.on(module.get.inputEvent() + eventNamespace, _AN_Read_search('search', selector), module.event.input);
}
if (module.is.multiple()) {
$document.on('keydown' + elementNamespace, module.event.document.keydown);
}
}
,
inputEvents: function (){
module.verbose('Binding input change events');
$module.on('change' + eventNamespace, selector.input, module.event.change);
}
,
mouseEvents: function (){
module.verbose('Binding mouse events');
if (module.is.multiple()) {
$module.on('click' + eventNamespace, selector.label, module.event.label.click).on('click' + eventNamespace, selector.remove, module.event.remove.click);
}
if (module.is.searchSelection()) {
$module.on('mousedown' + eventNamespace, module.event.mousedown).on('mouseup' + eventNamespace, module.event.mouseup).on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown).on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup).on('click' + eventNamespace, selector.icon, module.event.icon.click).on('focus' + eventNamespace, _AN_Read_search('search', selector), _AN_Read_search('search', module.event).focus).on('click' + eventNamespace, _AN_Read_search('search', selector), _AN_Read_search('search', module.event).focus).on('blur' + eventNamespace, _AN_Read_search('search', selector), _AN_Read_search('search', module.event).blur).on('click' + eventNamespace, selector.text, module.event.text.focus);
if (module.is.multiple()) {
$module.on('click' + eventNamespace, module.event.click);
}
}
else {
if (settings.on == 'click') {
$module.on('click' + eventNamespace, selector.icon, module.event.icon.click).on('click' + eventNamespace, module.event.test.toggle);
}
else if (settings.on == 'hover') {
$module.on('mouseenter' + eventNamespace, module.delay.show).on('mouseleave' + eventNamespace, module.delay.hide);
}
else {
$module.on(settings.on + eventNamespace, module.toggle);
}
$module.on('mousedown' + eventNamespace, module.event.mousedown).on('mouseup' + eventNamespace, module.event.mouseup).on('focus' + eventNamespace, module.event.focus);
if (module.has.menuSearch()) {
$module.on('blur' + eventNamespace, _AN_Read_search('search', selector), _AN_Read_search('search', module.event).blur);
}
else {
$module.on('blur' + eventNamespace, module.event.blur);
}
}
$menu.on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter).on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave).on('click' + eventNamespace, selector.item, module.event.item.click);
}
,
intent: function (){
module.verbose('Binding hide intent event to document');
if (hasTouch) {
$document.on('touchstart' + elementNamespace, module.event.test.touch).on('touchmove' + elementNamespace, module.event.test.touch);
}
$document.on('click' + elementNamespace, module.event.test.hide);
}
}
,
unbind: {
intent: function (){
module.verbose('Removing hide intent event from document');
if (hasTouch) {
$document.off('touchstart' + elementNamespace).off('touchmove' + elementNamespace);
}
$document.off('click' + elementNamespace);
}
}
,
filter: function (query){
var searchTerm = (query !== undefined)? query: module.get.query(), afterFiltered = function (){
if (module.is.multiple()) {
module.filterActive();
}
if (query || (!query && _AN_Read_length('length', module.get.activeItem()) == 0)) {
module.select.firstUnfiltered();
}
if (module.has.allResultsFiltered()) {
if (settings.onNoResults.call(element, searchTerm)) {
if (settings.allowAdditions) {
if (settings.hideAdditions) {
module.verbose('User addition with no menu, setting empty style');
module.set.empty();
module.hideMenu();
}
}
else {
module.verbose('All items filtered, showing message', searchTerm);
module.add.message(message.noResults);
}
}
else {
module.verbose('All items filtered, hiding dropdown', searchTerm);
module.hideMenu();
}
}
else {
module.remove.empty();
module.remove.message();
}
if (settings.allowAdditions) {
module.add.userSuggestion(query);
}
if (module.is.searchSelection() && _AN_Call_show('show', module.can) && module.is.focusedOnSearch()) {
_AN_Call_show('show', module);
}
}
;
if (settings.useLabels && module.has.maxSelections()) {
return ;
}
if (settings.apiSettings) {
if (module.can.useAPI()) {
module.queryRemote(searchTerm, function (){
if (settings.filterRemoteData) {
module.filterItems(searchTerm);
}
afterFiltered();
}
);
}
else {
module.error(error.noAPI);
}
}
else {
module.filterItems(searchTerm);
afterFiltered();
}
}
,
queryRemote: function (query, callback){
var apiSettings = {
errorDuration: false ,
cache: 'local',
throttle: settings.throttle,
urlData: {
query: query}
,
onError: function (){
module.add.message(message.serverError);
callback();
}
,
onFailure: function (){
module.add.message(message.serverError);
callback();
}
,
onSuccess: function (response){
module.remove.message();
module.setup.menu({
values: response[fields.remoteValues]}
);
callback();
}
}
;
if (!$module.api('get request')) {
module.setup.api();
}
apiSettings = $.extend(true , {
}
, apiSettings, settings.apiSettings);
$module.api('setting', apiSettings).api('query');
}
,
filterItems: function (query){
var searchTerm = (query !== undefined)? query: module.get.query(), results = null , escapedTerm = module.escape.string(searchTerm), beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm');
if (module.has.query()) {
results = [] ;
module.verbose('Searching for matching values', searchTerm);
$item.each(function (){
var $choice = $(this), text, value;
if (settings.match == 'both' || settings.match == 'text') {
text = String(module.get.choiceText($choice, false ));
if (text.search(beginsWithRegExp) !== -1) {
results.push(this);
return true ;
}
else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
results.push(this);
return true ;
}
else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
results.push(this);
return true ;
}
}
if (settings.match == 'both' || settings.match == 'value') {
value = String(module.get.choiceValue($choice, text));
if (value.search(beginsWithRegExp) !== -1) {
results.push(this);
return true ;
}
else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) {
results.push(this);
return true ;
}
else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) {
results.push(this);
return true ;
}
}
}
);
}
module.debug('Showing only matched items', searchTerm);
module.remove.filteredItem();
if (results) {
$item.not(results).addClass(className.filtered);
}
}
,
fuzzySearch: function (query, term){
var termLength = _AN_Read_length('length', term), queryLength = _AN_Read_length('length', query);
query = query.toLowerCase();
term = term.toLowerCase();
if (queryLength > termLength) {
return false ;
}
if (queryLength === termLength) {
return (query === term);
}
search: for (var characterIndex = 0, nextCharacterIndex = 0;
characterIndex < queryLength; characterIndex++ ){
var queryCharacter = query.charCodeAt(characterIndex);
while (nextCharacterIndex < termLength){
if (term.charCodeAt(nextCharacterIndex++ ) === queryCharacter) {
continue search;
}
}
return false ;
}
return true ;
}
,
exactSearch: function (query, term){
query = query.toLowerCase();
term = term.toLowerCase();
if (term.indexOf(query) > -1) {
return true ;
}
return false ;
}
,
filterActive: function (){
if (settings.useLabels) {
$item.filter('.' + className.active).addClass(className.filtered);
}
}
,
focusSearch: function (skipHandler){
if (module.has.search() && !module.is.focusedOnSearch()) {
if (skipHandler) {
$module.off('focus' + eventNamespace, _AN_Read_search('search', selector));
$search.focus();
$module.on('focus' + eventNamespace, _AN_Read_search('search', selector), _AN_Read_search('search', module.event).focus);
}
else {
$search.focus();
}
}
}
,
forceSelection: function (){
var $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), $selectedItem = (_AN_Read_length('length', $currentlySelected) > 0)? $currentlySelected: $activeItem, hasSelected = (_AN_Read_length('length', $selectedItem) > 0);
if (hasSelected && !module.is.multiple()) {
module.debug('Forcing partial selection to selected item', $selectedItem);
module.event.item.click.call($selectedItem, {
}
, true );
return ;
}
else {
if (settings.allowAdditions) {
module.set.selected(module.get.query());
module.remove.searchTerm();
}
else {
module.remove.searchTerm();
}
}
}
,
change: {
values: function (values){
if (!settings.allowAdditions) {
_AN_Call_clear('clear', module);
}
module.debug('Creating dropdown with specified values', values);
module.setup.menu({
values: values}
);
$.each(values, function (index, item){
if (item.selected == true ) {
module.debug('Setting initial selection to', item.value);
module.set.selected(item.value);
return true ;
}
}
);
}
}
,
event: {
change: function (){
if (!internalChange) {
module.debug('Input changed, updating selection');
module.set.selected();
}
}
,
focus: function (){
if (settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
_AN_Call_show('show', module);
}
}
,
blur: function (event){
pageLostFocus = (document.activeElement === this);
if (!activated && !pageLostFocus) {
module.remove.activeLabel();
module.hide();
}
}
,
mousedown: function (){
if (module.is.searchSelection()) {
willRefocus = true ;
}
else {
activated = true ;
}
}
,
mouseup: function (){
if (module.is.searchSelection()) {
willRefocus = false ;
}
else {
activated = false ;
}
}
,
click: function (event){
var $target = $(_AN_Read_target('target', event));
if ($target.is($module)) {
if (!module.is.focusedOnSearch()) {
module.focusSearch();
}
else {
_AN_Call_show('show', module);
}
}
}
,
search: {
focus: function (){
activated = true ;
if (module.is.multiple()) {
module.remove.activeLabel();
}
if (settings.showOnFocus) {
module.search();
}
}
,
blur: function (event){
pageLostFocus = (document.activeElement === this);
if (module.is.searchSelection() && !willRefocus) {
if (!itemActivated && !pageLostFocus) {
if (settings.forceSelection) {
module.forceSelection();
}
module.hide();
}
}
willRefocus = false ;
}
}
,
icon: {
click: function (event){
module.toggle();
}
}
,
text: {
focus: function (event){
activated = true ;
module.focusSearch();
}
}
,
input: function (event){
if (module.is.multiple() || module.is.searchSelection()) {
module.set.filtered();
}
clearTimeout(module.timer);
module.timer = _AN_Call_settimeout('setTimeout', window, _AN_Read_search('search', module), _AN_Read_search('search', settings.delay));
}
,
label: {
click: function (event){
var $label = $(this), $labels = $module.find(selector.label), $activeLabels = $labels.filter('.' + className.active), $nextActive = $label.nextAll('.' + className.active), $prevActive = $label.prevAll('.' + className.active), $range = (_AN_Read_length('length', $nextActive) > 0)? $label.nextUntil($nextActive).add($activeLabels).add($label): $label.prevUntil($prevActive).add($activeLabels).add($label);
if (event.shiftKey) {
$activeLabels.removeClass(className.active);
$range.addClass(className.active);
}
else if (event.ctrlKey) {
$label.toggleClass(className.active);
}
else {
$activeLabels.removeClass(className.active);
$label.addClass(className.active);
}
settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
}
}
,
remove: {
click: function (){
var $label = $(this).parent();
if ($label.hasClass(className.active)) {
module.remove.activeLabels();
}
else {
module.remove.activeLabels($label);
}
}
}
,
test: {
toggle: function (event){
var toggleBehavior = (module.is.multiple())? module.show: module.toggle;
if (module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) {
return ;
}
if (module.determine.eventOnElement(event, toggleBehavior)) {
event.preventDefault();
}
}
,
touch: function (event){
module.determine.eventOnElement(event, function (){
if (event.type == 'touchstart') {
module.timer = _AN_Call_settimeout('setTimeout', window, function (){
module.hide();
}
, settings.delay.touch);
}
else if (event.type == 'touchmove') {
clearTimeout(module.timer);
}
}
);
event.stopPropagation();
}
,
hide: function (event){
module.determine.eventInModule(event, module.hide);
}
}
,
select: {
mutation: function (mutations){
module.debug('