mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	We would never update or build fomantic again, we have forked it as a private library long time ago. So just put the JS and CSS files in "fomantic/build" into git. And use "import" to use them. Remove "form.js", rewrite "tab" component. All source code is from official Fomantic UI build. Will apply patches in separate PRs.
		
			
				
	
	
		
			1566 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1566 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
|  * # Fomantic-UI - Search
 | |
|  * http://github.com/fomantic/Fomantic-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) {
 | |
|                 event.preventDefault();
 | |
|                 module.verbose('Opening search link found in result', $link);
 | |
|                 if(target == '_blank' || event.ctrlKey) {
 | |
|                   window.open(href);
 | |
|                 }
 | |
|                 else {
 | |
|                   window.location.href = (href);
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         },
 | |
|         ensureVisible: function ensureVisible($el) {
 | |
|           var elTop, elBottom, resultsScrollTop, resultsHeight;
 | |
| 
 | |
|           elTop = $el.position().top;
 | |
|           elBottom = elTop + $el.outerHeight(true);
 | |
| 
 | |
|           resultsScrollTop = $results.scrollTop();
 | |
|           resultsHeight = $results.height()
 | |
|             parseInt($results.css('paddingTop'), 0) +
 | |
|             parseInt($results.css('paddingBottom'), 0);
 | |
|             
 | |
|           if (elTop < 0) {
 | |
|             $results.scrollTop(resultsScrollTop + elTop);
 | |
|           }
 | |
| 
 | |
|           else if (resultsHeight < elBottom) {
 | |
|             $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight));
 | |
|           }
 | |
|         },
 | |
|         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)
 | |
|               ;
 | |
|               module.ensureVisible($result.eq(newIndex));
 | |
|               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)
 | |
|               ;
 | |
|               module.ensureVisible($result.eq(newIndex));
 | |
|               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
 | |
|               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)[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)[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.source),
 | |
|               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') || (typeof content[field] == 'number')
 | |
|                 ;
 | |
|                 if(fieldExists) {
 | |
|                   var text;
 | |
|                   if (typeof content[field] === 'string'){  
 | |
|                       text = module.remove.diacritics(content[field]);
 | |
|                   } else {
 | |
|                       text = content[field].toString(); 
 | |
|                   }
 | |
|                   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,
 | |
|                   onShow     : function() {
 | |
|                     var $firstResult = $module.find(selector.result).eq(0);
 | |
|                     if($firstResult.length > 0) {
 | |
|                       module.ensureVisible($firstResult);
 | |
|                     }
 | |
|                   },
 | |
|                   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   : [
 | |
|     'id',
 | |
|     '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       = {
 | |
|           "<": "<",
 | |
|           ">": ">",
 | |
|           '"': """,
 | |
|           "'": "'",
 | |
|           "`": "`"
 | |
|         },
 | |
|         escapedChar  = function(chr) {
 | |
|           return escape[chr];
 | |
|         }
 | |
|       ;
 | |
|       if(shouldEscape.test(string)) {
 | |
|         string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&");
 | |
|         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 );
 |