Skip to content

Commit 53e0cd6

Browse files
Merge pull request #98 from angular/master
Update upstream
2 parents c82433e + 840b5f0 commit 53e0cd6

File tree

9 files changed

+236
-171
lines changed

9 files changed

+236
-171
lines changed

angularFiles.js

+2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ var angularFiles = {
131131
],
132132
'ngRoute': [
133133
'src/shallowCopy.js',
134+
'src/routeToRegExp.js',
134135
'src/ngRoute/route.js',
135136
'src/ngRoute/routeParams.js',
136137
'src/ngRoute/directive/ngView.js'
@@ -140,6 +141,7 @@ var angularFiles = {
140141
'src/ngSanitize/filter/linky.js'
141142
],
142143
'ngMock': [
144+
'src/routeToRegExp.js',
143145
'src/ngMock/angular-mocks.js',
144146
'src/ngMock/browserTrigger.js'
145147
],

src/ng/directive/form.js

+25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
var nullFormCtrl = {
66
$addControl: noop,
7+
$getControls: valueFn([]),
78
$$renameControl: nullFormRenameControl,
89
$removeControl: noop,
910
$setValidity: noop,
@@ -159,6 +160,30 @@ FormController.prototype = {
159160
control.$$parentForm = this;
160161
},
161162

163+
/**
164+
* @ngdoc method
165+
* @name form.FormController#$getControls
166+
* @returns {Array} the controls that are currently part of this form
167+
*
168+
* @description
169+
* This method returns a **shallow copy** of the controls that are currently part of this form.
170+
* The controls can be instances of {@link form.FormController `FormController`}
171+
* ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}.
172+
* If you need access to the controls of child-forms, you have to call `$getControls()`
173+
* recursively on them.
174+
* This can be used for example to iterate over all controls to validate them.
175+
*
176+
* The controls can be accessed normally, but adding to, or removing controls from the array has
177+
* no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and
178+
* {@link form.FormController#$removeControl `$removeControl()`} for this use-case.
179+
* Likewise, adding a control to, or removing a control from the form is not reflected
180+
* in the shallow copy. That means you should get a fresh copy from `$getControls()` every time
181+
* you need access to the controls.
182+
*/
183+
$getControls: function() {
184+
return shallowCopy(this.$$controls);
185+
},
186+
162187
// Private API: rename a form control
163188
$$renameControl: function(control, newName) {
164189
var oldName = control.$name;

src/ngAria/aria.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,10 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
387387
if ($aria.config('bindKeydown') && !attr.ngKeydown && !attr.ngKeypress && !attr.ngKeyup) {
388388
elem.on('keydown', function(event) {
389389
var keyCode = event.which || event.keyCode;
390-
if (keyCode === 32 || keyCode === 13) {
390+
391+
if (keyCode === 13 || keyCode === 32) {
392+
// Prevent the default browser behavior (e.g. scrolling when pressing spacebar).
393+
event.preventDefault();
391394
scope.$apply(callback);
392395
}
393396

src/ngMock/angular-mocks.js

+15-41
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
/* global routeToRegExp: false */
4+
35
/**
46
* @ngdoc object
57
* @name angular.mock
@@ -1282,7 +1284,7 @@ angular.mock.dump = function(object) {
12821284
* ## Matching route requests
12831285
*
12841286
* For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon
1285-
* delimited matching of the url path, ignoring the query string. This allows declarations
1287+
* delimited matching of the url path, ignoring the query string and trailing slashes. This allows declarations
12861288
* similar to how application routes are configured with `$routeProvider`. Because these methods convert
12871289
* the definition url to regex, declaration order is important. Combined with query parameter parsing,
12881290
* the following is possible:
@@ -1481,8 +1483,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
14811483
* ```
14821484
* – The respond method takes a set of static data to be returned or a function that can
14831485
* return an array containing response status (number), response data (Array|Object|string),
1484-
* response headers (Object), and the text for the status (string). The respond method returns
1485-
* the `requestHandler` object for possible overrides.
1486+
* response headers (Object), HTTP status text (string), and XMLHttpRequest status (string:
1487+
* `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler`
1488+
* object for possible overrides.
14861489
*/
14871490
$httpBackend.when = function(method, url, data, headers, keys) {
14881491

@@ -1663,40 +1666,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
16631666
* See {@link ngMock.$httpBackend#when `when`} for more info.
16641667
*/
16651668
$httpBackend.whenRoute = function(method, url) {
1666-
var pathObj = parseRoute(url);
1669+
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
16671670
return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
16681671
};
16691672

1670-
function parseRoute(url) {
1671-
var ret = {
1672-
regexp: url
1673-
},
1674-
keys = ret.keys = [];
1675-
1676-
if (!url || !angular.isString(url)) return ret;
1677-
1678-
url = url
1679-
.replace(/([().])/g, '\\$1')
1680-
.replace(/(\/)?:(\w+)([?*])?/g, function(_, slash, key, option) {
1681-
var optional = option === '?' ? option : null;
1682-
var star = option === '*' ? option : null;
1683-
keys.push({ name: key, optional: !!optional });
1684-
slash = slash || '';
1685-
return ''
1686-
+ (optional ? '' : slash)
1687-
+ '(?:'
1688-
+ (optional ? slash : '')
1689-
+ (star && '(.+?)' || '([^/]+)')
1690-
+ (optional || '')
1691-
+ ')'
1692-
+ (optional || '');
1693-
})
1694-
.replace(/([/$*])/g, '\\$1');
1695-
1696-
ret.regexp = new RegExp('^' + url, 'i');
1697-
return ret;
1698-
}
1699-
17001673
/**
17011674
* @ngdoc method
17021675
* @name $httpBackend#expect
@@ -1717,14 +1690,15 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
17171690
* order to change how a matched request is handled.
17181691
*
17191692
* - respond –
1720-
* ```
1721-
* { function([status,] data[, headers, statusText])
1722-
* | function(function(method, url, data, headers, params)}
1723-
* ```
1693+
* ```js
1694+
* {function([status,] data[, headers, statusText])
1695+
* | function(function(method, url, data, headers, params)}
1696+
* ```
17241697
* – The respond method takes a set of static data to be returned or a function that can
17251698
* return an array containing response status (number), response data (Array|Object|string),
1726-
* response headers (Object), and the text for the status (string). The respond method returns
1727-
* the `requestHandler` object for possible overrides.
1699+
* response headers (Object), HTTP status text (string), and XMLHttpRequest status (string:
1700+
* `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler`
1701+
* object for possible overrides.
17281702
*/
17291703
$httpBackend.expect = function(method, url, data, headers, keys) {
17301704

@@ -1876,7 +1850,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
18761850
* See {@link ngMock.$httpBackend#expect `expect`} for more info.
18771851
*/
18781852
$httpBackend.expectRoute = function(method, url) {
1879-
var pathObj = parseRoute(url);
1853+
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
18801854
return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys);
18811855
};
18821856

src/ngRoute/route.js

+3-43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
/* global routeToRegExp: false */
34
/* global shallowCopy: false */
45

56
// `isArray` and `isObject` are necessary for `shallowCopy()` (included via `src/shallowCopy.js`).
@@ -224,7 +225,7 @@ function $RouteProvider() {
224225
}
225226
routes[path] = angular.extend(
226227
routeCopy,
227-
path && pathRegExp(path, routeCopy)
228+
path && routeToRegExp(path, routeCopy)
228229
);
229230

230231
// create redirection for trailing slashes
@@ -235,7 +236,7 @@ function $RouteProvider() {
235236

236237
routes[redirectPath] = angular.extend(
237238
{redirectTo: path},
238-
pathRegExp(redirectPath, routeCopy)
239+
routeToRegExp(redirectPath, routeCopy)
239240
);
240241
}
241242

@@ -253,47 +254,6 @@ function $RouteProvider() {
253254
*/
254255
this.caseInsensitiveMatch = false;
255256

256-
/**
257-
* @param path {string} path
258-
* @param opts {Object} options
259-
* @return {?Object}
260-
*
261-
* @description
262-
* Normalizes the given path, returning a regular expression
263-
* and the original path.
264-
*
265-
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
266-
*/
267-
function pathRegExp(path, opts) {
268-
var insensitive = opts.caseInsensitiveMatch,
269-
ret = {
270-
originalPath: path,
271-
regexp: path
272-
},
273-
keys = ret.keys = [];
274-
275-
path = path
276-
.replace(/([().])/g, '\\$1')
277-
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
278-
var optional = (option === '?' || option === '*?') ? '?' : null;
279-
var star = (option === '*' || option === '*?') ? '*' : null;
280-
keys.push({ name: key, optional: !!optional });
281-
slash = slash || '';
282-
return ''
283-
+ (optional ? '' : slash)
284-
+ '(?:'
285-
+ (optional ? slash : '')
286-
+ (star && '(.+?)' || '([^/]+)')
287-
+ (optional || '')
288-
+ ')'
289-
+ (optional || '');
290-
})
291-
.replace(/([/$*])/g, '\\$1');
292-
293-
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
294-
return ret;
295-
}
296-
297257
/**
298258
* @ngdoc method
299259
* @name $routeProvider#otherwise

src/routeToRegExp.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
/* global routeToRegExp: true */
4+
5+
/**
6+
* @param path {string} path
7+
* @param opts {Object} options
8+
* @return {?Object}
9+
*
10+
* @description
11+
* Normalizes the given path, returning a regular expression
12+
* and the original path.
13+
*
14+
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
15+
*/
16+
function routeToRegExp(path, opts) {
17+
var keys = [];
18+
19+
var pattern = path
20+
.replace(/([().])/g, '\\$1')
21+
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
22+
var optional = option === '?' || option === '*?';
23+
var star = option === '*' || option === '*?';
24+
keys.push({ name: key, optional: optional });
25+
slash = slash || '';
26+
return (
27+
(optional ? '(?:' + slash : slash + '(?:') +
28+
(star ? '([^?#]+?)' : '([^/?#]+)') +
29+
(optional ? '?)?' : ')')
30+
);
31+
})
32+
.replace(/([/$*])/g, '\\$1');
33+
34+
if (opts.ignoreTrailingSlashes) {
35+
pattern = pattern.replace(/\/+$/, '') + '/*';
36+
}
37+
38+
return {
39+
originalPath: path,
40+
keys: keys,
41+
regexp: new RegExp(
42+
'^' + pattern + '(?:[?#]|$)',
43+
opts.caseInsensitiveMatch ? 'i' : ''
44+
)
45+
};
46+
}

test/ng/directive/formSpec.js

+46
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,52 @@ describe('form', function() {
12001200
});
12011201
});
12021202

1203+
describe('$getControls', function() {
1204+
it('should return an empty array if the controller has no controls', function() {
1205+
doc = $compile('<form name="testForm"></form>')(scope);
1206+
1207+
scope.$digest();
1208+
1209+
var formCtrl = scope.testForm;
1210+
1211+
expect(formCtrl.$getControls()).toEqual([]);
1212+
});
1213+
1214+
it('should return a shallow copy of the form controls', function() {
1215+
doc = $compile(
1216+
'<form name="testForm">' +
1217+
'<input ng-model="named" name="foo">' +
1218+
'<div ng-form>' +
1219+
'<input ng-model="named" name="foo">' +
1220+
'</div>' +
1221+
'</form>')(scope);
1222+
1223+
scope.$digest();
1224+
1225+
var form = doc,
1226+
formCtrl = scope.testForm,
1227+
formInput = form.children('input').eq(0),
1228+
formInputCtrl = formInput.controller('ngModel'),
1229+
nestedForm = form.find('div'),
1230+
nestedFormCtrl = nestedForm.controller('form'),
1231+
nestedInput = nestedForm.children('input').eq(0),
1232+
nestedInputCtrl = nestedInput.controller('ngModel');
1233+
1234+
var controls = formCtrl.$getControls();
1235+
1236+
expect(controls).not.toBe(formCtrl.$$controls);
1237+
1238+
controls.push('something');
1239+
expect(formCtrl.$$controls).not.toContain('something');
1240+
1241+
expect(controls[0]).toBe(formInputCtrl);
1242+
expect(controls[1]).toBe(nestedFormCtrl);
1243+
1244+
var nestedControls = controls[1].$getControls();
1245+
1246+
expect(nestedControls[0]).toBe(nestedInputCtrl);
1247+
});
1248+
});
12031249

12041250
it('should rename nested form controls when interpolated name changes', function() {
12051251
scope.idA = 'A';

0 commit comments

Comments
 (0)