text: /*!
   * # Semantic UI - Search
   * http://github.com/semantic-org/semantic-ui/
   *
   *
   * Released under the MIT license
   * http://opensource.org/licenses/MIT
   *
   */
  
  ;(function ($, window, document, undefined) {
  
  'use strict';
  
  $.isFunction = $.isFunction || function(obj) {
    return typeof obj === "function" && typeof obj.nodeType !== "number";
  };
  
  window = (typeof window != 'undefined' && window.Math == Math)
    ? window
    : (typeof self != 'undefined' && self.Math == Math)
      ? self
      : Function('return this')()
  ;
  
  $.fn.search = function(parameters) {
    var
      $allModules     = $(this),
      moduleSelector  = $allModules.selector || '',
  
      time            = new Date().getTime(),
      performance     = [],
  
      query           = arguments[0],
      methodInvoked   = (typeof query == 'string'),
      queryArguments  = [].slice.call(arguments, 1),
      returnedValue
    ;
    $(this)
      .each(function() {
        var
          settings          = ( $.isPlainObject(parameters) )
            ? $.extend(true, {}, $.fn.search.settings, parameters)
            : $.extend({}, $.fn.search.settings),
  
          className        = settings.className,
          metadata         = settings.metadata,
          regExp           = settings.regExp,
          fields           = settings.fields,
          selector         = settings.selector,
          error            = settings.error,
          namespace        = settings.namespace,
  
          eventNamespace   = '.' + namespace,
          moduleNamespace  = namespace + '-module',
  
          $module          = $(this),
          $prompt          = $module.find(selector.prompt),
          $searchButton    = $module.find(selector.searchButton),
          $results         = $module.find(selector.results),
          $result          = $module.find(selector.result),
          $category        = $module.find(selector.category),
  
          element          = this,
          instance         = $module.data(moduleNamespace),
  
          disabledBubbled  = false,
          resultsDismissed = false,
  
          module
        ;
  
        module = {
  
          initialize: function() {
            module.verbose('Initializing module');
            module.get.settings();
            module.determine.searchFields();
            module.bind.events();
            module.set.type();
            module.create.results();
            module.instantiate();
          },
          instantiate: function() {
            module.verbose('Storing instance of module', module);
            instance = module;
            $module
              .data(moduleNamespace, module)
            ;
          },
          destroy: function() {
            module.verbose('Destroying instance');
            $module
              .off(eventNamespace)
              .removeData(moduleNamespace)
            ;
          },
  
          refresh: function() {
            module.debug('Refreshing selector cache');
            $prompt         = $module.find(selector.prompt);
            $searchButton   = $module.find(selector.searchButton);
            $category       = $module.find(selector.category);
            $results        = $module.find(selector.results);
            $result         = $module.find(selector.result);
          },
  
          refreshResults: function() {
            $results = $module.find(selector.results);
            $result  = $module.find(selector.result);
          },
  
          bind: {
            events: function() {
              module.verbose('Binding events to search');
              if(settings.automatic) {
                $module
                  .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input)
                ;
                $prompt
                  .attr('autocomplete', 'off')
                ;
              }
              $module
                // prompt
                .on('focus'     + eventNamespace, selector.prompt, module.event.focus)
                .on('blur'      + eventNamespace, selector.prompt, module.event.blur)
                .on('keydown'   + eventNamespace, selector.prompt, module.handleKeyboard)
                // search button
                .on('click'     + eventNamespace, selector.searchButton, module.query)
                // results
                .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown)
                .on('mouseup'   + eventNamespace, selector.results, module.event.result.mouseup)
                .on('click'     + eventNamespace, selector.result,  module.event.result.click)
              ;
            }
          },
  
          determine: {
            searchFields: function() {
              // this makes sure $.extend does not add specified search fields to default fields
              // this is the only setting which should not extend defaults
              if(parameters && parameters.searchFields !== undefined) {
                settings.searchFields = parameters.searchFields;
              }
            }
          },
  
          event: {
            input: function() {
              if(settings.searchDelay) {
                clearTimeout(module.timer);
                module.timer = setTimeout(function() {
                  if(module.is.focused()) {
                    module.query();
                  }
                }, settings.searchDelay);
              }
              else {
                module.query();
              }
            },
            focus: function() {
              module.set.focus();
              if(settings.searchOnFocus && module.has.minimumCharacters() ) {
                module.query(function() {
                  if(module.can.show() ) {
                    module.showResults();
                  }
                });
              }
            },
            blur: function(event) {
              var
                pageLostFocus = (document.activeElement === this),
                callback      = function() {
                  module.cancel.query();
                  module.remove.focus();
                  module.timer = setTimeout(module.hideResults, settings.hideDelay);
                }
              ;
              if(pageLostFocus) {
                return;
              }
              resultsDismissed = false;
              if(module.resultsClicked) {
                module.debug('Determining if user action caused search to close');
                $module
                  .one('click.close' + eventNamespace, selector.results, function(event) {
                    if(module.is.inMessage(event) || disabledBubbled) {
                      $prompt.focus();
                      return;
                    }
                    disabledBubbled = false;
                    if( !module.is.animating() && !module.is.hidden()) {
                      callback();
                    }
                  })
                ;
              }
              else {
                module.debug('Input blurred without user action, closing results');
                callback();
              }
            },
            result: {
              mousedown: function() {
                module.resultsClicked = true;
              },
              mouseup: function() {
                module.resultsClicked = false;
              },
              click: function(event) {
                module.debug('Search result selected');
                var
                  $result = $(this),
                  $title  = $result.find(selector.title).eq(0),
                  $link   = $result.is('a[href]')
                    ? $result
                    : $result.find('a[href]').eq(0),
                  href    = $link.attr('href')   || false,
                  target  = $link.attr('target') || false,
                  // title is used for result lookup
                  value   = ($title.length > 0)
                    ? $title.text()
                    : false,
                  results = module.get.results(),
                  result  = $result.data(metadata.result) || module.get.result(value, results)
                ;
                if(value) {
                  module.set.value(value);
                }
                if( $.isFunction(settings.onSelect) ) {
                  if(settings.onSelect.call(element, result, results) === false) {
                    module.debug('Custom onSelect callback cancelled default select action');
                    disabledBubbled = true;
                    return;
                  }
                }
                module.hideResults();
                if(href) {
                  module.verbose('Opening search link found in result', $link);
                  if(target == '_blank' || event.ctrlKey) {
                    window.open(href);
                  }
                  else {
                    window.location.href = (href);
                  }
                }
              }
            }
          },
          handleKeyboard: function(event) {
            var
              // force selector refresh
              $result         = $module.find(selector.result),
              $category       = $module.find(selector.category),
              $activeResult   = $result.filter('.' + className.active),
              currentIndex    = $result.index( $activeResult ),
              resultSize      = $result.length,
              hasActiveResult = $activeResult.length > 0,
  
              keyCode         = event.which,
              keys            = {
                backspace : 8,
                enter     : 13,
                escape    : 27,
                upArrow   : 38,
                downArrow : 40
              },
              newIndex
            ;
            // search shortcuts
            if(keyCode == keys.escape) {
              module.verbose('Escape key pressed, blurring search field');
              module.hideResults();
              resultsDismissed = true;
            }
            if( module.is.visible() ) {
              if(keyCode == keys.enter) {
                module.verbose('Enter key pressed, selecting active result');
                if( $result.filter('.' + className.active).length > 0 ) {
                  module.event.result.click.call($result.filter('.' + className.active), event);
                  event.preventDefault();
                  return false;
                }
              }
              else if(keyCode == keys.upArrow && hasActiveResult) {
                module.verbose('Up key pressed, changing active result');
                newIndex = (currentIndex - 1 < 0)
                  ? currentIndex
                  : currentIndex - 1
                ;
                $category
                  .removeClass(className.active)
                ;
                $result
                  .removeClass(className.active)
                  .eq(newIndex)
                    .addClass(className.active)
                    .closest($category)
                      .addClass(className.active)
                ;
                event.preventDefault();
              }
              else if(keyCode == keys.downArrow) {
                module.verbose('Down key pressed, changing active result');
                newIndex = (currentIndex + 1 >= resultSize)
                  ? currentIndex
                  : currentIndex + 1
                ;
                $category
                  .removeClass(className.active)
                ;
                $result
                  .removeClass(className.active)
                  .eq(newIndex)
                    .addClass(className.active)
                    .closest($category)
                      .addClass(className.active)
                ;
                event.preventDefault();
              }
            }
            else {
              // query shortcuts
              if(keyCode == keys.enter) {
                module.verbose('Enter key pressed, executing query');
                module.query();
                module.set.buttonPressed();
                $prompt.one('keyup', module.remove.buttonFocus);
              }
            }
          },
  
          setup: {
            api: function(searchTerm, callback) {
              var
                apiSettings = {
                  debug             : settings.debug,
                  on                : false,
                  cache             : settings.cache,
                  action            : 'search',
                  urlData           : {
                    query : searchTerm
                  },
                  onSuccess         : function(response) {
                    module.parse.response.call(element, response, searchTerm);
                    callback();
                  },
                  onFailure         : function() {
                    module.displayMessage(error.serverError);
                    callback();
                  },
                  onAbort : function(response) {
                  },
                  onError           : module.error
                }
              ;
              $.extend(true, apiSettings, settings.apiSettings);
              module.verbose('Setting up API request', apiSettings);
              $module.api(apiSettings);
            }
          },
  
          can: {
            useAPI: function() {
              return $.fn.api !== undefined;
            },
            show: function() {
              return module.is.focused() && !module.is.visible() && !module.is.empty();
            },
            transition: function() {
              return settings.transition && $.fn.transition !== undefined && $module.transition('is supported');
            }
          },
  
          is: {
            animating: function() {
              return $results.hasClass(className.animating);
            },
            hidden: function() {
              return $results.hasClass(className.hidden);
            },
            inMessage: function(event) {
              if(!event.target) {
                return;
              }
              var
                $target = $(event.target),
                isInDOM = $.contains(document.documentElement, event.target)
              ;
              return (isInDOM && $target.closest(selector.message).length > 0);
            },
            empty: function() {
              return ($results.html() === '');
            },
            visible: function() {
              return ($results.filter(':visible').length > 0);
            },
            focused: function() {
              return ($prompt.filter(':focus').length > 0);
            }
          },
  
          get: {
            settings: function() {
              if($.isPlainObject(parameters) && parameters.searchFullText) {
                settings.fullTextSearch = parameters.searchFullText;
                module.error(settings.error.oldSearchSyntax, element);
              }
              if (settings.ignoreDiacritics && !String.prototype.normalize) {
                settings.ignoreDiacritics = false;
                module.error(error.noNormalize, element);
              }
            },
            inputEvent: function() {
              var
                prompt = $prompt[0],
                inputEvent   = (prompt !== undefined && prompt.oninput !== undefined)
                  ? 'input'
                  : (prompt !== undefined && prompt.onpropertychange !== undefined)
                    ? 'propertychange'
                    : 'keyup'
              ;
              return inputEvent;
            },
            value: function() {
              return $prompt.val();
            },
            results: function() {
              var
                results = $module.data(metadata.results)
              ;
              return results;
            },
            result: function(value, results) {
              var
                lookupFields = ['title', 'id'],
                result       = false
              ;
              value = (value !== undefined)
                ? value
                : module.get.value()
              ;
              results = (results !== undefined)
                ? results
                : module.get.results()
              ;
              if(settings.type === 'category') {
                module.debug('Finding result that matches', value);
                $.each(results, function(index, category) {
                  if(Array.isArray(category.results)) {
                    result = module.search.object(value, category.results, lookupFields)[0];
                    // don't continue searching if a result is found
                    if(result) {
                      return false;
                    }
                  }
                });
              }
              else {
                module.debug('Finding result in results object', value);
                result = module.search.object(value, results, lookupFields)[0];
              }
              return result || false;
            },
          },
  
          select: {
            firstResult: function() {
              module.verbose('Selecting first result');
              $result.first().addClass(className.active);
            }
          },
  
          set: {
            focus: function() {
              $module.addClass(className.focus);
            },
            loading: function() {
              $module.addClass(className.loading);
            },
            value: function(value) {
              module.verbose('Setting search input value', value);
              $prompt
                .val(value)
              ;
            },
            type: function(type) {
              type = type || settings.type;
              if(settings.type == 'category') {
                $module.addClass(settings.type);
              }
            },
            buttonPressed: function() {
              $searchButton.addClass(className.pressed);
            }
          },
  
          remove: {
            loading: function() {
              $module.removeClass(className.loading);
            },
            focus: function() {
              $module.removeClass(className.focus);
            },
            buttonPressed: function() {
              $searchButton.removeClass(className.pressed);
            },
            diacritics: function(text) {
              return settings.ignoreDiacritics ?  text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text;
            }
          },
  
          query: function(callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            var
              searchTerm = module.get.value(),
              cache = module.read.cache(searchTerm)
            ;
            callback = callback || function() {};
            if( module.has.minimumCharacters() )  {
              if(cache) {
                module.debug('Reading result from cache', searchTerm);
                module.save.results(cache.results);
                module.addResults(cache.html);
                module.inject.id(cache.results);
                callback();
              }
              else {
                module.debug('Querying for', searchTerm);
                if($.isPlainObject(settings.source) || Array.isArray(settings.source)) {
                  module.search.local(searchTerm);
                  callback();
                }
                else if( module.can.useAPI() ) {
                  module.search.remote(searchTerm, callback);
                }
                else {
                  module.error(error.source);
                  callback();
                }
              }
              settings.onSearchQuery.call(element, searchTerm);
            }
            else {
              module.hideResults();
            }
          },
  
          search: {
            local: function(searchTerm) {
              var
                results = module.search.object(searchTerm, settings.content),
                searchHTML
              ;
              module.set.loading();
              module.save.results(results);
              module.debug('Returned full local search results', results);
              if(settings.maxResults > 0) {
                module.debug('Using specified max results', results);
                results = results.slice(0, settings.maxResults);
              }
              if(settings.type == 'category') {
                results = module.create.categoryResults(results);
              }
              searchHTML = module.generateResults({
                results: results
              });
              module.remove.loading();
              module.addResults(searchHTML);
              module.inject.id(results);
              module.write.cache(searchTerm, {
                html    : searchHTML,
                results : results
              });
            },
            remote: function(searchTerm, callback) {
              callback = $.isFunction(callback)
                ? callback
                : function(){}
              ;
              if($module.api('is loading')) {
                $module.api('abort');
              }
              module.setup.api(searchTerm, callback);
              $module
                .api('query')
              ;
            },
            object: function(searchTerm, source, searchFields) {
              searchTerm = module.remove.diacritics(String(searchTerm));
              var
                results      = [],
                exactResults = [],
                fuzzyResults = [],
                searchExp    = searchTerm.replace(regExp.escape, '\\$&'),
                matchRegExp  = new RegExp(regExp.beginsWith + searchExp, 'i'),
  
                // avoid duplicates when pushing results
                addResult = function(array, result) {
                  var
                    notResult      = ($.inArray(result, results) == -1),
                    notFuzzyResult = ($.inArray(result, fuzzyResults) == -1),
                    notExactResults = ($.inArray(result, exactResults) == -1)
                  ;
                  if(notResult && notFuzzyResult && notExactResults) {
                    array.push(result);
                  }
                }
              ;
              source = source || settings.source;
              searchFields = (searchFields !== undefined)
                ? searchFields
                : settings.searchFields
              ;
  
              // search fields should be array to loop correctly
              if(!Array.isArray(searchFields)) {
                searchFields = [searchFields];
              }
  
              // exit conditions if no source
              if(source === undefined || source === false) {
                module.error(error.source);
                return [];
              }
              // iterate through search fields looking for matches
              $.each(searchFields, function(index, field) {
                $.each(source, function(label, content) {
                  var
                    fieldExists = (typeof content[field] == 'string')
                  ;
                  if(fieldExists) {
                    var text = module.remove.diacritics(content[field]);
                    if( text.search(matchRegExp) !== -1) {
                      // content starts with value (first in results)
                      addResult(results, content);
                    }
                    else if(settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text) ) {
                      // content fuzzy matches (last in results)
                      addResult(exactResults, content);
                    }
                    else if(settings.fullTextSearch == true && module.fuzzySearch(searchTerm, text) ) {
                      // content fuzzy matches (last in results)
                      addResult(fuzzyResults, content);
                    }
                  }
                });
              });
              $.merge(exactResults, fuzzyResults);
              $.merge(results, exactResults);
              return results;
            }
          },
          exactSearch: function (query, term) {
            query = query.toLowerCase();
            term  = term.toLowerCase();
            return term.indexOf(query) > -1;
          },
          fuzzySearch: function(query, term) {
            var
              termLength  = term.length,
              queryLength = query.length
            ;
            if(typeof query !== 'string') {
              return false;
            }
            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;
          },
  
          parse: {
            response: function(response, searchTerm) {
              if(Array.isArray(response)){
                  var o={};
                  o[fields.results]=response;
                  response = o;
              }
              var
                searchHTML = module.generateResults(response)
              ;
              module.verbose('Parsing server response', response);
              if(response !== undefined) {
                if(searchTerm !== undefined && response[fields.results] !== undefined) {
                  module.addResults(searchHTML);
                  module.inject.id(response[fields.results]);
                  module.write.cache(searchTerm, {
                    html    : searchHTML,
                    results : response[fields.results]
                  });
                  module.save.results(response[fields.results]);
                }
              }
            }
          },
  
          cancel: {
            query: function() {
              if( module.can.useAPI() ) {
                $module.api('abort');
              }
            }
          },
  
          has: {
            minimumCharacters: function() {
              var
                searchTerm    = module.get.value(),
                numCharacters = searchTerm.length
              ;
              return (numCharacters >= settings.minCharacters);
            },
            results: function() {
              if($results.length === 0) {
                return false;
              }
              var
                html = $results.html()
              ;
              return html != '';
            }
          },
  
          clear: {
            cache: function(value) {
              var
                cache = $module.data(metadata.cache)
              ;
              if(!value) {
                module.debug('Clearing cache', value);
                $module.removeData(metadata.cache);
              }
              else if(value && cache && cache[value]) {
                module.debug('Removing value from cache', value);
                delete cache[value];
                $module.data(metadata.cache, cache);
              }
            }
          },
  
          read: {
            cache: function(name) {
              var
                cache = $module.data(metadata.cache)
              ;
              if(settings.cache) {
                module.verbose('Checking cache for generated html for query', name);
                return (typeof cache == 'object') && (cache[name] !== undefined)
                  ? cache[name]
                  : false
                ;
              }
              return false;
            }
          },
  
          create: {
            categoryResults: function(results) {
              var
                categoryResults = {}
              ;
              $.each(results, function(index, result) {
                if(!result.category) {
                  return;
                }
                if(categoryResults[result.category] === undefined) {
                  module.verbose('Creating new category of results', result.category);
                  categoryResults[result.category] = {
                    name    : result.category,
                    results : [result]
                  };
                }
                else {
                  categoryResults[result.category].results.push(result);
                }
              });
              return categoryResults;
            },
            id: function(resultIndex, categoryIndex) {
              var
                resultID      = (resultIndex + 1), // not zero indexed
                letterID,
                id
              ;
              if(categoryIndex !== undefined) {
                // start char code for "A"
                letterID = String.fromCharCode(97 + categoryIndex);
                id          = letterID + resultID;
                module.verbose('Creating category result id', id);
              }
              else {
                id = resultID;
                module.verbose('Creating result id', id);
              }
              return id;
            },
            results: function() {
              if($results.length === 0) {
                $results = $('<div />')
                  .addClass(className.results)
                  .appendTo($module)
                ;
              }
            }
          },
  
          inject: {
            result: function(result, resultIndex, categoryIndex) {
              module.verbose('Injecting result into results');
              var
                $selectedResult = (categoryIndex !== undefined)
                  ? $results
                      .children().eq(categoryIndex)
                        .children(selector.results)
                          .first()
                          .children(selector.result)
                            .eq(resultIndex)
                  : $results
                      .children(selector.result).eq(resultIndex)
              ;
              module.verbose('Injecting results metadata', $selectedResult);
              $selectedResult
                .data(metadata.result, result)
              ;
            },
            id: function(results) {
              module.debug('Injecting unique ids into results');
              var
                // since results may be object, we must use counters
                categoryIndex = 0,
                resultIndex   = 0
              ;
              if(settings.type === 'category') {
                // iterate through each category result
                $.each(results, function(index, category) {
                  if(category.results.length > 0){
                    resultIndex = 0;
                    $.each(category.results, function(index, result) {
                      if(result.id === undefined) {
                        result.id = module.create.id(resultIndex, categoryIndex);
                      }
                      module.inject.result(result, resultIndex, categoryIndex);
                      resultIndex++;
                    });
                    categoryIndex++;
                  }
                });
              }
              else {
                // top level
                $.each(results, function(index, result) {
                  if(result.id === undefined) {
                    result.id = module.create.id(resultIndex);
                  }
                  module.inject.result(result, resultIndex);
                  resultIndex++;
                });
              }
              return results;
            }
          },
  
          save: {
            results: function(results) {
              module.verbose('Saving current search results to metadata', results);
              $module.data(metadata.results, results);
            }
          },
  
          write: {
            cache: function(name, value) {
              var
                cache = ($module.data(metadata.cache) !== undefined)
                  ? $module.data(metadata.cache)
                  : {}
              ;
              if(settings.cache) {
                module.verbose('Writing generated html to cache', name, value);
                cache[name] = value;
                $module
                  .data(metadata.cache, cache)
                ;
              }
            }
          },
  
          addResults: function(html) {
            if( $.isFunction(settings.onResultsAdd) ) {
              if( settings.onResultsAdd.call($results, html) === false ) {
                module.debug('onResultsAdd callback cancelled default action');
                return false;
              }
            }
            if(html) {
              $results
                .html(html)
              ;
              module.refreshResults();
              if(settings.selectFirstResult) {
                module.select.firstResult();
              }
              module.showResults();
            }
            else {
              module.hideResults(function() {
                $results.empty();
              });
            }
          },
  
          showResults: function(callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            if(resultsDismissed) {
              return;
            }
            if(!module.is.visible() && module.has.results()) {
              if( module.can.transition() ) {
                module.debug('Showing results with css animations');
                $results
                  .transition({
                    animation  : settings.transition + ' in',
                    debug      : settings.debug,
                    verbose    : settings.verbose,
                    duration   : settings.duration,
                    onComplete : function() {
                      callback();
                    },
                    queue      : true
                  })
                ;
              }
              else {
                module.debug('Showing results with javascript');
                $results
                  .stop()
                  .fadeIn(settings.duration, settings.easing)
                ;
              }
              settings.onResultsOpen.call($results);
            }
          },
          hideResults: function(callback) {
            callback = $.isFunction(callback)
              ? callback
              : function(){}
            ;
            if( module.is.visible() ) {
              if( module.can.transition() ) {
                module.debug('Hiding results with css animations');
                $results
                  .transition({
                    animation  : settings.transition + ' out',
                    debug      : settings.debug,
                    verbose    : settings.verbose,
                    duration   : settings.duration,
                    onComplete : function() {
                      callback();
                    },
                    queue      : true
                  })
                ;
              }
              else {
                module.debug('Hiding results with javascript');
                $results
                  .stop()
                  .fadeOut(settings.duration, settings.easing)
                ;
              }
              settings.onResultsClose.call($results);
            }
          },
  
          generateResults: function(response) {
            module.debug('Generating html from response', response);
            var
              template       = settings.templates[settings.type],
              isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])),
              isProperArray  = (Array.isArray(response[fields.results]) && response[fields.results].length > 0),
              html           = ''
            ;
            if(isProperObject || isProperArray ) {
              if(settings.maxResults > 0) {
                if(isProperObject) {
                  if(settings.type == 'standard') {
                    module.error(error.maxResults);
                  }
                }
                else {
                  response[fields.results] = response[fields.results].slice(0, settings.maxResults);
                }
              }
              if($.isFunction(template)) {
                html = template(response, fields, settings.preserveHTML);
              }
              else {
                module.error(error.noTemplate, false);
              }
            }
            else if(settings.showNoResults) {
              html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader);
            }
            settings.onResults.call(element, response);
            return html;
          },
  
          displayMessage: function(text, type, header) {
            type = type || 'standard';
            module.debug('Displaying message', text, type, header);
            module.addResults( settings.templates.message(text, type, header) );
            return settings.templates.message(text, type, header);
          },
  
          setting: function(name, value) {
            if( $.isPlainObject(name) ) {
              $.extend(true, settings, name);
            }
            else if(value !== undefined) {
              settings[name] = value;
            }
            else {
              return settings[name];
            }
          },
          internal: function(name, value) {
            if( $.isPlainObject(name) ) {
              $.extend(true, module, name);
            }
            else if(value !== undefined) {
              module[name] = value;
            }
            else {
              return module[name];
            }
          },
          debug: function() {
            if(!settings.silent && settings.debug) {
              if(settings.performance) {
                module.performance.log(arguments);
              }
              else {
                module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
                module.debug.apply(console, arguments);
              }
            }
          },
          verbose: function() {
            if(!settings.silent && settings.verbose && settings.debug) {
              if(settings.performance) {
                module.performance.log(arguments);
              }
              else {
                module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
                module.verbose.apply(console, arguments);
              }
            }
          },
          error: function() {
            if(!settings.silent) {
              module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
              module.error.apply(console, arguments);
            }
          },
          performance: {
            log: function(message) {
              var
                currentTime,
                executionTime,
                previousTime
              ;
              if(settings.performance) {
                currentTime   = new Date().getTime();
                previousTime  = time || currentTime;
                executionTime = currentTime - previousTime;
                time          = currentTime;
                performance.push({
                  'Name'           : message[0],
                  'Arguments'      : [].slice.call(message, 1) || '',
                  'Element'        : element,
                  'Execution Time' : executionTime
                });
              }
              clearTimeout(module.performance.timer);
              module.performance.timer = setTimeout(module.performance.display, 500);
            },
            display: function() {
              var
                title = settings.name + ':',
                totalTime = 0
              ;
              time = false;
              clearTimeout(module.performance.timer);
              $.each(performance, function(index, data) {
                totalTime += data['Execution Time'];
              });
              title += ' ' + totalTime + 'ms';
              if(moduleSelector) {
                title += ' \'' + moduleSelector + '\'';
              }
              if($allModules.length > 1) {
                title += ' ' + '(' + $allModules.length + ')';
              }
              if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
                console.groupCollapsed(title);
                if(console.table) {
                  console.table(performance);
                }
                else {
                  $.each(performance, function(index, data) {
                    console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
                  });
                }
                console.groupEnd();
              }
              performance = [];
            }
          },
          invoke: function(query, passedArguments, context) {
            var
              object = instance,
              maxDepth,
              found,
              response
            ;
            passedArguments = passedArguments || queryArguments;
            context         = element         || context;
            if(typeof query == 'string' && object !== undefined) {
              query    = query.split(/[\. ]/);
              maxDepth = query.length - 1;
              $.each(query, function(depth, value) {
                var camelCaseValue = (depth != maxDepth)
                  ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                  : query
                ;
                if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
                  object = object[camelCaseValue];
                }
                else if( object[camelCaseValue] !== undefined ) {
                  found = object[camelCaseValue];
                  return false;
                }
                else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
                  object = object[value];
                }
                else if( object[value] !== undefined ) {
                  found = object[value];
                  return false;
                }
                else {
                  return false;
                }
              });
            }
            if( $.isFunction( found ) ) {
              response = found.apply(context, passedArguments);
            }
            else if(found !== undefined) {
              response = found;
            }
            if(Array.isArray(returnedValue)) {
              returnedValue.push(response);
            }
            else if(returnedValue !== undefined) {
              returnedValue = [returnedValue, response];
            }
            else if(response !== undefined) {
              returnedValue = response;
            }
            return found;
          }
        };
        if(methodInvoked) {
          if(instance === undefined) {
            module.initialize();
          }
          module.invoke(query);
        }
        else {
          if(instance !== undefined) {
            instance.invoke('destroy');
          }
          module.initialize();
        }
  
      })
    ;
  
    return (returnedValue !== undefined)
      ? returnedValue
      : this
    ;
  };
  
  $.fn.search.settings = {
  
    name              : 'Search',
    namespace         : 'search',
  
    silent            : false,
    debug             : false,
    verbose           : false,
    performance       : true,
  
    // template to use (specified in settings.templates)
    type              : 'standard',
  
    // minimum characters required to search
    minCharacters     : 1,
  
    // whether to select first result after searching automatically
    selectFirstResult : false,
  
    // API config
    apiSettings       : false,
  
    // object to search
    source            : false,
  
    // Whether search should query current term on focus
    searchOnFocus     : true,
  
    // fields to search
    searchFields   : [
      'title',
      'description'
    ],
  
    // field to display in standard results template
    displayField   : '',
  
    // search anywhere in value (set to 'exact' to require exact matches
    fullTextSearch : 'exact',
  
    // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...)
    ignoreDiacritics : false,
  
    // whether to add events to prompt automatically
    automatic      : true,
  
    // delay before hiding menu after blur
    hideDelay      : 0,
  
    // delay before searching
    searchDelay    : 200,
  
    // maximum results returned from search
    maxResults     : 7,
  
    // whether to store lookups in local cache
    cache          : true,
  
    // whether no results errors should be shown
    showNoResults  : true,
  
    // preserve possible html of resultset values
    preserveHTML   : true,
  
    // transition settings
    transition     : 'scale',
    duration       : 200,
    easing         : 'easeOutExpo',
  
    // callbacks
    onSelect       : false,
    onResultsAdd   : false,
  
    onSearchQuery  : function(query){},
    onResults      : function(response){},
  
    onResultsOpen  : function(){},
    onResultsClose : function(){},
  
    className: {
      animating : 'animating',
      active    : 'active',
      empty     : 'empty',
      focus     : 'focus',
      hidden    : 'hidden',
      loading   : 'loading',
      results   : 'results',
      pressed   : 'down'
    },
  
    error : {
      source          : 'Cannot search. No source used, and Semantic API module was not included',
      noResultsHeader : 'No Results',
      noResults       : 'Your search returned no results',
      logging         : 'Error in debug logging, exiting.',
      noEndpoint      : 'No search endpoint was specified',
      noTemplate      : 'A valid template name was not specified.',
      oldSearchSyntax : 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.',
      serverError     : 'There was an issue querying the server.',
      maxResults      : 'Results must be an array to use maxResults setting',
      method          : 'The method you called is not defined.',
      noNormalize     : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.'
    },
  
    metadata: {
      cache   : 'cache',
      results : 'results',
      result  : 'result'
    },
  
    regExp: {
      escape     : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
      beginsWith : '(?:\s|^)'
    },
  
    // maps api response attributes to internal representation
    fields: {
      categories      : 'results',     // array of categories (category view)
      categoryName    : 'name',        // name of category (category view)
      categoryResults : 'results',     // array of results (category view)
      description     : 'description', // result description
      image           : 'image',       // result image
      price           : 'price',       // result price
      results         : 'results',     // array of results (standard)
      title           : 'title',       // result title
      url             : 'url',         // result url
      action          : 'action',      // "view more" object name
      actionText      : 'text',        // "view more" text
      actionURL       : 'url'          // "view more" url
    },
  
    selector : {
      prompt       : '.prompt',
      searchButton : '.search.button',
      results      : '.results',
      message      : '.results > .message',
      category     : '.category',
      result       : '.result',
      title        : '.title, .name'
    },
  
    templates: {
      escape: function(string, preserveHTML) {
        if (preserveHTML){
          return string;
        }
        var
          badChars     = /[&<>"'`]/g,
          shouldEscape = /[&<>"'`]/,
          escape       = {
            "&": "&amp;",
            "<": "&lt;",
            ">": "&gt;",
            '"': "&quot;",
            "'": "&#x27;",
            "`": "&#x60;"
          },
          escapedChar  = function(chr) {
            return escape[chr];
          }
        ;
        if(shouldEscape.test(string)) {
          return string.replace(badChars, escapedChar);
        }
        return string;
      },
      message: function(message, type, header) {
        var
          html = ''
        ;
        if(message !== undefined && type !== undefined) {
          html +=  ''
            + '<div class="message ' + type + '">'
          ;
          if(header) {
            html += ''
            + '<div class="header">' + header + '</div>'
            ;
          }
          html += ' <div class="description">' + message + '</div>';
          html += '</div>';
        }
        return html;
      },
      category: function(response, fields, preserveHTML) {
        var
          html = '',
          escape = $.fn.search.settings.templates.escape
        ;
        if(response[fields.categoryResults] !== undefined) {
  
          // each category
          $.each(response[fields.categoryResults], function(index, category) {
            if(category[fields.results] !== undefined && category.results.length > 0) {
  
              html  += '<div class="category">';
  
              if(category[fields.categoryName] !== undefined) {
                html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>';
              }
  
              // each item inside category
              html += '<div class="results">';
              $.each(category.results, function(index, result) {
                if(result[fields.url]) {
                  html  += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">';
                }
                else {
                  html  += '<a class="result">';
                }
                if(result[fields.image] !== undefined) {
                  html += ''
                    + '<div class="image">'
                    + ' <img src="' + result[fields.image].replace(/"/g,"") + '">'
                    + '</div>'
                  ;
                }
                html += '<div class="content">';
                if(result[fields.price] !== undefined) {
                  html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>';
                }
                if(result[fields.title] !== undefined) {
                  html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>';
                }
                if(result[fields.description] !== undefined) {
                  html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>';
                }
                html  += ''
                  + '</div>'
                ;
                html += '</a>';
              });
              html += '</div>';
              html  += ''
                + '</div>'
              ;
            }
          });
          if(response[fields.action]) {
            if(fields.actionURL === false) {
              html += ''
              + '<div class="action">'
              +   escape(response[fields.action][fields.actionText], preserveHTML)
              + '</div>';
            } else {
              html += ''
              + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">'
              +   escape(response[fields.action][fields.actionText], preserveHTML)
              + '</a>';
            }
          }
          return html;
        }
        return false;
      },
      standard: function(response, fields, preserveHTML) {
        var
          html = '',
          escape = $.fn.search.settings.templates.escape
        ;
        if(response[fields.results] !== undefined) {
  
          // each result
          $.each(response[fields.results], function(index, result) {
            if(result[fields.url]) {
              html  += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">';
            }
            else {
              html  += '<a class="result">';
            }
            if(result[fields.image] !== undefined) {
              html += ''
                + '<div class="image">'
                + ' <img src="' + result[fields.image].replace(/"/g,"") + '">'
                + '</div>'
              ;
            }
            html += '<div class="content">';
            if(result[fields.price] !== undefined) {
              html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>';
            }
            if(result[fields.title] !== undefined) {
              html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>';
            }
            if(result[fields.description] !== undefined) {
              html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>';
            }
            html  += ''
              + '</div>'
            ;
            html += '</a>';
          });
          if(response[fields.action]) {
            if(fields.actionURL === false) {
              html += ''
              + '<div class="action">'
              +   escape(response[fields.action][fields.actionText], preserveHTML)
              + '</div>';
            } else {
              html += ''
              + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">'
              +   escape(response[fields.action][fields.actionText], preserveHTML)
              + '</a>';
            }
          }
          return html;
        }
        return false;
      }
    }
  };
  
  })( jQuery, window, document );
  