[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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


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