- /**
- Mixes support for JSONP and YQL result sources into AutoCompleteBase.
-
- @module autocomplete
- @submodule autocomplete-sources
- **/
-
- var ACBase = Y.AutoCompleteBase,
- Lang = Y.Lang,
-
- _SOURCE_SUCCESS = '_sourceSuccess',
-
- MAX_RESULTS = 'maxResults',
- REQUEST_TEMPLATE = 'requestTemplate',
- RESULT_LIST_LOCATOR = 'resultListLocator';
-
- // Add prototype properties and methods to AutoCompleteBase.
- Y.mix(ACBase.prototype, {
- /**
- Regular expression used to determine whether a String source is a YQL query.
-
- @property _YQL_SOURCE_REGEX
- @type RegExp
- @protected
- @for AutoCompleteBase
- **/
- _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
-
- /**
- Runs before AutoCompleteBase's `_createObjectSource()` method and augments
- it to support additional object-based source types.
-
- @method _beforeCreateObjectSource
- @param {String} source
- @protected
- @for AutoCompleteBase
- **/
- _beforeCreateObjectSource: function (source) {
- // If the object is a <select> node, use the options as the result
- // source.
- if (source instanceof Y.Node &&
- source.get('nodeName').toLowerCase() === 'select') {
-
- return this._createSelectSource(source);
- }
-
- // If the object is a JSONPRequest instance, try to use it as a JSONP
- // source.
- if (Y.JSONPRequest && source instanceof Y.JSONPRequest) {
- return this._createJSONPSource(source);
- }
-
- // Fall back to a basic object source.
- return this._createObjectSource(source);
- },
-
- /**
- Creates a DataSource-like object that uses `Y.io` as a source. See the
- `source` attribute for more details.
-
- @method _createIOSource
- @param {String} source URL.
- @return {Object} DataSource-like object.
- @protected
- @for AutoCompleteBase
- **/
- _createIOSource: function (source) {
- var ioSource = {type: 'io'},
- that = this,
- ioRequest, lastRequest, loading;
-
- // Private internal _sendRequest method that will be assigned to
- // ioSource.sendRequest once io-base and json-parse are available.
- function _sendRequest(request) {
- var cacheKey = request.request;
-
- // Return immediately on a cached response.
- if (that._cache && cacheKey in that._cache) {
- that[_SOURCE_SUCCESS](that._cache[cacheKey], request);
- return;
- }
-
- // Cancel any outstanding requests.
- if (ioRequest && ioRequest.isInProgress()) {
- ioRequest.abort();
- }
-
- ioRequest = Y.io(that._getXHRUrl(source, request), {
- on: {
- success: function (tid, response) {
- var data;
-
- try {
- data = Y.JSON.parse(response.responseText);
- } catch (ex) {
- Y.error('JSON parse error', ex);
- }
-
- if (data) {
- that._cache && (that._cache[cacheKey] = data);
- that[_SOURCE_SUCCESS](data, request);
- }
- }
- }
- });
- }
-
- ioSource.sendRequest = function (request) {
- // Keep track of the most recent request in case there are multiple
- // requests while we're waiting for the IO module to load. Only the
- // most recent request will be sent.
- lastRequest = request;
-
- if (loading) { return; }
-
- loading = true;
-
- // Lazy-load the io-base and json-parse modules if necessary,
- // then overwrite the sendRequest method to bypass this check in
- // the future.
- Y.use('io-base', 'json-parse', function () {
- ioSource.sendRequest = _sendRequest;
- _sendRequest(lastRequest);
- });
- };
-
- return ioSource;
- },
-
- /**
- Creates a DataSource-like object that uses the specified JSONPRequest
- instance as a source. See the `source` attribute for more details.
-
- @method _createJSONPSource
- @param {JSONPRequest|String} source URL string or JSONPRequest instance.
- @return {Object} DataSource-like object.
- @protected
- @for AutoCompleteBase
- **/
- _createJSONPSource: function (source) {
- var jsonpSource = {type: 'jsonp'},
- that = this,
- lastRequest, loading;
-
- function _sendRequest(request) {
- var cacheKey = request.request,
- query = request.query;
-
- if (that._cache && cacheKey in that._cache) {
- that[_SOURCE_SUCCESS](that._cache[cacheKey], request);
- return;
- }
-
- // Hack alert: JSONPRequest currently doesn't support
- // per-request callbacks, so we're reaching into the protected
- // _config object to make it happen.
- //
- // This limitation is mentioned in the following JSONP
- // enhancement ticket:
- //
- // http://yuilibrary.com/projects/yui3/ticket/2529371
- source._config.on.success = function (data) {
- that._cache && (that._cache[cacheKey] = data);
- that[_SOURCE_SUCCESS](data, request);
- };
-
- source.send(query);
- }
-
- jsonpSource.sendRequest = function (request) {
- // Keep track of the most recent request in case there are multiple
- // requests while we're waiting for the JSONP module to load. Only
- // the most recent request will be sent.
- lastRequest = request;
-
- if (loading) { return; }
-
- loading = true;
-
- // Lazy-load the JSONP module if necessary, then overwrite the
- // sendRequest method to bypass this check in the future.
- Y.use('jsonp', function () {
- // Turn the source into a JSONPRequest instance if it isn't
- // one already.
- if (!(source instanceof Y.JSONPRequest)) {
- source = new Y.JSONPRequest(source, {
- format: Y.bind(that._jsonpFormatter, that)
- });
- }
-
- jsonpSource.sendRequest = _sendRequest;
- _sendRequest(lastRequest);
- });
- };
-
- return jsonpSource;
- },
-
- /**
- Creates a DataSource-like object that uses the specified `<select>` node as
- a source.
-
- @method _createSelectSource
- @param {Node} source YUI Node instance wrapping a `<select>` node.
- @return {Object} DataSource-like object.
- @protected
- @for AutoCompleteBase
- **/
- _createSelectSource: function (source) {
- var that = this;
-
- return {
- type: 'select',
- sendRequest: function (request) {
- var options = [];
-
- source.get('options').each(function (option) {
- options.push({
- html : option.get('innerHTML'),
- index : option.get('index'),
- node : option,
- selected: option.get('selected'),
- text : option.get('text'),
- value : option.get('value')
- });
- });
-
- that[_SOURCE_SUCCESS](options, request);
- }
- };
- },
-
- /**
- Creates a DataSource-like object that calls the specified URL or executes
- the specified YQL query for results. If the string starts with "select ",
- "use ", or "set " (case-insensitive), it's assumed to be a YQL query;
- otherwise, it's assumed to be a URL (which may be absolute or relative).
- URLs containing a "{callback}" placeholder are assumed to be JSONP URLs; all
- others will use XHR. See the `source` attribute for more details.
-
- @method _createStringSource
- @param {String} source URL or YQL query.
- @return {Object} DataSource-like object.
- @protected
- @for AutoCompleteBase
- **/
- _createStringSource: function (source) {
- if (this._YQL_SOURCE_REGEX.test(source)) {
- // Looks like a YQL query.
- return this._createYQLSource(source);
- } else if (source.indexOf('{callback}') !== -1) {
- // Contains a {callback} param and isn't a YQL query, so it must be
- // JSONP.
- return this._createJSONPSource(source);
- } else {
- // Not a YQL query or JSONP, so we'll assume it's an XHR URL.
- return this._createIOSource(source);
- }
- },
-
- /**
- Creates a DataSource-like object that uses the specified YQL query string to
- create a YQL-based source. See the `source` attribute for details. If no
- `resultListLocator` is defined, this method will set a best-guess locator
- that might work for many typical YQL queries.
-
- @method _createYQLSource
- @param {String} source YQL query.
- @return {Object} DataSource-like object.
- @protected
- @for AutoCompleteBase
- **/
- _createYQLSource: function (source) {
- var that = this,
- yqlSource = {type: 'yql'},
- lastRequest, loading, yqlRequest;
-
- if (!that.get(RESULT_LIST_LOCATOR)) {
- that.set(RESULT_LIST_LOCATOR, that._defaultYQLLocator);
- }
-
- function _sendRequest(request) {
- var query = request.query,
- env = that.get('yqlEnv'),
- maxResults = that.get(MAX_RESULTS),
- callback, opts, yqlQuery;
-
- yqlQuery = Lang.sub(source, {
- maxResults: maxResults > 0 ? maxResults : 1000,
- request : request.request,
- query : query
- });
-
- if (that._cache && yqlQuery in that._cache) {
- that[_SOURCE_SUCCESS](that._cache[yqlQuery], request);
- return;
- }
-
- callback = function (data) {
- that._cache && (that._cache[yqlQuery] = data);
- that[_SOURCE_SUCCESS](data, request);
- };
-
- opts = {proto: that.get('yqlProtocol')};
-
- // Only create a new YQLRequest instance if this is the
- // first request. For subsequent requests, we'll reuse the
- // original instance.
- if (yqlRequest) {
- yqlRequest._callback = callback;
- yqlRequest._opts = opts;
- yqlRequest._params.q = yqlQuery;
-
- if (env) {
- yqlRequest._params.env = env;
- }
- } else {
- yqlRequest = new Y.YQLRequest(yqlQuery, {
- on: {success: callback},
- allowCache: false // temp workaround until JSONP has per-URL callback proxies
- }, env ? {env: env} : null, opts);
- }
-
- yqlRequest.send();
- }
-
- yqlSource.sendRequest = function (request) {
- // Keep track of the most recent request in case there are multiple
- // requests while we're waiting for the YQL module to load. Only the
- // most recent request will be sent.
- lastRequest = request;
-
- if (!loading) {
- // Lazy-load the YQL module if necessary, then overwrite the
- // sendRequest method to bypass this check in the future.
- loading = true;
-
- Y.use('yql', function () {
- yqlSource.sendRequest = _sendRequest;
- _sendRequest(lastRequest);
- });
- }
- };
-
- return yqlSource;
- },
-
- /**
- Default resultListLocator used when a string-based YQL source is set and the
- implementer hasn't already specified one.
-
- @method _defaultYQLLocator
- @param {Object} response YQL response object.
- @return {Array}
- @protected
- @for AutoCompleteBase
- **/
- _defaultYQLLocator: function (response) {
- var results = response && response.query && response.query.results,
- values;
-
- if (results && Lang.isObject(results)) {
- // If there's only a single value on YQL's results object, that
- // value almost certainly contains the array of results we want. If
- // there are 0 or 2+ values, then the values themselves are most
- // likely the results we want.
- values = Y.Object.values(results) || [];
- results = values.length === 1 ? values[0] : values;
-
- if (!Lang.isArray(results)) {
- results = [results];
- }
- } else {
- results = [];
- }
-
- return results;
- },
-
- /**
- Returns a formatted XHR URL based on the specified base _url_, _query_, and
- the current _requestTemplate_ if any.
-
- @method _getXHRUrl
- @param {String} url Base URL.
- @param {Object} request Request object containing `query` and `request`
- properties.
- @return {String} Formatted URL.
- @protected
- @for AutoCompleteBase
- **/
- _getXHRUrl: function (url, request) {
- var maxResults = this.get(MAX_RESULTS);
-
- if (request.query !== request.request) {
- // Append the request template to the URL.
- url += request.request;
- }
-
- return Lang.sub(url, {
- maxResults: maxResults > 0 ? maxResults : 1000,
- query : encodeURIComponent(request.query)
- });
- },
-
- /**
- URL formatter passed to `JSONPRequest` instances.
-
- @method _jsonpFormatter
- @param {String} url
- @param {String} proxy
- @param {String} query
- @return {String} Formatted URL
- @protected
- @for AutoCompleteBase
- **/
- _jsonpFormatter: function (url, proxy, query) {
- var maxResults = this.get(MAX_RESULTS),
- requestTemplate = this.get(REQUEST_TEMPLATE);
-
- if (requestTemplate) {
- url += requestTemplate(query);
- }
-
- return Lang.sub(url, {
- callback : proxy,
- maxResults: maxResults > 0 ? maxResults : 1000,
- query : encodeURIComponent(query)
- });
- }
- });
-
- // Add attributes to AutoCompleteBase.
- Y.mix(ACBase.ATTRS, {
- /**
- YQL environment file URL to load when the `source` is set to a YQL query.
- Set this to `null` to use the default Open Data Tables environment file
- (http://datatables.org/alltables.env).
-
- @attribute yqlEnv
- @type String
- @default null
- @for AutoCompleteBase
- **/
- yqlEnv: {
- value: null
- },
-
- /**
- URL protocol to use when the `source` is set to a YQL query.
-
- @attribute yqlProtocol
- @type String
- @default 'http'
- @for AutoCompleteBase
- **/
- yqlProtocol: {
- value: 'http'
- }
- });
-
- // Tell AutoCompleteBase about the new source types it can now support.
- Y.mix(ACBase.SOURCE_TYPES, {
- io : '_createIOSource',
- jsonp : '_createJSONPSource',
- object: '_beforeCreateObjectSource', // Run our version before the base version.
- select: '_createSelectSource',
- string: '_createStringSource',
- yql : '_createYQLSource'
- }, true);
-
-