@@ 13289-14729 (lines=1441) @@ | ||
13286 | * |
|
13287 | */ |
|
13288 | ||
13289 | ;(function ($, window, document, undefined) { |
|
13290 | ||
13291 | "use strict"; |
|
13292 | ||
13293 | window = (typeof window != 'undefined' && window.Math == Math) |
|
13294 | ? window |
|
13295 | : (typeof self != 'undefined' && self.Math == Math) |
|
13296 | ? self |
|
13297 | : Function('return this')() |
|
13298 | ; |
|
13299 | ||
13300 | $.fn.search = function(parameters) { |
|
13301 | var |
|
13302 | $allModules = $(this), |
|
13303 | moduleSelector = $allModules.selector || '', |
|
13304 | ||
13305 | time = new Date().getTime(), |
|
13306 | performance = [], |
|
13307 | ||
13308 | query = arguments[0], |
|
13309 | methodInvoked = (typeof query == 'string'), |
|
13310 | queryArguments = [].slice.call(arguments, 1), |
|
13311 | returnedValue |
|
13312 | ; |
|
13313 | $(this) |
|
13314 | .each(function() { |
|
13315 | var |
|
13316 | settings = ( $.isPlainObject(parameters) ) |
|
13317 | ? $.extend(true, {}, $.fn.search.settings, parameters) |
|
13318 | : $.extend({}, $.fn.search.settings), |
|
13319 | ||
13320 | className = settings.className, |
|
13321 | metadata = settings.metadata, |
|
13322 | regExp = settings.regExp, |
|
13323 | fields = settings.fields, |
|
13324 | selector = settings.selector, |
|
13325 | error = settings.error, |
|
13326 | namespace = settings.namespace, |
|
13327 | ||
13328 | eventNamespace = '.' + namespace, |
|
13329 | moduleNamespace = namespace + '-module', |
|
13330 | ||
13331 | $module = $(this), |
|
13332 | $prompt = $module.find(selector.prompt), |
|
13333 | $searchButton = $module.find(selector.searchButton), |
|
13334 | $results = $module.find(selector.results), |
|
13335 | $result = $module.find(selector.result), |
|
13336 | $category = $module.find(selector.category), |
|
13337 | ||
13338 | element = this, |
|
13339 | instance = $module.data(moduleNamespace), |
|
13340 | ||
13341 | disabledBubbled = false, |
|
13342 | resultsDismissed = false, |
|
13343 | ||
13344 | module |
|
13345 | ; |
|
13346 | ||
13347 | module = { |
|
13348 | ||
13349 | initialize: function() { |
|
13350 | module.verbose('Initializing module'); |
|
13351 | module.determine.searchFields(); |
|
13352 | module.bind.events(); |
|
13353 | module.set.type(); |
|
13354 | module.create.results(); |
|
13355 | module.instantiate(); |
|
13356 | }, |
|
13357 | instantiate: function() { |
|
13358 | module.verbose('Storing instance of module', module); |
|
13359 | instance = module; |
|
13360 | $module |
|
13361 | .data(moduleNamespace, module) |
|
13362 | ; |
|
13363 | }, |
|
13364 | destroy: function() { |
|
13365 | module.verbose('Destroying instance'); |
|
13366 | $module |
|
13367 | .off(eventNamespace) |
|
13368 | .removeData(moduleNamespace) |
|
13369 | ; |
|
13370 | }, |
|
13371 | ||
13372 | refresh: function() { |
|
13373 | module.debug('Refreshing selector cache'); |
|
13374 | $prompt = $module.find(selector.prompt); |
|
13375 | $searchButton = $module.find(selector.searchButton); |
|
13376 | $category = $module.find(selector.category); |
|
13377 | $results = $module.find(selector.results); |
|
13378 | $result = $module.find(selector.result); |
|
13379 | }, |
|
13380 | ||
13381 | refreshResults: function() { |
|
13382 | $results = $module.find(selector.results); |
|
13383 | $result = $module.find(selector.result); |
|
13384 | }, |
|
13385 | ||
13386 | bind: { |
|
13387 | events: function() { |
|
13388 | module.verbose('Binding events to search'); |
|
13389 | if(settings.automatic) { |
|
13390 | $module |
|
13391 | .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input) |
|
13392 | ; |
|
13393 | $prompt |
|
13394 | .attr('autocomplete', 'off') |
|
13395 | ; |
|
13396 | } |
|
13397 | $module |
|
13398 | // prompt |
|
13399 | .on('focus' + eventNamespace, selector.prompt, module.event.focus) |
|
13400 | .on('blur' + eventNamespace, selector.prompt, module.event.blur) |
|
13401 | .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard) |
|
13402 | // search button |
|
13403 | .on('click' + eventNamespace, selector.searchButton, module.query) |
|
13404 | // results |
|
13405 | .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown) |
|
13406 | .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup) |
|
13407 | .on('click' + eventNamespace, selector.result, module.event.result.click) |
|
13408 | ; |
|
13409 | } |
|
13410 | }, |
|
13411 | ||
13412 | determine: { |
|
13413 | searchFields: function() { |
|
13414 | // this makes sure $.extend does not add specified search fields to default fields |
|
13415 | // this is the only setting which should not extend defaults |
|
13416 | if(parameters && parameters.searchFields !== undefined) { |
|
13417 | settings.searchFields = parameters.searchFields; |
|
13418 | } |
|
13419 | } |
|
13420 | }, |
|
13421 | ||
13422 | event: { |
|
13423 | input: function() { |
|
13424 | if(settings.searchDelay) { |
|
13425 | clearTimeout(module.timer); |
|
13426 | module.timer = setTimeout(function() { |
|
13427 | if(module.is.focused()) { |
|
13428 | module.query(); |
|
13429 | } |
|
13430 | }, settings.searchDelay); |
|
13431 | } |
|
13432 | else { |
|
13433 | module.query(); |
|
13434 | } |
|
13435 | }, |
|
13436 | focus: function() { |
|
13437 | module.set.focus(); |
|
13438 | if(settings.searchOnFocus && module.has.minimumCharacters() ) { |
|
13439 | module.query(function() { |
|
13440 | if(module.can.show() ) { |
|
13441 | module.showResults(); |
|
13442 | } |
|
13443 | }); |
|
13444 | } |
|
13445 | }, |
|
13446 | blur: function(event) { |
|
13447 | var |
|
13448 | pageLostFocus = (document.activeElement === this), |
|
13449 | callback = function() { |
|
13450 | module.cancel.query(); |
|
13451 | module.remove.focus(); |
|
13452 | module.timer = setTimeout(module.hideResults, settings.hideDelay); |
|
13453 | } |
|
13454 | ; |
|
13455 | if(pageLostFocus) { |
|
13456 | return; |
|
13457 | } |
|
13458 | resultsDismissed = false; |
|
13459 | if(module.resultsClicked) { |
|
13460 | module.debug('Determining if user action caused search to close'); |
|
13461 | $module |
|
13462 | .one('click.close' + eventNamespace, selector.results, function(event) { |
|
13463 | if(module.is.inMessage(event) || disabledBubbled) { |
|
13464 | $prompt.focus(); |
|
13465 | return; |
|
13466 | } |
|
13467 | disabledBubbled = false; |
|
13468 | if( !module.is.animating() && !module.is.hidden()) { |
|
13469 | callback(); |
|
13470 | } |
|
13471 | }) |
|
13472 | ; |
|
13473 | } |
|
13474 | else { |
|
13475 | module.debug('Input blurred without user action, closing results'); |
|
13476 | callback(); |
|
13477 | } |
|
13478 | }, |
|
13479 | result: { |
|
13480 | mousedown: function() { |
|
13481 | module.resultsClicked = true; |
|
13482 | }, |
|
13483 | mouseup: function() { |
|
13484 | module.resultsClicked = false; |
|
13485 | }, |
|
13486 | click: function(event) { |
|
13487 | module.debug('Search result selected'); |
|
13488 | var |
|
13489 | $result = $(this), |
|
13490 | $title = $result.find(selector.title).eq(0), |
|
13491 | $link = $result.is('a[href]') |
|
13492 | ? $result |
|
13493 | : $result.find('a[href]').eq(0), |
|
13494 | href = $link.attr('href') || false, |
|
13495 | target = $link.attr('target') || false, |
|
13496 | title = $title.html(), |
|
13497 | // title is used for result lookup |
|
13498 | value = ($title.length > 0) |
|
13499 | ? $title.text() |
|
13500 | : false, |
|
13501 | results = module.get.results(), |
|
13502 | result = $result.data(metadata.result) || module.get.result(value, results), |
|
13503 | returnedValue |
|
13504 | ; |
|
13505 | if( $.isFunction(settings.onSelect) ) { |
|
13506 | if(settings.onSelect.call(element, result, results) === false) { |
|
13507 | module.debug('Custom onSelect callback cancelled default select action'); |
|
13508 | disabledBubbled = true; |
|
13509 | return; |
|
13510 | } |
|
13511 | } |
|
13512 | module.hideResults(); |
|
13513 | if(value) { |
|
13514 | module.set.value(value); |
|
13515 | } |
|
13516 | if(href) { |
|
13517 | module.verbose('Opening search link found in result', $link); |
|
13518 | if(target == '_blank' || event.ctrlKey) { |
|
13519 | window.open(href); |
|
13520 | } |
|
13521 | else { |
|
13522 | window.location.href = (href); |
|
13523 | } |
|
13524 | } |
|
13525 | } |
|
13526 | } |
|
13527 | }, |
|
13528 | handleKeyboard: function(event) { |
|
13529 | var |
|
13530 | // force selector refresh |
|
13531 | $result = $module.find(selector.result), |
|
13532 | $category = $module.find(selector.category), |
|
13533 | $activeResult = $result.filter('.' + className.active), |
|
13534 | currentIndex = $result.index( $activeResult ), |
|
13535 | resultSize = $result.length, |
|
13536 | hasActiveResult = $activeResult.length > 0, |
|
13537 | ||
13538 | keyCode = event.which, |
|
13539 | keys = { |
|
13540 | backspace : 8, |
|
13541 | enter : 13, |
|
13542 | escape : 27, |
|
13543 | upArrow : 38, |
|
13544 | downArrow : 40 |
|
13545 | }, |
|
13546 | newIndex |
|
13547 | ; |
|
13548 | // search shortcuts |
|
13549 | if(keyCode == keys.escape) { |
|
13550 | module.verbose('Escape key pressed, blurring search field'); |
|
13551 | module.hideResults(); |
|
13552 | resultsDismissed = true; |
|
13553 | } |
|
13554 | if( module.is.visible() ) { |
|
13555 | if(keyCode == keys.enter) { |
|
13556 | module.verbose('Enter key pressed, selecting active result'); |
|
13557 | if( $result.filter('.' + className.active).length > 0 ) { |
|
13558 | module.event.result.click.call($result.filter('.' + className.active), event); |
|
13559 | event.preventDefault(); |
|
13560 | return false; |
|
13561 | } |
|
13562 | } |
|
13563 | else if(keyCode == keys.upArrow && hasActiveResult) { |
|
13564 | module.verbose('Up key pressed, changing active result'); |
|
13565 | newIndex = (currentIndex - 1 < 0) |
|
13566 | ? currentIndex |
|
13567 | : currentIndex - 1 |
|
13568 | ; |
|
13569 | $category |
|
13570 | .removeClass(className.active) |
|
13571 | ; |
|
13572 | $result |
|
13573 | .removeClass(className.active) |
|
13574 | .eq(newIndex) |
|
13575 | .addClass(className.active) |
|
13576 | .closest($category) |
|
13577 | .addClass(className.active) |
|
13578 | ; |
|
13579 | event.preventDefault(); |
|
13580 | } |
|
13581 | else if(keyCode == keys.downArrow) { |
|
13582 | module.verbose('Down key pressed, changing active result'); |
|
13583 | newIndex = (currentIndex + 1 >= resultSize) |
|
13584 | ? currentIndex |
|
13585 | : currentIndex + 1 |
|
13586 | ; |
|
13587 | $category |
|
13588 | .removeClass(className.active) |
|
13589 | ; |
|
13590 | $result |
|
13591 | .removeClass(className.active) |
|
13592 | .eq(newIndex) |
|
13593 | .addClass(className.active) |
|
13594 | .closest($category) |
|
13595 | .addClass(className.active) |
|
13596 | ; |
|
13597 | event.preventDefault(); |
|
13598 | } |
|
13599 | } |
|
13600 | else { |
|
13601 | // query shortcuts |
|
13602 | if(keyCode == keys.enter) { |
|
13603 | module.verbose('Enter key pressed, executing query'); |
|
13604 | module.query(); |
|
13605 | module.set.buttonPressed(); |
|
13606 | $prompt.one('keyup', module.remove.buttonFocus); |
|
13607 | } |
|
13608 | } |
|
13609 | }, |
|
13610 | ||
13611 | setup: { |
|
13612 | api: function(searchTerm, callback) { |
|
13613 | var |
|
13614 | apiSettings = { |
|
13615 | debug : settings.debug, |
|
13616 | on : false, |
|
13617 | cache : true, |
|
13618 | action : 'search', |
|
13619 | urlData : { |
|
13620 | query : searchTerm |
|
13621 | }, |
|
13622 | onSuccess : function(response) { |
|
13623 | module.parse.response.call(element, response, searchTerm); |
|
13624 | callback(); |
|
13625 | }, |
|
13626 | onFailure : function() { |
|
13627 | module.displayMessage(error.serverError); |
|
13628 | callback(); |
|
13629 | }, |
|
13630 | onAbort : function(response) { |
|
13631 | }, |
|
13632 | onError : module.error |
|
13633 | }, |
|
13634 | searchHTML |
|
13635 | ; |
|
13636 | $.extend(true, apiSettings, settings.apiSettings); |
|
13637 | module.verbose('Setting up API request', apiSettings); |
|
13638 | $module.api(apiSettings); |
|
13639 | } |
|
13640 | }, |
|
13641 | ||
13642 | can: { |
|
13643 | useAPI: function() { |
|
13644 | return $.fn.api !== undefined; |
|
13645 | }, |
|
13646 | show: function() { |
|
13647 | return module.is.focused() && !module.is.visible() && !module.is.empty(); |
|
13648 | }, |
|
13649 | transition: function() { |
|
13650 | return settings.transition && $.fn.transition !== undefined && $module.transition('is supported'); |
|
13651 | } |
|
13652 | }, |
|
13653 | ||
13654 | is: { |
|
13655 | animating: function() { |
|
13656 | return $results.hasClass(className.animating); |
|
13657 | }, |
|
13658 | hidden: function() { |
|
13659 | return $results.hasClass(className.hidden); |
|
13660 | }, |
|
13661 | inMessage: function(event) { |
|
13662 | if(!event.target) { |
|
13663 | return; |
|
13664 | } |
|
13665 | var |
|
13666 | $target = $(event.target), |
|
13667 | isInDOM = $.contains(document.documentElement, event.target) |
|
13668 | ; |
|
13669 | return (isInDOM && $target.closest(selector.message).length > 0); |
|
13670 | }, |
|
13671 | empty: function() { |
|
13672 | return ($results.html() === ''); |
|
13673 | }, |
|
13674 | visible: function() { |
|
13675 | return ($results.filter(':visible').length > 0); |
|
13676 | }, |
|
13677 | focused: function() { |
|
13678 | return ($prompt.filter(':focus').length > 0); |
|
13679 | } |
|
13680 | }, |
|
13681 | ||
13682 | get: { |
|
13683 | inputEvent: function() { |
|
13684 | var |
|
13685 | prompt = $prompt[0], |
|
13686 | inputEvent = (prompt !== undefined && prompt.oninput !== undefined) |
|
13687 | ? 'input' |
|
13688 | : (prompt !== undefined && prompt.onpropertychange !== undefined) |
|
13689 | ? 'propertychange' |
|
13690 | : 'keyup' |
|
13691 | ; |
|
13692 | return inputEvent; |
|
13693 | }, |
|
13694 | value: function() { |
|
13695 | return $prompt.val(); |
|
13696 | }, |
|
13697 | results: function() { |
|
13698 | var |
|
13699 | results = $module.data(metadata.results) |
|
13700 | ; |
|
13701 | return results; |
|
13702 | }, |
|
13703 | result: function(value, results) { |
|
13704 | var |
|
13705 | lookupFields = ['title', 'id'], |
|
13706 | result = false |
|
13707 | ; |
|
13708 | value = (value !== undefined) |
|
13709 | ? value |
|
13710 | : module.get.value() |
|
13711 | ; |
|
13712 | results = (results !== undefined) |
|
13713 | ? results |
|
13714 | : module.get.results() |
|
13715 | ; |
|
13716 | if(settings.type === 'category') { |
|
13717 | module.debug('Finding result that matches', value); |
|
13718 | $.each(results, function(index, category) { |
|
13719 | if($.isArray(category.results)) { |
|
13720 | result = module.search.object(value, category.results, lookupFields)[0]; |
|
13721 | // don't continue searching if a result is found |
|
13722 | if(result) { |
|
13723 | return false; |
|
13724 | } |
|
13725 | } |
|
13726 | }); |
|
13727 | } |
|
13728 | else { |
|
13729 | module.debug('Finding result in results object', value); |
|
13730 | result = module.search.object(value, results, lookupFields)[0]; |
|
13731 | } |
|
13732 | return result || false; |
|
13733 | }, |
|
13734 | }, |
|
13735 | ||
13736 | select: { |
|
13737 | firstResult: function() { |
|
13738 | module.verbose('Selecting first result'); |
|
13739 | $result.first().addClass(className.active); |
|
13740 | } |
|
13741 | }, |
|
13742 | ||
13743 | set: { |
|
13744 | focus: function() { |
|
13745 | $module.addClass(className.focus); |
|
13746 | }, |
|
13747 | loading: function() { |
|
13748 | $module.addClass(className.loading); |
|
13749 | }, |
|
13750 | value: function(value) { |
|
13751 | module.verbose('Setting search input value', value); |
|
13752 | $prompt |
|
13753 | .val(value) |
|
13754 | ; |
|
13755 | }, |
|
13756 | type: function(type) { |
|
13757 | type = type || settings.type; |
|
13758 | if(settings.type == 'category') { |
|
13759 | $module.addClass(settings.type); |
|
13760 | } |
|
13761 | }, |
|
13762 | buttonPressed: function() { |
|
13763 | $searchButton.addClass(className.pressed); |
|
13764 | } |
|
13765 | }, |
|
13766 | ||
13767 | remove: { |
|
13768 | loading: function() { |
|
13769 | $module.removeClass(className.loading); |
|
13770 | }, |
|
13771 | focus: function() { |
|
13772 | $module.removeClass(className.focus); |
|
13773 | }, |
|
13774 | buttonPressed: function() { |
|
13775 | $searchButton.removeClass(className.pressed); |
|
13776 | } |
|
13777 | }, |
|
13778 | ||
13779 | query: function(callback) { |
|
13780 | callback = $.isFunction(callback) |
|
13781 | ? callback |
|
13782 | : function(){} |
|
13783 | ; |
|
13784 | var |
|
13785 | searchTerm = module.get.value(), |
|
13786 | cache = module.read.cache(searchTerm) |
|
13787 | ; |
|
13788 | callback = callback || function() {}; |
|
13789 | if( module.has.minimumCharacters() ) { |
|
13790 | if(cache) { |
|
13791 | module.debug('Reading result from cache', searchTerm); |
|
13792 | module.save.results(cache.results); |
|
13793 | module.addResults(cache.html); |
|
13794 | module.inject.id(cache.results); |
|
13795 | callback(); |
|
13796 | } |
|
13797 | else { |
|
13798 | module.debug('Querying for', searchTerm); |
|
13799 | if($.isPlainObject(settings.source) || $.isArray(settings.source)) { |
|
13800 | module.search.local(searchTerm); |
|
13801 | callback(); |
|
13802 | } |
|
13803 | else if( module.can.useAPI() ) { |
|
13804 | module.search.remote(searchTerm, callback); |
|
13805 | } |
|
13806 | else { |
|
13807 | module.error(error.source); |
|
13808 | callback(); |
|
13809 | } |
|
13810 | } |
|
13811 | settings.onSearchQuery.call(element, searchTerm); |
|
13812 | } |
|
13813 | else { |
|
13814 | module.hideResults(); |
|
13815 | } |
|
13816 | }, |
|
13817 | ||
13818 | search: { |
|
13819 | local: function(searchTerm) { |
|
13820 | var |
|
13821 | results = module.search.object(searchTerm, settings.content), |
|
13822 | searchHTML |
|
13823 | ; |
|
13824 | module.set.loading(); |
|
13825 | module.save.results(results); |
|
13826 | module.debug('Returned local search results', results); |
|
13827 | ||
13828 | searchHTML = module.generateResults({ |
|
13829 | results: results |
|
13830 | }); |
|
13831 | module.remove.loading(); |
|
13832 | module.addResults(searchHTML); |
|
13833 | module.inject.id(results); |
|
13834 | module.write.cache(searchTerm, { |
|
13835 | html : searchHTML, |
|
13836 | results : results |
|
13837 | }); |
|
13838 | }, |
|
13839 | remote: function(searchTerm, callback) { |
|
13840 | callback = $.isFunction(callback) |
|
13841 | ? callback |
|
13842 | : function(){} |
|
13843 | ; |
|
13844 | if($module.api('is loading')) { |
|
13845 | $module.api('abort'); |
|
13846 | } |
|
13847 | module.setup.api(searchTerm, callback); |
|
13848 | $module |
|
13849 | .api('query') |
|
13850 | ; |
|
13851 | }, |
|
13852 | object: function(searchTerm, source, searchFields) { |
|
13853 | var |
|
13854 | results = [], |
|
13855 | fuzzyResults = [], |
|
13856 | searchExp = searchTerm.toString().replace(regExp.escape, '\\$&'), |
|
13857 | matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), |
|
13858 | ||
13859 | // avoid duplicates when pushing results |
|
13860 | addResult = function(array, result) { |
|
13861 | var |
|
13862 | notResult = ($.inArray(result, results) == -1), |
|
13863 | notFuzzyResult = ($.inArray(result, fuzzyResults) == -1) |
|
13864 | ; |
|
13865 | if(notResult && notFuzzyResult) { |
|
13866 | array.push(result); |
|
13867 | } |
|
13868 | } |
|
13869 | ; |
|
13870 | source = source || settings.source; |
|
13871 | searchFields = (searchFields !== undefined) |
|
13872 | ? searchFields |
|
13873 | : settings.searchFields |
|
13874 | ; |
|
13875 | ||
13876 | // search fields should be array to loop correctly |
|
13877 | if(!$.isArray(searchFields)) { |
|
13878 | searchFields = [searchFields]; |
|
13879 | } |
|
13880 | ||
13881 | // exit conditions if no source |
|
13882 | if(source === undefined || source === false) { |
|
13883 | module.error(error.source); |
|
13884 | return []; |
|
13885 | } |
|
13886 | ||
13887 | // iterate through search fields looking for matches |
|
13888 | $.each(searchFields, function(index, field) { |
|
13889 | $.each(source, function(label, content) { |
|
13890 | var |
|
13891 | fieldExists = (typeof content[field] == 'string') |
|
13892 | ; |
|
13893 | if(fieldExists) { |
|
13894 | if( content[field].search(matchRegExp) !== -1) { |
|
13895 | // content starts with value (first in results) |
|
13896 | addResult(results, content); |
|
13897 | } |
|
13898 | else if(settings.searchFullText && module.fuzzySearch(searchTerm, content[field]) ) { |
|
13899 | // content fuzzy matches (last in results) |
|
13900 | addResult(fuzzyResults, content); |
|
13901 | } |
|
13902 | } |
|
13903 | }); |
|
13904 | }); |
|
13905 | return $.merge(results, fuzzyResults); |
|
13906 | } |
|
13907 | }, |
|
13908 | ||
13909 | fuzzySearch: function(query, term) { |
|
13910 | var |
|
13911 | termLength = term.length, |
|
13912 | queryLength = query.length |
|
13913 | ; |
|
13914 | if(typeof query !== 'string') { |
|
13915 | return false; |
|
13916 | } |
|
13917 | query = query.toLowerCase(); |
|
13918 | term = term.toLowerCase(); |
|
13919 | if(queryLength > termLength) { |
|
13920 | return false; |
|
13921 | } |
|
13922 | if(queryLength === termLength) { |
|
13923 | return (query === term); |
|
13924 | } |
|
13925 | search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { |
|
13926 | var |
|
13927 | queryCharacter = query.charCodeAt(characterIndex) |
|
13928 | ; |
|
13929 | while(nextCharacterIndex < termLength) { |
|
13930 | if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { |
|
13931 | continue search; |
|
13932 | } |
|
13933 | } |
|
13934 | return false; |
|
13935 | } |
|
13936 | return true; |
|
13937 | }, |
|
13938 | ||
13939 | parse: { |
|
13940 | response: function(response, searchTerm) { |
|
13941 | var |
|
13942 | searchHTML = module.generateResults(response) |
|
13943 | ; |
|
13944 | module.verbose('Parsing server response', response); |
|
13945 | if(response !== undefined) { |
|
13946 | if(searchTerm !== undefined && response[fields.results] !== undefined) { |
|
13947 | module.addResults(searchHTML); |
|
13948 | module.inject.id(response[fields.results]); |
|
13949 | module.write.cache(searchTerm, { |
|
13950 | html : searchHTML, |
|
13951 | results : response[fields.results] |
|
13952 | }); |
|
13953 | module.save.results(response[fields.results]); |
|
13954 | } |
|
13955 | } |
|
13956 | } |
|
13957 | }, |
|
13958 | ||
13959 | cancel: { |
|
13960 | query: function() { |
|
13961 | if( module.can.useAPI() ) { |
|
13962 | $module.api('abort'); |
|
13963 | } |
|
13964 | } |
|
13965 | }, |
|
13966 | ||
13967 | has: { |
|
13968 | minimumCharacters: function() { |
|
13969 | var |
|
13970 | searchTerm = module.get.value(), |
|
13971 | numCharacters = searchTerm.length |
|
13972 | ; |
|
13973 | return (numCharacters >= settings.minCharacters); |
|
13974 | }, |
|
13975 | results: function() { |
|
13976 | if($results.length === 0) { |
|
13977 | return false; |
|
13978 | } |
|
13979 | var |
|
13980 | html = $results.html() |
|
13981 | ; |
|
13982 | return html != ''; |
|
13983 | } |
|
13984 | }, |
|
13985 | ||
13986 | clear: { |
|
13987 | cache: function(value) { |
|
13988 | var |
|
13989 | cache = $module.data(metadata.cache) |
|
13990 | ; |
|
13991 | if(!value) { |
|
13992 | module.debug('Clearing cache', value); |
|
13993 | $module.removeData(metadata.cache); |
|
13994 | } |
|
13995 | else if(value && cache && cache[value]) { |
|
13996 | module.debug('Removing value from cache', value); |
|
13997 | delete cache[value]; |
|
13998 | $module.data(metadata.cache, cache); |
|
13999 | } |
|
14000 | } |
|
14001 | }, |
|
14002 | ||
14003 | read: { |
|
14004 | cache: function(name) { |
|
14005 | var |
|
14006 | cache = $module.data(metadata.cache) |
|
14007 | ; |
|
14008 | if(settings.cache) { |
|
14009 | module.verbose('Checking cache for generated html for query', name); |
|
14010 | return (typeof cache == 'object') && (cache[name] !== undefined) |
|
14011 | ? cache[name] |
|
14012 | : false |
|
14013 | ; |
|
14014 | } |
|
14015 | return false; |
|
14016 | } |
|
14017 | }, |
|
14018 | ||
14019 | create: { |
|
14020 | id: function(resultIndex, categoryIndex) { |
|
14021 | var |
|
14022 | resultID = (resultIndex + 1), // not zero indexed |
|
14023 | categoryID = (categoryIndex + 1), |
|
14024 | firstCharCode, |
|
14025 | letterID, |
|
14026 | id |
|
14027 | ; |
|
14028 | if(categoryIndex !== undefined) { |
|
14029 | // start char code for "A" |
|
14030 | letterID = String.fromCharCode(97 + categoryIndex); |
|
14031 | id = letterID + resultID; |
|
14032 | module.verbose('Creating category result id', id); |
|
14033 | } |
|
14034 | else { |
|
14035 | id = resultID; |
|
14036 | module.verbose('Creating result id', id); |
|
14037 | } |
|
14038 | return id; |
|
14039 | }, |
|
14040 | results: function() { |
|
14041 | if($results.length === 0) { |
|
14042 | $results = $('<div />') |
|
14043 | .addClass(className.results) |
|
14044 | .appendTo($module) |
|
14045 | ; |
|
14046 | } |
|
14047 | } |
|
14048 | }, |
|
14049 | ||
14050 | inject: { |
|
14051 | result: function(result, resultIndex, categoryIndex) { |
|
14052 | module.verbose('Injecting result into results'); |
|
14053 | var |
|
14054 | $selectedResult = (categoryIndex !== undefined) |
|
14055 | ? $results |
|
14056 | .children().eq(categoryIndex) |
|
14057 | .children(selector.result).eq(resultIndex) |
|
14058 | : $results |
|
14059 | .children(selector.result).eq(resultIndex) |
|
14060 | ; |
|
14061 | module.verbose('Injecting results metadata', $selectedResult); |
|
14062 | $selectedResult |
|
14063 | .data(metadata.result, result) |
|
14064 | ; |
|
14065 | }, |
|
14066 | id: function(results) { |
|
14067 | module.debug('Injecting unique ids into results'); |
|
14068 | var |
|
14069 | // since results may be object, we must use counters |
|
14070 | categoryIndex = 0, |
|
14071 | resultIndex = 0 |
|
14072 | ; |
|
14073 | if(settings.type === 'category') { |
|
14074 | // iterate through each category result |
|
14075 | $.each(results, function(index, category) { |
|
14076 | resultIndex = 0; |
|
14077 | $.each(category.results, function(index, value) { |
|
14078 | var |
|
14079 | result = category.results[index] |
|
14080 | ; |
|
14081 | if(result.id === undefined) { |
|
14082 | result.id = module.create.id(resultIndex, categoryIndex); |
|
14083 | } |
|
14084 | module.inject.result(result, resultIndex, categoryIndex); |
|
14085 | resultIndex++; |
|
14086 | }); |
|
14087 | categoryIndex++; |
|
14088 | }); |
|
14089 | } |
|
14090 | else { |
|
14091 | // top level |
|
14092 | $.each(results, function(index, value) { |
|
14093 | var |
|
14094 | result = results[index] |
|
14095 | ; |
|
14096 | if(result.id === undefined) { |
|
14097 | result.id = module.create.id(resultIndex); |
|
14098 | } |
|
14099 | module.inject.result(result, resultIndex); |
|
14100 | resultIndex++; |
|
14101 | }); |
|
14102 | } |
|
14103 | return results; |
|
14104 | } |
|
14105 | }, |
|
14106 | ||
14107 | save: { |
|
14108 | results: function(results) { |
|
14109 | module.verbose('Saving current search results to metadata', results); |
|
14110 | $module.data(metadata.results, results); |
|
14111 | } |
|
14112 | }, |
|
14113 | ||
14114 | write: { |
|
14115 | cache: function(name, value) { |
|
14116 | var |
|
14117 | cache = ($module.data(metadata.cache) !== undefined) |
|
14118 | ? $module.data(metadata.cache) |
|
14119 | : {} |
|
14120 | ; |
|
14121 | if(settings.cache) { |
|
14122 | module.verbose('Writing generated html to cache', name, value); |
|
14123 | cache[name] = value; |
|
14124 | $module |
|
14125 | .data(metadata.cache, cache) |
|
14126 | ; |
|
14127 | } |
|
14128 | } |
|
14129 | }, |
|
14130 | ||
14131 | addResults: function(html) { |
|
14132 | if( $.isFunction(settings.onResultsAdd) ) { |
|
14133 | if( settings.onResultsAdd.call($results, html) === false ) { |
|
14134 | module.debug('onResultsAdd callback cancelled default action'); |
|
14135 | return false; |
|
14136 | } |
|
14137 | } |
|
14138 | if(html) { |
|
14139 | $results |
|
14140 | .html(html) |
|
14141 | ; |
|
14142 | module.refreshResults(); |
|
14143 | if(settings.selectFirstResult) { |
|
14144 | module.select.firstResult(); |
|
14145 | } |
|
14146 | module.showResults(); |
|
14147 | } |
|
14148 | else { |
|
14149 | module.hideResults(function() { |
|
14150 | $results.empty(); |
|
14151 | }); |
|
14152 | } |
|
14153 | }, |
|
14154 | ||
14155 | showResults: function(callback) { |
|
14156 | callback = $.isFunction(callback) |
|
14157 | ? callback |
|
14158 | : function(){} |
|
14159 | ; |
|
14160 | if(resultsDismissed) { |
|
14161 | return; |
|
14162 | } |
|
14163 | if(!module.is.visible() && module.has.results()) { |
|
14164 | if( module.can.transition() ) { |
|
14165 | module.debug('Showing results with css animations'); |
|
14166 | $results |
|
14167 | .transition({ |
|
14168 | animation : settings.transition + ' in', |
|
14169 | debug : settings.debug, |
|
14170 | verbose : settings.verbose, |
|
14171 | duration : settings.duration, |
|
14172 | onComplete : function() { |
|
14173 | callback(); |
|
14174 | }, |
|
14175 | queue : true |
|
14176 | }) |
|
14177 | ; |
|
14178 | } |
|
14179 | else { |
|
14180 | module.debug('Showing results with javascript'); |
|
14181 | $results |
|
14182 | .stop() |
|
14183 | .fadeIn(settings.duration, settings.easing) |
|
14184 | ; |
|
14185 | } |
|
14186 | settings.onResultsOpen.call($results); |
|
14187 | } |
|
14188 | }, |
|
14189 | hideResults: function(callback) { |
|
14190 | callback = $.isFunction(callback) |
|
14191 | ? callback |
|
14192 | : function(){} |
|
14193 | ; |
|
14194 | if( module.is.visible() ) { |
|
14195 | if( module.can.transition() ) { |
|
14196 | module.debug('Hiding results with css animations'); |
|
14197 | $results |
|
14198 | .transition({ |
|
14199 | animation : settings.transition + ' out', |
|
14200 | debug : settings.debug, |
|
14201 | verbose : settings.verbose, |
|
14202 | duration : settings.duration, |
|
14203 | onComplete : function() { |
|
14204 | callback(); |
|
14205 | }, |
|
14206 | queue : true |
|
14207 | }) |
|
14208 | ; |
|
14209 | } |
|
14210 | else { |
|
14211 | module.debug('Hiding results with javascript'); |
|
14212 | $results |
|
14213 | .stop() |
|
14214 | .fadeOut(settings.duration, settings.easing) |
|
14215 | ; |
|
14216 | } |
|
14217 | settings.onResultsClose.call($results); |
|
14218 | } |
|
14219 | }, |
|
14220 | ||
14221 | generateResults: function(response) { |
|
14222 | module.debug('Generating html from response', response); |
|
14223 | var |
|
14224 | template = settings.templates[settings.type], |
|
14225 | isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), |
|
14226 | isProperArray = ($.isArray(response[fields.results]) && response[fields.results].length > 0), |
|
14227 | html = '' |
|
14228 | ; |
|
14229 | if(isProperObject || isProperArray ) { |
|
14230 | if(settings.maxResults > 0) { |
|
14231 | if(isProperObject) { |
|
14232 | if(settings.type == 'standard') { |
|
14233 | module.error(error.maxResults); |
|
14234 | } |
|
14235 | } |
|
14236 | else { |
|
14237 | response[fields.results] = response[fields.results].slice(0, settings.maxResults); |
|
14238 | } |
|
14239 | } |
|
14240 | if($.isFunction(template)) { |
|
14241 | html = template(response, fields); |
|
14242 | } |
|
14243 | else { |
|
14244 | module.error(error.noTemplate, false); |
|
14245 | } |
|
14246 | } |
|
14247 | else if(settings.showNoResults) { |
|
14248 | html = module.displayMessage(error.noResults, 'empty'); |
|
14249 | } |
|
14250 | settings.onResults.call(element, response); |
|
14251 | return html; |
|
14252 | }, |
|
14253 | ||
14254 | displayMessage: function(text, type) { |
|
14255 | type = type || 'standard'; |
|
14256 | module.debug('Displaying message', text, type); |
|
14257 | module.addResults( settings.templates.message(text, type) ); |
|
14258 | return settings.templates.message(text, type); |
|
14259 | }, |
|
14260 | ||
14261 | setting: function(name, value) { |
|
14262 | if( $.isPlainObject(name) ) { |
|
14263 | $.extend(true, settings, name); |
|
14264 | } |
|
14265 | else if(value !== undefined) { |
|
14266 | settings[name] = value; |
|
14267 | } |
|
14268 | else { |
|
14269 | return settings[name]; |
|
14270 | } |
|
14271 | }, |
|
14272 | internal: function(name, value) { |
|
14273 | if( $.isPlainObject(name) ) { |
|
14274 | $.extend(true, module, name); |
|
14275 | } |
|
14276 | else if(value !== undefined) { |
|
14277 | module[name] = value; |
|
14278 | } |
|
14279 | else { |
|
14280 | return module[name]; |
|
14281 | } |
|
14282 | }, |
|
14283 | debug: function() { |
|
14284 | if(!settings.silent && settings.debug) { |
|
14285 | if(settings.performance) { |
|
14286 | module.performance.log(arguments); |
|
14287 | } |
|
14288 | else { |
|
14289 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
14290 | module.debug.apply(console, arguments); |
|
14291 | } |
|
14292 | } |
|
14293 | }, |
|
14294 | verbose: function() { |
|
14295 | if(!settings.silent && settings.verbose && settings.debug) { |
|
14296 | if(settings.performance) { |
|
14297 | module.performance.log(arguments); |
|
14298 | } |
|
14299 | else { |
|
14300 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
14301 | module.verbose.apply(console, arguments); |
|
14302 | } |
|
14303 | } |
|
14304 | }, |
|
14305 | error: function() { |
|
14306 | if(!settings.silent) { |
|
14307 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
|
14308 | module.error.apply(console, arguments); |
|
14309 | } |
|
14310 | }, |
|
14311 | performance: { |
|
14312 | log: function(message) { |
|
14313 | var |
|
14314 | currentTime, |
|
14315 | executionTime, |
|
14316 | previousTime |
|
14317 | ; |
|
14318 | if(settings.performance) { |
|
14319 | currentTime = new Date().getTime(); |
|
14320 | previousTime = time || currentTime; |
|
14321 | executionTime = currentTime - previousTime; |
|
14322 | time = currentTime; |
|
14323 | performance.push({ |
|
14324 | 'Name' : message[0], |
|
14325 | 'Arguments' : [].slice.call(message, 1) || '', |
|
14326 | 'Element' : element, |
|
14327 | 'Execution Time' : executionTime |
|
14328 | }); |
|
14329 | } |
|
14330 | clearTimeout(module.performance.timer); |
|
14331 | module.performance.timer = setTimeout(module.performance.display, 500); |
|
14332 | }, |
|
14333 | display: function() { |
|
14334 | var |
|
14335 | title = settings.name + ':', |
|
14336 | totalTime = 0 |
|
14337 | ; |
|
14338 | time = false; |
|
14339 | clearTimeout(module.performance.timer); |
|
14340 | $.each(performance, function(index, data) { |
|
14341 | totalTime += data['Execution Time']; |
|
14342 | }); |
|
14343 | title += ' ' + totalTime + 'ms'; |
|
14344 | if(moduleSelector) { |
|
14345 | title += ' \'' + moduleSelector + '\''; |
|
14346 | } |
|
14347 | if($allModules.length > 1) { |
|
14348 | title += ' ' + '(' + $allModules.length + ')'; |
|
14349 | } |
|
14350 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
|
14351 | console.groupCollapsed(title); |
|
14352 | if(console.table) { |
|
14353 | console.table(performance); |
|
14354 | } |
|
14355 | else { |
|
14356 | $.each(performance, function(index, data) { |
|
14357 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
|
14358 | }); |
|
14359 | } |
|
14360 | console.groupEnd(); |
|
14361 | } |
|
14362 | performance = []; |
|
14363 | } |
|
14364 | }, |
|
14365 | invoke: function(query, passedArguments, context) { |
|
14366 | var |
|
14367 | object = instance, |
|
14368 | maxDepth, |
|
14369 | found, |
|
14370 | response |
|
14371 | ; |
|
14372 | passedArguments = passedArguments || queryArguments; |
|
14373 | context = element || context; |
|
14374 | if(typeof query == 'string' && object !== undefined) { |
|
14375 | query = query.split(/[\. ]/); |
|
14376 | maxDepth = query.length - 1; |
|
14377 | $.each(query, function(depth, value) { |
|
14378 | var camelCaseValue = (depth != maxDepth) |
|
14379 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
|
14380 | : query |
|
14381 | ; |
|
14382 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
|
14383 | object = object[camelCaseValue]; |
|
14384 | } |
|
14385 | else if( object[camelCaseValue] !== undefined ) { |
|
14386 | found = object[camelCaseValue]; |
|
14387 | return false; |
|
14388 | } |
|
14389 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
|
14390 | object = object[value]; |
|
14391 | } |
|
14392 | else if( object[value] !== undefined ) { |
|
14393 | found = object[value]; |
|
14394 | return false; |
|
14395 | } |
|
14396 | else { |
|
14397 | return false; |
|
14398 | } |
|
14399 | }); |
|
14400 | } |
|
14401 | if( $.isFunction( found ) ) { |
|
14402 | response = found.apply(context, passedArguments); |
|
14403 | } |
|
14404 | else if(found !== undefined) { |
|
14405 | response = found; |
|
14406 | } |
|
14407 | if($.isArray(returnedValue)) { |
|
14408 | returnedValue.push(response); |
|
14409 | } |
|
14410 | else if(returnedValue !== undefined) { |
|
14411 | returnedValue = [returnedValue, response]; |
|
14412 | } |
|
14413 | else if(response !== undefined) { |
|
14414 | returnedValue = response; |
|
14415 | } |
|
14416 | return found; |
|
14417 | } |
|
14418 | }; |
|
14419 | if(methodInvoked) { |
|
14420 | if(instance === undefined) { |
|
14421 | module.initialize(); |
|
14422 | } |
|
14423 | module.invoke(query); |
|
14424 | } |
|
14425 | else { |
|
14426 | if(instance !== undefined) { |
|
14427 | instance.invoke('destroy'); |
|
14428 | } |
|
14429 | module.initialize(); |
|
14430 | } |
|
14431 | ||
14432 | }) |
|
14433 | ; |
|
14434 | ||
14435 | return (returnedValue !== undefined) |
|
14436 | ? returnedValue |
|
14437 | : this |
|
14438 | ; |
|
14439 | }; |
|
14440 | ||
14441 | $.fn.search.settings = { |
|
14442 | ||
14443 | name : 'Search', |
|
14444 | namespace : 'search', |
|
14445 | ||
14446 | silent : false, |
|
14447 | debug : false, |
|
14448 | verbose : false, |
|
14449 | performance : true, |
|
14450 | ||
14451 | // template to use (specified in settings.templates) |
|
14452 | type : 'standard', |
|
14453 | ||
14454 | // minimum characters required to search |
|
14455 | minCharacters : 1, |
|
14456 | ||
14457 | // whether to select first result after searching automatically |
|
14458 | selectFirstResult : false, |
|
14459 | ||
14460 | // API config |
|
14461 | apiSettings : false, |
|
14462 | ||
14463 | // object to search |
|
14464 | source : false, |
|
14465 | ||
14466 | // Whether search should query current term on focus |
|
14467 | searchOnFocus : true, |
|
14468 | ||
14469 | // fields to search |
|
14470 | searchFields : [ |
|
14471 | 'title', |
|
14472 | 'description' |
|
14473 | ], |
|
14474 | ||
14475 | // field to display in standard results template |
|
14476 | displayField : '', |
|
14477 | ||
14478 | // whether to include fuzzy results in local search |
|
14479 | searchFullText : true, |
|
14480 | ||
14481 | // whether to add events to prompt automatically |
|
14482 | automatic : true, |
|
14483 | ||
14484 | // delay before hiding menu after blur |
|
14485 | hideDelay : 0, |
|
14486 | ||
14487 | // delay before searching |
|
14488 | searchDelay : 200, |
|
14489 | ||
14490 | // maximum results returned from local |
|
14491 | maxResults : 7, |
|
14492 | ||
14493 | // whether to store lookups in local cache |
|
14494 | cache : true, |
|
14495 | ||
14496 | // whether no results errors should be shown |
|
14497 | showNoResults : true, |
|
14498 | ||
14499 | // transition settings |
|
14500 | transition : 'scale', |
|
14501 | duration : 200, |
|
14502 | easing : 'easeOutExpo', |
|
14503 | ||
14504 | // callbacks |
|
14505 | onSelect : false, |
|
14506 | onResultsAdd : false, |
|
14507 | ||
14508 | onSearchQuery : function(query){}, |
|
14509 | onResults : function(response){}, |
|
14510 | ||
14511 | onResultsOpen : function(){}, |
|
14512 | onResultsClose : function(){}, |
|
14513 | ||
14514 | className: { |
|
14515 | animating : 'animating', |
|
14516 | active : 'active', |
|
14517 | empty : 'empty', |
|
14518 | focus : 'focus', |
|
14519 | hidden : 'hidden', |
|
14520 | loading : 'loading', |
|
14521 | results : 'results', |
|
14522 | pressed : 'down' |
|
14523 | }, |
|
14524 | ||
14525 | error : { |
|
14526 | source : 'Cannot search. No source used, and Semantic API module was not included', |
|
14527 | noResults : 'Your search returned no results', |
|
14528 | logging : 'Error in debug logging, exiting.', |
|
14529 | noEndpoint : 'No search endpoint was specified', |
|
14530 | noTemplate : 'A valid template name was not specified.', |
|
14531 | serverError : 'There was an issue querying the server.', |
|
14532 | maxResults : 'Results must be an array to use maxResults setting', |
|
14533 | method : 'The method you called is not defined.' |
|
14534 | }, |
|
14535 | ||
14536 | metadata: { |
|
14537 | cache : 'cache', |
|
14538 | results : 'results', |
|
14539 | result : 'result' |
|
14540 | }, |
|
14541 | ||
14542 | regExp: { |
|
14543 | escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, |
|
14544 | beginsWith : '(?:\s|^)' |
|
14545 | }, |
|
14546 | ||
14547 | // maps api response attributes to internal representation |
|
14548 | fields: { |
|
14549 | categories : 'results', // array of categories (category view) |
|
14550 | categoryName : 'name', // name of category (category view) |
|
14551 | categoryResults : 'results', // array of results (category view) |
|
14552 | description : 'description', // result description |
|
14553 | image : 'image', // result image |
|
14554 | price : 'price', // result price |
|
14555 | results : 'results', // array of results (standard) |
|
14556 | title : 'title', // result title |
|
14557 | url : 'url', // result url |
|
14558 | action : 'action', // "view more" object name |
|
14559 | actionText : 'text', // "view more" text |
|
14560 | actionURL : 'url' // "view more" url |
|
14561 | }, |
|
14562 | ||
14563 | selector : { |
|
14564 | prompt : '.prompt', |
|
14565 | searchButton : '.search.button', |
|
14566 | results : '.results', |
|
14567 | message : '.results > .message', |
|
14568 | category : '.category', |
|
14569 | result : '.result', |
|
14570 | title : '.title, .name' |
|
14571 | }, |
|
14572 | ||
14573 | templates: { |
|
14574 | escape: function(string) { |
|
14575 | var |
|
14576 | badChars = /[&<>"'`]/g, |
|
14577 | shouldEscape = /[&<>"'`]/, |
|
14578 | escape = { |
|
14579 | "&": "&", |
|
14580 | "<": "<", |
|
14581 | ">": ">", |
|
14582 | '"': """, |
|
14583 | "'": "'", |
|
14584 | "`": "`" |
|
14585 | }, |
|
14586 | escapedChar = function(chr) { |
|
14587 | return escape[chr]; |
|
14588 | } |
|
14589 | ; |
|
14590 | if(shouldEscape.test(string)) { |
|
14591 | return string.replace(badChars, escapedChar); |
|
14592 | } |
|
14593 | return string; |
|
14594 | }, |
|
14595 | message: function(message, type) { |
|
14596 | var |
|
14597 | html = '' |
|
14598 | ; |
|
14599 | if(message !== undefined && type !== undefined) { |
|
14600 | html += '' |
|
14601 | + '<div class="message ' + type + '">' |
|
14602 | ; |
|
14603 | // message type |
|
14604 | if(type == 'empty') { |
|
14605 | html += '' |
|
14606 | + '<div class="header">No Results</div class="header">' |
|
14607 | + '<div class="description">' + message + '</div class="description">' |
|
14608 | ; |
|
14609 | } |
|
14610 | else { |
|
14611 | html += ' <div class="description">' + message + '</div>'; |
|
14612 | } |
|
14613 | html += '</div>'; |
|
14614 | } |
|
14615 | return html; |
|
14616 | }, |
|
14617 | category: function(response, fields) { |
|
14618 | var |
|
14619 | html = '', |
|
14620 | escape = $.fn.search.settings.templates.escape |
|
14621 | ; |
|
14622 | if(response[fields.categoryResults] !== undefined) { |
|
14623 | ||
14624 | // each category |
|
14625 | $.each(response[fields.categoryResults], function(index, category) { |
|
14626 | if(category[fields.results] !== undefined && category.results.length > 0) { |
|
14627 | ||
14628 | html += '<div class="category">'; |
|
14629 | ||
14630 | if(category[fields.categoryName] !== undefined) { |
|
14631 | html += '<div class="name">' + category[fields.categoryName] + '</div>'; |
|
14632 | } |
|
14633 | ||
14634 | // each item inside category |
|
14635 | $.each(category.results, function(index, result) { |
|
14636 | if(result[fields.url]) { |
|
14637 | html += '<a class="result" href="' + result[fields.url] + '">'; |
|
14638 | } |
|
14639 | else { |
|
14640 | html += '<a class="result">'; |
|
14641 | } |
|
14642 | if(result[fields.image] !== undefined) { |
|
14643 | html += '' |
|
14644 | + '<div class="image">' |
|
14645 | + ' <img src="' + result[fields.image] + '">' |
|
14646 | + '</div>' |
|
14647 | ; |
|
14648 | } |
|
14649 | html += '<div class="content">'; |
|
14650 | if(result[fields.price] !== undefined) { |
|
14651 | html += '<div class="price">' + result[fields.price] + '</div>'; |
|
14652 | } |
|
14653 | if(result[fields.title] !== undefined) { |
|
14654 | html += '<div class="title">' + result[fields.title] + '</div>'; |
|
14655 | } |
|
14656 | if(result[fields.description] !== undefined) { |
|
14657 | html += '<div class="description">' + result[fields.description] + '</div>'; |
|
14658 | } |
|
14659 | html += '' |
|
14660 | + '</div>' |
|
14661 | ; |
|
14662 | html += '</a>'; |
|
14663 | }); |
|
14664 | html += '' |
|
14665 | + '</div>' |
|
14666 | ; |
|
14667 | } |
|
14668 | }); |
|
14669 | if(response[fields.action]) { |
|
14670 | html += '' |
|
14671 | + '<a href="' + response[fields.action][fields.actionURL] + '" class="action">' |
|
14672 | + response[fields.action][fields.actionText] |
|
14673 | + '</a>'; |
|
14674 | } |
|
14675 | return html; |
|
14676 | } |
|
14677 | return false; |
|
14678 | }, |
|
14679 | standard: function(response, fields) { |
|
14680 | var |
|
14681 | html = '' |
|
14682 | ; |
|
14683 | if(response[fields.results] !== undefined) { |
|
14684 | ||
14685 | // each result |
|
14686 | $.each(response[fields.results], function(index, result) { |
|
14687 | if(result[fields.url]) { |
|
14688 | html += '<a class="result" href="' + result[fields.url] + '">'; |
|
14689 | } |
|
14690 | else { |
|
14691 | html += '<a class="result">'; |
|
14692 | } |
|
14693 | if(result[fields.image] !== undefined) { |
|
14694 | html += '' |
|
14695 | + '<div class="image">' |
|
14696 | + ' <img src="' + result[fields.image] + '">' |
|
14697 | + '</div>' |
|
14698 | ; |
|
14699 | } |
|
14700 | html += '<div class="content">'; |
|
14701 | if(result[fields.price] !== undefined) { |
|
14702 | html += '<div class="price">' + result[fields.price] + '</div>'; |
|
14703 | } |
|
14704 | if(result[fields.title] !== undefined) { |
|
14705 | html += '<div class="title">' + result[fields.title] + '</div>'; |
|
14706 | } |
|
14707 | if(result[fields.description] !== undefined) { |
|
14708 | html += '<div class="description">' + result[fields.description] + '</div>'; |
|
14709 | } |
|
14710 | html += '' |
|
14711 | + '</div>' |
|
14712 | ; |
|
14713 | html += '</a>'; |
|
14714 | }); |
|
14715 | ||
14716 | if(response[fields.action]) { |
|
14717 | html += '' |
|
14718 | + '<a href="' + response[fields.action][fields.actionURL] + '" class="action">' |
|
14719 | + response[fields.action][fields.actionText] |
|
14720 | + '</a>'; |
|
14721 | } |
|
14722 | return html; |
|
14723 | } |
|
14724 | return false; |
|
14725 | } |
|
14726 | } |
|
14727 | }; |
|
14728 | ||
14729 | })( jQuery, window, document ); |
|
14730 | ||
14731 | /*! |
|
14732 | * # Semantic UI 2.2.11 - Shape |
@@ 11-1451 (lines=1441) @@ | ||
8 | * |
|
9 | */ |
|
10 | ||
11 | ;(function ($, window, document, undefined) { |
|
12 | ||
13 | "use strict"; |
|
14 | ||
15 | window = (typeof window != 'undefined' && window.Math == Math) |
|
16 | ? window |
|
17 | : (typeof self != 'undefined' && self.Math == Math) |
|
18 | ? self |
|
19 | : Function('return this')() |
|
20 | ; |
|
21 | ||
22 | $.fn.search = function(parameters) { |
|
23 | var |
|
24 | $allModules = $(this), |
|
25 | moduleSelector = $allModules.selector || '', |
|
26 | ||
27 | time = new Date().getTime(), |
|
28 | performance = [], |
|
29 | ||
30 | query = arguments[0], |
|
31 | methodInvoked = (typeof query == 'string'), |
|
32 | queryArguments = [].slice.call(arguments, 1), |
|
33 | returnedValue |
|
34 | ; |
|
35 | $(this) |
|
36 | .each(function() { |
|
37 | var |
|
38 | settings = ( $.isPlainObject(parameters) ) |
|
39 | ? $.extend(true, {}, $.fn.search.settings, parameters) |
|
40 | : $.extend({}, $.fn.search.settings), |
|
41 | ||
42 | className = settings.className, |
|
43 | metadata = settings.metadata, |
|
44 | regExp = settings.regExp, |
|
45 | fields = settings.fields, |
|
46 | selector = settings.selector, |
|
47 | error = settings.error, |
|
48 | namespace = settings.namespace, |
|
49 | ||
50 | eventNamespace = '.' + namespace, |
|
51 | moduleNamespace = namespace + '-module', |
|
52 | ||
53 | $module = $(this), |
|
54 | $prompt = $module.find(selector.prompt), |
|
55 | $searchButton = $module.find(selector.searchButton), |
|
56 | $results = $module.find(selector.results), |
|
57 | $result = $module.find(selector.result), |
|
58 | $category = $module.find(selector.category), |
|
59 | ||
60 | element = this, |
|
61 | instance = $module.data(moduleNamespace), |
|
62 | ||
63 | disabledBubbled = false, |
|
64 | resultsDismissed = false, |
|
65 | ||
66 | module |
|
67 | ; |
|
68 | ||
69 | module = { |
|
70 | ||
71 | initialize: function() { |
|
72 | module.verbose('Initializing module'); |
|
73 | module.determine.searchFields(); |
|
74 | module.bind.events(); |
|
75 | module.set.type(); |
|
76 | module.create.results(); |
|
77 | module.instantiate(); |
|
78 | }, |
|
79 | instantiate: function() { |
|
80 | module.verbose('Storing instance of module', module); |
|
81 | instance = module; |
|
82 | $module |
|
83 | .data(moduleNamespace, module) |
|
84 | ; |
|
85 | }, |
|
86 | destroy: function() { |
|
87 | module.verbose('Destroying instance'); |
|
88 | $module |
|
89 | .off(eventNamespace) |
|
90 | .removeData(moduleNamespace) |
|
91 | ; |
|
92 | }, |
|
93 | ||
94 | refresh: function() { |
|
95 | module.debug('Refreshing selector cache'); |
|
96 | $prompt = $module.find(selector.prompt); |
|
97 | $searchButton = $module.find(selector.searchButton); |
|
98 | $category = $module.find(selector.category); |
|
99 | $results = $module.find(selector.results); |
|
100 | $result = $module.find(selector.result); |
|
101 | }, |
|
102 | ||
103 | refreshResults: function() { |
|
104 | $results = $module.find(selector.results); |
|
105 | $result = $module.find(selector.result); |
|
106 | }, |
|
107 | ||
108 | bind: { |
|
109 | events: function() { |
|
110 | module.verbose('Binding events to search'); |
|
111 | if(settings.automatic) { |
|
112 | $module |
|
113 | .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input) |
|
114 | ; |
|
115 | $prompt |
|
116 | .attr('autocomplete', 'off') |
|
117 | ; |
|
118 | } |
|
119 | $module |
|
120 | // prompt |
|
121 | .on('focus' + eventNamespace, selector.prompt, module.event.focus) |
|
122 | .on('blur' + eventNamespace, selector.prompt, module.event.blur) |
|
123 | .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard) |
|
124 | // search button |
|
125 | .on('click' + eventNamespace, selector.searchButton, module.query) |
|
126 | // results |
|
127 | .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown) |
|
128 | .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup) |
|
129 | .on('click' + eventNamespace, selector.result, module.event.result.click) |
|
130 | ; |
|
131 | } |
|
132 | }, |
|
133 | ||
134 | determine: { |
|
135 | searchFields: function() { |
|
136 | // this makes sure $.extend does not add specified search fields to default fields |
|
137 | // this is the only setting which should not extend defaults |
|
138 | if(parameters && parameters.searchFields !== undefined) { |
|
139 | settings.searchFields = parameters.searchFields; |
|
140 | } |
|
141 | } |
|
142 | }, |
|
143 | ||
144 | event: { |
|
145 | input: function() { |
|
146 | if(settings.searchDelay) { |
|
147 | clearTimeout(module.timer); |
|
148 | module.timer = setTimeout(function() { |
|
149 | if(module.is.focused()) { |
|
150 | module.query(); |
|
151 | } |
|
152 | }, settings.searchDelay); |
|
153 | } |
|
154 | else { |
|
155 | module.query(); |
|
156 | } |
|
157 | }, |
|
158 | focus: function() { |
|
159 | module.set.focus(); |
|
160 | if(settings.searchOnFocus && module.has.minimumCharacters() ) { |
|
161 | module.query(function() { |
|
162 | if(module.can.show() ) { |
|
163 | module.showResults(); |
|
164 | } |
|
165 | }); |
|
166 | } |
|
167 | }, |
|
168 | blur: function(event) { |
|
169 | var |
|
170 | pageLostFocus = (document.activeElement === this), |
|
171 | callback = function() { |
|
172 | module.cancel.query(); |
|
173 | module.remove.focus(); |
|
174 | module.timer = setTimeout(module.hideResults, settings.hideDelay); |
|
175 | } |
|
176 | ; |
|
177 | if(pageLostFocus) { |
|
178 | return; |
|
179 | } |
|
180 | resultsDismissed = false; |
|
181 | if(module.resultsClicked) { |
|
182 | module.debug('Determining if user action caused search to close'); |
|
183 | $module |
|
184 | .one('click.close' + eventNamespace, selector.results, function(event) { |
|
185 | if(module.is.inMessage(event) || disabledBubbled) { |
|
186 | $prompt.focus(); |
|
187 | return; |
|
188 | } |
|
189 | disabledBubbled = false; |
|
190 | if( !module.is.animating() && !module.is.hidden()) { |
|
191 | callback(); |
|
192 | } |
|
193 | }) |
|
194 | ; |
|
195 | } |
|
196 | else { |
|
197 | module.debug('Input blurred without user action, closing results'); |
|
198 | callback(); |
|
199 | } |
|
200 | }, |
|
201 | result: { |
|
202 | mousedown: function() { |
|
203 | module.resultsClicked = true; |
|
204 | }, |
|
205 | mouseup: function() { |
|
206 | module.resultsClicked = false; |
|
207 | }, |
|
208 | click: function(event) { |
|
209 | module.debug('Search result selected'); |
|
210 | var |
|
211 | $result = $(this), |
|
212 | $title = $result.find(selector.title).eq(0), |
|
213 | $link = $result.is('a[href]') |
|
214 | ? $result |
|
215 | : $result.find('a[href]').eq(0), |
|
216 | href = $link.attr('href') || false, |
|
217 | target = $link.attr('target') || false, |
|
218 | title = $title.html(), |
|
219 | // title is used for result lookup |
|
220 | value = ($title.length > 0) |
|
221 | ? $title.text() |
|
222 | : false, |
|
223 | results = module.get.results(), |
|
224 | result = $result.data(metadata.result) || module.get.result(value, results), |
|
225 | returnedValue |
|
226 | ; |
|
227 | if( $.isFunction(settings.onSelect) ) { |
|
228 | if(settings.onSelect.call(element, result, results) === false) { |
|
229 | module.debug('Custom onSelect callback cancelled default select action'); |
|
230 | disabledBubbled = true; |
|
231 | return; |
|
232 | } |
|
233 | } |
|
234 | module.hideResults(); |
|
235 | if(value) { |
|
236 | module.set.value(value); |
|
237 | } |
|
238 | if(href) { |
|
239 | module.verbose('Opening search link found in result', $link); |
|
240 | if(target == '_blank' || event.ctrlKey) { |
|
241 | window.open(href); |
|
242 | } |
|
243 | else { |
|
244 | window.location.href = (href); |
|
245 | } |
|
246 | } |
|
247 | } |
|
248 | } |
|
249 | }, |
|
250 | handleKeyboard: function(event) { |
|
251 | var |
|
252 | // force selector refresh |
|
253 | $result = $module.find(selector.result), |
|
254 | $category = $module.find(selector.category), |
|
255 | $activeResult = $result.filter('.' + className.active), |
|
256 | currentIndex = $result.index( $activeResult ), |
|
257 | resultSize = $result.length, |
|
258 | hasActiveResult = $activeResult.length > 0, |
|
259 | ||
260 | keyCode = event.which, |
|
261 | keys = { |
|
262 | backspace : 8, |
|
263 | enter : 13, |
|
264 | escape : 27, |
|
265 | upArrow : 38, |
|
266 | downArrow : 40 |
|
267 | }, |
|
268 | newIndex |
|
269 | ; |
|
270 | // search shortcuts |
|
271 | if(keyCode == keys.escape) { |
|
272 | module.verbose('Escape key pressed, blurring search field'); |
|
273 | module.hideResults(); |
|
274 | resultsDismissed = true; |
|
275 | } |
|
276 | if( module.is.visible() ) { |
|
277 | if(keyCode == keys.enter) { |
|
278 | module.verbose('Enter key pressed, selecting active result'); |
|
279 | if( $result.filter('.' + className.active).length > 0 ) { |
|
280 | module.event.result.click.call($result.filter('.' + className.active), event); |
|
281 | event.preventDefault(); |
|
282 | return false; |
|
283 | } |
|
284 | } |
|
285 | else if(keyCode == keys.upArrow && hasActiveResult) { |
|
286 | module.verbose('Up key pressed, changing active result'); |
|
287 | newIndex = (currentIndex - 1 < 0) |
|
288 | ? currentIndex |
|
289 | : currentIndex - 1 |
|
290 | ; |
|
291 | $category |
|
292 | .removeClass(className.active) |
|
293 | ; |
|
294 | $result |
|
295 | .removeClass(className.active) |
|
296 | .eq(newIndex) |
|
297 | .addClass(className.active) |
|
298 | .closest($category) |
|
299 | .addClass(className.active) |
|
300 | ; |
|
301 | event.preventDefault(); |
|
302 | } |
|
303 | else if(keyCode == keys.downArrow) { |
|
304 | module.verbose('Down key pressed, changing active result'); |
|
305 | newIndex = (currentIndex + 1 >= resultSize) |
|
306 | ? currentIndex |
|
307 | : currentIndex + 1 |
|
308 | ; |
|
309 | $category |
|
310 | .removeClass(className.active) |
|
311 | ; |
|
312 | $result |
|
313 | .removeClass(className.active) |
|
314 | .eq(newIndex) |
|
315 | .addClass(className.active) |
|
316 | .closest($category) |
|
317 | .addClass(className.active) |
|
318 | ; |
|
319 | event.preventDefault(); |
|
320 | } |
|
321 | } |
|
322 | else { |
|
323 | // query shortcuts |
|
324 | if(keyCode == keys.enter) { |
|
325 | module.verbose('Enter key pressed, executing query'); |
|
326 | module.query(); |
|
327 | module.set.buttonPressed(); |
|
328 | $prompt.one('keyup', module.remove.buttonFocus); |
|
329 | } |
|
330 | } |
|
331 | }, |
|
332 | ||
333 | setup: { |
|
334 | api: function(searchTerm, callback) { |
|
335 | var |
|
336 | apiSettings = { |
|
337 | debug : settings.debug, |
|
338 | on : false, |
|
339 | cache : true, |
|
340 | action : 'search', |
|
341 | urlData : { |
|
342 | query : searchTerm |
|
343 | }, |
|
344 | onSuccess : function(response) { |
|
345 | module.parse.response.call(element, response, searchTerm); |
|
346 | callback(); |
|
347 | }, |
|
348 | onFailure : function() { |
|
349 | module.displayMessage(error.serverError); |
|
350 | callback(); |
|
351 | }, |
|
352 | onAbort : function(response) { |
|
353 | }, |
|
354 | onError : module.error |
|
355 | }, |
|
356 | searchHTML |
|
357 | ; |
|
358 | $.extend(true, apiSettings, settings.apiSettings); |
|
359 | module.verbose('Setting up API request', apiSettings); |
|
360 | $module.api(apiSettings); |
|
361 | } |
|
362 | }, |
|
363 | ||
364 | can: { |
|
365 | useAPI: function() { |
|
366 | return $.fn.api !== undefined; |
|
367 | }, |
|
368 | show: function() { |
|
369 | return module.is.focused() && !module.is.visible() && !module.is.empty(); |
|
370 | }, |
|
371 | transition: function() { |
|
372 | return settings.transition && $.fn.transition !== undefined && $module.transition('is supported'); |
|
373 | } |
|
374 | }, |
|
375 | ||
376 | is: { |
|
377 | animating: function() { |
|
378 | return $results.hasClass(className.animating); |
|
379 | }, |
|
380 | hidden: function() { |
|
381 | return $results.hasClass(className.hidden); |
|
382 | }, |
|
383 | inMessage: function(event) { |
|
384 | if(!event.target) { |
|
385 | return; |
|
386 | } |
|
387 | var |
|
388 | $target = $(event.target), |
|
389 | isInDOM = $.contains(document.documentElement, event.target) |
|
390 | ; |
|
391 | return (isInDOM && $target.closest(selector.message).length > 0); |
|
392 | }, |
|
393 | empty: function() { |
|
394 | return ($results.html() === ''); |
|
395 | }, |
|
396 | visible: function() { |
|
397 | return ($results.filter(':visible').length > 0); |
|
398 | }, |
|
399 | focused: function() { |
|
400 | return ($prompt.filter(':focus').length > 0); |
|
401 | } |
|
402 | }, |
|
403 | ||
404 | get: { |
|
405 | inputEvent: function() { |
|
406 | var |
|
407 | prompt = $prompt[0], |
|
408 | inputEvent = (prompt !== undefined && prompt.oninput !== undefined) |
|
409 | ? 'input' |
|
410 | : (prompt !== undefined && prompt.onpropertychange !== undefined) |
|
411 | ? 'propertychange' |
|
412 | : 'keyup' |
|
413 | ; |
|
414 | return inputEvent; |
|
415 | }, |
|
416 | value: function() { |
|
417 | return $prompt.val(); |
|
418 | }, |
|
419 | results: function() { |
|
420 | var |
|
421 | results = $module.data(metadata.results) |
|
422 | ; |
|
423 | return results; |
|
424 | }, |
|
425 | result: function(value, results) { |
|
426 | var |
|
427 | lookupFields = ['title', 'id'], |
|
428 | result = false |
|
429 | ; |
|
430 | value = (value !== undefined) |
|
431 | ? value |
|
432 | : module.get.value() |
|
433 | ; |
|
434 | results = (results !== undefined) |
|
435 | ? results |
|
436 | : module.get.results() |
|
437 | ; |
|
438 | if(settings.type === 'category') { |
|
439 | module.debug('Finding result that matches', value); |
|
440 | $.each(results, function(index, category) { |
|
441 | if($.isArray(category.results)) { |
|
442 | result = module.search.object(value, category.results, lookupFields)[0]; |
|
443 | // don't continue searching if a result is found |
|
444 | if(result) { |
|
445 | return false; |
|
446 | } |
|
447 | } |
|
448 | }); |
|
449 | } |
|
450 | else { |
|
451 | module.debug('Finding result in results object', value); |
|
452 | result = module.search.object(value, results, lookupFields)[0]; |
|
453 | } |
|
454 | return result || false; |
|
455 | }, |
|
456 | }, |
|
457 | ||
458 | select: { |
|
459 | firstResult: function() { |
|
460 | module.verbose('Selecting first result'); |
|
461 | $result.first().addClass(className.active); |
|
462 | } |
|
463 | }, |
|
464 | ||
465 | set: { |
|
466 | focus: function() { |
|
467 | $module.addClass(className.focus); |
|
468 | }, |
|
469 | loading: function() { |
|
470 | $module.addClass(className.loading); |
|
471 | }, |
|
472 | value: function(value) { |
|
473 | module.verbose('Setting search input value', value); |
|
474 | $prompt |
|
475 | .val(value) |
|
476 | ; |
|
477 | }, |
|
478 | type: function(type) { |
|
479 | type = type || settings.type; |
|
480 | if(settings.type == 'category') { |
|
481 | $module.addClass(settings.type); |
|
482 | } |
|
483 | }, |
|
484 | buttonPressed: function() { |
|
485 | $searchButton.addClass(className.pressed); |
|
486 | } |
|
487 | }, |
|
488 | ||
489 | remove: { |
|
490 | loading: function() { |
|
491 | $module.removeClass(className.loading); |
|
492 | }, |
|
493 | focus: function() { |
|
494 | $module.removeClass(className.focus); |
|
495 | }, |
|
496 | buttonPressed: function() { |
|
497 | $searchButton.removeClass(className.pressed); |
|
498 | } |
|
499 | }, |
|
500 | ||
501 | query: function(callback) { |
|
502 | callback = $.isFunction(callback) |
|
503 | ? callback |
|
504 | : function(){} |
|
505 | ; |
|
506 | var |
|
507 | searchTerm = module.get.value(), |
|
508 | cache = module.read.cache(searchTerm) |
|
509 | ; |
|
510 | callback = callback || function() {}; |
|
511 | if( module.has.minimumCharacters() ) { |
|
512 | if(cache) { |
|
513 | module.debug('Reading result from cache', searchTerm); |
|
514 | module.save.results(cache.results); |
|
515 | module.addResults(cache.html); |
|
516 | module.inject.id(cache.results); |
|
517 | callback(); |
|
518 | } |
|
519 | else { |
|
520 | module.debug('Querying for', searchTerm); |
|
521 | if($.isPlainObject(settings.source) || $.isArray(settings.source)) { |
|
522 | module.search.local(searchTerm); |
|
523 | callback(); |
|
524 | } |
|
525 | else if( module.can.useAPI() ) { |
|
526 | module.search.remote(searchTerm, callback); |
|
527 | } |
|
528 | else { |
|
529 | module.error(error.source); |
|
530 | callback(); |
|
531 | } |
|
532 | } |
|
533 | settings.onSearchQuery.call(element, searchTerm); |
|
534 | } |
|
535 | else { |
|
536 | module.hideResults(); |
|
537 | } |
|
538 | }, |
|
539 | ||
540 | search: { |
|
541 | local: function(searchTerm) { |
|
542 | var |
|
543 | results = module.search.object(searchTerm, settings.content), |
|
544 | searchHTML |
|
545 | ; |
|
546 | module.set.loading(); |
|
547 | module.save.results(results); |
|
548 | module.debug('Returned local search results', results); |
|
549 | ||
550 | searchHTML = module.generateResults({ |
|
551 | results: results |
|
552 | }); |
|
553 | module.remove.loading(); |
|
554 | module.addResults(searchHTML); |
|
555 | module.inject.id(results); |
|
556 | module.write.cache(searchTerm, { |
|
557 | html : searchHTML, |
|
558 | results : results |
|
559 | }); |
|
560 | }, |
|
561 | remote: function(searchTerm, callback) { |
|
562 | callback = $.isFunction(callback) |
|
563 | ? callback |
|
564 | : function(){} |
|
565 | ; |
|
566 | if($module.api('is loading')) { |
|
567 | $module.api('abort'); |
|
568 | } |
|
569 | module.setup.api(searchTerm, callback); |
|
570 | $module |
|
571 | .api('query') |
|
572 | ; |
|
573 | }, |
|
574 | object: function(searchTerm, source, searchFields) { |
|
575 | var |
|
576 | results = [], |
|
577 | fuzzyResults = [], |
|
578 | searchExp = searchTerm.toString().replace(regExp.escape, '\\$&'), |
|
579 | matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), |
|
580 | ||
581 | // avoid duplicates when pushing results |
|
582 | addResult = function(array, result) { |
|
583 | var |
|
584 | notResult = ($.inArray(result, results) == -1), |
|
585 | notFuzzyResult = ($.inArray(result, fuzzyResults) == -1) |
|
586 | ; |
|
587 | if(notResult && notFuzzyResult) { |
|
588 | array.push(result); |
|
589 | } |
|
590 | } |
|
591 | ; |
|
592 | source = source || settings.source; |
|
593 | searchFields = (searchFields !== undefined) |
|
594 | ? searchFields |
|
595 | : settings.searchFields |
|
596 | ; |
|
597 | ||
598 | // search fields should be array to loop correctly |
|
599 | if(!$.isArray(searchFields)) { |
|
600 | searchFields = [searchFields]; |
|
601 | } |
|
602 | ||
603 | // exit conditions if no source |
|
604 | if(source === undefined || source === false) { |
|
605 | module.error(error.source); |
|
606 | return []; |
|
607 | } |
|
608 | ||
609 | // iterate through search fields looking for matches |
|
610 | $.each(searchFields, function(index, field) { |
|
611 | $.each(source, function(label, content) { |
|
612 | var |
|
613 | fieldExists = (typeof content[field] == 'string') |
|
614 | ; |
|
615 | if(fieldExists) { |
|
616 | if( content[field].search(matchRegExp) !== -1) { |
|
617 | // content starts with value (first in results) |
|
618 | addResult(results, content); |
|
619 | } |
|
620 | else if(settings.searchFullText && module.fuzzySearch(searchTerm, content[field]) ) { |
|
621 | // content fuzzy matches (last in results) |
|
622 | addResult(fuzzyResults, content); |
|
623 | } |
|
624 | } |
|
625 | }); |
|
626 | }); |
|
627 | return $.merge(results, fuzzyResults); |
|
628 | } |
|
629 | }, |
|
630 | ||
631 | fuzzySearch: function(query, term) { |
|
632 | var |
|
633 | termLength = term.length, |
|
634 | queryLength = query.length |
|
635 | ; |
|
636 | if(typeof query !== 'string') { |
|
637 | return false; |
|
638 | } |
|
639 | query = query.toLowerCase(); |
|
640 | term = term.toLowerCase(); |
|
641 | if(queryLength > termLength) { |
|
642 | return false; |
|
643 | } |
|
644 | if(queryLength === termLength) { |
|
645 | return (query === term); |
|
646 | } |
|
647 | search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { |
|
648 | var |
|
649 | queryCharacter = query.charCodeAt(characterIndex) |
|
650 | ; |
|
651 | while(nextCharacterIndex < termLength) { |
|
652 | if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { |
|
653 | continue search; |
|
654 | } |
|
655 | } |
|
656 | return false; |
|
657 | } |
|
658 | return true; |
|
659 | }, |
|
660 | ||
661 | parse: { |
|
662 | response: function(response, searchTerm) { |
|
663 | var |
|
664 | searchHTML = module.generateResults(response) |
|
665 | ; |
|
666 | module.verbose('Parsing server response', response); |
|
667 | if(response !== undefined) { |
|
668 | if(searchTerm !== undefined && response[fields.results] !== undefined) { |
|
669 | module.addResults(searchHTML); |
|
670 | module.inject.id(response[fields.results]); |
|
671 | module.write.cache(searchTerm, { |
|
672 | html : searchHTML, |
|
673 | results : response[fields.results] |
|
674 | }); |
|
675 | module.save.results(response[fields.results]); |
|
676 | } |
|
677 | } |
|
678 | } |
|
679 | }, |
|
680 | ||
681 | cancel: { |
|
682 | query: function() { |
|
683 | if( module.can.useAPI() ) { |
|
684 | $module.api('abort'); |
|
685 | } |
|
686 | } |
|
687 | }, |
|
688 | ||
689 | has: { |
|
690 | minimumCharacters: function() { |
|
691 | var |
|
692 | searchTerm = module.get.value(), |
|
693 | numCharacters = searchTerm.length |
|
694 | ; |
|
695 | return (numCharacters >= settings.minCharacters); |
|
696 | }, |
|
697 | results: function() { |
|
698 | if($results.length === 0) { |
|
699 | return false; |
|
700 | } |
|
701 | var |
|
702 | html = $results.html() |
|
703 | ; |
|
704 | return html != ''; |
|
705 | } |
|
706 | }, |
|
707 | ||
708 | clear: { |
|
709 | cache: function(value) { |
|
710 | var |
|
711 | cache = $module.data(metadata.cache) |
|
712 | ; |
|
713 | if(!value) { |
|
714 | module.debug('Clearing cache', value); |
|
715 | $module.removeData(metadata.cache); |
|
716 | } |
|
717 | else if(value && cache && cache[value]) { |
|
718 | module.debug('Removing value from cache', value); |
|
719 | delete cache[value]; |
|
720 | $module.data(metadata.cache, cache); |
|
721 | } |
|
722 | } |
|
723 | }, |
|
724 | ||
725 | read: { |
|
726 | cache: function(name) { |
|
727 | var |
|
728 | cache = $module.data(metadata.cache) |
|
729 | ; |
|
730 | if(settings.cache) { |
|
731 | module.verbose('Checking cache for generated html for query', name); |
|
732 | return (typeof cache == 'object') && (cache[name] !== undefined) |
|
733 | ? cache[name] |
|
734 | : false |
|
735 | ; |
|
736 | } |
|
737 | return false; |
|
738 | } |
|
739 | }, |
|
740 | ||
741 | create: { |
|
742 | id: function(resultIndex, categoryIndex) { |
|
743 | var |
|
744 | resultID = (resultIndex + 1), // not zero indexed |
|
745 | categoryID = (categoryIndex + 1), |
|
746 | firstCharCode, |
|
747 | letterID, |
|
748 | id |
|
749 | ; |
|
750 | if(categoryIndex !== undefined) { |
|
751 | // start char code for "A" |
|
752 | letterID = String.fromCharCode(97 + categoryIndex); |
|
753 | id = letterID + resultID; |
|
754 | module.verbose('Creating category result id', id); |
|
755 | } |
|
756 | else { |
|
757 | id = resultID; |
|
758 | module.verbose('Creating result id', id); |
|
759 | } |
|
760 | return id; |
|
761 | }, |
|
762 | results: function() { |
|
763 | if($results.length === 0) { |
|
764 | $results = $('<div />') |
|
765 | .addClass(className.results) |
|
766 | .appendTo($module) |
|
767 | ; |
|
768 | } |
|
769 | } |
|
770 | }, |
|
771 | ||
772 | inject: { |
|
773 | result: function(result, resultIndex, categoryIndex) { |
|
774 | module.verbose('Injecting result into results'); |
|
775 | var |
|
776 | $selectedResult = (categoryIndex !== undefined) |
|
777 | ? $results |
|
778 | .children().eq(categoryIndex) |
|
779 | .children(selector.result).eq(resultIndex) |
|
780 | : $results |
|
781 | .children(selector.result).eq(resultIndex) |
|
782 | ; |
|
783 | module.verbose('Injecting results metadata', $selectedResult); |
|
784 | $selectedResult |
|
785 | .data(metadata.result, result) |
|
786 | ; |
|
787 | }, |
|
788 | id: function(results) { |
|
789 | module.debug('Injecting unique ids into results'); |
|
790 | var |
|
791 | // since results may be object, we must use counters |
|
792 | categoryIndex = 0, |
|
793 | resultIndex = 0 |
|
794 | ; |
|
795 | if(settings.type === 'category') { |
|
796 | // iterate through each category result |
|
797 | $.each(results, function(index, category) { |
|
798 | resultIndex = 0; |
|
799 | $.each(category.results, function(index, value) { |
|
800 | var |
|
801 | result = category.results[index] |
|
802 | ; |
|
803 | if(result.id === undefined) { |
|
804 | result.id = module.create.id(resultIndex, categoryIndex); |
|
805 | } |
|
806 | module.inject.result(result, resultIndex, categoryIndex); |
|
807 | resultIndex++; |
|
808 | }); |
|
809 | categoryIndex++; |
|
810 | }); |
|
811 | } |
|
812 | else { |
|
813 | // top level |
|
814 | $.each(results, function(index, value) { |
|
815 | var |
|
816 | result = results[index] |
|
817 | ; |
|
818 | if(result.id === undefined) { |
|
819 | result.id = module.create.id(resultIndex); |
|
820 | } |
|
821 | module.inject.result(result, resultIndex); |
|
822 | resultIndex++; |
|
823 | }); |
|
824 | } |
|
825 | return results; |
|
826 | } |
|
827 | }, |
|
828 | ||
829 | save: { |
|
830 | results: function(results) { |
|
831 | module.verbose('Saving current search results to metadata', results); |
|
832 | $module.data(metadata.results, results); |
|
833 | } |
|
834 | }, |
|
835 | ||
836 | write: { |
|
837 | cache: function(name, value) { |
|
838 | var |
|
839 | cache = ($module.data(metadata.cache) !== undefined) |
|
840 | ? $module.data(metadata.cache) |
|
841 | : {} |
|
842 | ; |
|
843 | if(settings.cache) { |
|
844 | module.verbose('Writing generated html to cache', name, value); |
|
845 | cache[name] = value; |
|
846 | $module |
|
847 | .data(metadata.cache, cache) |
|
848 | ; |
|
849 | } |
|
850 | } |
|
851 | }, |
|
852 | ||
853 | addResults: function(html) { |
|
854 | if( $.isFunction(settings.onResultsAdd) ) { |
|
855 | if( settings.onResultsAdd.call($results, html) === false ) { |
|
856 | module.debug('onResultsAdd callback cancelled default action'); |
|
857 | return false; |
|
858 | } |
|
859 | } |
|
860 | if(html) { |
|
861 | $results |
|
862 | .html(html) |
|
863 | ; |
|
864 | module.refreshResults(); |
|
865 | if(settings.selectFirstResult) { |
|
866 | module.select.firstResult(); |
|
867 | } |
|
868 | module.showResults(); |
|
869 | } |
|
870 | else { |
|
871 | module.hideResults(function() { |
|
872 | $results.empty(); |
|
873 | }); |
|
874 | } |
|
875 | }, |
|
876 | ||
877 | showResults: function(callback) { |
|
878 | callback = $.isFunction(callback) |
|
879 | ? callback |
|
880 | : function(){} |
|
881 | ; |
|
882 | if(resultsDismissed) { |
|
883 | return; |
|
884 | } |
|
885 | if(!module.is.visible() && module.has.results()) { |
|
886 | if( module.can.transition() ) { |
|
887 | module.debug('Showing results with css animations'); |
|
888 | $results |
|
889 | .transition({ |
|
890 | animation : settings.transition + ' in', |
|
891 | debug : settings.debug, |
|
892 | verbose : settings.verbose, |
|
893 | duration : settings.duration, |
|
894 | onComplete : function() { |
|
895 | callback(); |
|
896 | }, |
|
897 | queue : true |
|
898 | }) |
|
899 | ; |
|
900 | } |
|
901 | else { |
|
902 | module.debug('Showing results with javascript'); |
|
903 | $results |
|
904 | .stop() |
|
905 | .fadeIn(settings.duration, settings.easing) |
|
906 | ; |
|
907 | } |
|
908 | settings.onResultsOpen.call($results); |
|
909 | } |
|
910 | }, |
|
911 | hideResults: function(callback) { |
|
912 | callback = $.isFunction(callback) |
|
913 | ? callback |
|
914 | : function(){} |
|
915 | ; |
|
916 | if( module.is.visible() ) { |
|
917 | if( module.can.transition() ) { |
|
918 | module.debug('Hiding results with css animations'); |
|
919 | $results |
|
920 | .transition({ |
|
921 | animation : settings.transition + ' out', |
|
922 | debug : settings.debug, |
|
923 | verbose : settings.verbose, |
|
924 | duration : settings.duration, |
|
925 | onComplete : function() { |
|
926 | callback(); |
|
927 | }, |
|
928 | queue : true |
|
929 | }) |
|
930 | ; |
|
931 | } |
|
932 | else { |
|
933 | module.debug('Hiding results with javascript'); |
|
934 | $results |
|
935 | .stop() |
|
936 | .fadeOut(settings.duration, settings.easing) |
|
937 | ; |
|
938 | } |
|
939 | settings.onResultsClose.call($results); |
|
940 | } |
|
941 | }, |
|
942 | ||
943 | generateResults: function(response) { |
|
944 | module.debug('Generating html from response', response); |
|
945 | var |
|
946 | template = settings.templates[settings.type], |
|
947 | isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), |
|
948 | isProperArray = ($.isArray(response[fields.results]) && response[fields.results].length > 0), |
|
949 | html = '' |
|
950 | ; |
|
951 | if(isProperObject || isProperArray ) { |
|
952 | if(settings.maxResults > 0) { |
|
953 | if(isProperObject) { |
|
954 | if(settings.type == 'standard') { |
|
955 | module.error(error.maxResults); |
|
956 | } |
|
957 | } |
|
958 | else { |
|
959 | response[fields.results] = response[fields.results].slice(0, settings.maxResults); |
|
960 | } |
|
961 | } |
|
962 | if($.isFunction(template)) { |
|
963 | html = template(response, fields); |
|
964 | } |
|
965 | else { |
|
966 | module.error(error.noTemplate, false); |
|
967 | } |
|
968 | } |
|
969 | else if(settings.showNoResults) { |
|
970 | html = module.displayMessage(error.noResults, 'empty'); |
|
971 | } |
|
972 | settings.onResults.call(element, response); |
|
973 | return html; |
|
974 | }, |
|
975 | ||
976 | displayMessage: function(text, type) { |
|
977 | type = type || 'standard'; |
|
978 | module.debug('Displaying message', text, type); |
|
979 | module.addResults( settings.templates.message(text, type) ); |
|
980 | return settings.templates.message(text, type); |
|
981 | }, |
|
982 | ||
983 | setting: function(name, value) { |
|
984 | if( $.isPlainObject(name) ) { |
|
985 | $.extend(true, settings, name); |
|
986 | } |
|
987 | else if(value !== undefined) { |
|
988 | settings[name] = value; |
|
989 | } |
|
990 | else { |
|
991 | return settings[name]; |
|
992 | } |
|
993 | }, |
|
994 | internal: function(name, value) { |
|
995 | if( $.isPlainObject(name) ) { |
|
996 | $.extend(true, module, name); |
|
997 | } |
|
998 | else if(value !== undefined) { |
|
999 | module[name] = value; |
|
1000 | } |
|
1001 | else { |
|
1002 | return module[name]; |
|
1003 | } |
|
1004 | }, |
|
1005 | debug: function() { |
|
1006 | if(!settings.silent && settings.debug) { |
|
1007 | if(settings.performance) { |
|
1008 | module.performance.log(arguments); |
|
1009 | } |
|
1010 | else { |
|
1011 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
1012 | module.debug.apply(console, arguments); |
|
1013 | } |
|
1014 | } |
|
1015 | }, |
|
1016 | verbose: function() { |
|
1017 | if(!settings.silent && settings.verbose && settings.debug) { |
|
1018 | if(settings.performance) { |
|
1019 | module.performance.log(arguments); |
|
1020 | } |
|
1021 | else { |
|
1022 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
1023 | module.verbose.apply(console, arguments); |
|
1024 | } |
|
1025 | } |
|
1026 | }, |
|
1027 | error: function() { |
|
1028 | if(!settings.silent) { |
|
1029 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
|
1030 | module.error.apply(console, arguments); |
|
1031 | } |
|
1032 | }, |
|
1033 | performance: { |
|
1034 | log: function(message) { |
|
1035 | var |
|
1036 | currentTime, |
|
1037 | executionTime, |
|
1038 | previousTime |
|
1039 | ; |
|
1040 | if(settings.performance) { |
|
1041 | currentTime = new Date().getTime(); |
|
1042 | previousTime = time || currentTime; |
|
1043 | executionTime = currentTime - previousTime; |
|
1044 | time = currentTime; |
|
1045 | performance.push({ |
|
1046 | 'Name' : message[0], |
|
1047 | 'Arguments' : [].slice.call(message, 1) || '', |
|
1048 | 'Element' : element, |
|
1049 | 'Execution Time' : executionTime |
|
1050 | }); |
|
1051 | } |
|
1052 | clearTimeout(module.performance.timer); |
|
1053 | module.performance.timer = setTimeout(module.performance.display, 500); |
|
1054 | }, |
|
1055 | display: function() { |
|
1056 | var |
|
1057 | title = settings.name + ':', |
|
1058 | totalTime = 0 |
|
1059 | ; |
|
1060 | time = false; |
|
1061 | clearTimeout(module.performance.timer); |
|
1062 | $.each(performance, function(index, data) { |
|
1063 | totalTime += data['Execution Time']; |
|
1064 | }); |
|
1065 | title += ' ' + totalTime + 'ms'; |
|
1066 | if(moduleSelector) { |
|
1067 | title += ' \'' + moduleSelector + '\''; |
|
1068 | } |
|
1069 | if($allModules.length > 1) { |
|
1070 | title += ' ' + '(' + $allModules.length + ')'; |
|
1071 | } |
|
1072 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
|
1073 | console.groupCollapsed(title); |
|
1074 | if(console.table) { |
|
1075 | console.table(performance); |
|
1076 | } |
|
1077 | else { |
|
1078 | $.each(performance, function(index, data) { |
|
1079 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
|
1080 | }); |
|
1081 | } |
|
1082 | console.groupEnd(); |
|
1083 | } |
|
1084 | performance = []; |
|
1085 | } |
|
1086 | }, |
|
1087 | invoke: function(query, passedArguments, context) { |
|
1088 | var |
|
1089 | object = instance, |
|
1090 | maxDepth, |
|
1091 | found, |
|
1092 | response |
|
1093 | ; |
|
1094 | passedArguments = passedArguments || queryArguments; |
|
1095 | context = element || context; |
|
1096 | if(typeof query == 'string' && object !== undefined) { |
|
1097 | query = query.split(/[\. ]/); |
|
1098 | maxDepth = query.length - 1; |
|
1099 | $.each(query, function(depth, value) { |
|
1100 | var camelCaseValue = (depth != maxDepth) |
|
1101 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
|
1102 | : query |
|
1103 | ; |
|
1104 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
|
1105 | object = object[camelCaseValue]; |
|
1106 | } |
|
1107 | else if( object[camelCaseValue] !== undefined ) { |
|
1108 | found = object[camelCaseValue]; |
|
1109 | return false; |
|
1110 | } |
|
1111 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
|
1112 | object = object[value]; |
|
1113 | } |
|
1114 | else if( object[value] !== undefined ) { |
|
1115 | found = object[value]; |
|
1116 | return false; |
|
1117 | } |
|
1118 | else { |
|
1119 | return false; |
|
1120 | } |
|
1121 | }); |
|
1122 | } |
|
1123 | if( $.isFunction( found ) ) { |
|
1124 | response = found.apply(context, passedArguments); |
|
1125 | } |
|
1126 | else if(found !== undefined) { |
|
1127 | response = found; |
|
1128 | } |
|
1129 | if($.isArray(returnedValue)) { |
|
1130 | returnedValue.push(response); |
|
1131 | } |
|
1132 | else if(returnedValue !== undefined) { |
|
1133 | returnedValue = [returnedValue, response]; |
|
1134 | } |
|
1135 | else if(response !== undefined) { |
|
1136 | returnedValue = response; |
|
1137 | } |
|
1138 | return found; |
|
1139 | } |
|
1140 | }; |
|
1141 | if(methodInvoked) { |
|
1142 | if(instance === undefined) { |
|
1143 | module.initialize(); |
|
1144 | } |
|
1145 | module.invoke(query); |
|
1146 | } |
|
1147 | else { |
|
1148 | if(instance !== undefined) { |
|
1149 | instance.invoke('destroy'); |
|
1150 | } |
|
1151 | module.initialize(); |
|
1152 | } |
|
1153 | ||
1154 | }) |
|
1155 | ; |
|
1156 | ||
1157 | return (returnedValue !== undefined) |
|
1158 | ? returnedValue |
|
1159 | : this |
|
1160 | ; |
|
1161 | }; |
|
1162 | ||
1163 | $.fn.search.settings = { |
|
1164 | ||
1165 | name : 'Search', |
|
1166 | namespace : 'search', |
|
1167 | ||
1168 | silent : false, |
|
1169 | debug : false, |
|
1170 | verbose : false, |
|
1171 | performance : true, |
|
1172 | ||
1173 | // template to use (specified in settings.templates) |
|
1174 | type : 'standard', |
|
1175 | ||
1176 | // minimum characters required to search |
|
1177 | minCharacters : 1, |
|
1178 | ||
1179 | // whether to select first result after searching automatically |
|
1180 | selectFirstResult : false, |
|
1181 | ||
1182 | // API config |
|
1183 | apiSettings : false, |
|
1184 | ||
1185 | // object to search |
|
1186 | source : false, |
|
1187 | ||
1188 | // Whether search should query current term on focus |
|
1189 | searchOnFocus : true, |
|
1190 | ||
1191 | // fields to search |
|
1192 | searchFields : [ |
|
1193 | 'title', |
|
1194 | 'description' |
|
1195 | ], |
|
1196 | ||
1197 | // field to display in standard results template |
|
1198 | displayField : '', |
|
1199 | ||
1200 | // whether to include fuzzy results in local search |
|
1201 | searchFullText : true, |
|
1202 | ||
1203 | // whether to add events to prompt automatically |
|
1204 | automatic : true, |
|
1205 | ||
1206 | // delay before hiding menu after blur |
|
1207 | hideDelay : 0, |
|
1208 | ||
1209 | // delay before searching |
|
1210 | searchDelay : 200, |
|
1211 | ||
1212 | // maximum results returned from local |
|
1213 | maxResults : 7, |
|
1214 | ||
1215 | // whether to store lookups in local cache |
|
1216 | cache : true, |
|
1217 | ||
1218 | // whether no results errors should be shown |
|
1219 | showNoResults : true, |
|
1220 | ||
1221 | // transition settings |
|
1222 | transition : 'scale', |
|
1223 | duration : 200, |
|
1224 | easing : 'easeOutExpo', |
|
1225 | ||
1226 | // callbacks |
|
1227 | onSelect : false, |
|
1228 | onResultsAdd : false, |
|
1229 | ||
1230 | onSearchQuery : function(query){}, |
|
1231 | onResults : function(response){}, |
|
1232 | ||
1233 | onResultsOpen : function(){}, |
|
1234 | onResultsClose : function(){}, |
|
1235 | ||
1236 | className: { |
|
1237 | animating : 'animating', |
|
1238 | active : 'active', |
|
1239 | empty : 'empty', |
|
1240 | focus : 'focus', |
|
1241 | hidden : 'hidden', |
|
1242 | loading : 'loading', |
|
1243 | results : 'results', |
|
1244 | pressed : 'down' |
|
1245 | }, |
|
1246 | ||
1247 | error : { |
|
1248 | source : 'Cannot search. No source used, and Semantic API module was not included', |
|
1249 | noResults : 'Your search returned no results', |
|
1250 | logging : 'Error in debug logging, exiting.', |
|
1251 | noEndpoint : 'No search endpoint was specified', |
|
1252 | noTemplate : 'A valid template name was not specified.', |
|
1253 | serverError : 'There was an issue querying the server.', |
|
1254 | maxResults : 'Results must be an array to use maxResults setting', |
|
1255 | method : 'The method you called is not defined.' |
|
1256 | }, |
|
1257 | ||
1258 | metadata: { |
|
1259 | cache : 'cache', |
|
1260 | results : 'results', |
|
1261 | result : 'result' |
|
1262 | }, |
|
1263 | ||
1264 | regExp: { |
|
1265 | escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, |
|
1266 | beginsWith : '(?:\s|^)' |
|
1267 | }, |
|
1268 | ||
1269 | // maps api response attributes to internal representation |
|
1270 | fields: { |
|
1271 | categories : 'results', // array of categories (category view) |
|
1272 | categoryName : 'name', // name of category (category view) |
|
1273 | categoryResults : 'results', // array of results (category view) |
|
1274 | description : 'description', // result description |
|
1275 | image : 'image', // result image |
|
1276 | price : 'price', // result price |
|
1277 | results : 'results', // array of results (standard) |
|
1278 | title : 'title', // result title |
|
1279 | url : 'url', // result url |
|
1280 | action : 'action', // "view more" object name |
|
1281 | actionText : 'text', // "view more" text |
|
1282 | actionURL : 'url' // "view more" url |
|
1283 | }, |
|
1284 | ||
1285 | selector : { |
|
1286 | prompt : '.prompt', |
|
1287 | searchButton : '.search.button', |
|
1288 | results : '.results', |
|
1289 | message : '.results > .message', |
|
1290 | category : '.category', |
|
1291 | result : '.result', |
|
1292 | title : '.title, .name' |
|
1293 | }, |
|
1294 | ||
1295 | templates: { |
|
1296 | escape: function(string) { |
|
1297 | var |
|
1298 | badChars = /[&<>"'`]/g, |
|
1299 | shouldEscape = /[&<>"'`]/, |
|
1300 | escape = { |
|
1301 | "&": "&", |
|
1302 | "<": "<", |
|
1303 | ">": ">", |
|
1304 | '"': """, |
|
1305 | "'": "'", |
|
1306 | "`": "`" |
|
1307 | }, |
|
1308 | escapedChar = function(chr) { |
|
1309 | return escape[chr]; |
|
1310 | } |
|
1311 | ; |
|
1312 | if(shouldEscape.test(string)) { |
|
1313 | return string.replace(badChars, escapedChar); |
|
1314 | } |
|
1315 | return string; |
|
1316 | }, |
|
1317 | message: function(message, type) { |
|
1318 | var |
|
1319 | html = '' |
|
1320 | ; |
|
1321 | if(message !== undefined && type !== undefined) { |
|
1322 | html += '' |
|
1323 | + '<div class="message ' + type + '">' |
|
1324 | ; |
|
1325 | // message type |
|
1326 | if(type == 'empty') { |
|
1327 | html += '' |
|
1328 | + '<div class="header">No Results</div class="header">' |
|
1329 | + '<div class="description">' + message + '</div class="description">' |
|
1330 | ; |
|
1331 | } |
|
1332 | else { |
|
1333 | html += ' <div class="description">' + message + '</div>'; |
|
1334 | } |
|
1335 | html += '</div>'; |
|
1336 | } |
|
1337 | return html; |
|
1338 | }, |
|
1339 | category: function(response, fields) { |
|
1340 | var |
|
1341 | html = '', |
|
1342 | escape = $.fn.search.settings.templates.escape |
|
1343 | ; |
|
1344 | if(response[fields.categoryResults] !== undefined) { |
|
1345 | ||
1346 | // each category |
|
1347 | $.each(response[fields.categoryResults], function(index, category) { |
|
1348 | if(category[fields.results] !== undefined && category.results.length > 0) { |
|
1349 | ||
1350 | html += '<div class="category">'; |
|
1351 | ||
1352 | if(category[fields.categoryName] !== undefined) { |
|
1353 | html += '<div class="name">' + category[fields.categoryName] + '</div>'; |
|
1354 | } |
|
1355 | ||
1356 | // each item inside category |
|
1357 | $.each(category.results, function(index, result) { |
|
1358 | if(result[fields.url]) { |
|
1359 | html += '<a class="result" href="' + result[fields.url] + '">'; |
|
1360 | } |
|
1361 | else { |
|
1362 | html += '<a class="result">'; |
|
1363 | } |
|
1364 | if(result[fields.image] !== undefined) { |
|
1365 | html += '' |
|
1366 | + '<div class="image">' |
|
1367 | + ' <img src="' + result[fields.image] + '">' |
|
1368 | + '</div>' |
|
1369 | ; |
|
1370 | } |
|
1371 | html += '<div class="content">'; |
|
1372 | if(result[fields.price] !== undefined) { |
|
1373 | html += '<div class="price">' + result[fields.price] + '</div>'; |
|
1374 | } |
|
1375 | if(result[fields.title] !== undefined) { |
|
1376 | html += '<div class="title">' + result[fields.title] + '</div>'; |
|
1377 | } |
|
1378 | if(result[fields.description] !== undefined) { |
|
1379 | html += '<div class="description">' + result[fields.description] + '</div>'; |
|
1380 | } |
|
1381 | html += '' |
|
1382 | + '</div>' |
|
1383 | ; |
|
1384 | html += '</a>'; |
|
1385 | }); |
|
1386 | html += '' |
|
1387 | + '</div>' |
|
1388 | ; |
|
1389 | } |
|
1390 | }); |
|
1391 | if(response[fields.action]) { |
|
1392 | html += '' |
|
1393 | + '<a href="' + response[fields.action][fields.actionURL] + '" class="action">' |
|
1394 | + response[fields.action][fields.actionText] |
|
1395 | + '</a>'; |
|
1396 | } |
|
1397 | return html; |
|
1398 | } |
|
1399 | return false; |
|
1400 | }, |
|
1401 | standard: function(response, fields) { |
|
1402 | var |
|
1403 | html = '' |
|
1404 | ; |
|
1405 | if(response[fields.results] !== undefined) { |
|
1406 | ||
1407 | // each result |
|
1408 | $.each(response[fields.results], function(index, result) { |
|
1409 | if(result[fields.url]) { |
|
1410 | html += '<a class="result" href="' + result[fields.url] + '">'; |
|
1411 | } |
|
1412 | else { |
|
1413 | html += '<a class="result">'; |
|
1414 | } |
|
1415 | if(result[fields.image] !== undefined) { |
|
1416 | html += '' |
|
1417 | + '<div class="image">' |
|
1418 | + ' <img src="' + result[fields.image] + '">' |
|
1419 | + '</div>' |
|
1420 | ; |
|
1421 | } |
|
1422 | html += '<div class="content">'; |
|
1423 | if(result[fields.price] !== undefined) { |
|
1424 | html += '<div class="price">' + result[fields.price] + '</div>'; |
|
1425 | } |
|
1426 | if(result[fields.title] !== undefined) { |
|
1427 | html += '<div class="title">' + result[fields.title] + '</div>'; |
|
1428 | } |
|
1429 | if(result[fields.description] !== undefined) { |
|
1430 | html += '<div class="description">' + result[fields.description] + '</div>'; |
|
1431 | } |
|
1432 | html += '' |
|
1433 | + '</div>' |
|
1434 | ; |
|
1435 | html += '</a>'; |
|
1436 | }); |
|
1437 | ||
1438 | if(response[fields.action]) { |
|
1439 | html += '' |
|
1440 | + '<a href="' + response[fields.action][fields.actionURL] + '" class="action">' |
|
1441 | + response[fields.action][fields.actionText] |
|
1442 | + '</a>'; |
|
1443 | } |
|
1444 | return html; |
|
1445 | } |
|
1446 | return false; |
|
1447 | } |
|
1448 | } |
|
1449 | }; |
|
1450 | ||
1451 | })( jQuery, window, document ); |
|
1452 |