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.

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