From ab40a5f38d7fc036b25ecca1b3cd6677eed35cd6 Mon Sep 17 00:00:00 2001 From: David Chang Date: Thu, 17 Jan 2013 20:08:27 -0800 Subject: [PATCH 1/4] [issue1797] add ng-open attribute --- src/jqLite.js | 4 ++-- src/ng/directive/booleanAttrs.js | 31 +++++++++++++++++++++++++++ test/ng/directive/booleanAttrsSpec.js | 10 +++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 46e0a73c3c54..f84e7d1fe896 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -353,11 +353,11 @@ var JQLitePrototype = JQLite.prototype = { // value on get. ////////////////////////////////////////// var BOOLEAN_ATTR = {}; -forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) { +forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { BOOLEAN_ATTR[lowercase(value)] = value; }); var BOOLEAN_ELEMENTS = {}; -forEach('input,select,option,textarea,button,form'.split(','), function(value) { +forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { BOOLEAN_ELEMENTS[uppercase(value)] = true; }); diff --git a/src/ng/directive/booleanAttrs.js b/src/ng/directive/booleanAttrs.js index 8902c35d2d4b..864c12c118d9 100644 --- a/src/ng/directive/booleanAttrs.js +++ b/src/ng/directive/booleanAttrs.js @@ -272,6 +272,37 @@ * @param {string} expression Angular expression that will be evaluated. */ +/** + * @ngdoc directive + * @name ng.directive:ngOpen + * @restrict A + * + * @description + * The HTML specs do not require browsers to preserve the special attributes such as open. + * (The presence of them means true and absence means false) + * This prevents the angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduce the `ngMultiple` directive. + * + * @example + + + Check me check multiple:
+
+ Show/Hide me +
+
+ + it('should toggle open', function() { + expect(element('#details').prop('open')).toBeFalsy(); + input('open').check(); + expect(element('#details').prop('open')).toBeTruthy(); + }); + +
+ * + * @element DETAILS + * @param {string} expression Angular expression that will be evaluated. + */ var ngAttributeAliasDirectives = {}; diff --git a/test/ng/directive/booleanAttrsSpec.js b/test/ng/directive/booleanAttrsSpec.js index 435ffcb9727f..0ce6b555108e 100644 --- a/test/ng/directive/booleanAttrsSpec.js +++ b/test/ng/directive/booleanAttrsSpec.js @@ -74,6 +74,16 @@ describe('boolean attr directives', function() { $rootScope.$digest(); expect(element.attr('multiple')).toBeTruthy(); })); + + it('should bind open', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope) + $rootScope.isOpen=false; + $rootScope.$digest(); + expect(element.attr('open')).toBeFalsy(); + $rootScope.isOpen=true; + $rootScope.$digest(); + expect(element.attr('open')).toBeTruthy(); + })); }); From 03d48c3df3b03065ac42e7354379bf111246406f Mon Sep 17 00:00:00 2001 From: David Chang Date: Wed, 30 Jan 2013 14:52:57 -0800 Subject: [PATCH 2/4] setting default headers and adding short function for PATCH --- test/ng/httpSpec.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 1473ab1ccc3f..3f6c09f8bc39 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -406,6 +406,15 @@ describe('$http', function() { $httpBackend.flush(); }); + it('should set default headers for PATCH request', function() { + $httpBackend.expect('PATCH', '/url', 'messageBody', function(headers) { + return headers['Accept'] == 'application/json, text/plain, */*' && + headers['Content-Type'] == 'application/json;charset=utf-8'; + }).respond(''); + + $http({url: '/url', method: 'PATCH', headers: {}, data: 'messageBody'}); + $httpBackend.flush(); + }); it('should set default headers for custom HTTP method', function() { $httpBackend.expect('FOO', '/url', undefined, function(headers) { @@ -464,11 +473,13 @@ describe('$http', function() { $httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('DELETE', '/url', undefined, checkXSRF('secret')).respond(''); + $httpBackend.expect('PATCH', '/url', undefined, checkXSRF('secret')).respond(''); $http({url: '/url', method: 'GET'}); $http({url: '/url', method: 'POST', headers: {'S-ome': 'Header'}}); $http({url: '/url', method: 'PUT', headers: {'Another': 'Header'}}); $http({url: '/url', method: 'DELETE', headers: {}}); + $http({url: '/url', method: 'PATCH'}); $httpBackend.flush(); })); @@ -553,6 +564,17 @@ describe('$http', function() { $httpBackend.expect('JSONP', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.jsonp('/url', {headers: {'Custom': 'Header'}}); }); + + it('should have patch()', function() { + $httpBackend.expect('PATCH', '/url').respond(''); + $http.patch('/url'); + }); + + + it('patch() should allow config param', function() { + $httpBackend.expect('PATCH', '/url', 'some-data', checkHeader('Custom', 'Header')).respond(''); + $http.patch('/url', 'some-data', {headers: {'Custom': 'Header'}}); + }); }); From 6da99cc9347576f4b42c574ead92becb9627343f Mon Sep 17 00:00:00 2001 From: David Chang Date: Wed, 30 Jan 2013 14:57:25 -0800 Subject: [PATCH 3/4] setting default headers and adding short function for PATCH --- src/ng/http.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ng/http.js b/src/ng/http.js index ed9e6712c89b..41e558abe36c 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -148,7 +148,8 @@ function $HttpProvider() { 'Accept': 'application/json, text/plain, */*' }, post: {'Content-Type': 'application/json;charset=utf-8'}, - put: {'Content-Type': 'application/json;charset=utf-8'} + put: {'Content-Type': 'application/json;charset=utf-8'}, + patch: {'Content-Type': 'application/json;charset=utf-8'} } }; @@ -654,7 +655,7 @@ function $HttpProvider() { * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ - createShortMethodsWithData('post', 'put'); + createShortMethodsWithData('post', 'put', 'patch'); /** * @ngdoc property From fe39c76aacf8102be6501e12f7722c5752d43172 Mon Sep 17 00:00:00 2001 From: David Chang Date: Wed, 30 Jan 2013 15:53:42 -0800 Subject: [PATCH 4/4] Functionality to send urlencoded request data instead of only JSON stringified --- src/Angular.js | 27 +++++++++++++++++++++++++++ src/ng/http.js | 15 +++++++++++++-- test/AngularSpec.js | 10 ++++++++++ test/ng/httpSpec.js | 27 +++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 21b3ef070eef..37beb5026a60 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -740,6 +740,33 @@ function fromJson(json) { : json; } +function serialize(obj, prefix) { + var str = []; + for(var p in obj) { + var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p]; + str.push(typeof v == "object" ? + serialize(v, k) : + encodeURIComponent(k) + "=" + encodeURIComponent(v)); + } + return str.join("&"); +} + +/** + * @ngdoc function + * @name angular.toUrlEncodedString + * @function + * + * @description + * URL encodes a string, following jQuery's param function, + * but using a pure JavaScript solution from + * http://stackoverflow.com/questions/1714786/querystring-encoding-of-a-javascript-object + * + * @param {Object} object Object to serialize into a url encoded string + * @returns {string} A url encoded string + */ +function toUrlEncodedString(object) { + return (isString(object) && object) || serialize(object); +} function toBoolean(value) { if (value && value.length !== 0) { diff --git a/src/ng/http.js b/src/ng/http.js index 41e558abe36c..2778835d132c 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -142,6 +142,11 @@ function $HttpProvider() { return isObject(d) && !isFile(d) ? toJson(d) : d; }], + // transform outgoing request data by Url Encoding + transformRequestByUrlEncode: [function(d) { + return isObject(d) && !isFile(d) ? toUrlEncodedString(d) : d; + }], + // default headers headers: { common: { @@ -417,11 +422,13 @@ function $HttpProvider() { * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for * caching. * - **timeout** – `{number}` – timeout in milliseconds. - * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the + * - **withCredentials** - `{boolean}` - whether or not to set the `withCredentials` flag on the * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 * requests with credentials} for more information. * - **responseType** - `{string}` - see {@link * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. + * - **urlEncodeRequestData** - `{boolean}` - whether or not to URL encode request data. Default + * behavior is to JSON stringify the request data * * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the * standard `then` method and two http specific methods: `success` and `error`. The `then` @@ -514,7 +521,7 @@ function $HttpProvider() { function $http(config) { config.method = uppercase(config.method); - var reqTransformFn = config.transformRequest || defaults.transformRequest, + var reqTransformFn = config.transformRequest || ((config.urlEncodeRequestData || defaults.urlEncodeRequestData) && defaults.transformRequestByUrlEncode) || defaults.transformRequest, respTransformFn = config.transformResponse || defaults.transformResponse, defHeaders = defaults.headers, xsrfToken = isSameDomain(config.url, $browser.url()) ? @@ -524,6 +531,10 @@ function $HttpProvider() { reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn), promise; + if(config.urlEncodeRequestData || defaults.urlEncodeRequestData) { + reqHeaders['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'; + } + // strip content-type if data is undefined if (isUndefined(config.data)) { delete reqHeaders['Content-Type']; diff --git a/test/AngularSpec.js b/test/AngularSpec.js index f5638b9c5288..9ce726a3a56a 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -640,4 +640,14 @@ describe('angular', function() { expect(toJson({key: $rootScope})).toEqual('{"key":"$SCOPE"}'); })); }); + + describe('toUrlEncodedString', function() { + + it('should encode objects properly', function() { + expect(toUrlEncodedString({ })).toEqual(''); + expect(toUrlEncodedString({ one: "one", two: 2 })).toEqual('one=one&two=2'); + expect(toUrlEncodedString({ a:1, b:{ c:3, d:2 } })).toEqual('a=1&b%5Bc%5D=3&b%5Bd%5D=2'); + expect(toUrlEncodedString({ a:1, b:{ c:3, d:[1,2,3] } })).toEqual('a=1&b%5Bc%5D=3&b%5Bd%5D%5B0%5D=1&b%5Bd%5D%5B1%5D=2&b%5Bd%5D%5B2%5D=3'); + }); + }); }); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 3f6c09f8bc39..e42cc208e675 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -439,6 +439,33 @@ describe('$http', function() { $httpBackend.flush(); }); + it('should change content-type header to urlencoded if specified in config', function() { + $httpBackend.expect('POST', '/url', 'messageBody', function(headers) { + return headers['Content-Type'] == 'application/x-www-form-urlencoded;charset=utf-8'; + }).respond(''); + + $http({url: '/url', method: 'POST', data: 'messageBody', urlEncodeRequestData: true }); + $httpBackend.flush(); + }); + + it('should automatically JSON encode request data if not specified in config', function() { + $httpBackend.expect('POST', '/url', '{"one":"one","two":2}', function(headers) { + return headers['Content-Type'] == 'application/json;charset=utf-8'; + }).respond(''); + + $http({url: '/url', method: 'POST', data: { one: 'one', two: 2 } }); + $httpBackend.flush(); + }); + + it('should URL encode request data if specified in config', function() { + $httpBackend.expect('POST', '/url', 'one=one&two=2', function(headers) { + return headers['Content-Type'] == 'application/x-www-form-urlencoded;charset=utf-8'; + }).respond(''); + + $http({url: '/url', method: 'POST', data: { one: 'one', two: 2 }, urlEncodeRequestData: true }); + $httpBackend.flush(); + }); + it('should not set XSRF cookie for cross-domain requests', inject(function($browser) { $browser.cookies('XSRF-TOKEN', 'secret'); $browser.url('http://host.com/base');