Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ad109e745 | |||
| c5cba6e9c1 | |||
| 924d3c6bfe | |||
| ee29819dba | |||
| 41f03e4b02 | |||
| facfec9841 | |||
| e2d1969d68 | |||
| f380cd220c | |||
| 2db0aabee3 | |||
| ee42cfea04 | |||
| fbbf6ac161 | |||
| a1c5f2b4f0 | |||
| 2d6a0a1dc1 | |||
| 0c4f9fa574 | |||
| 7ad66527ba | |||
| f2e7f875e2 | |||
| 40a537c25f | |||
| cb192293f4 | |||
| 8c3a42cd68 | |||
| 96e7897fef | |||
| 2dc34a9699 | |||
| 10ac594809 | |||
| d21dff21ed | |||
| 55d9db56a6 | |||
| 579aa59324 | |||
| b9e5eaf669 | |||
| 228281eecc | |||
| acb066e84a | |||
| ed1243ffc7 | |||
| 3aa5752894 | |||
| 8bfeddb5d6 | |||
| 015111fd79 | |||
| cc0fbe37d4 | |||
| 1b640f9665 | |||
| 32806caf13 | |||
| 9c113aa4af | |||
| 9a616eade4 | |||
| 7daf4e0125 | |||
| 1191edba4e | |||
| c8c9bbc412 | |||
| 429938da1f | |||
| a75537d461 | |||
| 5ced914cc8 | |||
| a631a759d2 | |||
| f7cf846045 | |||
| 96c61fe756 | |||
| 915a891ad4 | |||
| 8df47db72f | |||
| 013b522c9e | |||
| 7c6be43e83 | |||
| e93710fe0e | |||
| 5481e2cfcd | |||
| c6b57f1ec6 | |||
| 240e0d5c8e |
+7
-5
@@ -8,14 +8,16 @@ branches:
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- JOB=unit
|
||||
- JOB=e2e TEST_TARGET=jqlite
|
||||
- JOB=e2e TEST_TARGET=jquery
|
||||
- JOB=unit BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
global:
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
- BROWSER_STACK_USERNAME=VojtaJina
|
||||
- BROWSER_STACK_ACCESS_KEY=QCQJ1ZpWXpBkSwEdD8ev
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
install:
|
||||
# - npm config set registry http://23.251.144.68
|
||||
@@ -28,7 +30,7 @@ install:
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./lib/sauce/sauce_connect_setup.sh
|
||||
- ./scripts/travis/start_browser_provider.sh
|
||||
- npm install -g grunt-cli
|
||||
- grunt package
|
||||
- ./scripts/travis/wait_for_browser_provider.sh
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
<a name="1.3.5"></a>
|
||||
# 1.3.5 cybernetic-mercantilism (2014-12-01)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$templateRequest:** propagate rejection reason when ignoreRequestError flag is set
|
||||
([f6458826](https://github.com/angular/angular.js/commit/f6458826ac974914597a10b0ffdeee3c5d2c62ef),
|
||||
[#10266](https://github.com/angular/angular.js/issues/10266))
|
||||
- **$httpBackend:** allow canceling request with falsy timeoutId
|
||||
([719d5c5f](https://github.com/angular/angular.js/commit/719d5c5fa59ae1617691a0dca02da861fcf5f933),
|
||||
[#10177](https://github.com/angular/angular.js/issues/10177))
|
||||
- **linky:** encode all double quotes when serializing email addresses
|
||||
([2ec8d1ff](https://github.com/angular/angular.js/commit/2ec8d1ffc04e06a39cb1b74a8d675da38e0a1c6b),
|
||||
[#10090](https://github.com/angular/angular.js/issues/10090))
|
||||
- **ngMock:**
|
||||
- annotate $RootScopeDecorator
|
||||
([9a83f9d2](https://github.com/angular/angular.js/commit/9a83f9d2fabe0a259c283b7f7cd935e4b36e2b5d),
|
||||
[#10273](https://github.com/angular/angular.js/issues/10273), [#10275](https://github.com/angular/angular.js/issues/10275), [#10277](https://github.com/angular/angular.js/issues/10277))
|
||||
- respond did not always take a statusText argument
|
||||
([08cd5c19](https://github.com/angular/angular.js/commit/08cd5c19c7a5116e7e74691391fc5e28bfae4521),
|
||||
[#8270](https://github.com/angular/angular.js/issues/8270))
|
||||
- **select:**
|
||||
- use strict compare when removing option from ctrl
|
||||
([9fa73cb4](https://github.com/angular/angular.js/commit/9fa73cb4e7190b4d00b65f2f8f9f7d37607308ba),
|
||||
[#9714](https://github.com/angular/angular.js/issues/9714), [#10115](https://github.com/angular/angular.js/issues/10115), [#10203](https://github.com/angular/angular.js/issues/10203))
|
||||
- fix several issues when moving options between groups
|
||||
([30694c80](https://github.com/angular/angular.js/commit/30694c802763d46d6787f7298f47dfef53ed4229),
|
||||
[#10166](https://github.com/angular/angular.js/issues/10166))
|
||||
|
||||
|
||||
<a name="1.3.4"></a>
|
||||
# 1.3.4 highfalutin-petroglyph (2014-11-24)
|
||||
|
||||
@@ -2767,6 +2798,16 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
|
||||
|
||||
The isolated scope of a component directive no longer leaks into the template
|
||||
that contains the instance of the directive. This means that you can no longer
|
||||
access the isolated scope from attributes on the element where the isolated
|
||||
directive is defined.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push -f
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
@@ -30,14 +30,6 @@ module.exports = function(grunt) {
|
||||
benchmarksPath: 'benchmarks'
|
||||
}
|
||||
},
|
||||
parallel: {
|
||||
travis: {
|
||||
tasks: [
|
||||
util.parallelTask(['test:unit', 'test:promises-aplus', 'tests:docs'], {stream: true}),
|
||||
util.parallelTask(['test:e2e'])
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
connect: {
|
||||
devserver: {
|
||||
|
||||
@@ -202,6 +202,7 @@ var generate = function(version, file) {
|
||||
|
||||
// publish for testing
|
||||
exports.parseRawCommit = parseRawCommit;
|
||||
exports.printSection = printSection;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
|
||||
+62
-1
@@ -1,4 +1,4 @@
|
||||
/* global describe: false, it: false, expect: false */
|
||||
/* global describe: false, beforeEach: false, afterEach: false, it: false, expect: false */
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -44,4 +44,65 @@ describe('changelog.js', function() {
|
||||
expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('printSection', function() {
|
||||
var output;
|
||||
var streamMock = {
|
||||
write: function(str) {
|
||||
output += str;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
output = '';
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there is 1 item per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [{subject: 'breaking change 1'}],
|
||||
module2: [{subject: 'breaking change 2'}]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:** breaking change 1\n' +
|
||||
'- **module2:** breaking change 2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there are multiple items per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [
|
||||
{subject: 'breaking change 1.1'},
|
||||
{subject: 'breaking change 1.2'}
|
||||
],
|
||||
module2: [
|
||||
{subject: 'breaking change 2.1'},
|
||||
{subject: 'breaking change 2.2'}
|
||||
]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:**\n' +
|
||||
' - breaking change 1.1\n' +
|
||||
' - breaking change 1.2\n' +
|
||||
'- **module2:**\n' +
|
||||
' - breaking change 2.1\n' +
|
||||
' - breaking change 2.2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $location:ihshprfx
|
||||
@fullName Missing Hash Prefix
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$location $location} service is configured to use a hash prefix but this prefix was not present in a url that the `$location` service was asked to parse.
|
||||
|
||||
For example if you configure `$location` service with prefix `'!'`:
|
||||
```
|
||||
myApp.config(function($locationProvider) {
|
||||
$locationProvider.hashPrefix('!');
|
||||
});
|
||||
```
|
||||
|
||||
If you enter the app at url `http://myapp.com/#/myView` this error will be thrown.
|
||||
|
||||
The correct url for this configuration is `http://myapp.com/#!/myView` (note the `'!'` after `'#'` symbol).
|
||||
@@ -31,12 +31,12 @@ added it as a dependency, you can test a few things:
|
||||
##Supported directives
|
||||
Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* <a href="#ngmodel">ngModel</a>
|
||||
* <a href="#ngdisabled">ngDisabled</a>
|
||||
* <a href="#ngshow">ngShow</a>
|
||||
* <a href="#nghide">ngHide</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngClick</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
|
||||
* {@link guide/accessibility#ngmodel ngModel}
|
||||
* {@link guide/accessibility#ngdisabled ngDisabled}
|
||||
* {@link guide/accessibility#ngshow ngShow}
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
@@ -206,9 +206,9 @@ shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
|
||||
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
|
||||
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2 id="ngclick-and-ngdblclick">ngClick and ngDblclick</h2>
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
|
||||
@@ -326,7 +326,7 @@ describe('state', function() {
|
||||
expect(childScope.timeOfDay).toBe('morning');
|
||||
expect(childScope.name).toBe('Mattie');
|
||||
expect(grandChildScope.timeOfDay).toBe('evening');
|
||||
expect(grandChildScope.name).toBe('Gingerbreak Baby');
|
||||
expect(grandChildScope.name).toBe('Gingerbread Baby');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -13,21 +13,26 @@ Angular sets these CSS classes. It is up to your application to provide useful s
|
||||
is defined. (see {@link guide/scope scope} guide for more information about scopes)
|
||||
|
||||
* `ng-isolate-scope`
|
||||
- **Usage:** angular applies this class to any element for which a new
|
||||
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
|
||||
- **Usage:** angular applies this class to any element for which a new
|
||||
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
|
||||
|
||||
* `ng-binding`
|
||||
- **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or
|
||||
`{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide)
|
||||
|
||||
* `ng-invalid`, `ng-valid`
|
||||
- **Usage:** angular applies this class to an input widget element if that element's input does
|
||||
- **Usage:** angular applies this class to a form control widget element if that element's input does
|
||||
not pass validation. (see {@link ng.directive:input input} directive)
|
||||
|
||||
* `ng-pristine`, `ng-dirty`
|
||||
- **Usage:** angular {@link ng.directive:input input} directive applies `ng-pristine` class
|
||||
to a new input widget element which did not have user interaction. Once the user interacts with
|
||||
the input widget the class is changed to `ng-dirty`.
|
||||
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-pristine` class
|
||||
to a new form control widget which did not have user interaction. Once the user interacts with
|
||||
the form control, the class is changed to `ng-dirty`.
|
||||
|
||||
* `ng-touched`, `ng-untouched`
|
||||
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-untouched` class
|
||||
to a new form control widget which has not been blurred. Once the user blurs the form control,
|
||||
the class is changed to `ng-touched`.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
@@ -27,8 +27,8 @@ for other directives to augment its behavior.
|
||||
E-mail: <input type="email" ng-model="user.email" /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
<button ng-click="reset()">RESET</button>
|
||||
<button ng-click="update(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
@@ -77,29 +77,29 @@ To allow styling of form as well as controls, `ngModel` adds these CSS classes:
|
||||
|
||||
The following example uses the CSS to display validity of each form control.
|
||||
In the example both `user.name` and `user.email` are required, but are rendered
|
||||
with red background only when they are dirty. This ensures that the user is not distracted
|
||||
with an error until after interacting with the control, and failing to satisfy its validity.
|
||||
with red background only after the input is blurred (loses focus).
|
||||
This ensures that the user is not distracted with an error until after interacting with the control,
|
||||
and failing to satisfy its validity.
|
||||
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form novalidate class="css-form">
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" required /><br />
|
||||
Name: <input type="text" ng-model="user.name" required /><br />
|
||||
E-mail: <input type="email" ng-model="user.email" required /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
<button ng-click="reset()">RESET</button>
|
||||
<button ng-click="update(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style type="text/css">
|
||||
.css-form input.ng-invalid.ng-dirty {
|
||||
.css-form input.ng-invalid.ng-touched {
|
||||
background-color: #FA787E;
|
||||
}
|
||||
|
||||
.css-form input.ng-valid.ng-dirty {
|
||||
.css-form input.ng-valid.ng-touched {
|
||||
background-color: #78FA89;
|
||||
}
|
||||
</style>
|
||||
@@ -140,34 +140,45 @@ the view using the standard binding primitives.
|
||||
|
||||
This allows us to extend the above example with these features:
|
||||
|
||||
- RESET button is enabled only if form has some changes
|
||||
- SAVE button is enabled only if form has some changes and is valid
|
||||
- custom error messages for `user.email` and `user.agree`
|
||||
- Custom error message displayed after the user interacted with a control (i.e. when `$touched` is set)
|
||||
- Custom error message displayed upon submitting the form (`$submitted` is set), even if the user
|
||||
didn't interact with a control
|
||||
|
||||
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form name="form" class="css-form" novalidate>
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" name="uName" required /><br />
|
||||
<input type="text" ng-model="user.name" name="uName" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.uName.$touched">
|
||||
<div ng-show="form.uName.$error.required">Tell us your name.</div>
|
||||
</div>
|
||||
|
||||
E-mail:
|
||||
<input type="email" ng-model="user.email" name="uEmail" required/><br />
|
||||
<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
|
||||
<input type="email" ng-model="user.email" name="uEmail" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.uEmail.$touched">
|
||||
<span ng-show="form.uEmail.$error.required">Tell us your email.</span>
|
||||
<span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
|
||||
</div>
|
||||
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
Gender:
|
||||
<input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female
|
||||
<br />
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required="" />
|
||||
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required />
|
||||
I agree: <input ng-show="user.agree" type="text" ng-model="user.agreeSign"
|
||||
required /><br />
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
I agree:
|
||||
<input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.userAgree.$touched">
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
</div>
|
||||
|
||||
<button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button>
|
||||
<button ng-click="update(user)"
|
||||
ng-disabled="form.$invalid || isUnchanged(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset(form)" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</file>
|
||||
@@ -181,14 +192,14 @@ This allows us to extend the above example with these features:
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.reset = function(form) {
|
||||
if (form) {
|
||||
form.$setPristine();
|
||||
form.$setUntouched();
|
||||
}
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
|
||||
$scope.isUnchanged = function(user) {
|
||||
return angular.equals(user, $scope.master);
|
||||
};
|
||||
|
||||
$scope.reset();
|
||||
}]);
|
||||
</file>
|
||||
|
||||
+5
-184
@@ -6,7 +6,7 @@
|
||||
# Internet Explorer Compatibility
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** AngularJS 1.3 is dropping support for IE8. Read more about it on
|
||||
**Note:** AngularJS 1.3 has dropped support for IE8. Read more about it on
|
||||
[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html).
|
||||
AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time
|
||||
addressing issues specific to IE8 or earlier.
|
||||
@@ -14,7 +14,7 @@ addressing issues specific to IE8 or earlier.
|
||||
|
||||
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
|
||||
attributes and tags. Read this document if you are planning on deploying your Angular application
|
||||
on IE8 or earlier.
|
||||
on IE.
|
||||
|
||||
The project currently supports and will attempt to fix bugs for IE9 and above. The continuous
|
||||
integration server runs all the tests against IE9, IE10, and IE11. See
|
||||
@@ -25,186 +25,7 @@ We do not run tests on IE8 and below. A subset of the AngularJS functionality ma
|
||||
browsers, but it is up to you to test and decide whether it works for your particular app.
|
||||
|
||||
|
||||
## Short Version
|
||||
|
||||
To make your Angular application work on IE please make sure that:
|
||||
|
||||
1. You polyfill JSON.stringify for IE7 and below. You can use
|
||||
[JSON2](https://github.com/douglascrockford/JSON-js) or
|
||||
[JSON3](http://bestiejs.github.com/json3/) polyfills for this.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<!--[if lte IE 7]>
|
||||
<script src="/path/to/json2.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. add `id="ng-app"` to the root element in conjunction with `ng-app` attribute
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
||||
3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version
|
||||
`<div ng-view>` instead), or
|
||||
|
||||
4. if you **do use** custom element tags, then you must take these steps to make IE 8 and below happy:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
<script>
|
||||
document.createElement('ng-include');
|
||||
document.createElement('ng-pluralize');
|
||||
document.createElement('ng-view');
|
||||
|
||||
// Optionally these for CSS
|
||||
document.createElement('ng:include');
|
||||
document.createElement('ng:pluralize');
|
||||
document.createElement('ng:view');
|
||||
</script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
5. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
|
||||
|
||||
The **important** parts are:
|
||||
|
||||
* `xmlns:ng` - *namespace* - you need one namespace for each custom tag you are planning on
|
||||
using.
|
||||
|
||||
* `document.createElement(yourTagName)` - *creation of custom tag names* - Since this is an
|
||||
issue only for older version of IE you need to load it conditionally. For each tag which does
|
||||
not have namespace and which is not defined in HTML you need to pre-declare it to make IE
|
||||
happy.
|
||||
|
||||
|
||||
## Long Version
|
||||
|
||||
IE has issues with element tag names which are not standard HTML tag names. These fall into two
|
||||
categories, and each category has its own fix.
|
||||
|
||||
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
|
||||
have corresponding namespace declaration on `<html xmlns:my="ignored">`
|
||||
|
||||
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
|
||||
`document.createElement('my-tag')`
|
||||
|
||||
* If you are planning on styling the custom tag with CSS selectors, then it must be
|
||||
pre-created using `document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
|
||||
## The Good News
|
||||
|
||||
The good news is that these restrictions only apply to element tag names, and not to element
|
||||
attribute names. So this requires no special handling in IE: `<div my-tag your:tag></div>`.
|
||||
|
||||
|
||||
## What happens if I fail to do this?
|
||||
|
||||
Suppose you have HTML with unknown tag `mytag` (this could also be `my:tag` or `my-tag` with same
|
||||
result):
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<mytag>some text</mytag>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
It should parse into the following DOM:
|
||||
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
```
|
||||
|
||||
The expected behavior is that the `BODY` element has a child element `mytag`, which in turn has
|
||||
the text `some text`.
|
||||
|
||||
But this is not what IE does (if the above fixes are not included):
|
||||
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
+- /mytag
|
||||
```
|
||||
|
||||
In IE, the behavior is that the `BODY` element has three children:
|
||||
|
||||
1. A self closing `mytag`. Example of self closing tag is `<br/>`. The trailing `/` is optional,
|
||||
but the `<br>` tag is not allowed to have any children, and browsers consider `<br>some
|
||||
text</br>` as three siblings not a `<br>` with `some text` as child.
|
||||
|
||||
2. A text node with `some text`. This should have been a child of `mytag` above, not a sibling.
|
||||
|
||||
3. A corrupt self closing `/mytag`. This is corrupt since element names are not allowed to have
|
||||
the `/` character. Furthermore this closing element should not be part of the DOM since it is
|
||||
only used to delineate the structure of the DOM.
|
||||
|
||||
|
||||
## CSS Styling of Custom Tag Names
|
||||
|
||||
To make CSS selectors work with custom elements, the custom element name must be pre-created with
|
||||
`document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
```html
|
||||
<html xmlns:ng="needed for ng: namespace">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
<script>
|
||||
// needed to make ng-include parse properly
|
||||
document.createElement('ng-include');
|
||||
|
||||
// needed to enable CSS reference
|
||||
document.createElement('ng:view');
|
||||
</script>
|
||||
<![endif]-->
|
||||
<style>
|
||||
ng\:view {
|
||||
display: block;
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
ng-include {
|
||||
display: block;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ng:view></ng:view>
|
||||
<ng-include></ng-include>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
To ensure your Angular application works on IE please consider:
|
||||
|
||||
1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
+223
-148
@@ -15,28 +15,68 @@ which drives many of these changes.
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
- **$parse:**
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
## Angular Expression Parsing (`$parse` + `$interpolate`)
|
||||
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
|
||||
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
|
||||
This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
The (deprecated) __proto__ property does not work inside angular expressions
|
||||
anymore.
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
|
||||
expressions. If you really need them for some reason, please wrap/bind them to make them
|
||||
less dangerous, then make them available through the scope object.
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
This prevents the use of `Object` inside angular expressions.
|
||||
If you need Object.keys, make it accessible in the scope.
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
|
||||
- due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
|
||||
- due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
|
||||
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
|
||||
It can no longer be turned on.
|
||||
Two methods have been removed:
|
||||
* `$parseProvider.unwrapPromises`
|
||||
* `$parseProvider.logPromiseWarnings`
|
||||
|
||||
|
||||
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
|
||||
the function returned by `$interpolate`
|
||||
no longer has a `.parts` array set on it.
|
||||
|
||||
Instead it has two arrays:
|
||||
* `.expressions`, an array of the expressions in the
|
||||
interpolated text. The expressions are parsed with
|
||||
`$parse`, with an extra layer converting them to strings
|
||||
when computed
|
||||
* `.separators`, an array of strings representing the
|
||||
separations between interpolations in the text.
|
||||
This array is **always** 1 item longer than the
|
||||
`.expressions` array for easy merging with it
|
||||
|
||||
|
||||
|
||||
|
||||
## Miscellaneous Angular helpers
|
||||
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
This changes `angular.copy` so that it applies the prototype of the original
|
||||
object to the copied object. Previously, `angular.copy` would copy properties
|
||||
of the original object's prototype chain directly onto the copied object.
|
||||
@@ -53,17 +93,54 @@ not filter them with `hasOwnProperty`.
|
||||
**Be aware that this change also uses a feature that is not compatible with
|
||||
IE8.** If you need this to work on IE8 then you would need to provide a polyfill
|
||||
for `Object.create` and `Object.getPrototypeOf`.
|
||||
- **core:** due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
Closes #3969
|
||||
Closes #4277
|
||||
Closes #7960
|
||||
|
||||
|
||||
- **$compile:** due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before, you will have to
|
||||
manually do this yourself now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## jqLite / JQuery
|
||||
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
|
||||
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
|
||||
the jQuery `detach()` method does not trigger the `$destroy` event.
|
||||
If you want to destroy Angular data attached to the element, use `remove()`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Angular HTML Compiler (`$compile`)
|
||||
|
||||
|
||||
- due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
|
||||
|
||||
The isolated scope of a component directive no longer leaks into the template
|
||||
that contains the instance of the directive. This means that you can no longer
|
||||
access the isolated scope from attributes on the element where the isolated
|
||||
directive is defined.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
- due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
|
||||
|
||||
Requesting isolate scope and any other scope on a single element is an error.
|
||||
@@ -77,9 +154,50 @@ If you find that your code is now throwing a `$compile:multidir` error,
|
||||
check that you do not have directives on the same element that are trying
|
||||
to request both an isolate and a non-isolate scope and fix your code.
|
||||
|
||||
Closes #4402
|
||||
Closes #4421
|
||||
- **NgModel:** due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
- due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
|
||||
replace the element that they are on will be removed in the next major angular version.
|
||||
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
|
||||
problems compared to what it solves. Also, with Web Components it is normal to have
|
||||
custom elements in the DOM.
|
||||
|
||||
|
||||
- due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
|
||||
calling `attr.$observe` no longer returns the observer function, but a
|
||||
deregistration function instead. To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = attr.$observe('someAttr', function(value) {
|
||||
console.log(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
After:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = function(value) {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
attr.$observe('someAttr', observer);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
## Forms, Inputs and ngModel
|
||||
|
||||
- due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
|
||||
If an expression is used on ng-pattern (such as `ng-pattern="exp"`) or on the
|
||||
@@ -95,45 +213,70 @@ this limitation, use a regular expression object as the value for the expression
|
||||
|
||||
//after
|
||||
$scope.exp = /abc/i;
|
||||
- **Scope:** due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
|
||||
|
||||
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
|
||||
|
||||
|
||||
This commit changes the API on `NgModelController`, both semantically and
|
||||
in terms of adding and renaming methods.
|
||||
|
||||
* `$setViewValue(value)` -
|
||||
This method still changes the `$viewValue` but does not immediately commit this
|
||||
change through to the `$modelValue` as it did previously.
|
||||
Now the value is committed only when a trigger specified in an associated
|
||||
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
|
||||
specified for the trigger then the change will also be debounced before being
|
||||
committed.
|
||||
In most cases this should not have a significant impact on how `NgModelController`
|
||||
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
|
||||
a (potentially debounced) commit immediately.
|
||||
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
|
||||
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
|
||||
to cancel any pending debounced updates and to re-render the input.
|
||||
|
||||
To migrate code that used `$cancelUpdate()` follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$cancelUpdate();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$rollbackViewValue();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- types date, time, datetime-local, month, week now always
|
||||
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Scopes and Digests (`$scope`)
|
||||
|
||||
- due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
Scope#$id is now of type number rather than string. Since the
|
||||
id is primarily being used for debugging purposes this change should not affect
|
||||
anyone.
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
- **$compile:** due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
|
||||
replace the element that they are on will be removed in the next major angular version.
|
||||
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
|
||||
problems compared to what it solves. Also, with Web Components it is normal to have
|
||||
custom elements in the DOM.
|
||||
|
||||
- **$parse:** due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
|
||||
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
|
||||
It can no longer be turned on.
|
||||
Two methods have been removed:
|
||||
* `$parseProvider.unwrapPromises`
|
||||
* `$parseProvider.logPromiseWarnings`
|
||||
|
||||
- **Scope:** due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
|
||||
- due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
|
||||
[#7445](https://github.com/angular/angular.js/issues/7445),
|
||||
[#7523](https://github.com/angular/angular.js/issues/7523)
|
||||
`$broadcast` and `$emit` will now reset the `currentScope` property of the event to
|
||||
@@ -141,11 +284,11 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
`currentScope` property, it should be migrated to use `targetScope` instead. All of these cases
|
||||
should be considered programming bugs.
|
||||
|
||||
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
|
||||
the jQuery `detach()` method does not trigger the `$destroy` event.
|
||||
If you want to destroy Angular data attached to the element, use `remove()`.
|
||||
|
||||
|
||||
|
||||
|
||||
## Server Requests (`$http`, `$resource`)
|
||||
- **$http:** due to [ad4336f9](https://github.com/angular/angular.js/commit/ad4336f9359a073e272930f8f9bcd36587a8648f),
|
||||
|
||||
|
||||
@@ -197,7 +340,24 @@ More details on the new interceptors API (which has been around as of v1.1.4) ca
|
||||
{@link $http#interceptors interceptors}
|
||||
|
||||
|
||||
- **injector:** due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
|
||||
|
||||
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
|
||||
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
|
||||
correct events to detect errors, i.e. even empty responses can be successful.
|
||||
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Modules and Injector (`$inject`)
|
||||
|
||||
- due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
|
||||
|
||||
Previously, config blocks would be able to control behaviour of provider registration, due to being
|
||||
invoked prior to provider registration. Now, provider registration always occurs prior to configuration
|
||||
@@ -234,66 +394,13 @@ and "$dependentProvider" would have actually accomplished something and changed
|
||||
app. This is no longer possible within a single module.
|
||||
|
||||
|
||||
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
|
||||
|
||||
|
||||
This commit changes the API on `NgModelController`, both semantically and
|
||||
in terms of adding and renaming methods.
|
||||
|
||||
* `$setViewValue(value)` -
|
||||
This method still changes the `$viewValue` but does not immediately commit this
|
||||
change through to the `$modelValue` as it did previously.
|
||||
Now the value is committed only when a trigger specified in an associated
|
||||
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
|
||||
specified for the trigger then the change will also be debounced before being
|
||||
committed.
|
||||
In most cases this should not have a significant impact on how `NgModelController`
|
||||
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
|
||||
a (potentially debounced) commit immediately.
|
||||
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
|
||||
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
|
||||
to cancel any pending debounced updates and to re-render the input.
|
||||
|
||||
To migrate code that used `$cancelUpdate()` follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$cancelUpdate();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$rollbackViewValue();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
|
||||
the function returned by `$interpolate`
|
||||
no longer has a `.parts` array set on it.
|
||||
|
||||
Instead it has two arrays:
|
||||
* `.expressions`, an array of the expressions in the
|
||||
interpolated text. The expressions are parsed with
|
||||
`$parse`, with an extra layer converting them to strings
|
||||
when computed
|
||||
* `.separators`, an array of strings representing the
|
||||
separations between interpolations in the text.
|
||||
This array is **always** 1 item longer than the
|
||||
`.expressions` array for easy merging with it
|
||||
## Animation (`ngAnimate`)
|
||||
|
||||
|
||||
- **$animate:** due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
|
||||
- due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
|
||||
`$animate` will no longer default the after parameter to the last element of the parent
|
||||
container. Instead, when after is not specified, the new element will be inserted as the
|
||||
first child of the parent container.
|
||||
@@ -308,7 +415,7 @@ to:
|
||||
|
||||
|
||||
|
||||
- **$animate:** due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
|
||||
- due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
|
||||
|
||||
Any class-based animation code that makes use of transitions
|
||||
and uses the setup CSS classes (such as class-add and class-remove) must now
|
||||
@@ -343,45 +450,13 @@ After:
|
||||
Please view the documentation for ngAnimate for more info.
|
||||
|
||||
|
||||
- **$compile:** due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
|
||||
calling `attr.$observe` no longer returns the observer function, but a
|
||||
deregistration function instead. To migrate the code follow the example below:
|
||||
## Internet Explorer 8
|
||||
|
||||
Before:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = attr.$observe('someAttr', function(value) {
|
||||
console.log(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
After:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = function(value) {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
attr.$observe('someAttr', observer);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
|
||||
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
|
||||
correct events to detect errors, i.e. even empty responses can be successful.
|
||||
|
||||
- **build:** due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
|
||||
- due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
|
||||
As communicated before, IE8 is no longer supported.
|
||||
- **input:** types date, time, datetime-local, month, week now always
|
||||
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -345,7 +345,7 @@ access on JavaScript object.
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient strategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
|
||||
+10
-6
@@ -114,26 +114,30 @@ module.exports = function(config, specificOptions) {
|
||||
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
|
||||
|
||||
config.logLevel = config.LOG_DEBUG;
|
||||
config.transports = ['websocket', 'xhr-polling'];
|
||||
config.captureTimeout = 0; // rely on SL timeout
|
||||
// Karma (with socket.io 1.x) buffers by 50 and 50 tests can take a long time on IEs;-)
|
||||
config.browserNoActivityTimeout = 120000;
|
||||
|
||||
config.browserStack.build = buildLabel;
|
||||
config.browserStack.startTunnel = false;
|
||||
config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
config.sauceLabs.build = buildLabel;
|
||||
config.sauceLabs.startConnect = false;
|
||||
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
config.sauceLabs.recordScreenshots = true;
|
||||
|
||||
// TODO(vojta): remove once SauceLabs supports websockets.
|
||||
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
|
||||
config.transports = ['xhr-polling'];
|
||||
|
||||
// Debug logging into a file, that we print out at the end of the build.
|
||||
config.loggers.push({
|
||||
type: 'file',
|
||||
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log')
|
||||
});
|
||||
|
||||
if (process.env.BROWSER_PROVIDER === 'saucelabs' || !process.env.BROWSER_PROVIDER) {
|
||||
// Allocating a browser can take pretty long (eg. if we are out of capacity and need to wait
|
||||
// for another build to finish) and so the `captureTimeout` typically kills
|
||||
// an in-queue-pending request, which makes no sense.
|
||||
config.captureTimeout = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node ./lib/browser-stack/start-tunnel.js &
|
||||
@@ -5,9 +5,10 @@ var http = require('http');
|
||||
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
|
||||
|
||||
var HOSTNAME = 'localhost';
|
||||
var PORTS = require('../grunt/utils').availablePorts;
|
||||
var PORTS = [9876, 8000];
|
||||
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
|
||||
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
|
||||
var READY_FILE = process.env.BROWSER_PROVIDER_READY_FILE;
|
||||
var TUNNEL_IDENTIFIER = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
// We need to start fake servers, otherwise the tunnel does not start.
|
||||
var fakeServers = [];
|
||||
@@ -24,6 +25,7 @@ PORTS.forEach(function(port) {
|
||||
|
||||
var tunnel = new BrowserStackTunnel({
|
||||
key: ACCESS_KEY,
|
||||
tunnelIdentifier: TUNNEL_IDENTIFIER,
|
||||
hosts: hosts
|
||||
});
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
|
||||
node ./lib/browserstack/start_tunnel.js &
|
||||
+1
-43
@@ -11,21 +11,6 @@ var _ = require('lodash');
|
||||
|
||||
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
|
||||
|
||||
var PORT_MIN = 8000;
|
||||
var PORT_MAX = 9999;
|
||||
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
|
||||
var getRandomPorts = function() {
|
||||
if (!process.env.TRAVIS) {
|
||||
return [9876, 9877];
|
||||
}
|
||||
|
||||
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
|
||||
return [
|
||||
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
|
||||
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -312,32 +297,5 @@ module.exports = {
|
||||
}
|
||||
next();
|
||||
};
|
||||
},
|
||||
|
||||
parallelTask: function(args, options) {
|
||||
var task = {
|
||||
grunt: true,
|
||||
args: args,
|
||||
stream: options && options.stream
|
||||
};
|
||||
|
||||
args.push('--port=' + this.sauceLabsAvailablePorts.pop());
|
||||
|
||||
if (args.indexOf('test:e2e') !== -1 && grunt.option('e2e-browsers')) {
|
||||
args.push('--browsers=' + grunt.option('e2e-browsers'));
|
||||
} else if (grunt.option('browsers')) {
|
||||
args.push('--browsers=' + grunt.option('browsers'));
|
||||
}
|
||||
|
||||
if (grunt.option('reporters')) {
|
||||
args.push('--reporters=' + grunt.option('reporters'));
|
||||
}
|
||||
|
||||
return task;
|
||||
},
|
||||
|
||||
// see http://saucelabs.com/docs/connect#localhost
|
||||
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
|
||||
// pseudo-random port numbers for BrowserStack
|
||||
availablePorts: getRandomPorts()
|
||||
}
|
||||
};
|
||||
|
||||
+2
-3
@@ -25,7 +25,6 @@
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~0.7.1",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
@@ -38,8 +37,8 @@
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "^0.12.0",
|
||||
"karma-browserstack-launcher": "0.1.1",
|
||||
"karma": "vojtajina/karma#socketio_10",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-jasmine": "0.1.5",
|
||||
|
||||
+72
-23
@@ -2,32 +2,81 @@
|
||||
|
||||
var config = require('./protractor-shared-conf').config;
|
||||
|
||||
config.sauceUser = process.env.SAUCE_USERNAME;
|
||||
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
|
||||
if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
// Using BrowserStack.
|
||||
config.seleniumAddress = 'http://hub.browserstack.com/wd/hub';
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'chrome',
|
||||
platform: 'MAC',
|
||||
version: '34'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'safari',
|
||||
platform: 'MAC',
|
||||
version: '7'
|
||||
})
|
||||
];
|
||||
} else {
|
||||
// Using SauceLabs.
|
||||
config.sauceUser = process.env.SAUCE_USERNAME;
|
||||
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'chrome',
|
||||
platform: 'OS X 10.9',
|
||||
version: '34'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
config.multiCapabilities = [{
|
||||
'browserName': 'chrome',
|
||||
'platform': 'OS X 10.9',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
'version': '34'
|
||||
}, {
|
||||
'browserName': 'firefox',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
'version': '28'
|
||||
}, {
|
||||
browserName: 'safari',
|
||||
'platform': 'OS X 10.9',
|
||||
'version': '7',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER
|
||||
}];
|
||||
|
||||
config.allScriptsTimeout = 30000;
|
||||
config.getPageTimeout = 30000;
|
||||
|
||||
exports.config = config;
|
||||
|
||||
|
||||
function capabilitiesForBrowserStack(capabilities) {
|
||||
return {
|
||||
'browserstack.user': process.env.BROWSER_STACK_USERNAME,
|
||||
'browserstack.key': process.env.BROWSER_STACK_ACCESS_KEY,
|
||||
'browserstack.local' : 'true',
|
||||
'browserstack.debug': 'true',
|
||||
'browserstack.tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
|
||||
'name': 'Angular E2E',
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
};
|
||||
}
|
||||
|
||||
function capabilitiesForSauceLabs(capabilities) {
|
||||
return {
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
|
||||
'name': 'Angular E2E',
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,13 +2,20 @@
|
||||
|
||||
set -e
|
||||
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
|
||||
|
||||
if [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11"
|
||||
else
|
||||
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11"
|
||||
fi
|
||||
|
||||
grunt test:promises-aplus
|
||||
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
elif [ $JOB = "e2e" ]; then
|
||||
if [ $TEST_TARGET = "jquery" ]; then
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Has to be run from project root directory.
|
||||
|
||||
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
echo "Using BrowserStack"
|
||||
elif [ "$BROWSER_PROVIDER" == "saucelabs" ]; then
|
||||
echo "Using SauceLabs"
|
||||
else
|
||||
echo "Invalid BROWSER_PROVIDER. Please set env var BROWSER_PROVIDER to 'saucelabs' or 'browserstack'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./lib/${BROWSER_PROVIDER}/start_tunnel.sh
|
||||
+6
-2
@@ -964,12 +964,16 @@ function toJsonReplacer(key, value) {
|
||||
* stripped since angular uses this notation internally.
|
||||
*
|
||||
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
|
||||
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
|
||||
* @returns {string|undefined} JSON-ified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
if (typeof obj === 'undefined') return undefined;
|
||||
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
|
||||
if (!isNumber(pretty)) {
|
||||
pretty = pretty ? 2 : null;
|
||||
}
|
||||
return JSON.stringify(obj, toJsonReplacer, pretty);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@
|
||||
$TimeoutProvider,
|
||||
$$RAFProvider,
|
||||
$$AsyncCallbackProvider,
|
||||
$WindowProvider
|
||||
$WindowProvider,
|
||||
$$jqLiteProvider
|
||||
*/
|
||||
|
||||
|
||||
@@ -236,7 +237,8 @@ function publishExternalAPI(angular) {
|
||||
$timeout: $TimeoutProvider,
|
||||
$window: $WindowProvider,
|
||||
$$rAF: $$RAFProvider,
|
||||
$$asyncCallback: $$AsyncCallbackProvider
|
||||
$$asyncCallback: $$AsyncCallbackProvider,
|
||||
$$jqLite: $$jqLiteProvider
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
+12
-8
@@ -179,6 +179,7 @@ function annotate(fn, strictDi, name) {
|
||||
* Return an instance of the service.
|
||||
*
|
||||
* @param {string} name The name of the instance to retrieve.
|
||||
* @param {string} caller An optional string to provide the origin of the function call for error messages.
|
||||
* @return {*} The instance.
|
||||
*/
|
||||
|
||||
@@ -629,14 +630,17 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
},
|
||||
providerInjector = (providerCache.$injector =
|
||||
createInternalInjector(providerCache, function() {
|
||||
createInternalInjector(providerCache, function(serviceName, caller) {
|
||||
if (angular.isString(caller)) {
|
||||
path.push(caller);
|
||||
}
|
||||
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
|
||||
})),
|
||||
instanceCache = {},
|
||||
instanceInjector = (instanceCache.$injector =
|
||||
createInternalInjector(instanceCache, function(servicename) {
|
||||
var provider = providerInjector.get(servicename + providerSuffix);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
|
||||
createInternalInjector(instanceCache, function(serviceName, caller) {
|
||||
var provider = providerInjector.get(serviceName + providerSuffix, caller);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
|
||||
}));
|
||||
|
||||
|
||||
@@ -671,7 +675,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
|
||||
function enforceReturnValue(name, factory) {
|
||||
return function enforcedReturnValue() {
|
||||
var result = instanceInjector.invoke(factory, this, undefined, name);
|
||||
var result = instanceInjector.invoke(factory, this);
|
||||
if (isUndefined(result)) {
|
||||
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
|
||||
}
|
||||
@@ -766,7 +770,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
|
||||
function createInternalInjector(cache, factory) {
|
||||
|
||||
function getService(serviceName) {
|
||||
function getService(serviceName, caller) {
|
||||
if (cache.hasOwnProperty(serviceName)) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
|
||||
@@ -777,7 +781,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
try {
|
||||
path.unshift(serviceName);
|
||||
cache[serviceName] = INSTANTIATING;
|
||||
return cache[serviceName] = factory(serviceName);
|
||||
return cache[serviceName] = factory(serviceName, caller);
|
||||
} catch (err) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
delete cache[serviceName];
|
||||
@@ -809,7 +813,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
args.push(
|
||||
locals && locals.hasOwnProperty(key)
|
||||
? locals[key]
|
||||
: getService(key)
|
||||
: getService(key, serviceName)
|
||||
);
|
||||
}
|
||||
if (isArray(fn)) {
|
||||
|
||||
@@ -1003,3 +1003,24 @@ forEach({
|
||||
JQLite.prototype.bind = JQLite.prototype.on;
|
||||
JQLite.prototype.unbind = JQLite.prototype.off;
|
||||
});
|
||||
|
||||
|
||||
// Provider for private $$jqLite service
|
||||
function $$jqLiteProvider() {
|
||||
this.$get = function $$jqLite() {
|
||||
return extend(JQLite, {
|
||||
hasClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteHasClass(node, classes);
|
||||
},
|
||||
addClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteAddClass(node, classes);
|
||||
},
|
||||
removeClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteRemoveClass(node, classes);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
+8
-1
@@ -61,6 +61,11 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
}
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Note: this method is used only by scenario runner
|
||||
@@ -190,8 +195,10 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
} else {
|
||||
} else if (!sameBase) {
|
||||
location.href = url;
|
||||
} else {
|
||||
location.hash = getHash(url);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
|
||||
+16
-8
@@ -115,7 +115,7 @@
|
||||
* #### `multiElement`
|
||||
* When this property is set to true, the HTML compiler will collect DOM nodes between
|
||||
* nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
|
||||
* together as the directive elements. It is recomended that this feature be used on directives
|
||||
* together as the directive elements. It is recommended that this feature be used on directives
|
||||
* which are not strictly behavioural (such as {@link ngClick}), and which
|
||||
* do not manipulate or replace child nodes (such as {@link ngInclude}).
|
||||
*
|
||||
@@ -907,6 +907,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
|
||||
Attributes.prototype = {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compile.directive.Attributes#$normalize
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
|
||||
* `data-`) to its normalized, camelCase form.
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* @param {string} name Name to normalize
|
||||
*/
|
||||
$normalize: directiveNormalize,
|
||||
|
||||
|
||||
@@ -2485,13 +2500,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
* All of these will become 'myDirective':
|
||||
* my:Directive
|
||||
* my-directive
|
||||
* x-my-directive
|
||||
* data-my:directive
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
* @param name Name to normalize
|
||||
*/
|
||||
function directiveNormalize(name) {
|
||||
|
||||
+14
-14
@@ -954,7 +954,6 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
var placeholder = element[0].placeholder, noevent = {};
|
||||
var type = lowercase(element[0].type);
|
||||
|
||||
// In composition mode, users are still inputing intermediate text buffer,
|
||||
@@ -974,19 +973,14 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
var listener = function(ev) {
|
||||
if (timeout) {
|
||||
$browser.defer.cancel(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
if (composing) return;
|
||||
var value = element.val(),
|
||||
event = ev && ev.type;
|
||||
|
||||
// IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
|
||||
// We don't want to dirty the value when this happens, so we abort here. Unfortunately,
|
||||
// IE also sends input events for other non-input-related things, (such as focusing on a
|
||||
// form control), so this change is not entirely enough to solve this.
|
||||
if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
|
||||
placeholder = element[0].placeholder;
|
||||
return;
|
||||
}
|
||||
|
||||
// By default we will trim the value
|
||||
// If the attribute ng-trim exists we will avoid trimming
|
||||
// If input type is 'password', the value is never trimmed
|
||||
@@ -1009,11 +1003,13 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
} else {
|
||||
var timeout;
|
||||
|
||||
var deferListener = function(ev) {
|
||||
var deferListener = function(ev, input, origValue) {
|
||||
if (!timeout) {
|
||||
timeout = $browser.defer(function() {
|
||||
listener(ev);
|
||||
timeout = null;
|
||||
if (!input || input.value !== origValue) {
|
||||
listener(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1025,7 +1021,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// command modifiers arrows
|
||||
if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
|
||||
|
||||
deferListener(event);
|
||||
deferListener(event, this, this.value);
|
||||
});
|
||||
|
||||
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
|
||||
@@ -2200,11 +2196,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
var prevModelValue = ctrl.$modelValue;
|
||||
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
|
||||
ctrl.$$rawModelValue = modelValue;
|
||||
|
||||
if (allowInvalid) {
|
||||
ctrl.$modelValue = modelValue;
|
||||
writeToModelIfNeeded();
|
||||
}
|
||||
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
|
||||
|
||||
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
|
||||
// This can happen if e.g. $setViewValue is called from inside a parser
|
||||
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
|
||||
if (!allowInvalid) {
|
||||
// Note: Don't check ctrl.$valid here, as we could have
|
||||
// external validators (e.g. calculated on the server),
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<input ng-model="title"><br>
|
||||
<input ng-model="title"> <br/>
|
||||
<textarea ng-model="text"></textarea> <br/>
|
||||
<pane title="{{title}}">{{text}}</pane>
|
||||
</div>
|
||||
|
||||
@@ -42,9 +42,9 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* or property name (for object data sources) of the value within the collection. If a `track by` expression
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
* ### `select as` with `trackexpr`
|
||||
* ### `select as` with `track by`
|
||||
*
|
||||
* Using `select as` together with `trackexpr` is not recommended. Reasoning:
|
||||
* Using `select as` together with `track by` is not recommended. Reasoning:
|
||||
*
|
||||
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
|
||||
@@ -69,8 +69,10 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
||||
* (for including a filter with `track by`)
|
||||
* * for object data sources:
|
||||
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
|
||||
+86
-89
@@ -119,104 +119,101 @@ function filterFilter() {
|
||||
return function(array, expression, comparator) {
|
||||
if (!isArray(array)) return array;
|
||||
|
||||
var comparatorType = typeof(comparator),
|
||||
predicates = [];
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
|
||||
predicates.check = function(value, index) {
|
||||
for (var j = 0; j < predicates.length; j++) {
|
||||
if (!predicates[j](value, index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (comparatorType !== 'function') {
|
||||
if (comparatorType === 'boolean' && comparator) {
|
||||
comparator = function(obj, text) {
|
||||
return angular.equals(obj, text);
|
||||
};
|
||||
} else {
|
||||
comparator = function(obj, text) {
|
||||
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
|
||||
for (var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
|
||||
comparator(obj[objKey], text[objKey])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
text = ('' + text).toLowerCase();
|
||||
return ('' + obj).toLowerCase().indexOf(text) > -1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var search = function(obj, text) {
|
||||
if (typeof text === 'string' && text.charAt(0) === '!') {
|
||||
return !search(obj, text.substr(1));
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
return comparator(obj, text);
|
||||
case 'object':
|
||||
switch (typeof text) {
|
||||
case 'object':
|
||||
return comparator(obj, text);
|
||||
default:
|
||||
for (var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case 'array':
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
if (search(obj[i], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
switch (typeof expression) {
|
||||
case 'function':
|
||||
predicateFn = expression;
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
// Set up expression object and fall through
|
||||
expression = {$:expression};
|
||||
// jshint -W086
|
||||
matchAgainstAnyProp = true;
|
||||
//jshint -W086
|
||||
case 'object':
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
if (typeof expression[path] === 'undefined') return;
|
||||
predicates.push(function(value) {
|
||||
return search(path == '$' ? value : (value && value[path]), expression[path]);
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
predicates.push(expression);
|
||||
//jshint +W086
|
||||
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
|
||||
break;
|
||||
default:
|
||||
return array;
|
||||
}
|
||||
var filtered = [];
|
||||
for (var j = 0; j < array.length; j++) {
|
||||
var value = array[j];
|
||||
if (predicates.check(value, j)) {
|
||||
filtered.push(value);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
|
||||
return array.filter(predicateFn);
|
||||
};
|
||||
}
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var predicateFn;
|
||||
|
||||
if (comparator === true) {
|
||||
comparator = equals;
|
||||
} else if (!isFunction(comparator)) {
|
||||
comparator = function(actual, expected) {
|
||||
if (isObject(actual) || isObject(expected)) {
|
||||
// Prevent an object to be considered equal to a string like `'[object'`
|
||||
return false;
|
||||
}
|
||||
|
||||
actual = lowercase('' + actual);
|
||||
expected = lowercase('' + expected);
|
||||
return actual.indexOf(expected) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
predicateFn = function(item) {
|
||||
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
|
||||
};
|
||||
|
||||
return predicateFn;
|
||||
}
|
||||
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
|
||||
var actualType = typeof actual;
|
||||
var expectedType = typeof expected;
|
||||
|
||||
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
|
||||
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
|
||||
} else if (actualType === 'array') {
|
||||
// In case `actual` is an array, consider it a match
|
||||
// if ANY of it's items matches `expected`
|
||||
return actual.some(function(item) {
|
||||
return deepCompare(item, expected, comparator, matchAgainstAnyProp);
|
||||
});
|
||||
}
|
||||
|
||||
switch (actualType) {
|
||||
case 'object':
|
||||
var key;
|
||||
if (matchAgainstAnyProp) {
|
||||
for (key in actual) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
if (isFunction(expectedVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyIsDollar = key === '$';
|
||||
var actualVal = keyIsDollar ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
return false;
|
||||
default:
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-11
@@ -150,7 +150,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (numStr.indexOf('e') !== -1) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
numStr = '0';
|
||||
number = 0;
|
||||
} else {
|
||||
formatedText = numStr;
|
||||
@@ -171,10 +170,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
@@ -207,12 +202,16 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
|
||||
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
} else {
|
||||
|
||||
if (fractionSize > 0 && number > -1 && number < 1) {
|
||||
if (fractionSize > 0 && number < 1) {
|
||||
formatedText = number.toFixed(fractionSize);
|
||||
number = parseFloat(formatedText);
|
||||
}
|
||||
}
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre,
|
||||
formatedText,
|
||||
isNegative ? pattern.negSuf : pattern.posSuf);
|
||||
@@ -502,25 +501,31 @@ function dateFilter($locale) {
|
||||
* the binding is automatically converted to JSON.
|
||||
*
|
||||
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
|
||||
* @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
|
||||
* @returns {string} JSON string.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<pre>{{ {'name':'value'} | json }}</pre>
|
||||
<pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
|
||||
<pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
*/
|
||||
function jsonFilter() {
|
||||
return function(object) {
|
||||
return toJson(object, true);
|
||||
return function(object, spacing) {
|
||||
if (isUndefined(spacing)) {
|
||||
spacing = 2;
|
||||
}
|
||||
return toJson(object, spacing);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -163,12 +163,29 @@ function orderByFilter($parse) {
|
||||
function compare(v1, v2) {
|
||||
var t1 = typeof v1;
|
||||
var t2 = typeof v2;
|
||||
if (t1 == t2) {
|
||||
if (isDate(v1) && isDate(v2)) {
|
||||
v1 = v1.valueOf();
|
||||
v2 = v2.valueOf();
|
||||
// Prepare values for Abstract Relational Comparison
|
||||
// (http://www.ecma-international.org/ecma-262/5.1/#sec-11.8.5):
|
||||
// If the resulting values are identical, return 0 to prevent
|
||||
// incorrect re-ordering.
|
||||
if (t1 === t2 && t1 === "object") {
|
||||
// If types are both numbers, emulate abstract ToPrimitive() operation
|
||||
// in order to get primitive values suitable for comparison
|
||||
t1 = typeof (v1.valueOf ? v1 = v1.valueOf() : v1);
|
||||
t2 = typeof (v2.valueOf ? v2 = v2.valueOf() : v2);
|
||||
if (t1 === t2 && t1 === "object") {
|
||||
// Object.prototype.valueOf will return the original object, by
|
||||
// default. If we do not receive a primitive value, use ToString()
|
||||
// instead.
|
||||
t1 = typeof (v1.toString ? v1 = v1.toString() : v1);
|
||||
t2 = typeof (v2.toString ? v2 = v2.toString() : v2);
|
||||
|
||||
// If the end result of toString() for each item is the same, do not
|
||||
// perform relational comparison, and do not re-order objects.
|
||||
if (t1 === t2 && v1 === v2 || t1 === "object") return 0;
|
||||
}
|
||||
if (t1 == "string") {
|
||||
}
|
||||
if (t1 === t2) {
|
||||
if (t1 === "string") {
|
||||
v1 = v1.toLowerCase();
|
||||
v2 = v2.toLowerCase();
|
||||
}
|
||||
|
||||
+4
-2
@@ -1027,8 +1027,7 @@ function $HttpProvider() {
|
||||
if (isDefined(cachedResp)) {
|
||||
if (isPromiseLike(cachedResp)) {
|
||||
// cached request has already been sent, but there is no response yet
|
||||
cachedResp.then(removePendingReq, removePendingReq);
|
||||
return cachedResp;
|
||||
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
|
||||
} else {
|
||||
// serving from cache
|
||||
if (isArray(cachedResp)) {
|
||||
@@ -1106,6 +1105,9 @@ function $HttpProvider() {
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePromiseWithResult(result) {
|
||||
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
|
||||
}
|
||||
|
||||
function removePendingReq() {
|
||||
var idx = $http.pendingRequests.indexOf(config);
|
||||
|
||||
+24
-24
@@ -57,33 +57,33 @@ function $IntervalProvider() {
|
||||
* // Don't start a new fight if we are already fighting
|
||||
* if ( angular.isDefined(stop) ) return;
|
||||
*
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* };
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* // Register the 'myCurrentTime' directive factory method.
|
||||
* // We inject $interval and dateFilter service since the factory method is DI.
|
||||
* .directive('myCurrentTime', ['$interval', 'dateFilter',
|
||||
|
||||
+25
-11
@@ -68,6 +68,10 @@ function stripHash(url) {
|
||||
return index == -1 ? url : url.substr(0, index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/(#.+)|#$/, '$1');
|
||||
}
|
||||
|
||||
|
||||
function stripFile(url) {
|
||||
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
|
||||
@@ -179,16 +183,25 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
*/
|
||||
this.$$parse = function(url) {
|
||||
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
|
||||
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
|
||||
? beginsWith(hashPrefix, withoutBaseUrl)
|
||||
: (this.$$html5)
|
||||
? withoutBaseUrl
|
||||
: '';
|
||||
var withoutHashUrl;
|
||||
|
||||
if (!isString(withoutHashUrl)) {
|
||||
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
|
||||
hashPrefix);
|
||||
if (withoutBaseUrl.charAt(0) === '#') {
|
||||
|
||||
// The rest of the url starts with a hash so we have
|
||||
// got either a hashbang path or a plain hash fragment
|
||||
withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
|
||||
if (isUndefined(withoutHashUrl)) {
|
||||
// There was no hashbang prefix so we just have a hash fragment
|
||||
withoutHashUrl = withoutBaseUrl;
|
||||
}
|
||||
|
||||
} else {
|
||||
// There was no hashbang path nor hash fragment:
|
||||
// If we are in HTML5 mode we use what is left as the path;
|
||||
// Otherwise we ignore what is left
|
||||
withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
|
||||
}
|
||||
|
||||
parseAppUrl(withoutHashUrl, this);
|
||||
|
||||
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
|
||||
@@ -551,7 +564,7 @@ var locationPrototype = {
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/some/path?foo=bar&baz=xoxo#hashValue
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
|
||||
* var hash = $location.hash();
|
||||
* // => "hashValue"
|
||||
* ```
|
||||
@@ -903,10 +916,11 @@ function $LocationProvider() {
|
||||
|
||||
// update browser
|
||||
$rootScope.$watch(function $locationWatch() {
|
||||
var oldUrl = $browser.url();
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== $location.absUrl() ||
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
|
||||
+7
-7
@@ -598,8 +598,8 @@ Parser.prototype = {
|
||||
logicalAND: function() {
|
||||
var left = this.equality();
|
||||
var token;
|
||||
if ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.text, this.logicalAND(), true);
|
||||
while ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.text, this.equality(), true);
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -607,8 +607,8 @@ Parser.prototype = {
|
||||
equality: function() {
|
||||
var left = this.relational();
|
||||
var token;
|
||||
if ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.text, this.equality());
|
||||
while ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.text, this.relational());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -616,8 +616,8 @@ Parser.prototype = {
|
||||
relational: function() {
|
||||
var left = this.additive();
|
||||
var token;
|
||||
if ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.text, this.relational());
|
||||
while ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.text, this.additive());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -709,7 +709,7 @@ Parser.prototype = {
|
||||
var args = argsFn.length ? [] : null;
|
||||
|
||||
return function $parseFunctionCall(scope, locals) {
|
||||
var context = contextGetter ? contextGetter(scope, locals) : scope;
|
||||
var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
|
||||
var fn = fnGetter(scope, locals, context) || noop;
|
||||
|
||||
if (args) {
|
||||
|
||||
+3
-1
@@ -67,7 +67,9 @@ function $SnifferProvider() {
|
||||
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
|
||||
// it. In particular the event is not fired when backspace or delete key are pressed or
|
||||
// when cut operation is performed.
|
||||
if (event == 'input' && msie == 9) return false;
|
||||
// IE10+ implements 'input' event but it erroneously fires under various situations,
|
||||
// e.g. when placeholder changes, or a form is focused.
|
||||
if (event === 'input' && msie <= 11) return false;
|
||||
|
||||
if (isUndefined(eventSupport[event])) {
|
||||
var divElm = document.createElement('div');
|
||||
|
||||
@@ -42,10 +42,8 @@ function $TemplateRequestProvider() {
|
||||
|
||||
return $http.get(tpl, httpOptions)
|
||||
.then(function(response) {
|
||||
var html = response.data;
|
||||
self.totalPendingRequests--;
|
||||
$templateCache.put(tpl, html);
|
||||
return html;
|
||||
return response.data;
|
||||
}, handleError);
|
||||
|
||||
function handleError(resp) {
|
||||
|
||||
+120
-119
@@ -13,7 +13,7 @@
|
||||
* # Usage
|
||||
*
|
||||
* To see animations in action, all that is required is to define the appropriate CSS classes
|
||||
* or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
|
||||
* or to register a JavaScript animation via the `myModule.animation()` function. The directives that support animation automatically are:
|
||||
* `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
|
||||
* by using the `$animate` service.
|
||||
*
|
||||
@@ -155,8 +155,8 @@
|
||||
* ### Structural transition animations
|
||||
*
|
||||
* Structural transitions (such as enter, leave and move) will always apply a `0s none` transition
|
||||
* value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave
|
||||
* or .ng-move) class. This means that any active transition animations operating on the element
|
||||
* value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave`
|
||||
* or `.ng-move`) class. This means that any active transition animations operating on the element
|
||||
* will be cut off to make way for the enter, leave or move animation.
|
||||
*
|
||||
* ### Class-based transition animations
|
||||
@@ -473,11 +473,12 @@ angular.module('ngAnimate', ['ng'])
|
||||
function isMatchingElement(elm1, elm2) {
|
||||
return extractElementNode(elm1) == extractElementNode(elm2);
|
||||
}
|
||||
|
||||
var $$jqLite;
|
||||
$provide.decorator('$animate',
|
||||
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
|
||||
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
|
||||
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite',
|
||||
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest, $$$jqLite) {
|
||||
|
||||
$$jqLite = $$$jqLite;
|
||||
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
|
||||
|
||||
// Wait until all directive and route-related templates are downloaded and
|
||||
@@ -871,22 +872,22 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during the `animate` animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | 1. $animate.animate(...) is called | class="my-animation" |
|
||||
* | 2. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 3. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 4. the className class value is added to the element | class="my-animation ng-animate className" |
|
||||
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate className" |
|
||||
* | 6. $animate blocks all CSS transitions on the element to ensure the .className class styling is applied right away| class="my-animation ng-animate className" |
|
||||
* | 7. $animate applies the provided collection of `from` CSS styles to the element | class="my-animation ng-animate className" |
|
||||
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate className" |
|
||||
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate className" |
|
||||
* | 10. the className-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate className className-active" |
|
||||
* | 11. $animate applies the collection of `to` CSS styles to the element which are then handled by the transition | class="my-animation ng-animate className className-active" |
|
||||
* | 12. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate className className-active" |
|
||||
* | 13. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 14. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
* | 1. `$animate.animate(...)` is called | `class="my-animation"` |
|
||||
* | 2. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 3. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 4. the `className` class value is added to the element | `class="my-animation ng-animate className"` |
|
||||
* | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate className"` |
|
||||
* | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"` |
|
||||
* | 7. `$animate` applies the provided collection of `from` CSS styles to the element | `class="my-animation ng-animate className"` |
|
||||
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate className"` |
|
||||
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate className"` |
|
||||
* | 10. the `className-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate className className-active"` |
|
||||
* | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition | `class="my-animation ng-animate className className-active"` |
|
||||
* | 12. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate className className-active"` |
|
||||
* | 13. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 14. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the enter animation
|
||||
* @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation
|
||||
@@ -917,21 +918,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during enter animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
* | 1. $animate.enter(...) is called | class="my-animation" |
|
||||
* | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
|
||||
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
|
||||
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" |
|
||||
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" |
|
||||
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" |
|
||||
* | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" |
|
||||
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 13. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | 1. `$animate.enter(...)` is called | `class="my-animation"` |
|
||||
* | 2. element is inserted into the `parentElement` element or beside the `afterElement` element | `class="my-animation"` |
|
||||
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 5. the `.ng-enter` class is added to the element | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
|
||||
* | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 13. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the enter animation
|
||||
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
|
||||
@@ -963,21 +964,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during leave animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
* | 1. $animate.leave(...) is called | class="my-animation" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
|
||||
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
|
||||
* | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave" |
|
||||
* | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" |
|
||||
* | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave" |
|
||||
* | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" |
|
||||
* | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" |
|
||||
* | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 12. The element is removed from the DOM | ... |
|
||||
* | 13. The returned promise is resolved. | ... |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | 1. `$animate.leave(...)` is called | `class="my-animation"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 4. the `.ng-leave` class is added to the element | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 7. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 8. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
|
||||
* | 10. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
|
||||
* | 11. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 12. The element is removed from the DOM | ... |
|
||||
* | 13. The returned promise is resolved. | ... |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the leave animation
|
||||
* @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
|
||||
@@ -1008,21 +1009,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during move animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
|
||||
* | 1. $animate.move(...) is called | class="my-animation" |
|
||||
* | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
|
||||
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
|
||||
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move" |
|
||||
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" |
|
||||
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move" |
|
||||
* | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" |
|
||||
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 13. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
* | 1. `$animate.move(...)` is called | `class="my-animation"` |
|
||||
* | 2. element is moved into the parentElement element or beside the afterElement element | `class="my-animation"` |
|
||||
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 5. the `.ng-move` class is added to the element | `class="my-animation ng-animate ng-move"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-move"` |
|
||||
* | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"` |
|
||||
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-move"` |
|
||||
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-move"` |
|
||||
* | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-move ng-move-active"` |
|
||||
* | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-move ng-move-active"` |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 13. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the move animation
|
||||
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
|
||||
@@ -1056,18 +1057,18 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during addClass animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
* | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" |
|
||||
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" |
|
||||
* | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super super-add super-add-active" |
|
||||
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super super-add super-add-active" |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
|
||||
* | 9. The super class is kept on the element | class="my-animation super" |
|
||||
* | 10. The returned promise is resolved. | class="my-animation super" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
* | 1. `$animate.addClass(element, 'super')` is called | `class="my-animation"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 3. the `.super-add` class is added to the element | `class="my-animation ng-animate super-add"` |
|
||||
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate super-add"` |
|
||||
* | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super super-add super-add-active"` |
|
||||
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super super-add super-add-active"` |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation super"` |
|
||||
* | 9. The super class is kept on the element | `class="my-animation super"` |
|
||||
* | 10. The returned promise is resolved. | `class="my-animation super"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be animated
|
||||
* @param {string} className the CSS class that will be added to the element and then animated
|
||||
@@ -1090,17 +1091,17 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during removeClass animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
* | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" |
|
||||
* | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" |
|
||||
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" |
|
||||
* | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-remove super-remove-active" |
|
||||
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 9. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
* | 1. `$animate.removeClass(element, 'super')` is called | `class="my-animation super"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation super ng-animate"` |
|
||||
* | 3. the `.super-remove` class is added to the element | `class="my-animation super ng-animate super-remove"` |
|
||||
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation super ng-animate super-remove"` |
|
||||
* | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super-remove super-remove-active"` |
|
||||
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super-remove super-remove-active"` |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 9. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
*
|
||||
* @param {DOMElement} element the element that will be animated
|
||||
@@ -1118,19 +1119,19 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @name $animate#setClass
|
||||
*
|
||||
* @description Adds and/or removes the given CSS classes to and from the element.
|
||||
* Once complete, the done() callback will be fired (if provided).
|
||||
* Once complete, the `done()` callback will be fired (if provided).
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
|
||||
* | 1. $animate.setClass(element, 'on', 'off') is called | class="my-animation off" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate off" |
|
||||
* | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off" |
|
||||
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off" |
|
||||
* | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
|
||||
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" |
|
||||
* | 9. The returned promise is resolved. | class="my-animation on" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
|
||||
* | 1. `$animate.setClass(element, 'on', 'off')` is called | `class="my-animation off"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate off"` |
|
||||
* | 3. the `.on-add` and `.off-remove` classes are added to the element | `class="my-animation ng-animate on-add off-remove off"` |
|
||||
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate on-add off-remove off"` |
|
||||
* | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
|
||||
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation on"` |
|
||||
* | 9. The returned promise is resolved. | `class="my-animation on"` |
|
||||
*
|
||||
* @param {DOMElement} element the element which will have its CSS classes changed
|
||||
* removed from it
|
||||
@@ -1268,7 +1269,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
all animations call this shared animation triggering function internally.
|
||||
The animationEvent variable refers to the JavaScript animation event that will be triggered
|
||||
and the className value is the name of the animation that will be applied within the
|
||||
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
|
||||
CSS code. Element, `parentElement` and `afterElement` are provided DOM elements for the animation
|
||||
and the onComplete callback will be fired once the animation is fully complete.
|
||||
*/
|
||||
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
|
||||
@@ -1380,10 +1381,10 @@ angular.module('ngAnimate', ['ng'])
|
||||
|
||||
//the ng-animate class does nothing, but it's here to allow for
|
||||
//parent animations to find and cancel child animations when needed
|
||||
element.addClass(NG_ANIMATE_CLASS_NAME);
|
||||
$$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME);
|
||||
if (options && options.tempClasses) {
|
||||
forEach(options.tempClasses, function(className) {
|
||||
element.addClass(className);
|
||||
$$jqLite.addClass(element, className);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1461,7 +1462,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
closeAnimation.hasBeenRun = true;
|
||||
if (options && options.tempClasses) {
|
||||
forEach(options.tempClasses, function(className) {
|
||||
element.removeClass(className);
|
||||
$$jqLite.removeClass(element, className);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1523,7 +1524,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
if (removeAnimations || !data.totalActive) {
|
||||
element.removeClass(NG_ANIMATE_CLASS_NAME);
|
||||
$$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME);
|
||||
element.removeData(NG_ANIMATE_STATE);
|
||||
}
|
||||
}
|
||||
@@ -1764,14 +1765,14 @@ angular.module('ngAnimate', ['ng'])
|
||||
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
|
||||
var applyClasses = !lookupCache[staggerCacheKey];
|
||||
|
||||
applyClasses && element.addClass(staggerClassName);
|
||||
applyClasses && $$jqLite.addClass(element, staggerClassName);
|
||||
|
||||
stagger = getElementAnimationDetails(element, staggerCacheKey);
|
||||
|
||||
applyClasses && element.removeClass(staggerClassName);
|
||||
applyClasses && $$jqLite.removeClass(element, staggerClassName);
|
||||
}
|
||||
|
||||
element.addClass(className);
|
||||
$$jqLite.addClass(element, className);
|
||||
|
||||
var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
|
||||
var timings = getElementAnimationDetails(element, eventCacheKey);
|
||||
@@ -1779,7 +1780,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var animationDuration = timings.animationDuration;
|
||||
|
||||
if (structural && transitionDuration === 0 && animationDuration === 0) {
|
||||
element.removeClass(className);
|
||||
$$jqLite.removeClass(element, className);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1851,7 +1852,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
if (!staggerTime) {
|
||||
element.addClass(activeClassName);
|
||||
$$jqLite.addClass(element, activeClassName);
|
||||
if (elementData.blockTransition) {
|
||||
blockTransitions(node, false);
|
||||
}
|
||||
@@ -1861,7 +1862,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var timings = getElementAnimationDetails(element, eventCacheKey);
|
||||
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
|
||||
if (maxDuration === 0) {
|
||||
element.removeClass(activeClassName);
|
||||
$$jqLite.removeClass(element, activeClassName);
|
||||
animateClose(element, className);
|
||||
activeAnimationComplete();
|
||||
return;
|
||||
@@ -1896,7 +1897,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
|
||||
var staggerTimeout;
|
||||
if (staggerTime > 0) {
|
||||
element.addClass(pendingClassName);
|
||||
$$jqLite.addClass(element, pendingClassName);
|
||||
staggerTimeout = $timeout(function() {
|
||||
staggerTimeout = null;
|
||||
|
||||
@@ -1907,8 +1908,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
blockAnimations(node, false);
|
||||
}
|
||||
|
||||
element.addClass(activeClassName);
|
||||
element.removeClass(pendingClassName);
|
||||
$$jqLite.addClass(element, activeClassName);
|
||||
$$jqLite.removeClass(element, pendingClassName);
|
||||
|
||||
if (styles) {
|
||||
if (timings.transitionDuration === 0) {
|
||||
@@ -1935,8 +1936,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
// timeout done method.
|
||||
function onEnd() {
|
||||
element.off(css3AnimationEvents, onAnimationProgress);
|
||||
element.removeClass(activeClassName);
|
||||
element.removeClass(pendingClassName);
|
||||
$$jqLite.removeClass(element, activeClassName);
|
||||
$$jqLite.removeClass(element, pendingClassName);
|
||||
if (staggerTimeout) {
|
||||
$timeout.cancel(staggerTimeout);
|
||||
}
|
||||
@@ -2024,7 +2025,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
function animateClose(element, className) {
|
||||
element.removeClass(className);
|
||||
$$jqLite.removeClass(element, className);
|
||||
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
|
||||
if (data) {
|
||||
if (data.running) {
|
||||
|
||||
+38
-27
@@ -26,7 +26,7 @@
|
||||
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
|
||||
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
|
||||
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event |
|
||||
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
|
||||
* | {@link module:ngMessages ngMessages} | aria-live |
|
||||
*
|
||||
@@ -82,7 +82,8 @@ function $AriaProvider() {
|
||||
ariaInvalid: true,
|
||||
ariaMultiline: true,
|
||||
ariaValue: true,
|
||||
tabindex: true
|
||||
tabindex: true,
|
||||
bindKeypress: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -99,6 +100,7 @@ function $AriaProvider() {
|
||||
* - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
|
||||
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
|
||||
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on ng-click
|
||||
*
|
||||
* @description
|
||||
* Enables/disables various ARIA attributes
|
||||
@@ -107,16 +109,9 @@ function $AriaProvider() {
|
||||
config = angular.extend(config, newConfig);
|
||||
};
|
||||
|
||||
function camelCase(input) {
|
||||
return input.replace(/-./g, function(letter, pos) {
|
||||
return letter[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function watchExpr(attrName, ariaAttr, negate) {
|
||||
var ariaCamelName = camelCase(ariaAttr);
|
||||
return function(scope, elem, attr) {
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
if (negate) {
|
||||
@@ -176,20 +171,13 @@ function $AriaProvider() {
|
||||
this.$get = function() {
|
||||
return {
|
||||
config: function(key) {
|
||||
return config[camelCase(key)];
|
||||
return config[key];
|
||||
},
|
||||
$$watchExpr: watchExpr
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
var ngAriaTabindex = ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
|
||||
@@ -199,8 +187,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}])
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
|
||||
function shouldAttachAttr(attr, elem) {
|
||||
return $aria.config(attr) && !elem.attr(attr);
|
||||
function shouldAttachAttr(attr, normalizedAttr, elem) {
|
||||
return $aria.config(normalizedAttr) && !elem.attr(attr);
|
||||
}
|
||||
|
||||
function getShape(attr, elem) {
|
||||
@@ -218,7 +206,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
require: '?ngModel',
|
||||
link: function(scope, elem, attr, ngModel) {
|
||||
var shape = getShape(attr, elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
|
||||
|
||||
function ngAriaWatchModelValue() {
|
||||
return ngModel.$modelValue;
|
||||
@@ -246,7 +234,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
switch (shape) {
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
if (shouldAttachAttr('aria-checked', elem)) {
|
||||
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
|
||||
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
|
||||
getRadioReaction() : ngAriaCheckboxReaction);
|
||||
}
|
||||
@@ -267,7 +255,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
break;
|
||||
case 'multiline':
|
||||
if (shouldAttachAttr('aria-multiline', elem)) {
|
||||
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
|
||||
elem.attr('aria-multiline', true);
|
||||
}
|
||||
break;
|
||||
@@ -277,7 +265,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', elem)) {
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
|
||||
scope.$watch(function ngAriaRequiredWatch() {
|
||||
return ngModel.$error.required;
|
||||
}, function ngAriaRequiredReaction(newVal) {
|
||||
@@ -285,7 +273,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldAttachAttr('aria-invalid', elem)) {
|
||||
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
|
||||
scope.$watch(function ngAriaInvalidWatch() {
|
||||
return ngModel.$invalid;
|
||||
}, function ngAriaInvalidReaction(newVal) {
|
||||
@@ -309,5 +297,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('ngClick', ngAriaTabindex)
|
||||
.directive('ngDblclick', ngAriaTabindex);
|
||||
.directive('ngClick',['$aria', function($aria) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !elem.attr('ng-keypress')) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$eval(attr.ngClick);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
Vendored
+6
-4
@@ -1112,7 +1112,7 @@ angular.mock.dump = function(object) {
|
||||
```
|
||||
*/
|
||||
angular.mock.$HttpBackendProvider = function() {
|
||||
this.$get = ['$rootScope', createHttpBackendMock];
|
||||
this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1129,7 +1129,7 @@ angular.mock.$HttpBackendProvider = function() {
|
||||
* @param {Object=} $browser Auto-flushing enabled if specified
|
||||
* @return {Object} Instance of $httpBackend mock
|
||||
*/
|
||||
function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
var definitions = [],
|
||||
expectations = [],
|
||||
responses = [],
|
||||
@@ -1159,7 +1159,9 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
}
|
||||
|
||||
function wrapResponse(wrapped) {
|
||||
if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
|
||||
if (!$browser && timeout) {
|
||||
timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
|
||||
}
|
||||
|
||||
return handleResponse;
|
||||
|
||||
@@ -2033,7 +2035,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
*/
|
||||
angular.mock.e2e = {};
|
||||
angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
|
||||
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,7 +79,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
||||
.view-animate-container {
|
||||
position:relative;
|
||||
height:100px!important;
|
||||
position:relative;
|
||||
background:white;
|
||||
border:1px solid black;
|
||||
height:40px;
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
*/
|
||||
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
var LINKY_URL_REGEXP =
|
||||
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
|
||||
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
|
||||
MAILTO_REGEXP = /^mailto:/;
|
||||
|
||||
return function(text, target) {
|
||||
@@ -117,8 +117,10 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
while ((match = raw.match(LINKY_URL_REGEXP))) {
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2] == match[3]) url = 'mailto:' + url;
|
||||
// if we did not match ftp/http/www/mailto then assume mailto
|
||||
if (!match[2] && !match[4]) {
|
||||
url = (match[3] ? 'http://' : 'mailto:') + url;
|
||||
}
|
||||
i = match.index;
|
||||
addText(raw.substr(0, i));
|
||||
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
||||
|
||||
@@ -199,7 +199,7 @@ angular.scenario.dsl('binding', function() {
|
||||
*/
|
||||
angular.scenario.dsl('input', function() {
|
||||
var chain = {};
|
||||
var supportInputEvent = 'oninput' in document.createElement('div') && msie != 9;
|
||||
var supportInputEvent = 'oninput' in document.createElement('div') && !(msie && msie <= 11);
|
||||
|
||||
chain.enter = function(value, event) {
|
||||
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'",
|
||||
|
||||
+10
-2
@@ -1191,9 +1191,17 @@ describe('angular', function() {
|
||||
|
||||
it('should format objects pretty', function() {
|
||||
expect(toJson({a: 1, b: 2}, true)).
|
||||
toBeOneOf('{\n "a": 1,\n "b": 2\n}', '{\n "a":1,\n "b":2\n}');
|
||||
toBe('{\n "a": 1,\n "b": 2\n}');
|
||||
expect(toJson({a: {b: 2}}, true)).
|
||||
toBeOneOf('{\n "a": {\n "b": 2\n }\n}', '{\n "a":{\n "b":2\n }\n}');
|
||||
toBe('{\n "a": {\n "b": 2\n }\n}');
|
||||
expect(toJson({a: 1, b: 2}, false)).
|
||||
toBe('{"a":1,"b":2}');
|
||||
expect(toJson({a: 1, b: 2}, 0)).
|
||||
toBe('{"a":1,"b":2}');
|
||||
expect(toJson({a: 1, b: 2}, 1)).
|
||||
toBe('{\n "a": 1,\n "b": 2\n}');
|
||||
expect(toJson({a: 1, b: 2}, {})).
|
||||
toBe('{\n "a": 1,\n "b": 2\n}');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ describe('injector', function() {
|
||||
var providers;
|
||||
var injector;
|
||||
var providerInjector;
|
||||
var controllerProvider;
|
||||
|
||||
beforeEach(module(function($provide, $injector) {
|
||||
beforeEach(module(function($provide, $injector, $controllerProvider) {
|
||||
providers = function(name, factory, annotations) {
|
||||
$provide.factory(name, extend(factory, annotations || {}));
|
||||
};
|
||||
providerInjector = $injector;
|
||||
controllerProvider = $controllerProvider;
|
||||
}));
|
||||
beforeEach(inject(function($injector) {
|
||||
injector = $injector;
|
||||
@@ -74,6 +76,22 @@ describe('injector', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should provide the caller name if given', function(done) {
|
||||
expect(function() {
|
||||
injector.get('idontexist', 'callerName');
|
||||
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- callerName");
|
||||
});
|
||||
|
||||
|
||||
it('should provide the caller name for controllers', function(done) {
|
||||
controllerProvider.register('myCtrl', function(idontexist) {});
|
||||
var $controller = injector.get('$controller');
|
||||
expect(function() {
|
||||
$controller('myCtrl', {$scope: {}});
|
||||
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- myCtrl");
|
||||
});
|
||||
|
||||
|
||||
it('should not corrupt the cache when an object fails to get instantiated', function() {
|
||||
expect(function() {
|
||||
injector.get('idontexist');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* global getHash:true, stripHash:true */
|
||||
|
||||
var historyEntriesLength;
|
||||
var sniffer = {};
|
||||
|
||||
@@ -51,6 +53,12 @@ function MockWindow(options) {
|
||||
mockWindow.history.state = null;
|
||||
historyEntriesLength++;
|
||||
},
|
||||
get hash() {
|
||||
return getHash(locationHref);
|
||||
},
|
||||
set hash(value) {
|
||||
locationHref = stripHash(locationHref) + '#' + value;
|
||||
},
|
||||
replace: function(url) {
|
||||
locationHref = url;
|
||||
mockWindow.history.state = null;
|
||||
@@ -550,6 +558,17 @@ describe('browser', function() {
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should retain the # character when the only change is clearing the hash fragment, to prevent page reload", function() {
|
||||
sniffer.history = true;
|
||||
|
||||
browser.url('http://server/#123');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/#123');
|
||||
|
||||
browser.url('http://server/');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/#');
|
||||
|
||||
});
|
||||
|
||||
it('should use location.replace when history.replaceState not available', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://new.org', true);
|
||||
@@ -561,6 +580,7 @@ describe('browser', function() {
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
|
||||
it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://server/#123', true);
|
||||
@@ -572,6 +592,7 @@ describe('browser', function() {
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
|
||||
it('should return $browser to allow chaining', function() {
|
||||
expect(browser.url('http://any.com')).toBe(browser);
|
||||
});
|
||||
|
||||
+228
-11
@@ -1066,6 +1066,56 @@ describe('NgModelController', function() {
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
it('should always use the most recent $viewValue for validation', function() {
|
||||
ctrl.$parsers.push(function(value) {
|
||||
if (value && value.substr(-1) === 'b') {
|
||||
value = 'a';
|
||||
ctrl.$setViewValue(value);
|
||||
ctrl.$render();
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
ctrl.$validators.mock = function(modelValue) {
|
||||
return true;
|
||||
};
|
||||
|
||||
spyOn(ctrl.$validators, 'mock').andCallThrough();
|
||||
|
||||
ctrl.$setViewValue('ab');
|
||||
|
||||
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a');
|
||||
expect(ctrl.$validators.mock.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should validate even if the modelValue did not change', function() {
|
||||
ctrl.$parsers.push(function(value) {
|
||||
if (value && value.substr(-1) === 'b') {
|
||||
value = 'a';
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
ctrl.$validators.mock = function(modelValue) {
|
||||
return true;
|
||||
};
|
||||
|
||||
spyOn(ctrl.$validators, 'mock').andCallThrough();
|
||||
|
||||
ctrl.$setViewValue('a');
|
||||
|
||||
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a');
|
||||
expect(ctrl.$validators.mock.calls.length).toEqual(1);
|
||||
|
||||
ctrl.$setViewValue('ab');
|
||||
|
||||
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'ab');
|
||||
expect(ctrl.$validators.mock.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1549,22 +1599,170 @@ describe('input', function() {
|
||||
expect(scope.name).toEqual('caitp');
|
||||
});
|
||||
|
||||
it('should not dirty the model on an input event in response to a placeholder change', inject(function($sniffer) {
|
||||
if (msie && $sniffer.hasEvent('input')) {
|
||||
compileInput('<input type="text" ng-model="name" name="name" />');
|
||||
inputElm.attr('placeholder', 'Test');
|
||||
browserTrigger(inputElm, 'input');
|
||||
describe("IE placeholder input events", function() {
|
||||
//IE fires an input event whenever a placeholder visually changes, essentially treating it as a value
|
||||
//Events:
|
||||
// placeholder attribute change: *input*
|
||||
// focus (which visually removes the placeholder value): focusin focus *input*
|
||||
// blur (which visually creates the placeholder value): focusout *input* blur
|
||||
//However none of these occur if the placeholder is not visible at the time of the event.
|
||||
//These tests try simulate various scenerios which do/do-not fire the extra input event
|
||||
|
||||
it('should not dirty the model on an input event in response to a placeholder change', function() {
|
||||
compileInput('<input type="text" placeholder="Test" attr-capture ng-model="unsetValue" name="name" />');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('Test');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
inputElm.attr('placeholder', 'Test Again');
|
||||
browserTrigger(inputElm, 'input');
|
||||
attrs.$set('placeholder', '');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
attrs.$set('placeholder', 'Test Again');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('Test Again');
|
||||
expect(inputElm).toBePristine();
|
||||
}
|
||||
}));
|
||||
|
||||
attrs.$set('placeholder', undefined);
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe(undefined);
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('foo');
|
||||
expect(inputElm).toBeDirty();
|
||||
});
|
||||
|
||||
it('should not dirty the model on an input event in response to a interpolated placeholder change', inject(function($rootScope) {
|
||||
compileInput('<input type="text" placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
$rootScope.ph = 1;
|
||||
$rootScope.$digest();
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
$rootScope.ph = "";
|
||||
$rootScope.$digest();
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('foo');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should dirty the model on an input event while in focus even if the placeholder changes', inject(function($rootScope) {
|
||||
$rootScope.ph = 'Test';
|
||||
compileInput('<input type="text" ng-attr-placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusin');
|
||||
browserTrigger(inputElm, 'focus');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('Test');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
$rootScope.ph = 'Test Again';
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('foo');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should not dirty the model on an input event in response to a ng-attr-placeholder change', inject(function($rootScope) {
|
||||
compileInput('<input type="text" ng-attr-placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
$rootScope.ph = 1;
|
||||
$rootScope.$digest();
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
$rootScope.ph = "";
|
||||
$rootScope.$digest();
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('foo');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should not dirty the model on an input event in response to a focus', inject(function($sniffer) {
|
||||
compileInput('<input type="text" placeholder="Test" ng-model="unsetValue" name="name" />');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('Test');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusin');
|
||||
browserTrigger(inputElm, 'focus');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('Test');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('foo');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should not dirty the model on an input event in response to a blur', inject(function($sniffer) {
|
||||
compileInput('<input type="text" placeholder="Test" ng-model="unsetValue" name="name" />');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm.attr('placeholder')).toBe('Test');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusin');
|
||||
browserTrigger(inputElm, 'focus');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusout');
|
||||
msie && browserTrigger(inputElm, 'input');
|
||||
browserTrigger(inputElm, 'blur');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('foo');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should dirty the model on an input event if there is a placeholder and value', inject(function($rootScope) {
|
||||
$rootScope.name = 'foo';
|
||||
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
|
||||
expect(inputElm.val()).toBe($rootScope.name);
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
changeInputValueTo('bar');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should dirty the model on an input event if there is a placeholder and value after focusing', inject(function($rootScope) {
|
||||
$rootScope.name = 'foo';
|
||||
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
|
||||
expect(inputElm.val()).toBe($rootScope.name);
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusin');
|
||||
browserTrigger(inputElm, 'focus');
|
||||
changeInputValueTo('bar');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
|
||||
it('should dirty the model on an input event if there is a placeholder and value after bluring', inject(function($rootScope) {
|
||||
$rootScope.name = 'foo';
|
||||
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
|
||||
expect(inputElm.val()).toBe($rootScope.name);
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusin');
|
||||
browserTrigger(inputElm, 'focus');
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
browserTrigger(inputElm, 'focusout');
|
||||
browserTrigger(inputElm, 'blur');
|
||||
changeInputValueTo('bar');
|
||||
expect(inputElm).toBeDirty();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate input names', function() {
|
||||
@@ -1656,7 +1854,7 @@ describe('input', function() {
|
||||
}
|
||||
});
|
||||
|
||||
describe('"paste" and "cut" events', function() {
|
||||
describe('"keydown", "paste" and "cut" events', function() {
|
||||
beforeEach(function() {
|
||||
// Force browser to report a lack of an 'input' event
|
||||
$sniffer.hasEvent = function(eventName) {
|
||||
@@ -1664,9 +1862,13 @@ describe('input', function() {
|
||||
};
|
||||
});
|
||||
|
||||
it('should update the model on "paste" event', function() {
|
||||
it('should update the model on "paste" event if the input value changes', function() {
|
||||
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
|
||||
|
||||
browserTrigger(inputElm, 'keydown');
|
||||
$browser.defer.flush();
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
inputElm.val('mark');
|
||||
browserTrigger(inputElm, 'paste');
|
||||
$browser.defer.flush();
|
||||
@@ -1682,6 +1884,21 @@ describe('input', function() {
|
||||
expect(scope.name).toEqual('john');
|
||||
});
|
||||
|
||||
it('should cancel the delayed dirty if a change occurs', function() {
|
||||
compileInput('<input type="text" ng-model="name" />');
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
|
||||
browserTrigger(inputElm, 'keydown', {target: inputElm[0]});
|
||||
inputElm.val('f');
|
||||
browserTrigger(inputElm, 'change');
|
||||
expect(inputElm).toBeDirty();
|
||||
|
||||
ctrl.$setPristine();
|
||||
scope.$apply();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(inputElm).toBePristine();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -98,6 +98,19 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support deep expression objects with multiple properties', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{person: {name: 'Joan', email: 'joan@example.net'}},
|
||||
{person: {name: 'John', email: 'john@example.com'}},
|
||||
{person: {name: 'Rita', email: 'rita@example.com'}}];
|
||||
var expr = {person: {name: 'Jo', email: '!example.com'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[2]]);
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties for given "$" property', function() {
|
||||
var items = [{first: 'tom', last: 'hevery'},
|
||||
{first: 'adam', last: 'hevery', alias: 'tom', done: false},
|
||||
@@ -110,6 +123,43 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties in the nested object for given deep "$" property', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{person: {name: 'Joan', email: 'joan@example.net'}},
|
||||
{person: {name: 'John', email: 'john@example.com'}},
|
||||
{person: {name: 'Rita', email: 'rita@example.com'}}];
|
||||
var expr = {person: {$: 'net'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[2]]);
|
||||
});
|
||||
|
||||
|
||||
it('should respect the depth level of a "$" property', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}];
|
||||
var expr = {person: {$: 'net'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[0]]);
|
||||
});
|
||||
|
||||
|
||||
it('should respect the nesting level of "$"', function() {
|
||||
var items = [{supervisor: 'me', person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{supervisor: 'me', person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{supervisor: 'me', person: {name: 'Joan', email: 'joan@example.net'}},
|
||||
{supervisor: 'me', person: {name: 'John', email: 'john@example.com'}},
|
||||
{supervisor: 'me', person: {name: 'Rita', email: 'rita@example.com'}}];
|
||||
var expr = {$: {$: 'me'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[1]]);
|
||||
});
|
||||
|
||||
|
||||
it('should support boolean properties', function() {
|
||||
var items = [{name: 'tom', current: true},
|
||||
{name: 'demi', current: false},
|
||||
@@ -129,8 +179,159 @@ describe('Filter: filter', function() {
|
||||
expect(filter(items, '!isk')[0]).toEqual(items[1]);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore function properties in items', function() {
|
||||
// Own function properties
|
||||
var items = [
|
||||
{text: 'hello', func: noop},
|
||||
{text: 'goodbye'},
|
||||
{text: 'kittens'},
|
||||
{text: 'puppies'}
|
||||
];
|
||||
var expr = {text: 'hello'};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
|
||||
// Inherited function proprties
|
||||
function Item(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Item.prototype.func = noop;
|
||||
|
||||
items = [
|
||||
new Item('hello'),
|
||||
new Item('goodbye'),
|
||||
new Item('kittens'),
|
||||
new Item('puppies')
|
||||
];
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore function properties in expression', function() {
|
||||
// Own function properties
|
||||
var items = [
|
||||
{text: 'hello'},
|
||||
{text: 'goodbye'},
|
||||
{text: 'kittens'},
|
||||
{text: 'puppies'}
|
||||
];
|
||||
var expr = {text: 'hello', func: noop};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
|
||||
// Inherited function proprties
|
||||
function Expr(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Expr.prototype.func = noop;
|
||||
|
||||
expr = new Expr('hello');
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should consider inherited properties in items', function() {
|
||||
function Item(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Item.prototype.doubleL = 'maybe';
|
||||
|
||||
var items = [
|
||||
new Item('hello'),
|
||||
new Item('goodbye'),
|
||||
new Item('kittens'),
|
||||
new Item('puppies')
|
||||
];
|
||||
var expr = {text: 'hello', doubleL: 'perhaps'};
|
||||
|
||||
expect(filter(items, expr).length).toBe(0);
|
||||
expect(filter(items, expr, true).length).toBe(0);
|
||||
|
||||
expr = {text: 'hello', doubleL: 'maybe'};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should consider inherited properties in expression', function() {
|
||||
function Expr(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Expr.prototype.doubleL = true;
|
||||
|
||||
var items = [
|
||||
{text: 'hello', doubleL: true},
|
||||
{text: 'goodbye'},
|
||||
{text: 'kittens'},
|
||||
{text: 'puppies'}
|
||||
];
|
||||
var expr = new Expr('e');
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
|
||||
expr = new Expr('hello');
|
||||
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should not be affected by `Object.prototype` when using a string expression', function() {
|
||||
Object.prototype.someProp = 'oo';
|
||||
|
||||
var items = [
|
||||
createMap(),
|
||||
createMap(),
|
||||
createMap(),
|
||||
createMap()
|
||||
];
|
||||
items[0].someProp = 'hello';
|
||||
items[1].someProp = 'goodbye';
|
||||
items[2].someProp = 'kittens';
|
||||
items[3].someProp = 'puppies';
|
||||
|
||||
// Affected by `Object.prototype`
|
||||
expect(filter(items, {}).length).toBe(1);
|
||||
expect(filter(items, {})[0]).toBe(items[1]);
|
||||
|
||||
expect(filter(items, {$: 'll'}).length).toBe(0);
|
||||
|
||||
// Not affected by `Object.prototype`
|
||||
expect(filter(items, 'll').length).toBe(1);
|
||||
expect(filter(items, 'll')[0]).toBe(items[0]);
|
||||
|
||||
delete Object.prototype.someProp;
|
||||
});
|
||||
|
||||
|
||||
describe('should support comparator', function() {
|
||||
|
||||
it('not consider `object === "[object Object]"` in non-strict comparison', function() {
|
||||
var items = [{test: {}}];
|
||||
var expr = '[object';
|
||||
expect(filter(items, expr).length).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('as equality when true', function() {
|
||||
var items = ['misko', 'adam', 'adamson'];
|
||||
var expr = 'adam';
|
||||
@@ -177,5 +378,46 @@ describe('Filter: filter', function() {
|
||||
expr = 10;
|
||||
expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]);
|
||||
});
|
||||
|
||||
|
||||
it('and use it correctly with deep expression objects', function() {
|
||||
var items = [
|
||||
{id: 0, details: {email: 'admin@example.com', role: 'admin'}},
|
||||
{id: 1, details: {email: 'user1@example.com', role: 'user'}},
|
||||
{id: 2, details: {email: 'user2@example.com', role: 'user'}}
|
||||
];
|
||||
var expr, comp;
|
||||
|
||||
expr = {details: {email: 'user@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr)).toEqual([]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr)).toEqual([items[0]]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr, true)).toEqual([]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'admin'}};
|
||||
expect(filter(items, expr, true)).toEqual([items[0]]);
|
||||
|
||||
expr = {details: {email: 'user', role: 'us'}};
|
||||
expect(filter(items, expr)).toEqual([items[1], items[2]]);
|
||||
|
||||
expr = {id: 0, details: {email: 'user', role: 'us'}};
|
||||
expect(filter(items, expr)).toEqual([]);
|
||||
|
||||
expr = {id: 1, details: {email: 'user', role: 'us'}};
|
||||
expect(filter(items, expr)).toEqual([items[1]]);
|
||||
|
||||
comp = function(actual, expected) {
|
||||
return isString(actual) && isString(expected) && (actual.indexOf(expected) === 0);
|
||||
};
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'min'}};
|
||||
expect(filter(items, expr, comp)).toEqual([]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr, comp)).toEqual([items[0]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,8 +85,10 @@ describe('filters', function() {
|
||||
});
|
||||
|
||||
it('should format numbers that round to zero as nonnegative', function() {
|
||||
var num = formatNumber(-0.01, pattern, ',', '.', 1);
|
||||
expect(num).toBe('0.0');
|
||||
expect(formatNumber(-0.01, pattern, ',', '.', 1)).toBe('0.0');
|
||||
expect(formatNumber(-1e-10, pattern, ',', '.', 1)).toBe('0.0');
|
||||
expect(formatNumber(-0.0001, pattern, ',', '.', 3)).toBe('0.000');
|
||||
expect(formatNumber(-0.0000001, pattern, ',', '.', 6)).toBe('0.000000');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,10 +197,12 @@ describe('filters', function() {
|
||||
expect(number(1e-50, 0)).toEqual('0');
|
||||
expect(number(1e-6, 6)).toEqual('0.000001');
|
||||
expect(number(1e-7, 6)).toEqual('0.000000');
|
||||
expect(number(9e-7, 6)).toEqual('0.000001');
|
||||
|
||||
expect(number(-1e-50, 0)).toEqual('0');
|
||||
expect(number(-1e-6, 6)).toEqual('-0.000001');
|
||||
expect(number(-1e-7, 6)).toEqual('-0.000000');
|
||||
expect(number(-1e-7, 6)).toEqual('0.000000');
|
||||
expect(number(-1e-8, 9)).toEqual('-0.000000010');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,6 +210,9 @@ describe('filters', function() {
|
||||
it('should do basic filter', function() {
|
||||
expect(filter('json')({a:"b"})).toEqual(toJson({a:"b"}, true));
|
||||
});
|
||||
it('should allow custom indentation', function() {
|
||||
expect(filter('json')({a:"b"}, 4)).toEqual(toJson({a:"b"}, 4));
|
||||
});
|
||||
});
|
||||
|
||||
describe('lowercase', function() {
|
||||
|
||||
@@ -104,6 +104,27 @@ describe('Filter: orderBy', function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('should not reverse array of objects with no predicate', function() {
|
||||
var array = [
|
||||
{ id: 2 },
|
||||
{ id: 1 },
|
||||
{ id: 4 },
|
||||
{ id: 3 }
|
||||
];
|
||||
expect(orderBy(array)).toEqualData(array);
|
||||
});
|
||||
|
||||
|
||||
it('should not reverse array of objects with null prototype and no predicate', function() {
|
||||
var array = [2,1,4,3].map(function(id) {
|
||||
var obj = Object.create(null);
|
||||
obj.id = id;
|
||||
return obj;
|
||||
});
|
||||
expect(orderBy(array)).toEqualData(array);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -210,5 +231,26 @@ describe('Filter: orderBy', function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('should not reverse array of objects with no predicate', function() {
|
||||
var array = [
|
||||
{ id: 2 },
|
||||
{ id: 1 },
|
||||
{ id: 4 },
|
||||
{ id: 3 }
|
||||
];
|
||||
expect(orderBy(array)).toEqualData(array);
|
||||
});
|
||||
|
||||
|
||||
it('should not reverse array of objects with null prototype and no predicate', function() {
|
||||
var array = [2,1,4,3].map(function(id) {
|
||||
var obj = Object.create(null);
|
||||
obj.id = id;
|
||||
return obj;
|
||||
});
|
||||
expect(orderBy(array)).toEqualData(array);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1352,6 +1352,24 @@ describe('$http', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should not share the pending cached headers object instance', inject(function($rootScope) {
|
||||
var firstResult;
|
||||
callback.andCallFake(function(result) {
|
||||
expect(result.headers()).toEqual(firstResult.headers());
|
||||
expect(result.headers()).not.toBe(firstResult.headers());
|
||||
});
|
||||
|
||||
$httpBackend.expect('GET', '/url').respond(200, 'content', {'content-encoding': 'gzip', 'server': 'Apache'});
|
||||
$http({method: 'GET', url: '/url', cache: cache}).then(function(result) {
|
||||
firstResult = result;
|
||||
});
|
||||
$http({method: 'GET', url: '/url', cache: cache}).then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
|
||||
it('should cache status code as well', inject(function($rootScope) {
|
||||
doFirstCacheRequest('GET', 201);
|
||||
callback.andCallFake(function(r, status, h) {
|
||||
@@ -1381,6 +1399,40 @@ describe('$http', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should preserve config object when resolving from cache', function() {
|
||||
$httpBackend.expect('GET', '/url').respond(200, 'content');
|
||||
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'bar'}});
|
||||
$httpBackend.flush();
|
||||
|
||||
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'baz'}}).then(callback);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(callback.mostRecentCall.args[0].config.headers.foo).toBe('baz');
|
||||
});
|
||||
|
||||
|
||||
it('should preserve config object when resolving from pending cache', function() {
|
||||
$httpBackend.expect('GET', '/url').respond(200, 'content');
|
||||
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'bar'}});
|
||||
|
||||
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'baz'}}).then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback.mostRecentCall.args[0].config.headers.foo).toBe('baz');
|
||||
});
|
||||
|
||||
|
||||
it('should preserve config object when rejecting from pending cache', function() {
|
||||
$httpBackend.expect('GET', '/url').respond(404, 'content');
|
||||
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'bar'}});
|
||||
|
||||
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'baz'}}).catch(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback.mostRecentCall.args[0].config.headers.foo).toBe('baz');
|
||||
});
|
||||
|
||||
|
||||
it('should allow the cached value to be an empty string', function() {
|
||||
cache.put('/abc', '');
|
||||
|
||||
|
||||
@@ -58,6 +58,74 @@ describe('$interpolate', function() {
|
||||
expect($interpolate("Hello, world!{{bloop}}")()).toBe("Hello, world!");
|
||||
}));
|
||||
|
||||
describe('watching', function() {
|
||||
it('should be watchable with any input types', inject(function($interpolate, $rootScope) {
|
||||
var lastVal;
|
||||
$rootScope.$watch($interpolate('{{i}}'), function(val) {
|
||||
lastVal = val;
|
||||
});
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('');
|
||||
|
||||
$rootScope.i = null;
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('');
|
||||
|
||||
$rootScope.i = '';
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('');
|
||||
|
||||
$rootScope.i = 0;
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('0');
|
||||
|
||||
$rootScope.i = [0];
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('[0]');
|
||||
|
||||
$rootScope.i = {a: 1, b: 2};
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('{"a":1,"b":2}');
|
||||
}));
|
||||
|
||||
it('should be watchable with literal values', inject(function($interpolate, $rootScope) {
|
||||
var lastVal;
|
||||
$rootScope.$watch($interpolate('{{1}}{{"2"}}{{true}}{{[false]}}{{ {a: 2} }}'), function(val) {
|
||||
lastVal = val;
|
||||
});
|
||||
$rootScope.$apply();
|
||||
expect(lastVal).toBe('12true[false]{"a":2}');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should respect one-time bindings for each individual expression', inject(function($interpolate, $rootScope) {
|
||||
var calls = [];
|
||||
$rootScope.$watch($interpolate('{{::a | limitTo:1}} {{::s}} {{::i | number}}'), function(val) {
|
||||
calls.push(val);
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(calls.length).toBe(1);
|
||||
|
||||
$rootScope.a = [1];
|
||||
$rootScope.$apply();
|
||||
expect(calls.length).toBe(2);
|
||||
expect(calls[1]).toBe('[1] ');
|
||||
|
||||
$rootScope.a = [0];
|
||||
$rootScope.$apply();
|
||||
expect(calls.length).toBe(2);
|
||||
|
||||
$rootScope.i = $rootScope.a = 123;
|
||||
$rootScope.s = 'str!';
|
||||
$rootScope.$apply();
|
||||
expect(calls.length).toBe(3);
|
||||
expect(calls[2]).toBe('[1] str! 123');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('interpolation escaping', function() {
|
||||
var obj;
|
||||
|
||||
+30
-4
@@ -554,10 +554,24 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should throw error when invalid hashbang prefix given', function() {
|
||||
expect(function() {
|
||||
url.$$parse('http://www.server.org:1234/base#/path');
|
||||
}).toThrowMinErr('$location', 'ihshprfx', 'Invalid url "http://www.server.org:1234/base#/path", missing hash prefix "#!".');
|
||||
it('should insert default hashbang if a hash is given with no hashbang prefix', function() {
|
||||
|
||||
url.$$parse('http://www.server.org:1234/base#/path');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!#%2Fpath');
|
||||
expect(url.hash()).toBe('/path');
|
||||
expect(url.path()).toBe('');
|
||||
|
||||
url.$$parse('http://www.server.org:1234/base#');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base');
|
||||
expect(url.hash()).toBe('');
|
||||
expect(url.path()).toBe('');
|
||||
});
|
||||
|
||||
it('should ignore extra path segments if no hashbang is given', function() {
|
||||
url.$$parse('http://www.server.org:1234/base/extra/path');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base');
|
||||
expect(url.path()).toBe('');
|
||||
expect(url.hash()).toBe('');
|
||||
});
|
||||
|
||||
|
||||
@@ -647,6 +661,18 @@ describe('$location', function() {
|
||||
};
|
||||
}
|
||||
|
||||
describe('location watch', function() {
|
||||
beforeEach(initService({supportHistory: true}));
|
||||
beforeEach(inject(initBrowser({url:'http://new.com/a/b#'})));
|
||||
|
||||
it('should not update browser if only the empty hash fragment is cleared by updating the search', inject(function($rootScope, $browser, $location) {
|
||||
$browser.url('http://new.com/a/b#');
|
||||
var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough();
|
||||
$rootScope.$digest();
|
||||
expect($browserUrl).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('wiring', function() {
|
||||
|
||||
beforeEach(initService({html5Mode:false,hashPrefix: '!',supportHistory: true}));
|
||||
|
||||
+15
-2
@@ -276,6 +276,10 @@ describe('parser', function() {
|
||||
expect(scope.$eval("2>=1")).toEqual(2 >= 1);
|
||||
expect(scope.$eval("true==2<3")).toEqual(true == 2 < 3);
|
||||
expect(scope.$eval("true===2<3")).toEqual(true === 2 < 3);
|
||||
|
||||
expect(scope.$eval("true===3===3")).toEqual(true === 3 === 3);
|
||||
expect(scope.$eval("3===3===true")).toEqual(3 === 3 === true);
|
||||
expect(scope.$eval("3 >= 3 > 2")).toEqual(3 >= 3 > 2);
|
||||
});
|
||||
|
||||
it('should parse logical', function() {
|
||||
@@ -508,11 +512,18 @@ describe('parser', function() {
|
||||
});
|
||||
|
||||
it('should evaluate function call from a return value', function() {
|
||||
scope.val = 33;
|
||||
scope.getter = function() { return function() { return this.val; }; };
|
||||
scope.getter = function() { return function() { return 33; }; };
|
||||
expect(scope.$eval("getter()()")).toBe(33);
|
||||
});
|
||||
|
||||
// There is no "strict mode" in IE9
|
||||
if (!msie || msie > 9) {
|
||||
it('should set no context to functions returned by other functions', function() {
|
||||
scope.getter = function() { return function() { expect(this).toBeUndefined(); }; };
|
||||
scope.$eval("getter()()");
|
||||
});
|
||||
}
|
||||
|
||||
it('should evaluate multiplication and division', function() {
|
||||
scope.taxRate = 8;
|
||||
scope.subTotal = 100;
|
||||
@@ -696,6 +707,7 @@ describe('parser', function() {
|
||||
throw "IT SHOULD NOT HAVE RUN";
|
||||
};
|
||||
expect(scope.$eval('false && run()')).toBe(false);
|
||||
expect(scope.$eval('false && true && run()')).toBe(false);
|
||||
});
|
||||
|
||||
it('should short-circuit OR operator', function() {
|
||||
@@ -703,6 +715,7 @@ describe('parser', function() {
|
||||
throw "IT SHOULD NOT HAVE RUN";
|
||||
};
|
||||
expect(scope.$eval('true || run()')).toBe(true);
|
||||
expect(scope.$eval('true || false || run()')).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -64,9 +64,10 @@ describe('$sniffer', function() {
|
||||
|
||||
it('should claim that IE9 doesn\'t have support for "oninput"', function() {
|
||||
// IE9 implementation is fubared, so it's better to pretend that it doesn't have the support
|
||||
// IE10+ implementation is fubared when mixed with placeholders
|
||||
mockDivElement = {oninput: noop};
|
||||
|
||||
expect($sniffer.hasEvent('input')).toBe((msie == 9) ? false : true);
|
||||
expect($sniffer.hasEvent('input')).toBe(!(msie && msie <= 11));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,16 +16,24 @@ describe('$templateRequest', function() {
|
||||
expect(content).toBe('<div>abc</div>');
|
||||
}));
|
||||
|
||||
it('should cache the request using $templateCache to prevent extra downloads',
|
||||
inject(function($rootScope, $templateRequest, $templateCache) {
|
||||
it('should cache the request to prevent extra downloads',
|
||||
inject(function($rootScope, $templateRequest, $httpBackend) {
|
||||
|
||||
$templateCache.put('tpl.html', 'matias');
|
||||
$httpBackend.expectGET('tpl.html').respond('matias');
|
||||
|
||||
var content;
|
||||
$templateRequest('tpl.html').then(function(html) { content = html; });
|
||||
var content = [];
|
||||
function tplRequestCb(html) {
|
||||
content.push(html);
|
||||
}
|
||||
|
||||
$templateRequest('tpl.html').then(tplRequestCb);
|
||||
$httpBackend.flush();
|
||||
|
||||
$templateRequest('tpl.html').then(tplRequestCb);
|
||||
$rootScope.$digest();
|
||||
expect(content).toBe('matias');
|
||||
|
||||
expect(content[0]).toBe('matias');
|
||||
expect(content[1]).toBe('matias');
|
||||
}));
|
||||
|
||||
it('should throw an error when the template is not found',
|
||||
|
||||
@@ -5316,7 +5316,7 @@ describe("ngAnimate", function() {
|
||||
//jQuery doesn't handle SVG elements natively. Instead, an add-on library
|
||||
//is required which is called jquery.svg.js. Therefore, when jQuery is
|
||||
//active here there is no point to test this since it won't work by default.
|
||||
if (!$sniffer.transitions || !_jqLiteMode) return;
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
ss.addRule('circle.ng-enter', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
@@ -5336,13 +5336,13 @@ describe("ngAnimate", function() {
|
||||
|
||||
var child = element.find('circle');
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
expect(jqLiteHasClass(child[0], 'ng-enter')).toBe(true);
|
||||
expect(jqLiteHasClass(child[0], 'ng-enter-active')).toBe(true);
|
||||
|
||||
browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(false);
|
||||
expect(jqLiteHasClass(child[0], 'ng-enter')).toBe(false);
|
||||
expect(jqLiteHasClass(child[0], 'ng-enter-active')).toBe(false);
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -481,6 +481,54 @@ describe('$aria', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessible actions', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
var clickFn;
|
||||
|
||||
it('should a trigger click from the keyboard', function() {
|
||||
scope.someAction = function() {};
|
||||
compileInput('<div ng-click="someAction()" tabindex="0"></div>');
|
||||
clickFn = spyOn(scope, 'someAction');
|
||||
|
||||
element.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
|
||||
expect(clickFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not override existing ng-keypress', function() {
|
||||
scope.someOtherAction = function() {};
|
||||
var keypressFn = spyOn(scope, 'someOtherAction');
|
||||
|
||||
scope.someAction = function() {};
|
||||
clickFn = spyOn(scope, 'someAction');
|
||||
compileInput('<div ng-click="someAction()" ng-keypress="someOtherAction()" tabindex="0"></div>');
|
||||
|
||||
element.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
|
||||
expect(clickFn).not.toHaveBeenCalled();
|
||||
expect(keypressFn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions when bindKeypress set to false', function() {
|
||||
beforeEach(configAriaProvider({
|
||||
bindKeypress: false
|
||||
}));
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should not a trigger click from the keyboard', function() {
|
||||
scope.someAction = function() {};
|
||||
var clickFn = spyOn(scope, 'someAction');
|
||||
|
||||
element = $compile('<div ng-click="someAction()" tabindex="0">></div>')(scope);
|
||||
|
||||
element.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
|
||||
expect(clickFn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('tabindex when disabled', function() {
|
||||
beforeEach(configAriaProvider({
|
||||
tabindex: false
|
||||
|
||||
Vendored
+12
@@ -1310,6 +1310,18 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should abort requests when timeout passed as a numeric value', inject(function($timeout) {
|
||||
hb.expect('GET', '/url1').respond(200);
|
||||
|
||||
hb('GET', '/url1', null, callback, null, 200);
|
||||
$timeout.flush(300);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith(-1, undefined, '');
|
||||
hb.verifyNoOutstandingExpectation();
|
||||
hb.verifyNoOutstandingRequest();
|
||||
}));
|
||||
|
||||
|
||||
it('should throw an exception if no response defined', function() {
|
||||
hb.when('GET', '/test');
|
||||
expect(function() {
|
||||
|
||||
@@ -10,14 +10,20 @@ describe('linky', function() {
|
||||
}));
|
||||
|
||||
it('should do basic filter', function() {
|
||||
expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c")).
|
||||
expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c “http://example.com” ‘http://me.com’")).
|
||||
toEqual('<a href="http://ab/">http://ab/</a> ' +
|
||||
'(<a href="http://a/">http://a/</a>) ' +
|
||||
'<<a href="http://a/">http://a/</a>> ' +
|
||||
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c');
|
||||
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c ' +
|
||||
'“<a href="http://example.com">http://example.com</a>” ' +
|
||||
'‘<a href="http://me.com">http://me.com</a>’');
|
||||
expect(linky(undefined)).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle www.', function() {
|
||||
expect(linky('www.example.com')).toEqual('<a href="http://www.example.com">www.example.com</a>');
|
||||
});
|
||||
|
||||
it('should handle mailto:', function() {
|
||||
expect(linky("mailto:me@example.com")).
|
||||
toEqual('<a href="mailto:me@example.com">me@example.com</a>');
|
||||
|
||||
Reference in New Issue
Block a user