rasputin 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1071 @@
1
+
2
+ (function(exports) {
3
+ // ==========================================================================
4
+ // Project: SproutCore - JavaScript Application Framework
5
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
6
+ // Portions ©2008-2011 Apple Inc. All rights reserved.
7
+ // License: Licensed under MIT license (see license.js)
8
+ // ==========================================================================
9
+
10
+ var get = SC.get, set = SC.set, getPath = SC.getPath;
11
+
12
+ /**
13
+ @class
14
+
15
+ A response represents a single response from a server request. An instance
16
+ of this class is returned whenever you call SC.Request.send().
17
+
18
+ @extend SC.Object
19
+ @since SproutCore 1.0
20
+ */
21
+ SC.Response = SC.Object.extend(
22
+ /** @scope SC.Response.prototype */ {
23
+
24
+ /**
25
+ Walk like a duck
26
+
27
+ @type Boolean
28
+ */
29
+ isResponse: true,
30
+
31
+ /**
32
+ Becomes true if there was a failure. Makes this into an error object.
33
+
34
+ @type Boolean
35
+ @default false
36
+ */
37
+ isError: false,
38
+
39
+ /**
40
+ Always the current response
41
+
42
+ @field
43
+ @type SC.Response
44
+ @default `this`
45
+ */
46
+ errorValue: function() {
47
+ return this;
48
+ }.property().cacheable(),
49
+
50
+ /**
51
+ The error object generated when this becomes an error
52
+
53
+ @type SC.Error
54
+ @default null
55
+ */
56
+ errorObject: null,
57
+
58
+ /**
59
+ Request used to generate this response. This is a copy of the original
60
+ request object as you may have modified the original request object since
61
+ then.
62
+
63
+ To retrieve the original request object use originalRequest.
64
+
65
+ @type SC.Request
66
+ @default null
67
+ */
68
+ request: null,
69
+
70
+ /**
71
+ The request object that originated this request series. Mostly this is
72
+ useful if you are looking for a reference to the original request. To
73
+ inspect actual properties you should use request instead.
74
+
75
+ @field
76
+ @type SC.Request
77
+ @observes request
78
+ */
79
+ originalRequest: function() {
80
+ var ret = get(this, 'request');
81
+ while (get(ret, 'source')) { ret = get(ret, 'source'); }
82
+ return ret ;
83
+ }.property('request').cacheable(),
84
+
85
+ /**
86
+ Type of request. Must be an HTTP method. Based on the request.
87
+
88
+ @field
89
+ @type String
90
+ @observes request
91
+ */
92
+ type: function() {
93
+ return getPath(this, 'request.type');
94
+ }.property('request').cacheable(),
95
+
96
+ /**
97
+ URL of request.
98
+
99
+ @field
100
+ @type String
101
+ @observes request
102
+ */
103
+ url: function() {
104
+ return getPath(this, 'request.url');
105
+ }.property('request').cacheable(),
106
+
107
+ /**
108
+ Returns the hash of listeners set on the request.
109
+
110
+ @field
111
+ @type Hash
112
+ @observes request
113
+ */
114
+ listeners: function() {
115
+ return getPath(this, 'request.listeners');
116
+ }.property('request').cacheable(),
117
+
118
+ /**
119
+ The response status code.
120
+
121
+ @type Number
122
+ @default -100
123
+ */
124
+ status: -100, // READY
125
+
126
+ /**
127
+ Headers from the response. Computed on-demand
128
+
129
+ @type Hash
130
+ @default null
131
+ */
132
+ headers: null,
133
+
134
+ /**
135
+ The response body or the parsed JSON.
136
+ */
137
+ body: null,
138
+
139
+ /**
140
+ Set to true if response is cancelled
141
+
142
+ @type Boolean
143
+ @default false
144
+ */
145
+ isCancelled: false,
146
+
147
+ /**
148
+ Set to true if the request timed out. Set to false if the request has
149
+ completed before the timeout value. Set to null if the timeout timer is
150
+ still ticking.
151
+
152
+ @type Boolean
153
+ @default null
154
+ */
155
+ timedOut: null,
156
+
157
+ // ..........................................................
158
+ // METHODS
159
+ //
160
+
161
+ /**
162
+ Called by the request manager when its time to actually run. This will
163
+ invoke any callbacks on the source request then invoke transport() to
164
+ begin the actual request.
165
+ */
166
+ fire: function() {
167
+ var req = get(this, 'request'),
168
+ source = req ? get(req, 'source') : null;
169
+
170
+ // first give the source a chance to fixup the request and response
171
+ // then freeze req so no more changes can happen.
172
+ if (source && source.willSend) { source.willSend(req, this); }
173
+ req.freeze();
174
+
175
+ // if the source did not cancel the request, then invoke the transport
176
+ // to actually trigger the request. This might receive a response
177
+ // immediately if it is synchronous.
178
+ if (!get(this, 'isCancelled')) { this.invokeTransport(); }
179
+
180
+ // if the transport did not cancel the request for some reason, let the
181
+ // source know that the request was sent
182
+ if (!this.get('isCancelled') && source && source.didSend) {
183
+ source.didSend(req, this);
184
+ }
185
+ },
186
+
187
+ /**
188
+ Called by `SC.Response#fire()`. Starts the transport by invoking the
189
+ `SC.Response#receive()` function.
190
+ */
191
+ invokeTransport: function() {
192
+ this.receive(function(proceed) { set(this, 'status', 200); }, this);
193
+ },
194
+
195
+ /**
196
+ Invoked by the transport when it receives a response. The passed-in
197
+ callback will be invoked to actually process the response. If cancelled
198
+ we will pass false. You should clean up instead.
199
+
200
+ Invokes callbacks on the source request also.
201
+
202
+ @param {Function} callback the function to receive
203
+ @param {Object} context context to execute the callback in
204
+ @returns {SC.Response} receiver
205
+ */
206
+ receive: function(callback, context) {
207
+ var req = get(this, 'request');
208
+ var source = req ? get(req, 'source') : null;
209
+
210
+ SC.run(this, function() {
211
+ // invoke the source, giving a chance to fixup the response or (more
212
+ // likely) cancel the request.
213
+ if (source && source.willReceive) { source.willReceive(req, this); }
214
+
215
+ // invoke the callback. note if the response was cancelled or not
216
+ callback.call(context, !get(this, 'isCancelled'));
217
+
218
+ // if we weren't cancelled, then give the source first crack at handling
219
+ // the response. if the source doesn't want listeners to be notified,
220
+ // it will cancel the response.
221
+ if (!get(this, 'isCancelled') && source && source.didReceive) {
222
+ source.didReceive(req, this);
223
+ }
224
+
225
+ // notify listeners if we weren't cancelled.
226
+ if (!get(this, 'isCancelled')) { this.notify(); }
227
+ });
228
+
229
+ // no matter what, remove from inflight queue
230
+ SC.Request.manager.transportDidClose(this);
231
+ return this;
232
+ },
233
+
234
+ /**
235
+ Default method just closes the connection. It will also mark the request
236
+ as cancelled, which will not call any listeners.
237
+ */
238
+ cancel: function() {
239
+ if (!get(this, 'isCancelled')) {
240
+ set(this, 'isCancelled', true) ;
241
+ this.cancelTransport() ;
242
+ SC.Request.manager.transportDidClose(this) ;
243
+ }
244
+ },
245
+
246
+ /**
247
+ Override with concrete implementation to actually cancel the transport.
248
+ */
249
+ cancelTransport: function() {},
250
+
251
+ /**
252
+ Notifies any saved target/action. Call whenever you cancel, or end.
253
+
254
+ @returns {SC.Response} receiver
255
+ */
256
+ notify: function() {
257
+ var listeners = this.get('listeners'),
258
+ status = this.get('status'),
259
+ baseStat = Math.floor(status / 100) * 100,
260
+ handled = false;
261
+
262
+ if (!listeners) { return this; }
263
+
264
+ handled = this._notifyListeners(listeners, status);
265
+ if (!handled && baseStat !== status) { handled = this._notifyListeners(listeners, baseStat); }
266
+ if (!handled && status !== 0) { handled = this._notifyListeners(listeners, 0); }
267
+
268
+ return this ;
269
+ },
270
+
271
+ /**
272
+ String representation of the response object
273
+
274
+ @returns {String}
275
+ */
276
+ toString: function() {
277
+ var ret = this._super();
278
+ return "%@<%@ %@, status=%@".fmt(ret, this.get('type'), this.get('address'), this.get('status'));
279
+ },
280
+
281
+ /**
282
+ @private
283
+
284
+ Will notify each listener. Returns true if any of the listeners handle.
285
+ */
286
+ _notifyListeners: function(listeners, status) {
287
+ var notifiers = listeners[status], params, target, action;
288
+ if (!notifiers) { return false; }
289
+
290
+ var handled = false;
291
+ var len = notifiers.length;
292
+
293
+ for (var i = 0; i < len; i++) {
294
+ var notifier = notifiers[i];
295
+ params = (notifier.params || []).copy();
296
+ params.unshift(this);
297
+
298
+ target = notifier.target;
299
+ action = notifier.action;
300
+ if (SC.typeOf(action) === 'string') { action = target[action]; }
301
+
302
+ handled = action.apply(target, params);
303
+ }
304
+
305
+ return handled;
306
+ }
307
+ });
308
+
309
+ /**
310
+ Concrete implementation of SC.Response that implements support for using
311
+ jqXHR requests.
312
+
313
+ @extends SC.Response
314
+ */
315
+ SC.XHRResponse = SC.Response.extend({
316
+
317
+ /**
318
+ Implement transport-specific support for fetching all headers
319
+ */
320
+ headers: function() {
321
+ var rawRequest = get(this, 'rawRequest'),
322
+ str = rawRequest ? rawRequest.getAllResponseHeaders() : null,
323
+ ret = {};
324
+
325
+ if (!str) return ret;
326
+
327
+ str.split("\n").forEach(function(header) {
328
+ var idx = header.indexOf(':'),
329
+ key, value;
330
+ if (idx>=0) {
331
+ key = header.slice(0,idx);
332
+ value = SC.String.trim(header.slice(idx+1));
333
+ ret[key] = value ;
334
+ }
335
+ }, this);
336
+
337
+ return ret ;
338
+ }.property('status').cacheable(),
339
+
340
+ /**
341
+ Implement transport-specific support for fetching named header
342
+ */
343
+ header: function(key) {
344
+ var rawRequest = get(this, 'rawRequest');
345
+ return rawRequest ? rawRequest.getResponseHeader(key) : null;
346
+ },
347
+
348
+ /**
349
+ */
350
+ cancelTransport: function() {
351
+ var rawRequest = get(this, 'rawRequest');
352
+ if (rawRequest) rawRequest.abort();
353
+ set(this, 'rawRequest', null);
354
+ },
355
+
356
+ /**
357
+ */
358
+ invokeTransport: function() {
359
+ var async = !!getPath(this, 'request.isAsynchronous');
360
+ var rawRequest = this.createRequest();
361
+
362
+ // save it
363
+ set(this, 'rawRequest', rawRequest);
364
+
365
+ // not async
366
+ if (!async) this.finishRequest();
367
+
368
+ return rawRequest;
369
+ },
370
+
371
+ /**
372
+ Creates the jqXHR object.
373
+
374
+ @returns {jqXHR}
375
+ */
376
+ createRequest: function() {
377
+ var request = get(this, 'request');
378
+ return SC.$.ajax({
379
+ url: get(this, 'url'),
380
+ type: get(this, 'type'),
381
+ dataType: get(request, 'dataType'),
382
+ async: get(request, 'isAsynchronous'),
383
+ headers: get(request, 'headers'),
384
+ data: get(request, 'body'),
385
+ timeout: get(request, 'timeout'),
386
+ ifModified: get(request, 'ifModified'),
387
+ complete: this.finishRequest,
388
+ success: this._didLoadContent,
389
+ context: this
390
+ });
391
+ },
392
+
393
+ /**
394
+ @private
395
+
396
+ Called by the jqXHR when it responds with some final results.
397
+
398
+ @param {jqXHR} rawRequest the actual request
399
+ @returns {Boolean} request success
400
+ */
401
+ finishRequest: function(rawRequest) {
402
+ if (rawRequest.readyState === 4 && !get(this, 'timedOut')) {
403
+ this.receive(function(proceed) {
404
+ if (!proceed) return; // skip receiving...
405
+ var statusText = rawRequest.statusText,
406
+ status = rawRequest.status;
407
+ set(this, 'status', status);
408
+ if (statusText === 'success' || statusText === 'notmodified') {
409
+ set(this, 'isError', false);
410
+ set(this, 'errorObject', null);
411
+ } else {
412
+ if (statusText === 'timeout') {
413
+ set(this, 'timedOut', true);
414
+ this.cancelTransport();
415
+ }
416
+ var error = new SC.Error('%@ Request %@'.fmt(statusText, status));
417
+ set(error, 'errorValue', this);
418
+ set(this, 'isError', true);
419
+ set(this, 'errorObject', error);
420
+ }
421
+ }, this);
422
+
423
+ return true;
424
+ }
425
+ return false;
426
+ },
427
+
428
+ /**
429
+ @private
430
+ */
431
+ _didLoadContent: function(data) {
432
+ set(this, 'body', data);
433
+ }
434
+
435
+ });
436
+
437
+ SC.HTTPError = SC.Object.extend({
438
+
439
+ });
440
+
441
+ SC.ok = function(ret) {
442
+ return (ret !== false) && !(ret && ret.isError);
443
+ };
444
+
445
+ })({});
446
+
447
+
448
+ (function(exports) {
449
+ // ==========================================================================
450
+ // Project: SproutCore - JavaScript Application Framework
451
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
452
+ // Portions ©2008-2011 Apple Inc. All rights reserved.
453
+ // License: Licensed under MIT license (see license.js)
454
+ // ==========================================================================
455
+
456
+ var get = SC.get, set = SC.set;
457
+
458
+ /**
459
+ @class
460
+
461
+ Implements support for Ajax requests using XHR and other prototcols.
462
+
463
+ SC.Request is much like an inverted version of the request/response objects
464
+ you receive when implementing HTTP servers.
465
+
466
+ To send a request, you just need to create your request object, configure
467
+ your options, and call send() to initiate the request.
468
+
469
+ @extends SC.Object
470
+ @extends SC.Copyable
471
+ @extends SC.Freezable
472
+ */
473
+ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable, {
474
+
475
+ // ..........................................................
476
+ // PROPERTIES
477
+ //
478
+
479
+ /**
480
+ Sends the request asynchronously instead of blocking the browser. You
481
+ should almost always make requests asynchronous. You can change this
482
+ options with the async() helper option (or simply set it directly).
483
+
484
+ @type Boolean
485
+ @default YES
486
+ */
487
+ isAsynchronous: true,
488
+
489
+ /**
490
+ Current set of headers for the request
491
+
492
+ @field
493
+ @type Hash
494
+ @default {}
495
+ */
496
+ headers: function() {
497
+ var ret = this._headers;
498
+ if (!ret) { ret = this._headers = {}; }
499
+ return ret;
500
+ }.property().cacheable(),
501
+
502
+ /**
503
+ Underlying response class to actually handle this request. Currently the
504
+ only supported option is SC.XHRResponse which uses a traditional
505
+ XHR transport.
506
+
507
+ @type SC.Response
508
+ @default SC.XHRResponse
509
+ */
510
+ responseClass: SC.XHRResponse,
511
+
512
+ /**
513
+ The original request for copied requests.
514
+
515
+ @property SC.Request
516
+ @default null
517
+ */
518
+ source: null,
519
+
520
+ /**
521
+ The URL this request to go to.
522
+
523
+ @type String
524
+ @default null
525
+ */
526
+ url: null,
527
+
528
+ /**
529
+ The HTTP method to use.
530
+
531
+ @type String
532
+ @default 'GET'
533
+ */
534
+ type: 'GET',
535
+
536
+ /**
537
+ The body of the request. May be an object is isJSON or isXML is set,
538
+ otherwise should be a string.
539
+
540
+ @type Object|String
541
+ @default null
542
+ */
543
+ body: null,
544
+
545
+ /**
546
+ @type String
547
+ @default 'json'
548
+ */
549
+ dataType: 'json',
550
+
551
+ /**
552
+ An optional timeout value of the request, in milliseconds. The timer
553
+ begins when SC.Response#fire is actually invoked by the request manager
554
+ and not necessarily when SC.Request#send is invoked. If this timeout is
555
+ reached before a response is received, the equivalent of
556
+ SC.Request.manager#cancel() will be invoked on the SC.Response instance
557
+ and the didReceive() callback will be called.
558
+
559
+ An exception will be thrown if you try to invoke send() on a request that
560
+ has both a timeout and isAsyncronous set to NO.
561
+
562
+ @type Number
563
+ @default null
564
+ */
565
+ timeout: null,
566
+
567
+ /**
568
+
569
+ */
570
+ ifModified: false,
571
+
572
+ // ..........................................................
573
+ // CALLBACKS
574
+ //
575
+
576
+ /**
577
+ Invoked on the original request object just before a copied request is
578
+ frozen and then sent to the server. This gives you one last change to
579
+ fixup the request; possibly adding headers and other options.
580
+
581
+ If you do not want the request to actually send, call cancel().
582
+
583
+ @param {SC.Request} request A copy of the request object, not frozen
584
+ @param {SC.Response} response The object that will wrap the response
585
+ */
586
+ willSend: function(request, response) {},
587
+
588
+ /**
589
+ Invoked on the original request object just after the request is sent to
590
+ the server. You might use this callback to update some state in your
591
+ application.
592
+
593
+ The passed request is a frozen copy of the request, indicating the
594
+ options set at the time of the request.
595
+
596
+ @param {SC.Request} request A copy of the request object, frozen
597
+ @param {SC.Response} response The object that will wrap the response
598
+ @returns {Boolean} YES on success, NO on failure
599
+ */
600
+ didSend: function(request, response) {},
601
+
602
+ /**
603
+ Invoked when a response has been received but not yet processed. This is
604
+ your chance to fix up the response based on the results. If you don't
605
+ want to continue processing the response call response.cancel().
606
+
607
+ @param {SC.Request} request A copy of the request object, frozen
608
+ @param {SC.Response} response The object that will wrap the response
609
+ */
610
+ willReceive: function(request, response) {},
611
+
612
+ /**
613
+ Invoked after a response has been processed but before any listeners are
614
+ notified. You can do any standard processing on the request at this
615
+ point. If you don't want to allow notifications to continue, call
616
+ response.cancel()
617
+
618
+ @param {SC.Request} request A copy of the request object, frozen
619
+ @param {SC.Response} response The object that will wrap the response
620
+ */
621
+ didReceive: function(request, response) {},
622
+
623
+
624
+ // ..........................................................
625
+ // HELPER METHODS
626
+ //
627
+
628
+ /** @private */
629
+ concatenatedProperties: 'COPY_KEYS',
630
+
631
+ /** @private */
632
+ COPY_KEYS: ['isAsynchronous', 'dataType', 'url', 'type', 'timeout', 'body', 'responseClass', 'willSend', 'didSend', 'willReceive', 'didReceive'],
633
+
634
+ /**
635
+ Returns a copy of the current request. This will only copy certain
636
+ properties so if you want to add additional properties to the copy you
637
+ will need to override copy() in a subclass.
638
+
639
+ @returns {SC.Request} new request
640
+ */
641
+ copy: function() {
642
+ var ret = {},
643
+ keys = this.COPY_KEYS,
644
+ loc = keys.length,
645
+ key, listeners, headers;
646
+
647
+ while(--loc >= 0) {
648
+ key = keys[loc];
649
+ if (this.hasOwnProperty(key)) {
650
+ ret[key] = this.get(key);
651
+ }
652
+ }
653
+
654
+ if (this.hasOwnProperty('listeners')) {
655
+ ret.listeners = SC.copy(this.get('listeners'));
656
+ }
657
+
658
+ if (this.hasOwnProperty('_headers')) {
659
+ ret._headers = SC.copy(this._headers);
660
+ }
661
+
662
+ ret.source = get(this, 'source') || this;
663
+
664
+ return this.constructor.create(ret);
665
+ },
666
+
667
+ /**
668
+ To set headers on the request object. Pass either a single key/value
669
+ pair or a hash of key/value pairs. If you pass only a header name, this
670
+ will return the current value of the header.
671
+
672
+ @param {String|Hash} key
673
+ @param {String} value
674
+ @returns {SC.Request|Object} receiver
675
+ */
676
+ header: function(key, value) {
677
+ var headers;
678
+
679
+ if (SC.typeOf(key) === 'string') {
680
+ headers = this._headers;
681
+ if (arguments.length === 1) {
682
+ return headers ? headers[key] : null;
683
+ } else {
684
+ this.propertyWillChange('headers');
685
+ if (!headers) { headers = this._headers = {}; }
686
+ headers[key] = value;
687
+ this.propertyDidChange('headers');
688
+ return this;
689
+ }
690
+
691
+ // handle parsing hash of parameters
692
+ } else if (value === undefined) {
693
+ headers = key;
694
+ this.beginPropertyChanges();
695
+ for(key in headers) {
696
+ if (!headers.hasOwnProperty(key)) { continue; }
697
+ this.header(key, headers[key]);
698
+ }
699
+ this.endPropertyChanges();
700
+ return this;
701
+ }
702
+
703
+ return this;
704
+ },
705
+
706
+ /**
707
+ Clears the list of headers that were set on this request.
708
+ This could be used by a subclass to blow-away any custom
709
+ headers that were added by the super class.
710
+ */
711
+ clearHeaders: function() {
712
+ this.propertyWillChange('headers');
713
+ this._headers = {};
714
+ this.propertyDidChange('headers');
715
+ },
716
+
717
+ /**
718
+ Converts the current request to be asynchronous.
719
+
720
+ @param {Boolean} flag YES to make asynchronous, NO or undefined. Default YES.
721
+ @returns {SC.Request} receiver
722
+ */
723
+ async: function(flag) {
724
+ if (flag === undefined) { flag = true; }
725
+ set(this, 'isAsynchronous', flag);
726
+ return this;
727
+ },
728
+
729
+ /**
730
+ Sets the maximum amount of time the request will wait for a response.
731
+
732
+ @param {Number} timeout The timeout in milliseconds.
733
+ @returns {SC.Request} receiver
734
+ */
735
+ timeoutAfter: function(timeout) {
736
+ set(this, 'timeout', timeout);
737
+ return this;
738
+ },
739
+
740
+ /**
741
+ Converts the current request to use JSON.
742
+
743
+ @returns {SC.Request} receiver
744
+ */
745
+ json: function() {
746
+ set(this, 'dataType', 'json');
747
+ return this;
748
+ },
749
+
750
+ /**
751
+ Converts the current request to use XML.
752
+
753
+ @returns {SC.Request} recevier
754
+ */
755
+ xml: function() {
756
+ set(this, 'dataType', 'xml');
757
+ return this;
758
+ },
759
+
760
+ /**
761
+
762
+ */
763
+ isJSON: function() {
764
+ return get(this, 'dataType') === 'json';
765
+ }.property('dataType').cacheable(),
766
+
767
+ /**
768
+
769
+ */
770
+ isXML: function() {
771
+ return get(this, 'dataType') === 'xml';
772
+ }.property('dataType').cacheable(),
773
+
774
+ /**
775
+ Will fire the actual request. If you have set the request to use JSON
776
+ mode then you can pass any object that can be converted to JSON as the
777
+ body. Otherwise you should pass a string body.
778
+
779
+ @param {String|Object} [body]
780
+ @returns {SC.Response} New response object
781
+ */
782
+ send: function(body) {
783
+ // Sanity-check: Be sure a timeout value was not specified if the request
784
+ // is synchronous (because it wouldn't work).
785
+ var timeout = get(this, 'timeout');
786
+ if (timeout && !get(this, 'isAsynchronous')) {
787
+ throw "Timeout values cannot be used with synchronous requests";
788
+ } else if (timeout === 0) {
789
+ throw "The timeout value must either not be specified or must be greater than 0";
790
+ }
791
+
792
+ if (body) { set(this, 'body', body); }
793
+ return SC.Request.manager.sendRequest(this.copy());
794
+ },
795
+
796
+ /**
797
+ Resends the current request. This is more efficient than calling send()
798
+ for requests that have already been used in a send. Otherwise acts just
799
+ like send(). Does not take a body argument.
800
+
801
+ @returns {SC.Response} new response object
802
+ */
803
+ resend: function() {
804
+ var req = get(this, 'source') ? this : this.copy();
805
+ return SC.Request.manager.sendRequest(req);
806
+ },
807
+
808
+ /**
809
+ Configures a callback to execute when a request completes. You must pass
810
+ at least a target and action/method to this and optionally a status code.
811
+ You may also pass additional parameters which will be passed along to your
812
+ callback. If your callback handled the notification, it should return YES.
813
+
814
+ ## Scoping With Status Codes
815
+
816
+ If you pass a status code as the first option to this method, then your
817
+ notification callback will only be called if the response status matches
818
+ the code. For example, if you pass 201 (or SC.Request.CREATED) then
819
+ your method will only be called if the response status from the server
820
+ is 201.
821
+
822
+ You can also pass "generic" status codes such as 200, 300, or 400, which
823
+ will be invoked anytime the status code is the range if a more specific
824
+ notifier was not registered first and returned YES.
825
+
826
+ Finally, passing a status code of 0 or no status at all will cause your
827
+ method to be executed no matter what the resulting status is unless a
828
+ more specific notifier was registered and returned YES.
829
+
830
+ ## Callback Format
831
+
832
+ Your notification callback should expect to receive the Response object
833
+ as the first parameter plus any additional parameters that you pass.
834
+
835
+ @param {Number} status
836
+ @param {Object} target
837
+ @param {String|Function} action
838
+ @param {Hash} params
839
+ @returns {SC.Request} receiver
840
+ */
841
+ notify: function(status, target, action, params) {
842
+ // normalize status
843
+ var hasStatus = true;
844
+ if (SC.typeOf(status) !== 'number') {
845
+ params = $.makeArray(arguments).slice(2);
846
+ action = target;
847
+ target = status;
848
+ status = 0;
849
+ hasStatus = false;
850
+ } else {
851
+ params = $.makeArray(arguments).slice(3);
852
+ }
853
+
854
+ var listeners = get(this, 'listeners');
855
+ if (!listeners) { set(this, 'listeners', listeners = {}); }
856
+ if(!listeners[status]) { listeners[status] = []; }
857
+
858
+ listeners[status].push({target: target, action: action, params: params});
859
+
860
+ return this;
861
+ }
862
+
863
+ });
864
+
865
+ SC.Request.reopenClass({
866
+
867
+ /**
868
+ Helper method for quickly setting up a GET request.
869
+
870
+ @param {String} url of request
871
+ @returns {SC.Request} receiver
872
+ */
873
+ getUrl: function(url) {
874
+ return this.create().set('url', url).set('type', 'GET');
875
+ },
876
+
877
+ /**
878
+ Helper method for quickly setting up a HEAD request.
879
+
880
+ @param {String} url of request
881
+ @returns {SC.Request} receiver
882
+ */
883
+ headUrl: function(url) {
884
+ return this.create().set('url', url).set('type', 'HEAD');
885
+ },
886
+
887
+ /**
888
+ Helper method for quickly setting up a DELETE request.
889
+
890
+ @param {String} url of request
891
+ @returns {SC.Request} receiver
892
+ */
893
+ deleteUrl: function(url) {
894
+ return this.create().set('url', url).set('type', 'DELETE');
895
+ },
896
+
897
+ /**
898
+ Helper method for quickly setting up a POST request.
899
+
900
+ @param {String} url of request
901
+ @param {String} body
902
+ @returns {SC.Request} receiver
903
+ */
904
+ postUrl: function(url, body) {
905
+ var req = this.create().set('url', url).set('type', 'POST');
906
+ if (body) { set(req, 'body', body); }
907
+ return req;
908
+ },
909
+
910
+ /**
911
+ Helper method for quickly setting up a PUT request.
912
+
913
+ @param {String} url of request
914
+ @param {String} body
915
+ @returns {SC.Request} receiver
916
+ */
917
+ putUrl: function(url, body) {
918
+ var req = this.create().set('url', url).set('type', 'PUT');
919
+ if (body) { set(req, 'body', body); }
920
+ return req;
921
+ }
922
+
923
+ });
924
+
925
+ /**
926
+ @class
927
+
928
+ The request manager coordinates all of the active XHR requests. It will
929
+ only allow a certain number of requests to be active at a time; queuing
930
+ any others. This allows you more precise control over which requests load
931
+ in which order.
932
+ */
933
+ SC.Request.manager = SC.Object.create({
934
+
935
+ /**
936
+ Maximum number of concurrent requests allowed. 6 for all browsers.
937
+
938
+ @type Number
939
+ @default 6
940
+ */
941
+ maxRequests: 6,
942
+
943
+ /**
944
+ Current requests that are inflight.
945
+
946
+ @type Array
947
+ @default []
948
+ */
949
+ inflight: [],
950
+
951
+ /**
952
+ Requests that are pending and have not been started yet.
953
+
954
+ @type Array
955
+ @default []
956
+ */
957
+ pending: [],
958
+
959
+
960
+ // ..........................................................
961
+ // METHODS
962
+ //
963
+
964
+ /**
965
+ Invoked by the send() method on a request. This will create a new low-
966
+ level transport object and queue it if needed.
967
+
968
+ @param {SC.Request} request the request to send
969
+ @returns {SC.Object} response object
970
+ */
971
+ sendRequest: function(request) {
972
+ if (!request) { return null; }
973
+
974
+ // create low-level transport. copy all critical data for request over
975
+ // so that if the request has been reconfigured the transport will still
976
+ // work.
977
+ var response = get(request, 'responseClass').create({request: request});
978
+
979
+ // add to pending queue
980
+ get(this, 'pending').pushObject(response);
981
+ this.fireRequestIfNeeded();
982
+
983
+ return response;
984
+ },
985
+
986
+ /**
987
+ Cancels a specific request. If the request is pending it will simply
988
+ be removed. Otherwise it will actually be cancelled.
989
+
990
+ @param {Object} response a response object
991
+ @returns {Boolean} YES if cancelled
992
+ */
993
+ cancel: function(response) {
994
+ var pending = get(this, 'pending'),
995
+ inflight = get(this, 'inflight'),
996
+ idx;
997
+
998
+ if (pending.indexOf(response) >= 0) {
999
+ this.propertyWillChange('pending');
1000
+ pending.removeObject(response);
1001
+ this.propertyDidChange('pending');
1002
+ return true;
1003
+ } else if (inflight.indexOf(response) >= 0) {
1004
+ response.cancel();
1005
+
1006
+ inflight.removeObject(response);
1007
+ this.fireRequestIfNeeded();
1008
+ return true;
1009
+ }
1010
+
1011
+ return false;
1012
+ },
1013
+
1014
+ /**
1015
+ Cancels all inflight and pending requests.
1016
+
1017
+ @returns {Boolean} YES if any items were cancelled.
1018
+ */
1019
+ cancelAll: function() {
1020
+ if (get(this, 'pending').length || get(this, 'inflight').length) {
1021
+ set(this, 'pending', []);
1022
+ get(this, 'inflight').forEach(function(r) { r.cancel(); });
1023
+ set(this, 'inflight', []);
1024
+ return true;
1025
+ }
1026
+
1027
+ return false;
1028
+ },
1029
+
1030
+ /**
1031
+ Checks the inflight queue. If there is an open slot, this will move a
1032
+ request from pending to inflight.
1033
+
1034
+ @returns {Object} receiver
1035
+ */
1036
+ fireRequestIfNeeded: function() {
1037
+ var pending = get(this, 'pending'),
1038
+ inflight = get(this, 'inflight'),
1039
+ max = get(this, 'maxRequests'),
1040
+ next;
1041
+
1042
+ if ((pending.length>0) && (inflight.length<max)) {
1043
+ next = pending.shiftObject();
1044
+ inflight.pushObject(next);
1045
+ next.fire();
1046
+ }
1047
+ },
1048
+
1049
+ /**
1050
+ Called by a response/transport object when finishes running. Removes
1051
+ the transport from the queue and kicks off the next one.
1052
+ */
1053
+ transportDidClose: function(response) {
1054
+ get(this, 'inflight').removeObject(response);
1055
+ this.fireRequestIfNeeded();
1056
+ }
1057
+
1058
+ });
1059
+
1060
+ })({});
1061
+
1062
+
1063
+ (function(exports) {
1064
+ // ==========================================================================
1065
+ // Project: SproutCore AJAX
1066
+ // Copyright: ©2011 Paul Chavard
1067
+ // License: Licensed under MIT license (see license.js)
1068
+ // ==========================================================================
1069
+
1070
+
1071
+ })({});