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