<commit_before>/*
 * # Semantic - Dropdown
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Copyright 2014 Contributor
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;(function ( $, window, document, undefined ) {

"use strict";

$.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() {
      var
        settings          = ( $.isPlainObject(parameters) )
          ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
          : $.extend({}, $.fn.dropdown.settings),

        className       = settings.className,
        metadata        = settings.metadata,
        namespace       = settings.namespace,
        selector        = settings.selector,
        error           = settings.error,

        eventNamespace  = '.' + namespace,
        moduleNamespace = 'module-' + namespace,

        $module         = $(this),
        $text           = $module.find(selector.text),
        $search         = $module.find(selector.search),
        $input          = $module.find(selector.input),

        $combo = ($module.prev().find(selector.text).length > 0)
          ? $module.prev().find(selector.text)
          : $module.prev(),

        $menu           = $module.children(selector.menu),
        $item           = $menu.find(selector.item),

        activated       = false,
        itemActivated   = false,
        element         = this,
        instance        = $module.data(moduleNamespace),

        elementNamespace,
        id,
        observer,
        module
      ;

      module = {

        initialize: function() {
          module.debug('Initializing dropdown', settings);
          module.setup.layout();
          module.save.defaults();
          module.set.selected();

          module.create.id();

          if(hasTouch) {
            module.bind.touchEvents();
          }
          module.bind.mouseEvents();
          module.bind.keyboardEvents();

          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 for', $module);
          module.remove.tabbable();
          $module
            .off(eventNamespace)
            .removeData(moduleNamespace)
          ;
          $document
            .off(elementNamespace)
          ;
        },

        observeChanges: function() {
          if('MutationObserver' in window) {
            observer = new MutationObserver(function(mutations) {
              module.debug('DOM tree modified, updating selector cache');
              module.refresh();
            });
            observer.observe(element, {
              childList : true,
              subtree   : true
            });
            module.debug('Setting up mutation observer', observer);
          }
        },

        create: {
          id: function() {
            module.verbose('Creating unique id for element');
            id = module.get.uniqueID();
            elementNamespace = '.' + id;
          }
        },

        search: function() {
          var
            query
          ;
          query = $search.val();

          module.verbose('Searching for query', query);

          if(module.is.searchSelection()) {
            module.filter(query);
            if( module.can.show() ) {
              module.show();
            }
          }
        },

        setup: {

          layout: function() {
            if( $module.is('select') ) {
              module.setup.select();
            }
            if( module.is.search() && !module.is.searchable() ) {
              $search = $('<input />')
                .addClass(className.search)
                .insertBefore($text)
              ;
            }
            if(settings.allowTab) {
              module.set.tabbable();
            }
          },
          select: function() {
            var
              selectValues = module.get.selectValues()
            ;
            module.debug('Dropdown initialized on a select', selectValues);
            // see if select exists inside a dropdown
            $input = $module;
            if($input.parents(selector.dropdown).length > 0) {
              module.debug('Creating dropdown menu only from template');
              $module = $input.closest(selector.dropdown);
              if($module.find('.' + className.dropdown).length === 0) {
                $('<div />')
                  .addClass(className.menu)
                  .html( settings.templates.menu( selectValues ))
                  .appendTo($module)
                ;
              }
            }
            else {
              module.debug('Creating entire dropdown from template');
              $module = $('<div />')
                .attr('class', $input.attr('class') )
                .addClass(className.selection)
                .addClass(className.dropdown)
                .html( settings.templates.dropdown(selectValues) )
                .insertBefore($input)
              ;
              $input
                .removeAttr('class')
                .prependTo($module)
              ;
            }
            module.refresh();
          }
        },

        refresh: function() {
          $text   = $module.find(selector.text);
          $search = $module.find(selector.search);
          $input  = $module.find(selector.input);
          $menu   = $module.children(selector.menu);
          $item   = $menu.find(selector.item);
        },

        toggle: function() {
          module.verbose('Toggling menu visibility');
          if( !module.is.active() ) {
            module.show();
          }
          else {
            module.hide();
          }
        },

        show: function(callback) {
          callback = $.isFunction(callback)
            ? callback
            : function(){}
          ;
          if( !module.is.active() && !module.is.allFiltered() ) {
            module.debug('Showing dropdown');
            module.animate.show(function() {
              if( module.can.click() ) {
                module.bind.intent();
              }
              module.set.visible();
              callback.call(element);
            });
            settings.onShow.call(element);
          }
        },

        hide: function(callback) {
          callback = $.isFunction(callback)
            ? callback
            : function(){}
          ;
          if( module.is.active() ) {
            module.debug('Hiding dropdown');
            module.animate.hide(function() {
              module.remove.visible();
              callback.call(element);
            });
            settings.onHide.call(element);
          }
        },

        hideOthers: function() {
          module.verbose('Finding other dropdowns to hide');
          $allModules
            .not($module)
              .has(selector.menu + ':visible:not(.' + className.animating + ')')
                .dropdown('hide')
          ;
        },

        hideSubMenus: function() {
          var
            $subMenus = $menu.find(selector.menu)
          ;
          $subMenus.transition('hide');
        },

        bind: {
          keyboardEvents: function() {
            module.debug('Binding keyboard events');
            $module
              .on('keydown' + eventNamespace, module.event.keydown)
            ;
            if( module.is.searchable() ) {
              $module
                .on(module.get.inputEvent(), selector.search, module.event.input)
              ;
            }
          },
          touchEvents: function() {
            module.debug('Touch device detected binding additional touch events');
            if( module.is.searchSelection() ) {
              // do nothing special yet
            }
            else {
              $module
                .on('touchstart' + eventNamespace, module.event.test.toggle)
              ;
            }
            $menu
              .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
            ;
          },
          mouseEvents: function() {
            module.verbose('Mouse detected binding mouse events');

            if( module.is.searchSelection() ) {
              $module
                .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
                .on('mouseup'   + eventNamespace, selector.menu, module.event.menu.deactivate)
                .on('click'     + eventNamespace, selector.search, module.show)
                .on('focus'     + eventNamespace, selector.search, module.event.searchFocus)
                .on('blur'      + eventNamespace, selector.search, module.event.searchBlur)
              ;
            }
            else {
              if(settings.on == 'click') {
                $module
                  .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)
                .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(searchTerm) {
          var
            $results       = $(),
            exactRegExp    = new RegExp('^' + searchTerm, 'igm'),
            fullTextRegExp = new RegExp(searchTerm, 'ig'),
            allItemsFiltered
          ;
          module.verbose('Searching for matching values');
          $item
            .each(function(){
              var
                $choice = $(this),
                text    = module.get.choiceText($choice, false),
                value   = module.get.choiceValue($choice, text)
              ;
              if( text.match(exactRegExp) || value.match(exactRegExp) ) {
                $results = $results.add($choice);
              }
              else if(settings.fullTextSearch) {
                if( text.match(fullTextRegExp) || value.match(fullTextRegExp) ) {
                  $results = $results.add($choice);
                }
              }
            })
          ;

          module.debug('Setting filter', searchTerm);
          module.remove.filteredItem();
          $item
            .not($results)
            .addClass(className.filtered)
          ;

          module.verbose('Selecting first non-filtered element');
          module.remove.selectedItem();
          $item
            .not('.' + className.filtered)
              .eq(0)
              .addClass(className.selected)
          ;
          if( module.is.allFiltered() ) {
            module.debug('All items filtered, hiding dropdown', searchTerm);
            module.hide();
            settings.onNoResults.call(element, searchTerm);
          }
        },

        focusSearch: function() {
          if( module.is.search() ) {
            $search
              .focus()
            ;
          }
        },

        event: {
          // prevents focus callback from occuring on mousedown
          mousedown: function() {
            activated = true;
          },
          mouseup: function() {
            activated = false;
          },
          focus: function() {
            if(!activated && module.is.hidden()) {
              module.show();
            }
          },
          blur: function(event) {
            var
              pageLostFocus = (document.activeElement === this)
            ;
            if(!activated && !pageLostFocus) {
              module.hide();
            }
          },
          searchFocus: function() {
            activated = true;
            module.show();
          },
          searchBlur: function(event) {
            var
              pageLostFocus = (document.activeElement === this)
            ;
            if(!itemActivated && !pageLostFocus) {
              module.hide();
            }
          },
          input: function(event) {
            module.set.filtered();
            clearTimeout(module.timer);
            module.timer = setTimeout(module.search, settings.delay.search);
          },
          keydown: function(event) {
            var
              $selectedItem = $item.not(className.filtered).filter('.' + className.selected).eq(0),
              $visibleItems = $item.not('.' + className.filtered),
              pressedKey    = event.which,
              keys          = {
                enter     : 13,
                escape    : 27,
                upArrow   : 38,
                downArrow : 40
              },
              selectedClass   = className.selected,
              currentIndex    = $visibleItems.index( $selectedItem ),
              hasSelectedItem = ($selectedItem.length > 0),
              $nextItem,
              newIndex
            ;
            // default to activated choice if no selection present
            if(!hasSelectedItem) {
              $selectedItem   = $item.filter('.' + className.active).eq(0);
              hasSelectedItem = ($selectedItem.length > 0);
            }
            // close shortcuts
            if(pressedKey == keys.escape) {
              module.verbose('Escape key pressed, closing dropdown');
              module.hide();
            }
            // open menu
            if(pressedKey == keys.downArrow) {
              module.verbose('Down key pressed, showing dropdown');
              module.show();
            }
            // result shortcuts
            if(module.is.visible()) {
              if(pressedKey == keys.enter && hasSelectedItem) {
                module.verbose('Enter key pressed, choosing selected item');
                module.event.item.click.call($selectedItem, event);
                event.preventDefault();
                return false;
              }
              else if(pressedKey == keys.upArrow) {
                if(!hasSelectedItem) {
                  $nextItem = $visibleItems.eq(0);
                }
                else {
                  $nextItem = $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
                }
                if(currentIndex !== 0) {
                  module.verbose('Up key pressed, changing active item');
                  $item
                    .removeClass(selectedClass)
                  ;
                  $nextItem
                    .addClass(selectedClass)
                  ;
                  module.set.scrollPosition($nextItem);
                }
                event.preventDefault();
              }
              else if(pressedKey == keys.downArrow) {
                if(!hasSelectedItem) {
                  $nextItem = $visibleItems.eq(0);
                }
                else {
                  $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
                }
                if(currentIndex + 1 < $visibleItems.length ) {
                  module.verbose('Down key pressed, changing active item');
                  $item
                    .removeClass(selectedClass)
                  ;
                  $nextItem
                    .addClass(selectedClass)
                  ;
                  module.set.scrollPosition($nextItem);
                }
                event.preventDefault();
              }
            }
            else {
              if(pressedKey == keys.enter) {
                module.show();
              }
            }
          },
          test: {
            toggle: function(event) {
              if( module.determine.eventInMenu(event, module.toggle) ) {
                event.preventDefault();
              }
            },
            touch: function(event) {
              module.determine.eventInMenu(event, function() {
                if(event.type == 'touchstart') {
                  module.timer = setTimeout(module.hide, settings.delay.touch);
                }
                else if(event.type == 'touchmove') {
                  clearTimeout(module.timer);
                }
              });
              event.stopPropagation();
            },
            hide: function(event) {
              module.determine.eventInModule(event, module.hide);
            }
          },

          menu: {
            activate: function() {
              itemActivated = true;
            },
            deactivate: function() {
              itemActivated = false;
            }
          },
          item: {
            mouseenter: function(event) {
              var
                $currentMenu = $(this).children(selector.menu),
                $otherMenus  = $(this).siblings(selector.item).children(selector.menu)
              ;
              if( $currentMenu.length > 0 ) {
                clearTimeout(module.itemTimer);
                module.itemTimer = setTimeout(function() {
                  $.each($otherMenus, function() {
                    module.animate.hide(false, $(this));
                  });
                  module.verbose('Showing sub-menu', $currentMenu);
                  module.animate.show(false,  $currentMenu);
                }, settings.delay.show);
                event.preventDefault();
              }
            },

            mouseleave: function(event) {
              var
                $currentMenu = $(this).children(selector.menu)
              ;
              if($currentMenu.length > 0) {
                clearTimeout(module.itemTimer);
                module.itemTimer = setTimeout(function() {
                  module.verbose('Hiding sub-menu', $currentMenu);
                  module.animate.hide(false,  $currentMenu);
                }, settings.delay.hide);
              }
            },

            click: function (event) {
              var
                $choice  = $(this),
                $target  = $(event.target),
                $subMenu = $choice.find(selector.menu),
                text     = module.get.choiceText($choice),
                value    = module.get.choiceValue($choice, text),
                callback = function() {
                  module.remove.searchTerm();
                  module.determine.selectAction(text, value);
                },
                openingSubMenu = ($subMenu.length > 0),
                isSubItem      = ($subMenu.find($target).length > 0)
              ;
              if(isSubItem) {
                return false;
              }
              if(!openingSubMenu || settings.allowCategorySelection) {
                callback();
              }
            }

          },

          resetStyle: function() {
            $(this).removeAttr('style');
          }

        },

        determine: {
          selectAction: function(text, value) {
            module.verbose('Determining action', settings.action);
            if( $.isFunction( module.action[settings.action] ) ) {
              module.verbose('Triggering preset action', settings.action, text, value);
              module.action[ settings.action ](text, value);
            }
            else if( $.isFunction(settings.action) ) {
              module.verbose('Triggering user action', settings.action, text, value);
              settings.action(text, value);
            }
            else {
              module.error(error.action, settings.action);
            }
          },
          eventInModule: function(event, callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            if( $(event.target).closest($module).length === 0 ) {
              module.verbose('Triggering event', callback);
              callback();
              return true;
            }
            else {
              module.verbose('Event occurred in dropdown, canceling callback');
              return false;
            }
          },
          eventInMenu: function(event, callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            if( $(event.target).closest($menu).length === 0 ) {
              module.verbose('Triggering event', callback);
              callback();
              return true;
            }
            else {
              module.verbose('Event occurred in dropdown menu, canceling callback');
              return false;
            }
          }
        },

        action: {

          nothing: function() {},

          hide: function() {
            module.hide(function() {
              module.remove.filteredItem();
            });
          },

          select: function(text, value) {
            value = (value !== undefined)
              ? value
              : text
            ;
            module.set.selected(value);
            module.set.value(value);
            module.hide(function() {
              module.remove.filteredItem();
            });
          },

          activate: function(text, value) {
            value = (value !== undefined)
              ? value
              : text
            ;
            module.set.selected(value);
            module.set.value(value);
            module.hide(function() {
              module.remove.filteredItem();
            });
          },

          combo: function(text, value) {
            value = (value !== undefined)
              ? value
              : text
            ;
            module.set.selected(value);
            module.set.value(value);
            module.hide(function() {
              module.remove.filteredItem();
            });
          }

        },

        get: {
          text: function() {
            return $text.text();
          },
          value: function() {
            return ($input.length > 0)
              ? $input.val()
              : $module.data(metadata.value)
            ;
          },
          choiceText: function($choice, preserveHTML) {
            preserveHTML = (preserveHTML !== undefined)
              ? preserveHTML
              : settings.preserveHTML
            ;
            if($choice !== undefined) {
              if($choice.find(selector.menu).length > 0) {
                module.verbose('Retreiving text of element with sub-menu');
                $choice = $choice.clone();
                $choice.find(selector.menu).remove();
                $choice.find(selector.menuIcon).remove();
              }
              return ($choice.data(metadata.text) !== undefined)
                ? $choice.data(metadata.text)
                : (preserveHTML)
                  ? $choice.html()
                  : $choice.text()
              ;
            }
          },
          choiceValue: function($choice, choiceText) {
            choiceText = choiceText || module.get.choiceText($text);
            return ($choice.data(metadata.value) !== undefined)
              ? $choice.data(metadata.value)
              : (typeof choiceText === 'string')
                ? choiceText.toLowerCase()
                : choiceText
            ;
          },
          inputEvent: function() {
            var
              input = $search[0]
            ;
            if(input) {
              return (input.oninput !== undefined)
                ? 'input'
                : (input.onpropertychange !== undefined)
                  ? 'propertychange'
                  : 'keyup'
              ;
            }
            return false;
          },
          selectValues: function() {
            var
              select = {}
            ;
            select.values = (settings.sortSelect)
              ? {} // properties will be sorted in object when re-accessed
              : [] // properties will keep original order in array
            ;
            $module
              .find('option')
                .each(function() {
                  var<commit_msg>Fix bug with search input inside dropdown<commit_after>/*
 * # Semantic - Dropdown
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Copyright 2014 Contributor
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;(function ( $, window, document, undefined ) {

"use strict";

$.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() {
      var
        settings          = ( $.isPlainObject(parameters) )
          ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
          : $.extend({}, $.fn.dropdown.settings),

        className       = settings.className,
        metadata        = settings.metadata,
        namespace       = settings.namespace,
        selector        = settings.selector,
        error           = settings.error,

        eventNamespace  = '.' + namespace,
        moduleNamespace = 'module-' + namespace,

        $module         = $(this),
        $text           = $module.find(selector.text),
        $search         = $module.find(selector.search),
        $input          = $module.find(selector.input),

        $combo = ($module.prev().find(selector.text).length > 0)
          ? $module.prev().find(selector.text)
          : $module.prev(),

        $menu           = $module.children(selector.menu),
        $item           = $menu.find(selector.item),

        activated       = false,
        itemActivated   = false,
        element         = this,
        instance        = $module.data(moduleNamespace),

        elementNamespace,
        id,
        observer,
        module
      ;

      module = {

        initialize: function() {
          module.debug('Initializing dropdown', settings);
          module.setup.layout();
          module.save.defaults();
          module.set.selected();

          module.create.id();

          if(hasTouch) {
            module.bind.touchEvents();
          }
          module.bind.mouseEvents();
          module.bind.keyboardEvents();

          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 for', $module);
          module.remove.tabbable();
          $module
            .off(eventNamespace)
            .removeData(moduleNamespace)
          ;
          $document
            .off(elementNamespace)
          ;
        },

        observeChanges: function() {
          if('MutationObserver' in window) {
            observer = new MutationObserver(function(mutations) {
              module.debug('DOM tree modified, updating selector cache');
              module.refresh();
            });
            observer.observe(element, {
              childList : true,
              subtree   : true
            });
            module.debug('Setting up mutation observer', observer);
          }
        },

        create: {
          id: function() {
            module.verbose('Creating unique id for element');
            id = module.get.uniqueID();
            elementNamespace = '.' + id;
          }
        },

        search: function() {
          var
            query
          ;
          query = $search.val();

          module.verbose('Searching for query', query);
          module.filter(query);
          if(module.is.searchSelection() && module.can.show() ) {
            module.show();
          }
        },

        setup: {

          layout: function() {
            if( $module.is('select') ) {
              module.setup.select();
            }
            if( module.is.search() && !module.is.searchable() ) {
              $search = $('<input />')
                .addClass(className.search)
                .insertBefore($text)
              ;
            }
            if(settings.allowTab) {
              module.set.tabbable();
            }
          },
          select: function() {
            var
              selectValues = module.get.selectValues()
            ;
            module.debug('Dropdown initialized on a select', selectValues);
            // see if select exists inside a dropdown
            $input = $module;
            if($input.parents(selector.dropdown).length > 0) {
              module.debug('Creating dropdown menu only from template');
              $module = $input.closest(selector.dropdown);
              if($module.find('.' + className.dropdown).length === 0) {
                $('<div />')
                  .addClass(className.menu)
                  .html( settings.templates.menu( selectValues ))
                  .appendTo($module)
                ;
              }
            }
            else {
              module.debug('Creating entire dropdown from template');
              $module = $('<div />')
                .attr('class', $input.attr('class') )
                .addClass(className.selection)
                .addClass(className.dropdown)
                .html( settings.templates.dropdown(selectValues) )
                .insertBefore($input)
              ;
              $input
                .removeAttr('class')
                .prependTo($module)
              ;
            }
            module.refresh();
          }
        },

        refresh: function() {
          $text   = $module.find(selector.text);
          $search = $module.find(selector.search);
          $input  = $module.find(selector.input);
          $menu   = $module.children(selector.menu);
          $item   = $menu.find(selector.item);
        },

        toggle: function() {
          module.verbose('Toggling menu visibility');
          if( !module.is.active() ) {
            module.show();
          }
          else {
            module.hide();
          }
        },

        show: function(callback) {
          callback = $.isFunction(callback)
            ? callback
            : function(){}
          ;
          if( !module.is.active() && !module.is.allFiltered() ) {
            module.debug('Showing dropdown');
            module.animate.show(function() {
              if( module.can.click() ) {
                module.bind.intent();
              }
              module.set.visible();
              callback.call(element);
            });
            settings.onShow.call(element);
          }
        },

        hide: function(callback) {
          callback = $.isFunction(callback)
            ? callback
            : function(){}
          ;
          if( module.is.active() ) {
            module.debug('Hiding dropdown');
            module.animate.hide(function() {
              module.remove.visible();
              callback.call(element);
            });
            settings.onHide.call(element);
          }
        },

        hideOthers: function() {
          module.verbose('Finding other dropdowns to hide');
          $allModules
            .not($module)
              .has(selector.menu + ':visible:not(.' + className.animating + ')')
                .dropdown('hide')
          ;
        },

        hideSubMenus: function() {
          var
            $subMenus = $menu.find(selector.menu)
          ;
          $subMenus.transition('hide');
        },

        bind: {
          keyboardEvents: function() {
            module.debug('Binding keyboard events');
            $module
              .on('keydown' + eventNamespace, module.event.keydown)
            ;
            if( module.is.searchable() ) {
              $module
                .on(module.get.inputEvent(), selector.search, module.event.input)
              ;
            }
          },
          touchEvents: function() {
            module.debug('Touch device detected binding additional touch events');
            if( module.is.searchSelection() ) {
              // do nothing special yet
            }
            else {
              $module
                .on('touchstart' + eventNamespace, module.event.test.toggle)
              ;
            }
            $menu
              .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
            ;
          },
          mouseEvents: function() {
            module.verbose('Mouse detected binding mouse events');

            if( module.is.searchSelection() ) {
              $module
                .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
                .on('mouseup'   + eventNamespace, selector.menu, module.event.menu.deactivate)
                .on('click'     + eventNamespace, selector.search, module.show)
                .on('focus'     + eventNamespace, selector.search, module.event.searchFocus)
                .on('blur'      + eventNamespace, selector.search, module.event.searchBlur)
              ;
            }
            else {
              if(settings.on == 'click') {
                $module
                  .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)
                .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(searchTerm) {
          var
            $results       = $(),
            exactRegExp    = new RegExp('^' + searchTerm, 'igm'),
            fullTextRegExp = new RegExp(searchTerm, 'ig'),
            allItemsFiltered
          ;
          module.verbose('Searching for matching values');
          $item
            .each(function(){
              var
                $choice = $(this),
                text    = module.get.choiceText($choice, false),
                value   = module.get.choiceValue($choice, text)
              ;
              if( text.match(exactRegExp) || value.match(exactRegExp) ) {
                $results = $results.add($choice);
              }
              else if(settings.fullTextSearch) {
                if( text.match(fullTextRegExp) || value.match(fullTextRegExp) ) {
                  $results = $results.add($choice);
                }
              }
            })
          ;

          module.debug('Setting filter', searchTerm);
          module.remove.filteredItem();
          $item
            .not($results)
            .addClass(className.filtered)
          ;

          module.verbose('Selecting first non-filtered element');
          module.remove.selectedItem();
          $item
            .not('.' + className.filtered)
              .eq(0)
              .addClass(className.selected)
          ;
          if( module.is.allFiltered() ) {
            module.debug('All items filtered, hiding dropdown', searchTerm);
            if(module.is.searchSelection()) {
              module.hide();
            }
            settings.onNoResults.call(element, searchTerm);
          }
        },

        focusSearch: function() {
          if( module.is.search() ) {
            $search
              .focus()
            ;
          }
        },

        event: {
          // prevents focus callback from occuring on mousedown
          mousedown: function() {
            activated = true;
          },
          mouseup: function() {
            activated = false;
          },
          focus: function() {
            if(!activated && module.is.hidden()) {
              module.show();
            }
          },
          blur: function(event) {
            var
              pageLostFocus = (document.activeElement === this)
            ;
            if(!activated && !pageLostFocus) {
              module.hide();
            }
          },
          searchFocus: function() {
            activated = true;
            module.show();
          },
          searchBlur: function(event) {
            var
              pageLostFocus = (document.activeElement === this)
            ;
            if(!itemActivated && !pageLostFocus) {
              module.hide();
            }
          },
          input: function(event) {
            if(module.is.searchSelection()) {
              module.set.filtered();
            }
            clearTimeout(module.timer);
            module.timer = setTimeout(module.search, settings.delay.search);
          },
          keydown: function(event) {
            var
              $selectedItem = $item.not(className.filtered).filter('.' + className.selected).eq(0),
              $visibleItems = $item.not('.' + className.filtered),
              pressedKey    = event.which,
              keys          = {
                enter     : 13,
                escape    : 27,
                upArrow   : 38,
                downArrow : 40
              },
              selectedClass   = className.selected,
              currentIndex    = $visibleItems.index( $selectedItem ),
              hasSelectedItem = ($selectedItem.length > 0),
              $nextItem,
              newIndex
            ;
            // default to activated choice if no selection present
            if(!hasSelectedItem) {
              $selectedItem   = $item.filter('.' + className.active).eq(0);
              hasSelectedItem = ($selectedItem.length > 0);
            }
            // close shortcuts
            if(pressedKey == keys.escape) {
              module.verbose('Escape key pressed, closing dropdown');
              module.hide();
            }
            // open menu
            if(pressedKey == keys.downArrow) {
              module.verbose('Down key pressed, showing dropdown');
              module.show();
            }
            // result shortcuts
            if(module.is.visible()) {
              if(pressedKey == keys.enter && hasSelectedItem) {
                module.verbose('Enter key pressed, choosing selected item');
                module.event.item.click.call($selectedItem, event);
                event.preventDefault();
                return false;
              }
              else if(pressedKey == keys.upArrow) {
                if(!hasSelectedItem) {
                  $nextItem = $visibleItems.eq(0);
                }
                else {
                  $nextItem = $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
                }
                if(currentIndex !== 0) {
                  module.verbose('Up key pressed, changing active item');
                  $item
                    .removeClass(selectedClass)
                  ;
                  $nextItem
                    .addClass(selectedClass)
                  ;
                  module.set.scrollPosition($nextItem);
                }
                event.preventDefault();
              }
              else if(pressedKey == keys.downArrow) {
                if(!hasSelectedItem) {
                  $nextItem = $visibleItems.eq(0);
                }
                else {
                  $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
                }
                if(currentIndex + 1 < $visibleItems.length ) {
                  module.verbose('Down key pressed, changing active item');
                  $item
                    .removeClass(selectedClass)
                  ;
                  $nextItem
                    .addClass(selectedClass)
                  ;
                  module.set.scrollPosition($nextItem);
                }
                event.preventDefault();
              }
            }
            else {
              if(pressedKey == keys.enter) {
                module.show();
              }
            }
          },
          test: {
            toggle: function(event) {
              if( module.determine.eventInMenu(event, module.toggle) ) {
                event.preventDefault();
              }
            },
            touch: function(event) {
              module.determine.eventInMenu(event, function() {
                if(event.type == 'touchstart') {
                  module.timer = setTimeout(module.hide, settings.delay.touch);
                }
                else if(event.type == 'touchmove') {
                  clearTimeout(module.timer);
                }
              });
              event.stopPropagation();
            },
            hide: function(event) {
              module.determine.eventInModule(event, module.hide);
            }
          },

          menu: {
            activate: function() {
              itemActivated = true;
            },
            deactivate: function() {
              itemActivated = false;
            }
          },
          item: {
            mouseenter: function(event) {
              var
                $currentMenu = $(this).children(selector.menu),
                $otherMenus  = $(this).siblings(selector.item).children(selector.menu)
              ;
              if( $currentMenu.length > 0 ) {
                clearTimeout(module.itemTimer);
                module.itemTimer = setTimeout(function() {
                  $.each($otherMenus, function() {
                    module.animate.hide(false, $(this));
                  });
                  module.verbose('Showing sub-menu', $currentMenu);
                  module.animate.show(false,  $currentMenu);
                }, settings.delay.show);
                event.preventDefault();
              }
            },

            mouseleave: function(event) {
              var
                $currentMenu = $(this).children(selector.menu)
              ;
              if($currentMenu.length > 0) {
                clearTimeout(module.itemTimer);
                module.itemTimer = setTimeout(function() {
                  module.verbose('Hiding sub-menu', $currentMenu);
                  module.animate.hide(false,  $currentMenu);
                }, settings.delay.hide);
              }
            },

            click: function (event) {
              var
                $choice  = $(this),
                $target  = $(event.target),
                $subMenu = $choice.find(selector.menu),
                text     = module.get.choiceText($choice),
                value    = module.get.choiceValue($choice, text),
                callback = function() {
                  module.remove.searchTerm();
                  module.determine.selectAction(text, value);
                },
                openingSubMenu = ($subMenu.length > 0),
                isSubItem      = ($subMenu.find($target).length > 0)
              ;
              if(isSubItem) {
                return false;
              }
              if(!openingSubMenu || settings.allowCategorySelection) {
                callback();
              }
            }

          },

          resetStyle: function() {
            $(this).removeAttr('style');
          }

        },

        determine: {
          selectAction: function(text, value) {
            module.verbose('Determining action', settings.action);
            if( $.isFunction( module.action[settings.action] ) ) {
              module.verbose('Triggering preset action', settings.action, text, value);
              module.action[ settings.action ](text, value);
            }
            else if( $.isFunction(settings.action) ) {
              module.verbose('Triggering user action', settings.action, text, value);
              settings.action(text, value);
            }
            else {
              module.error(error.action, settings.action);
            }
          },
          eventInModule: function(event, callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            if( $(event.target).closest($module).length === 0 ) {
              module.verbose('Triggering event', callback);
              callback();
              return true;
            }
            else {
              module.verbose('Event occurred in dropdown, canceling callback');
              return false;
            }
          },
          eventInMenu: function(event, callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            if( $(event.target).closest($menu).length === 0 ) {
              module.verbose('Triggering event', callback);
              callback();
              return true;
            }
            else {
              module.verbose('Event occurred in dropdown menu, canceling callback');
              return false;
            }
          }
        },

        action: {

          nothing: function() {},

          hide: function() {
            module.hide(function() {
              module.remove.filteredItem();
            });
          },

          select: function(text, value) {
            value = (value !== undefined)
              ? value
              : text
            ;
            module.set.selected(value);
            module.set.value(value);
            module.hide(function() {
              module.remove.filteredItem();
            });
          },

          activate: function(text, value) {
            value = (value !== undefined)
              ? value
              : text
            ;
            module.set.selected(value);
            module.set.value(value);
            module.hide(function() {
              module.remove.filteredItem();
            });
          },

          combo: function(text, value) {
            value = (value !== undefined)
              ? value
              : text
            ;
            module.set.selected(value);
            module.set.value(value);
            module.hide(function() {
              module.remove.filteredItem();
            });
          }

        },

        get: {
          text: function() {
            return $text.text();
          },
          value: function() {
            return ($input.length > 0)
              ? $input.val()
              : $module.data(metadata.value)
            ;
          },
          choiceText: function($choice, preserveHTML) {
            preserveHTML = (preserveHTML !== undefined)
              ? preserveHTML
              : settings.preserveHTML
            ;
            if($choice !== undefined) {
              if($choice.find(selector.menu).length > 0) {
                module.verbose('Retreiving text of element with sub-menu');
                $choice = $choice.clone();
                $choice.find(selector.menu).remove();
                $choice.find(selector.menuIcon).remove();
              }
              return ($choice.data(metadata.text) !== undefined)
                ? $choice.data(metadata.text)
                : (preserveHTML)
                  ? $choice.html().trim()
                  : $choice.text().trim()
              ;
            }
          },
          choiceValue: function($choice, choiceText) {
            choiceText = choiceText || module.get.choiceText($text);
            return ($choice.data(metadata.value) !== undefined)
              ? $choice.data(metadata.value)
              : (typeof choiceText === 'string')
                ? choiceText.toLowerCase().trim()
                : choiceText.trim()
            ;
          },
          inputEvent: function() {
            var
              input = $search[0]
            ;
            if(input) {
              return (input.oninput !== undefined)
                ? 'input'
                : (input.onpropertychange !== undefined)
                  ? 'propertychange'
                  : 'keyup'
              ;
            }
            return false;
          },
          selectValues: function() {
            var
              select = {}
            ;
            select.values = (settings.sortSelect)
              ? {} // properties will be sorted in object when re-accessed
              : [] // properties will keep original order in array
            ;
            $module
              .find('option')
                .each(function() {
                  var
{'fraction_non_alphanumeric': 0.1174734017061248, 'fraction_numerical': 0.0014185756733441962, 'mean_word_length': 1.2291257157507904, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7040851', 'n_tokens_mistral': 12817, 'n_tokens_neox': 12280, 'n_words': 2198}