rasputin 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ })({});