github.com/fanux/shipyard@v0.0.0-20161009071005-6515ce223235/controller/static/semantic/dist/components/search.js (about)

     1  /*!
     2   * # Semantic UI x.x - Search
     3   * http://github.com/semantic-org/semantic-ui/
     4   *
     5   *
     6   * Copyright 2014 Contributors
     7   * Released under the MIT license
     8   * http://opensource.org/licenses/MIT
     9   *
    10   */
    11  
    12  ;(function ($, window, document, undefined) {
    13  
    14  "use strict";
    15  
    16  $.fn.search = function(parameters) {
    17    var
    18      $allModules     = $(this),
    19      moduleSelector  = $allModules.selector || '',
    20  
    21      time            = new Date().getTime(),
    22      performance     = [],
    23  
    24      query           = arguments[0],
    25      methodInvoked   = (typeof query == 'string'),
    26      queryArguments  = [].slice.call(arguments, 1),
    27      returnedValue
    28    ;
    29    $(this)
    30      .each(function() {
    31        var
    32          settings        = $.extend(true, {}, $.fn.search.settings, parameters),
    33  
    34          className       = settings.className,
    35          metadata        = settings.metadata,
    36          regExp          = settings.regExp,
    37          selector        = settings.selector,
    38          error           = settings.error,
    39          namespace       = settings.namespace,
    40  
    41          eventNamespace  = '.' + namespace,
    42          moduleNamespace = namespace + '-module',
    43  
    44          $module         = $(this),
    45          $prompt         = $module.find(selector.prompt),
    46          $searchButton   = $module.find(selector.searchButton),
    47          $results        = $module.find(selector.results),
    48          $result         = $module.find(selector.result),
    49          $category       = $module.find(selector.category),
    50  
    51          element         = this,
    52          instance        = $module.data(moduleNamespace),
    53  
    54          module
    55        ;
    56        module = {
    57  
    58          initialize: function() {
    59            module.verbose('Initializing module');
    60            var
    61              prompt = $prompt[0],
    62              inputEvent   = (prompt !== undefined && prompt.oninput !== undefined)
    63                ? 'input'
    64                : (prompt !== undefined && prompt.onpropertychange !== undefined)
    65                  ? 'propertychange'
    66                  : 'keyup'
    67            ;
    68            if(settings.automatic) {
    69              $prompt
    70                .on(inputEvent + eventNamespace, module.throttle)
    71                .attr('autocomplete', 'off')
    72              ;
    73            }
    74            $prompt
    75              .on('focus' + eventNamespace, module.event.focus)
    76              .on('blur' + eventNamespace, module.event.blur)
    77              .on('keydown' + eventNamespace, module.handleKeyboard)
    78            ;
    79            $searchButton
    80              .on('click' + eventNamespace, module.query)
    81            ;
    82            $results
    83              .on('mousedown' + eventNamespace, module.event.result.mousedown)
    84              .on('mouseup' + eventNamespace, module.event.result.mouseup)
    85              .on('click' + eventNamespace, selector.result, module.event.result.click)
    86            ;
    87            module.instantiate();
    88          },
    89          instantiate: function() {
    90            module.verbose('Storing instance of module', module);
    91            instance = module;
    92            $module
    93              .data(moduleNamespace, module)
    94            ;
    95          },
    96          destroy: function() {
    97            module.verbose('Destroying instance');
    98            $module
    99              .removeData(moduleNamespace)
   100            ;
   101            $prompt
   102              .off(eventNamespace)
   103            ;
   104            $searchButton
   105              .off(eventNamespace)
   106            ;
   107            $results
   108              .off(eventNamespace)
   109            ;
   110          },
   111          event: {
   112            focus: function() {
   113              module.set.focus();
   114              clearTimeout(module.timer);
   115              module.throttle();
   116              if( module.has.minimumCharacters() ) {
   117                module.showResults();
   118              }
   119            },
   120            blur: function(event) {
   121              var
   122                pageLostFocus = (document.activeElement === this)
   123              ;
   124              if(!pageLostFocus && !module.resultsClicked) {
   125                module.cancel.query();
   126                module.remove.focus();
   127                module.timer = setTimeout(module.hideResults, settings.hideDelay);
   128              }
   129            },
   130            result: {
   131              mousedown: function() {
   132                module.resultsClicked = true;
   133              },
   134              mouseup: function() {
   135                module.resultsClicked = false;
   136              },
   137              click: function(event) {
   138                module.debug('Search result selected');
   139                var
   140                  $result = $(this),
   141                  $title  = $result.find(selector.title).eq(0),
   142                  $link   = $result.find('a[href]').eq(0),
   143                  href    = $link.attr('href')   || false,
   144                  target  = $link.attr('target') || false,
   145                  title   = $title.html(),
   146                  name    = ($title.length > 0)
   147                    ? $title.text()
   148                    : false,
   149                  results = module.get.results(),
   150                  result  = module.get.result(name, results),
   151                  returnedValue
   152                ;
   153                if( $.isFunction(settings.onSelect) ) {
   154                  if(settings.onSelect.call(element, result, results) === false) {
   155                    module.debug('Custom onSelect callback cancelled default select action');
   156                    return;
   157                  }
   158                }
   159                module.hideResults();
   160                if(name) {
   161                  module.set.value(name);
   162                }
   163                if(href) {
   164                  module.verbose('Opening search link found in result', $link);
   165                  if(target == '_blank' || event.ctrlKey) {
   166                    window.open(href);
   167                  }
   168                  else {
   169                    window.location.href = (href);
   170                  }
   171                }
   172              }
   173            }
   174          },
   175          handleKeyboard: function(event) {
   176            var
   177              // force selector refresh
   178              $result      = $module.find(selector.result),
   179              $category    = $module.find(selector.category),
   180              currentIndex = $result.index( $result.filter('.' + className.active) ),
   181              resultSize   = $result.length,
   182  
   183              keyCode      = event.which,
   184              keys         = {
   185                backspace : 8,
   186                enter     : 13,
   187                escape    : 27,
   188                upArrow   : 38,
   189                downArrow : 40
   190              },
   191              newIndex
   192            ;
   193            // search shortcuts
   194            if(keyCode == keys.escape) {
   195              module.verbose('Escape key pressed, blurring search field');
   196              $prompt
   197                .trigger('blur')
   198              ;
   199            }
   200            if( module.is.visible() ) {
   201              if(keyCode == keys.enter) {
   202                module.verbose('Enter key pressed, selecting active result');
   203                if( $result.filter('.' + className.active).length > 0 ) {
   204                  module.event.result.click.call($result.filter('.' + className.active), event);
   205                  event.preventDefault();
   206                  return false;
   207                }
   208              }
   209              else if(keyCode == keys.upArrow) {
   210                module.verbose('Up key pressed, changing active result');
   211                newIndex = (currentIndex - 1 < 0)
   212                  ? currentIndex
   213                  : currentIndex - 1
   214                ;
   215                $category
   216                  .removeClass(className.active)
   217                ;
   218                $result
   219                  .removeClass(className.active)
   220                  .eq(newIndex)
   221                    .addClass(className.active)
   222                    .closest($category)
   223                      .addClass(className.active)
   224                ;
   225                event.preventDefault();
   226              }
   227              else if(keyCode == keys.downArrow) {
   228                module.verbose('Down key pressed, changing active result');
   229                newIndex = (currentIndex + 1 >= resultSize)
   230                  ? currentIndex
   231                  : currentIndex + 1
   232                ;
   233                $category
   234                  .removeClass(className.active)
   235                ;
   236                $result
   237                  .removeClass(className.active)
   238                  .eq(newIndex)
   239                    .addClass(className.active)
   240                    .closest($category)
   241                      .addClass(className.active)
   242                ;
   243                event.preventDefault();
   244              }
   245            }
   246            else {
   247              // query shortcuts
   248              if(keyCode == keys.enter) {
   249                module.verbose('Enter key pressed, executing query');
   250                module.query();
   251                module.set.buttonPressed();
   252                $prompt.one('keyup', module.remove.buttonFocus);
   253              }
   254            }
   255          },
   256  
   257          setup: {
   258            api: function() {
   259              var
   260                apiSettings = {
   261                  on        : false,
   262                  action    : 'search',
   263                  onFailure : module.error
   264                },
   265                searchHTML
   266              ;
   267              module.verbose('First request, initializing API');
   268              $module.api(apiSettings);
   269            }
   270          },
   271  
   272          can: {
   273            useAPI: function() {
   274              return $.fn.api !== undefined;
   275            },
   276            transition: function() {
   277              return settings.transition && $.fn.transition !== undefined && $module.transition('is supported');
   278            }
   279          },
   280  
   281          is: {
   282            empty: function() {
   283              return ($results.html() === '');
   284            },
   285            visible: function() {
   286              return ($results.filter(':visible').length > 0);
   287            },
   288            focused: function() {
   289              return ($prompt.filter(':focus').length > 0);
   290            }
   291          },
   292  
   293          get: {
   294            value: function() {
   295              return $prompt.val();
   296            },
   297            results: function() {
   298              var
   299                results = $module.data(metadata.results)
   300              ;
   301              return results;
   302            },
   303            result: function(value, results) {
   304              var
   305                result = false
   306              ;
   307              value   = value   || module.get.value();
   308              results = results || module.get.results();
   309              if(settings.type === 'category') {
   310                module.debug('Finding result that matches', value);
   311                $.each(results, function(index, category) {
   312                  if($.isArray(category.results)) {
   313                    result = module.search.object(value, category.results)[0];
   314                    if(result && result.length > 0) {
   315                      return true;
   316                    }
   317                  }
   318                });
   319              }
   320              else {
   321                module.debug('Finding result in results object', value);
   322                result = module.search.object(value, results)[0];
   323              }
   324              return result;
   325            },
   326          },
   327  
   328          set: {
   329            focus: function() {
   330              $module.addClass(className.focus);
   331            },
   332            loading: function() {
   333              $module.addClass(className.loading);
   334            },
   335            value: function(value) {
   336              module.verbose('Setting search input value', value);
   337              $prompt.val(value);
   338              module.query();
   339            },
   340            buttonPressed: function() {
   341              $searchButton.addClass(className.pressed);
   342            }
   343          },
   344  
   345          remove: {
   346            loading: function() {
   347              $module.removeClass(className.loading);
   348            },
   349            focus: function() {
   350              $module.removeClass(className.focus);
   351            },
   352            buttonPressed: function() {
   353              $searchButton.removeClass(className.pressed);
   354            }
   355          },
   356  
   357          query: function() {
   358            var
   359              searchTerm = module.get.value(),
   360              cache = module.read.cache(searchTerm)
   361            ;
   362            if(cache) {
   363              module.debug('Reading result for ' + searchTerm + ' from cache');
   364              module.save.results(cache.results);
   365              module.addResults(cache.html);
   366            }
   367            else {
   368              module.debug('Querying for ' + searchTerm);
   369              if($.isPlainObject(settings.source) || $.isArray(settings.source)) {
   370                module.search.local(searchTerm);
   371              }
   372              else if( module.can.useAPI() ) {
   373                if(settings.apiSettings) {
   374                  module.debug('Searching with specified API settings', settings.apiSettings);
   375                  module.search.remote(searchTerm);
   376                }
   377                else if($.api.settings.api.search !== undefined) {
   378                  module.debug('Searching with default search API endpoint');
   379                  module.search.remote(searchTerm);
   380                }
   381                else {
   382                  module.error(error.noEndpoint);
   383                }
   384              }
   385              else {
   386                module.error(error.source);
   387              }
   388              settings.onSearchQuery.call(element, searchTerm);
   389            }
   390          },
   391  
   392          search: {
   393            local: function(searchTerm) {
   394              var
   395                searchResults = module.search.object(searchTerm, settings.content),
   396                searchHTML
   397              ;
   398              module.set.loading();
   399              module.save.results(searchResults);
   400              module.debug('Returned local search results', searchResults);
   401  
   402              searchHTML = module.generateResults({
   403                results: searchResults
   404              });
   405              module.remove.loading();
   406              module.write.cache(searchTerm, {
   407                html    : searchHTML,
   408                results : searchResults
   409              });
   410              module.addResults(searchHTML);
   411            },
   412            remote: function(searchTerm) {
   413              var
   414                apiSettings = {
   415                  onSuccess : function(response) {
   416                    module.parse.response.call(element, response, searchTerm);
   417                  },
   418                  urlData: {
   419                    query: searchTerm
   420                  }
   421                }
   422              ;
   423              if( !$module.api('get request') ) {
   424                module.setup.api();
   425              }
   426              $.extend(true, apiSettings, settings.apiSettings);
   427              module.debug('Executing search', apiSettings);
   428              module.cancel.query();
   429              $module
   430                .api('setting', apiSettings)
   431                .api('query')
   432              ;
   433            },
   434            object: function(searchTerm, source) {
   435              var
   436                results         = [],
   437                fullTextResults = [],
   438                searchFields    = $.isArray(settings.searchFields)
   439                  ? settings.searchFields
   440                  : [settings.searchFields],
   441                searchExp       = searchTerm.replace(regExp.escape, '\\$&'),
   442                searchRegExp    = new RegExp(regExp.exact + searchExp, 'i')
   443              ;
   444  
   445              source = source || settings.source;
   446  
   447              // exit conditions on no source
   448              if(source === undefined) {
   449                module.error(error.source);
   450                return [];
   451              }
   452  
   453              // iterate through search fields in array order
   454              $.each(searchFields, function(index, field) {
   455                $.each(source, function(label, content) {
   456                  var
   457                    fieldExists = (typeof content[field] == 'string'),
   458                    notAlreadyResult = ($.inArray(content, results) == -1 && $.inArray(content, fullTextResults) == -1)
   459                  ;
   460                  if(fieldExists && notAlreadyResult) {
   461                    if( content[field].match(searchRegExp) ) {
   462                      results.push(content);
   463                    }
   464                    else if(settings.searchFullText && module.fuzzySearch(searchTerm, content[field]) ) {
   465                      fullTextResults.push(content);
   466                    }
   467                  }
   468                });
   469              });
   470              return $.merge(results, fullTextResults);
   471            }
   472          },
   473  
   474          fuzzySearch: function(query, term) {
   475            var
   476              termLength  = term.length,
   477              queryLength = query.length
   478            ;
   479            query = query.toLowerCase();
   480            term  = term.toLowerCase();
   481            if(queryLength > termLength) {
   482              return false;
   483            }
   484            if(queryLength === termLength) {
   485              return (query === term);
   486            }
   487            search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
   488              var
   489                queryCharacter = query.charCodeAt(characterIndex)
   490              ;
   491              while(nextCharacterIndex < termLength) {
   492                if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
   493                  continue search;
   494                }
   495              }
   496              return false;
   497            }
   498            return true;
   499          },
   500  
   501          parse: {
   502            response: function(response, searchTerm) {
   503              var
   504                searchHTML = module.generateResults(response)
   505              ;
   506              module.verbose('Parsing server response', response);
   507              if(response !== undefined) {
   508                if(searchTerm !== undefined && response.results !== undefined) {
   509                  module.write.cache(searchTerm, {
   510                    html    : searchHTML,
   511                    results : response.results
   512                  });
   513                  module.save.results(response.results);
   514                  module.addResults(searchHTML);
   515                }
   516              }
   517            }
   518          },
   519  
   520          throttle: function() {
   521            clearTimeout(module.timer);
   522            if(module.has.minimumCharacters())  {
   523              module.timer = setTimeout(module.query, settings.searchDelay);
   524            }
   525            else {
   526              module.hideResults();
   527            }
   528          },
   529  
   530          cancel: {
   531            query: function() {
   532              if( module.can.useAPI() ) {
   533                $module.api('abort');
   534              }
   535            }
   536          },
   537  
   538          has: {
   539            minimumCharacters: function() {
   540              var
   541                searchTerm    = module.get.value(),
   542                numCharacters = searchTerm.length
   543              ;
   544              return (numCharacters >= settings.minCharacters);
   545            }
   546          },
   547  
   548          read: {
   549            cache: function(name) {
   550              var
   551                cache = $module.data(metadata.cache)
   552              ;
   553              if(settings.cache) {
   554                module.verbose('Checking cache for generated html for query', name);
   555                return (typeof cache == 'object') && (cache[name] !== undefined)
   556                  ? cache[name]
   557                  : false
   558                ;
   559              }
   560              return false;
   561            }
   562          },
   563  
   564          save: {
   565            results: function(results) {
   566              module.verbose('Saving current search results to metadata', results);
   567              $module.data(metadata.results, results);
   568            }
   569          },
   570  
   571          write: {
   572            cache: function(name, value) {
   573              var
   574                cache = ($module.data(metadata.cache) !== undefined)
   575                  ? $module.data(metadata.cache)
   576                  : {}
   577              ;
   578              if(settings.cache) {
   579                module.verbose('Writing generated html to cache', name, value);
   580                cache[name] = value;
   581                $module
   582                  .data(metadata.cache, cache)
   583                ;
   584              }
   585            }
   586          },
   587  
   588          addResults: function(html) {
   589            if( $.isFunction(settings.onResultsAdd) ) {
   590              if( settings.onResultsAdd.call($results, html) === false ) {
   591                module.debug('onResultsAdd callback cancelled default action');
   592                return false;
   593              }
   594            }
   595            $results
   596              .html(html)
   597            ;
   598            module.showResults();
   599          },
   600  
   601          showResults: function() {
   602            if( !module.is.visible() && module.is.focused() && !module.is.empty() ) {
   603              if( module.can.transition() ) {
   604                module.debug('Showing results with css animations');
   605                $results
   606                  .transition({
   607                    animation  : settings.transition + ' in',
   608                    duration   : settings.duration,
   609                    queue      : true
   610                  })
   611                ;
   612              }
   613              else {
   614                module.debug('Showing results with javascript');
   615                $results
   616                  .stop()
   617                  .fadeIn(settings.duration, settings.easing)
   618                ;
   619              }
   620              settings.onResultsOpen.call($results);
   621            }
   622          },
   623          hideResults: function() {
   624            if( module.is.visible() ) {
   625              if( module.can.transition() ) {
   626                module.debug('Hiding results with css animations');
   627                $results
   628                  .transition({
   629                    animation  : settings.transition + ' out',
   630                    duration   : settings.duration,
   631                    queue      : true
   632                  })
   633                ;
   634              }
   635              else {
   636                module.debug('Hiding results with javascript');
   637                $results
   638                  .stop()
   639                  .fadeOut(settings.duration, settings.easing)
   640                ;
   641              }
   642              settings.onResultsClose.call($results);
   643            }
   644          },
   645  
   646          generateResults: function(response) {
   647            module.debug('Generating html from response', response);
   648            var
   649              template       = settings.templates[settings.type],
   650              isProperObject = ($.isPlainObject(response.results) && !$.isEmptyObject(response.results)),
   651              isProperArray  = ($.isArray(response.results) && response.results.length > 0),
   652              html           = ''
   653            ;
   654            if(isProperObject || isProperArray ) {
   655              if(settings.maxResults > 0) {
   656                if(isProperObject) {
   657                  if(settings.type == 'standard') {
   658                    module.error(error.maxResults);
   659                  }
   660                }
   661                else {
   662                  response.results = response.results.slice(0, settings.maxResults);
   663                }
   664              }
   665              if($.isFunction(template)) {
   666                html = template(response);
   667              }
   668              else {
   669                module.error(error.noTemplate, false);
   670              }
   671            }
   672            else {
   673              html = module.displayMessage(error.noResults, 'empty');
   674            }
   675            settings.onResults.call(element, response);
   676            return html;
   677          },
   678  
   679          displayMessage: function(text, type) {
   680            type = type || 'standard';
   681            module.debug('Displaying message', text, type);
   682            module.addResults( settings.templates.message(text, type) );
   683            return settings.templates.message(text, type);
   684          },
   685  
   686          setting: function(name, value) {
   687            if( $.isPlainObject(name) ) {
   688              $.extend(true, settings, name);
   689            }
   690            else if(value !== undefined) {
   691              settings[name] = value;
   692            }
   693            else {
   694              return settings[name];
   695            }
   696          },
   697          internal: function(name, value) {
   698            if( $.isPlainObject(name) ) {
   699              $.extend(true, module, name);
   700            }
   701            else if(value !== undefined) {
   702              module[name] = value;
   703            }
   704            else {
   705              return module[name];
   706            }
   707          },
   708          debug: function() {
   709            if(settings.debug) {
   710              if(settings.performance) {
   711                module.performance.log(arguments);
   712              }
   713              else {
   714                module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
   715                module.debug.apply(console, arguments);
   716              }
   717            }
   718          },
   719          verbose: function() {
   720            if(settings.verbose && settings.debug) {
   721              if(settings.performance) {
   722                module.performance.log(arguments);
   723              }
   724              else {
   725                module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
   726                module.verbose.apply(console, arguments);
   727              }
   728            }
   729          },
   730          error: function() {
   731            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
   732            module.error.apply(console, arguments);
   733          },
   734          performance: {
   735            log: function(message) {
   736              var
   737                currentTime,
   738                executionTime,
   739                previousTime
   740              ;
   741              if(settings.performance) {
   742                currentTime   = new Date().getTime();
   743                previousTime  = time || currentTime;
   744                executionTime = currentTime - previousTime;
   745                time          = currentTime;
   746                performance.push({
   747                  'Name'           : message[0],
   748                  'Arguments'      : [].slice.call(message, 1) || '',
   749                  'Element'        : element,
   750                  'Execution Time' : executionTime
   751                });
   752              }
   753              clearTimeout(module.performance.timer);
   754              module.performance.timer = setTimeout(module.performance.display, 100);
   755            },
   756            display: function() {
   757              var
   758                title = settings.name + ':',
   759                totalTime = 0
   760              ;
   761              time = false;
   762              clearTimeout(module.performance.timer);
   763              $.each(performance, function(index, data) {
   764                totalTime += data['Execution Time'];
   765              });
   766              title += ' ' + totalTime + 'ms';
   767              if(moduleSelector) {
   768                title += ' \'' + moduleSelector + '\'';
   769              }
   770              if($allModules.length > 1) {
   771                title += ' ' + '(' + $allModules.length + ')';
   772              }
   773              if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
   774                console.groupCollapsed(title);
   775                if(console.table) {
   776                  console.table(performance);
   777                }
   778                else {
   779                  $.each(performance, function(index, data) {
   780                    console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
   781                  });
   782                }
   783                console.groupEnd();
   784              }
   785              performance = [];
   786            }
   787          },
   788          invoke: function(query, passedArguments, context) {
   789            var
   790              object = instance,
   791              maxDepth,
   792              found,
   793              response
   794            ;
   795            passedArguments = passedArguments || queryArguments;
   796            context         = element         || context;
   797            if(typeof query == 'string' && object !== undefined) {
   798              query    = query.split(/[\. ]/);
   799              maxDepth = query.length - 1;
   800              $.each(query, function(depth, value) {
   801                var camelCaseValue = (depth != maxDepth)
   802                  ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
   803                  : query
   804                ;
   805                if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
   806                  object = object[camelCaseValue];
   807                }
   808                else if( object[camelCaseValue] !== undefined ) {
   809                  found = object[camelCaseValue];
   810                  return false;
   811                }
   812                else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
   813                  object = object[value];
   814                }
   815                else if( object[value] !== undefined ) {
   816                  found = object[value];
   817                  return false;
   818                }
   819                else {
   820                  return false;
   821                }
   822              });
   823            }
   824            if( $.isFunction( found ) ) {
   825              response = found.apply(context, passedArguments);
   826            }
   827            else if(found !== undefined) {
   828              response = found;
   829            }
   830            if($.isArray(returnedValue)) {
   831              returnedValue.push(response);
   832            }
   833            else if(returnedValue !== undefined) {
   834              returnedValue = [returnedValue, response];
   835            }
   836            else if(response !== undefined) {
   837              returnedValue = response;
   838            }
   839            return found;
   840          }
   841        };
   842        if(methodInvoked) {
   843          if(instance === undefined) {
   844            module.initialize();
   845          }
   846          module.invoke(query);
   847        }
   848        else {
   849          if(instance !== undefined) {
   850            instance.invoke('destroy');
   851          }
   852          module.initialize();
   853        }
   854  
   855      })
   856    ;
   857  
   858    return (returnedValue !== undefined)
   859      ? returnedValue
   860      : this
   861    ;
   862  };
   863  
   864  $.fn.search.settings = {
   865  
   866    name           : 'Search Module',
   867    namespace      : 'search',
   868  
   869    debug          : false,
   870    verbose        : true,
   871    performance    : true,
   872  
   873    type           : 'standard',
   874    minCharacters  : 1,
   875  
   876    // api config
   877    apiSettings    : false,
   878  
   879    source         : false,
   880    searchFields   : [
   881      'title',
   882      'description'
   883    ],
   884    searchFullText : true,
   885  
   886    automatic      : 'true',
   887    hideDelay      : 0,
   888    searchDelay    : 100,
   889    maxResults     : 7,
   890    cache          : true,
   891  
   892    transition     : 'scale',
   893    duration       : 300,
   894    easing         : 'easeOutExpo',
   895  
   896    onSelect       : false,
   897    onResultsAdd   : false,
   898  
   899    onSearchQuery  : function(){},
   900    onResults      : function(response){},
   901  
   902    onResultsOpen  : function(){},
   903    onResultsClose : function(){},
   904  
   905    className: {
   906      active  : 'active',
   907      empty   : 'empty',
   908      focus   : 'focus',
   909      loading : 'loading',
   910      pressed : 'down'
   911    },
   912  
   913    error : {
   914      source      : 'Cannot search. No source used, and Semantic API module was not included',
   915      noResults   : 'Your search returned no results',
   916      logging     : 'Error in debug logging, exiting.',
   917      noEndpoint  : 'No search endpoint was specified',
   918      noTemplate  : 'A valid template name was not specified.',
   919      serverError : 'There was an issue with querying the server.',
   920      maxResults  : 'Results must be an array to use maxResults setting',
   921      method      : 'The method you called is not defined.'
   922    },
   923  
   924    metadata: {
   925      cache   : 'cache',
   926      results : 'results'
   927    },
   928  
   929    regExp: {
   930      escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
   931      exact  : '(?:\s|^)'
   932    },
   933  
   934    selector : {
   935      prompt       : '.prompt',
   936      searchButton : '.search.button',
   937      results      : '.results',
   938      category     : '.category',
   939      result       : '.result',
   940      title        : '.title, .name'
   941    },
   942  
   943    templates: {
   944      escape: function(string) {
   945        var
   946          badChars     = /[&<>"'`]/g,
   947          shouldEscape = /[&<>"'`]/,
   948          escape       = {
   949            "&": "&amp;",
   950            "<": "&lt;",
   951            ">": "&gt;",
   952            '"': "&quot;",
   953            "'": "&#x27;",
   954            "`": "&#x60;"
   955          },
   956          escapedChar  = function(chr) {
   957            return escape[chr];
   958          }
   959        ;
   960        if(shouldEscape.test(string)) {
   961          return string.replace(badChars, escapedChar);
   962        }
   963        return string;
   964      },
   965      message: function(message, type) {
   966        var
   967          html = ''
   968        ;
   969        if(message !== undefined && type !== undefined) {
   970          html +=  ''
   971            + '<div class="message ' + type + '">'
   972          ;
   973          // message type
   974          if(type == 'empty') {
   975            html += ''
   976              + '<div class="header">No Results</div class="header">'
   977              + '<div class="description">' + message + '</div class="description">'
   978            ;
   979          }
   980          else {
   981            html += ' <div class="description">' + message + '</div>';
   982          }
   983          html += '</div>';
   984        }
   985        return html;
   986      },
   987      category: function(response) {
   988        var
   989          html = '',
   990          escape = $.fn.search.settings.templates.escape
   991        ;
   992        if(response.results !== undefined) {
   993          // each category
   994          $.each(response.results, function(index, category) {
   995            if(category.results !== undefined && category.results.length > 0) {
   996              html  += ''
   997                + '<div class="category">'
   998                + '<div class="name">' + category.name + '</div>'
   999              ;
  1000              // each item inside category
  1001              $.each(category.results, function(index, result) {
  1002                html  += '<div class="result">';
  1003                if(result.url) {
  1004                  html  += '<a href="' + result.url + '"></a>';
  1005                }
  1006                if(result.image !== undefined) {
  1007                  result.image = escape(result.image);
  1008                  html += ''
  1009                    + '<div class="image">'
  1010                    + ' <img src="' + result.image + '" alt="">'
  1011                    + '</div>'
  1012                  ;
  1013                }
  1014                html += '<div class="content">';
  1015                if(result.price !== undefined) {
  1016                  result.price = escape(result.price);
  1017                  html += '<div class="price">' + result.price + '</div>';
  1018                }
  1019                if(result.title !== undefined) {
  1020                  result.title = escape(result.title);
  1021                  html += '<div class="title">' + result.title + '</div>';
  1022                }
  1023                if(result.description !== undefined) {
  1024                  html += '<div class="description">' + result.description + '</div>';
  1025                }
  1026                html  += ''
  1027                  + '</div>'
  1028                  + '</div>'
  1029                ;
  1030              });
  1031              html  += ''
  1032                + '</div>'
  1033              ;
  1034            }
  1035          });
  1036          if(response.action) {
  1037            html += ''
  1038            + '<a href="' + response.action.url + '" class="action">'
  1039            +   response.action.text
  1040            + '</a>';
  1041          }
  1042          return html;
  1043        }
  1044        return false;
  1045      },
  1046      standard: function(response) {
  1047        var
  1048          html = ''
  1049        ;
  1050        if(response.results !== undefined) {
  1051  
  1052          // each result
  1053          $.each(response.results, function(index, result) {
  1054            if(result.url) {
  1055              html  += '<a class="result" href="' + result.url + '">';
  1056            }
  1057            else {
  1058              html  += '<a class="result">';
  1059            }
  1060            if(result.image !== undefined) {
  1061              html += ''
  1062                + '<div class="image">'
  1063                + ' <img src="' + result.image + '">'
  1064                + '</div>'
  1065              ;
  1066            }
  1067            html += '<div class="content">';
  1068            if(result.price !== undefined) {
  1069              html += '<div class="price">' + result.price + '</div>';
  1070            }
  1071            if(result.title !== undefined) {
  1072              html += '<div class="title">' + result.title + '</div>';
  1073            }
  1074            if(result.description !== undefined) {
  1075              html += '<div class="description">' + result.description + '</div>';
  1076            }
  1077            html  += ''
  1078              + '</div>'
  1079            ;
  1080            html += '</a>';
  1081          });
  1082  
  1083          if(response.action) {
  1084            html += ''
  1085            + '<a href="' + response.action.url + '" class="action">'
  1086            +   response.action.text
  1087            + '</a>';
  1088          }
  1089          return html;
  1090        }
  1091        return false;
  1092      }
  1093    }
  1094  };
  1095  
  1096  })( jQuery, window , document );