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