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.

1538 lines
62 KiB

  1. /*!
  2. * typeahead.js 0.11.1
  3. * https://github.com/twitter/typeahead.js
  4. * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
  5. */
  6. (function(root, factory) {
  7. if (typeof define === "function" && define.amd) {
  8. define("typeahead.js", [ "jquery" ], function(a0) {
  9. return factory(a0);
  10. });
  11. } else if (typeof exports === "object") {
  12. module.exports = factory(require("jquery"));
  13. } else {
  14. factory(jQuery);
  15. }
  16. })(this, function($) {
  17. var _ = function() {
  18. "use strict";
  19. return {
  20. isMsie: function() {
  21. return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
  22. },
  23. isBlankString: function(str) {
  24. return !str || /^\s*$/.test(str);
  25. },
  26. escapeRegExChars: function(str) {
  27. return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  28. },
  29. isString: function(obj) {
  30. return typeof obj === "string";
  31. },
  32. isNumber: function(obj) {
  33. return typeof obj === "number";
  34. },
  35. isArray: $.isArray,
  36. isFunction: $.isFunction,
  37. isObject: $.isPlainObject,
  38. isUndefined: function(obj) {
  39. return typeof obj === "undefined";
  40. },
  41. isElement: function(obj) {
  42. return !!(obj && obj.nodeType === 1);
  43. },
  44. isJQuery: function(obj) {
  45. return obj instanceof $;
  46. },
  47. toStr: function toStr(s) {
  48. return _.isUndefined(s) || s === null ? "" : s + "";
  49. },
  50. bind: $.proxy,
  51. each: function(collection, cb) {
  52. $.each(collection, reverseArgs);
  53. function reverseArgs(index, value) {
  54. return cb(value, index);
  55. }
  56. },
  57. map: $.map,
  58. filter: $.grep,
  59. every: function(obj, test) {
  60. var result = true;
  61. if (!obj) {
  62. return result;
  63. }
  64. $.each(obj, function(key, val) {
  65. if (!(result = test.call(null, val, key, obj))) {
  66. return false;
  67. }
  68. });
  69. return !!result;
  70. },
  71. some: function(obj, test) {
  72. var result = false;
  73. if (!obj) {
  74. return result;
  75. }
  76. $.each(obj, function(key, val) {
  77. if (result = test.call(null, val, key, obj)) {
  78. return false;
  79. }
  80. });
  81. return !!result;
  82. },
  83. mixin: $.extend,
  84. identity: function(x) {
  85. return x;
  86. },
  87. clone: function(obj) {
  88. return $.extend(true, {}, obj);
  89. },
  90. getIdGenerator: function() {
  91. var counter = 0;
  92. return function() {
  93. return counter++;
  94. };
  95. },
  96. templatify: function templatify(obj) {
  97. return $.isFunction(obj) ? obj : template;
  98. function template() {
  99. return String(obj);
  100. }
  101. },
  102. defer: function(fn) {
  103. setTimeout(fn, 0);
  104. },
  105. debounce: function(func, wait, immediate) {
  106. var timeout, result;
  107. return function() {
  108. var context = this, args = arguments, later, callNow;
  109. later = function() {
  110. timeout = null;
  111. if (!immediate) {
  112. result = func.apply(context, args);
  113. }
  114. };
  115. callNow = immediate && !timeout;
  116. clearTimeout(timeout);
  117. timeout = setTimeout(later, wait);
  118. if (callNow) {
  119. result = func.apply(context, args);
  120. }
  121. return result;
  122. };
  123. },
  124. throttle: function(func, wait) {
  125. var context, args, timeout, result, previous, later;
  126. previous = 0;
  127. later = function() {
  128. previous = new Date();
  129. timeout = null;
  130. result = func.apply(context, args);
  131. };
  132. return function() {
  133. var now = new Date(), remaining = wait - (now - previous);
  134. context = this;
  135. args = arguments;
  136. if (remaining <= 0) {
  137. clearTimeout(timeout);
  138. timeout = null;
  139. previous = now;
  140. result = func.apply(context, args);
  141. } else if (!timeout) {
  142. timeout = setTimeout(later, remaining);
  143. }
  144. return result;
  145. };
  146. },
  147. stringify: function(val) {
  148. return _.isString(val) ? val : JSON.stringify(val);
  149. },
  150. noop: function() {}
  151. };
  152. }();
  153. var WWW = function() {
  154. "use strict";
  155. var defaultClassNames = {
  156. wrapper: "twitter-typeahead",
  157. input: "tt-input",
  158. hint: "tt-hint",
  159. menu: "tt-menu",
  160. dataset: "tt-dataset",
  161. suggestion: "tt-suggestion",
  162. selectable: "tt-selectable",
  163. empty: "tt-empty",
  164. open: "tt-open",
  165. cursor: "tt-cursor",
  166. highlight: "tt-highlight"
  167. };
  168. return build;
  169. function build(o) {
  170. var www, classes;
  171. classes = _.mixin({}, defaultClassNames, o);
  172. www = {
  173. css: buildCss(),
  174. classes: classes,
  175. html: buildHtml(classes),
  176. selectors: buildSelectors(classes)
  177. };
  178. return {
  179. css: www.css,
  180. html: www.html,
  181. classes: www.classes,
  182. selectors: www.selectors,
  183. mixin: function(o) {
  184. _.mixin(o, www);
  185. }
  186. };
  187. }
  188. function buildHtml(c) {
  189. return {
  190. wrapper: '<span class="' + c.wrapper + '"></span>',
  191. menu: '<div class="' + c.menu + '"></div>'
  192. };
  193. }
  194. function buildSelectors(classes) {
  195. var selectors = {};
  196. _.each(classes, function(v, k) {
  197. selectors[k] = "." + v;
  198. });
  199. return selectors;
  200. }
  201. function buildCss() {
  202. var css = {
  203. wrapper: {
  204. position: "relative",
  205. display: "inline-block"
  206. },
  207. hint: {
  208. position: "absolute",
  209. top: "0",
  210. left: "0",
  211. borderColor: "transparent",
  212. boxShadow: "none",
  213. opacity: "1"
  214. },
  215. input: {
  216. position: "relative",
  217. verticalAlign: "top",
  218. backgroundColor: "transparent"
  219. },
  220. inputWithNoHint: {
  221. position: "relative",
  222. verticalAlign: "top"
  223. },
  224. menu: {
  225. position: "absolute",
  226. top: "100%",
  227. left: "0",
  228. zIndex: "100",
  229. display: "none"
  230. },
  231. ltr: {
  232. left: "0",
  233. right: "auto"
  234. },
  235. rtl: {
  236. left: "auto",
  237. right: " 0"
  238. }
  239. };
  240. if (_.isMsie()) {
  241. _.mixin(css.input, {
  242. backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
  243. });
  244. }
  245. return css;
  246. }
  247. }();
  248. var EventBus = function() {
  249. "use strict";
  250. var namespace, deprecationMap;
  251. namespace = "typeahead:";
  252. deprecationMap = {
  253. render: "rendered",
  254. cursorchange: "cursorchanged",
  255. select: "selected",
  256. autocomplete: "autocompleted"
  257. };
  258. function EventBus(o) {
  259. if (!o || !o.el) {
  260. $.error("EventBus initialized without el");
  261. }
  262. this.$el = $(o.el);
  263. }
  264. _.mixin(EventBus.prototype, {
  265. _trigger: function(type, args) {
  266. var $e;
  267. $e = $.Event(namespace + type);
  268. (args = args || []).unshift($e);
  269. this.$el.trigger.apply(this.$el, args);
  270. return $e;
  271. },
  272. before: function(type) {
  273. var args, $e;
  274. args = [].slice.call(arguments, 1);
  275. $e = this._trigger("before" + type, args);
  276. return $e.isDefaultPrevented();
  277. },
  278. trigger: function(type) {
  279. var deprecatedType;
  280. this._trigger(type, [].slice.call(arguments, 1));
  281. if (deprecatedType = deprecationMap[type]) {
  282. this._trigger(deprecatedType, [].slice.call(arguments, 1));
  283. }
  284. }
  285. });
  286. return EventBus;
  287. }();
  288. var EventEmitter = function() {
  289. "use strict";
  290. var splitter = /\s+/, nextTick = getNextTick();
  291. return {
  292. onSync: onSync,
  293. onAsync: onAsync,
  294. off: off,
  295. trigger: trigger
  296. };
  297. function on(method, types, cb, context) {
  298. var type;
  299. if (!cb) {
  300. return this;
  301. }
  302. types = types.split(splitter);
  303. cb = context ? bindContext(cb, context) : cb;
  304. this._callbacks = this._callbacks || {};
  305. while (type = types.shift()) {
  306. this._callbacks[type] = this._callbacks[type] || {
  307. sync: [],
  308. async: []
  309. };
  310. this._callbacks[type][method].push(cb);
  311. }
  312. return this;
  313. }
  314. function onAsync(types, cb, context) {
  315. return on.call(this, "async", types, cb, context);
  316. }
  317. function onSync(types, cb, context) {
  318. return on.call(this, "sync", types, cb, context);
  319. }
  320. function off(types) {
  321. var type;
  322. if (!this._callbacks) {
  323. return this;
  324. }
  325. types = types.split(splitter);
  326. while (type = types.shift()) {
  327. delete this._callbacks[type];
  328. }
  329. return this;
  330. }
  331. function trigger(types) {
  332. var type, callbacks, args, syncFlush, asyncFlush;
  333. if (!this._callbacks) {
  334. return this;
  335. }
  336. types = types.split(splitter);
  337. args = [].slice.call(arguments, 1);
  338. while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
  339. syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
  340. asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
  341. syncFlush() && nextTick(asyncFlush);
  342. }
  343. return this;
  344. }
  345. function getFlush(callbacks, context, args) {
  346. return flush;
  347. function flush() {
  348. var cancelled;
  349. for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
  350. cancelled = callbacks[i].apply(context, args) === false;
  351. }
  352. return !cancelled;
  353. }
  354. }
  355. function getNextTick() {
  356. var nextTickFn;
  357. if (window.setImmediate) {
  358. nextTickFn = function nextTickSetImmediate(fn) {
  359. setImmediate(function() {
  360. fn();
  361. });
  362. };
  363. } else {
  364. nextTickFn = function nextTickSetTimeout(fn) {
  365. setTimeout(function() {
  366. fn();
  367. }, 0);
  368. };
  369. }
  370. return nextTickFn;
  371. }
  372. function bindContext(fn, context) {
  373. return fn.bind ? fn.bind(context) : function() {
  374. fn.apply(context, [].slice.call(arguments, 0));
  375. };
  376. }
  377. }();
  378. var highlight = function(doc) {
  379. "use strict";
  380. var defaults = {
  381. node: null,
  382. pattern: null,
  383. tagName: "strong",
  384. className: null,
  385. wordsOnly: false,
  386. caseSensitive: false
  387. };
  388. return function hightlight(o) {
  389. var regex;
  390. o = _.mixin({}, defaults, o);
  391. if (!o.node || !o.pattern) {
  392. return;
  393. }
  394. o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
  395. regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
  396. traverse(o.node, hightlightTextNode);
  397. function hightlightTextNode(textNode) {
  398. var match, patternNode, wrapperNode;
  399. if (match = regex.exec(textNode.data)) {
  400. wrapperNode = doc.createElement(o.tagName);
  401. o.className && (wrapperNode.className = o.className);
  402. patternNode = textNode.splitText(match.index);
  403. patternNode.splitText(match[0].length);
  404. wrapperNode.appendChild(patternNode.cloneNode(true));
  405. textNode.parentNode.replaceChild(wrapperNode, patternNode);
  406. }
  407. return !!match;
  408. }
  409. function traverse(el, hightlightTextNode) {
  410. var childNode, TEXT_NODE_TYPE = 3;
  411. for (var i = 0; i < el.childNodes.length; i++) {
  412. childNode = el.childNodes[i];
  413. if (childNode.nodeType === TEXT_NODE_TYPE) {
  414. i += hightlightTextNode(childNode) ? 1 : 0;
  415. } else {
  416. traverse(childNode, hightlightTextNode);
  417. }
  418. }
  419. }
  420. };
  421. function getRegex(patterns, caseSensitive, wordsOnly) {
  422. var escapedPatterns = [], regexStr;
  423. for (var i = 0, len = patterns.length; i < len; i++) {
  424. escapedPatterns.push(_.escapeRegExChars(patterns[i]));
  425. }
  426. regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
  427. return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
  428. }
  429. }(window.document);
  430. var Input = function() {
  431. "use strict";
  432. var specialKeyCodeMap;
  433. specialKeyCodeMap = {
  434. 9: "tab",
  435. 27: "esc",
  436. 37: "left",
  437. 39: "right",
  438. 13: "enter",
  439. 38: "up",
  440. 40: "down"
  441. };
  442. function Input(o, www) {
  443. o = o || {};
  444. if (!o.input) {
  445. $.error("input is missing");
  446. }
  447. www.mixin(this);
  448. this.$hint = $(o.hint);
  449. this.$input = $(o.input);
  450. this.query = this.$input.val();
  451. this.queryWhenFocused = this.hasFocus() ? this.query : null;
  452. this.$overflowHelper = buildOverflowHelper(this.$input);
  453. this._checkLanguageDirection();
  454. if (this.$hint.length === 0) {
  455. this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
  456. }
  457. }
  458. Input.normalizeQuery = function(str) {
  459. return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
  460. };
  461. _.mixin(Input.prototype, EventEmitter, {
  462. _onBlur: function onBlur() {
  463. this.resetInputValue();
  464. this.trigger("blurred");
  465. },
  466. _onFocus: function onFocus() {
  467. this.queryWhenFocused = this.query;
  468. this.trigger("focused");
  469. },
  470. _onKeydown: function onKeydown($e) {
  471. var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
  472. this._managePreventDefault(keyName, $e);
  473. if (keyName && this._shouldTrigger(keyName, $e)) {
  474. this.trigger(keyName + "Keyed", $e);
  475. }
  476. },
  477. _onInput: function onInput() {
  478. this._setQuery(this.getInputValue());
  479. this.clearHintIfInvalid();
  480. this._checkLanguageDirection();
  481. },
  482. _managePreventDefault: function managePreventDefault(keyName, $e) {
  483. var preventDefault;
  484. switch (keyName) {
  485. case "up":
  486. case "down":
  487. preventDefault = !withModifier($e);
  488. break;
  489. default:
  490. preventDefault = false;
  491. }
  492. preventDefault && $e.preventDefault();
  493. },
  494. _shouldTrigger: function shouldTrigger(keyName, $e) {
  495. var trigger;
  496. switch (keyName) {
  497. case "tab":
  498. trigger = !withModifier($e);
  499. break;
  500. default:
  501. trigger = true;
  502. }
  503. return trigger;
  504. },
  505. _checkLanguageDirection: function checkLanguageDirection() {
  506. var dir = (this.$input.css("direction") || "ltr").toLowerCase();
  507. if (this.dir !== dir) {
  508. this.dir = dir;
  509. this.$hint.attr("dir", dir);
  510. this.trigger("langDirChanged", dir);
  511. }
  512. },
  513. _setQuery: function setQuery(val, silent) {
  514. var areEquivalent, hasDifferentWhitespace;
  515. areEquivalent = areQueriesEquivalent(val, this.query);
  516. hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
  517. this.query = val;
  518. if (!silent && !areEquivalent) {
  519. this.trigger("queryChanged", this.query);
  520. } else if (!silent && hasDifferentWhitespace) {
  521. this.trigger("whitespaceChanged", this.query);
  522. }
  523. },
  524. bind: function() {
  525. var that = this, onBlur, onFocus, onKeydown, onInput;
  526. onBlur = _.bind(this._onBlur, this);
  527. onFocus = _.bind(this._onFocus, this);
  528. onKeydown = _.bind(this._onKeydown, this);
  529. onInput = _.bind(this._onInput, this);
  530. this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
  531. if (!_.isMsie() || _.isMsie() > 9) {
  532. this.$input.on("input.tt", onInput);
  533. } else {
  534. this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
  535. if (specialKeyCodeMap[$e.which || $e.keyCode]) {
  536. return;
  537. }
  538. _.defer(_.bind(that._onInput, that, $e));
  539. });
  540. }
  541. return this;
  542. },
  543. focus: function focus() {
  544. this.$input.focus();
  545. },
  546. blur: function blur() {
  547. this.$input.blur();
  548. },
  549. getLangDir: function getLangDir() {
  550. return this.dir;
  551. },
  552. getQuery: function getQuery() {
  553. return this.query || "";
  554. },
  555. setQuery: function setQuery(val, silent) {
  556. this.setInputValue(val);
  557. this._setQuery(val, silent);
  558. },
  559. hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
  560. return this.query !== this.queryWhenFocused;
  561. },
  562. getInputValue: function getInputValue() {
  563. return this.$input.val();
  564. },
  565. setInputValue: function setInputValue(value) {
  566. this.$input.val(value);
  567. this.clearHintIfInvalid();
  568. this._checkLanguageDirection();
  569. },
  570. resetInputValue: function resetInputValue() {
  571. this.setInputValue(this.query);
  572. },
  573. getHint: function getHint() {
  574. return this.$hint.val();
  575. },
  576. setHint: function setHint(value) {
  577. this.$hint.val(value);
  578. },
  579. clearHint: function clearHint() {
  580. this.setHint("");
  581. },
  582. clearHintIfInvalid: function clearHintIfInvalid() {
  583. var val, hint, valIsPrefixOfHint, isValid;
  584. val = this.getInputValue();
  585. hint = this.getHint();
  586. valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
  587. isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
  588. !isValid && this.clearHint();
  589. },
  590. hasFocus: function hasFocus() {
  591. return this.$input.is(":focus");
  592. },
  593. hasOverflow: function hasOverflow() {
  594. var constraint = this.$input.width() - 2;
  595. this.$overflowHelper.text(this.getInputValue());
  596. return this.$overflowHelper.width() >= constraint;
  597. },
  598. isCursorAtEnd: function() {
  599. var valueLength, selectionStart, range;
  600. valueLength = this.$input.val().length;
  601. selectionStart = this.$input[0].selectionStart;
  602. if (_.isNumber(selectionStart)) {
  603. return selectionStart === valueLength;
  604. } else if (document.selection) {
  605. range = document.selection.createRange();
  606. range.moveStart("character", -valueLength);
  607. return valueLength === range.text.length;
  608. }
  609. return true;
  610. },
  611. destroy: function destroy() {
  612. this.$hint.off(".tt");
  613. this.$input.off(".tt");
  614. this.$overflowHelper.remove();
  615. this.$hint = this.$input = this.$overflowHelper = $("<div>");
  616. }
  617. });
  618. return Input;
  619. function buildOverflowHelper($input) {
  620. return $('<pre aria-hidden="true"></pre>').css({
  621. position: "absolute",
  622. visibility: "hidden",
  623. whiteSpace: "pre",
  624. fontFamily: $input.css("font-family"),
  625. fontSize: $input.css("font-size"),
  626. fontStyle: $input.css("font-style"),
  627. fontVariant: $input.css("font-variant"),
  628. fontWeight: $input.css("font-weight"),
  629. wordSpacing: $input.css("word-spacing"),
  630. letterSpacing: $input.css("letter-spacing"),
  631. textIndent: $input.css("text-indent"),
  632. textRendering: $input.css("text-rendering"),
  633. textTransform: $input.css("text-transform")
  634. }).insertAfter($input);
  635. }
  636. function areQueriesEquivalent(a, b) {
  637. return Input.normalizeQuery(a) === Input.normalizeQuery(b);
  638. }
  639. function withModifier($e) {
  640. return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
  641. }
  642. }();
  643. var Dataset = function() {
  644. "use strict";
  645. var keys, nameGenerator;
  646. keys = {
  647. val: "tt-selectable-display",
  648. obj: "tt-selectable-object"
  649. };
  650. nameGenerator = _.getIdGenerator();
  651. function Dataset(o, www) {
  652. o = o || {};
  653. o.templates = o.templates || {};
  654. o.templates.notFound = o.templates.notFound || o.templates.empty;
  655. if (!o.source) {
  656. $.error("missing source");
  657. }
  658. if (!o.node) {
  659. $.error("missing node");
  660. }
  661. if (o.name && !isValidName(o.name)) {
  662. $.error("invalid dataset name: " + o.name);
  663. }
  664. www.mixin(this);
  665. this.highlight = !!o.highlight;
  666. this.name = o.name || nameGenerator();
  667. this.limit = o.limit || 5;
  668. this.displayFn = getDisplayFn(o.display || o.displayKey);
  669. this.templates = getTemplates(o.templates, this.displayFn);
  670. this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
  671. this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
  672. this._resetLastSuggestion();
  673. this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
  674. }
  675. Dataset.extractData = function extractData(el) {
  676. var $el = $(el);
  677. if ($el.data(keys.obj)) {
  678. return {
  679. val: $el.data(keys.val) || "",
  680. obj: $el.data(keys.obj) || null
  681. };
  682. }
  683. return null;
  684. };
  685. _.mixin(Dataset.prototype, EventEmitter, {
  686. _overwrite: function overwrite(query, suggestions) {
  687. suggestions = suggestions || [];
  688. if (suggestions.length) {
  689. this._renderSuggestions(query, suggestions);
  690. } else if (this.async && this.templates.pending) {
  691. this._renderPending(query);
  692. } else if (!this.async && this.templates.notFound) {
  693. this._renderNotFound(query);
  694. } else {
  695. this._empty();
  696. }
  697. this.trigger("rendered", this.name, suggestions, false);
  698. },
  699. _append: function append(query, suggestions) {
  700. suggestions = suggestions || [];
  701. if (suggestions.length && this.$lastSuggestion.length) {
  702. this._appendSuggestions(query, suggestions);
  703. } else if (suggestions.length) {
  704. this._renderSuggestions(query, suggestions);
  705. } else if (!this.$lastSuggestion.length && this.templates.notFound) {
  706. this._renderNotFound(query);
  707. }
  708. this.trigger("rendered", this.name, suggestions, true);
  709. },
  710. _renderSuggestions: function renderSuggestions(query, suggestions) {
  711. var $fragment;
  712. $fragment = this._getSuggestionsFragment(query, suggestions);
  713. this.$lastSuggestion = $fragment.children().last();
  714. this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
  715. },
  716. _appendSuggestions: function appendSuggestions(query, suggestions) {
  717. var $fragment, $lastSuggestion;
  718. $fragment = this._getSuggestionsFragment(query, suggestions);
  719. $lastSuggestion = $fragment.children().last();
  720. this.$lastSuggestion.after($fragment);
  721. this.$lastSuggestion = $lastSuggestion;
  722. },
  723. _renderPending: function renderPending(query) {
  724. var template = this.templates.pending;
  725. this._resetLastSuggestion();
  726. template && this.$el.html(template({
  727. query: query,
  728. dataset: this.name
  729. }));
  730. },
  731. _renderNotFound: function renderNotFound(query) {
  732. var template = this.templates.notFound;
  733. this._resetLastSuggestion();
  734. template && this.$el.html(template({
  735. query: query,
  736. dataset: this.name
  737. }));
  738. },
  739. _empty: function empty() {
  740. this.$el.empty();
  741. this._resetLastSuggestion();
  742. },
  743. _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
  744. var that = this, fragment;
  745. fragment = document.createDocumentFragment();
  746. _.each(suggestions, function getSuggestionNode(suggestion) {
  747. var $el, context;
  748. context = that._injectQuery(query, suggestion);
  749. $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
  750. fragment.appendChild($el[0]);
  751. });
  752. this.highlight && highlight({
  753. className: this.classes.highlight,
  754. node: fragment,
  755. pattern: query
  756. });
  757. return $(fragment);
  758. },
  759. _getFooter: function getFooter(query, suggestions) {
  760. return this.templates.footer ? this.templates.footer({
  761. query: query,
  762. suggestions: suggestions,
  763. dataset: this.name
  764. }) : null;
  765. },
  766. _getHeader: function getHeader(query, suggestions) {
  767. return this.templates.header ? this.templates.header({
  768. query: query,
  769. suggestions: suggestions,
  770. dataset: this.name
  771. }) : null;
  772. },
  773. _resetLastSuggestion: function resetLastSuggestion() {
  774. this.$lastSuggestion = $();
  775. },
  776. _injectQuery: function injectQuery(query, obj) {
  777. return _.isObject(obj) ? _.mixin({
  778. _query: query
  779. }, obj) : obj;
  780. },
  781. update: function update(query) {
  782. var that = this, canceled = false, syncCalled = false, rendered = 0;
  783. this.cancel();
  784. this.cancel = function cancel() {
  785. canceled = true;
  786. that.cancel = $.noop;
  787. that.async && that.trigger("asyncCanceled", query);
  788. };
  789. this.source(query, sync, async);
  790. !syncCalled && sync([]);
  791. function sync(suggestions) {
  792. if (syncCalled) {
  793. return;
  794. }
  795. syncCalled = true;
  796. suggestions = (suggestions || []).slice(0, that.limit);
  797. rendered = suggestions.length;
  798. that._overwrite(query, suggestions);
  799. if (rendered < that.limit && that.async) {
  800. that.trigger("asyncRequested", query);
  801. }
  802. }
  803. function async(suggestions) {
  804. suggestions = suggestions || [];
  805. if (!canceled && rendered < that.limit) {
  806. that.cancel = $.noop;
  807. that._append(query, suggestions.slice(0, that.limit - rendered));
  808. rendered += suggestions.length;
  809. that.async && that.trigger("asyncReceived", query);
  810. }
  811. }
  812. },
  813. cancel: $.noop,
  814. clear: function clear() {
  815. this._empty();
  816. this.cancel();
  817. this.trigger("cleared");
  818. },
  819. isEmpty: function isEmpty() {
  820. return this.$el.is(":empty");
  821. },
  822. destroy: function destroy() {
  823. this.$el = $("<div>");
  824. }
  825. });
  826. return Dataset;
  827. function getDisplayFn(display) {
  828. display = display || _.stringify;
  829. return _.isFunction(display) ? display : displayFn;
  830. function displayFn(obj) {
  831. return obj[display];
  832. }
  833. }
  834. function getTemplates(templates, displayFn) {
  835. return {
  836. notFound: templates.notFound && _.templatify(templates.notFound),
  837. pending: templates.pending && _.templatify(templates.pending),
  838. header: templates.header && _.templatify(templates.header),
  839. footer: templates.footer && _.templatify(templates.footer),
  840. suggestion: templates.suggestion || suggestionTemplate
  841. };
  842. function suggestionTemplate(context) {
  843. return $("<div>").text(displayFn(context));
  844. }
  845. }
  846. function isValidName(str) {
  847. return /^[_a-zA-Z0-9-]+$/.test(str);
  848. }
  849. }();
  850. var Menu = function() {
  851. "use strict";
  852. function Menu(o, www) {
  853. var that = this;
  854. o = o || {};
  855. if (!o.node) {
  856. $.error("node is required");
  857. }
  858. www.mixin(this);
  859. this.$node = $(o.node);
  860. this.query = null;
  861. this.datasets = _.map(o.datasets, initializeDataset);
  862. function initializeDataset(oDataset) {
  863. var node = that.$node.find(oDataset.node).first();
  864. oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
  865. return new Dataset(oDataset, www);
  866. }
  867. }
  868. _.mixin(Menu.prototype, EventEmitter, {
  869. _onSelectableClick: function onSelectableClick($e) {
  870. this.trigger("selectableClicked", $($e.currentTarget));
  871. },
  872. _onRendered: function onRendered(type, dataset, suggestions, async) {
  873. this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
  874. this.trigger("datasetRendered", dataset, suggestions, async);
  875. },
  876. _onCleared: function onCleared() {
  877. this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
  878. this.trigger("datasetCleared");
  879. },
  880. _propagate: function propagate() {
  881. this.trigger.apply(this, arguments);
  882. },
  883. _allDatasetsEmpty: function allDatasetsEmpty() {
  884. return _.every(this.datasets, isDatasetEmpty);
  885. function isDatasetEmpty(dataset) {
  886. return dataset.isEmpty();
  887. }
  888. },
  889. _getSelectables: function getSelectables() {
  890. return this.$node.find(this.selectors.selectable);
  891. },
  892. _removeCursor: function _removeCursor() {
  893. var $selectable = this.getActiveSelectable();
  894. $selectable && $selectable.removeClass(this.classes.cursor);
  895. },
  896. _ensureVisible: function ensureVisible($el) {
  897. var elTop, elBottom, nodeScrollTop, nodeHeight;
  898. elTop = $el.position().top;
  899. elBottom = elTop + $el.outerHeight(true);
  900. nodeScrollTop = this.$node.scrollTop();
  901. nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
  902. if (elTop < 0) {
  903. this.$node.scrollTop(nodeScrollTop + elTop);
  904. } else if (nodeHeight < elBottom) {
  905. this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
  906. }
  907. },
  908. bind: function() {
  909. var that = this, onSelectableClick;
  910. onSelectableClick = _.bind(this._onSelectableClick, this);
  911. this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
  912. _.each(this.datasets, function(dataset) {
  913. dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
  914. });
  915. return this;
  916. },
  917. isOpen: function isOpen() {
  918. return this.$node.hasClass(this.classes.open);
  919. },
  920. open: function open() {
  921. this.$node.addClass(this.classes.open);
  922. },
  923. close: function close() {
  924. this.$node.removeClass(this.classes.open);
  925. this._removeCursor();
  926. },
  927. setLanguageDirection: function setLanguageDirection(dir) {
  928. this.$node.attr("dir", dir);
  929. },
  930. selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
  931. var $selectables, $oldCursor, oldIndex, newIndex;
  932. $oldCursor = this.getActiveSelectable();
  933. $selectables = this._getSelectables();
  934. oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
  935. newIndex = oldIndex + delta;
  936. newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
  937. newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
  938. return newIndex === -1 ? null : $selectables.eq(newIndex);
  939. },
  940. setCursor: function setCursor($selectable) {
  941. this._removeCursor();
  942. if ($selectable = $selectable && $selectable.first()) {
  943. $selectable.addClass(this.classes.cursor);
  944. this._ensureVisible($selectable);
  945. }
  946. },
  947. getSelectableData: function getSelectableData($el) {
  948. return $el && $el.length ? Dataset.extractData($el) : null;
  949. },
  950. getActiveSelectable: function getActiveSelectable() {
  951. var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
  952. return $selectable.length ? $selectable : null;
  953. },
  954. getTopSelectable: function getTopSelectable() {
  955. var $selectable = this._getSelectables().first();
  956. return $selectable.length ? $selectable : null;
  957. },
  958. update: function update(query) {
  959. var isValidUpdate = query !== this.query;
  960. if (isValidUpdate) {
  961. this.query = query;
  962. _.each(this.datasets, updateDataset);
  963. }
  964. return isValidUpdate;
  965. function updateDataset(dataset) {
  966. dataset.update(query);
  967. }
  968. },
  969. empty: function empty() {
  970. _.each(this.datasets, clearDataset);
  971. this.query = null;
  972. this.$node.addClass(this.classes.empty);
  973. function clearDataset(dataset) {
  974. dataset.clear();
  975. }
  976. },
  977. destroy: function destroy() {
  978. this.$node.off(".tt");
  979. this.$node = $("<div>");
  980. _.each(this.datasets, destroyDataset);
  981. function destroyDataset(dataset) {
  982. dataset.destroy();
  983. }
  984. }
  985. });
  986. return Menu;
  987. }();
  988. var DefaultMenu = function() {
  989. "use strict";
  990. var s = Menu.prototype;
  991. function DefaultMenu() {
  992. Menu.apply(this, [].slice.call(arguments, 0));
  993. }
  994. _.mixin(DefaultMenu.prototype, Menu.prototype, {
  995. open: function open() {
  996. !this._allDatasetsEmpty() && this._show();
  997. return s.open.apply(this, [].slice.call(arguments, 0));
  998. },
  999. close: function close() {
  1000. this._hide();
  1001. return s.close.apply(this, [].slice.call(arguments, 0));
  1002. },
  1003. _onRendered: function onRendered() {
  1004. if (this._allDatasetsEmpty()) {
  1005. this._hide();
  1006. } else {
  1007. this.isOpen() && this._show();
  1008. }
  1009. return s._onRendered.apply(this, [].slice.call(arguments, 0));
  1010. },
  1011. _onCleared: function onCleared() {
  1012. if (this._allDatasetsEmpty()) {
  1013. this._hide();
  1014. } else {
  1015. this.isOpen() && this._show();
  1016. }
  1017. return s._onCleared.apply(this, [].slice.call(arguments, 0));
  1018. },
  1019. setLanguageDirection: function setLanguageDirection(dir) {
  1020. this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
  1021. return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
  1022. },
  1023. _hide: function hide() {
  1024. this.$node.hide();
  1025. },
  1026. _show: function show() {
  1027. this.$node.css("display", "block");
  1028. }
  1029. });
  1030. return DefaultMenu;
  1031. }();
  1032. var Typeahead = function() {
  1033. "use strict";
  1034. function Typeahead(o, www) {
  1035. var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
  1036. o = o || {};
  1037. if (!o.input) {
  1038. $.error("missing input");
  1039. }
  1040. if (!o.menu) {
  1041. $.error("missing menu");
  1042. }
  1043. if (!o.eventBus) {
  1044. $.error("missing event bus");
  1045. }
  1046. www.mixin(this);
  1047. this.eventBus = o.eventBus;
  1048. this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
  1049. this.input = o.input;
  1050. this.menu = o.menu;
  1051. this.enabled = true;
  1052. this.active = false;
  1053. this.input.hasFocus() && this.activate();
  1054. this.dir = this.input.getLangDir();
  1055. this._hacks();
  1056. this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this);
  1057. onFocused = c(this, "activate", "open", "_onFocused");
  1058. onBlurred = c(this, "deactivate", "_onBlurred");
  1059. onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
  1060. onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
  1061. onEscKeyed = c(this, "isActive", "_onEscKeyed");
  1062. onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
  1063. onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
  1064. onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
  1065. onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
  1066. onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
  1067. onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
  1068. this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this);
  1069. }
  1070. _.mixin(Typeahead.prototype, {
  1071. _hacks: function hacks() {
  1072. var $input, $menu;
  1073. $input = this.input.$input || $("<div>");
  1074. $menu = this.menu.$node || $("<div>");
  1075. $input.on("blur.tt", function($e) {
  1076. var active, isActive, hasActive;
  1077. active = document.activeElement;
  1078. isActive = $menu.is(active);
  1079. hasActive = $menu.has(active).length > 0;
  1080. if (_.isMsie() && (isActive || hasActive)) {
  1081. $e.preventDefault();
  1082. $e.stopImmediatePropagation();
  1083. _.defer(function() {
  1084. $input.focus();
  1085. });
  1086. }
  1087. });
  1088. $menu.on("mousedown.tt", function($e) {
  1089. $e.preventDefault();
  1090. });
  1091. },
  1092. _onSelectableClicked: function onSelectableClicked(type, $el) {
  1093. this.select($el);
  1094. },
  1095. _onDatasetCleared: function onDatasetCleared() {
  1096. this._updateHint();
  1097. },
  1098. _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
  1099. this._updateHint();
  1100. this.eventBus.trigger("render", suggestions, async, dataset);
  1101. },
  1102. _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
  1103. this.eventBus.trigger("asyncrequest", query, dataset);
  1104. },
  1105. _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
  1106. this.eventBus.trigger("asynccancel", query, dataset);
  1107. },
  1108. _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
  1109. this.eventBus.trigger("asyncreceive", query, dataset);
  1110. },
  1111. _onFocused: function onFocused() {
  1112. this._minLengthMet() && this.menu.update(this.input.getQuery());
  1113. },
  1114. _onBlurred: function onBlurred() {
  1115. if (this.input.hasQueryChangedSinceLastFocus()) {
  1116. this.eventBus.trigger("change", this.input.getQuery());
  1117. }
  1118. },
  1119. _onEnterKeyed: function onEnterKeyed(type, $e) {
  1120. var $selectable;
  1121. if ($selectable = this.menu.getActiveSelectable()) {
  1122. this.select($selectable) && $e.preventDefault();
  1123. }
  1124. },
  1125. _onTabKeyed: function onTabKeyed(type, $e) {
  1126. var $selectable;
  1127. if ($selectable = this.menu.getActiveSelectable()) {
  1128. this.select($selectable) && $e.preventDefault();
  1129. } else if ($selectable = this.menu.getTopSelectable()) {
  1130. this.autocomplete($selectable) && $e.preventDefault();
  1131. }
  1132. },
  1133. _onEscKeyed: function onEscKeyed() {
  1134. this.close();
  1135. },
  1136. _onUpKeyed: function onUpKeyed() {
  1137. this.moveCursor(-1);
  1138. },
  1139. _onDownKeyed: function onDownKeyed() {
  1140. this.moveCursor(+1);
  1141. },
  1142. _onLeftKeyed: function onLeftKeyed() {
  1143. if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
  1144. this.autocomplete(this.menu.getTopSelectable());
  1145. }
  1146. },
  1147. _onRightKeyed: function onRightKeyed() {
  1148. if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
  1149. this.autocomplete(this.menu.getTopSelectable());
  1150. }
  1151. },
  1152. _onQueryChanged: function onQueryChanged(e, query) {
  1153. this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
  1154. },
  1155. _onWhitespaceChanged: function onWhitespaceChanged() {
  1156. this._updateHint();
  1157. },
  1158. _onLangDirChanged: function onLangDirChanged(e, dir) {
  1159. if (this.dir !== dir) {
  1160. this.dir = dir;
  1161. this.menu.setLanguageDirection(dir);
  1162. }
  1163. },
  1164. _openIfActive: function openIfActive() {
  1165. this.isActive() && this.open();
  1166. },
  1167. _minLengthMet: function minLengthMet(query) {
  1168. query = _.isString(query) ? query : this.input.getQuery() || "";
  1169. return query.length >= this.minLength;
  1170. },
  1171. _updateHint: function updateHint() {
  1172. var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
  1173. $selectable = this.menu.getTopSelectable();
  1174. data = this.menu.getSelectableData($selectable);
  1175. val = this.input.getInputValue();
  1176. if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
  1177. query = Input.normalizeQuery(val);
  1178. escapedQuery = _.escapeRegExChars(query);
  1179. frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
  1180. match = frontMatchRegEx.exec(data.val);
  1181. match && this.input.setHint(val + match[1]);
  1182. } else {
  1183. this.input.clearHint();
  1184. }
  1185. },
  1186. isEnabled: function isEnabled() {
  1187. return this.enabled;
  1188. },
  1189. enable: function enable() {
  1190. this.enabled = true;
  1191. },
  1192. disable: function disable() {
  1193. this.enabled = false;
  1194. },
  1195. isActive: function isActive() {
  1196. return this.active;
  1197. },
  1198. activate: function activate() {
  1199. if (this.isActive()) {
  1200. return true;
  1201. } else if (!this.isEnabled() || this.eventBus.before("active")) {
  1202. return false;
  1203. } else {
  1204. this.active = true;
  1205. this.eventBus.trigger("active");
  1206. return true;
  1207. }
  1208. },
  1209. deactivate: function deactivate() {
  1210. if (!this.isActive()) {
  1211. return true;
  1212. } else if (this.eventBus.before("idle")) {
  1213. return false;
  1214. } else {
  1215. this.active = false;
  1216. this.close();
  1217. this.eventBus.trigger("idle");
  1218. return true;
  1219. }
  1220. },
  1221. isOpen: function isOpen() {
  1222. return this.menu.isOpen();
  1223. },
  1224. open: function open() {
  1225. if (!this.isOpen() && !this.eventBus.before("open")) {
  1226. this.menu.open();
  1227. this._updateHint();
  1228. this.eventBus.trigger("open");
  1229. }
  1230. return this.isOpen();
  1231. },
  1232. close: function close() {
  1233. if (this.isOpen() && !this.eventBus.before("close")) {
  1234. this.menu.close();
  1235. this.input.clearHint();
  1236. this.input.resetInputValue();
  1237. this.eventBus.trigger("close");
  1238. }
  1239. return !this.isOpen();
  1240. },
  1241. setVal: function setVal(val) {
  1242. this.input.setQuery(_.toStr(val));
  1243. },
  1244. getVal: function getVal() {
  1245. return this.input.getQuery();
  1246. },
  1247. select: function select($selectable) {
  1248. var data = this.menu.getSelectableData($selectable);
  1249. if (data && !this.eventBus.before("select", data.obj)) {
  1250. this.input.setQuery(data.val, true);
  1251. this.eventBus.trigger("select", data.obj);
  1252. this.close();
  1253. return true;
  1254. }
  1255. return false;
  1256. },
  1257. autocomplete: function autocomplete($selectable) {
  1258. var query, data, isValid;
  1259. query = this.input.getQuery();
  1260. data = this.menu.getSelectableData($selectable);
  1261. isValid = data && query !== data.val;
  1262. if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
  1263. this.input.setQuery(data.val);
  1264. this.eventBus.trigger("autocomplete", data.obj);
  1265. return true;
  1266. }
  1267. return false;
  1268. },
  1269. moveCursor: function moveCursor(delta) {
  1270. var query, $candidate, data, payload, cancelMove;
  1271. query = this.input.getQuery();
  1272. $candidate = this.menu.selectableRelativeToCursor(delta);
  1273. data = this.menu.getSelectableData($candidate);
  1274. payload = data ? data.obj : null;
  1275. cancelMove = this._minLengthMet() && this.menu.update(query);
  1276. if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
  1277. this.menu.setCursor($candidate);
  1278. if (data) {
  1279. this.input.setInputValue(data.val);
  1280. } else {
  1281. this.input.resetInputValue();
  1282. this._updateHint();
  1283. }
  1284. this.eventBus.trigger("cursorchange", payload);
  1285. return true;
  1286. }
  1287. return false;
  1288. },
  1289. destroy: function destroy() {
  1290. this.input.destroy();
  1291. this.menu.destroy();
  1292. }
  1293. });
  1294. return Typeahead;
  1295. function c(ctx) {
  1296. var methods = [].slice.call(arguments, 1);
  1297. return function() {
  1298. var args = [].slice.call(arguments);
  1299. _.each(methods, function(method) {
  1300. return ctx[method].apply(ctx, args);
  1301. });
  1302. };
  1303. }
  1304. }();
  1305. (function() {
  1306. "use strict";
  1307. var old, keys, methods;
  1308. old = $.fn.typeahead;
  1309. keys = {
  1310. www: "tt-www",
  1311. attrs: "tt-attrs",
  1312. typeahead: "tt-typeahead"
  1313. };
  1314. methods = {
  1315. initialize: function initialize(o, datasets) {
  1316. var www;
  1317. datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
  1318. o = o || {};
  1319. www = WWW(o.classNames);
  1320. return this.each(attach);
  1321. function attach() {
  1322. var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
  1323. _.each(datasets, function(d) {
  1324. d.highlight = !!o.highlight;
  1325. });
  1326. $input = $(this);
  1327. $wrapper = $(www.html.wrapper);
  1328. $hint = $elOrNull(o.hint);
  1329. $menu = $elOrNull(o.menu);
  1330. defaultHint = o.hint !== false && !$hint;
  1331. defaultMenu = o.menu !== false && !$menu;
  1332. defaultHint && ($hint = buildHintFromInput($input, www));
  1333. defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
  1334. $hint && $hint.val("");
  1335. $input = prepInput($input, www);
  1336. if (defaultHint || defaultMenu) {
  1337. $wrapper.css(www.css.wrapper);
  1338. $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
  1339. $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
  1340. }
  1341. MenuConstructor = defaultMenu ? DefaultMenu : Menu;
  1342. eventBus = new EventBus({
  1343. el: $input
  1344. });
  1345. input = new Input({
  1346. hint: $hint,
  1347. input: $input
  1348. }, www);
  1349. menu = new MenuConstructor({
  1350. node: $menu,
  1351. datasets: datasets
  1352. }, www);
  1353. typeahead = new Typeahead({
  1354. input: input,
  1355. menu: menu,
  1356. eventBus: eventBus,
  1357. minLength: o.minLength
  1358. }, www);
  1359. $input.data(keys.www, www);
  1360. $input.data(keys.typeahead, typeahead);
  1361. }
  1362. },
  1363. isEnabled: function isEnabled() {
  1364. var enabled;
  1365. ttEach(this.first(), function(t) {
  1366. enabled = t.isEnabled();
  1367. });
  1368. return enabled;
  1369. },
  1370. enable: function enable() {
  1371. ttEach(this, function(t) {
  1372. t.enable();
  1373. });
  1374. return this;
  1375. },
  1376. disable: function disable() {
  1377. ttEach(this, function(t) {
  1378. t.disable();
  1379. });
  1380. return this;
  1381. },
  1382. isActive: function isActive() {
  1383. var active;
  1384. ttEach(this.first(), function(t) {
  1385. active = t.isActive();
  1386. });
  1387. return active;
  1388. },
  1389. activate: function activate() {
  1390. ttEach(this, function(t) {
  1391. t.activate();
  1392. });
  1393. return this;
  1394. },
  1395. deactivate: function deactivate() {
  1396. ttEach(this, function(t) {
  1397. t.deactivate();
  1398. });
  1399. return this;
  1400. },
  1401. isOpen: function isOpen() {
  1402. var open;
  1403. ttEach(this.first(), function(t) {
  1404. open = t.isOpen();
  1405. });
  1406. return open;
  1407. },
  1408. open: function open() {
  1409. ttEach(this, function(t) {
  1410. t.open();
  1411. });
  1412. return this;
  1413. },
  1414. close: function close() {
  1415. ttEach(this, function(t) {
  1416. t.close();
  1417. });
  1418. return this;
  1419. },
  1420. select: function select(el) {
  1421. var success = false, $el = $(el);
  1422. ttEach(this.first(), function(t) {
  1423. success = t.select($el);
  1424. });
  1425. return success;
  1426. },
  1427. autocomplete: function autocomplete(el) {
  1428. var success = false, $el = $(el);
  1429. ttEach(this.first(), function(t) {
  1430. success = t.autocomplete($el);
  1431. });
  1432. return success;
  1433. },
  1434. moveCursor: function moveCursoe(delta) {
  1435. var success = false;
  1436. ttEach(this.first(), function(t) {
  1437. success = t.moveCursor(delta);
  1438. });
  1439. return success;
  1440. },
  1441. val: function val(newVal) {
  1442. var query;
  1443. if (!arguments.length) {
  1444. ttEach(this.first(), function(t) {
  1445. query = t.getVal();
  1446. });
  1447. return query;
  1448. } else {
  1449. ttEach(this, function(t) {
  1450. t.setVal(newVal);
  1451. });
  1452. return this;
  1453. }
  1454. },
  1455. destroy: function destroy() {
  1456. ttEach(this, function(typeahead, $input) {
  1457. revert($input);
  1458. typeahead.destroy();
  1459. });
  1460. return this;
  1461. }
  1462. };
  1463. $.fn.typeahead = function(method) {
  1464. if (methods[method]) {
  1465. return methods[method].apply(this, [].slice.call(arguments, 1));
  1466. } else {
  1467. return methods.initialize.apply(this, arguments);
  1468. }
  1469. };
  1470. $.fn.typeahead.noConflict = function noConflict() {
  1471. $.fn.typeahead = old;
  1472. return this;
  1473. };
  1474. function ttEach($els, fn) {
  1475. $els.each(function() {
  1476. var $input = $(this), typeahead;
  1477. (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
  1478. });
  1479. }
  1480. function buildHintFromInput($input, www) {
  1481. return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
  1482. autocomplete: "off",
  1483. spellcheck: "false",
  1484. tabindex: -1
  1485. });
  1486. }
  1487. function prepInput($input, www) {
  1488. $input.data(keys.attrs, {
  1489. dir: $input.attr("dir"),
  1490. autocomplete: $input.attr("autocomplete"),
  1491. spellcheck: $input.attr("spellcheck"),
  1492. style: $input.attr("style")
  1493. });
  1494. $input.addClass(www.classes.input).attr({
  1495. autocomplete: "off",
  1496. spellcheck: false
  1497. });
  1498. try {
  1499. !$input.attr("dir") && $input.attr("dir", "auto");
  1500. } catch (e) {}
  1501. return $input;
  1502. }
  1503. function getBackgroundStyles($el) {
  1504. return {
  1505. backgroundAttachment: $el.css("background-attachment"),
  1506. backgroundClip: $el.css("background-clip"),
  1507. backgroundColor: $el.css("background-color"),
  1508. backgroundImage: $el.css("background-image"),
  1509. backgroundOrigin: $el.css("background-origin"),
  1510. backgroundPosition: $el.css("background-position"),
  1511. backgroundRepeat: $el.css("background-repeat"),
  1512. backgroundSize: $el.css("background-size")
  1513. };
  1514. }
  1515. function revert($input) {
  1516. var www, $wrapper;
  1517. www = $input.data(keys.www);
  1518. $wrapper = $input.parent().filter(www.selectors.wrapper);
  1519. _.each($input.data(keys.attrs), function(val, key) {
  1520. _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
  1521. });
  1522. $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
  1523. if ($wrapper.length) {
  1524. $input.detach().insertAfter($wrapper);
  1525. $wrapper.remove();
  1526. }
  1527. }
  1528. function $elOrNull(obj) {
  1529. var isValid, $el;
  1530. isValid = _.isJQuery(obj) || _.isElement(obj);
  1531. $el = isValid ? $(obj).first() : [];
  1532. return $el.length ? $el : null;
  1533. }
  1534. })();
  1535. });