Compare commits

...

43 Commits

Author SHA1 Message Date
Igor Minar cfbe1a701a revert: fix($timeout): make $flush handle new $timeouts added in $timeout callbacks
This reverts commit 1e5d36d4b0.

Breaks tests at google, needs more investigation.
2016-06-14 08:08:25 -07:00
Pete Bacon Darwin aa38ded59d chore(version-info): disable remote requests when offline
When the internet is not accessible build scripts are unnecessarily slow
because of failed attempts to download git tag and CDN version information
from remote servers.

This commit does a synchronous check of internet connectivity before attempting
any remote requests.

Closes #14759
2016-06-13 15:58:21 +01:00
Wesley Cho b03957f004 feat(limitTo): add support for array-like objects
Fixes #14657

Closes #14694
2016-06-10 21:14:04 +03:00
Martin Staffa 56f33af890 fix(ngMessages): create new scope for ngMessage, clean it up correctly
Previously, ngMessage elements used the same scope as ngMessages. When ngMessage
has interpolation in the textContent, then removing the message would not remove
the watcher from the scope - it would only be removed when the whole ngMessages
element was removed.

This commit changes the ngMessage transclude function to create a new child scope
instead, which can be destroyed safely when the message element is removed and
the message is detached

Fixes #14307
PR (#14308)
2016-06-10 14:49:08 +02:00
CarlosMiguelDesarrollo 41bea59eb7 chore(ngAnimate): delete an unused conditional statement
Delete a conditional statement that appears to have only been for debugging.
It is marked with a TODO tag to be deleted.

Closes #14282
2016-06-10 12:27:24 +01:00
Georgios Kalpakas 6d0dcca6f1 fix($routeProvider): do not deep-copy route definition objects
Deep-copying route definition objects can break specific custom implementations of `$sce` (used to
trust a `templateUrl` as RESOURCE_URL). The purpose of copying route definition objects was to guard
against the user's modifying the route definition object after route registration, while still
capturing inherited properties.
As suggested by @IgorMinar in https://github.com/angular/angular.js/pull/14699#discussion_r66480539,
we can achieve both _and_ support custom `$sce` implementations, by shallow-copying instead.

This is an alternative implementation for #14699, which avoids the breaking change.

Fixes #14478
Closes #14699

Closes #14750
2016-06-10 14:09:40 +03:00
mirabilos f423dc6b51 fix(input[email]): improve email address validation
**Limit size of local-part and total path size in eMail addresses**

RFC 5321 §4.5.3.1.1 ⇒ local-part can have up to 64 octets
RFC 5321 §4.5.3.1.3 ⇒ path “including the punctuation and
element separators” can have up to 256 octets
RFC 5321 §4.1.2 specifies path as ‘<’ + mailbox¹ + ‘>’ in
the best case, leaving us 254 octets

The limitation of the total path size to 254 octets leaves
at most 252 octets (one local-part, one ‘@’) for the domain,
which means we don’t need to explicitly check the domain
size any more (removing the assertion after the ‘@’).

① RFC 821/5321 “mailbox” is the same as RFC 822 “addr-spec”

**Optimise eMail address regex for speed**

There is no need to make it case-insensitive; the local-part
already catches the entire range, and the host part is easily
done. Furthermore, this makes the regex locale-independent,
avoiding issues with e.g. turkish case conversions.

cf. http://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/mailfrom.php?rev=HEAD

**Limit eMail address total host part length**

RFC 1035 §2.3.4 imposes a maximum length for any DNS object;
RFC 5321 §2.3.5 references this (and requires FQDNs, but there
have been cases where a TLD had an MX RR and thus eMail addresses
like “localpart@tld” are valid).

Credits: Natureshadow <d.george@tarent.de>

**Limit eMail address individual host part length**

A “label” (each of the things between the dots (‘.’) after the ‘@’ in
the eMail address) MUST NOT be longer than 63 characters.

cf. http://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/mailfrom.php?rev=HEAD
and RFC 1035 §2.3.4

**Fix eMail address local-part validation**

A period (‘.’) may not begin or end a local-part

cf. http://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/mailfrom.php?rev=HEAD
and RFC 822 / 5321

Closes #14719
2016-06-10 10:46:21 +01:00
Georgios Kalpakas 9fca87ca5b docs(misc/downloading): update content - include missing modules
Closes #14746
2016-06-10 10:41:25 +01:00
Martin Staffa ebbaa4ac5e fix($compile): don't add merged attributes twice to $attrs
In replace directives, attribute values from the template are added twice
to the replaced element when both the replaced element and the template
element have the attribute. This does not affect the DOM, as it normalizes
duplicate values. The values are however duplicated in the $attrs object
that is available to directive link functions.

Fixes #8159
Closes #14737
2016-06-09 16:27:13 +01:00
J. Roberto Vidal 617137231e docs($resource): document Resource#toJSON
Document the `toJSON` method of `Resource` instances as part of its public API.

See #14637
Closes #14725
2016-06-09 16:00:59 +01:00
Stepan Suvorov 71b966ace7 docs(misc/downloading): update Angular version in example
Closes #14741
2016-06-09 11:17:37 +03:00
Stepan Suvorov 488d5b4288 updated version of angular for script src 2016-06-09 11:17:36 +03:00
Georgios Kalpakas 01b8772cca feat(ngMessagesInclude): don't break on empty (or whitespace-only) templates
Fixes #12941

Closes #14726
2016-06-07 23:22:15 +03:00
Georgios Kalpakas 05ac0ba926 style(ngMessages): fix indentation 2016-06-07 23:20:28 +03:00
mirabilos 399e872228 docs(guide/Security): warn about $http.jsonp()
Warn users about security issues with JSONP that are *not* mitigated by AngularJS

Closes #14727
2016-06-07 18:34:48 +01:00
Stephen Fluin 3115408be7 docs(guide/Component Router): improving comms 2016-06-07 16:33:20 +01:00
Martin Staffa aea39e9fdc docs(changelog, guide/migration): mention rare BC for ngInclude
See https://github.com/angular/angular.js/issues/13555#issuecomment-165118890
for detailed explanation.

Closes #13555
2016-06-07 10:43:40 +02:00
Georgios Kalpakas 51f6752cec style(ngMocks): remove unnecessary jsHint comment 2016-06-07 11:19:11 +03:00
Georgios Kalpakas db58801a55 fix(ngMock#$controller): properly assign bindings to all types of controllers (e.g. class-based)
The decorated version of `$controller` is able to assign bindings to a controller instance prior to
instantiation, emulating the behavior of `$compile` with directive controllers.

There are cases, that the actual controller instance is different than the pre-populated one (e.g.
when the controller constructor function returns a value or when the controller is an ES2015 Class).
While `$compile` accounts for such situation, by re-asigning the bindings after the controller has
been instantiated, `ngMock`'s `$controller` didn't.

This commit fixes it, by re-applying the bindings if the actual controller instance is different
than the original one (similar to how `$compile` would do it).

Fixes #14437

Closes #14439
2016-06-07 11:18:34 +03:00
Georgios Kalpakas 2e33d170be feat(orderBy): add support for custom comparators
Add an optional, 4th argument (`comparator`) for specifying a custom comparator function, used to
compare the values returned by the predicates. Omitting the argument, falls back to the default,
built-in comparator. The 3rd argument (`reverse`) can still be used for controlling the sorting
order (i.e. ascending/descending).

Additionally, the documentation has been expanded to cover the algorithm used by the built-in
comparator and a few more unit and e2e tests (unrelated to the change) have been added.

Helps with #12572 (maybe this is as close as we want to get).

Fixes #13238
Fixes #14455

Closes #5123
Closes #8112
Closes #10368

Closes #14468
2016-06-07 00:32:19 +03:00
Peter Bacon Darwin cc6dcb4bc2 fix($parse): allow arguments to contain filter chains
Thanks to @esarbanis for the original PR that got out-dated due to the
big $parse overhaul.

Closes #4175
Closes #4168
Closes #14720
2016-06-06 15:20:50 +01:00
Guillaume Salles 294d6793fd fix($location) : initialize $$absUrl to empty string
Initialize `$$absUrl` to an empty string, in order to avoid exception, when base href
and current location have different domains.

Fixes #11091
Fixes #13565

Closes #14488
2016-06-06 15:52:28 +03:00
Peter Bacon Darwin cefcc46e1b docs(tutorial/step-9): fix typo 2016-06-06 13:38:17 +01:00
Martin Staffa c10dba144e docs(ngComponentRouter): add deprecation notice and better install notes
Yes, that contradicts itself.

Closes #14655
Closes #14717
2016-06-06 13:30:31 +01:00
Peter Bacon Darwin f05ffd9bf1 test(browserTrigger): fix typo in eventData property 2016-06-06 13:28:17 +01:00
Peter Bacon Darwin e208d81293 test(browserTrigger): allow event bubbling
In some browsers, events don't bubble when they are dispatched on node inside
a detached tree. When this is the case, the bubbling is made manually.
This may be convenient when unit testing directives, for example.

Closes #5178
2016-06-06 11:57:23 +01:00
Peter Bacon Darwin b51fca1a2b test(browserTrigger): support key events
Support for key events like keypress, keyup or keydown.

Closes #8688
2016-06-06 11:57:21 +01:00
Andrew Schmadel 6685043ad4 fix(ngMockE2E): allow $httpBackend.passThrough() to work when ngMock is loaded
Allow $httpBackend.passThrough() to work normally when ngMock is loaded
concurrently with ngMockE2E, as is typically the case when writing tests with
angular.mock.module()

Fixes #1434
Closes #13124
2016-06-06 11:30:50 +01:00
Martin Staffa f176a2dbb7 docs(angular.element): note restrictions of addClass and removeClass
Closes #12851
Closes #12793
2016-06-03 21:50:00 +02:00
Georgios Kalpakas 3a366bd42b docs($exceptionHandler): include better example
The previous example overwrote the `$exceptionHandler` in order to re-throw the error. This is not
recommended, since it would leave the application in an inconsistent state and prevent proper
clean-up.
The new example simulates logging the error both to the backend and the console.

Fixes #14704
2016-06-03 21:34:48 +03:00
Georgios Kalpakas 1c5db02638 refactor($resource): explicitly set $resourceProvider.defaults.cancellable to false
Previously, it was just `undefined` and had the same behavior (since we only check for falsy-ness).
Just making it explicit, that this property is also available on `defaults` and is "not true" by
default.

Closes #14711
2016-06-03 20:15:45 +03:00
Georgios Kalpakas ffd5308d86 docs($resourceProvider): improve wording and formatting
Closes #11910
2016-06-03 20:15:02 +03:00
Yiling Lu aed9de9d37 docs($resourceProvider): provide info and example for configuring $resourceProvider 2016-06-03 20:14:37 +03:00
Wesley Cho aab6825e85 docs($animate): detail options object
- Document options object used in various $animate methods

Fixes #12645
PR: #14700
2016-06-03 13:28:44 +02:00
Martin Staffa c4fad6ad61 fix(ngSanitize): call attribute setter in linky for all links
Fixes #14707 
PR: #14710
2016-06-03 13:22:24 +02:00
ZachLamb 2a7c37cf01 docs(tutorial/step_04): fix type (JacaScript --> JavaScript)
Closes #14661
2016-06-01 11:08:42 +03:00
Peter Bacon Darwin 194c3adfed docs(CHANGELOG): fix typo 2016-05-27 22:34:51 +01:00
Martin Staffa 371957d9d5 docs(changelog): update with changes for 1.5.6 2016-05-27 17:59:43 +02:00
Lucas Galfasó 1e5d36d4b0 fix($timeout): make $flush handle new $timeouts added in $timeout callbacks
If a $timeout handler calls $timeout itself, this new $timeout should be
added to the mock deferred queue with a "now" time based on when the original
handler was triggered.

Previously it was being added with a now time of when the `$timeout.flush`
method was originally called.

Closes #5420
Closes #14686
2016-05-27 16:19:54 +01:00
Peter Bacon Darwin 3749c85829 fix($compile): cope with $onChanges hooks throwing
Previously, if one of these hooks threw an error, then the compilation
was terminated unceremoniously. In particular any other `$onChanges`
hooks that had been scheduled were not executed, nor was the queue cleaned
up properly.

Closes #14444
Closes #14463
2016-05-27 16:14:51 +01:00
Martin Staffa 886b59d317 chore(angularFiles): add missing files
ngMessageFormat test files were missing from angularTest
ngMessages and ngAnimate were missing from karmaModules.

This change highlighted a number of problems in the tests and code,
which are now fixed.

Closes #14314
Closes #14669
2016-05-27 16:13:31 +01:00
Victor Oliveira da Silva 2048b6df24 docs(ngRepeat): fix typo
Closes #14681
2016-05-26 22:22:57 +03:00
Georgii Dolzhykov 5ec0a50d39 chore(package.json): remove unused grunt-jasmine-node
Closes #14363
2016-05-26 01:43:24 +03:00
46 changed files with 5211 additions and 3347 deletions
+118
View File
@@ -1,3 +1,83 @@
<a name="1.5.6"></a>
# 1.5.6 arrow-stringification (2016-05-27)
## Bug Fixes
- **$browser:** set the url even if the browser transforms it
([743b33ab](https://github.com/angular/angular.js/commit/743b33ab9d8cdb8aa6ff28028a54ac0023f4dc15),
[#14427](https://github.com/angular/angular.js/issues/14427), [#14499](https://github.com/angular/angular.js/issues/14499))
- **$compile:**
- properly bind context to linking functions for directives with `templateUrl`
([5ae72dc7](https://github.com/angular/angular.js/commit/5ae72dc7affb252774b7c4cbf278269c5e178f38))
- always use the DDO as `this` in pre-/post-linking functions
([47bc98a1](https://github.com/angular/angular.js/commit/47bc98a1eafd7853babc1a7dc54f13363a3f4a43),
[#9306](https://github.com/angular/angular.js/issues/9306))
- don't run unnecessary update to one-way bindings
([30479647](https://github.com/angular/angular.js/commit/304796471292f9805b9cf77e51aacc9cfbb09921),
[#14546](https://github.com/angular/angular.js/issues/14546), [#14580](https://github.com/angular/angular.js/issues/14580))
- removing unnecessary white space in element-transclusion comments
([1116a96d](https://github.com/angular/angular.js/commit/1116a96df662c95aa83ab527c479b644a14532b6),
[#14549](https://github.com/angular/angular.js/issues/14549), [#14550](https://github.com/angular/angular.js/issues/14550))
- properly handle setting `srcset` to undefined
([308f22ba](https://github.com/angular/angular.js/commit/308f22ba9a803967ce84c995d810990c80478f6f),
[#14470](https://github.com/angular/angular.js/issues/14470), [#14493](https://github.com/angular/angular.js/issues/14493))
- **$injector:**
- add workaround for class stringification in Chrome v50/51
([7550b7bd](https://github.com/angular/angular.js/commit/7550b7bda8d992cc6ed3c65b1f9f7e323a09cae4),
[#14531](https://github.com/angular/angular.js/issues/14531))
- add workaround for fat-arrow stringification in Chrome v50/51
([bb730704](https://github.com/angular/angular.js/commit/bb730704f2f3dea4620e7b40083dcd65f208e2b2),
[#14487](https://github.com/angular/angular.js/issues/14487), [#14495](https://github.com/angular/angular.js/issues/14495))
- **$templateRequest:** trust empty templates in `$templateCache` as well
([10955969](https://github.com/angular/angular.js/commit/10955969620248bd31a0bd37bc9fd7cc3b1f5e88),
[#14479](https://github.com/angular/angular.js/issues/14479), [#14496](https://github.com/angular/angular.js/issues/14496))
- **filters:** always call `splice()` with 2 arguments or more
([6f1fae8a](https://github.com/angular/angular.js/commit/6f1fae8a916cc6f26725f64869f86fcd4991e819),
[#14467](https://github.com/angular/angular.js/issues/14467), [#14489](https://github.com/angular/angular.js/issues/14489))
- **ng-bind-html:** watch the unwrapped value using `$sce.valueOf()` (instead of `toString()`)
([1c1c9b27](https://github.com/angular/angular.js/commit/1c1c9b27cbb57b3219d4c9765eeea8a11553d297),
[#14526](https://github.com/angular/angular.js/issues/14526), [#14527](https://github.com/angular/angular.js/issues/14527))
- **ngAnimate:**
- don't break on anchored animations without duration
([21b76aed](https://github.com/angular/angular.js/commit/21b76aed06d9cc04bd25a99c23ba852af782b357),
[#14641](https://github.com/angular/angular.js/issues/14641), [#14645](https://github.com/angular/angular.js/issues/14645))
- properly handle empty jqLite collections
([9f480257](https://github.com/angular/angular.js/commit/9f4802573348401ee24090f815f3138fa17c161d),
[#14558](https://github.com/angular/angular.js/issues/14558), [#14559](https://github.com/angular/angular.js/issues/14559))
- **ngMessages:** do not compile ngMessagesInclude template if scope is destroyed
([69d9072d](https://github.com/angular/angular.js/commit/69d9072d5b5aba988ac8a9717c92ce1fac465cbe),
[#12695](https://github.com/angular/angular.js/issues/12695), [#14640](https://github.com/angular/angular.js/issues/14640))
- **ngMock:**
- match HTTP request regardless of the order of query params
([363fb16c](https://github.com/angular/angular.js/commit/363fb16c1076a806bb31006daaf583ab5c82e467),
[#12762](https://github.com/angular/angular.js/issues/12762))
- pass eventHandlers to $httpBackend if passThrough is active
([147650b3](https://github.com/angular/angular.js/commit/147650b3beae7ad8b556a930129887d187a0f5d3),
[#14471](https://github.com/angular/angular.js/issues/14471))
## Features
- **$compile:** support omitting required controller name if same as the local name
([8965d57a](https://github.com/angular/angular.js/commit/8965d57aa0788c96859cf3cfa0f78b46d2cebedb),
[#14513](https://github.com/angular/angular.js/issues/14513))
- **$parse:** Add support for ES6 object initializers
([1c9b9e24](https://github.com/angular/angular.js/commit/1c9b9e24ede7efc10ce4d53c6ab5528cc77e79d7))
## Performance Improvements
- **$rootScope:** make queues more efficient
([51b78f61](https://github.com/angular/angular.js/commit/51b78f61f954d94dc937f57190c9f881df9ab3ad),
[#14545](https://github.com/angular/angular.js/issues/14545))
- **ngAnimate:** listen for document visibility changes
([b377d6b0](https://github.com/angular/angular.js/commit/b377d6b043db9c8da4e68493892f75a0f9759464),
[#14568](https://github.com/angular/angular.js/issues/14568))
- **ngClass:** improve even-odd checking
([cf1b1755](https://github.com/angular/angular.js/commit/cf1b175508a72d4b7e3d64b8506903096c6ec25f))
<a name="1.5.5"></a>
# 1.5.5 material-conspiration (2016-04-18)
@@ -1913,6 +1993,44 @@ describe('$q.when', function() {
[#11580](https://github.com/angular/angular.js/issues/11580), [#12234](https://github.com/angular/angular.js/issues/12234))
## Breaking Changes
- **ngInclude:** due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
Before:
```html
<div ng-include="findTemplate('https://example.com/myTemplate.html')"></div>
```
```js
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
```
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
whitelist in the `config()` function:
After:
```js
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/**'])
});
```
<a name="1.3.17"></a>
# 1.3.17 tsktskskly-euouae (2015-07-06)
+5
View File
@@ -5,6 +5,7 @@ var angularFiles = {
'src/minErr.js',
'src/Angular.js',
'src/loader.js',
'src/shallowCopy.js',
'src/stringify.js',
'src/AngularPublic.js',
'src/jqLite.js',
@@ -128,6 +129,7 @@ var angularFiles = {
'src/ngResource/resource.js'
],
'ngRoute': [
'src/shallowCopy.js',
'src/ngRoute/route.js',
'src/ngRoute/routeParams.js',
'src/ngRoute/directive/ngView.js'
@@ -175,6 +177,7 @@ var angularFiles = {
'test/auto/*.js',
'test/ng/**/*.js',
'test/ngAnimate/*.js',
'test/ngMessageFormat/*.js',
'test/ngMessages/*.js',
'test/ngCookies/*.js',
'test/ngResource/*.js',
@@ -212,7 +215,9 @@ var angularFiles = {
'test/modules/no_bootstrap.js',
'src/ngScenario/browserTrigger.js',
'test/helpers/*.js',
'test/ngAnimate/*.js',
'test/ngMessageFormat/*.js',
'test/ngMessages/*.js',
'test/ngMock/*.js',
'test/ngCookies/*.js',
'test/ngRoute/**/*.js',
+13
View File
@@ -5,6 +5,11 @@
# Component Router
<div class="alert alert-info">
**Deprecation Notice:** In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module) has been deprecated and will not receive further updates.
We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
</div>
This guide describes the new Component Router for AngularJS 1.5.
<div class="alert alert-info">
@@ -482,6 +487,14 @@ Just like any Angular application, we load the JavaScript files into our `index.
<script src="/app/app.js"></script>
```
You also need to include ES6 shims for browsers that do not support ES6 code (Internet Explorer,
iOs < 8, Android < 5.0, Windows Mobile < 10):
```html
<!-- IE required polyfills, in this exact order -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.33.3/es6-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.20/system-polyfills.js"></script>
<script src="https://npmcdn.com/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
```
## Create the `app` module
+39 -6
View File
@@ -514,7 +514,7 @@ angular.module('myApp').directive('form', function() {
});
```
### Templating (`ngRepeat`, `$compile`)
### Templating (`ngRepeat`, `$compile`, `ngInclude`)
#### ngRepeat
@@ -545,6 +545,44 @@ Due to [62d514b](https://github.com/angular/angular.js/commit/62d514b06937cc7dd8
returning an object from a controller constructor function will now override the scope. Views that use the
controllerAs method will no longer get the this reference, but the returned object.
#### ngInclude
Due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
Before:
```html
<div ng-include="findTemplate('https://example.com/templates/myTemplate.html')"></div>
```
```js
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
```
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
whitelist in the `config()` function:
After:
```js
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`, which means you don't
// have to use `$sce.trustAsResourceUrl()` at all:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])
});
```
### Cookies (`ngCookies`)
@@ -583,8 +621,6 @@ has been moved to `$cookies`, to which `$cookieStore` now simply
delegates calls.
### Server Requests (`$http`)
Due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
@@ -616,8 +652,6 @@ $http.get(url, {
```
### Filters (`filter`, `limitTo`)
#### `filter` filter
@@ -636,7 +670,6 @@ Now, instead of returning empty object/array, it returns unchanged input.
## Migrating from 1.2 to 1.3
### Controllers
+4
View File
@@ -87,6 +87,10 @@ Protection from JSON Hijacking is provided if the server prefixes all JSON reque
Angular will automatically strip the prefix before processing it as JSON.
For more information please visit {@link $http#json-vulnerability-protection JSON Hijacking Protection}.
Bear in mind that calling `$http.jsonp`, like in [our Yahoo! finance example](https://docs.angularjs.org/guide/concepts#accessing-the-backend),
gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
instant remote code execution in your application: the result of these requests is handed off
to the browser as regular `<script>` tag.
## Strict Contextual Escaping
+87 -51
View File
@@ -2,85 +2,121 @@
@name Downloading
@description
# Including angular scripts from the Google CDN
The quickest way to get started is to point your html `<script>` tag to a Google CDN URL.
# Including Angular scripts from the Google CDN
The quickest way to get started is to point your html `<script>` tag to a
[Google CDN](https://developers.google.com/speed/libraries/#angularjs) URL.
This way, you don't have to download anything or maintain a local copy.
There are two types of angular script URLs you can point to, one for development and one for
There are two types of Angular script URLs you can point to, one for development and one for
production:
* __angular.js__ — This is the human-readable, non-minified version, suitable for web
development.
* __angular.js__ — This is the human-readable, non-minified version, suitable for web development.
* __angular.min.js__ — This is the minified version, which we strongly suggest you use in
production.
production.
To point your code to an angular script on the Google CDN server, use the following template. This
example points to the minified version 1.4.5:
To point your code to an angular script on the Google CDN server, use the following template. This
example points to the minified version 1.5.6:
```
```html
<!doctype html>
<html ng-app>
<head>
<title>My Angular App</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
</head>
<body>
</body>
</html>
```
Note that only versions 1.0.1 and above are available on the CDN, if you need an earlier version
you can use the <http://code.angularjs.org/> URL which was the previous recommended location for
hosted code source. If you're still using the angular server you should switch to the CDN version
for even faster loading times.
<div class="alert alert-info">
Note that only versions 1.0.1 and above are available on the CDN. If you need an earlier version
(which you shouldn't) you can use the https://code.angularjs.org/ URL, which was the previous
recommended location for hosted code source. If you're still using the Angular server you should
switch to the CDN version for even faster loading times.
</div>
<br />
# Downloading and hosting angular files locally
This option is for those who want to work with angular offline, or those who want to host the
angular files on their own servers.
This option is for those who want to work with Angular offline, or those who want to host the
Angular files on their own servers.
If you navigate to <http://code.angularjs.org/>, you'll see a directory listing with all of the
angular versions since we started releasing versioned build artifacts (quite late in the project
lifetime). Each directory contains all artifacts that we released for a particular version.
Download the version you want and have fun.
If you navigate to https://code.angularjs.org/, you'll see a directory listing with all of the
Angular versions since we started releasing versioned build artifacts. Each directory contains all
artifacts that we released for a particular version. Download the version you want and have fun.
Each directory under <http://code.angularjs.org/> includes the following set of files:
<div class="alert alert-warning">
You can ignore directories starting with `2.` (e.g. `2.0.0-beta.17`) — they are not related to
AngularJS. They contain build artifacts from [Angular 2](https://angular.io) versions.
</div>
* __`angular.js`__ — This file is non-obfuscated, non-minified, and human-readable by
opening it in any editor or browser. In order to get better error messages during development, you
should always use this non-minified angular script.
<br />
Each directory under https://code.angularjs.org/ includes a set of files that comprise the
corresponding version. All JavaScript files (except for `angular-mocks` which is only used during
development) come in two flavors — one suitable for development, the other for production:
* __`angular.min.js`__ — This is a minified and obfuscated version of
`angular.js` created with the Closure compiler. Use this version for production in order
to minimize the size of the application that is downloaded by your user's browser.
* __`<filename>.js`__ — These files are non-obfuscated, non-minified, and human-readable by opening
them in any editor or browser. In order to get better error messages during development, you
should always use these non-minified scripts.
* __`angular.zip`__ — This is a zip archive that contains all of the files released
for this angular version. Use this file to get everything in a single download.
* __`<filename>.min.js`__ — These are minified and obfuscated versions, created with the
[Closure compiler](https://developers.google.com/closure/compiler/). Use these versions for
production in order to minimize the size of the application that is downloaded by your user's
browser.
* __`angular-mocks.js`__ — This file contains an implementation of mocks that makes
testing angular apps even easier. Your unit/integration test harness should load this file after
`angular.js` is loaded.
* __`<filename>.min.js.map`__ — These are
[sourcemap files](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/). You can
serve them alongside the `.min.js` files in order to be able to debug the minified code (e.g. on a
production deployment) more easily, but without impacting performance.
* __`angular-scenario.js`__ — This file is a very nifty JavaScript file that allows you
to write and execute end-to-end tests for angular applications.
<br />
The set of files included in each version directory are:
* __`angular-loader.min.js`__ — Module loader for Angular modules. If you are loading multiple script files containing
Angular modules, you can load them asynchronously and in any order as long as you load this file first. Often the
contents of this file are copy&pasted into the `index.html` to avoid even the initial request to `angular-loader.min.js`.
See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for an example of usage.
* __`angular.zip`__ — This is a zip archive that contains all of the files released for this Angular
version. Use this file to get everything in a single download.
* __Additional Angular modules:__ optional modules with additional functionality. These files should be loaded
after the core `angular.js` file:
* __`angular-animate.js`__ - Enable animation support
* __`angular-cookies.js`__ - A convenient wrapper for reading and writing browser cookies
* __`angular-resource.js`__ - Interaction support with RESTful services via the $resource service
* __`angular-route.js`__ - Routing and deeplinking services and directives for angular apps
* __`angular-sanitize.js`__ - Functionality to sanitize HTML
* __`angular-touch.js`__ - Touch events and other helpers for touch-enabled devices
* __`angular.js`__ — The core Angular framework. This is all you need to get your Angular app
running.
* __`angular-csp.css`__ — You only need this file if you are using
[CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP). See
{@link directive:ngCsp here} for more info.
* __`angular-mocks.js`__ This file contains an implementation of mocks that makes testing angular
apps even easier. Your unit/integration test harness should load this file after `angular.js` is
loaded.
* __`angular-loader.js`__ — Module loader for Angular modules. If you are loading multiple
script files containing Angular modules, you can load them asynchronously and in any order as long
as you load this file first. Often the contents of this file are copy&pasted into the `index.html`
to avoid even the initial request to `angular-loader[.min].js`.
See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for
an example of usage.
* __Additional Angular modules:__ Optional modules with additional functionality. These files
should be loaded after the core `angular[.min].js` file:
* __`angular-animate.js`__ — Enable animation support. ({@link module:ngAnimate API docs})
* __`angular-aria.js`__ — Make your apps [accessible](http://www.w3.org/TR/wai-aria/) to users of
assistive technologies. ({@link module:ngAria API docs})
* __`angular-cookies.js`__ — A convenient wrapper for reading and writing browser cookies.
({@link module:ngCookies API docs})
<!-- * __`angular-message-format.js`__ — ({@link module:ngMessageFormat API docs}) -->
* __`angular-messages.js`__ — Enhanced support for displaying validation messages.
({@link module:ngMessages API docs})
* __`angular-parse-ext.js`__ — Allow Unicode characters in identifiers inside Angular expressions.
({@link module:ngParseExt API docs})
* __`angular-resource.js`__ — Easy interaction with RESTful services.
({@link module:ngResource API docs})
* __`angular-route.js`__ — Routing and deep-linking services and directives for Angular apps.
({@link module:ngRoute API docs})
* __`angular-sanitize.js`__ — Functionality to sanitize HTML. ({@link module:ngSanitize API docs})
* __`angular-touch.js`__ — Touch events and other helpers for touch-enabled devices.
({@link module:ngTouch API docs})
* __`docs`__ — this directory contains all the files that compose the
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
our docs, or even more importantly, view the docs offline.
* __`docs/`__ — This directory contains all the files that compose the https://docs.angularjs.org/
documentation app. These files are handy to see the older versions of our docs, or even more
importantly, view the docs offline.
* __`i18n`__ - this directory contains locale specific `ngLocale` angular modules to override the defaults
defined in the `ng` module.
* __`i18n/`__ - This directory contains [locale specific](https://docs.angularjs.org/guide/i18n)
`ngLocale` Angular modules to override the defaults defined in the main `ng` module.
+1 -1
View File
@@ -171,7 +171,7 @@ will make all entities registered on `phoneList` available on `phonecatApp` as w
<div class="alert alert-info">
<p>
Don't forget to also update your `index.html` adding a `<script>` tag for each JacaScript file
Don't forget to also update your `index.html` adding a `<script>` tag for each JavaScript file
we have created. This might seem tedious, but is totally worth it.
</p>
<p>
+1 -1
View File
@@ -87,7 +87,7 @@ feature, we can implement [deep linking][deep-linking], which lets us utilize th
<div class="alert alert-success">
<p>
`ngRoute` allows as to associate a controller and a template with a specific URL (or URL
`ngRoute` lets us associate a controller and a template with a specific URL (or URL
pattern). This is pretty close to what we did with `ngController` and `index.html` back in
{@link step_02 step 2}.
</p>
+7
View File
@@ -0,0 +1,7 @@
"use strict";
var isOnlineAsync = require('is-online');
isOnlineAsync(function(err, isOnline) {
console.log(isOnline ? 'online' : 'offline');
});
+59 -47
View File
@@ -1,5 +1,6 @@
'use strict';
var childProcess = require('child_process');
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
@@ -7,7 +8,9 @@ var semver = require('semver');
var _ = require('lodash');
var currentPackage, previousVersions, cdnVersion, gitRepoInfo;
var connectivityStatus = childProcess.execSync('node is-online.js', { cwd: __dirname, encoding: 'utf8' }).trim();
var isOnline = connectivityStatus == 'online';
console.log('Running ' + connectivityStatus);
/**
* Load information about this project from the package.json
@@ -100,58 +103,68 @@ var getPreviousVersions = function() {
// not contain all commits when cloned with git clone --depth=...
// Needed e.g. for Travis
var repo_url = currentPackage.repository.url;
var tagResults = shell.exec('git ls-remote --tags ' + repo_url,
{silent: true});
if (tagResults.code === 0) {
return _(tagResults.output.match(/v[0-9].*[0-9]$/mg))
.map(function(tag) {
var version = semver.parse(tag);
return version;
})
.filter()
.map(function(version) {
// angular.js didn't follow semantic version until 1.20rc1
if ((version.major === 1 && version.minor === 0 && version.prerelease.length > 0) || (version.major === 1 && version.minor === 2 && version.prerelease[0] === 'rc1')) {
version.version = [version.major, version.minor, version.patch].join('.') + version.prerelease.join('');
version.raw = 'v' + version.version;
}
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
// Versions before 1.0.2 had a different docs folder name
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.patch < 2)) {
version.docsUrl += '-' + version.version;
version.isOldDocsUrl = true;
}
return version;
})
.sort(semver.compare)
.value();
if (isOnline) {
console.log('looking up remote git tags...');
var tagResults = shell.exec('git ls-remote --tags ' + repo_url,
{silent: true});
if (tagResults.code === 0) {
return _(tagResults.output.match(/v[0-9].*[0-9]$/mg))
.map(function(tag) {
var version = semver.parse(tag);
return version;
})
.filter()
.map(function(version) {
// angular.js didn't follow semantic version until 1.20rc1
if ((version.major === 1 && version.minor === 0 && version.prerelease.length > 0) || (version.major === 1 && version.minor === 2 && version.prerelease[0] === 'rc1')) {
version.version = [version.major, version.minor, version.patch].join('.') + version.prerelease.join('');
version.raw = 'v' + version.version;
}
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
// Versions before 1.0.2 had a different docs folder name
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.patch < 2)) {
version.docsUrl += '-' + version.version;
version.isOldDocsUrl = true;
}
return version;
})
.sort(semver.compare)
.value();
}
} else {
return [];
console.log('-- skipping remote git tag lookup');
}
return [];
};
var getCdnVersion = function() {
return _(previousVersions)
.filter(function(tag) {
return semver.satisfies(tag, currentPackage.branchVersion);
})
.reverse()
.reduce(function(cdnVersion, version) {
if (!cdnVersion) {
// Note: need to use shell.exec and curl here
// as version-infos returns its result synchronously...
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/' + version + '/angular.min.js ' +
'--head --write-out "%{http_code}" -o /dev/null -silent',
{silent: true});
if (cdnResult.code === 0) {
var statusCode = cdnResult.output.trim();
if (statusCode === '200') {
cdnVersion = version;
if (isOnline) {
console.log('searching for CDN versions...');
return _(previousVersions)
.filter(function(tag) {
return semver.satisfies(tag, currentPackage.branchVersion);
})
.reverse()
.reduce(function(cdnVersion, version) {
if (!cdnVersion) {
// Note: need to use shell.exec and curl here
// as version-infos returns its result synchronously...
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/' + version + '/angular.min.js ' +
'--head --write-out "%{http_code}" -o /dev/null -silent',
{silent: true});
if (cdnResult.code === 0) {
var statusCode = cdnResult.output.trim();
if (statusCode === '200') {
console.log('-- found CDN version ' + version);
cdnVersion = version;
}
}
}
}
return cdnVersion;
}, null);
return cdnVersion;
}, null);
} else {
console.log('-- skipping CDN version lookup');
}
};
/**
@@ -198,7 +211,6 @@ var getSnapshotVersion = function() {
return version;
};
exports.currentPackage = currentPackage = getPackage();
exports.gitRepoInfo = gitRepoInfo = getGitRepoInfo();
exports.previousVersions = previousVersions = getPreviousVersions();
+105 -65
View File
@@ -3943,10 +3943,6 @@
}
}
},
"grunt-jasmine-node": {
"version": "0.1.0",
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
},
"grunt-jscs": {
"version": "2.8.0",
"dependencies": {
@@ -6396,6 +6392,50 @@
}
}
},
"is-online": {
"version": "5.1.1",
"dependencies": {
"is-reachable": {
"version": "1.3.0",
"dependencies": {
"arrify": {
"version": "1.0.1"
},
"each-async": {
"version": "1.1.1",
"dependencies": {
"set-immediate-shim": {
"version": "1.0.1"
}
}
},
"is-port-reachable": {
"version": "1.0.0"
},
"router-ips": {
"version": "0.2.0"
},
"url-parse-lax": {
"version": "1.0.0",
"dependencies": {
"prepend-http": {
"version": "1.0.4"
}
}
}
}
},
"onetime": {
"version": "1.1.0"
},
"random-item": {
"version": "1.0.0"
},
"root-hints": {
"version": "1.0.10"
}
}
},
"jasmine-core": {
"version": "2.4.1"
},
@@ -6946,15 +6986,15 @@
"ansi": {
"version": "0.3.1"
},
"ansi-regex": {
"version": "2.0.0"
},
"ansi-styles": {
"version": "2.2.1"
},
"are-we-there-yet": {
"version": "1.1.2"
},
"ansi-regex": {
"version": "2.0.0"
},
"asn1": {
"version": "0.2.3"
},
@@ -6964,12 +7004,12 @@
"async": {
"version": "1.5.2"
},
"aws-sign2": {
"version": "0.6.0"
},
"bl": {
"version": "1.0.3"
},
"aws-sign2": {
"version": "0.6.0"
},
"block-stream": {
"version": "0.0.8"
},
@@ -6979,35 +7019,38 @@
"caseless": {
"version": "0.11.0"
},
"combined-stream": {
"version": "1.0.5"
},
"commander": {
"version": "2.9.0"
},
"chalk": {
"version": "1.1.3"
},
"combined-stream": {
"version": "1.0.5"
},
"core-util-is": {
"version": "1.0.2"
},
"cryptiles": {
"version": "2.0.5"
},
"debug": {
"version": "2.2.0"
"commander": {
"version": "2.9.0"
},
"deep-extend": {
"version": "0.4.1"
},
"delegates": {
"version": "1.0.0"
"cryptiles": {
"version": "2.0.5"
},
"ecc-jsbn": {
"version": "0.1.1"
},
"delayed-stream": {
"version": "1.0.0"
},
"ecc-jsbn": {
"version": "0.1.1"
"debug": {
"version": "2.2.0"
},
"delegates": {
"version": "1.0.0"
},
"extend": {
"version": "3.0.0"
},
"escape-string-regexp": {
"version": "1.0.5"
@@ -7015,20 +7058,17 @@
"extsprintf": {
"version": "1.0.2"
},
"extend": {
"version": "3.0.0"
},
"forever-agent": {
"version": "0.6.1"
},
"fstream": {
"version": "1.0.8"
"gauge": {
"version": "1.2.7"
},
"form-data": {
"version": "1.0.0-rc4"
},
"gauge": {
"version": "1.2.7"
"fstream": {
"version": "1.0.8"
},
"generate-object-property": {
"version": "1.2.0"
@@ -7048,21 +7088,21 @@
"has-ansi": {
"version": "2.0.0"
},
"has-unicode": {
"version": "2.0.0"
},
"hawk": {
"version": "3.1.3"
},
"hoek": {
"version": "2.16.3"
},
"http-signature": {
"version": "1.1.1"
"has-unicode": {
"version": "2.0.0"
},
"ini": {
"version": "1.3.4"
},
"http-signature": {
"version": "1.1.1"
},
"inherits": {
"version": "2.0.1"
},
@@ -7072,24 +7112,24 @@
"is-property": {
"version": "1.0.2"
},
"is-typedarray": {
"isarray": {
"version": "1.0.0"
},
"isarray": {
"is-typedarray": {
"version": "1.0.0"
},
"isstream": {
"version": "0.1.2"
},
"jsbn": {
"version": "0.1.0"
},
"jodid25519": {
"version": "1.0.2"
},
"json-schema": {
"version": "0.2.2"
},
"jsbn": {
"version": "0.1.0"
},
"json-stringify-safe": {
"version": "5.0.1"
},
@@ -7102,10 +7142,10 @@
"lodash.pad": {
"version": "4.1.0"
},
"lodash.padstart": {
"lodash.padend": {
"version": "4.2.0"
},
"lodash.padend": {
"lodash.padstart": {
"version": "4.2.0"
},
"lodash.repeat": {
@@ -7114,11 +7154,14 @@
"lodash.tostring": {
"version": "4.1.2"
},
"mime-types": {
"version": "2.1.10"
},
"mime-db": {
"version": "1.22.0"
},
"mime-types": {
"version": "2.1.10"
"minimist": {
"version": "0.0.8"
},
"mkdirp": {
"version": "0.5.1"
@@ -7126,15 +7169,12 @@
"ms": {
"version": "0.7.1"
},
"minimist": {
"version": "0.0.8"
"node-uuid": {
"version": "1.4.7"
},
"npmlog": {
"version": "2.0.3"
},
"node-uuid": {
"version": "1.4.7"
},
"oauth-sign": {
"version": "0.8.1"
},
@@ -7147,12 +7187,12 @@
"pinkie-promise": {
"version": "2.0.0"
},
"process-nextick-args": {
"version": "1.0.6"
},
"qs": {
"version": "6.0.2"
},
"process-nextick-args": {
"version": "1.0.6"
},
"readable-stream": {
"version": "2.0.6"
},
@@ -7168,11 +7208,14 @@
"sshpk": {
"version": "1.7.4"
},
"string_decoder": {
"version": "0.10.31"
},
"stringstream": {
"version": "0.0.5"
},
"string_decoder": {
"version": "0.10.31"
"strip-json-comments": {
"version": "1.0.4"
},
"strip-ansi": {
"version": "3.0.1"
@@ -7180,12 +7223,6 @@
"supports-color": {
"version": "2.0.0"
},
"strip-json-comments": {
"version": "1.0.4"
},
"tar": {
"version": "2.2.1"
},
"tough-cookie": {
"version": "2.2.2"
},
@@ -7195,18 +7232,21 @@
"tunnel-agent": {
"version": "0.4.2"
},
"tar": {
"version": "2.2.1"
},
"tweetnacl": {
"version": "0.14.3"
},
"uid-number": {
"version": "0.0.6"
},
"util-deprecate": {
"version": "1.0.2"
},
"verror": {
"version": "1.3.6"
},
"uid-number": {
"version": "0.0.6"
},
"wrappy": {
"version": "1.0.1"
},
+2495 -2432
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -41,7 +41,6 @@
"grunt-contrib-copy": "~0.6.0",
"grunt-contrib-jshint": "^1.0.0",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs": "^2.1.0",
"grunt-merge-conflict": "~0.0.1",
"grunt-shell": "~1.1.1",
@@ -53,6 +52,7 @@
"gulp-sourcemaps": "^1.2.2",
"gulp-uglify": "^1.0.1",
"gulp-util": "^3.0.1",
"is-online": "^5.1.1",
"jasmine-core": "^2.4.0",
"jasmine-node": "^2.0.0",
"jasmine-reporters": "~1.0.1",
-26
View File
@@ -57,7 +57,6 @@
includes: true,
arrayRemove: true,
copy: true,
shallowCopy: true,
equals: true,
csp: true,
jq: true,
@@ -935,31 +934,6 @@ function copy(source, destination) {
}
}
/**
* Creates a shallow copy of an object, an array or a primitive.
*
* Assumes that there are no proto properties for objects.
*/
function shallowCopy(src, dst) {
if (isArray(src)) {
dst = dst || [];
for (var i = 0, ii = src.length; i < ii; i++) {
dst[i] = src[i];
}
} else if (isObject(src)) {
dst = dst || {};
for (var key in src) {
if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
}
return dst || src;
}
/**
* @ngdoc function
+2 -2
View File
@@ -53,7 +53,7 @@
* ## Angular's jqLite
* jqLite provides only the following jQuery methods:
*
* - [`addClass()`](http://api.jquery.com/addClass/)
* - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
* - [`after()`](http://api.jquery.com/after/)
* - [`append()`](http://api.jquery.com/append/)
* - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
@@ -80,7 +80,7 @@
* - [`ready()`](http://api.jquery.com/ready/)
* - [`remove()`](http://api.jquery.com/remove/)
* - [`removeAttr()`](http://api.jquery.com/removeAttr/)
* - [`removeClass()`](http://api.jquery.com/removeClass/)
* - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
* - [`removeData()`](http://api.jquery.com/removeData/)
* - [`replaceWith()`](http://api.jquery.com/replaceWith/)
* - [`text()`](http://api.jquery.com/text/)
+49 -7
View File
@@ -420,7 +420,13 @@ var $AnimateProvider = ['$provide', function($provide) {
* @param {DOMElement} parent the parent element which will append the element as
* a child (so long as the after element is not present)
* @param {DOMElement=} after the sibling element after which the element will be appended
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
@@ -446,7 +452,13 @@ var $AnimateProvider = ['$provide', function($provide) {
* @param {DOMElement} parent the parent element which will append the element as
* a child (so long as the after element is not present)
* @param {DOMElement=} after the sibling element after which the element will be appended
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
@@ -467,7 +479,13 @@ var $AnimateProvider = ['$provide', function($provide) {
* digest once the animation has completed.
*
* @param {DOMElement} element the element which will be removed from the DOM
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
@@ -491,7 +509,13 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @param {DOMElement} element the element which the CSS classes will be applied to
* @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
@@ -515,7 +539,13 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @param {DOMElement} element the element which the CSS classes will be applied to
* @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
@@ -540,7 +570,13 @@ var $AnimateProvider = ['$provide', function($provide) {
* @param {DOMElement} element the element which the CSS classes will be applied to
* @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
* @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
@@ -581,7 +617,13 @@ var $AnimateProvider = ['$provide', function($provide) {
* @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
* this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
* (Note that if no animation is detected then this value will not be applied to the element.)
* @param {object=} options an optional collection of options/styles that will be applied to the element
* @param {object=} options an optional collection of options/styles that will be applied to the element.
* The object can have the following properties:
*
* - **addClass** - `{string}` - space-separated CSS classes to add to element
* - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
* - **removeClass** - `{string}` - space-separated CSS classes to remove from element
* - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
*
* @return {Promise} the animation callback promise
*/
+28 -14
View File
@@ -1311,11 +1311,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
// We must run this hook in an apply since the $$postDigest runs outside apply
$rootScope.$apply(function() {
var errors = [];
for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
onChangesQueue[i]();
try {
onChangesQueue[i]();
} catch (e) {
errors.push(e);
}
}
// Reset the queue to trigger a new schedule next time there is a change
onChangesQueue = undefined;
if (errors.length) {
throw errors;
}
});
} finally {
onChangesTtl++;
@@ -2491,10 +2499,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
forEach(elementControllers, function(controller) {
var controllerInstance = controller.instance;
if (isFunction(controllerInstance.$onChanges)) {
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
try {
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
} catch (e) {
$exceptionHandler(e);
}
}
if (isFunction(controllerInstance.$onInit)) {
controllerInstance.$onInit();
try {
controllerInstance.$onInit();
} catch (e) {
$exceptionHandler(e);
}
}
if (isFunction(controllerInstance.$onDestroy)) {
controllerScope.$on('$destroy', function callOnDestroyHook() {
@@ -2758,18 +2774,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// copy the new attributes on the old attrs object
forEach(src, function(value, key) {
if (key == 'class') {
safeAddClass($element, value);
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
} else if (key == 'style') {
$element.attr('style', $element.attr('style') + ';' + value);
dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
// `dst` will never contain hasOwnProperty as DOM parser won't let it.
// You will get an "InvalidCharacterError: DOM Exception 5" error if you
// have an attribute like "has-own-property" or "data-has-own-property", etc.
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
// Check if we already set this attribute in the loop above.
// `dst` will never contain hasOwnProperty as DOM parser won't let it.
// You will get an "InvalidCharacterError: DOM Exception 5" error if you
// have an attribute like "has-own-property" or "data-has-own-property", etc.
if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
dst[key] = value;
dstAttr[key] = srcAttr[key];
if (key !== 'class' && key !== 'style') {
dstAttr[key] = srcAttr[key];
}
}
});
}
+3 -1
View File
@@ -24,7 +24,9 @@ var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-
// 9. Fragment
// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999
var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
/* jshint maxlen:220 */
var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
/* jshint maxlen:200 */
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
+1 -1
View File
@@ -49,7 +49,7 @@
* it's a prefix used by Angular for public (`$`) and private (`$$`) properties.
*
* - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
* objects, and will throw if used with one.
* objects, and will throw an error if used with one.
*
* If you are hitting any of these limitations, the recommended workaround is to convert your object into an array
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
+14 -11
View File
@@ -15,17 +15,20 @@
*
* ## Example:
*
* ```js
* angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
* return function(exception, cause) {
* exception.message += ' (caused by "' + cause + '")';
* throw exception;
* };
* });
* ```
* The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
* errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
* of `$log.error()`.
*
* This example will override the normal action of `$exceptionHandler`, to make angular
* exceptions fail hard when they happen, instead of just logging to the console.
* ```js
* angular.
* module('exceptionOverwrite', []).
* factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
* return function myExceptionHandler(exception, cause) {
* logErrorsToBackend(exception, cause);
* $log.warn(exception, cause);
* };
* });
* ```
*
* <hr />
* Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
@@ -36,7 +39,7 @@
* `try { ... } catch(e) { $exceptionHandler(e); }`
*
* @param {Error} exception Exception associated with the error.
* @param {string=} cause optional information about the context in which
* @param {string=} cause Optional information about the context in which
* the error was thrown.
*
*/
+21 -14
View File
@@ -6,21 +6,22 @@
* @kind function
*
* @description
* Creates a new array or string containing only a specified number of elements. The elements
* are taken from either the beginning or the end of the source array, string or number, as specified by
* the value and sign (positive or negative) of `limit`. If a number is used as input, it is
* converted to a string.
* Creates a new array or string containing only a specified number of elements. The elements are
* taken from either the beginning or the end of the source array, string or number, as specified by
* the value and sign (positive or negative) of `limit`. Other array-like objects are also supported
* (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input,
* it is converted to a string.
*
* @param {Array|string|number} input Source array, string or number to be limited.
* @param {string|number} limit The length of the returned array or string. If the `limit` number
* @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited.
* @param {string|number} limit - The length of the returned array or string. If the `limit` number
* is positive, `limit` number of items from the beginning of the source array/string are copied.
* If the number is negative, `limit` number of items from the end of the source array/string
* are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
* the input will be returned unchanged.
* @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
* indicates an offset from the end of `input`. Defaults to `0`.
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
* had less than `limit` elements.
* @param {(string|number)=} begin - Index at which to begin limitation. As a negative index,
* `begin` indicates an offset from the end of `input`. Defaults to `0`.
* @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had
* less than `limit` elements.
*
* @example
<example module="limitToExample">
@@ -108,19 +109,25 @@ function limitToFilter() {
if (isNaN(limit)) return input;
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
if (!isArrayLike(input)) return input;
begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
if (limit >= 0) {
return input.slice(begin, begin + limit);
return sliceFn(input, begin, begin + limit);
} else {
if (begin === 0) {
return input.slice(limit, input.length);
return sliceFn(input, limit, input.length);
} else {
return input.slice(Math.max(0, begin + limit), begin);
return sliceFn(input, Math.max(0, begin + limit), begin);
}
}
};
}
function sliceFn(input, begin, end) {
if (isString(input)) return input.slice(begin, end);
return slice.call(input, begin, end);
}
+534 -159
View File
@@ -6,44 +6,128 @@
* @kind function
*
* @description
* Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
* for strings and numerically for numbers. Note: if you notice numbers are not being sorted
* as expected, make sure they are actually being saved as numbers and not strings.
* Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported.
* Returns an array containing the items from the specified `collection`, ordered by a `comparator`
* function based on the values computed using the `expression` predicate.
*
* @param {Array} array The array (or array-like object) to sort.
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
* used by the comparator to determine the order of elements.
* For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in
* `[{id: 'bar'}, {id: 'foo'}]`.
*
* The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray,
* String, etc).
*
* The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker
* for the preceeding one. The `expression` is evaluated against each item and the output is used
* for comparing with other items.
*
* You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in
* ascending order.
*
* The comparison is done using the `comparator` function. If none is specified, a default, built-in
* comparator is used (see below for details - in a nutshell, it compares numbers numerically and
* strings alphabetically).
*
* ### Under the hood
*
* Ordering the specified `collection` happens in two phases:
*
* 1. All items are passed through the predicate (or predicates), and the returned values are saved
* along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed
* through a predicate that extracts the value of the `label` property, would be transformed to:
* ```
* {
* value: 'foo',
* type: 'string',
* index: ...
* }
* ```
* 2. The comparator function is used to sort the items, based on the derived values, types and
* indices.
*
* If you use a custom comparator, it will be called with pairs of objects of the form
* `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal
* (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the
* second, or `1` otherwise.
*
* In order to ensure that the sorting will be deterministic across platforms, if none of the
* specified predicates can distinguish between two items, `orderBy` will automatically introduce a
* dummy predicate that returns the item's index as `value`.
* (If you are using a custom comparator, make sure it can handle this predicate as well.)
*
* Finally, in an attempt to simplify things, if a predicate returns an object as the extracted
* value for an item, `orderBy` will try to convert that object to a primitive value, before passing
* it to the comparator. The following rules govern the conversion:
*
* 1. If the object has a `valueOf()` method that returns a primitive, its return value will be
* used instead.<br />
* (If the object has a `valueOf()` method that returns another object, then the returned object
* will be used in subsequent steps.)
* 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that
* returns a primitive, its return value will be used instead.<br />
* (If the object has a `toString()` method that returns another object, then the returned object
* will be used in subsequent steps.)
* 3. No conversion; the object itself is used.
*
* ### The default comparator
*
* The default, built-in comparator should be sufficient for most usecases. In short, it compares
* numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
* using their index in the original collection, and sorts values of different types by type.
*
* More specifically, it follows these steps to determine the relative order of items:
*
* 1. If the compared values are of different types, compare the types themselves alphabetically.
* 2. If both values are of type `string`, compare them alphabetically in a case- and
* locale-insensitive way.
* 3. If both values are objects, compare their indices instead.
* 4. Otherwise, return:
* - `0`, if the values are equal (by strict equality comparison, i.e. using `===`).
* - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator).
* - `1`, otherwise.
*
* **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
* saved as numbers and not strings.
*
* @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
* @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
* predicates) to be used by the comparator to determine the order of elements.
*
* Can be one of:
*
* - `function`: Getter function. The result of this function will be sorted using the
* `<`, `===`, `>` operator.
* - `string`: An Angular expression. The result of this expression is used to compare elements
* (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
* 3 first characters of a property called `name`). The result of a constant expression
* is interpreted as a property name to be used in comparisons (for example `"special name"`
* to sort object by the value of their `special name` property). An expression can be
* optionally prefixed with `+` or `-` to control ascending or descending sort order
* (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
* element itself is used to compare where sorting.
* - `Array`: An array of function or string predicates. The first predicate in the array
* is used for sorting, but when two items are equivalent, the next predicate is used.
* - `Function`: A getter function. This function will be called with each item as argument and
* the return value will be used for sorting.
* - `string`: An Angular expression. This expression will be evaluated against each item and the
* result will be used for sorting. For example, use `'label'` to sort by a property called
* `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label`
* property.<br />
* (The result of a constant expression is interpreted as a property name to be used for
* comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a
* property called `special name`.)<br />
* An expression can be optionally prefixed with `+` or `-` to control the sorting direction,
* ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided,
* (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons.
* - `Array`: An array of function and/or string predicates. If a predicate cannot determine the
* relative order of two items, the next predicate is used as a tie-breaker.
*
* If the predicate is missing or empty then it defaults to `'+'`.
* **Note:** If the predicate is missing or empty then it defaults to `'+'`.
*
* @param {boolean=} reverse Reverse the order of the array.
* @returns {Array} Sorted copy of the source array.
* @param {boolean=} reverse - If `true`, reverse the sorting order.
* @param {(Function)=} comparator - The comparator function used to determine the relative order of
* value pairs. If omitted, the built-in comparator will be used.
*
* @returns {Array} - The sorted array.
*
*
* @example
* The example below demonstrates a simple ngRepeat, where the data is sorted
* by age in descending order (predicate is set to `'-age'`).
* `reverse` is not set, which means it defaults to `false`.
<example module="orderByExample">
* ### Ordering a table with `ngRepeat`
*
* The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by
* age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means
* it defaults to the built-in comparator.
*
<example name="orderBy-static" module="orderByExample1">
<file name="index.html">
<div ng-controller="ExampleController">
<table class="friend">
<table class="friends">
<tr>
<th>Name</th>
<th>Phone Number</th>
@@ -58,43 +142,77 @@
</div>
</file>
<file name="script.js">
angular.module('orderByExample', [])
angular.module('orderByExample1', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends =
[{name:'John', phone:'555-1212', age:10},
{name:'Mary', phone:'555-9876', age:19},
{name:'Mike', phone:'555-4321', age:21},
{name:'Adam', phone:'555-5678', age:35},
{name:'Julie', phone:'555-8765', age:29}];
$scope.friends = [
{name: 'John', phone: '555-1212', age: 10},
{name: 'Mary', phone: '555-9876', age: 19},
{name: 'Mike', phone: '555-4321', age: 21},
{name: 'Adam', phone: '555-5678', age: 35},
{name: 'Julie', phone: '555-8765', age: 29}
];
}]);
</file>
<file name="style.css">
.friends {
border-collapse: collapse;
}
.friends th {
border-bottom: 1px solid;
}
.friends td, .friends th {
border-left: 1px solid;
padding: 5px 10px;
}
.friends td:first-child, .friends th:first-child {
border-left: none;
}
</file>
<file name="protractor.js" type="protractor">
// Element locators
var names = element.all(by.repeater('friends').column('friend.name'));
it('should sort friends by age in reverse order', function() {
expect(names.get(0).getText()).toBe('Adam');
expect(names.get(1).getText()).toBe('Julie');
expect(names.get(2).getText()).toBe('Mike');
expect(names.get(3).getText()).toBe('Mary');
expect(names.get(4).getText()).toBe('John');
});
</file>
</example>
* <hr />
*
* The predicate and reverse parameters can be controlled dynamically through scope properties,
* as shown in the next example.
* @example
<example module="orderByExample">
* ### Changing parameters dynamically
*
* All parameters can be changed dynamically. The next example shows how you can make the columns of
* a table sortable, by binding the `expression` and `reverse` parameters to scope properties.
*
<example name="orderBy-dynamic" module="orderByExample2">
<file name="index.html">
<div ng-controller="ExampleController">
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
<pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
<hr/>
<button ng-click="predicate=''">Set to unsorted</button>
<table class="friend">
<button ng-click="propertyName = null; reverse = false">Set to unsorted</button>
<hr/>
<table class="friends">
<tr>
<th>
<button ng-click="order('name')">Name</button>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<button ng-click="order('phone')">Phone Number</button>
<span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<button ng-click="order('age')">Age</button>
<span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<button ng-click="sortBy('name')">Name</button>
<span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
</th>
<th>
<button ng-click="sortBy('phone')">Phone Number</button>
<span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
</th>
<th>
<button ng-click="sortBy('age')">Age</button>
<span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:predicate:reverse">
<tr ng-repeat="friend in friends | orderBy:propertyName:reverse">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
@@ -103,100 +221,335 @@
</div>
</file>
<file name="script.js">
angular.module('orderByExample', [])
angular.module('orderByExample2', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends =
[{name:'John', phone:'555-1212', age:10},
{name:'Mary', phone:'555-9876', age:19},
{name:'Mike', phone:'555-4321', age:21},
{name:'Adam', phone:'555-5678', age:35},
{name:'Julie', phone:'555-8765', age:29}];
$scope.predicate = 'age';
var friends = [
{name: 'John', phone: '555-1212', age: 10},
{name: 'Mary', phone: '555-9876', age: 19},
{name: 'Mike', phone: '555-4321', age: 21},
{name: 'Adam', phone: '555-5678', age: 35},
{name: 'Julie', phone: '555-8765', age: 29}
];
$scope.propertyName = 'age';
$scope.reverse = true;
$scope.order = function(predicate) {
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate;
$scope.friends = friends;
$scope.sortBy = function(propertyName) {
$scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
$scope.propertyName = propertyName;
};
}]);
</file>
</file>
<file name="style.css">
.friends {
border-collapse: collapse;
}
.friends th {
border-bottom: 1px solid;
}
.friends td, .friends th {
border-left: 1px solid;
padding: 5px 10px;
}
.friends td:first-child, .friends th:first-child {
border-left: none;
}
.sortorder:after {
content: '\25b2';
content: '\25b2'; // BLACK UP-POINTING TRIANGLE
}
.sortorder.reverse:after {
content: '\25bc';
content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
}
</file>
<file name="protractor.js" type="protractor">
// Element locators
var unsortButton = element(by.partialButtonText('unsorted'));
var nameHeader = element(by.partialButtonText('Name'));
var phoneHeader = element(by.partialButtonText('Phone'));
var ageHeader = element(by.partialButtonText('Age'));
var firstName = element(by.repeater('friends').column('friend.name').row(0));
var lastName = element(by.repeater('friends').column('friend.name').row(4));
it('should sort friends by some property, when clicking on the column header', function() {
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
phoneHeader.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Mary');
nameHeader.click();
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('Mike');
ageHeader.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Adam');
});
it('should sort friends in reverse order, when clicking on the same column', function() {
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
ageHeader.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Adam');
ageHeader.click();
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
});
it('should restore the original order, when clicking "Set to unsorted"', function() {
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
unsortButton.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Julie');
});
</file>
</example>
* <hr />
*
* @example
* ### Using `orderBy` inside a controller
*
* It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and
* calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory
* and retrieve the `orderBy` filter with `$filter('orderBy')`.)
*
<example name="orderBy-call-manually" module="orderByExample3">
<file name="index.html">
<div ng-controller="ExampleController">
<pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
<hr/>
<button ng-click="sortBy(null)">Set to unsorted</button>
<hr/>
<table class="friends">
<tr>
<th>
<button ng-click="sortBy('name')">Name</button>
<span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
</th>
<th>
<button ng-click="sortBy('phone')">Phone Number</button>
<span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
</th>
<th>
<button ng-click="sortBy('age')">Age</button>
<span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
</th>
</tr>
<tr ng-repeat="friend in friends">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
</tr>
</table>
</div>
</file>
<file name="script.js">
angular.module('orderByExample3', [])
.controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) {
var friends = [
{name: 'John', phone: '555-1212', age: 10},
{name: 'Mary', phone: '555-9876', age: 19},
{name: 'Mike', phone: '555-4321', age: 21},
{name: 'Adam', phone: '555-5678', age: 35},
{name: 'Julie', phone: '555-8765', age: 29}
];
$scope.propertyName = 'age';
$scope.reverse = true;
$scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
$scope.sortBy = function(propertyName) {
$scope.reverse = (propertyName !== null && $scope.propertyName === propertyName)
? !$scope.reverse : false;
$scope.propertyName = propertyName;
$scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
};
}]);
</file>
<file name="style.css">
.friends {
border-collapse: collapse;
}
.friends th {
border-bottom: 1px solid;
}
.friends td, .friends th {
border-left: 1px solid;
padding: 5px 10px;
}
.friends td:first-child, .friends th:first-child {
border-left: none;
}
.sortorder:after {
content: '\25b2'; // BLACK UP-POINTING TRIANGLE
}
.sortorder.reverse:after {
content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
}
</file>
<file name="protractor.js" type="protractor">
// Element locators
var unsortButton = element(by.partialButtonText('unsorted'));
var nameHeader = element(by.partialButtonText('Name'));
var phoneHeader = element(by.partialButtonText('Phone'));
var ageHeader = element(by.partialButtonText('Age'));
var firstName = element(by.repeater('friends').column('friend.name').row(0));
var lastName = element(by.repeater('friends').column('friend.name').row(4));
it('should sort friends by some property, when clicking on the column header', function() {
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
phoneHeader.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Mary');
nameHeader.click();
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('Mike');
ageHeader.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Adam');
});
it('should sort friends in reverse order, when clicking on the same column', function() {
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
ageHeader.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Adam');
ageHeader.click();
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
});
it('should restore the original order, when clicking "Set to unsorted"', function() {
expect(firstName.getText()).toBe('Adam');
expect(lastName.getText()).toBe('John');
unsortButton.click();
expect(firstName.getText()).toBe('John');
expect(lastName.getText()).toBe('Julie');
});
</file>
</example>
* <hr />
*
* @example
* ### Using a custom comparator
*
* If you have very specific requirements about the way items are sorted, you can pass your own
* comparator function. For example, you might need to compare some strings in a locale-sensitive
* way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
* argument - passing `false` retains the default sorting order, i.e. ascending.)
*
<example name="orderBy-custom-comparator" module="orderByExample4">
<file name="index.html">
<div ng-controller="ExampleController">
<div class="friends-container custom-comparator">
<h3>Locale-sensitive Comparator</h3>
<table class="friends">
<tr>
<th>Name</th>
<th>Favorite Letter</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator">
<td>{{friend.name}}</td>
<td>{{friend.favoriteLetter}}</td>
</tr>
</table>
</div>
<div class="friends-container default-comparator">
<h3>Default Comparator</h3>
<table class="friends">
<tr>
<th>Name</th>
<th>Favorite Letter</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:'favoriteLetter'">
<td>{{friend.name}}</td>
<td>{{friend.favoriteLetter}}</td>
</tr>
</table>
</div>
</div>
</file>
<file name="script.js">
angular.module('orderByExample4', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends = [
{name: 'John', favoriteLetter: 'Ä'},
{name: 'Mary', favoriteLetter: 'Ü'},
{name: 'Mike', favoriteLetter: 'Ö'},
{name: 'Adam', favoriteLetter: 'H'},
{name: 'Julie', favoriteLetter: 'Z'}
];
$scope.localeSensitiveComparator = function(v1, v2) {
// If we don't get strings, just compare by index
if (v1.type !== 'string' || v2.type !== 'string') {
return (v1.index < v2.index) ? -1 : 1;
}
// Compare strings alphabetically, taking locale into account
return v1.value.localeCompare(v2.value);
};
}]);
</file>
<file name="style.css">
.friends-container {
display: inline-block;
margin: 0 30px;
}
.friends {
border-collapse: collapse;
}
.friends th {
border-bottom: 1px solid;
}
.friends td, .friends th {
border-left: 1px solid;
padding: 5px 10px;
}
.friends td:first-child, .friends th:first-child {
border-left: none;
}
</file>
<file name="protractor.js" type="protractor">
// Element locators
var container = element(by.css('.custom-comparator'));
var names = container.all(by.repeater('friends').column('friend.name'));
it('should sort friends by favorite letter (in correct alphabetical order)', function() {
expect(names.get(0).getText()).toBe('John');
expect(names.get(1).getText()).toBe('Adam');
expect(names.get(2).getText()).toBe('Mike');
expect(names.get(3).getText()).toBe('Mary');
expect(names.get(4).getText()).toBe('Julie');
});
</file>
</example>
*
* It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
* filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
* desired parameters.
*
* Example:
*
* @example
<example module="orderByExample">
<file name="index.html">
<div ng-controller="ExampleController">
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
<table class="friend">
<tr>
<th>
<button ng-click="order('name')">Name</button>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<button ng-click="order('phone')">Phone Number</button>
<span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<button ng-click="order('age')">Age</button>
<span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
</th>
</tr>
<tr ng-repeat="friend in friends">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
</tr>
</table>
</div>
</file>
<file name="script.js">
angular.module('orderByExample', [])
.controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
var orderBy = $filter('orderBy');
$scope.friends = [
{ name: 'John', phone: '555-1212', age: 10 },
{ name: 'Mary', phone: '555-9876', age: 19 },
{ name: 'Mike', phone: '555-4321', age: 21 },
{ name: 'Adam', phone: '555-5678', age: 35 },
{ name: 'Julie', phone: '555-8765', age: 29 }
];
$scope.order = function(predicate) {
$scope.predicate = predicate;
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.friends = orderBy($scope.friends, predicate, $scope.reverse);
};
$scope.order('age', true);
}]);
</file>
<file name="style.css">
.sortorder:after {
content: '\25b2';
}
.sortorder.reverse:after {
content: '\25bc';
}
</file>
</example>
*/
orderByFilter.$inject = ['$parse'];
function orderByFilter($parse) {
return function(array, sortPredicate, reverseOrder) {
return function(array, sortPredicate, reverseOrder, compareFn) {
if (array == null) return array;
if (!isArrayLike(array)) {
@@ -206,11 +559,12 @@ function orderByFilter($parse) {
if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
var predicates = processPredicates(sortPredicate, reverseOrder);
// Add a predicate at the end that evaluates to the element index. This makes the
// sort stable as it works as a tie-breaker when all the input predicates cannot
// distinguish between two elements.
predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
var predicates = processPredicates(sortPredicate);
var descending = reverseOrder ? -1 : 1;
// Define the `compare()` function. Use a default comparator if none is specified.
var compare = isFunction(compareFn) ? compareFn : defaultCompare;
// The next three lines are a version of a Swartzian Transform idiom from Perl
// (sometimes called the Decorate-Sort-Undecorate idiom)
@@ -222,8 +576,12 @@ function orderByFilter($parse) {
return array;
function getComparisonObject(value, index) {
// NOTE: We are adding an extra `tieBreaker` value based on the element's index.
// This will be used to keep the sort stable when none of the input predicates can
// distinguish between two elements.
return {
value: value,
tieBreaker: {value: index, type: 'number', index: index},
predicateValues: predicates.map(function(predicate) {
return getPredicateValue(predicate.get(value), index);
})
@@ -231,18 +589,19 @@ function orderByFilter($parse) {
}
function doComparison(v1, v2) {
var result = 0;
for (var index=0, length = predicates.length; index < length; ++index) {
result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
if (result) break;
for (var i = 0, ii = predicates.length; i < ii; i++) {
var result = compare(v1.predicateValues[i], v2.predicateValues[i]);
if (result) {
return result * predicates[i].descending * descending;
}
}
return result;
return compare(v1.tieBreaker, v2.tieBreaker) * descending;
}
};
function processPredicates(sortPredicate, reverseOrder) {
reverseOrder = reverseOrder ? -1 : 1;
return sortPredicate.map(function(predicate) {
function processPredicates(sortPredicates) {
return sortPredicates.map(function(predicate) {
var descending = 1, get = identity;
if (isFunction(predicate)) {
@@ -260,7 +619,7 @@ function orderByFilter($parse) {
}
}
}
return { get: get, descending: descending * reverseOrder };
return {get: get, descending: descending};
});
}
@@ -275,9 +634,9 @@ function orderByFilter($parse) {
}
}
function objectValue(value, index) {
function objectValue(value) {
// If `valueOf` is a valid function use that
if (typeof value.valueOf === 'function') {
if (isFunction(value.valueOf)) {
value = value.valueOf();
if (isPrimitive(value)) return value;
}
@@ -286,8 +645,8 @@ function orderByFilter($parse) {
value = value.toString();
if (isPrimitive(value)) return value;
}
// We have a basic object so we use the position of the object in the collection
return index;
return value;
}
function getPredicateValue(value, index) {
@@ -295,23 +654,39 @@ function orderByFilter($parse) {
if (value === null) {
type = 'string';
value = 'null';
} else if (type === 'string') {
value = value.toLowerCase();
} else if (type === 'object') {
value = objectValue(value, index);
value = objectValue(value);
}
return { value: value, type: type };
return {value: value, type: type, index: index};
}
function compare(v1, v2) {
function defaultCompare(v1, v2) {
var result = 0;
if (v1.type === v2.type) {
if (v1.value !== v2.value) {
result = v1.value < v2.value ? -1 : 1;
var type1 = v1.type;
var type2 = v2.type;
if (type1 === type2) {
var value1 = v1.value;
var value2 = v2.value;
if (type1 === 'string') {
// Compare strings case-insensitively
value1 = value1.toLowerCase();
value2 = value2.toLowerCase();
} else if (type1 === 'object') {
// For basic objects, use the position of the object
// in the collection instead of the value
if (isObject(value1)) value1 = v1.index;
if (isObject(value2)) value2 = v2.index;
}
if (value1 !== value2) {
result = value1 < value2 ? -1 : 1;
}
} else {
result = v1.type < v2.type ? -1 : 1;
result = type1 < type2 ? -1 : 1;
}
return result;
}
}
+6
View File
@@ -328,6 +328,12 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
var locationPrototype = {
/**
* Ensure absolute url is initialized.
* @private
*/
$$absUrl:'',
/**
* Are we in html5 mode?
* @private
+1 -1
View File
@@ -545,7 +545,7 @@ AST.prototype = {
var args = [];
if (this.peekToken().text !== ')') {
do {
args.push(this.expression());
args.push(this.filterChain());
} while (this.expect(','));
}
return args;
-2
View File
@@ -348,8 +348,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
// may attempt more elements, but custom drivers are more particular
for (var i = drivers.length - 1; i >= 0; i--) {
var driverName = drivers[i];
if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
var factory = $injector.get(driverName);
var driver = factory(animationDetails);
if (driver) {
+69 -1
View File
@@ -3,6 +3,11 @@
* @name ngComponentRouter
* @installation
*
* <div class="alert alert-info">
* **Deprecation Notice:** In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module) has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
* </div>
*
* Currently, the **Component Router** module must be installed via `npm`, it is not yet available
* on Bower or the Google CDN.
*
@@ -15,7 +20,8 @@
* <script src="/node_modules/@angular/router/angular1/angular_1_router.js"></script>
*```
*
* You also need to include ES6 shims to support running on Internet Explorer:
* You also need to include ES6 shims for browsers that do not support ES6 code (Internet Explorer,
iOs < 8, Android < 5.0, Windows Mobile < 10):
* ```html
* <!-- IE required polyfills, in this exact order -->
* <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.33.3/es6-shim.min.js"></script>
@@ -45,6 +51,11 @@
* You can see the state of a router by inspecting the read-only field `router.navigating`.
* This may be useful for showing a spinner, for instance.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
@@ -57,6 +68,12 @@
* Apart from the **Top Level Component** ({@link $routerRootComponent}) which is associated with
* the {@link $rootRouter}, every **Routing Component** is associated with a `ChildRouter`,
* which manages the routing for that **Routing Component**.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
@@ -69,12 +86,19 @@
* There is only one instance of this type in a Component Router application injectable as the
* {@link $rootRouter} service. This **Router** is associate with the **Top Level Component**
* ({@link $routerRootComponent}). It acts as the connection between the **Routers** and the **Location**.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
* @ngdoc type
* @name ComponentInstruction
* @description
*
* A `ComponentInstruction` represents the route state for a single component. An `Instruction` is
* composed of a tree of these `ComponentInstruction`s.
*
@@ -82,6 +106,12 @@
* to route lifecycle hooks, like `$routerCanActivate`.
*
* You should not modify this object. It should be treated as immutable.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
@@ -96,14 +126,27 @@
* * `component`, `loader`, `redirectTo` (requires exactly one of these)
* * `name` - the name used to identify the **Route Definition** when generating links
* * `data` (optional)
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
* @ngdoc type
* @name RouteParams
* @description
*
* A map of parameters for a given route, passed as part of the {@link ComponentInstruction} to
* the Lifecycle Hooks, such as `$routerOnActivate` and `$routerOnDeactivate`.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
@@ -114,6 +157,12 @@
* @description
*
* The directive that identifies where the {@link Router} should render its **Components**.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
/**
@@ -128,6 +177,12 @@
* ```html
* <a ng-link="['Parent', {param: 1}, 'Child']">Link to Child View</a>
* ````
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
@@ -135,8 +190,15 @@
* @ngdoc service
* @name $rootRouter
* @description
*
* The singleton instance of the {@link RootRouter} type, which is associated
* with the top level {@link $routerRootComponent}.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
@@ -146,4 +208,10 @@
* @description
*
* The top level **Routing Component** associated with the {@link $rootRouter}.
*
* @deprecated
* In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module)
* has been deprecated and will not receive further updates.
* We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed
* projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
*/
+346 -335
View File
@@ -264,377 +264,387 @@ var jqLite = angular.element;
*/
angular.module('ngMessages', [])
/**
* @ngdoc directive
* @module ngMessages
* @name ngMessages
* @restrict AE
*
* @description
* `ngMessages` is a directive that is designed to show and hide messages based on the state
* of a key/value object that it listens on. The directive itself complements error message
* reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
*
* `ngMessages` manages the state of internal messages within its container element. The internal
* messages use the `ngMessage` directive and will be inserted/removed from the page depending
* on if they're present within the key/value object. By default, only one message will be displayed
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
*
* A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* @param {string} ngMessages an angular expression evaluating to a key/value object
* (this is typically the $error object on an ngModel instance).
* @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
*
* @example
* <example name="ngMessages-directive" module="ngMessagesExample"
* deps="angular-messages.js"
* animations="true" fixBase="true">
* <file name="index.html">
* <form name="myForm">
* <label>
* Enter your name:
* <input type="text"
* name="myName"
* ng-model="name"
* ng-minlength="5"
* ng-maxlength="20"
* required />
* </label>
* <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
*
* <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
* <div ng-message="required">You did not enter a field</div>
* <div ng-message="minlength">Your field is too short</div>
* <div ng-message="maxlength">Your field is too long</div>
* </div>
* </form>
* </file>
* <file name="script.js">
* angular.module('ngMessagesExample', ['ngMessages']);
* </file>
* </example>
*/
.directive('ngMessages', ['$animate', function($animate) {
var ACTIVE_CLASS = 'ng-active';
var INACTIVE_CLASS = 'ng-inactive';
/**
* @ngdoc directive
* @module ngMessages
* @name ngMessages
* @restrict AE
*
* @description
* `ngMessages` is a directive that is designed to show and hide messages based on the state
* of a key/value object that it listens on. The directive itself complements error message
* reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
*
* `ngMessages` manages the state of internal messages within its container element. The internal
* messages use the `ngMessage` directive and will be inserted/removed from the page depending
* on if they're present within the key/value object. By default, only one message will be displayed
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
*
* A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* @param {string} ngMessages an angular expression evaluating to a key/value object
* (this is typically the $error object on an ngModel instance).
* @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
*
* @example
* <example name="ngMessages-directive" module="ngMessagesExample"
* deps="angular-messages.js"
* animations="true" fixBase="true">
* <file name="index.html">
* <form name="myForm">
* <label>
* Enter your name:
* <input type="text"
* name="myName"
* ng-model="name"
* ng-minlength="5"
* ng-maxlength="20"
* required />
* </label>
* <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
*
* <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
* <div ng-message="required">You did not enter a field</div>
* <div ng-message="minlength">Your field is too short</div>
* <div ng-message="maxlength">Your field is too long</div>
* </div>
* </form>
* </file>
* <file name="script.js">
* angular.module('ngMessagesExample', ['ngMessages']);
* </file>
* </example>
*/
.directive('ngMessages', ['$animate', function($animate) {
var ACTIVE_CLASS = 'ng-active';
var INACTIVE_CLASS = 'ng-inactive';
return {
require: 'ngMessages',
restrict: 'AE',
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var ctrl = this;
var latestKey = 0;
var nextAttachId = 0;
return {
require: 'ngMessages',
restrict: 'AE',
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var ctrl = this;
var latestKey = 0;
var nextAttachId = 0;
this.getAttachId = function getAttachId() { return nextAttachId++; };
this.getAttachId = function getAttachId() { return nextAttachId++; };
var messages = this.messages = {};
var renderLater, cachedCollection;
var messages = this.messages = {};
var renderLater, cachedCollection;
this.render = function(collection) {
collection = collection || {};
this.render = function(collection) {
collection = collection || {};
renderLater = false;
cachedCollection = collection;
renderLater = false;
cachedCollection = collection;
// this is true if the attribute is empty or if the attribute value is truthy
var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
isAttrTruthy($scope, $attrs.multiple);
// this is true if the attribute is empty or if the attribute value is truthy
var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
isAttrTruthy($scope, $attrs.multiple);
var unmatchedMessages = [];
var matchedKeys = {};
var messageItem = ctrl.head;
var messageFound = false;
var totalMessages = 0;
var unmatchedMessages = [];
var matchedKeys = {};
var messageItem = ctrl.head;
var messageFound = false;
var totalMessages = 0;
// we use != instead of !== to allow for both undefined and null values
while (messageItem != null) {
totalMessages++;
var messageCtrl = messageItem.message;
// we use != instead of !== to allow for both undefined and null values
while (messageItem != null) {
totalMessages++;
var messageCtrl = messageItem.message;
var messageUsed = false;
if (!messageFound) {
forEach(collection, function(value, key) {
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
// this is to prevent the same error name from showing up twice
if (matchedKeys[key]) return;
matchedKeys[key] = true;
var messageUsed = false;
if (!messageFound) {
forEach(collection, function(value, key) {
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
// this is to prevent the same error name from showing up twice
if (matchedKeys[key]) return;
matchedKeys[key] = true;
messageUsed = true;
messageCtrl.attach();
}
});
}
messageUsed = true;
messageCtrl.attach();
}
});
}
if (messageUsed) {
// unless we want to display multiple messages then we should
// set a flag here to avoid displaying the next message in the list
messageFound = !multiple;
} else {
unmatchedMessages.push(messageCtrl);
}
if (messageUsed) {
// unless we want to display multiple messages then we should
// set a flag here to avoid displaying the next message in the list
messageFound = !multiple;
} else {
unmatchedMessages.push(messageCtrl);
}
messageItem = messageItem.next;
}
messageItem = messageItem.next;
}
forEach(unmatchedMessages, function(messageCtrl) {
messageCtrl.detach();
});
forEach(unmatchedMessages, function(messageCtrl) {
messageCtrl.detach();
});
unmatchedMessages.length !== totalMessages
unmatchedMessages.length !== totalMessages
? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS)
: $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
};
};
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
// If the element is destroyed, proactively destroy all the currently visible messages
$element.on('$destroy', function() {
forEach(messages, function(item) {
item.message.detach();
});
});
// If the element is destroyed, proactively destroy all the currently visible messages
$element.on('$destroy', function() {
forEach(messages, function(item) {
item.message.detach();
});
});
this.reRender = function() {
if (!renderLater) {
renderLater = true;
$scope.$evalAsync(function() {
if (renderLater) {
cachedCollection && ctrl.render(cachedCollection);
}
});
}
};
this.reRender = function() {
if (!renderLater) {
renderLater = true;
$scope.$evalAsync(function() {
if (renderLater) {
cachedCollection && ctrl.render(cachedCollection);
}
});
}
};
this.register = function(comment, messageCtrl) {
var nextKey = latestKey.toString();
messages[nextKey] = {
message: messageCtrl
};
insertMessageNode($element[0], comment, nextKey);
comment.$$ngMessageNode = nextKey;
latestKey++;
this.register = function(comment, messageCtrl) {
var nextKey = latestKey.toString();
messages[nextKey] = {
message: messageCtrl
};
insertMessageNode($element[0], comment, nextKey);
comment.$$ngMessageNode = nextKey;
latestKey++;
ctrl.reRender();
};
ctrl.reRender();
};
this.deregister = function(comment) {
var key = comment.$$ngMessageNode;
delete comment.$$ngMessageNode;
removeMessageNode($element[0], comment, key);
delete messages[key];
ctrl.reRender();
};
this.deregister = function(comment) {
var key = comment.$$ngMessageNode;
delete comment.$$ngMessageNode;
removeMessageNode($element[0], comment, key);
delete messages[key];
ctrl.reRender();
};
function findPreviousMessage(parent, comment) {
var prevNode = comment;
var parentLookup = [];
function findPreviousMessage(parent, comment) {
var prevNode = comment;
var parentLookup = [];
while (prevNode && prevNode !== parent) {
var prevKey = prevNode.$$ngMessageNode;
if (prevKey && prevKey.length) {
return messages[prevKey];
}
while (prevNode && prevNode !== parent) {
var prevKey = prevNode.$$ngMessageNode;
if (prevKey && prevKey.length) {
return messages[prevKey];
}
// dive deeper into the DOM and examine its children for any ngMessage
// comments that may be in an element that appears deeper in the list
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
parentLookup.push(prevNode);
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
} else if (prevNode.previousSibling) {
prevNode = prevNode.previousSibling;
} else {
prevNode = prevNode.parentNode;
parentLookup.push(prevNode);
}
}
}
// dive deeper into the DOM and examine its children for any ngMessage
// comments that may be in an element that appears deeper in the list
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) === -1) {
parentLookup.push(prevNode);
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
} else if (prevNode.previousSibling) {
prevNode = prevNode.previousSibling;
} else {
prevNode = prevNode.parentNode;
parentLookup.push(prevNode);
}
}
}
function insertMessageNode(parent, comment, key) {
var messageNode = messages[key];
if (!ctrl.head) {
ctrl.head = messageNode;
} else {
var match = findPreviousMessage(parent, comment);
if (match) {
messageNode.next = match.next;
match.next = messageNode;
} else {
messageNode.next = ctrl.head;
ctrl.head = messageNode;
}
}
}
function insertMessageNode(parent, comment, key) {
var messageNode = messages[key];
if (!ctrl.head) {
ctrl.head = messageNode;
} else {
var match = findPreviousMessage(parent, comment);
if (match) {
messageNode.next = match.next;
match.next = messageNode;
} else {
messageNode.next = ctrl.head;
ctrl.head = messageNode;
}
}
}
function removeMessageNode(parent, comment, key) {
var messageNode = messages[key];
function removeMessageNode(parent, comment, key) {
var messageNode = messages[key];
var match = findPreviousMessage(parent, comment);
if (match) {
match.next = messageNode.next;
} else {
ctrl.head = messageNode.next;
}
}
}]
};
var match = findPreviousMessage(parent, comment);
if (match) {
match.next = messageNode.next;
} else {
ctrl.head = messageNode.next;
}
}
}]
};
function isAttrTruthy(scope, attr) {
return (isString(attr) && attr.length === 0) || //empty attribute
truthy(scope.$eval(attr));
}
function isAttrTruthy(scope, attr) {
return (isString(attr) && attr.length === 0) || //empty attribute
truthy(scope.$eval(attr));
}
function truthy(val) {
return isString(val) ? val.length : !!val;
}
}])
function truthy(val) {
return isString(val) ? val.length : !!val;
}
}])
/**
* @ngdoc directive
* @name ngMessagesInclude
* @restrict AE
* @scope
*
* @description
* `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
* code from a remote template and place the downloaded template code into the exact spot
* that the ngMessagesInclude directive is placed within the ngMessages container. This allows
* for a series of pre-defined messages to be reused and also allows for the developer to
* determine what messages are overridden due to the placement of the ngMessagesInclude directive.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-messages-include="remoteTplString">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-messages-include src="expressionValue1">...</ng-messages-include>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
*/
.directive('ngMessagesInclude',
['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
/**
* @ngdoc directive
* @name ngMessagesInclude
* @restrict AE
* @scope
*
* @description
* `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
* code from a remote template and place the downloaded template code into the exact spot
* that the ngMessagesInclude directive is placed within the ngMessages container. This allows
* for a series of pre-defined messages to be reused and also allows for the developer to
* determine what messages are overridden due to the placement of the ngMessagesInclude directive.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-messages-include="remoteTplString">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-messages-include src="expressionValue1">...</ng-messages-include>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
*/
.directive('ngMessagesInclude',
['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
return {
restrict: 'AE',
require: '^^ngMessages', // we only require this for validation sake
link: function($scope, element, attrs) {
var src = attrs.ngMessagesInclude || attrs.src;
$templateRequest(src).then(function(html) {
if ($scope.$$destroyed) return;
return {
restrict: 'AE',
require: '^^ngMessages', // we only require this for validation sake
link: function($scope, element, attrs) {
var src = attrs.ngMessagesInclude || attrs.src;
$templateRequest(src).then(function(html) {
if ($scope.$$destroyed) return;
$compile(html)($scope, function(contents) {
element.after(contents);
if (isString(html) && !html.trim()) {
// Empty template - nothing to compile
replaceElementWithMarker(element, src);
} else {
// Non-empty template - compile and link
$compile(html)($scope, function(contents) {
element.after(contents);
replaceElementWithMarker(element, src);
});
}
});
}
};
// the anchor is placed for debugging purposes
var comment = $compile.$$createComment ?
$compile.$$createComment('ngMessagesInclude', src) :
$document[0].createComment(' ngMessagesInclude: ' + src + ' ');
var anchor = jqLite(comment);
element.after(anchor);
// Helpers
function replaceElementWithMarker(element, src) {
// A comment marker is placed for debugging purposes
var comment = $compile.$$createComment ?
$compile.$$createComment('ngMessagesInclude', src) :
$document[0].createComment(' ngMessagesInclude: ' + src + ' ');
var marker = jqLite(comment);
element.after(marker);
// we don't want to pollute the DOM anymore by keeping an empty directive element
element.remove();
});
});
}
};
}])
// Don't pollute the DOM anymore by keeping an empty directive element
element.remove();
}
}])
/**
* @ngdoc directive
* @name ngMessage
* @restrict AE
* @scope
*
* @description
* `ngMessage` is a directive with the purpose to show and hide a particular message.
* For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* More information about using `ngMessage` can be found in the
* {@link module:ngMessages `ngMessages` module documentation}.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* </ng-messages>
* ```
*
* @param {expression} ngMessage|when a string value corresponding to the message key.
*/
/**
* @ngdoc directive
* @name ngMessage
* @restrict AE
* @scope
*
* @description
* `ngMessage` is a directive with the purpose to show and hide a particular message.
* For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* More information about using `ngMessage` can be found in the
* {@link module:ngMessages `ngMessages` module documentation}.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* </ng-messages>
* ```
*
* @param {expression} ngMessage|when a string value corresponding to the message key.
*/
.directive('ngMessage', ngMessageDirectiveFactory())
/**
* @ngdoc directive
* @name ngMessageExp
* @restrict AE
* @priority 1
* @scope
*
* @description
* `ngMessageExp` is a directive with the purpose to show and hide a particular message.
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression">
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression">
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
*/
/**
* @ngdoc directive
* @name ngMessageExp
* @restrict AE
* @priority 1
* @scope
*
* @description
* `ngMessageExp` is a directive with the purpose to show and hide a particular message.
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression">
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression">
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
*/
.directive('ngMessageExp', ngMessageDirectiveFactory());
function ngMessageDirectiveFactory() {
@@ -654,8 +664,8 @@ function ngMessageDirectiveFactory() {
var assignRecords = function(items) {
records = items
? (isArray(items)
? items
: items.split(/[\s,]+/))
? items
: items.split(/[\s,]+/))
: null;
ngMessagesCtrl.reRender();
};
@@ -674,7 +684,7 @@ function ngMessageDirectiveFactory() {
},
attach: function() {
if (!currentElement) {
$transclude(scope, function(elm) {
$transclude(function(elm, newScope) {
$animate.enter(elm, null, element);
currentElement = elm;
@@ -690,6 +700,7 @@ function ngMessageDirectiveFactory() {
ngMessagesCtrl.deregister(commentNode);
messageCtrl.detach();
}
newScope.$destroy();
});
});
}
+10 -3
View File
@@ -2194,9 +2194,15 @@ angular.mock.$RootElementProvider = function() {
angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
return function(expression, locals, later, ident) {
if (later && typeof later === 'object') {
var create = $delegate(expression, locals, true, ident);
angular.extend(create.instance, later);
return create();
var instantiate = $delegate(expression, locals, true, ident);
angular.extend(instantiate.instance, later);
var instance = instantiate();
if (instance !== instantiate.instance) {
angular.extend(instance, later);
}
return instance;
}
return $delegate(expression, locals, later, ident);
};
@@ -2318,6 +2324,7 @@ angular.module('ngMock', ['ng']).provider({
* the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
*/
angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
$provide.value('$httpBackend', angular.injector(['ng']).get('$httpBackend'));
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
}]);
+91 -1
View File
@@ -56,7 +56,21 @@ function shallowClearAndCopy(src, dst) {
*
* <div doc-module-components="ngResource"></div>
*
* See {@link ngResource.$resource `$resource`} for usage.
* See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage.
*/
/**
* @ngdoc provider
* @name $resourceProvider
*
* @description
*
* Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource}
* service.
*
* ## Dependencies
* Requires the {@link ngResource } module to be installed.
*
*/
/**
@@ -251,6 +265,14 @@ function shallowClearAndCopy(src, dst) {
* - `$cancelRequest`: If there is a cancellable, pending request related to the instance or
* collection, calling this method will abort the request.
*
* The Resource instances have these additional methods:
*
* - `toJSON`: It returns a simple object without any of the extra properties added as part of
* the Resource API. This object can be serialized through {@link angular.toJson} safely
* without attaching Angular-specific fields. Notice that `JSON.stringify` (and
* `angular.toJson`) automatically use this method when serializing a Resource instance
* (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior)).
*
* @example
*
* # Credit card resource
@@ -402,10 +424,78 @@ angular.module('ngResource', ['ng']).
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
var provider = this;
/**
* @ngdoc property
* @name $resourceProvider#defaults
* @description
* Object containing default options used when creating `$resource` instances.
*
* The default values satisfy a wide range of usecases, but you may choose to overwrite any of
* them to further customize your instances. The available properties are:
*
* - **stripTrailingSlashes** `{boolean}` If true, then the trailing slashes from any
* calculated URL will be stripped.<br />
* (Defaults to true.)
* - **cancellable** `{boolean}` If true, the request made by a "non-instance" call will be
* cancelled (if not already completed) by calling `$cancelRequest()` on the call's return
* value. For more details, see {@link ngResource.$resource}. This can be overwritten per
* resource class or action.<br />
* (Defaults to false.)
* - **actions** - `{Object.<Object>}` - A hash with default actions declarations. Actions are
* high-level methods corresponding to RESTful actions/methods on resources. An action may
* specify what HTTP method to use, what URL to hit, if the return value will be a single
* object or a collection (array) of objects etc. For more details, see
* {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource
* class.<br />
* The default actions are:
* ```js
* {
* get: {method: 'GET'},
* save: {method: 'POST'},
* query: {method: 'GET', isArray: true},
* remove: {method: 'DELETE'},
* delete: {method: 'DELETE'}
* }
* ```
*
* #### Example
*
* For example, you can specify a new `update` action that uses the `PUT` HTTP verb:
*
* ```js
* angular.
* module('myApp').
* config(['resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions.update = {
* method: 'PUT'
* };
* });
* ```
*
* Or you can even overwrite the whole `actions` list and specify your own:
*
* ```js
* angular.
* module('myApp').
* config(['resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions = {
* create: {method: 'POST'}
* get: {method: 'GET'},
* getAll: {method: 'GET', isArray:true},
* update: {method: 'PUT'},
* delete: {method: 'DELETE'}
* };
* });
* ```
*
*/
this.defaults = {
// Strip slashes by default
stripTrailingSlashes: true,
// Make non-instance requests cancellable (via `$cancelRequest()`)
cancellable: false,
// Default actions configuration
actions: {
'get': {method: 'GET'},
+7 -1
View File
@@ -1,5 +1,11 @@
'use strict';
/* global shallowCopy: false */
// There are necessary for `shallowCopy()` (included via `src/shallowCopy.js`)
var isArray = angular.isArray;
var isObject = angular.isObject;
/**
* @ngdoc module
* @name ngRoute
@@ -154,7 +160,7 @@ function $RouteProvider() {
*/
this.when = function(path, route) {
//copy original route object to preserve params inherited from proto chain
var routeCopy = angular.copy(route);
var routeCopy = shallowCopy(route);
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true;
}
+11 -11
View File
@@ -141,6 +141,11 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
if (text == null || text === '') return text;
if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
var attributesFn =
angular.isFunction(attributes) ? attributes :
angular.isObject(attributes) ? function getAttributesObject() {return attributes;} :
function getEmptyAttributesObject() {return {};};
var match;
var raw = text;
var html = [];
@@ -169,19 +174,14 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
}
function addLink(url, text) {
var key;
var key, linkAttributes = attributesFn(url);
html.push('<a ');
if (angular.isFunction(attributes)) {
attributes = attributes(url);
for (key in linkAttributes) {
html.push(key + '="' + linkAttributes[key] + '" ');
}
if (angular.isObject(attributes)) {
for (key in attributes) {
html.push(key + '="' + attributes[key] + '" ');
}
} else {
attributes = {};
}
if (angular.isDefined(target) && !('target' in attributes)) {
if (angular.isDefined(target) && !('target' in linkAttributes)) {
html.push('target="',
target,
'" ');
+65 -1
View File
@@ -80,6 +80,17 @@
}
} else if (/touch/.test(eventType) && supportsTouchEvents()) {
evnt = createTouchEvent(element, eventType, x, y);
} else if (/key/.test(eventType)) {
evnt = window.document.createEvent('Events');
evnt.initEvent(eventType, eventData.bubbles, eventData.cancelable);
evnt.view = window;
evnt.ctrlKey = pressed('ctrl');
evnt.altKey = pressed('alt');
evnt.shiftKey = pressed('shift');
evnt.metaKey = pressed('meta');
evnt.keyCode = eventData.keyCode;
evnt.charCode = eventData.charCode;
evnt.which = eventData.which;
} else {
evnt = window.document.createEvent('MouseEvents');
x = x || 0;
@@ -108,7 +119,12 @@
return originalPreventDefault.apply(evnt, arguments);
};
element.dispatchEvent(evnt);
if (!eventData.bubbles || supportsEventBubblingInDetachedTree() || isAttachedToDocument(element)) {
element.dispatchEvent(evnt);
} else {
triggerForPath(element, evnt);
}
finalProcessDefault = !(angular['ff-684208-preventDefault'] || !fakeProcessDefault);
delete angular['ff-684208-preventDefault'];
@@ -146,4 +162,52 @@
return evnt;
}
function supportsEventBubblingInDetachedTree() {
if ('_cached' in supportsEventBubblingInDetachedTree) {
return supportsEventBubblingInDetachedTree._cached;
}
supportsEventBubblingInDetachedTree._cached = false;
var doc = window.document;
if (doc) {
var parent = doc.createElement('div'),
child = parent.cloneNode();
parent.appendChild(child);
parent.addEventListener('e', function() {
supportsEventBubblingInDetachedTree._cached = true;
});
var evnt = window.document.createEvent('Events');
evnt.initEvent('e', true, true);
child.dispatchEvent(evnt);
}
return supportsEventBubblingInDetachedTree._cached;
}
function triggerForPath(element, evnt) {
var stop = false;
var _stopPropagation = evnt.stopPropagation;
evnt.stopPropagation = function() {
stop = true;
_stopPropagation.apply(evnt, arguments);
};
patchEventTargetForBubbling(evnt, element);
do {
element.dispatchEvent(evnt);
} while (!stop && (element = element.parentNode));
}
function patchEventTargetForBubbling(event, target) {
event._target = target;
Object.defineProperty(event, "target", {get: function() { return this._target;}});
}
function isAttachedToDocument(element) {
while (element = element.parentNode) {
if (element === window) {
return true;
}
}
return false;
}
}());
+28
View File
@@ -0,0 +1,28 @@
'use strict';
/* global shallowCopy: true */
/**
* Creates a shallow copy of an object, an array or a primitive.
*
* Assumes that there are no proto properties for objects.
*/
function shallowCopy(src, dst) {
if (isArray(src)) {
dst = dst || [];
for (var i = 0, ii = src.length; i < ii; i++) {
dst[i] = src[i];
}
} else if (isObject(src)) {
dst = dst || {};
for (var key in src) {
if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
}
return dst || src;
}
+1 -1
View File
@@ -1,6 +1,6 @@
'use strict';
/* global: toDebugString: true */
/* global toDebugString: true */
function serializeObject(obj) {
var seen = [];
+6 -1
View File
@@ -233,6 +233,11 @@ beforeEach(function() {
compare: generateCompare(false),
negativeCompare: generateCompare(true)
};
function hasClass(element, selector) {
if (!element.getAttribute) return false;
return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
indexOf(" " + selector + " ") > -1);
}
function generateCompare(isNot) {
return function(actual, clazz) {
var message = function() {
@@ -240,7 +245,7 @@ beforeEach(function() {
};
var classes = clazz.trim().split(/\s+/);
for (var i = 0; i < classes.length; ++i) {
if (!jqLiteHasClass(actual[0], classes[i])) {
if (!hasClass(actual[0], classes[i])) {
return { pass: isNot };
}
}
+150
View File
@@ -979,6 +979,29 @@ describe('$compile', function() {
}));
it('should not set merged attributes twice in $attrs', function() {
var attrs;
module(function() {
directive('logAttrs', function() {
return {
link: function($scope, $element, $attrs) {
attrs = $attrs;
}
};
});
});
inject(function($compile, $rootScope) {
element = $compile(
'<div><div log-attrs replace class="myLog"></div><div>')($rootScope);
var div = element.find('div');
expect(div.attr('class')).toBe('myLog log');
expect(attrs.class).toBe('myLog log');
});
});
it('should prevent multiple templates per element', inject(function($compile) {
try {
$compile('<div><span replace class="replace"></span></div>');
@@ -3650,6 +3673,46 @@ describe('$compile', function() {
expect(Controller2.prototype.$onInit).toHaveBeenCalledOnce();
});
});
it('should continue to trigger other `$onInit` hooks if one throws an error', function() {
function ThrowingController() {
this.$onInit = function() {
throw new Error('bad hook');
};
}
function LoggingController($log) {
this.$onInit = function() {
$log.info('onInit');
};
}
angular.module('my', [])
.component('c1', {
controller: ThrowingController,
bindings: {'prop': '<'}
})
.component('c2', {
controller: LoggingController,
bindings: {'prop': '<'}
})
.config(function($exceptionHandlerProvider) {
// We need to test with the exceptionHandler not rethrowing...
$exceptionHandlerProvider.mode('log');
});
module('my');
inject(function($compile, $rootScope, $exceptionHandler, $log) {
// Setup the directive with bindings that will keep updating the bound value forever
element = $compile('<div><c1 prop="a"></c1><c2 prop="a"></c2>')($rootScope);
// The first component's error should be logged
expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook'));
// The second component's hook should still be called
expect($log.info.logs.pop()).toEqual(['onInit']);
});
});
});
@@ -4121,6 +4184,93 @@ describe('$compile', function() {
expect($exceptionHandler.errors[0].toString()).toContain('[$compile:infchng] 10 $onChanges() iterations reached.');
});
});
it('should continue to trigger other `$onChanges` hooks if one throws an error', function() {
function ThrowingController() {
this.$onChanges = function(change) {
throw new Error('bad hook');
};
}
function LoggingController($log) {
this.$onChanges = function(change) {
$log.info('onChange');
};
}
angular.module('my', [])
.component('c1', {
controller: ThrowingController,
bindings: {'prop': '<'}
})
.component('c2', {
controller: LoggingController,
bindings: {'prop': '<'}
})
.config(function($exceptionHandlerProvider) {
// We need to test with the exceptionHandler not rethrowing...
$exceptionHandlerProvider.mode('log');
});
module('my');
inject(function($compile, $rootScope, $exceptionHandler, $log) {
// Setup the directive with bindings that will keep updating the bound value forever
element = $compile('<div><c1 prop="a"></c1><c2 prop="a"></c2>')($rootScope);
// The first component's error should be logged
expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook'));
// The second component's changes should still be called
expect($log.info.logs.pop()).toEqual(['onChange']);
$rootScope.$apply('a = 42');
// The first component's error should be logged
var errors = $exceptionHandler.errors.pop();
expect(errors[0]).toEqual(new Error('bad hook'));
// The second component's changes should still be called
expect($log.info.logs.pop()).toEqual(['onChange']);
});
});
it('should collect up all `$onChanges` errors into one throw', function() {
function ThrowingController() {
this.$onChanges = function(change) {
throw new Error('bad hook: ' + this.prop);
};
}
angular.module('my', [])
.component('c1', {
controller: ThrowingController,
bindings: {'prop': '<'}
})
.config(function($exceptionHandlerProvider) {
// We need to test with the exceptionHandler not rethrowing...
$exceptionHandlerProvider.mode('log');
});
module('my');
inject(function($compile, $rootScope, $exceptionHandler, $log) {
// Setup the directive with bindings that will keep updating the bound value forever
element = $compile('<div><c1 prop="a"></c1><c1 prop="a * 2"></c1>')($rootScope);
// Both component's errors should be logged
expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook: NaN'));
expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook: undefined'));
$rootScope.$apply('a = 42');
// Both component's error should be logged
var errors = $exceptionHandler.errors.pop();
expect(errors.pop()).toEqual(new Error('bad hook: 84'));
expect(errors.pop()).toEqual(new Error('bad hook: 42'));
});
});
});
});
+89
View File
@@ -2784,14 +2784,103 @@ describe('input', function() {
describe('EMAIL_REGEXP', function() {
/* global EMAIL_REGEXP: false */
it('should validate email', function() {
/* basic functionality */
expect(EMAIL_REGEXP.test('a@b.com')).toBe(true);
expect(EMAIL_REGEXP.test('a@b.museum')).toBe(true);
expect(EMAIL_REGEXP.test('a@B.c')).toBe(true);
/* domain label separation, hyphen-minus, syntax */
expect(EMAIL_REGEXP.test('a@b.c.')).toBe(false);
expect(EMAIL_REGEXP.test('a@.b.c')).toBe(false);
expect(EMAIL_REGEXP.test('a@-b.c')).toBe(false);
expect(EMAIL_REGEXP.test('a@b-.c')).toBe(false);
expect(EMAIL_REGEXP.test('a@b-c')).toBe(true);
expect(EMAIL_REGEXP.test('a@-')).toBe(false);
expect(EMAIL_REGEXP.test('a@.')).toBe(false);
expect(EMAIL_REGEXP.test('a@host_name')).toBe(false);
/* leading or sole digit */
expect(EMAIL_REGEXP.test('a@3b.c')).toBe(true);
expect(EMAIL_REGEXP.test('a@3')).toBe(true);
/* TLD eMail address */
expect(EMAIL_REGEXP.test('a@b')).toBe(true);
/* domain valid characters */
expect(EMAIL_REGEXP.test('a@abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789')).toBe(true);
/* domain invalid characters */
expect(EMAIL_REGEXP.test('a@')).toBe(false);
expect(EMAIL_REGEXP.test('a@ ')).toBe(false);
expect(EMAIL_REGEXP.test('a@!')).toBe(false);
expect(EMAIL_REGEXP.test('a@"')).toBe(false);
expect(EMAIL_REGEXP.test('a@#')).toBe(false);
expect(EMAIL_REGEXP.test('a@$')).toBe(false);
expect(EMAIL_REGEXP.test('a@%')).toBe(false);
expect(EMAIL_REGEXP.test('a@&')).toBe(false);
expect(EMAIL_REGEXP.test("a@'")).toBe(false);
expect(EMAIL_REGEXP.test('a@(')).toBe(false);
expect(EMAIL_REGEXP.test('a@)')).toBe(false);
expect(EMAIL_REGEXP.test('a@*')).toBe(false);
expect(EMAIL_REGEXP.test('a@+')).toBe(false);
expect(EMAIL_REGEXP.test('a@,')).toBe(false);
expect(EMAIL_REGEXP.test('a@/')).toBe(false);
expect(EMAIL_REGEXP.test('a@:')).toBe(false);
expect(EMAIL_REGEXP.test('a@;')).toBe(false);
expect(EMAIL_REGEXP.test('a@<')).toBe(false);
expect(EMAIL_REGEXP.test('a@=')).toBe(false);
expect(EMAIL_REGEXP.test('a@>')).toBe(false);
expect(EMAIL_REGEXP.test('a@?')).toBe(false);
expect(EMAIL_REGEXP.test('a@@')).toBe(false);
expect(EMAIL_REGEXP.test('a@[')).toBe(false);
expect(EMAIL_REGEXP.test('a@\\')).toBe(false);
expect(EMAIL_REGEXP.test('a@]')).toBe(false);
expect(EMAIL_REGEXP.test('a@^')).toBe(false);
expect(EMAIL_REGEXP.test('a@_')).toBe(false);
expect(EMAIL_REGEXP.test('a@`')).toBe(false);
expect(EMAIL_REGEXP.test('a@{')).toBe(false);
expect(EMAIL_REGEXP.test('a@|')).toBe(false);
expect(EMAIL_REGEXP.test('a@}')).toBe(false);
expect(EMAIL_REGEXP.test('a@~')).toBe(false);
expect(EMAIL_REGEXP.test('a@İ')).toBe(false);
expect(EMAIL_REGEXP.test('a@ı')).toBe(false);
/* domain length, label and total */
expect(EMAIL_REGEXP.test('a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')).toBe(true);
expect(EMAIL_REGEXP.test('a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')).toBe(false);
/* jshint maxlen:320 */
expect(EMAIL_REGEXP.test('a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')).toBe(true);
expect(EMAIL_REGEXP.test('a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.x')).toBe(true);
expect(EMAIL_REGEXP.test('a@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xx')).toBe(false);
expect(EMAIL_REGEXP.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xx')).toBe(true);
expect(EMAIL_REGEXP.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxx')).toBe(false);
/* jshint maxlen:200 */
/* local-part valid characters and dot-atom syntax */
expect(EMAIL_REGEXP.test("'@x")).toBe(true);
expect(EMAIL_REGEXP.test('-!#$%&*+/0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ@x')).toBe(true);
expect(EMAIL_REGEXP.test('^_`abcdefghijklmnopqrstuvwxyz{|}~@x')).toBe(true);
expect(EMAIL_REGEXP.test(".@x")).toBe(false);
expect(EMAIL_REGEXP.test("'.@x")).toBe(false);
expect(EMAIL_REGEXP.test(".'@x")).toBe(false);
expect(EMAIL_REGEXP.test("'.'@x")).toBe(true);
/* local-part invalid characters */
expect(EMAIL_REGEXP.test('@x')).toBe(false);
expect(EMAIL_REGEXP.test(' @x')).toBe(false);
expect(EMAIL_REGEXP.test('"@x')).toBe(false);
expect(EMAIL_REGEXP.test('(@x')).toBe(false);
expect(EMAIL_REGEXP.test(')@x')).toBe(false);
expect(EMAIL_REGEXP.test(',@x')).toBe(false);
expect(EMAIL_REGEXP.test(':@x')).toBe(false);
expect(EMAIL_REGEXP.test(';@x')).toBe(false);
expect(EMAIL_REGEXP.test('<@x')).toBe(false);
expect(EMAIL_REGEXP.test('>@x')).toBe(false);
expect(EMAIL_REGEXP.test('@@x')).toBe(false);
expect(EMAIL_REGEXP.test('[@x')).toBe(false);
expect(EMAIL_REGEXP.test('\\@x')).toBe(false);
expect(EMAIL_REGEXP.test(']@x')).toBe(false);
expect(EMAIL_REGEXP.test('İ@x')).toBe(false);
expect(EMAIL_REGEXP.test('ı@x')).toBe(false);
/* local-part size limit */
expect(EMAIL_REGEXP.test('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@x')).toBe(true);
expect(EMAIL_REGEXP.test('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@x')).toBe(false);
/* content (local-part + @ + domain) is required */
expect(EMAIL_REGEXP.test('')).toBe(false);
expect(EMAIL_REGEXP.test('a')).toBe(false);
expect(EMAIL_REGEXP.test('aa')).toBe(false);
});
});
});
+74 -6
View File
@@ -4,12 +4,26 @@ describe('Filter: limitTo', function() {
var items;
var str;
var number;
var arrayLike;
var limitTo;
beforeEach(inject(function($filter) {
items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
str = "tuvwxyz";
number = 100.045;
arrayLike = {
0: 'a',
1: 'b',
2: 'c',
3: 'd',
4: 'e',
5: 'f',
6: 'g',
7: 'h',
get length() {
return Object.keys(this).length - 1;
}
};
limitTo = $filter('limitTo');
}));
@@ -21,6 +35,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(str, '3')).toEqual("tuv");
expect(limitTo(number, 3)).toEqual("100");
expect(limitTo(number, '3')).toEqual("100");
expect(limitTo(arrayLike, 3)).toEqual(['a', 'b', 'c']);
expect(limitTo(arrayLike, '3')).toEqual(['a', 'b', 'c']);
});
it('should return the first X items beginning from index Y when X and Y are positive', function() {
@@ -28,6 +44,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, '3', 3)).toEqual(['d', 'e', 'f']);
expect(limitTo(str, 3, 3)).toEqual("wxy");
expect(limitTo(str, '3', '3')).toEqual("wxy");
expect(limitTo(arrayLike, 3, 3)).toEqual(['d', 'e', 'f']);
expect(limitTo(arrayLike, '3', '3')).toEqual(['d', 'e', 'f']);
});
it('should return the first X items beginning from index Y when X is positive and Y is negative', function() {
@@ -35,6 +53,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, '3', -3)).toEqual(['f', 'g', 'h']);
expect(limitTo(str, 3, -3)).toEqual("xyz");
expect(limitTo(str, '3', '-3')).toEqual("xyz");
expect(limitTo(arrayLike, 3, '-3')).toEqual(['f', 'g', 'h']);
expect(limitTo(arrayLike, '3', -3)).toEqual(['f', 'g', 'h']);
});
it('should return the last X items when X is negative', function() {
@@ -44,6 +64,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(str, '-3')).toEqual("xyz");
expect(limitTo(number, -3)).toEqual("045");
expect(limitTo(number, '-3')).toEqual("045");
expect(limitTo(arrayLike, -3)).toEqual(['f', 'g', 'h']);
expect(limitTo(arrayLike, '-3')).toEqual(['f', 'g', 'h']);
});
it('should return the last X items until index Y when X and Y are negative', function() {
@@ -51,6 +73,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, '-3', -3)).toEqual(['c', 'd', 'e']);
expect(limitTo(str, -3, -3)).toEqual("uvw");
expect(limitTo(str, '-3', '-3')).toEqual("uvw");
expect(limitTo(arrayLike, -3, '-3')).toEqual(['c', 'd', 'e']);
expect(limitTo(arrayLike, '-3', -3)).toEqual(['c', 'd', 'e']);
});
it('should return the last X items until index Y when X is negative and Y is positive', function() {
@@ -58,19 +82,28 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, '-3', 4)).toEqual(['b', 'c', 'd']);
expect(limitTo(str, -3, 4)).toEqual("uvw");
expect(limitTo(str, '-3', '4')).toEqual("uvw");
expect(limitTo(arrayLike, -3, '4')).toEqual(['b', 'c', 'd']);
expect(limitTo(arrayLike, '-3', 4)).toEqual(['b', 'c', 'd']);
});
it('should return an empty array when X = 0', function() {
expect(limitTo(items, 0)).toEqual([]);
expect(limitTo(items, '0')).toEqual([]);
expect(limitTo(arrayLike, 0)).toEqual([]);
expect(limitTo(arrayLike, '0')).toEqual([]);
});
it('should return entire array when X cannot be parsed', function() {
expect(limitTo(items, 'bogus')).toEqual(items);
expect(limitTo(items, 'null')).toEqual(items);
expect(limitTo(items, 'undefined')).toEqual(items);
expect(limitTo(items, null)).toEqual(items);
expect(limitTo(items, undefined)).toEqual(items);
expect(limitTo(items, 'bogus')).toBe(items);
expect(limitTo(items, 'null')).toBe(items);
expect(limitTo(items, 'undefined')).toBe(items);
expect(limitTo(items, null)).toBe(items);
expect(limitTo(items, undefined)).toBe(items);
expect(limitTo(arrayLike, 'bogus')).toBe(arrayLike);
expect(limitTo(arrayLike, 'null')).toBe(arrayLike);
expect(limitTo(arrayLike, 'undefined')).toBe(arrayLike);
expect(limitTo(arrayLike, null)).toBe(arrayLike);
expect(limitTo(arrayLike, undefined)).toBe(arrayLike);
});
it('should return an empty string when X = 0', function() {
@@ -97,9 +130,14 @@ describe('Filter: limitTo', function() {
expect(limitTo(str, '3', 'undefined')).toEqual(limitTo(str, '3'));
expect(limitTo(str, '-3', null)).toEqual(limitTo(str, '-3', 0));
expect(limitTo(str, 3, undefined)).toEqual(limitTo(str, 3));
expect(limitTo(arrayLike, 3, 'bogus')).toEqual(limitTo(arrayLike, 3, 0));
expect(limitTo(arrayLike, -3, 'null')).toEqual(limitTo(arrayLike, -3));
expect(limitTo(arrayLike, '3', 'undefined')).toEqual(limitTo(arrayLike, '3', 0));
expect(limitTo(arrayLike, '-3', null)).toEqual(limitTo(arrayLike, '-3'));
expect(limitTo(arrayLike, 3, undefined)).toEqual(limitTo(arrayLike, 3, 0));
});
it('should return input if not String or Array or Number', function() {
it('should return input if not array-like or Number', function() {
expect(limitTo(null, 1)).toEqual(null);
expect(limitTo(undefined, 1)).toEqual(undefined);
expect(limitTo({}, 1)).toEqual({});
@@ -111,8 +149,13 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, '9')).toEqual(items);
expect(limitTo(items, -9)).toEqual(items);
expect(limitTo(items, '-9')).toEqual(items);
expect(limitTo(arrayLike, 9)).toEqual(items);
expect(limitTo(arrayLike, '9')).toEqual(items);
expect(limitTo(arrayLike, -9)).toEqual(items);
expect(limitTo(arrayLike, '-9')).toEqual(items);
expect(limitTo(items, 9)).not.toBe(items);
expect(limitTo(arrayLike, 9)).not.toBe(arrayLike);
});
it('should return the entire string if X exceeds input length', function() {
@@ -129,6 +172,10 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, 'Infinity')).toEqual(items);
expect(limitTo(items, -Infinity)).toEqual(items);
expect(limitTo(items, '-Infinity')).toEqual(items);
expect(limitTo(arrayLike, Infinity)).toEqual(items);
expect(limitTo(arrayLike, 'Infinity')).toEqual(items);
expect(limitTo(arrayLike, -Infinity)).toEqual(items);
expect(limitTo(arrayLike, '-Infinity')).toEqual(items);
});
it('should return the entire string when limited by Infinity', function() {
@@ -141,6 +188,8 @@ describe('Filter: limitTo', function() {
it('should return an empty array if Y exceeds input length', function() {
expect(limitTo(items, '3', 12)).toEqual([]);
expect(limitTo(items, -3, '12')).toEqual([]);
expect(limitTo(arrayLike, '3', 12)).toEqual([]);
expect(limitTo(arrayLike, -3, '12')).toEqual([]);
});
it('should return an empty string if Y exceeds input length', function() {
@@ -153,6 +202,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, '-4', -12)).toEqual(['e', 'f', 'g', 'h']);
expect(limitTo(str, 4, '-12')).toEqual("tuvw");
expect(limitTo(str, '-4', -12)).toEqual("wxyz");
expect(limitTo(arrayLike, 4, '-12')).toEqual(['a', 'b', 'c', 'd']);
expect(limitTo(arrayLike, '-4', -12)).toEqual(['e', 'f', 'g', 'h']);
});
it('should return the entire string beginning from Y if X is positive and X+Y exceeds input length', function() {
@@ -160,6 +211,8 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, 7, -3)).toEqual(['f', 'g', 'h']);
expect(limitTo(str, 6, 3)).toEqual("wxyz");
expect(limitTo(str, 6, -3)).toEqual("xyz");
expect(limitTo(arrayLike, 7, 3)).toEqual(['d', 'e', 'f', 'g', 'h']);
expect(limitTo(arrayLike, 7, -3)).toEqual(['f', 'g', 'h']);
});
it('should return the entire string until index Y if X is negative and X+Y exceeds input length', function() {
@@ -167,5 +220,20 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, -7, -3)).toEqual(['a', 'b', 'c', 'd', 'e']);
expect(limitTo(str, -6, 3)).toEqual("tuv");
expect(limitTo(str, -6, -3)).toEqual("tuvw");
expect(limitTo(arrayLike, -7, 3)).toEqual(['a', 'b', 'c']);
expect(limitTo(arrayLike, -7, -3)).toEqual(['a', 'b', 'c', 'd', 'e']);
});
it('should not throw an error if used with an array like object', function() {
function getArguments() {
return arguments;
}
var argsObj = getArguments({name: 'Misko'}, {name: 'Igor'}, {name: 'Brad'});
var nodeList = jqLite("<p><span>Misko</span><span>Igor</span><span>Brad</span></p>")[0].childNodes;
expect(limitTo(argsObj, 2).length).toBe(2);
expect(limitTo('abc', 1).length).toBe(1);
expect(limitTo(nodeList, 2).length).toBe(2);
});
});
+344 -23
View File
@@ -13,15 +13,18 @@ describe('Filter: orderBy', function() {
toThrowMinErr('orderBy', 'notarray', 'Expected array but received: {}');
});
it('should not throw an exception if a null or undefined value is provided', function() {
expect(orderBy(null)).toEqual(null);
expect(orderBy(undefined)).toEqual(undefined);
});
it('should not throw an exception if an array-like object is provided', function() {
expect(orderBy('cba')).toEqual(['a', 'b', 'c']);
});
it('should return sorted array if predicate is not provided', function() {
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
@@ -37,13 +40,6 @@ describe('Filter: orderBy', function() {
});
it('should reverse collection if `reverseOrder` param is truthy', function() {
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
});
it('should sort inherited from array', function() {
function BaseCollection() {}
BaseCollection.prototype = Array.prototype;
@@ -93,6 +89,7 @@ describe('Filter: orderBy', function() {
{ a:new Date('01/01/2014'), b:3 }]);
});
it('should compare timestamps when sorting dates', function() {
expect(orderBy([
new Date('01/01/2015'),
@@ -140,22 +137,6 @@ describe('Filter: orderBy', function() {
expect(orderBy(array)).toEqualData(array);
});
it('should reverse array of objects with no predicate and reverse is `true`', function() {
var array = [
{ id: 2 },
{ id: 1 },
{ id: 4 },
{ id: 3 }
];
var reversedArray = [
{ id: 3 },
{ id: 4 },
{ id: 1 },
{ id: 2 }
];
expect(orderBy(array, '', true)).toEqualData(reversedArray);
});
it('should reverse array of objects with predicate of "-"', function() {
var array = [
@@ -237,6 +218,346 @@ describe('Filter: orderBy', function() {
{foo: 1, bar: 8}, {foo: 1, bar: 5}, {foo: 1, bar: 2}
]);
});
describe('(reversing order)', function() {
it('should not reverse collection if `reverse` param is falsy',
function() {
var items = [{a: 2}, {a: 15}];
var expr = 'a';
var sorted = [{a: 2}, {a: 15}];
expect(orderBy(items, expr, false)).toEqual(sorted);
expect(orderBy(items, expr, 0)).toEqual(sorted);
expect(orderBy(items, expr, '')).toEqual(sorted);
expect(orderBy(items, expr, NaN)).toEqual(sorted);
expect(orderBy(items, expr, null)).toEqual(sorted);
expect(orderBy(items, expr, undefined)).toEqual(sorted);
}
);
it('should reverse collection if `reverse` param is truthy',
function() {
var items = [{a: 2}, {a: 15}];
var expr = 'a';
var sorted = [{a: 15}, {a: 2}];
expect(orderBy(items, expr, true)).toEqual(sorted);
expect(orderBy(items, expr, 1)).toEqual(sorted);
expect(orderBy(items, expr, 'reverse')).toEqual(sorted);
expect(orderBy(items, expr, {})).toEqual(sorted);
expect(orderBy(items, expr, [])).toEqual(sorted);
expect(orderBy(items, expr, noop)).toEqual(sorted);
}
);
it('should reverse collection if `reverse` param is `true`, even without an `expression`',
function() {
var originalItems = [{id: 2}, {id: 1}, {id: 4}, {id: 3}];
var reversedItems = [{id: 3}, {id: 4}, {id: 1}, {id: 2}];
expect(orderBy(originalItems, null, true)).toEqual(reversedItems);
}
);
});
describe('(built-in comparator)', function() {
it('should compare numbers numarically', function() {
var items = [100, 3, 20];
var expr = null;
var sorted = [3, 20, 100];
expect(orderBy(items, expr)).toEqual(sorted);
});
it('should compare strings alphabetically', function() {
var items = ['100', '3', '20', '_b', 'a'];
var expr = null;
var sorted = ['100', '20', '3', '_b', 'a'];
expect(orderBy(items, expr)).toEqual(sorted);
});
it('should compare strings case-insensitively', function() {
var items = ['c', 'B', 'a'];
var expr = null;
var sorted = ['a', 'B', 'c'];
expect(orderBy(items, expr)).toEqual(sorted);
});
it('should compare objects based on `index`', function() {
var items = [{c: 3}, {b: 2}, {a: 1}];
var expr = null;
var sorted = [{c: 3}, {b: 2}, {a: 1}];
expect(orderBy(items, expr)).toEqual(sorted);
});
it('should compare values of different type alphabetically by type', function() {
var items = [undefined, '1', {}, 999, noop, false];
var expr = null;
var sorted = [false, noop, 999, {}, '1', undefined];
expect(orderBy(items, expr)).toEqual(sorted);
});
});
describe('(custom comparator)', function() {
it('should support a custom comparator', function() {
var items = [4, 42, 2];
var expr = null;
var reverse = null;
var sorted = [42, 2, 4];
var comparator = function(o1, o2) {
var v1 = o1.value;
var v2 = o2.value;
// 42 always comes first
if (v1 === v2) return 0;
if (v1 === 42) return -1;
if (v2 === 42) return 1;
// Default comparison for other values
return (v1 < v2) ? -1 : 1;
};
expect(orderBy(items, expr, reverse, comparator)).toEqual(sorted);
});
it('should support `reverseOrder` with a custom comparator', function() {
var items = [4, 42, 2];
var expr = null;
var reverse = true;
var sorted = [4, 2, 42];
var comparator = function(o1, o2) {
var v1 = o1.value;
var v2 = o2.value;
// 42 always comes first
if (v1 === v2) return 0;
if (v1 === 42) return -1;
if (v2 === 42) return 1;
// Default comparison for other values
return (v1 < v2) ? -1 : 1;
};
expect(orderBy(items, expr, reverse, comparator)).toEqual(sorted);
});
it('should pass `{value, type, index}` objects to comparators', function() {
var items = [false, noop, 999, {}, '', undefined];
var expr = null;
var reverse = null;
var comparator = jasmine.createSpy('comparator').and.returnValue(-1);
orderBy(items, expr, reverse, comparator);
var allArgsFlat = Array.prototype.concat.apply([], comparator.calls.allArgs());
expect(allArgsFlat).toContain({index: 0, type: 'boolean', value: false });
expect(allArgsFlat).toContain({index: 1, type: 'function', value: noop });
expect(allArgsFlat).toContain({index: 2, type: 'number', value: 999 });
expect(allArgsFlat).toContain({index: 3, type: 'object', value: {} });
expect(allArgsFlat).toContain({index: 4, type: 'string', value: '' });
expect(allArgsFlat).toContain({index: 5, type: 'undefined', value: undefined});
});
it('should treat a value of `null` as `"null"`', function() {
var items = [null, null];
var expr = null;
var reverse = null;
var comparator = jasmine.createSpy('comparator').and.returnValue(-1);
orderBy(items, expr, reverse, comparator);
var arg = comparator.calls.argsFor(0)[0];
expect(arg).toEqual(jasmine.objectContaining({
type: 'string',
value: 'null'
}));
});
it('should not convert strings to lower-case', function() {
var items = ['c', 'B', 'a'];
var expr = null;
var reverse = null;
var sorted = ['B', 'a', 'c'];
var comparator = function(o1, o2) {
return (o1.value < o2.value) ? -1 : 1;
};
expect(orderBy(items, expr, reverse, comparator)).toEqual(sorted);
});
it('should use `index` as `value` if no other predicate can distinguish between two items',
function() {
var items = ['foo', 'bar'];
var expr = null;
var reverse = null;
var comparator = jasmine.createSpy('comparator').and.returnValue(0);
orderBy(items, expr, reverse, comparator);
expect(comparator).toHaveBeenCalledTimes(2);
var lastArgs = comparator.calls.mostRecent().args;
expect(lastArgs).toContain(jasmine.objectContaining({value: 0, type: 'number'}));
expect(lastArgs).toContain(jasmine.objectContaining({value: 1, type: 'number'}));
}
);
it('should support multiple predicates and per-predicate sorting direction', function() {
var items = [
{owner: 'ownerA', type: 'typeA'},
{owner: 'ownerB', type: 'typeB'},
{owner: 'ownerC', type: 'typeB'},
{owner: 'ownerD', type: 'typeB'}
];
var expr = ['type', '-owner'];
var reverse = null;
var sorted = [
{owner: 'ownerA', type: 'typeA'},
{owner: 'ownerC', type: 'typeB'},
{owner: 'ownerB', type: 'typeB'},
{owner: 'ownerD', type: 'typeB'}
];
var comparator = function(o1, o2) {
var v1 = o1.value;
var v2 = o2.value;
var isNerd1 = v1.toLowerCase().indexOf('nerd') !== -1;
var isNerd2 = v2.toLowerCase().indexOf('nerd') !== -1;
// Shamelessly promote "nerds"
if (isNerd1 || isNerd2) {
return (isNerd1 && isNerd2) ? 0 : (isNerd1) ? -1 : 1;
}
// No "nerd"; alpabetical order
return (v1 === v2) ? 0 : (v1 < v2) ? -1 : 1;
};
expect(orderBy(items, expr, reverse, comparator)).toEqual(sorted);
});
});
describe('(object as `value`)', function() {
it('should use the return value of `valueOf()` (if primitive)', function() {
var o1 = {k: 1, valueOf: function() { return 2; }};
var o2 = {k: 2, valueOf: function() { return 1; }};
var items = [o1, o2];
var expr = null;
var sorted = [o2, o1];
expect(orderBy(items, expr)).toEqual(sorted);
});
it('should use the return value of `toString()` (if primitive)', function() {
var o1 = {k: 1, toString: function() { return 2; }};
var o2 = {k: 2, toString: function() { return 1; }};
var items = [o1, o2];
var expr = null;
var sorted = [o2, o1];
expect(orderBy(items, expr)).toEqual(sorted);
});
it('should ignore the `toString()` inherited from `Object`', function() {
/* globals toString: true */
// The global `toString` variable (in 'src/Angular.js')
// has already captured `Object.prototype.toString`
var originalToString = toString;
toString = jasmine.createSpy('toString').and.callFake(originalToString);
var o1 = Object.create({toString: toString});
var o2 = Object.create({toString: toString});
var items = [o1, o2];
var expr = null;
orderBy(items, expr);
expect(o1.toString).not.toHaveBeenCalled();
expect(o2.toString).not.toHaveBeenCalled();
toString = originalToString;
});
it('should use the return value of `valueOf()` for subsequent steps (if non-primitive)',
function() {
var o1 = {k: 1, valueOf: function() { return o3; }};
var o2 = {k: 2, valueOf: function() { return o4; }};
var o3 = {k: 3, toString: function() { return 4; }};
var o4 = {k: 4, toString: function() { return 3; }};
var items = [o1, o2];
var expr = null;
var sorted = [o2, o1];
expect(orderBy(items, expr)).toEqual(sorted);
}
);
it('should use the return value of `toString()` for subsequent steps (if non-primitive)',
function() {
var o1 = {k: 1, toString: function() { return o3; }};
var o2 = {k: 2, toString: function() { return o4; }};
var o3 = {k: 3};
var o4 = {k: 4};
var items = [o1, o2];
var expr = null;
var reverse = null;
var comparator = jasmine.createSpy('comparator').and.returnValue(-1);
orderBy(items, expr, reverse, comparator);
var args = comparator.calls.argsFor(0);
expect(args).toContain(jasmine.objectContaining({value: o3, type: 'object'}));
expect(args).toContain(jasmine.objectContaining({value: o4, type: 'object'}));
}
);
it('should use the object itself as `value` if no conversion took place', function() {
var o1 = {k: 1};
var o2 = {k: 2};
var items = [o1, o2];
var expr = null;
var reverse = null;
var comparator = jasmine.createSpy('comparator').and.returnValue(-1);
orderBy(items, expr, reverse, comparator);
var args = comparator.calls.argsFor(0);
expect(args).toContain(jasmine.objectContaining({value: o1, type: 'object'}));
expect(args).toContain(jasmine.objectContaining({value: o2, type: 'object'}));
});
});
});
+12
View File
@@ -2537,6 +2537,18 @@ describe('$location', function() {
it('should throw on url(urlString, stateObject)', function() {
expectThrowOnStateChange(locationUrl);
});
it('should not throw when base path is another domain', function() {
initService({html5Mode: true, hashPrefix: '!', supportHistory: true});
inject(
initBrowser({url: 'http://domain.com/base/', basePath: 'http://otherdomain.com/base/'}),
function($location) {
expect(function() {
$location.absUrl();
}).not.toThrow();
}
);
});
});
+9
View File
@@ -2138,6 +2138,15 @@ describe('parser', function() {
expect(scope.$eval("add(1,2)")).toEqual(3);
});
it('should allow filter chains as arguments', function() {
scope.concat = function(a, b) {
return a + b;
};
scope.begin = 1;
scope.limit = 2;
expect(scope.$eval("concat('abcd'|limitTo:limit:begin,'abcd'|limitTo:2:1|uppercase)")).toEqual("bcBC");
});
it('should evaluate function call from a return value', function() {
scope.getter = function() { return function() { return 33; }; };
expect(scope.$eval("getter()()")).toBe(33);
+117 -117
View File
@@ -56,9 +56,9 @@ describe("ngAnimate $animateCss", function() {
inject(function($animateCss, $sniffer, $rootElement, $document) {
var animator;
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
$sniffer.transitions = $sniffer.animations = false;
animator = $animateCss(element, {
@@ -76,9 +76,9 @@ describe("ngAnimate $animateCss", function() {
$animate.enabled(false);
var animator, element = jqLite('<div></div>');
var animator, element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
animator = $animateCss(element, {
duration: 10,
@@ -91,7 +91,7 @@ describe("ngAnimate $animateCss", function() {
it("should silently quit the animation and not throw when an element has no parent during preparation",
inject(function($animateCss, $rootScope, $document, $rootElement) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
expect(function() {
$animateCss(element, {
duration: 1000,
@@ -108,8 +108,8 @@ describe("ngAnimate $animateCss", function() {
it("should silently quit the animation and not throw when an element has no parent before starting",
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
var element = jqLite('<div></div>');
jqLite($document[0].body).append($rootElement);
var element = angular.element('<div></div>');
angular.element($document[0].body).append($rootElement);
$rootElement.append(element);
$animateCss(element, {
@@ -129,12 +129,12 @@ describe("ngAnimate $animateCss", function() {
it("should buffer all requests into a single requestAnimationFrame call",
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var count = 0;
var runners = [];
function makeRequest() {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var runner = $animateCss(element, { duration: 5, to: fakeStyle }).start();
runner.then(function() {
@@ -168,10 +168,10 @@ describe("ngAnimate $animateCss", function() {
});
});
inject(function($animateCss, $$rAF, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
function makeRequest() {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
$animateCss(element, { duration: 5, to: fakeStyle }).start();
}
@@ -188,9 +188,9 @@ describe("ngAnimate $animateCss", function() {
var animationDuration = 5;
var element, animator;
beforeEach(inject(function($animateCss, $rootElement, $document) {
element = jqLite('<div></div>');
element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
animator = $animateCss(element, {
event: 'enter',
@@ -374,9 +374,9 @@ describe("ngAnimate $animateCss", function() {
}
beforeEach(inject(function($rootElement, $document) {
element = jqLite('<div></div>');
element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
options = { event: 'enter', structural: true };
}));
@@ -648,7 +648,7 @@ describe("ngAnimate $animateCss", function() {
it("should apply a stagger based when an active ng-EVENT-stagger class with a transition-delay is detected",
inject(function($animateCss, $document, $rootElement, $timeout) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addRule('.ng-enter', 'transition:2s linear all');
@@ -658,7 +658,7 @@ describe("ngAnimate $animateCss", function() {
var elm;
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
elements.push(elm);
$rootElement.append(elm);
@@ -689,7 +689,7 @@ describe("ngAnimate $animateCss", function() {
it("should apply a stagger based when for all provided addClass/removeClass CSS classes",
inject(function($animateCss, $document, $rootElement, $timeout) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.red-add-stagger,' +
'.blue-remove-stagger,' +
@@ -704,7 +704,7 @@ describe("ngAnimate $animateCss", function() {
var elm;
for (i = 0; i < 5; i++) {
elm = jqLite('<div class="blue"></div>');
elm = angular.element('<div class="blue"></div>');
elements.push(elm);
$rootElement.append(elm);
@@ -759,7 +759,7 @@ describe("ngAnimate $animateCss", function() {
it("should block the transition animation between start and animate when staggered",
inject(function($animateCss, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addRule('.ng-enter', 'transition:2s linear all;');
@@ -769,7 +769,7 @@ describe("ngAnimate $animateCss", function() {
var elms = [];
for (i = 0; i < 5; i++) {
element = jqLite('<div class="transition-animation"></div>');
element = angular.element('<div class="transition-animation"></div>');
$rootElement.append(element);
$animateCss(element, { event: 'enter', structural: true }).start();
@@ -790,14 +790,14 @@ describe("ngAnimate $animateCss", function() {
it("should block (pause) the keyframe animation between start and animate when staggered",
inject(function($animateCss, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'animation-delay:0.2s');
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:my_animation 2s;');
var i, element, elements = [];
for (i = 0; i < 5; i++) {
element = jqLite('<div class="transition-animation"></div>');
element = angular.element('<div class="transition-animation"></div>');
$rootElement.append(element);
$animateCss(element, { event: 'enter', structural: true }).start();
@@ -819,12 +819,12 @@ describe("ngAnimate $animateCss", function() {
it("should not apply a stagger if the transition delay value is inherited from a earlier CSS class",
inject(function($animateCss, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
for (var i = 0; i < 5; i++) {
var element = jqLite('<div class="transition-animation"></div>');
var element = angular.element('<div class="transition-animation"></div>');
$rootElement.append(element);
$animateCss(element, { event: 'enter', structural: true }).start();
@@ -838,7 +838,7 @@ describe("ngAnimate $animateCss", function() {
it("should apply a stagger only if the transition duration value is zero when inherited from a earlier CSS class",
inject(function($animateCss, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
ss.addRule('.transition-animation.ng-enter-stagger',
@@ -846,7 +846,7 @@ describe("ngAnimate $animateCss", function() {
var element, i, elms = [];
for (i = 0; i < 5; i++) {
element = jqLite('<div class="transition-animation"></div>');
element = angular.element('<div class="transition-animation"></div>');
$rootElement.append(element);
elms.push(element);
@@ -864,13 +864,13 @@ describe("ngAnimate $animateCss", function() {
it("should ignore animation staggers if only transition animations were detected",
inject(function($animateCss, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', prefix + 'animation-delay:0.2s');
ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
for (var i = 0; i < 5; i++) {
var element = jqLite('<div class="transition-animation"></div>');
var element = angular.element('<div class="transition-animation"></div>');
$rootElement.append(element);
$animateCss(element, { event: 'enter', structural: true }).start();
@@ -884,13 +884,13 @@ describe("ngAnimate $animateCss", function() {
it("should ignore transition staggers if only keyframe animations were detected",
inject(function($animateCss, $document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
ss.addPossiblyPrefixedRule('.transition-animation', 'animation: 2s 5s my_animation;');
for (var i = 0; i < 5; i++) {
var elm = jqLite('<div class="transition-animation"></div>');
var elm = angular.element('<div class="transition-animation"></div>');
$rootElement.append(elm);
var animator = $animateCss(elm, { event: 'enter', structural: true }).start();
@@ -904,7 +904,7 @@ describe("ngAnimate $animateCss", function() {
it("should start on the highest stagger value if both transition and keyframe staggers are used together",
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay: 0.5s; ' +
'animation-delay: 1s');
@@ -914,7 +914,7 @@ describe("ngAnimate $animateCss", function() {
var i, elm, elements = [];
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
elements.push(elm);
$rootElement.append(elm);
@@ -942,14 +942,14 @@ describe("ngAnimate $animateCss", function() {
it("should apply the closing timeout ontop of the stagger timeout",
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addRule('.ng-enter', 'transition:10s linear all;');
var elm, i, elms = [];
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
elms.push(elm);
$rootElement.append(elm);
@@ -969,14 +969,14 @@ describe("ngAnimate $animateCss", function() {
it("should apply the closing timeout ontop of the stagger timeout with an added delay",
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:50s;');
var elm, i, elms = [];
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
elms.push(elm);
$rootElement.append(elm);
@@ -996,12 +996,12 @@ describe("ngAnimate $animateCss", function() {
it("should issue a stagger if a stagger value is provided in the options",
inject(function($animateCss, $document, $rootElement, $timeout) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.ng-enter', 'transition:2s linear all');
var elm, i, elements = [];
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
elements.push(elm);
$rootElement.append(elm);
@@ -1035,9 +1035,9 @@ describe("ngAnimate $animateCss", function() {
it("should only add/remove classes once the stagger timeout has passed",
inject(function($animateCss, $document, $rootElement, $timeout) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var element = jqLite('<div class="green"></div>');
var element = angular.element('<div class="green"></div>');
$rootElement.append(element);
$animateCss(element, {
@@ -1064,9 +1064,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
animator.start();
@@ -1087,9 +1087,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:30s;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
animator.start();
@@ -1110,9 +1110,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
@@ -1135,9 +1135,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
@@ -1183,9 +1183,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.elm.blue', 'transition:2s linear all; background:blue;');
ss.addRule('.elm.green', 'background:green;');
var element = jqLite('<div class="elm"></div>');
var element = angular.element('<div class="elm"></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
// timeout will be at 1500s
animate(element, 'red', doneSpy);
@@ -1228,9 +1228,9 @@ describe("ngAnimate $animateCss", function() {
it("should not throw an error any pending timeout requests resolve after the element has already been removed",
inject(function($animateCss, $document, $rootElement, $timeout, $animate) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.red', 'transition:1s linear all;');
@@ -1246,9 +1246,9 @@ describe("ngAnimate $animateCss", function() {
it("should consider a positive options.delay value for the closing timeout",
inject(function($animateCss, $rootElement, $timeout, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var options = {
delay: 3,
@@ -1279,9 +1279,9 @@ describe("ngAnimate $animateCss", function() {
it("should ignore a boolean options.delay value for the closing timeout",
inject(function($animateCss, $rootElement, $timeout, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var options = {
delay: true,
@@ -1315,9 +1315,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.ng-enter', 'transition:10s linear all;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, { event: 'enter', structural: true });
animator.start();
@@ -1343,7 +1343,7 @@ describe("ngAnimate $animateCss", function() {
beforeEach(module(function($provide) {
count = {};
$provide.value('$window', extend({}, window, {
document: jqLite(window.document),
document: angular.element(window.document),
getComputedStyle: function(node) {
var key = node.className.indexOf('stagger') >= 0
? 'stagger' : 'normal';
@@ -1354,7 +1354,7 @@ describe("ngAnimate $animateCss", function() {
}));
return function($document, $rootElement) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
};
}));
@@ -1363,7 +1363,7 @@ describe("ngAnimate $animateCss", function() {
var i, elm, animator;
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
$rootElement.append(elm);
animator = $animateCss(elm, { event: 'enter', structural: true });
var runner = animator.start();
@@ -1372,7 +1372,7 @@ describe("ngAnimate $animateCss", function() {
expect(count.normal).toBe(1);
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
$rootElement.append(elm);
animator = $animateCss(elm, { event: 'enter', structural: true });
animator.start();
@@ -1384,7 +1384,7 @@ describe("ngAnimate $animateCss", function() {
expect(count.normal).toBe(2);
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
$rootElement.append(elm);
animator = $animateCss(elm, { event: 'enter', structural: true });
animator.start();
@@ -1396,7 +1396,7 @@ describe("ngAnimate $animateCss", function() {
it("should cache frequent calls to getComputedStyle for stagger animations before the next animation frame kicks in",
inject(function($animateCss, $document, $rootElement, $$rAF) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var animator = $animateCss(element, { event: 'enter', structural: true });
animator.start();
@@ -1406,7 +1406,7 @@ describe("ngAnimate $animateCss", function() {
var i, elm;
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
$rootElement.append(elm);
animator = $animateCss(elm, { event: 'enter', structural: true });
animator.start();
@@ -1415,7 +1415,7 @@ describe("ngAnimate $animateCss", function() {
expect(count.stagger).toBe(1);
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
$rootElement.append(elm);
animator = $animateCss(elm, { event: 'enter', structural: true });
animator.start();
@@ -1425,7 +1425,7 @@ describe("ngAnimate $animateCss", function() {
$$rAF.flush();
for (i = 0; i < 5; i++) {
elm = jqLite('<div></div>');
elm = angular.element('<div></div>');
$rootElement.append(elm);
animator = $animateCss(elm, { event: 'enter', structural: true });
animator.start();
@@ -1454,9 +1454,9 @@ describe("ngAnimate $animateCss", function() {
}
beforeEach(inject(function($rootElement, $document) {
element = jqLite('<div></div>');
element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
elementOnSpy = spyOn(element, 'on').and.callThrough();
elementOffSpy = spyOn(element, 'off').and.callThrough();
@@ -1585,9 +1585,9 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
}
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
startAnimation(element, 0.5, 'red');
expect(element.attr('style')).toContain('transition');
@@ -1608,13 +1608,13 @@ describe("ngAnimate $animateCss", function() {
it("should clear cache if no animation so follow-up animation on the same element will not be from cache",
inject(function($animateCss, $rootElement, $document, $$rAF) {
var element = jqLite('<div class="rclass"></div>');
var element = angular.element('<div class="rclass"></div>');
var options = {
event: 'enter',
structural: true
};
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, options);
expect(animator.$$willAnimate).toBeFalsy();
@@ -1628,9 +1628,9 @@ describe("ngAnimate $animateCss", function() {
it('should apply a custom temporary class when a non-structural animation is used',
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
$animateCss(element, {
event: 'super',
@@ -1647,9 +1647,9 @@ describe("ngAnimate $animateCss", function() {
they('should decorate the element with the ng-$prop CSS class',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
$animateCss(element, {
event: event,
@@ -1664,9 +1664,9 @@ describe("ngAnimate $animateCss", function() {
they('should decorate the element with the ng-$prop-active CSS class',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, {
event: event,
@@ -1685,9 +1685,9 @@ describe("ngAnimate $animateCss", function() {
they('should remove the ng-$prop and ng-$prop-active CSS classes from the element once the animation is done',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var animator = $animateCss(element, {
event: event,
@@ -1711,7 +1711,7 @@ describe("ngAnimate $animateCss", function() {
they('should allow additional CSS classes to be added and removed alongside the $prop animation',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement) {
var element = jqLite('<div class="green"></div>');
var element = angular.element('<div class="green"></div>');
$rootElement.append(element);
var animator = $animateCss(element, {
event: event,
@@ -1742,9 +1742,9 @@ describe("ngAnimate $animateCss", function() {
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
@@ -1772,9 +1772,9 @@ describe("ngAnimate $animateCss", function() {
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $document, $window) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
@@ -1813,9 +1813,9 @@ describe("ngAnimate $animateCss", function() {
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
ss.addRule('.cool-animation', 'transition:1.5s linear all;');
element.addClass('cool-animation');
@@ -1848,9 +1848,9 @@ describe("ngAnimate $animateCss", function() {
it('should allow multiple events to be animated at the same time',
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
$animateCss(element, {
event: ['enter', 'leave', 'move'],
@@ -1882,10 +1882,10 @@ describe("ngAnimate $animateCss", function() {
it('should not break when running anchored animations without duration',
inject(function($animate, $document, $rootElement) {
var element1 = jqLite('<div class="item" ng-animate-ref="test">Item 1</div>');
var element2 = jqLite('<div class="item" ng-animate-ref="test">Item 2</div>');
var element1 = angular.element('<div class="item" ng-animate-ref="test">Item 1</div>');
var element2 = angular.element('<div class="item" ng-animate-ref="test">Item 2</div>');
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
$rootElement.append(element1);
expect($rootElement.text()).toBe('Item 1');
@@ -1903,7 +1903,7 @@ describe("ngAnimate $animateCss", function() {
they('should decorate the element with the class-$prop CSS class',
['add', 'remove'], function(event) {
inject(function($animateCss, $rootElement) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {};
@@ -1918,7 +1918,7 @@ describe("ngAnimate $animateCss", function() {
they('should decorate the element with the class-$prop-active CSS class',
['add', 'remove'], function(event) {
inject(function($animateCss, $rootElement) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {};
@@ -1937,9 +1937,9 @@ describe("ngAnimate $animateCss", function() {
they('should remove the class-$prop-add and class-$prop-active CSS classes from the element once the animation is done',
['enter', 'leave', 'move'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var options = {};
options.event = event;
@@ -1963,7 +1963,7 @@ describe("ngAnimate $animateCss", function() {
['add', 'remove'], function(event) {
inject(function($animateCss, $rootElement, $document) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
if (event == 'add') {
ss.addRule('.natural-class', 'transition:1s linear all;');
@@ -1976,7 +1976,7 @@ describe("ngAnimate $animateCss", function() {
}
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var options = {};
options[event + 'Class'] = 'natural-class';
@@ -2001,9 +2001,9 @@ describe("ngAnimate $animateCss", function() {
ss.addRule('.blue.ng-' + event, 'transition:2s linear all;');
var element = jqLite('<div class="red"></div>');
var element = angular.element('<div class="red"></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
var runner = $animateCss(element, {
addClass: 'blue',
@@ -2039,9 +2039,9 @@ describe("ngAnimate $animateCss", function() {
var element;
beforeEach(module(function() {
return function($rootElement, $document) {
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
element = jqLite('<div></div>');
element = angular.element('<div></div>');
$rootElement.append(element);
};
}));
@@ -2140,7 +2140,7 @@ describe("ngAnimate $animateCss", function() {
describe("[duration]", function() {
it("should be applied for a transition directly", inject(function($animateCss, $rootElement) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {
@@ -2268,7 +2268,7 @@ describe("ngAnimate $animateCss", function() {
describe("[delay]", function() {
it("should be applied for a transition directly", inject(function($animateCss, $rootElement) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {
@@ -2290,7 +2290,7 @@ describe("ngAnimate $animateCss", function() {
it("should return false for the animator if a delay is provided but not a duration",
inject(function($animateCss, $rootElement) {
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {
@@ -2312,7 +2312,7 @@ describe("ngAnimate $animateCss", function() {
'-webkit-transition-delay:10s;' +
'transition-delay:10s;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {
@@ -2337,7 +2337,7 @@ describe("ngAnimate $animateCss", function() {
'-webkit-transition-delay:10s;' +
'transition-delay:10s;');
var element = jqLite('<div></div>');
var element = angular.element('<div></div>');
$rootElement.append(element);
var options = {
@@ -3043,9 +3043,9 @@ describe("ngAnimate $animateCss", function() {
var element;
beforeEach(inject(function($document, $rootElement) {
element = jqLite('<div></div>');
element = angular.element('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
}));
it("should apply easing to a transition animation if it exists", inject(function($animateCss) {
@@ -3184,7 +3184,7 @@ describe("ngAnimate $animateCss", function() {
'<circle cx="15" cy="5" r="100" fill="orange" />' +
'</svg>')($rootScope);
jqLite($document[0].body).append($rootElement);
angular.element($document[0].body).append($rootElement);
$rootElement.append(element);
$animateCss(element, {
@@ -3195,17 +3195,17 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
expect(jqLiteHasClass(element[0], 'ng-enter')).toBe(true);
expect(jqLiteHasClass(element[0], 'ng-enter-active')).toBe(true);
expect(element).toHaveClass('ng-enter');
expect(element).toHaveClass('ng-enter-active');
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 10 });
expect(jqLiteHasClass(element[0], 'ng-enter')).toBe(false);
expect(jqLiteHasClass(element[0], 'ng-enter-active')).toBe(false);
expect(element).not.toHaveClass('ng-enter');
expect(element).not.toHaveClass('ng-enter-active');
}));
it('should properly remove classes from SVG elements', inject(function($animateCss) {
var element = jqLite('<svg width="500" height="500">' +
var element = angular.element('<svg width="500" height="500">' +
'<rect class="class-of-doom"></rect>' +
'</svg>');
var child = element.find('rect');
+51 -1
View File
@@ -12,6 +12,10 @@ describe('ngMessages', function() {
return str.replace(/\s+/g,'');
}
function trim(value) {
return isString(value) ? value.trim() : value;
}
var element;
afterEach(function() {
dealoc(element);
@@ -605,6 +609,34 @@ describe('ngMessages', function() {
});
});
it('should clean-up the ngMessage scope when a message is removed',
inject(function($compile, $rootScope) {
var html =
'<div ng-messages="items">' +
'<div ng-message="a">{{forA}}</div>' +
'</div>';
element = $compile(html)($rootScope);
$rootScope.$apply(function() {
$rootScope.forA = 'A';
$rootScope.items = {a: true};
});
expect(element.text()).toBe('A');
var watchers = $rootScope.$countWatchers();
$rootScope.$apply('items.a = false');
expect(element.text()).toBe('');
// We don't know exactly how many watchers are on the scope, only that there should be
// one less now
expect($rootScope.$countWatchers()).toBe(watchers - 1);
})
);
describe('when including templates', function() {
they('should work with a dynamic collection model which is managed by ngRepeat',
{'<div ng-messages-include="...">': '<div ng-messages="item">' +
@@ -811,7 +843,6 @@ describe('ngMessages', function() {
expect(trim(element.text())).toEqual("C");
}));
it('should properly detect a previous message, even if it was registered later',
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('include.html', '<div ng-message="a">A</div>');
@@ -861,6 +892,25 @@ describe('ngMessages', function() {
$httpBackend.flush();
}).not.toThrow();
}));
it('should not throw if the template is empty',
inject(function($compile, $rootScope, $templateCache) {
var html =
'<div ng-messages="items">' +
'<div ng-messages-include="messages1.html"></div>' +
'<div ng-messages-include="messages2.html"></div>' +
'</div>';
$templateCache.put('messages1.html', '');
$templateCache.put('messages2.html', ' ');
element = $compile(html)($rootScope);
$rootScope.$digest();
expect(element.text()).toBe('');
expect(element.children().length).toBe(0);
expect(element.contents().length).toBe(2);
})
);
});
describe('when multiple', function() {
+49 -3
View File
@@ -1893,6 +1893,52 @@ describe('ngMock', function() {
expect(called).toBe(true);
});
});
it('should support assigning bindings when a value is returned from the constructor',
function() {
var called = false;
var data = [
{ name: 'derp1', id: 0 },
{ name: 'testname', id: 1 },
{ name: 'flurp', id: 2 }
];
module(function($controllerProvider) {
$controllerProvider.register('testCtrl', function() {
called = true;
expect(this.data).toBe(data);
return {};
});
});
inject(function($controller, $rootScope) {
var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data });
expect(ctrl.data).toBe(data);
expect(called).toBe(true);
});
}
);
if (/chrome/.test(navigator.userAgent)) {
it('should support assigning bindings to class-based controller', function() {
var called = false;
var data = [
{ name: 'derp1', id: 0 },
{ name: 'testname', id: 1 },
{ name: 'flurp', id: 2 }
];
module(function($controllerProvider) {
//jshint evil: true
var TestCtrl = eval('(class { constructor() { called = true; } })');
//jshint evil: false
$controllerProvider.register('testCtrl', TestCtrl);
});
inject(function($controller, $rootScope) {
var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data });
expect(ctrl.data).toBe(data);
expect(called).toBe(true);
});
});
}
});
@@ -2124,12 +2170,12 @@ describe('ngMockE2E', function() {
var hb, realHttpBackend, callback;
beforeEach(function() {
module(function($provide) {
callback = jasmine.createSpy('callback');
callback = jasmine.createSpy('callback');
angular.module('ng').config(function($provide) {
realHttpBackend = jasmine.createSpy('real $httpBackend');
$provide.value('$httpBackend', realHttpBackend);
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
});
module('ngMockE2E');
inject(function($injector) {
hb = $injector.get('$httpBackend');
});
+81
View File
@@ -900,6 +900,87 @@ describe('$route', function() {
});
it('should not get affected by modifying the route definition object after route registration',
function() {
module(function($routeProvider) {
var rdo = {};
rdo.templateUrl = 'foo.html';
$routeProvider.when('/foo', rdo);
rdo.templateUrl = 'bar.html';
$routeProvider.when('/bar', rdo);
});
inject(function($location, $rootScope, $route) {
$location.path('/bar');
$rootScope.$digest();
expect($location.path()).toBe('/bar');
expect($route.current.templateUrl).toBe('bar.html');
$location.path('/foo');
$rootScope.$digest();
expect($location.path()).toBe('/foo');
expect($route.current.templateUrl).toBe('foo.html');
});
}
);
it('should use the property values of the passed in route definition object directly',
function() {
var $routeProvider;
module(function(_$routeProvider_) {
$routeProvider = _$routeProvider_;
});
inject(function($location, $rootScope, $route, $sce) {
var sceWrappedUrl = $sce.trustAsResourceUrl('foo.html');
$routeProvider.when('/foo', {templateUrl: sceWrappedUrl});
$location.path('/foo');
$rootScope.$digest();
expect($location.path()).toBe('/foo');
expect($route.current.templateUrl).toBe(sceWrappedUrl);
});
}
);
it('should support custom `$sce` implementations', function() {
function MySafeResourceUrl(val) {
var self = this;
this._val = val;
this.getVal = function() {
return (this !== self) ? null : this._val;
};
}
var $routeProvider;
module(function($provide, _$routeProvider_) {
$routeProvider = _$routeProvider_;
$provide.decorator('$sce', function($delegate) {
$delegate.trustAsResourceUrl = function(url) { return new MySafeResourceUrl(url); };
$delegate.getTrustedResourceUrl = function(v) { return v.getVal(); };
$delegate.valueOf = function(v) { return v.getVal(); };
return $delegate;
});
});
inject(function($location, $rootScope, $route, $sce) {
$routeProvider.when('/foo', {templateUrl: $sce.trustAsResourceUrl('foo.html')});
$location.path('/foo');
$rootScope.$digest();
expect($location.path()).toBe('/foo');
expect($sce.valueOf($route.current.templateUrl)).toBe('foo.html');
});
});
describe('redirection', function() {
it('should support redirection via redirectTo property by updating $location', function() {
module(function($routeProvider) {
+7
View File
@@ -119,6 +119,13 @@ describe('linky', function() {
});
it('should call the attribute function for all links in the input', function() {
var attributeFn = jasmine.createSpy('attributeFn').and.returnValue({});
linky("http://example.com and http://google.com", "_self", attributeFn);
expect(attributeFn.calls.allArgs()).toEqual([['http://example.com'], ['http://google.com']]);
});
it('should strip unsafe attributes', function() {
expect(linky("http://example.com", "_self", {"class": "blue", "onclick": "alert('Hi')"})).
toBeOneOf('<a class="blue" target="_self" href="http://example.com">http://example.com</a>',