|
1 YUI.add('jsonp', function (Y, NAME) { |
|
2 |
|
3 var isFunction = Y.Lang.isFunction; |
|
4 |
|
5 /** |
|
6 * <p>Provides a JSONPRequest class for repeated JSONP calls, and a convenience |
|
7 * method Y.jsonp(url, callback) to instantiate and send a JSONP request.</p> |
|
8 * |
|
9 * <p>Both the constructor as well as the convenience function take two |
|
10 * parameters: a url string and a callback.</p> |
|
11 * |
|
12 * <p>The url provided must include the placeholder string |
|
13 * "{callback}" which will be replaced by a dynamically |
|
14 * generated routing function to pass the data to your callback function. |
|
15 * An example url might look like |
|
16 * "http://example.com/service?callback={callback}".</p> |
|
17 * |
|
18 * <p>The second parameter can be a callback function that accepts the JSON |
|
19 * payload as its argument, or a configuration object supporting the keys:</p> |
|
20 * <ul> |
|
21 * <li>on - map of callback subscribers |
|
22 * <ul> |
|
23 * <li>success - function handler for successful transmission</li> |
|
24 * <li>failure - function handler for failed transmission</li> |
|
25 * <li>timeout - function handler for transactions that timeout</li> |
|
26 * </ul> |
|
27 * </li> |
|
28 * <li>format - override function for inserting the proxy name in the url</li> |
|
29 * <li>timeout - the number of milliseconds to wait before giving up</li> |
|
30 * <li>context - becomes <code>this</code> in the callbacks</li> |
|
31 * <li>args - array of subsequent parameters to pass to the callbacks</li> |
|
32 * <li>allowCache - use the same proxy name for all requests? (boolean)</li> |
|
33 * </ul> |
|
34 * |
|
35 * @module jsonp |
|
36 * @class JSONPRequest |
|
37 * @constructor |
|
38 * @param url {String} the url of the JSONP service |
|
39 * @param callback {Object|Function} the default callback configuration or |
|
40 * success handler |
|
41 */ |
|
42 function JSONPRequest() { |
|
43 this._init.apply(this, arguments); |
|
44 } |
|
45 |
|
46 JSONPRequest.prototype = { |
|
47 /** |
|
48 * Set up the success and failure handlers and the regex pattern used |
|
49 * to insert the temporary callback name in the url. |
|
50 * |
|
51 * @method _init |
|
52 * @param url {String} the url of the JSONP service |
|
53 * @param callback {Object|Function} Optional success callback or config |
|
54 * object containing success and failure functions and |
|
55 * the url regex. |
|
56 * @protected |
|
57 */ |
|
58 _init : function (url, callback) { |
|
59 this.url = url; |
|
60 |
|
61 /** |
|
62 * Map of the number of requests currently pending responses per |
|
63 * generated proxy. Used to ensure the proxy is not flushed if the |
|
64 * request times out and there is a timeout handler and success |
|
65 * handler, and used by connections configured to allowCache to make |
|
66 * sure the proxy isn't deleted until the last response has returned. |
|
67 * |
|
68 * @property _requests |
|
69 * @private |
|
70 * @type {Object} |
|
71 */ |
|
72 this._requests = {}; |
|
73 |
|
74 /** |
|
75 * Map of the number of timeouts received from the destination url |
|
76 * by generated proxy. Used to ensure the proxy is not flushed if the |
|
77 * request times out and there is a timeout handler and success |
|
78 * handler, and used by connections configured to allowCache to make |
|
79 * sure the proxy isn't deleted until the last response has returned. |
|
80 * |
|
81 * @property _timeouts |
|
82 * @private |
|
83 * @type {Object} |
|
84 */ |
|
85 this._timeouts = {}; |
|
86 |
|
87 // Accept a function, an object, or nothing |
|
88 callback = (isFunction(callback)) ? |
|
89 { on: { success: callback } } : |
|
90 callback || {}; |
|
91 |
|
92 var subs = callback.on || {}; |
|
93 |
|
94 if (!subs.success) { |
|
95 subs.success = this._defaultCallback(url, callback); |
|
96 } |
|
97 |
|
98 // Apply defaults and store |
|
99 this._config = Y.merge({ |
|
100 context: this, |
|
101 args : [], |
|
102 format : this._format, |
|
103 allowCache: false |
|
104 }, callback, { on: subs }); |
|
105 }, |
|
106 |
|
107 /** |
|
108 * Override this method to provide logic to default the success callback if |
|
109 * it is not provided at construction. This is overridden by jsonp-url to |
|
110 * parse the callback from the url string. |
|
111 * |
|
112 * @method _defaultCallback |
|
113 * @param url {String} the url passed at construction |
|
114 * @param config {Object} (optional) the config object passed at |
|
115 * construction |
|
116 * @return {Function} |
|
117 */ |
|
118 _defaultCallback: function () {}, |
|
119 |
|
120 /** |
|
121 * Issues the JSONP request. |
|
122 * |
|
123 * @method send |
|
124 * @param args* {any} any additional arguments to pass to the url formatter |
|
125 * beyond the base url and the proxy function name |
|
126 * @chainable |
|
127 */ |
|
128 send : function () { |
|
129 var self = this, |
|
130 args = Y.Array(arguments, 0, true), |
|
131 config = self._config, |
|
132 proxy = self._proxy || Y.guid(), |
|
133 url; |
|
134 |
|
135 // TODO: support allowCache as time value |
|
136 if (config.allowCache) { |
|
137 self._proxy = proxy; |
|
138 } |
|
139 |
|
140 if (self._requests[proxy] === undefined) { |
|
141 self._requests[proxy] = 0; |
|
142 } |
|
143 if (self._timeouts[proxy] === undefined) { |
|
144 self._timeouts[proxy] = 0; |
|
145 } |
|
146 self._requests[proxy]++; |
|
147 |
|
148 Y.log('sending ' + proxy); |
|
149 |
|
150 args.unshift(self.url, 'YUI.Env.JSONP.' + proxy); |
|
151 url = config.format.apply(self, args); |
|
152 |
|
153 if (!config.on.success) { |
|
154 Y.log("No success handler defined. Aborting JSONP request.", "warn", "jsonp"); |
|
155 return self; |
|
156 } |
|
157 |
|
158 function wrap(fn, isTimeout) { |
|
159 return (isFunction(fn)) ? |
|
160 function (data) { |
|
161 var execute = true, |
|
162 counter = '_requests'; |
|
163 |
|
164 //if (config.allowCache) { |
|
165 // A lot of wrangling to make sure timeouts result in |
|
166 // fewer success callbacks, but the proxy is properly |
|
167 // cleaned up. |
|
168 if (isTimeout) { |
|
169 ++self._timeouts[proxy]; |
|
170 --self._requests[proxy]; |
|
171 Y.log(proxy + ' timed out - timeouts(' + self._timeouts[proxy] + ') requests(' + self._requests[proxy] + ')'); |
|
172 } else { |
|
173 if (!self._requests[proxy]) { |
|
174 execute = false; |
|
175 counter = '_timeouts'; |
|
176 } |
|
177 --self[counter][proxy]; |
|
178 Y.log(proxy + ' response received - timeouts(' + self._timeouts[proxy] + ') requests(' + self._requests[proxy] + ')'); |
|
179 } |
|
180 //} |
|
181 |
|
182 if (!self._requests[proxy] && !self._timeouts[proxy]) { |
|
183 Y.log('deleting ' + proxy); |
|
184 delete YUI.Env.JSONP[proxy]; |
|
185 } |
|
186 |
|
187 if (execute) { |
|
188 fn.apply(config.context, [data].concat(config.args)); |
|
189 } |
|
190 } : |
|
191 null; |
|
192 } |
|
193 |
|
194 // Temporary un-sandboxed function alias |
|
195 // TODO: queuing |
|
196 YUI.Env.JSONP[proxy] = wrap(config.on.success); |
|
197 |
|
198 // Y.Get transactions block each other by design, but can easily |
|
199 // be made non-blocking by just calling execute() on the transaction. |
|
200 // https://github.com/yui/yui3/pull/393#issuecomment-11961608 |
|
201 Y.Get.js(url, { |
|
202 onFailure : wrap(config.on.failure), |
|
203 onTimeout : wrap(config.on.timeout, true), |
|
204 timeout : config.timeout, |
|
205 charset : config.charset, |
|
206 attributes: config.attributes, |
|
207 async : config.async |
|
208 }).execute(); |
|
209 |
|
210 return self; |
|
211 }, |
|
212 |
|
213 /** |
|
214 * Default url formatter. Looks for callback= in the url and appends it |
|
215 * if not present. The supplied proxy name will be assigned to the query |
|
216 * param. Override this method by passing a function as the |
|
217 * "format" property in the config object to the constructor. |
|
218 * |
|
219 * @method _format |
|
220 * @param url { String } the original url |
|
221 * @param proxy {String} the function name that will be used as a proxy to |
|
222 * the configured callback methods. |
|
223 * @param args* {any} additional args passed to send() |
|
224 * @return {String} fully qualified JSONP url |
|
225 * @protected |
|
226 */ |
|
227 _format: function (url, proxy) { |
|
228 return url.replace(/\{callback\}/, proxy); |
|
229 } |
|
230 }; |
|
231 |
|
232 Y.JSONPRequest = JSONPRequest; |
|
233 |
|
234 /** |
|
235 * |
|
236 * @method jsonp |
|
237 * @param url {String} the url of the JSONP service with the {callback} |
|
238 * placeholder where the callback function name typically goes. |
|
239 * @param c {Function|Object} Callback function accepting the JSON payload |
|
240 * as its argument, or a configuration object (see above). |
|
241 * @param args* {any} additional arguments to pass to send() |
|
242 * @return {JSONPRequest} |
|
243 * @static |
|
244 * @for YUI |
|
245 */ |
|
246 Y.jsonp = function (url,c) { |
|
247 var req = new Y.JSONPRequest(url,c); |
|
248 return req.send.apply(req, Y.Array(arguments, 2, true)); |
|
249 }; |
|
250 |
|
251 if (!YUI.Env.JSONP) { |
|
252 YUI.Env.JSONP = {}; |
|
253 } |
|
254 |
|
255 |
|
256 }, '@VERSION@', {"requires": ["get", "oop"]}); |