Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions modules/angular2/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
* class.
*/
import {bind, Binding} from 'angular2/di';
import {Http} from 'angular2/src/http/http';
import {Http, Jsonp} from 'angular2/src/http/http';
import {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend';
import {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend';
import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
import {BrowserJsonp} from 'angular2/src/http/backends/browser_jsonp';
import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
import {ConnectionBackend} from 'angular2/src/http/interfaces';

Expand All @@ -26,7 +28,8 @@ export {
export {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
export {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
export {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend';
export {Http} from 'angular2/src/http/http';
export {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend';
export {Http, Jsonp} from 'angular2/src/http/http';

export {Headers} from 'angular2/src/http/headers';

Expand Down Expand Up @@ -65,3 +68,12 @@ export var httpInjectables: List<any> = [
bind(ResponseOptions).toClass(BaseResponseOptions),
Http
];

export var jsonpInjectables: List<any> = [
bind(ConnectionBackend)
.toClass(JSONPBackend),
BrowserJsonp,
bind(RequestOptions).toClass(BaseRequestOptions),
bind(ResponseOptions).toClass(BaseResponseOptions),
Jsonp
];
4 changes: 4 additions & 0 deletions modules/angular2/src/facade/lang.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ class BaseException extends Error {
}
}

Error makeTypeError([String message = ""]) {
return new BaseException(message);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dart's TypeError class doesn't let me attach an arbitrary message. I guess Dart doesn't use it as a catch-all exception the way JS does

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha.

}

const _NAN_KEY = const Object();

// Dart can have identical(str1, str2) == false while str1 == str2
Expand Down
4 changes: 4 additions & 0 deletions modules/angular2/src/facade/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class BaseException extends Error {
toString(): string { return this.message; }
}

export function makeTypeError(message?: string): Error {
return new TypeError(message);
}

export var Math = _global.Math;
export var Date = _global.Date;

Expand Down
59 changes: 59 additions & 0 deletions modules/angular2/src/http/backends/browser_jsonp.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
library angular2.src.http.backends.browser_jsonp;
import 'package:angular2/di.dart';
import 'dart:html' show document;
import 'dart:js' show context, JsObject, JsArray;

int _nextRequestId = 0;
const JSONP_HOME = '__ng_jsonp__';

var _jsonpConnections = null;

JsObject _getJsonpConnections() {
if (_jsonpConnections == null) {
_jsonpConnections = context[JSONP_HOME] = new JsObject(context['Object']);
}
return _jsonpConnections;
}

// Make sure not to evaluate this in a non-browser environment!
@Injectable()
class BrowserJsonp {
// Construct a <script> element with the specified URL
dynamic build(String url) {
var node = document.createElement('script');
node.src = url;
return node;
}

nextRequestID() {
return "__req${_nextRequestId++}";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just looking at the AngularJS httpBackend implementation and found that the id is toStringed as base 36: angular/angular.js@fd38655#diff-725ec6acb6c38b9cec7c30861f35be8cR31

Do you have any idea what that's for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra complexity? Igor might have a convincing reason for it

}

requestCallback(String id) {
return """${JSONP_HOME}.${id}.finished""";
}

exposeConnection(String id, dynamic connection) {
var connections = _getJsonpConnections();
var wrapper = new JsObject(context['Object']);

wrapper['_id'] = id;
wrapper['__dart__'] = connection;
wrapper['finished'] = ([dynamic data]) => connection.finished(data);

connections[id] = wrapper;
}

removeConnection(String id) {
var connections = _getJsonpConnections();
connections[id] = null;
}

// Attach the <script> element to the DOM
send(dynamic node) { document.body.append(node); }

// Remove <script> element from the DOM
cleanup(dynamic node) {
node.remove();
}
}
48 changes: 48 additions & 0 deletions modules/angular2/src/http/backends/browser_jsonp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {Injectable} from 'angular2/di';
import {global} from 'angular2/src/facade/lang';

let _nextRequestId = 0;
export const JSONP_HOME = '__ng_jsonp__';
var _jsonpConnections = null;

function _getJsonpConnections(): {[key: string]: any} {
if (_jsonpConnections === null) {
_jsonpConnections = global[JSONP_HOME] = {};
}
return _jsonpConnections;
}

// Make sure not to evaluate this in a non-browser environment!
@Injectable()
export class BrowserJsonp {
// Construct a <script> element with the specified URL
build(url: string): any {
let node = document.createElement('script');
node.src = url;
return node;
}

nextRequestID(): string { return `__req${_nextRequestId++}`; }

requestCallback(id: string): string { return `${JSONP_HOME}.${id}.finished`; }

exposeConnection(id: string, connection: any) {
let connections = _getJsonpConnections();
connections[id] = connection;
}

removeConnection(id: string) {
var connections = _getJsonpConnections();
connections[id] = null;
}

// Attach the <script> element to the DOM
send(node: any) { document.body.appendChild(<Node>(node)); }

// Remove <script> element from the DOM
cleanup(node: any) {
if (node.parentNode) {
node.parentNode.removeChild(<Node>(node));
}
}
}
97 changes: 97 additions & 0 deletions modules/angular2/src/http/backends/jsonp_backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {ConnectionBackend, Connection} from '../interfaces';
import {ReadyStates, RequestMethods, RequestMethodsMap} from '../enums';
import {Request} from '../static_request';
import {Response} from '../static_response';
import {ResponseOptions, BaseResponseOptions} from '../base_response_options';
import {Injectable} from 'angular2/di';
import {BrowserJsonp} from './browser_jsonp';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {StringWrapper, isPresent, ENUM_INDEX, makeTypeError} from 'angular2/src/facade/lang';

export class JSONPConnection implements Connection {
readyState: ReadyStates;
request: Request;
response: EventEmitter;
private _id: string;
private _script: Element;
private _responseData: any;
private _finished: boolean = false;

constructor(req: Request, private _dom: BrowserJsonp,
private baseResponseOptions?: ResponseOptions) {
if (req.method !== RequestMethods.GET) {
throw makeTypeError("JSONP requests must use GET request method.");
}
this.request = req;
this.response = new EventEmitter();
this.readyState = ReadyStates.LOADING;
this._id = _dom.nextRequestID();

_dom.exposeConnection(this._id, this);

// Workaround Dart
// url = url.replace(/=JSONP_CALLBACK(&|$)/, `generated method`);
let callback = _dom.requestCallback(this._id);
let url: string = req.url;
if (url.indexOf('=JSONP_CALLBACK&') > -1) {
url = StringWrapper.replace(url, '=JSONP_CALLBACK&', `=${callback}&`);
} else if (url.lastIndexOf('=JSONP_CALLBACK') === url.length - '=JSONP_CALLBACK'.length) {
url = StringWrapper.substring(url, 0, url.length - '=JSONP_CALLBACK'.length) + `=${callback}`;
}

let script = this._script = _dom.build(url);

script.addEventListener('load', (event) => {
if (this.readyState === ReadyStates.CANCELLED) return;
this.readyState = ReadyStates.DONE;
_dom.cleanup(script);
if (!this._finished) {
ObservableWrapper.callThrow(
this.response, makeTypeError('JSONP injected script did not invoke callback.'));
return;
}

let responseOptions = new ResponseOptions({body: this._responseData});
if (isPresent(this.baseResponseOptions)) {
responseOptions = this.baseResponseOptions.merge(responseOptions);
}

ObservableWrapper.callNext(this.response, new Response(responseOptions));
});

script.addEventListener('error', (error) => {
if (this.readyState === ReadyStates.CANCELLED) return;
this.readyState = ReadyStates.DONE;
_dom.cleanup(script);
ObservableWrapper.callThrow(this.response, error);
});

_dom.send(script);
}

finished(data?: any) {
// Don't leak connections
this._finished = true;
this._dom.removeConnection(this._id);
if (this.readyState === ReadyStates.CANCELLED) return;
this._responseData = data;
}

dispose(): void {
this.readyState = ReadyStates.CANCELLED;
let script = this._script;
this._script = null;
if (isPresent(script)) {
this._dom.cleanup(script);
}
ObservableWrapper.callReturn(this.response);
}
}

@Injectable()
export class JSONPBackend implements ConnectionBackend {
constructor(private _browserJSONP: BrowserJsonp, private _baseResponseOptions: ResponseOptions) {}
createConnection(request: Request): JSONPConnection {
return new JSONPConnection(request, this._browserJSONP, this._baseResponseOptions);
}
}
31 changes: 29 additions & 2 deletions modules/angular2/src/http/http.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isString, isPresent, isBlank} from 'angular2/src/facade/lang';
import {isString, isPresent, isBlank, makeTypeError} from 'angular2/src/facade/lang';
import {Injectable} from 'angular2/src/di/decorators';
import {IRequestOptions, Connection, ConnectionBackend} from './interfaces';
import {Request} from './static_request';
Expand Down Expand Up @@ -102,7 +102,7 @@ function mergeOptions(defaultOpts, providedOpts, method, url): RequestOptions {
**/
@Injectable()
export class Http {
constructor(private _backend: ConnectionBackend, private _defaultOptions: RequestOptions) {}
constructor(protected _backend: ConnectionBackend, protected _defaultOptions: RequestOptions) {}

/**
* Performs any type of http request. First argument is required, and can either be a url or
Expand Down Expand Up @@ -176,3 +176,30 @@ export class Http {
RequestMethods.HEAD, url)));
}
}

@Injectable()
export class Jsonp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}

/**
* Performs any type of http request. First argument is required, and can either be a url or
* a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions}
* object can be provided as the 2nd argument. The options object will be merged with the values
* of {@link BaseRequestOptions} before performing the request.
*/
request(url: string | Request, options?: IRequestOptions): EventEmitter {
var responseObservable: EventEmitter;
if (isString(url)) {
url = new Request(mergeOptions(this._defaultOptions, options, RequestMethods.GET, url));
}
if (url instanceof Request) {
if (url.method !== RequestMethods.GET) {
makeTypeError('JSONP requests must use GET request method.');
}
responseObservable = httpRequest(this._backend, url);
}
return responseObservable;
}
}
7 changes: 7 additions & 0 deletions modules/angular2/src/http/http_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
library angular2.src.http.http_utils;
import 'dart:js' show JsObject;
import 'dart:collection' show LinkedHashMap, LinkedHashSet;

bool isJsObject(o) {
return o is JsObject || o is LinkedHashMap || o is LinkedHashSet;
}
1 change: 1 addition & 0 deletions modules/angular2/src/http/http_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {isJsObject} from 'angular2/src/facade/lang';
10 changes: 2 additions & 8 deletions modules/angular2/src/http/static_response.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import {ResponseTypes} from './enums';
import {
BaseException,
CONST_EXPR,
isJsObject,
isString,
isPresent,
Json
} from 'angular2/src/facade/lang';
import {BaseException, CONST_EXPR, isString, isPresent, Json} from 'angular2/src/facade/lang';
import {Headers} from './headers';
import {ResponseOptions} from './base_response_options';
import {isJsObject} from './http_utils';

/**
* Creates `Response` instances from provided values.
Expand Down
Loading