; (function ($, window, document, undefined){ 'use strict'; window = (typeof window != 'undefined' && window.Math == Math)? window: (typeof self != 'undefined' && self.Math == Math)? self: Function('return this')(); $.fn.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 , { } , _AN_Read_search('search', $.fn).settings, parameters): $.extend({ } , _AN_Read_search('search', $.fn).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.on('focus' + eventNamespace, selector.prompt, module.event.focus).on('blur' + eventNamespace, selector.prompt, module.event.blur).on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard).on('click' + eventNamespace, selector.searchButton, module.query).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 (){ if (parameters && parameters.searchFields !== undefined) { settings.searchFields = parameters.searchFields; } } } , event: { input: function (){ if (settings.searchDelay) { clearTimeout(module.timer); module.timer = _AN_Call_settimeout('setTimeout', window, 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 (_AN_Call_show('show', module.can)) { module.showResults(); } } ); } } , blur: function (event){ var pageLostFocus = (document.activeElement === this), callback = function (){ module.cancel.query(); module.remove.focus(); module.timer = _AN_Call_settimeout('setTimeout', window, 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 = $title.html(), value = (_AN_Read_length('length', $title) > 0)? $title.text(): false , results = module.get.results(), result = $result.data(metadata.result) || module.get.result(value, results), returnedValue; 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 (value) { module.set.value(value); } if (href) { module.verbose('Opening search link found in result', $link); if (target == '_blank' || event.ctrlKey) { _AN_Call_open('open', window, href); } else { _AN_Write_href('href', _AN_Read_location('location', window), false , (href)); } } } } } , handleKeyboard: function (event){ var $result = $module.find(selector.result), $category = $module.find(selector.category), $activeResult = $result.filter('.' + className.active), currentIndex = $result.index($activeResult), resultSize = _AN_Read_length('length', $result), hasActiveResult = _AN_Read_length('length', $activeResult) > 0, keyCode = event.which, keys = { backspace: 8, enter: 13, escape: 27, upArrow: 38, downArrow: 40} , newIndex; 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 (_AN_Read_length('length', $result.filter('.' + className.active)) > 0) { module.event.result.click.call($result.filter('.' + className.active), event); event.preventDefault(); return false ; } } else if (keyCode == keys.upArrow && hasActiveResult) { module.verbose('Up key pressed, changing active result'); newIndex = (currentIndex - 1 < 0)? currentIndex: currentIndex - 1; $category.removeClass(className.active); $result.removeClass(className.active).eq(newIndex).addClass(className.active).closest($category).addClass(className.active); event.preventDefault(); } else if (keyCode == keys.downArrow) { module.verbose('Down key pressed, changing active result'); newIndex = (currentIndex + 1 >= resultSize)? currentIndex: currentIndex + 1; $category.removeClass(className.active); $result.removeClass(className.active).eq(newIndex).addClass(className.active).closest($category).addClass(className.active); event.preventDefault(); } } else { 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} , searchHTML; $.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 (!_AN_Read_target('target', event)) { return ; } var $target = $(_AN_Read_target('target', event)), isInDOM = $.contains(document.documentElement, _AN_Read_target('target', event)); return (isInDOM && _AN_Read_length('length', $target.closest(selector.message)) > 0); } , empty: function (){ return ($results.html() === ''); } , visible: function (){ return (_AN_Read_length('length', $results.filter(':visible')) > 0); } , focused: function (){ return (_AN_Read_length('length', $prompt.filter(':focus')) > 0); } } , get: { settings: function (){ if ($.isPlainObject(parameters) && parameters.searchFullText) { settings.fullTextSearch = parameters.searchFullText; module.error(settings.error.oldSearchSyntax, element); } } , inputEvent: function (){ var prompt = $prompt[0], inputEvent = (prompt !== undefined && prompt.oninput !== undefined)? 'input': (prompt !== undefined && prompt.onpropertychange !== undefined)? 'propertychange': 'keyup'; return inputEvent; } , value: function (){ return $prompt.val(); } , results: function (){ var results = $module.data(metadata.results); return results; } , result: function (value, results){ var lookupFields = ['title', 'id'] , result = false ; value = (value !== undefined)? value: module.get.value(); results = (results !== undefined)? results: module.get.results(); if (settings.type === 'category') { module.debug('Finding result that matches', value); $.each(results, function (index, category){ if ($.isArray(category.results)) { result = _AN_Read_search('search', module).object(value, category.results, lookupFields)[0]; if (result) { return false ; } } } ); } else { module.debug('Finding result in results object', value); result = _AN_Read_search('search', module).object(value, results, lookupFields)[0]; } return result || false ; } } , select: { firstResult: function (){ module.verbose('Selecting first result'); $result.first().addClass(className.active); } } , set: { focus: function (){ $module.addClass(className.focus); } , loading: function (){ $module.addClass(className.loading); } , value: function (value){ module.verbose('Setting search input value', value); $prompt.val(value); } , type: function (type){ type = type || settings.type; if (settings.type == 'category') { $module.addClass(settings.type); } } , buttonPressed: function (){ $searchButton.addClass(className.pressed); } } , remove: { loading: function (){ $module.removeClass(className.loading); } , focus: function (){ $module.removeClass(className.focus); } , buttonPressed: function (){ $searchButton.removeClass(className.pressed); } } , 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) || $.isArray(settings.source)) { _AN_Read_search('search', module).local(searchTerm); callback(); } else if (module.can.useAPI()) { _AN_Read_search('search', module).remote(searchTerm, callback); } else { module.error(error.source); callback(); } } settings.onSearchQuery.call(element, searchTerm); } else { module.hideResults(); } } , search: { local: function (searchTerm){ var results = _AN_Read_search('search', module).object(searchTerm, settings.content), searchHTML; module.set.loading(); module.save.results(results); module.debug('Returned full local search results', results); if (settings.maxResults > 0) { module.debug('Using specified max results', results); results = results.slice(0, settings.maxResults); } if (settings.type == 'category') { results = module.create.categoryResults(results); } searchHTML = module.generateResults({ results: results} ); module.remove.loading(); module.addResults(searchHTML); module.inject.id(results); module.write.cache(searchTerm, { html: searchHTML, results: results} ); } , remote: function (searchTerm, callback){ callback = $.isFunction(callback)? callback: function (){ } ; if ($module.api('is loading')) { $module.api('abort'); } module.setup.api(searchTerm, callback); $module.api('query'); } , object: function (searchTerm, source, searchFields){ var results = [] , exactResults = [] , fuzzyResults = [] , searchExp = _AN_Call_replace('replace', searchTerm.toString(), regExp.escape, '\\$&'), matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), 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; if (!$.isArray(searchFields)) { searchFields = [searchFields] ; } if (source === undefined || source === false ) { module.error(error.source); return [] ; } $.each(searchFields, function (index, field){ $.each(source, function (label, content){ var fieldExists = (typeof content[field] == 'string'); if (fieldExists) { if (content[field].search(matchRegExp) !== -1) { addResult(results, content); } else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, content[field])) { addResult(exactResults, content); } else if (settings.fullTextSearch == true && module.fuzzySearch(searchTerm, content[field])) { addResult(fuzzyResults, content); } } } ); } ); $.merge(exactResults, fuzzyResults); $.merge(results, exactResults); return results; } } , exactSearch: function (query, term){ query = query.toLowerCase(); term = term.toLowerCase(); if (term.indexOf(query) > -1) { return true ; } return false ; } , fuzzySearch: function (query, term){ var termLength = _AN_Read_length('length', term), queryLength = _AN_Read_length('length', query); 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){ 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 = _AN_Read_length('length', searchTerm); return (numCharacters >= settings.minCharacters); } , results: function (){ if (_AN_Read_length('length', $results) === 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), categoryID = (categoryIndex + 1), firstCharCode, letterID, id; if (categoryIndex !== undefined) { 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 (_AN_Read_length('length', $results) === 0) { $results = $('
').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 categoryIndex = 0, resultIndex = 0; if (settings.type === 'category') { $.each(results, function (index, category){ resultIndex = 0; $.each(category.results, function (index, value){ var result = category.results[index]; if (result.id === undefined) { result.id = module.create.id(resultIndex, categoryIndex); } module.inject.result(result, resultIndex, categoryIndex); resultIndex++ ; } ); categoryIndex++ ; } ); } else { $.each(results, function (index, value){ var result = results[index]; if (result.id === undefined) { result.id = module.create.id(resultIndex); } module.inject.result(result, resultIndex); resultIndex++ ; } ); } return results; } } , save: { results: function (results){ module.verbose('Saving current search results to metadata', results); $module.data(metadata.results, results); } } , write: { cache: function (name, value){ var cache = ($module.data(metadata.cache) !== undefined)? $module.data(metadata.cache): { } ; if (settings.cache) { module.verbose('Writing generated html to cache', name, value); cache[name] = value; $module.data(metadata.cache, cache); } } } , addResults: function (html){ if ($.isFunction(settings.onResultsAdd)) { if (settings.onResultsAdd.call($results, html) === false ) { module.debug('onResultsAdd callback cancelled default action'); return false ; } } if (html) { $results.html(html); module.refreshResults(); if (settings.selectFirstResult) { module.select.firstResult(); } module.showResults(); } else { module.hideResults(function (){ $results.empty(); } ); } } , showResults: function (callback){ callback = $.isFunction(callback)? callback: function (){ } ; if (resultsDismissed) { return ; } if (!module.is.visible() && module.has.results()) { if (module.can.transition()) { module.debug('Showing results with css animations'); $results.transition({ animation: settings.transition + ' in', debug: settings.debug, verbose: settings.verbose, duration: settings.duration, onComplete: function (){ callback(); } , queue: true } ); } else { module.debug('Showing results with javascript'); $results.stop().fadeIn(settings.duration, settings.easing); } settings.onResultsOpen.call($results); } } , hideResults: function (callback){ callback = $.isFunction(callback)? callback: function (){ } ; if (module.is.visible()) { if (module.can.transition()) { module.debug('Hiding results with css animations'); $results.transition({ animation: settings.transition + ' out', debug: settings.debug, verbose: settings.verbose, duration: settings.duration, onComplete: function (){ callback(); } , queue: true } ); } else { module.debug('Hiding results with javascript'); $results.stop().fadeOut(settings.duration, settings.easing); } settings.onResultsClose.call($results); } } , generateResults: function (response){ module.debug('Generating html from response', response); var template = settings.templates[settings.type], isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), isProperArray = ($.isArray(response[fields.results]) && _AN_Read_length('length', response[fields.results]) > 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); } else { module.error(error.noTemplate, false ); } } else if (settings.showNoResults) { html = module.displayMessage(error.noResults, 'empty'); } settings.onResults.call(element, response); return html; } , displayMessage: function (text, type){ type = type || 'standard'; module.debug('Displaying message', text, type); module.addResults(settings.templates.message(text, type)); return settings.templates.message(text, type); } , 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 = _AN_Call_settimeout('setTimeout', window, 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 (_AN_Read_length('length', $allModules) > 1) { title += ' ' + '(' + _AN_Read_length('length', $allModules) + ')'; } if ((console.group !== undefined || console.table !== undefined) && _AN_Read_length('length', performance) > 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 = _AN_Read_length('length', query) - 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 ($.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; } ; _AN_Read_search('search', $.fn).settings = { name: 'Search', namespace: 'search', silent: false , debug: false , verbose: false , performance: true , type: 'standard', minCharacters: 1, selectFirstResult: false , apiSettings: false , source: false , searchOnFocus: true , searchFields: ['title', 'description'] , displayField: '', fullTextSearch: 'exact', automatic: true , hideDelay: 0, searchDelay: 200, maxResults: 7, cache: true , showNoResults: true , transition: 'scale', duration: 200, easing: 'easeOutExpo', 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', 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.'} , metadata: { cache: 'cache', results: 'results', result: 'result'} , regExp: { escape: /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, beginsWith: '(?:s|^)'} , fields: { categories: 'results', categoryName: 'name', categoryResults: 'results', description: 'description', image: 'image', price: 'price', results: 'results', title: 'title', url: 'url', action: 'action', actionText: 'text', actionURL: 'url'} , selector: { prompt: '.prompt', searchButton: '.search.button', results: '.results', message: '.results > .message', category: '.category', result: '.result', title: '.title, .name'} , templates: { escape: function (string){ var badChars = /[&<>"'`]/g, shouldEscape = /[&<>"'`]/, escape = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "`": "`"} , escapedChar = function (chr){ return escape[chr]; } ; if (shouldEscape.test(string)) { return _AN_Call_replace("replace", string, badChars, escapedChar); } return string; } , message: function (message, type){ var html = ''; if (message !== undefined && type !== undefined) { html += '' + ''; } return html; } , category: function (response, fields){ var html = '', escape = _AN_Read_search('search', $.fn).settings.templates.escape; if (response[fields.categoryResults] !== undefined) { $.each(response[fields.categoryResults], function (index, category){ if (category[fields.results] !== undefined && _AN_Read_length('length', category.results) > 0) { html += '