[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/autocomplete-base/ -> autocomplete-base.js (source)

   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>&lt;select&gt; Node</dt>
1332        <dd>
1333          You may provide a YUI Node instance wrapping a &lt;select&gt;
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 &lt;option&gt; element.</p>
1345            </dd>
1346  
1347            <dt>index (Number)</dt>
1348            <dd>
1349              <p>Index of the &lt;option&gt; element in the list.</p>
1350            </dd>
1351  
1352            <dt>node (Y.Node)</dt>
1353            <dd>
1354              <p>Node instance referring to the original &lt;option&gt; 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              &lt;select&gt; list.</p>
1361            </dd>
1362  
1363            <dt>text (String)</dt>
1364            <dd>
1365              <p>Text content of the &lt;option&gt; element.</p>
1366            </dd>
1367  
1368            <dt>value (String)</dt>
1369            <dd>
1370              <p>Value of the &lt;option&gt; 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  });


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1