You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1251 lines
39 KiB

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