autocomplete-base-debug.js 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642
  1. /*
  2. YUI 3.4.1 (build 4118)
  3. Copyright 2011 Yahoo! Inc. All rights reserved.
  4. Licensed under the BSD License.
  5. http://yuilibrary.com/license/
  6. */
  7. YUI.add('autocomplete-base', function(Y) {
  8. /**
  9. * Provides automatic input completion or suggestions for text input fields and
  10. * textareas.
  11. *
  12. * @module autocomplete
  13. * @main autocomplete
  14. * @since 3.3.0
  15. */
  16. /**
  17. * <code>Y.Base</code> extension that provides core autocomplete logic (but no
  18. * UI implementation) for a text input field or textarea. Must be mixed into a
  19. * <code>Y.Base</code>-derived class to be useful.
  20. *
  21. * @submodule autocomplete-base
  22. */
  23. /**
  24. * <p>
  25. * Extension that provides core autocomplete logic (but no UI implementation)
  26. * for a text input field or textarea.
  27. * </p>
  28. *
  29. * <p>
  30. * The <code>AutoCompleteBase</code> class provides events and attributes that
  31. * abstract away core autocomplete logic and configuration, but does not provide
  32. * a widget implementation or suggestion UI. For a prepackaged autocomplete
  33. * widget, see <code>AutoCompleteList</code>.
  34. * </p>
  35. *
  36. * <p>
  37. * This extension cannot be instantiated directly, since it doesn't provide an
  38. * actual implementation. It's intended to be mixed into a
  39. * <code>Y.Base</code>-based class or widget.
  40. * </p>
  41. *
  42. * <p>
  43. * <code>Y.Widget</code>-based example:
  44. * </p>
  45. *
  46. * <pre>
  47. * YUI().use('autocomplete-base', 'widget', function (Y) {
  48. * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
  49. * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
  50. * &nbsp;&nbsp;}, {
  51. * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
  52. * &nbsp;&nbsp;});
  53. * &nbsp;
  54. * &nbsp;&nbsp;// Custom implementation code.
  55. * });
  56. * </pre>
  57. *
  58. * <p>
  59. * <code>Y.Base</code>-based example:
  60. * </p>
  61. *
  62. * <pre>
  63. * YUI().use('autocomplete-base', function (Y) {
  64. * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
  65. * &nbsp;&nbsp;&nbsp;&nbsp;initializer: function () {
  66. * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._bindUIACBase();
  67. * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._syncUIACBase();
  68. * &nbsp;&nbsp;&nbsp;&nbsp;},
  69. * &nbsp;
  70. * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
  71. * &nbsp;&nbsp;}, {
  72. * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
  73. * &nbsp;&nbsp;});
  74. * &nbsp;
  75. * &nbsp;&nbsp;// Custom implementation code.
  76. * });
  77. * </pre>
  78. *
  79. * @class AutoCompleteBase
  80. */
  81. var Escape = Y.Escape,
  82. Lang = Y.Lang,
  83. YArray = Y.Array,
  84. YObject = Y.Object,
  85. isFunction = Lang.isFunction,
  86. isString = Lang.isString,
  87. trim = Lang.trim,
  88. INVALID_VALUE = Y.Attribute.INVALID_VALUE,
  89. _FUNCTION_VALIDATOR = '_functionValidator',
  90. _SOURCE_SUCCESS = '_sourceSuccess',
  91. ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
  92. INPUT_NODE = 'inputNode',
  93. QUERY = 'query',
  94. QUERY_DELIMITER = 'queryDelimiter',
  95. REQUEST_TEMPLATE = 'requestTemplate',
  96. RESULTS = 'results',
  97. RESULT_LIST_LOCATOR = 'resultListLocator',
  98. VALUE = 'value',
  99. VALUE_CHANGE = 'valueChange',
  100. EVT_CLEAR = 'clear',
  101. EVT_QUERY = QUERY,
  102. EVT_RESULTS = RESULTS;
  103. function AutoCompleteBase() {
  104. // AOP bindings.
  105. Y.before(this._bindUIACBase, this, 'bindUI');
  106. Y.before(this._destructorACBase, this, 'destructor');
  107. Y.before(this._syncUIACBase, this, 'syncUI');
  108. // -- Public Events --------------------------------------------------------
  109. /**
  110. * Fires after the query has been completely cleared or no longer meets the
  111. * minimum query length requirement.
  112. *
  113. * @event clear
  114. * @param {EventFacade} e Event facade with the following additional
  115. * properties:
  116. *
  117. * <dl>
  118. * <dt>prevVal (String)</dt>
  119. * <dd>
  120. * Value of the query before it was cleared.
  121. * </dd>
  122. * </dl>
  123. *
  124. * @preventable _defClearFn
  125. */
  126. this.publish(EVT_CLEAR, {
  127. defaultFn: this._defClearFn
  128. });
  129. /**
  130. * Fires when the contents of the input field have changed and the input
  131. * value meets the criteria necessary to generate an autocomplete query.
  132. *
  133. * @event query
  134. * @param {EventFacade} e Event facade with the following additional
  135. * properties:
  136. *
  137. * <dl>
  138. * <dt>inputValue (String)</dt>
  139. * <dd>
  140. * Full contents of the text input field or textarea that generated
  141. * the query.
  142. * </dd>
  143. *
  144. * <dt>query (String)</dt>
  145. * <dd>
  146. * Autocomplete query. This is the string that will be used to
  147. * request completion results. It may or may not be the same as
  148. * <code>inputValue</code>.
  149. * </dd>
  150. * </dl>
  151. *
  152. * @preventable _defQueryFn
  153. */
  154. this.publish(EVT_QUERY, {
  155. defaultFn: this._defQueryFn
  156. });
  157. /**
  158. * Fires after query results are received from the <code>source</code>. If
  159. * no source has been set, this event will not fire.
  160. *
  161. * @event results
  162. * @param {EventFacade} e Event facade with the following additional
  163. * properties:
  164. *
  165. * <dl>
  166. * <dt>data (Array|Object)</dt>
  167. * <dd>
  168. * Raw, unfiltered result data (if available).
  169. * </dd>
  170. *
  171. * <dt>query (String)</dt>
  172. * <dd>
  173. * Query that generated these results.
  174. * </dd>
  175. *
  176. * <dt>results (Array)</dt>
  177. * <dd>
  178. * Array of filtered, formatted, and highlighted results. Each item in
  179. * the array is an object with the following properties:
  180. *
  181. * <dl>
  182. * <dt>display (Node|HTMLElement|String)</dt>
  183. * <dd>
  184. * Formatted result HTML suitable for display to the user. If no
  185. * custom formatter is set, this will be an HTML-escaped version of
  186. * the string in the <code>text</code> property.
  187. * </dd>
  188. *
  189. * <dt>highlighted (String)</dt>
  190. * <dd>
  191. * Highlighted (but not formatted) result text. This property will
  192. * only be set if a highlighter is in use.
  193. * </dd>
  194. *
  195. * <dt>raw (mixed)</dt>
  196. * <dd>
  197. * Raw, unformatted result in whatever form it was provided by the
  198. * <code>source</code>.
  199. * </dd>
  200. *
  201. * <dt>text (String)</dt>
  202. * <dd>
  203. * Plain text version of the result, suitable for being inserted
  204. * into the value of a text input field or textarea when the result
  205. * is selected by a user. This value is not HTML-escaped and should
  206. * not be inserted into the page using innerHTML.
  207. * </dd>
  208. * </dl>
  209. * </dd>
  210. * </dl>
  211. *
  212. * @preventable _defResultsFn
  213. */
  214. this.publish(EVT_RESULTS, {
  215. defaultFn: this._defResultsFn
  216. });
  217. }
  218. // -- Public Static Properties -------------------------------------------------
  219. AutoCompleteBase.ATTRS = {
  220. /**
  221. * Whether or not to enable the browser's built-in autocomplete
  222. * functionality for input fields.
  223. *
  224. * @attribute allowBrowserAutocomplete
  225. * @type Boolean
  226. * @default false
  227. */
  228. allowBrowserAutocomplete: {
  229. value: false
  230. },
  231. /**
  232. * When a <code>queryDelimiter</code> is set, trailing delimiters will
  233. * automatically be stripped from the input value by default when the
  234. * input node loses focus. Set this to <code>true</code> to allow trailing
  235. * delimiters.
  236. *
  237. * @attribute allowTrailingDelimiter
  238. * @type Boolean
  239. * @default false
  240. */
  241. allowTrailingDelimiter: {
  242. value: false
  243. },
  244. /**
  245. * Node to monitor for changes, which will generate <code>query</code>
  246. * events when appropriate. May be either an input field or a textarea.
  247. *
  248. * @attribute inputNode
  249. * @type Node|HTMLElement|String
  250. * @writeonce
  251. */
  252. inputNode: {
  253. setter: Y.one,
  254. writeOnce: 'initOnly'
  255. },
  256. /**
  257. * Maximum number of results to return. A value of <code>0</code> or less
  258. * will allow an unlimited number of results.
  259. *
  260. * @attribute maxResults
  261. * @type Number
  262. * @default 0
  263. */
  264. maxResults: {
  265. value: 0
  266. },
  267. /**
  268. * Minimum number of characters that must be entered before a
  269. * <code>query</code> event will be fired. A value of <code>0</code>
  270. * allows empty queries; a negative value will effectively disable all
  271. * <code>query</code> events.
  272. *
  273. * @attribute minQueryLength
  274. * @type Number
  275. * @default 1
  276. */
  277. minQueryLength: {
  278. value: 1
  279. },
  280. /**
  281. * <p>
  282. * Current query, or <code>null</code> if there is no current query.
  283. * </p>
  284. *
  285. * <p>
  286. * The query might not be the same as the current value of the input
  287. * node, both for timing reasons (due to <code>queryDelay</code>) and
  288. * because when one or more <code>queryDelimiter</code> separators are
  289. * in use, only the last portion of the delimited input string will be
  290. * used as the query value.
  291. * </p>
  292. *
  293. * @attribute query
  294. * @type String|null
  295. * @default null
  296. * @readonly
  297. */
  298. query: {
  299. readOnly: true,
  300. value: null
  301. },
  302. /**
  303. * <p>
  304. * Number of milliseconds to delay after input before triggering a
  305. * <code>query</code> event. If new input occurs before this delay is
  306. * over, the previous input event will be ignored and a new delay will
  307. * begin.
  308. * </p>
  309. *
  310. * <p>
  311. * This can be useful both to throttle queries to a remote data source
  312. * and to avoid distracting the user by showing them less relevant
  313. * results before they've paused their typing.
  314. * </p>
  315. *
  316. * @attribute queryDelay
  317. * @type Number
  318. * @default 100
  319. */
  320. queryDelay: {
  321. value: 100
  322. },
  323. /**
  324. * Query delimiter string. When a delimiter is configured, the input value
  325. * will be split on the delimiter, and only the last portion will be used in
  326. * autocomplete queries and updated when the <code>query</code> attribute is
  327. * modified.
  328. *
  329. * @attribute queryDelimiter
  330. * @type String|null
  331. * @default null
  332. */
  333. queryDelimiter: {
  334. value: null
  335. },
  336. /**
  337. * <p>
  338. * Source request template. This can be a function that accepts a query as a
  339. * parameter and returns a request string, or it can be a string containing
  340. * the placeholder "{query}", which will be replaced with the actual
  341. * URI-encoded query. In either case, the resulting string will be appended
  342. * to the request URL when the <code>source</code> attribute is set to a
  343. * remote DataSource, JSONP URL, or XHR URL (it will not be appended to YQL
  344. * URLs).
  345. * </p>
  346. *
  347. * <p>
  348. * While <code>requestTemplate</code> may be set to either a function or
  349. * a string, it will always be returned as a function that accepts a
  350. * query argument and returns a string.
  351. * </p>
  352. *
  353. * @attribute requestTemplate
  354. * @type Function|String|null
  355. * @default null
  356. */
  357. requestTemplate: {
  358. setter: '_setRequestTemplate',
  359. value: null
  360. },
  361. /**
  362. * <p>
  363. * Array of local result filter functions. If provided, each filter
  364. * will be called with two arguments when results are received: the query
  365. * and an array of result objects. See the documentation for the
  366. * <code>results</code> event for a list of the properties available on each
  367. * result object.
  368. * </p>
  369. *
  370. * <p>
  371. * Each filter is expected to return a filtered or modified version of the
  372. * results array, which will then be passed on to subsequent filters, then
  373. * the <code>resultHighlighter</code> function (if set), then the
  374. * <code>resultFormatter</code> function (if set), and finally to
  375. * subscribers to the <code>results</code> event.
  376. * </p>
  377. *
  378. * <p>
  379. * If no <code>source</code> is set, result filters will not be called.
  380. * </p>
  381. *
  382. * <p>
  383. * Prepackaged result filters provided by the autocomplete-filters and
  384. * autocomplete-filters-accentfold modules can be used by specifying the
  385. * filter name as a string, such as <code>'phraseMatch'</code> (assuming
  386. * the necessary filters module is loaded).
  387. * </p>
  388. *
  389. * @attribute resultFilters
  390. * @type Array
  391. * @default []
  392. */
  393. resultFilters: {
  394. setter: '_setResultFilters',
  395. value: []
  396. },
  397. /**
  398. * <p>
  399. * Function which will be used to format results. If provided, this function
  400. * will be called with two arguments after results have been received and
  401. * filtered: the query and an array of result objects. The formatter is
  402. * expected to return an array of HTML strings or Node instances containing
  403. * the desired HTML for each result.
  404. * </p>
  405. *
  406. * <p>
  407. * See the documentation for the <code>results</code> event for a list of
  408. * the properties available on each result object.
  409. * </p>
  410. *
  411. * <p>
  412. * If no <code>source</code> is set, the formatter will not be called.
  413. * </p>
  414. *
  415. * @attribute resultFormatter
  416. * @type Function|null
  417. */
  418. resultFormatter: {
  419. validator: _FUNCTION_VALIDATOR
  420. },
  421. /**
  422. * <p>
  423. * Function which will be used to highlight results. If provided, this
  424. * function will be called with two arguments after results have been
  425. * received and filtered: the query and an array of filtered result objects.
  426. * The highlighter is expected to return an array of highlighted result
  427. * text in the form of HTML strings.
  428. * </p>
  429. *
  430. * <p>
  431. * See the documentation for the <code>results</code> event for a list of
  432. * the properties available on each result object.
  433. * </p>
  434. *
  435. * <p>
  436. * If no <code>source</code> is set, the highlighter will not be called.
  437. * </p>
  438. *
  439. * @attribute resultHighlighter
  440. * @type Function|null
  441. */
  442. resultHighlighter: {
  443. setter: '_setResultHighlighter'
  444. },
  445. /**
  446. * <p>
  447. * Locator that should be used to extract an array of results from a
  448. * non-array response.
  449. * </p>
  450. *
  451. * <p>
  452. * By default, no locator is applied, and all responses are assumed to be
  453. * arrays by default. If all responses are already arrays, you don't need to
  454. * define a locator.
  455. * </p>
  456. *
  457. * <p>
  458. * The locator may be either a function (which will receive the raw response
  459. * as an argument and must return an array) or a string representing an
  460. * object path, such as "foo.bar.baz" (which would return the value of
  461. * <code>result.foo.bar.baz</code> if the response is an object).
  462. * </p>
  463. *
  464. * <p>
  465. * While <code>resultListLocator</code> may be set to either a function or a
  466. * string, it will always be returned as a function that accepts a response
  467. * argument and returns an array.
  468. * </p>
  469. *
  470. * @attribute resultListLocator
  471. * @type Function|String|null
  472. */
  473. resultListLocator: {
  474. setter: '_setLocator'
  475. },
  476. /**
  477. * Current results, or an empty array if there are no results.
  478. *
  479. * @attribute results
  480. * @type Array
  481. * @default []
  482. * @readonly
  483. */
  484. results: {
  485. readOnly: true,
  486. value: []
  487. },
  488. /**
  489. * <p>
  490. * Locator that should be used to extract a plain text string from a
  491. * non-string result item. The resulting text value will typically be the
  492. * value that ends up being inserted into an input field or textarea when
  493. * the user of an autocomplete implementation selects a result.
  494. * </p>
  495. *
  496. * <p>
  497. * By default, no locator is applied, and all results are assumed to be
  498. * plain text strings. If all results are already plain text strings, you
  499. * don't need to define a locator.
  500. * </p>
  501. *
  502. * <p>
  503. * The locator may be either a function (which will receive the raw result
  504. * as an argument and must return a string) or a string representing an
  505. * object path, such as "foo.bar.baz" (which would return the value of
  506. * <code>result.foo.bar.baz</code> if the result is an object).
  507. * </p>
  508. *
  509. * <p>
  510. * While <code>resultTextLocator</code> may be set to either a function or a
  511. * string, it will always be returned as a function that accepts a result
  512. * argument and returns a string.
  513. * </p>
  514. *
  515. * @attribute resultTextLocator
  516. * @type Function|String|null
  517. */
  518. resultTextLocator: {
  519. setter: '_setLocator'
  520. },
  521. /**
  522. * <p>
  523. * Source for autocomplete results. The following source types are
  524. * supported:
  525. * </p>
  526. *
  527. * <dl>
  528. * <dt>Array</dt>
  529. * <dd>
  530. * <p>
  531. * <i>Example:</i> <code>['first result', 'second result', 'etc']</code>
  532. * </p>
  533. *
  534. * <p>
  535. * The full array will be provided to any configured filters for each
  536. * query. This is an easy way to create a fully client-side autocomplete
  537. * implementation.
  538. * </p>
  539. * </dd>
  540. *
  541. * <dt>DataSource</dt>
  542. * <dd>
  543. * <p>
  544. * A <code>DataSource</code> instance or other object that provides a
  545. * DataSource-like <code>sendRequest</code> method. See the
  546. * <code>DataSource</code> documentation for details.
  547. * </p>
  548. * </dd>
  549. *
  550. * <dt>Function</dt>
  551. * <dd>
  552. * <p>
  553. * <i>Example (synchronous):</i> <code>function (query) { return ['foo', 'bar']; }</code><br>
  554. <i>Example (async):</i> <code>function (query, callback) { callback(['foo', 'bar']); }</code>
  555. * </p>
  556. *
  557. * <p>
  558. * A function source will be called with the current query and a
  559. * callback function as parameters, and should either return an array of
  560. * results (for synchronous operation) or return nothing and pass an
  561. * array of results to the provided callback (for asynchronous
  562. * operation).
  563. * </p>
  564. * </dd>
  565. *
  566. * <dt>Object</dt>
  567. * <dd>
  568. * <p>
  569. * <i>Example:</i> <code>{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}</code>
  570. * </p>
  571. *
  572. * <p>
  573. * An object will be treated as a query hashmap. If a property on the
  574. * object matches the current query, the value of that property will be
  575. * used as the response.
  576. * </p>
  577. *
  578. * <p>
  579. * The response is assumed to be an array of results by default. If the
  580. * response is not an array, provide a <code>resultListLocator</code> to
  581. * process the response and return an array.
  582. * </p>
  583. * </dd>
  584. * </dl>
  585. *
  586. * <p>
  587. * If the optional <code>autocomplete-sources</code> module is loaded, then
  588. * the following additional source types will be supported as well:
  589. * </p>
  590. *
  591. * <dl>
  592. * <dt>&lt;select&gt; Node</dt>
  593. * <dd>
  594. * <p>
  595. * You may provide a YUI Node instance wrapping a &lt;select&gt;
  596. * element, and the options in the list will be used as results. You
  597. * will also need to specify a <code>resultTextLocator</code> of 'text'
  598. * or 'value', depending on what you want to use as the text of the
  599. * result.
  600. * </p>
  601. *
  602. * <p>
  603. * Each result will be an object with the following properties:
  604. * </p>
  605. *
  606. * <dl>
  607. * <dt>html (String)</dt>
  608. * <dd>
  609. * <p>HTML content of the &lt;option&gt; element.</p>
  610. * </dd>
  611. *
  612. * <dt>index (Number)</dt>
  613. * <dd>
  614. * <p>Index of the &lt;option&gt; element in the list.</p>
  615. * </dd>
  616. *
  617. * <dt>node (Y.Node)</dt>
  618. * <dd>
  619. * <p>Node instance referring to the original &lt;option&gt; element.</p>
  620. * </dd>
  621. *
  622. * <dt>selected (Boolean)</dt>
  623. * <dd>
  624. * <p>Whether or not this item is currently selected in the
  625. * &lt;select&gt; list.</p>
  626. * </dd>
  627. *
  628. * <dt>text (String)</dt>
  629. * <dd>
  630. * <p>Text content of the &lt;option&gt; element.</p>
  631. * </dd>
  632. *
  633. * <dt>value (String)</dt>
  634. * <dd>
  635. * <p>Value of the &lt;option&gt; element.</p>
  636. * </dd>
  637. * </dl>
  638. * </dd>
  639. *
  640. * <dt>String (JSONP URL)</dt>
  641. * <dd>
  642. * <p>
  643. * <i>Example:</i> <code>'http://example.com/search?q={query}&callback={callback}'</code>
  644. * </p>
  645. *
  646. * <p>
  647. * If a URL with a <code>{callback}</code> placeholder is provided, it
  648. * will be used to make a JSONP request. The <code>{query}</code>
  649. * placeholder will be replaced with the current query, and the
  650. * <code>{callback}</code> placeholder will be replaced with an
  651. * internally-generated JSONP callback name. Both placeholders must
  652. * appear in the URL, or the request will fail. An optional
  653. * <code>{maxResults}</code> placeholder may also be provided, and will
  654. * be replaced with the value of the maxResults attribute (or 1000 if
  655. * the maxResults attribute is 0 or less).
  656. * </p>
  657. *
  658. * <p>
  659. * The response is assumed to be an array of results by default. If the
  660. * response is not an array, provide a <code>resultListLocator</code> to
  661. * process the response and return an array.
  662. * </p>
  663. *
  664. * <p>
  665. * <strong>The <code>jsonp</code> module must be loaded in order for
  666. * JSONP URL sources to work.</strong> If the <code>jsonp</code> module
  667. * is not already loaded, it will be loaded on demand if possible.
  668. * </p>
  669. * </dd>
  670. *
  671. * <dt>String (XHR URL)</dt>
  672. * <dd>
  673. * <p>
  674. * <i>Example:</i> <code>'http://example.com/search?q={query}'</code>
  675. * </p>
  676. *
  677. * <p>
  678. * If a URL without a <code>{callback}</code> placeholder is provided,
  679. * it will be used to make a same-origin XHR request. The
  680. * <code>{query}</code> placeholder will be replaced with the current
  681. * query. An optional <code>{maxResults}</code> placeholder may also be
  682. * provided, and will be replaced with the value of the maxResults
  683. * attribute (or 1000 if the maxResults attribute is 0 or less).
  684. * </p>
  685. *
  686. * <p>
  687. * The response is assumed to be a JSON array of results by default. If
  688. * the response is a JSON object and not an array, provide a
  689. * <code>resultListLocator</code> to process the response and return an
  690. * array. If the response is in some form other than JSON, you will
  691. * need to use a custom DataSource instance as the source.
  692. * </p>
  693. *
  694. * <p>
  695. * <strong>The <code>io-base</code> and <code>json-parse</code> modules
  696. * must be loaded in order for XHR URL sources to work.</strong> If
  697. * these modules are not already loaded, they will be loaded on demand
  698. * if possible.
  699. * </p>
  700. * </dd>
  701. *
  702. * <dt>String (YQL query)</dt>
  703. * <dd>
  704. * <p>
  705. * <i>Example:</i> <code>'select * from search.suggest where query="{query}"'</code>
  706. * </p>
  707. *
  708. * <p>
  709. * If a YQL query is provided, it will be used to make a YQL request.
  710. * The <code>{query}</code> placeholder will be replaced with the
  711. * current autocomplete query. This placeholder must appear in the YQL
  712. * query, or the request will fail. An optional
  713. * <code>{maxResults}</code> placeholder may also be provided, and will
  714. * be replaced with the value of the maxResults attribute (or 1000 if
  715. * the maxResults attribute is 0 or less).
  716. * </p>
  717. *
  718. * <p>
  719. * <strong>The <code>yql</code> module must be loaded in order for YQL
  720. * sources to work.</strong> If the <code>yql</code> module is not
  721. * already loaded, it will be loaded on demand if possible.
  722. * </p>
  723. * </dd>
  724. * </dl>
  725. *
  726. * <p>
  727. * As an alternative to providing a source, you could simply listen for
  728. * <code>query</code> events and handle them any way you see fit. Providing
  729. * a source is optional, but will usually be simpler.
  730. * </p>
  731. *
  732. * @attribute source
  733. * @type Array|DataSource|Function|Node|Object|String|null
  734. */
  735. source: {
  736. setter: '_setSource'
  737. },
  738. /**
  739. * <p>
  740. * May be used to force a specific source type, overriding the automatic
  741. * source type detection. It should almost never be necessary to do this,
  742. * but as they taught us in the Boy Scouts, one should always be prepared,
  743. * so it's here if you need it. Be warned that if you set this attribute and
  744. * something breaks, it's your own fault.
  745. * </p>
  746. *
  747. * <p>
  748. * Supported <code>sourceType</code> values are: 'array', 'datasource',
  749. * 'function', and 'object'.
  750. * </p>
  751. *
  752. * <p>
  753. * If the <code>autocomplete-sources</code> module is loaded, the following
  754. * additional source types are supported: 'io', 'jsonp', 'select',
  755. * 'string', 'yql'
  756. * </p>
  757. *
  758. * @attribute sourceType
  759. * @type String
  760. */
  761. sourceType: {
  762. value: null
  763. },
  764. /**
  765. * If the <code>inputNode</code> specified at instantiation time has a
  766. * <code>node-tokeninput</code> plugin attached to it, this attribute will
  767. * be a reference to the <code>Y.Plugin.TokenInput</code> instance.
  768. *
  769. * @attribute tokenInput
  770. * @type Plugin.TokenInput
  771. * @readonly
  772. */
  773. tokenInput: {
  774. readOnly: true
  775. },
  776. /**
  777. * Current value of the input node.
  778. *
  779. * @attribute value
  780. * @type String
  781. * @default ''
  782. */
  783. value: {
  784. // Why duplicate this._inputNode.get('value')? Because we need a
  785. // reliable way to track the source of value changes. We want to perform
  786. // completion when the user changes the value, but not when we change
  787. // the value.
  788. value: ''
  789. }
  790. };
  791. AutoCompleteBase.CSS_PREFIX = 'ac';
  792. AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
  793. /**
  794. * Mapping of built-in source types to their setter functions. DataSource
  795. * instances and DataSource-like objects are handled natively, so are not
  796. * mapped here.
  797. *
  798. * @property SOURCE_TYPES
  799. * @type {Object}
  800. * @static
  801. */
  802. AutoCompleteBase.SOURCE_TYPES = {
  803. array : '_createArraySource',
  804. 'function': '_createFunctionSource',
  805. object : '_createObjectSource'
  806. };
  807. AutoCompleteBase.prototype = {
  808. // -- Public Prototype Methods ---------------------------------------------
  809. /**
  810. * <p>
  811. * Sends a request to the configured source. If no source is configured,
  812. * this method won't do anything.
  813. * </p>
  814. *
  815. * <p>
  816. * Usually there's no reason to call this method manually; it will be
  817. * called automatically when user input causes a <code>query</code> event to
  818. * be fired. The only time you'll need to call this method manually is if
  819. * you want to force a request to be sent when no user input has occurred.
  820. * </p>
  821. *
  822. * @method sendRequest
  823. * @param {String} query (optional) Query to send. If specified, the
  824. * <code>query</code> attribute will be set to this query. If not
  825. * specified, the current value of the <code>query</code> attribute will
  826. * be used.
  827. * @param {Function} requestTemplate (optional) Request template function.
  828. * If not specified, the current value of the <code>requestTemplate</code>
  829. * attribute will be used.
  830. * @chainable
  831. */
  832. sendRequest: function (query, requestTemplate) {
  833. var request,
  834. source = this.get('source');
  835. if (query || query === '') {
  836. this._set(QUERY, query);
  837. } else {
  838. query = this.get(QUERY) || '';
  839. }
  840. if (source) {
  841. if (!requestTemplate) {
  842. requestTemplate = this.get(REQUEST_TEMPLATE);
  843. }
  844. request = requestTemplate ?
  845. requestTemplate.call(this, query) : query;
  846. Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
  847. source.sendRequest({
  848. query : query,
  849. request: request,
  850. callback: {
  851. success: Y.bind(this._onResponse, this, query)
  852. }
  853. });
  854. }
  855. return this;
  856. },
  857. // -- Protected Lifecycle Methods ------------------------------------------
  858. /**
  859. * Attaches event listeners and behaviors.
  860. *
  861. * @method _bindUIACBase
  862. * @protected
  863. */
  864. _bindUIACBase: function () {
  865. var inputNode = this.get(INPUT_NODE),
  866. tokenInput = inputNode && inputNode.tokenInput;
  867. // If the inputNode has a node-tokeninput plugin attached, bind to the
  868. // plugin's inputNode instead.
  869. if (tokenInput) {
  870. inputNode = tokenInput.get(INPUT_NODE);
  871. this._set('tokenInput', tokenInput);
  872. }
  873. if (!inputNode) {
  874. Y.error('No inputNode specified.');
  875. return;
  876. }
  877. this._inputNode = inputNode;
  878. this._acBaseEvents = new Y.EventHandle([
  879. // This is the valueChange event on the inputNode, provided by the
  880. // event-valuechange module, not our own valueChange.
  881. inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
  882. inputNode.on('blur', this._onInputBlur, this),
  883. this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
  884. this.after('sourceTypeChange', this._afterSourceTypeChange),
  885. this.after(VALUE_CHANGE, this._afterValueChange)
  886. ]);
  887. },
  888. /**
  889. * Detaches AutoCompleteBase event listeners.
  890. *
  891. * @method _destructorACBase
  892. * @protected
  893. */
  894. _destructorACBase: function () {
  895. this._acBaseEvents.detach();
  896. },
  897. /**
  898. * Synchronizes the UI state of the <code>inputNode</code>.
  899. *
  900. * @method _syncUIACBase
  901. * @protected
  902. */
  903. _syncUIACBase: function () {
  904. this._syncBrowserAutocomplete();
  905. this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
  906. },
  907. // -- Protected Prototype Methods ------------------------------------------
  908. /**
  909. * Creates a DataSource-like object that simply returns the specified array
  910. * as a response. See the <code>source</code> attribute for more details.
  911. *
  912. * @method _createArraySource
  913. * @param {Array} source
  914. * @return {Object} DataSource-like object.
  915. * @protected
  916. */
  917. _createArraySource: function (source) {
  918. var that = this;
  919. return {
  920. type: 'array',
  921. sendRequest: function (request) {
  922. that[_SOURCE_SUCCESS](source.concat(), request);
  923. }
  924. };
  925. },
  926. /**
  927. * Creates a DataSource-like object that passes the query to a
  928. * custom-defined function, which is expected to call the provided callback
  929. * with an array of results. See the <code>source</code> attribute for more
  930. * details.
  931. *
  932. * @method _createFunctionSource
  933. * @param {Function} source Function that accepts a query and a callback as
  934. * parameters, and calls the callback with an array of results.
  935. * @return {Object} DataSource-like object.
  936. * @protected
  937. */
  938. _createFunctionSource: function (source) {
  939. var that = this;
  940. return {
  941. type: 'function',
  942. sendRequest: function (request) {
  943. var value;
  944. function afterResults(results) {
  945. that[_SOURCE_SUCCESS](results || [], request);
  946. }
  947. // Allow both synchronous and asynchronous functions. If we get
  948. // a truthy return value, assume the function is synchronous.
  949. if ((value = source(request.query, afterResults))) {
  950. afterResults(value);
  951. }
  952. }
  953. };
  954. },
  955. /**
  956. * Creates a DataSource-like object that looks up queries as properties on
  957. * the specified object, and returns the found value (if any) as a response.
  958. * See the <code>source</code> attribute for more details.
  959. *
  960. * @method _createObjectSource
  961. * @param {Object} source
  962. * @return {Object} DataSource-like object.
  963. * @protected
  964. */
  965. _createObjectSource: function (source) {
  966. var that = this;
  967. return {
  968. type: 'object',
  969. sendRequest: function (request) {
  970. var query = request.query;
  971. that[_SOURCE_SUCCESS](
  972. YObject.owns(source, query) ? source[query] : [],
  973. request
  974. );
  975. }
  976. };
  977. },
  978. /**
  979. * Returns <code>true</code> if <i>value</i> is either a function or
  980. * <code>null</code>.
  981. *
  982. * @method _functionValidator
  983. * @param {Function|null} value Value to validate.
  984. * @protected
  985. */
  986. _functionValidator: function (value) {
  987. return value === null || isFunction(value);
  988. },
  989. /**
  990. * Faster and safer alternative to Y.Object.getValue(). Doesn't bother
  991. * casting the path to an array (since we already know it's an array) and
  992. * doesn't throw an error if a value in the middle of the object hierarchy
  993. * is neither <code>undefined</code> nor an object.
  994. *
  995. * @method _getObjectValue
  996. * @param {Object} obj
  997. * @param {Array} path
  998. * @return {mixed} Located value, or <code>undefined</code> if the value was
  999. * not found at the specified path.
  1000. * @protected
  1001. */
  1002. _getObjectValue: function (obj, path) {
  1003. if (!obj) {
  1004. return;
  1005. }
  1006. for (var i = 0, len = path.length; obj && i < len; i++) {
  1007. obj = obj[path[i]];
  1008. }
  1009. return obj;
  1010. },
  1011. /**
  1012. * Parses result responses, performs filtering and highlighting, and fires
  1013. * the <code>results</code> event.
  1014. *
  1015. * @method _parseResponse
  1016. * @param {String} query Query that generated these results.
  1017. * @param {Object} response Response containing results.
  1018. * @param {Object} data Raw response data.
  1019. * @protected
  1020. */
  1021. _parseResponse: function (query, response, data) {
  1022. var facade = {
  1023. data : data,
  1024. query : query,
  1025. results: []
  1026. },
  1027. listLocator = this.get(RESULT_LIST_LOCATOR),
  1028. results = [],
  1029. unfiltered = response && response.results,
  1030. filters,
  1031. formatted,
  1032. formatter,
  1033. highlighted,
  1034. highlighter,
  1035. i,
  1036. len,
  1037. maxResults,
  1038. result,
  1039. text,
  1040. textLocator;
  1041. if (unfiltered && listLocator) {
  1042. unfiltered = listLocator.call(this, unfiltered);
  1043. }
  1044. if (unfiltered && unfiltered.length) {
  1045. filters = this.get('resultFilters');
  1046. textLocator = this.get('resultTextLocator');
  1047. // Create a lightweight result object for each result to make them
  1048. // easier to work with. The various properties on the object
  1049. // represent different formats of the result, and will be populated
  1050. // as we go.
  1051. for (i = 0, len = unfiltered.length; i < len; ++i) {
  1052. result = unfiltered[i];
  1053. text = textLocator ?
  1054. textLocator.call(this, result) :
  1055. result.toString();
  1056. results.push({
  1057. display: Escape.html(text),
  1058. raw : result,
  1059. text : text
  1060. });
  1061. }
  1062. // Run the results through all configured result filters. Each
  1063. // filter returns an array of (potentially fewer) result objects,
  1064. // which is then passed to the next filter, and so on.
  1065. for (i = 0, len = filters.length; i < len; ++i) {
  1066. results = filters[i].call(this, query, results.concat());
  1067. if (!results) {
  1068. Y.log("Filter didn't return anything.", 'warn', 'autocomplete-base');
  1069. return;
  1070. }
  1071. if (!results.length) {
  1072. break;
  1073. }
  1074. }
  1075. if (results.length) {
  1076. formatter = this.get('resultFormatter');
  1077. highlighter = this.get('resultHighlighter');
  1078. maxResults = this.get('maxResults');
  1079. // If maxResults is set and greater than 0, limit the number of
  1080. // results.
  1081. if (maxResults && maxResults > 0 &&
  1082. results.length > maxResults) {
  1083. results.length = maxResults;
  1084. }
  1085. // Run the results through the configured highlighter (if any).
  1086. // The highlighter returns an array of highlighted strings (not
  1087. // an array of result objects), and these strings are then added
  1088. // to each result object.
  1089. if (highlighter) {
  1090. highlighted = highlighter.call(this, query,
  1091. results.concat());
  1092. if (!highlighted) {
  1093. Y.log("Highlighter didn't return anything.", 'warn', 'autocomplete-base');
  1094. return;
  1095. }
  1096. for (i = 0, len = highlighted.length; i < len; ++i) {
  1097. result = results[i];
  1098. result.highlighted = highlighted[i];
  1099. result.display = result.highlighted;
  1100. }
  1101. }
  1102. // Run the results through the configured formatter (if any) to
  1103. // produce the final formatted results. The formatter returns an
  1104. // array of strings or Node instances (not an array of result
  1105. // objects), and these strings/Nodes are then added to each
  1106. // result object.
  1107. if (formatter) {
  1108. formatted = formatter.call(this, query, results.concat());
  1109. if (!formatted) {
  1110. Y.log("Formatter didn't return anything.", 'warn', 'autocomplete-base');
  1111. return;
  1112. }
  1113. for (i = 0, len = formatted.length; i < len; ++i) {
  1114. results[i].display = formatted[i];
  1115. }
  1116. }
  1117. }
  1118. }
  1119. facade.results = results;
  1120. this.fire(EVT_RESULTS, facade);
  1121. },
  1122. /**
  1123. * <p>
  1124. * Returns the query portion of the specified input value, or
  1125. * <code>null</code> if there is no suitable query within the input value.
  1126. * </p>
  1127. *
  1128. * <p>
  1129. * If a query delimiter is defined, the query will be the last delimited
  1130. * part of of the string.
  1131. * </p>
  1132. *
  1133. * @method _parseValue
  1134. * @param {String} value Input value from which to extract the query.
  1135. * @return {String|null} query
  1136. * @protected
  1137. */
  1138. _parseValue: function (value) {
  1139. var delim = this.get(QUERY_DELIMITER);
  1140. if (delim) {
  1141. value = value.split(delim);
  1142. value = value[value.length - 1];
  1143. }
  1144. return Lang.trimLeft(value);
  1145. },
  1146. /**
  1147. * Setter for locator attributes.
  1148. *
  1149. * @method _setLocator
  1150. * @param {Function|String|null} locator
  1151. * @return {Function|null}
  1152. * @protected
  1153. */
  1154. _setLocator: function (locator) {
  1155. if (this[_FUNCTION_VALIDATOR](locator)) {
  1156. return locator;
  1157. }
  1158. var that = this;
  1159. locator = locator.toString().split('.');
  1160. return function (result) {
  1161. return result && that._getObjectValue(result, locator);
  1162. };
  1163. },
  1164. /**
  1165. * Setter for the <code>requestTemplate</code> attribute.
  1166. *
  1167. * @method _setRequestTemplate
  1168. * @param {Function|String|null} template
  1169. * @return {Function|null}
  1170. * @protected
  1171. */
  1172. _setRequestTemplate: function (template) {
  1173. if (this[_FUNCTION_VALIDATOR](template)) {
  1174. return template;
  1175. }
  1176. template = template.toString();
  1177. return function (query) {
  1178. return Lang.sub(template, {query: encodeURIComponent(query)});
  1179. };
  1180. },
  1181. /**
  1182. * Setter for the <code>resultFilters</code> attribute.
  1183. *
  1184. * @method _setResultFilters
  1185. * @param {Array|Function|String|null} filters <code>null</code>, a filter
  1186. * function, an array of filter functions, or a string or array of strings
  1187. * representing the names of methods on
  1188. * <code>Y.AutoCompleteFilters</code>.
  1189. * @return {Array} Array of filter functions (empty if <i>filters</i> is
  1190. * <code>null</code>).
  1191. * @protected
  1192. */
  1193. _setResultFilters: function (filters) {
  1194. var acFilters, getFilterFunction;
  1195. if (filters === null) {
  1196. return [];
  1197. }
  1198. acFilters = Y.AutoCompleteFilters;
  1199. getFilterFunction = function (filter) {
  1200. if (isFunction(filter)) {
  1201. return filter;
  1202. }
  1203. if (isString(filter) && acFilters &&
  1204. isFunction(acFilters[filter])) {
  1205. return acFilters[filter];
  1206. }
  1207. return false;
  1208. };
  1209. if (Lang.isArray(filters)) {
  1210. filters = YArray.map(filters, getFilterFunction);
  1211. return YArray.every(filters, function (f) { return !!f; }) ?
  1212. filters : INVALID_VALUE;
  1213. } else {
  1214. filters = getFilterFunction(filters);
  1215. return filters ? [filters] : INVALID_VALUE;
  1216. }
  1217. },
  1218. /**
  1219. * Setter for the <code>resultHighlighter</code> attribute.
  1220. *
  1221. * @method _setResultHighlighter
  1222. * @param {Function|String|null} highlighter <code>null</code>, a
  1223. * highlighter function, or a string representing the name of a method on
  1224. * <code>Y.AutoCompleteHighlighters</code>.
  1225. * @return {Function|null}
  1226. * @protected
  1227. */
  1228. _setResultHighlighter: function (highlighter) {
  1229. var acHighlighters;
  1230. if (this._functionValidator(highlighter)) {
  1231. return highlighter;
  1232. }
  1233. acHighlighters = Y.AutoCompleteHighlighters;
  1234. if (isString(highlighter) && acHighlighters &&
  1235. isFunction(acHighlighters[highlighter])) {
  1236. return acHighlighters[highlighter];
  1237. }
  1238. return INVALID_VALUE;
  1239. },
  1240. /**
  1241. * Setter for the <code>source</code> attribute. Returns a DataSource or
  1242. * a DataSource-like object depending on the type of <i>source</i> and/or
  1243. * the value of the <code>sourceType</code> attribute.
  1244. *
  1245. * @method _setSource
  1246. * @param {mixed} source AutoComplete source. See the <code>source</code>
  1247. * attribute for details.
  1248. * @return {DataSource|Object}
  1249. * @protected
  1250. */
  1251. _setSource: function (source) {
  1252. var sourceType = this.get('sourceType') || Lang.type(source),
  1253. sourceSetter;
  1254. if ((source && isFunction(source.sendRequest))
  1255. || source === null
  1256. || sourceType === 'datasource') {
  1257. // Quacks like a DataSource instance (or null). Make it so!
  1258. this._rawSource = source;
  1259. return source;
  1260. }
  1261. // See if there's a registered setter for this source type.
  1262. if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
  1263. this._rawSource = source;
  1264. return Lang.isString(sourceSetter) ?
  1265. this[sourceSetter](source) : sourceSetter(source);
  1266. }
  1267. Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
  1268. return INVALID_VALUE;
  1269. },
  1270. /**
  1271. * Shared success callback for non-DataSource sources.
  1272. *
  1273. * @method _sourceSuccess
  1274. * @param {mixed} data Response data.
  1275. * @param {Object} request Request object.
  1276. * @protected
  1277. */
  1278. _sourceSuccess: function (data, request) {
  1279. request.callback.success({
  1280. data: data,
  1281. response: {results: data}
  1282. });
  1283. },
  1284. /**
  1285. * Synchronizes the UI state of the <code>allowBrowserAutocomplete</code>
  1286. * attribute.
  1287. *
  1288. * @method _syncBrowserAutocomplete
  1289. * @protected
  1290. */
  1291. _syncBrowserAutocomplete: function () {
  1292. var inputNode = this.get(INPUT_NODE);
  1293. if (inputNode.get('nodeName').toLowerCase() === 'input') {
  1294. inputNode.setAttribute('autocomplete',
  1295. this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
  1296. }
  1297. },
  1298. /**
  1299. * <p>
  1300. * Updates the query portion of the <code>value</code> attribute.
  1301. * </p>
  1302. *
  1303. * <p>
  1304. * If a query delimiter is defined, the last delimited portion of the input
  1305. * value will be replaced with the specified <i>value</i>.
  1306. * </p>
  1307. *
  1308. * @method _updateValue
  1309. * @param {String} newVal New value.
  1310. * @protected
  1311. */
  1312. _updateValue: function (newVal) {
  1313. var delim = this.get(QUERY_DELIMITER),
  1314. insertDelim,
  1315. len,
  1316. prevVal;
  1317. newVal = Lang.trimLeft(newVal);
  1318. if (delim) {
  1319. insertDelim = trim(delim); // so we don't double up on spaces
  1320. prevVal = YArray.map(trim(this.get(VALUE)).split(delim), trim);
  1321. len = prevVal.length;
  1322. if (len > 1) {
  1323. prevVal[len - 1] = newVal;
  1324. newVal = prevVal.join(insertDelim + ' ');
  1325. }
  1326. newVal = newVal + insertDelim + ' ';
  1327. }
  1328. this.set(VALUE, newVal);
  1329. },
  1330. // -- Protected Event Handlers ---------------------------------------------
  1331. /**
  1332. * Updates the current <code>source</code> based on the new
  1333. * <code>sourceType</code> to ensure that the two attributes don't get out
  1334. * of sync when they're changed separately.
  1335. *
  1336. * @method _afterSourceTypeChange
  1337. * @param {EventFacade} e
  1338. * @protected
  1339. */
  1340. _afterSourceTypeChange: function (e) {
  1341. if (this._rawSource) {
  1342. this.set('source', this._rawSource);
  1343. }
  1344. },
  1345. /**
  1346. * Handles change events for the <code>value</code> attribute.
  1347. *
  1348. * @method _afterValueChange
  1349. * @param {EventFacade} e
  1350. * @protected
  1351. */
  1352. _afterValueChange: function (e) {
  1353. var delay,
  1354. fire,
  1355. minQueryLength,
  1356. newVal = e.newVal,
  1357. query,
  1358. that;
  1359. // Don't query on value changes that didn't come from the user.
  1360. if (e.src !== AutoCompleteBase.UI_SRC) {
  1361. this._inputNode.set(VALUE, newVal);
  1362. return;
  1363. }
  1364. Y.log('valueChange: new: "' + newVal + '"; old: "' + e.prevVal + '"', 'info', 'autocomplete-base');
  1365. minQueryLength = this.get('minQueryLength');
  1366. query = this._parseValue(newVal) || '';
  1367. if (minQueryLength >= 0 && query.length >= minQueryLength) {
  1368. delay = this.get('queryDelay');
  1369. that = this;
  1370. fire = function () {
  1371. that.fire(EVT_QUERY, {
  1372. inputValue: newVal,
  1373. query : query
  1374. });
  1375. };
  1376. if (delay) {
  1377. clearTimeout(this._delay);
  1378. this._delay = setTimeout(fire, delay);
  1379. } else {
  1380. fire();
  1381. }
  1382. } else {
  1383. clearTimeout(this._delay);
  1384. this.fire(EVT_CLEAR, {
  1385. prevVal: e.prevVal ? this._parseValue(e.prevVal) : null
  1386. });
  1387. }
  1388. },
  1389. /**
  1390. * Handles <code>blur</code> events on the input node.
  1391. *
  1392. * @method _onInputBlur
  1393. * @param {EventFacade} e
  1394. * @protected
  1395. */
  1396. _onInputBlur: function (e) {
  1397. var delim = this.get(QUERY_DELIMITER),
  1398. delimPos,
  1399. newVal,
  1400. value;
  1401. // If a query delimiter is set and the input's value contains one or
  1402. // more trailing delimiters, strip them.
  1403. if (delim && !this.get('allowTrailingDelimiter')) {
  1404. delim = Lang.trimRight(delim);
  1405. value = newVal = this._inputNode.get(VALUE);
  1406. if (delim) {
  1407. while ((newVal = Lang.trimRight(newVal)) &&
  1408. (delimPos = newVal.length - delim.length) &&
  1409. newVal.lastIndexOf(delim) === delimPos) {
  1410. newVal = newVal.substring(0, delimPos);
  1411. }
  1412. } else {
  1413. // Delimiter is one or more space characters, so just trim the
  1414. // value.
  1415. newVal = Lang.trimRight(newVal);
  1416. }
  1417. if (newVal !== value) {
  1418. this.set(VALUE, newVal);
  1419. }
  1420. }
  1421. },
  1422. /**
  1423. * Handles <code>valueChange</code> events on the input node and fires a
  1424. * <code>query</code> event when the input value meets the configured
  1425. * criteria.
  1426. *
  1427. * @method _onInputValueChange
  1428. * @param {EventFacade} e
  1429. * @protected
  1430. */
  1431. _onInputValueChange: function (e) {
  1432. var newVal = e.newVal;
  1433. // Don't query if the internal value is the same as the new value
  1434. // reported by valueChange.
  1435. if (newVal === this.get(VALUE)) {
  1436. return;
  1437. }
  1438. this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
  1439. },
  1440. /**
  1441. * Handles source responses and fires the <code>results</code> event.
  1442. *
  1443. * @method _onResponse
  1444. * @param {EventFacade} e
  1445. * @protected
  1446. */
  1447. _onResponse: function (query, e) {
  1448. // Ignore stale responses that aren't for the current query.
  1449. if (query === (this.get(QUERY) || '')) {
  1450. this._parseResponse(query || '', e.response, e.data);
  1451. }
  1452. },
  1453. // -- Protected Default Event Handlers -------------------------------------
  1454. /**
  1455. * Default <code>clear</code> event handler. Sets the <code>results</code>
  1456. * property to an empty array and <code>query</code> to null.
  1457. *
  1458. * @method _defClearFn
  1459. * @protected
  1460. */
  1461. _defClearFn: function () {
  1462. this._set(QUERY, null);
  1463. this._set(RESULTS, []);
  1464. },
  1465. /**
  1466. * Default <code>query</code> event handler. Sets the <code>query</code>
  1467. * property and sends a request to the source if one is configured.
  1468. *
  1469. * @method _defQueryFn
  1470. * @param {EventFacade} e
  1471. * @protected
  1472. */
  1473. _defQueryFn: function (e) {
  1474. var query = e.query;
  1475. Y.log('query: "' + query + '"; inputValue: "' + e.inputValue + '"', 'info', 'autocomplete-base');
  1476. this.sendRequest(query); // sendRequest will set the 'query' attribute
  1477. },
  1478. /**
  1479. * Default <code>results</code> event handler. Sets the <code>results</code>
  1480. * property to the latest results.
  1481. *
  1482. * @method _defResultsFn
  1483. * @param {EventFacade} e
  1484. * @protected
  1485. */
  1486. _defResultsFn: function (e) {
  1487. Y.log('results: ' + Y.dump(e.results), 'info', 'autocomplete-base');
  1488. this._set(RESULTS, e[RESULTS]);
  1489. }
  1490. };
  1491. Y.AutoCompleteBase = AutoCompleteBase;
  1492. }, '3.4.1' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']});