| /*! |
| * # Semantic UI - Search |
| * http://github.com/semantic-org/semantic-ui/ |
| * |
| * |
| * Released under the MIT license |
| * http://opensource.org/licenses/MIT |
| * |
| */ |
| |
| ;(function ($, window, document, undefined) { |
| |
| "use strict"; |
| |
| 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, |
| |
| module |
| ; |
| |
| module = { |
| |
| initialize: function() { |
| module.verbose('Initializing module'); |
| 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() { |
| clearTimeout(module.timer); |
| module.timer = setTimeout(module.query, settings.searchDelay); |
| }, |
| focus: function() { |
| module.set.focus(); |
| if( module.has.minimumCharacters() ) { |
| module.query(); |
| 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; |
| } |
| 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(), |
| // 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), |
| 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) { |
| window.open(href); |
| } |
| else { |
| window.location.href = (href); |
| } |
| } |
| } |
| } |
| }, |
| handleKeyboard: function(event) { |
| var |
| // force selector refresh |
| $result = $module.find(selector.result), |
| $category = $module.find(selector.category), |
| currentIndex = $result.index( $result.filter('.' + className.active) ), |
| resultSize = $result.length, |
| |
| 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.trigger.blur(); |
| } |
| 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) { |
| module.verbose('Up key pressed, changing active result'); |
| newIndex = (currentIndex - 1 < 0) |
| ? currentIndex |
| : currentIndex - 1 |
| ; |
| $category |
| .removeClass(className.active) |
| ; |
| $result |
| .removeClass(className.active) |
| .eq(newIndex) |
| .addClass(className.active) |
| .closest($category) |
| .addClass(className.active) |
| ; |
| event.preventDefault(); |
| } |
| else if(keyCode == keys.downArrow) { |
| module.verbose('Down key pressed, changing active result'); |
| newIndex = (currentIndex + 1 >= resultSize) |
| ? currentIndex |
| : currentIndex + 1 |
| ; |
| $category |
| .removeClass(className.active) |
| ; |
| $result |
| .removeClass(className.active) |
| .eq(newIndex) |
| .addClass(className.active) |
| .closest($category) |
| .addClass(className.active) |
| ; |
| event.preventDefault(); |
| } |
| } |
| else { |
| // query shortcuts |
| if(keyCode == keys.enter) { |
| module.verbose('Enter key pressed, executing query'); |
| module.query(); |
| module.set.buttonPressed(); |
| $prompt.one('keyup', module.remove.buttonFocus); |
| } |
| } |
| }, |
| |
| setup: { |
| api: function(searchTerm) { |
| var |
| apiSettings = { |
| debug : settings.debug, |
| on : false, |
| cache : true, |
| action : 'search', |
| urlData : { |
| query : searchTerm |
| }, |
| onSuccess : function(response) { |
| module.parse.response.call(element, response, searchTerm); |
| }, |
| onAbort : function(response) { |
| }, |
| onFailure : function() { |
| module.displayMessage(error.serverError); |
| }, |
| 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) { |
| return (event.target && $(event.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); |
| } |
| }, |
| |
| trigger: { |
| blur: function() { |
| var |
| events = document.createEvent('HTMLEvents'), |
| promptElement = $prompt[0] |
| ; |
| if(promptElement) { |
| module.verbose('Triggering native blur event'); |
| events.initEvent('blur', false, false); |
| promptElement.dispatchEvent(events); |
| } |
| } |
| }, |
| |
| get: { |
| 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 = module.search.object(value, category.results, lookupFields)[0]; |
| // don't continue searching if a result is found |
| if(result) { |
| return false; |
| } |
| } |
| }); |
| } |
| else { |
| module.debug('Finding result in results object', value); |
| result = module.search.object(value, results, lookupFields)[0]; |
| } |
| return result || false; |
| }, |
| }, |
| |
| select: { |
| firstResult: function() { |
| module.verbose('Selecting first result'); |
| $result.first().addClass(className.active); |
| } |
| }, |
| |
| set: { |
| focus: function() { |
| $module.addClass(className.focus); |
| }, |
| loading: function() { |
| $module.addClass(className.loading); |
| }, |
| value: function(value) { |
| module.verbose('Setting search input value', value); |
| $prompt |
| .val(value) |
| ; |
| }, |
| type: function(type) { |
| type = type || settings.type; |
| if(settings.type == 'category') { |
| $module.addClass(settings.type); |
| } |
| }, |
| buttonPressed: function() { |
| $searchButton.addClass(className.pressed); |
| } |
| }, |
| |
| remove: { |
| loading: function() { |
| $module.removeClass(className.loading); |
| }, |
| focus: function() { |
| $module.removeClass(className.focus); |
| }, |
| buttonPressed: function() { |
| $searchButton.removeClass(className.pressed); |
| } |
| }, |
| |
| query: function() { |
| var |
| searchTerm = module.get.value(), |
| cache = module.read.cache(searchTerm) |
| ; |
| 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); |
| } |
| else { |
| module.debug('Querying for', searchTerm); |
| if($.isPlainObject(settings.source) || $.isArray(settings.source)) { |
| module.search.local(searchTerm); |
| } |
| else if( module.can.useAPI() ) { |
| module.search.remote(searchTerm); |
| } |
| else { |
| module.error(error.source); |
| } |
| } |
| settings.onSearchQuery.call(element, searchTerm); |
| } |
| else { |
| module.hideResults(); |
| } |
| }, |
| |
| search: { |
| local: function(searchTerm) { |
| var |
| results = module.search.object(searchTerm, settings.content), |
| searchHTML |
| ; |
| module.set.loading(); |
| module.save.results(results); |
| module.debug('Returned local search results', 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) { |
| if($module.api('is loading')) { |
| $module.api('abort'); |
| } |
| module.setup.api(searchTerm); |
| $module |
| .api('query') |
| ; |
| }, |
| object: function(searchTerm, source, searchFields) { |
| var |
| results = [], |
| fuzzyResults = [], |
| searchExp = searchTerm.toString().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) |
| ; |
| if(notResult && notFuzzyResult) { |
| array.push(result); |
| } |
| } |
| ; |
| source = source || settings.source; |
| searchFields = (searchFields !== undefined) |
| ? searchFields |
| : settings.searchFields |
| ; |
| |
| // search fields should be array to loop correctly |
| if(!$.isArray(searchFields)) { |
| searchFields = [searchFields]; |
| } |
| |
| // exit conditions if no source |
| if(source === undefined || source === false) { |
| module.error(error.source); |
| return []; |
| } |
| |
| // iterate through search fields looking for matches |
| $.each(searchFields, function(index, field) { |
| $.each(source, function(label, content) { |
| var |
| fieldExists = (typeof content[field] == 'string') |
| ; |
| if(fieldExists) { |
| if( content[field].search(matchRegExp) !== -1) { |
| // content starts with value (first in results) |
| addResult(results, content); |
| } |
| else if(settings.searchFullText && module.fuzzySearch(searchTerm, content[field]) ) { |
| // content fuzzy matches (last in results) |
| addResult(fuzzyResults, content); |
| } |
| } |
| }); |
| }); |
| return $.merge(results, fuzzyResults); |
| } |
| }, |
| |
| 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) { |
| 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); |
| } |
| }, |
| |
| 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: { |
| id: function(resultIndex, categoryIndex) { |
| var |
| resultID = (resultIndex + 1), // not zero indexed |
| categoryID = (categoryIndex + 1), |
| firstCharCode, |
| 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.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) { |
| 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 { |
| // top level |
| $.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(); |
| } |
| }, |
| |
| showResults: function() { |
| if(!module.is.visible()) { |
| 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, |
| queue : true |
| }) |
| ; |
| } |
| else { |
| module.debug('Showing results with javascript'); |
| $results |
| .stop() |
| .fadeIn(settings.duration, settings.easing) |
| ; |
| } |
| settings.onResultsOpen.call($results); |
| } |
| }, |
| hideResults: 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, |
| 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]) && 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); |
| } |
| 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 = 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($.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, |
| |
| // fields to search |
| searchFields : [ |
| 'title', |
| 'description' |
| ], |
| |
| // field to display in standard results template |
| displayField : '', |
| |
| // whether to include fuzzy results in local search |
| searchFullText : true, |
| |
| // 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 local |
| maxResults : 7, |
| |
| // whether to store lookups in local cache |
| cache : true, |
| |
| // whether no results errors should be shown |
| showNoResults : 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', |
| 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.', |
| 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|^)' |
| }, |
| |
| // 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) { |
| var |
| badChars = /[&<>"'`]/g, |
| shouldEscape = /[&<>"'`]/, |
| escape = { |
| "&": "&", |
| "<": "<", |
| ">": ">", |
| '"': """, |
| "'": "'", |
| "`": "`" |
| }, |
| escapedChar = function(chr) { |
| return escape[chr]; |
| } |
| ; |
| if(shouldEscape.test(string)) { |
| return string.replace(badChars, escapedChar); |
| } |
| return string; |
| }, |
| message: function(message, type) { |
| var |
| html = '' |
| ; |
| if(message !== undefined && type !== undefined) { |
| html += '' |
| + '<div class="message ' + type + '">' |
| ; |
| // message type |
| if(type == 'empty') { |
| html += '' |
| + '<div class="header">No Results</div class="header">' |
| + '<div class="description">' + message + '</div class="description">' |
| ; |
| } |
| else { |
| html += ' <div class="description">' + message + '</div>'; |
| } |
| html += '</div>'; |
| } |
| return html; |
| }, |
| category: function(response, fields) { |
| 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">' + category[fields.categoryName] + '</div>'; |
| } |
| |
| // each item inside category |
| $.each(category.results, function(index, result) { |
| if(result[fields.url]) { |
| html += '<a class="result" href="' + result[fields.url] + '">'; |
| } |
| else { |
| html += '<a class="result">'; |
| } |
| if(result[fields.image] !== undefined) { |
| html += '' |
| + '<div class="image">' |
| + ' <img src="' + result[fields.image] + '">' |
| + '</div>' |
| ; |
| } |
| html += '<div class="content">'; |
| if(result[fields.price] !== undefined) { |
| html += '<div class="price">' + result[fields.price] + '</div>'; |
| } |
| if(result[fields.title] !== undefined) { |
| html += '<div class="title">' + result[fields.title] + '</div>'; |
| } |
| if(result[fields.description] !== undefined) { |
| html += '<div class="description">' + result[fields.description] + '</div>'; |
| } |
| html += '' |
| + '</div>' |
| ; |
| html += '</a>'; |
| }); |
| html += '' |
| + '</div>' |
| ; |
| } |
| }); |
| if(response[fields.action]) { |
| html += '' |
| + '<a href="' + response[fields.action][fields.actionURL] + '" class="action">' |
| + response[fields.action][fields.actionText] |
| + '</a>'; |
| } |
| return html; |
| } |
| return false; |
| }, |
| standard: function(response, fields) { |
| var |
| html = '' |
| ; |
| 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] + '">'; |
| } |
| else { |
| html += '<a class="result">'; |
| } |
| if(result[fields.image] !== undefined) { |
| html += '' |
| + '<div class="image">' |
| + ' <img src="' + result[fields.image] + '">' |
| + '</div>' |
| ; |
| } |
| html += '<div class="content">'; |
| if(result[fields.price] !== undefined) { |
| html += '<div class="price">' + result[fields.price] + '</div>'; |
| } |
| if(result[fields.title] !== undefined) { |
| html += '<div class="title">' + result[fields.title] + '</div>'; |
| } |
| if(result[fields.description] !== undefined) { |
| html += '<div class="description">' + result[fields.description] + '</div>'; |
| } |
| html += '' |
| + '</div>' |
| ; |
| html += '</a>'; |
| }); |
| |
| if(response[fields.action]) { |
| html += '' |
| + '<a href="' + response[fields.action][fields.actionURL] + '" class="action">' |
| + response[fields.action][fields.actionText] |
| + '</a>'; |
| } |
| return html; |
| } |
| return false; |
| } |
| } |
| }; |
| |
| })( jQuery, window, document ); |