Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0681a5400e | |||
| d5683d2116 | |||
| df24410c17 | |||
| e6d053de09 | |||
| e001400237 | |||
| 1002b80a6f | |||
| 7bb01bae72 | |||
| f7e9ff1aba | |||
| f7b999703f | |||
| b2ae35cd2c | |||
| 64d05180a6 | |||
| b5a9053ba3 | |||
| db20b830fc | |||
| 2aacc2d622 | |||
| bab474aa8b | |||
| f2c94c61d1 | |||
| 2420a0a77e | |||
| 9711e3e10e | |||
| ae826b007c | |||
| 2ea23e0685 | |||
| f1663088c3 | |||
| 84daf9752a | |||
| 426a5ac054 | |||
| 6874cca158 | |||
| 34c1a68fa8 | |||
| 1268b17bc1 | |||
| ae98dadf6d | |||
| a2a684fe24 | |||
| 40e00cdf34 | |||
| dfa722a8a6 | |||
| cc961888cd | |||
| 69f4d0ff70 | |||
| 7a04968673 | |||
| f2f9843ea8 | |||
| 7d57961b63 | |||
| 477e4047f7 | |||
| b35e744791 | |||
| d83bddcb79 | |||
| 5db6709f8d | |||
| f3b393258e | |||
| 47f9fc3e70 | |||
| 74eb17d7c8 | |||
| c075126c2e | |||
| 6c632d9cb0 | |||
| feeea8a1c8 | |||
| d20de4abe7 | |||
| 071b1bc790 | |||
| 4089f538c3 | |||
| 03d4bbc16f | |||
| 6d07005b18 | |||
| 266bc6520b | |||
| f0dd7c0374 | |||
| f0b88e047e | |||
| ef2435d176 | |||
| 95521876eb | |||
| 4eb16ae4b7 |
+3
-3
@@ -1,5 +1,5 @@
|
||||
<a name="v1.4.0-rc.1"></a>
|
||||
# v1.4.0-rc.1 Sartorial Chronography (2015-04-24)
|
||||
# v1.4.0-rc.1 sartorial-chronography (2015-04-24)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -4877,7 +4877,7 @@ For more info: http://blog.angularjs.org/2013/12/angularjs-13-new-release-approa
|
||||
- properly toggle multiple classes
|
||||
([4e73c80b](https://github.com/angular/angular.js/commit/4e73c80b17bd237a8491782bcf9e19f1889e12ed),
|
||||
[#4467](https://github.com/angular/angular.js/issues/4467), [#6448](https://github.com/angular/angular.js/issues/6448))
|
||||
- make jqLite('<iframe src="someurl">').contents() return iframe document, as in jQuery
|
||||
- make `jqLite(<iframe src="someurl">').contents()` return iframe document, as in jQuery
|
||||
([05fbed57](https://github.com/angular/angular.js/commit/05fbed5710b702c111c1425a9e241c40d13b0a54),
|
||||
[#6320](https://github.com/angular/angular.js/issues/6320), [#6323](https://github.com/angular/angular.js/issues/6323))
|
||||
- **numberFilter:** convert all non-finite/non-numbers/non-numeric strings to the empty string
|
||||
@@ -9253,7 +9253,7 @@ with the `$route` service
|
||||
mocks now part of `angular-mocks.js` (commit f5d08963)
|
||||
|
||||
### Bug Fixes
|
||||
- <select> (one/multiple) could not chose from a list of objects (commit 347be5ae)
|
||||
- `<select>` (one/multiple) could not chose from a list of objects (commit 347be5ae)
|
||||
- null and other falsy values should not be rendered in the view (issue #242)
|
||||
|
||||
### Docs
|
||||
|
||||
+1
-1
@@ -14,6 +14,6 @@ ng\:form {
|
||||
visibility:hidden;
|
||||
}
|
||||
|
||||
.ng-animate-anchor {
|
||||
.ng-anchor {
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
@@ -583,6 +583,12 @@ ul.events > li {
|
||||
margin-bottom:40px;
|
||||
}
|
||||
|
||||
.definition-table td {
|
||||
padding: 8px;
|
||||
border: 1px solid #eee;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 769px) and (max-width: 991px) {
|
||||
.main-body-grid {
|
||||
margin-top: 160px;
|
||||
|
||||
@@ -6,7 +6,7 @@ var packagePath = __dirname;
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
// Create and export a new Dgeni package called angularjs. This package depends upon
|
||||
// the ngdoc,nunjucks and examples packages defined in the dgeni-packages npm module.
|
||||
// the ngdoc, nunjucks, and examples packages defined in the dgeni-packages npm module.
|
||||
module.exports = new Package('angularjs', [
|
||||
require('dgeni-packages/ngdoc'),
|
||||
require('dgeni-packages/nunjucks'),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="Description"
|
||||
content="AngularJS is what HTML would have been, had it been designed for building web-apps.
|
||||
Declarative templates with data-binding, MVC, dependency injection and great
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{% macro typeList(types) -%}
|
||||
{% for typeName in types %}<a href="" class="{$ typeName | typeClass $}">{$ typeName | escape $}</a>{% endfor %}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro paramTable(params) %}
|
||||
<table class="variables-matrix input-arguments">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Param</th>
|
||||
<th>Type</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for param in params %}
|
||||
<tr>
|
||||
<td>
|
||||
{$ param.name $}
|
||||
{% if param.alias %}| {$ param.alias $}{% endif %}
|
||||
{% if param.optional %}<div><em>(optional)</em></div>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{$ typeList(param.typeList) $}
|
||||
</td>
|
||||
<td>
|
||||
{$ param.description | marked $}
|
||||
{% if param.defaultValue %}<p><em>(default: {$ param.defaultValue $})</em></p>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro -%}
|
||||
|
||||
|
||||
{%- macro directiveParam(name, type, join, sep) %}
|
||||
{%- if type.optional %}[{% endif -%}
|
||||
{$ name | dashCase $}{$ join $}{$ type.name $}{$ sep $}
|
||||
{%- if type.optional %}]{% endif -%}
|
||||
{% endmacro -%}
|
||||
|
||||
{%- macro functionSyntax(fn) %}
|
||||
{%- set sep = joiner(', ') -%}
|
||||
{% marked -%}
|
||||
`{$ fn.name $}({%- for param in fn.params %}{$ sep() $}
|
||||
{%- if param.type.optional %}[{% endif -%}
|
||||
{$ param.name $}
|
||||
{%- if param.type.optional %}]{% endif -%}
|
||||
{% endfor %});`
|
||||
{%- endmarked %}
|
||||
{% endmacro -%}
|
||||
|
||||
{%- macro typeInfo(fn) -%}
|
||||
<table class="variables-matrix return-arguments">
|
||||
<tr>
|
||||
<td>{$ typeList(fn.typeList) $}</td>
|
||||
<td>{$ fn.description | marked $}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{%- endmacro -%}
|
||||
@@ -5,4 +5,4 @@
|
||||
|
||||
This error occurs when the name of a directive is not valid.
|
||||
|
||||
Directives must start with a lowercase character.
|
||||
Directives must start with a lowercase character and must not contain leading or trailing whitespaces.
|
||||
|
||||
@@ -36,9 +36,25 @@ Following are invalid uses of this directive:
|
||||
```
|
||||
|
||||
|
||||
To resolve this error, always use path expressions with scope properties that are two-way data-bound:
|
||||
To resolve this error, do one of the following options:
|
||||
|
||||
- use path expressions with scope properties that are two-way data-bound like so:
|
||||
|
||||
```
|
||||
<my-directive bind="some.property">
|
||||
<my-directive bind="some[3]['property']">
|
||||
```
|
||||
|
||||
- Make the binding optional
|
||||
|
||||
```
|
||||
myModule.directive('myDirective', function factory() {
|
||||
return {
|
||||
...
|
||||
scope: {
|
||||
localValue: '=?bind' // <-- the '?' makes it optional
|
||||
}
|
||||
...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -43,7 +43,7 @@ Currently, ngAria interfaces with the following directives:
|
||||
|
||||
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
has a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
|
||||
attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
@@ -19,7 +19,7 @@ Angular supports i18n/l10n for {@link ng.filter:date date}, {@link ng.filter:num
|
||||
{@link ng.filter:currency currency} filters.
|
||||
|
||||
Localizable pluralization is supported via the {@link ng.directive:ngPluralize `ngPluralize`
|
||||
directive}. Additionally, you can use <a href="#MessageFormat">MessageFormat extensions</a> to
|
||||
directive}. Additionally, you can use {@link guide/i18n#messageformat-extensions MessageFormat extensions} to
|
||||
`$interpolate` for localizable pluralization and gender support in all interpolations via the
|
||||
`ngMessageFormat` module.
|
||||
|
||||
@@ -142,7 +142,7 @@ displaying the date with a timezone specified by the developer.
|
||||
|
||||
|
||||
<a name="MessageFormat"></a>
|
||||
## MessageFormat extensions
|
||||
## MessageFormat extensions
|
||||
|
||||
You can write localizable plural and gender based messages in Angular interpolation expressions and
|
||||
`$interpolate` calls.
|
||||
|
||||
@@ -29,7 +29,7 @@ to render error messages with ngMessages that are listed with a directive such a
|
||||
involves pulling error message data from a server and then displaying that data via the mechanics of ngMessages. Be
|
||||
sure to read the breaking change involved with `ngMessagesInclude` to upgrade your template code.
|
||||
|
||||
Other changes, such as the ordering of elements with ngRepeat and ngOptions, may also effect the behavior of your
|
||||
Other changes, such as the ordering of elements with ngRepeat and ngOptions, may also affect the behavior of your
|
||||
application. And be sure to also read up on the changes to `$cookies`. The migration jump from 1.3 to 1.4 should be
|
||||
relatively straightforward otherwise.
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
|
||||
3. **Model mutation**
|
||||
|
||||
For mutations to be properly observed, you should make them only within the {@link
|
||||
ng.$rootScope.Scope#$apply scope.$apply()}. (Angular APIs do this
|
||||
ng.$rootScope.Scope#$apply scope.$apply()}. Angular APIs do this
|
||||
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers,
|
||||
or asynchronous work with {@link ng.$http $http}, {@link ng.$timeout $timeout}
|
||||
or {@link ng.$interval $interval} services.
|
||||
|
||||
@@ -260,6 +260,11 @@ myModule.filter('length', function() {
|
||||
});
|
||||
|
||||
describe('length filter', function() {
|
||||
|
||||
beforeEach(inject(function(_$filter_){
|
||||
$filter= _$filter_;
|
||||
}));
|
||||
|
||||
it('returns 0 when given null', function() {
|
||||
var length = $filter('length');
|
||||
expect(length(null)).toEqual(0);
|
||||
|
||||
@@ -153,7 +153,7 @@ grunt test:unit --browsers Opera,Firefox
|
||||
|
||||
Note there should be _no spaces between browsers_. `Opera, Firefox` is INVALID.
|
||||
|
||||
During development it's however more productive to continuously run unit tests every time the source or test files
|
||||
During development, however, it's more productive to continuously run unit tests every time the source or test files
|
||||
change. To execute tests in this mode run:
|
||||
|
||||
1. To start the Karma server, capture Chrome browser and run unit tests, run:
|
||||
|
||||
+5
-1
@@ -190,7 +190,7 @@ module.exports = {
|
||||
shell.exec(
|
||||
'java ' +
|
||||
this.java32flags() + ' ' +
|
||||
'-Xmx2g ' +
|
||||
this.memoryRequirement() + ' ' +
|
||||
'-cp bower_components/closure-compiler/compiler.jar' + classPathSep +
|
||||
'bower_components/ng-closure-runner/ngcompiler.jar ' +
|
||||
'org.angularjs.closurerunner.NgClosureRunner ' +
|
||||
@@ -217,6 +217,10 @@ module.exports = {
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
memoryRequirement: function() {
|
||||
return (process.platform === 'win32') ? '' : '-Xmx2g';
|
||||
},
|
||||
|
||||
|
||||
//returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster
|
||||
java32flags: function(){
|
||||
|
||||
+65
-19
@@ -36,6 +36,7 @@
|
||||
isUndefined: true,
|
||||
isDefined: true,
|
||||
isObject: true,
|
||||
isBlankObject: true,
|
||||
isString: true,
|
||||
isNumber: true,
|
||||
isDate: true,
|
||||
@@ -175,6 +176,7 @@ var
|
||||
splice = [].splice,
|
||||
push = [].push,
|
||||
toString = Object.prototype.toString,
|
||||
getPrototypeOf = Object.getPrototypeOf,
|
||||
ngMinErr = minErr('ng'),
|
||||
|
||||
/** @name angular */
|
||||
@@ -200,7 +202,9 @@ function isArrayLike(obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = obj.length;
|
||||
// Support: iOS 8.2 (not reproducible in simulator)
|
||||
// "length" in obj used to prevent JIT error (gh-11508)
|
||||
var length = "length" in Object(obj) && obj.length;
|
||||
|
||||
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
|
||||
return true;
|
||||
@@ -265,12 +269,25 @@ function forEach(obj, iterator, context) {
|
||||
}
|
||||
} else if (obj.forEach && obj.forEach !== forEach) {
|
||||
obj.forEach(iterator, context, obj);
|
||||
} else {
|
||||
} else if (isBlankObject(obj)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in obj) {
|
||||
iterator.call(context, obj[key], key, obj);
|
||||
}
|
||||
} else if (typeof obj.hasOwnProperty === 'function') {
|
||||
// Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
|
||||
for (key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
iterator.call(context, obj[key], key, obj);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Slow path for objects which do not have a method `hasOwnProperty`
|
||||
for (key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) {
|
||||
iterator.call(context, obj[key], key, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
@@ -496,6 +513,16 @@ function isObject(value) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a value is an object with a null prototype
|
||||
*
|
||||
* @returns {boolean} True if `value` is an `Object` with a null prototype
|
||||
*/
|
||||
function isBlankObject(value) {
|
||||
return value !== null && typeof value === 'object' && !getPrototypeOf(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.isString
|
||||
@@ -779,7 +806,7 @@ function copy(source, destination, stackSource, stackDest) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isObject(source)) {
|
||||
var emptyObject = Object.create(Object.getPrototypeOf(source));
|
||||
var emptyObject = Object.create(getPrototypeOf(source));
|
||||
destination = copy(source, emptyObject, stackSource, stackDest);
|
||||
}
|
||||
}
|
||||
@@ -798,7 +825,7 @@ function copy(source, destination, stackSource, stackDest) {
|
||||
stackDest.push(destination);
|
||||
}
|
||||
|
||||
var result;
|
||||
var result, key;
|
||||
if (isArray(source)) {
|
||||
destination.length = 0;
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
@@ -818,21 +845,40 @@ function copy(source, destination, stackSource, stackDest) {
|
||||
delete destination[key];
|
||||
});
|
||||
}
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
result = copy(source[key], null, stackSource, stackDest);
|
||||
if (isObject(source[key])) {
|
||||
stackSource.push(source[key]);
|
||||
stackDest.push(result);
|
||||
if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
putValue(key, source[key], destination, stackSource, stackDest);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
putValue(key, source[key], destination, stackSource, stackDest);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
putValue(key, source[key], destination, stackSource, stackDest);
|
||||
}
|
||||
destination[key] = result;
|
||||
}
|
||||
}
|
||||
setHashKey(destination,h);
|
||||
}
|
||||
|
||||
}
|
||||
return destination;
|
||||
|
||||
function putValue(key, val, destination, stackSource, stackDest) {
|
||||
// No context allocation, trivial outer scope, easily inlined
|
||||
var result = copy(val, null, stackSource, stackDest);
|
||||
if (isObject(val)) {
|
||||
stackSource.push(val);
|
||||
stackDest.push(result);
|
||||
}
|
||||
destination[key] = result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -913,14 +959,14 @@ function equals(o1, o2) {
|
||||
} else {
|
||||
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
|
||||
isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
|
||||
keySet = {};
|
||||
keySet = createMap();
|
||||
for (key in o1) {
|
||||
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
|
||||
if (!equals(o1[key], o2[key])) return false;
|
||||
keySet[key] = true;
|
||||
}
|
||||
for (key in o2) {
|
||||
if (!keySet.hasOwnProperty(key) &&
|
||||
if (!(key in keySet) &&
|
||||
key.charAt(0) !== '$' &&
|
||||
o2[key] !== undefined &&
|
||||
!isFunction(o2[key])) return false;
|
||||
@@ -957,17 +1003,17 @@ var csp = function() {
|
||||
* @name ngJq
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string=} the name of the library available under `window`
|
||||
* @param {string=} ngJq the name of the library available under `window`
|
||||
* to be used for angular.element
|
||||
* @description
|
||||
* Use this directive to force the angular.element library. This should be
|
||||
* used to force either jqLite by leaving ng-jq blank or setting the name of
|
||||
* the jquery variable under window (eg. jQuery).
|
||||
*
|
||||
* Since this directive is global for the angular library, it is recommended
|
||||
* that it's added to the same element as ng-app or the HTML element, but it is not mandatory.
|
||||
* It needs to be noted that only the first instance of `ng-jq` will be used and all others
|
||||
* ignored.
|
||||
* Since angular looks for this directive when it is loaded (doesn't wait for the
|
||||
* DOMContentLoaded event), it must be placed on an element that comes before the script
|
||||
* which loads angular. Also, only the first instance of `ng-jq` will be used and all
|
||||
* others ignored.
|
||||
*
|
||||
* @example
|
||||
* This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
|
||||
|
||||
@@ -179,7 +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.
|
||||
* @param {string=} caller An optional string to provide the origin of the function call for error messages.
|
||||
* @return {*} The instance.
|
||||
*/
|
||||
|
||||
@@ -190,8 +190,8 @@ function annotate(fn, strictDi, name) {
|
||||
* @description
|
||||
* Invoke the method and supply the method arguments from the `$injector`.
|
||||
*
|
||||
* @param {!Function} fn The function to invoke. Function parameters are injected according to the
|
||||
* {@link guide/di $inject Annotation} rules.
|
||||
* @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
|
||||
* injected according to the {@link guide/di $inject Annotation} rules.
|
||||
* @param {Object=} self The `this` for the invoked method.
|
||||
* @param {Object=} locals Optional object. If preset then any argument names are read from this
|
||||
* object first, before the `$injector` is consulted.
|
||||
@@ -458,8 +458,8 @@ function annotate(fn, strictDi, name) {
|
||||
* configure your service in a provider.
|
||||
*
|
||||
* @param {string} name The name of the instance.
|
||||
* @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
|
||||
* for `$provide.provider(name, {$get: $getFn})`.
|
||||
* @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
|
||||
* Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
|
||||
* @returns {Object} registered provider instance
|
||||
*
|
||||
* @example
|
||||
@@ -494,7 +494,8 @@ function annotate(fn, strictDi, name) {
|
||||
* as a type/class.
|
||||
*
|
||||
* @param {string} name The name of the instance.
|
||||
* @param {Function} constructor A class (constructor function) that will be instantiated.
|
||||
* @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
|
||||
* that will be instantiated.
|
||||
* @returns {Object} registered provider instance
|
||||
*
|
||||
* @example
|
||||
@@ -593,7 +594,7 @@ function annotate(fn, strictDi, name) {
|
||||
* object which replaces or wraps and delegates to the original service.
|
||||
*
|
||||
* @param {string} name The name of the service to decorate.
|
||||
* @param {function()} decorator This function will be invoked when the service needs to be
|
||||
* @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance. The function is called using
|
||||
* the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
|
||||
* Local injection arguments:
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.</div>
|
||||
*
|
||||
* To use jQuery, simply load it before `DOMContentLoaded` event fired.
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
|
||||
*
|
||||
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
|
||||
* jqLite; they are never raw DOM references.</div>
|
||||
@@ -55,7 +55,7 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`detach()`](http://api.jquery.com/detach/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
|
||||
+11
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
var $animateMinErr = minErr('$animate');
|
||||
var ELEMENT_NODE = 1;
|
||||
var NG_ANIMATE_CLASSNAME = 'ng-animate';
|
||||
|
||||
function mergeClasses(a,b) {
|
||||
if (!a && !b) return '';
|
||||
@@ -26,7 +27,9 @@ function splitClasses(classes) {
|
||||
classes = classes.split(' ');
|
||||
}
|
||||
|
||||
var obj = {};
|
||||
// Use createMap() to prevent class assumptions involving property names in
|
||||
// Object.prototype
|
||||
var obj = createMap();
|
||||
forEach(classes, function(klass) {
|
||||
// sometimes the split leaves empty string values
|
||||
// incase extra spaces were applied to the options
|
||||
@@ -231,6 +234,13 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
this.classNameFilter = function(expression) {
|
||||
if (arguments.length === 1) {
|
||||
this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
|
||||
if (this.$$classNameFilter) {
|
||||
var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
|
||||
if (reservedRegex.test(this.$$classNameFilter.toString())) {
|
||||
throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.$$classNameFilter;
|
||||
};
|
||||
|
||||
@@ -802,6 +802,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!letter || letter !== lowercase(letter)) {
|
||||
throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
|
||||
}
|
||||
if (name !== name.trim()) {
|
||||
throw $compileMinErr('baddir',
|
||||
"Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -415,11 +415,11 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
<form name="myForm" ng-controller="FormController" class="my-form">
|
||||
userType: <input name="input" ng-model="userType" required>
|
||||
<span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
|
||||
<tt>userType = {{userType}}</tt><br>
|
||||
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
|
||||
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
|
||||
<tt>myForm.$valid = {{myForm.$valid}}</tt><br>
|
||||
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
|
||||
<code>userType = {{userType}}</code><br>
|
||||
<code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
|
||||
<code>myForm.input.$error = {{myForm.input.$error}}</code><br>
|
||||
<code>myForm.$valid = {{myForm.$valid}}</code><br>
|
||||
<code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
|
||||
</form>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
|
||||
@@ -39,7 +39,9 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
function digestClassCounts(classes, count) {
|
||||
var classCounts = element.data('$classCounts') || {};
|
||||
// Use createMap() to prevent class assumptions involving property
|
||||
// names in Object.prototype
|
||||
var classCounts = element.data('$classCounts') || createMap();
|
||||
var classesToUpdate = [];
|
||||
forEach(classes, function(className) {
|
||||
if (count > 0 || classCounts[className]) {
|
||||
|
||||
@@ -33,17 +33,13 @@
|
||||
* document; alternatively, the css rule above must be included in the external stylesheet of the
|
||||
* application.
|
||||
*
|
||||
* Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
|
||||
* cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
|
||||
* class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below.
|
||||
*
|
||||
* @element ANY
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<div id="template1" ng-cloak>{{ 'hello' }}</div>
|
||||
<div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
|
||||
<div id="template2" class="ng-cloak">{{ 'world' }}</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should remove the template directive and css class', function() {
|
||||
|
||||
@@ -504,7 +504,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* If the validity changes to invalid, the model will be set to `undefined`,
|
||||
* unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
|
||||
* If the validity changes to valid, it will set the model to the last available valid
|
||||
* modelValue, i.e. either the last parsed value or the last value set from the scope.
|
||||
* `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
|
||||
*/
|
||||
this.$validate = function() {
|
||||
// ignore $validate before model is initialized
|
||||
@@ -996,10 +996,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
var _name = 'Brian';
|
||||
$scope.user = {
|
||||
name: function(newName) {
|
||||
if (angular.isDefined(newName)) {
|
||||
_name = newName;
|
||||
}
|
||||
return _name;
|
||||
// Note that newName can be undefined for two reasons:
|
||||
// 1. Because it is called as a getter and thus called with no arguments
|
||||
// 2. Because the property should actually be set to undefined. This happens e.g. if the
|
||||
// input is invalid
|
||||
return arguments.length ? (_name = newName) : _name;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@@ -1217,7 +1218,11 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
var _name = 'Brian';
|
||||
$scope.user = {
|
||||
name: function(newName) {
|
||||
return angular.isDefined(newName) ? (_name = newName) : _name;
|
||||
// Note that newName can be undefined for two reasons:
|
||||
// 1. Because it is called as a getter and thus called with no arguments
|
||||
// 2. Because the property should actually be set to undefined. This happens e.g. if the
|
||||
// input is invalid
|
||||
return arguments.length ? (_name = newName) : _name;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
+107
-59
@@ -31,11 +31,21 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by`
|
||||
* in an `ngOptions` expression, however, deep equality checks will be performed.
|
||||
* </div>
|
||||
* ## Complex Models (objects or collections)
|
||||
*
|
||||
* **Note:** By default, `ngModel` watches the model by reference, not value. This is important when
|
||||
* binding any input directive to a model that is an object or a collection.
|
||||
*
|
||||
* Since this is a common situation for `ngOptions` the directive additionally watches the model using
|
||||
* `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in
|
||||
* the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual
|
||||
* object/collection has not changed identity but only a property on the object or an item in the collection
|
||||
* changes.
|
||||
*
|
||||
* Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
|
||||
* if the model is an array). This means that changing a property deeper inside the object/collection that the
|
||||
* first level will not trigger a re-rendering.
|
||||
*
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
@@ -251,9 +261,13 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// Get the value by which we are going to track the option
|
||||
// if we have a trackFn then use that (passing scope and locals)
|
||||
// otherwise just hash the given viewValue
|
||||
var getTrackByValue = trackBy ?
|
||||
function(viewValue, locals) { return trackByFn(scope, locals); } :
|
||||
function getHashOfValue(viewValue) { return hashKey(viewValue); };
|
||||
var getTrackByValueFn = trackBy ?
|
||||
function(value, locals) { return trackByFn(scope, locals); } :
|
||||
function getHashOfValue(value) { return hashKey(value); };
|
||||
var getTrackByValue = function(value, key) {
|
||||
return getTrackByValueFn(value, getLocals(value, key));
|
||||
};
|
||||
|
||||
var displayFn = $parse(match[2] || match[1]);
|
||||
var groupByFn = $parse(match[3] || '');
|
||||
var disableWhenFn = $parse(match[4] || '');
|
||||
@@ -280,6 +294,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
return {
|
||||
trackBy: trackBy,
|
||||
getTrackByValue: getTrackByValue,
|
||||
getWatchables: $parse(valuesFn, function(values) {
|
||||
// Create a collection of things that we would like to watch (watchedArray)
|
||||
// so that they can all be watched using a single $watchCollection
|
||||
@@ -289,11 +304,11 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
Object.keys(values).forEach(function getWatchable(key) {
|
||||
var locals = getLocals(values[key], key);
|
||||
var selectValue = getTrackByValue(values[key], locals);
|
||||
var selectValue = getTrackByValueFn(values[key], locals);
|
||||
watchedArray.push(selectValue);
|
||||
|
||||
// Only need to watch the displayFn if there is a specific label expression
|
||||
if (match[2]) {
|
||||
if (match[2] || match[1]) {
|
||||
var label = displayFn(scope, locals);
|
||||
watchedArray.push(label);
|
||||
}
|
||||
@@ -315,17 +330,29 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// The option values were already computed in the `getWatchables` fn,
|
||||
// which must have been called to trigger `getOptions`
|
||||
var optionValues = valuesFn(scope) || [];
|
||||
var optionValuesKeys;
|
||||
|
||||
var keys = Object.keys(optionValues);
|
||||
keys.forEach(function getOption(key) {
|
||||
|
||||
// Ignore "angular" properties that start with $ or $$
|
||||
if (key.charAt(0) === '$') return;
|
||||
if (!keyName && isArrayLike(optionValues)) {
|
||||
optionValuesKeys = optionValues;
|
||||
} else {
|
||||
// if object, extract keys, in enumeration order, unsorted
|
||||
optionValuesKeys = [];
|
||||
for (var itemKey in optionValues) {
|
||||
if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
|
||||
optionValuesKeys.push(itemKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var optionValuesLength = optionValuesKeys.length;
|
||||
|
||||
for (var index = 0; index < optionValuesLength; index++) {
|
||||
var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
|
||||
var value = optionValues[key];
|
||||
var locals = getLocals(value, key);
|
||||
var viewValue = viewValueFn(scope, locals);
|
||||
var selectValue = getTrackByValue(viewValue, locals);
|
||||
var selectValue = getTrackByValueFn(viewValue, locals);
|
||||
var label = displayFn(scope, locals);
|
||||
var group = groupByFn(scope, locals);
|
||||
var disabled = disableWhenFn(scope, locals);
|
||||
@@ -333,13 +360,13 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
optionItems.push(optionItem);
|
||||
selectValueMap[selectValue] = optionItem;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items: optionItems,
|
||||
selectValueMap: selectValueMap,
|
||||
getOptionFromViewValue: function(value) {
|
||||
return selectValueMap[getTrackByValue(value, getLocals(value))];
|
||||
return selectValueMap[getTrackByValue(value)];
|
||||
},
|
||||
getViewValueFromOption: function(option) {
|
||||
// If the viewValue could be an object that may be mutated by the application,
|
||||
@@ -417,44 +444,54 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
||||
var option = options.getOptionFromViewValue(value);
|
||||
|
||||
if (option && !option.disabled) {
|
||||
if (selectElement[0].value !== option.selectValue) {
|
||||
removeUnknownOption();
|
||||
removeEmptyOption();
|
||||
|
||||
selectElement[0].value = option.selectValue;
|
||||
option.element.selected = true;
|
||||
option.element.setAttribute('selected', 'selected');
|
||||
}
|
||||
} else {
|
||||
if (value === null || providedEmptyOption) {
|
||||
removeUnknownOption();
|
||||
renderEmptyOption();
|
||||
} else {
|
||||
removeEmptyOption();
|
||||
renderUnknownOption();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectCtrl.readValue = function readNgOptionsValue() {
|
||||
|
||||
var selectedOption = options.selectValueMap[selectElement.val()];
|
||||
|
||||
if (selectedOption && !selectedOption.disabled) {
|
||||
removeEmptyOption();
|
||||
removeUnknownOption();
|
||||
return options.getViewValueFromOption(selectedOption);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
// Update the controller methods for multiple selectable options
|
||||
if (multiple) {
|
||||
if (!multiple) {
|
||||
|
||||
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
||||
var option = options.getOptionFromViewValue(value);
|
||||
|
||||
if (option && !option.disabled) {
|
||||
if (selectElement[0].value !== option.selectValue) {
|
||||
removeUnknownOption();
|
||||
removeEmptyOption();
|
||||
|
||||
selectElement[0].value = option.selectValue;
|
||||
option.element.selected = true;
|
||||
option.element.setAttribute('selected', 'selected');
|
||||
}
|
||||
} else {
|
||||
if (value === null || providedEmptyOption) {
|
||||
removeUnknownOption();
|
||||
renderEmptyOption();
|
||||
} else {
|
||||
removeEmptyOption();
|
||||
renderUnknownOption();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectCtrl.readValue = function readNgOptionsValue() {
|
||||
|
||||
var selectedOption = options.selectValueMap[selectElement.val()];
|
||||
|
||||
if (selectedOption && !selectedOption.disabled) {
|
||||
removeEmptyOption();
|
||||
removeUnknownOption();
|
||||
return options.getViewValueFromOption(selectedOption);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// If we are using `track by` then we must watch the tracked value on the model
|
||||
// since ngModel only watches for object identity change
|
||||
if (ngOptions.trackBy) {
|
||||
scope.$watch(
|
||||
function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
|
||||
function() { ngModelCtrl.$render(); }
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
@@ -486,6 +523,22 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
return selections;
|
||||
};
|
||||
|
||||
// If we are using `track by` then we must watch these tracked values on the model
|
||||
// since ngModel only watches for object identity change
|
||||
if (ngOptions.trackBy) {
|
||||
|
||||
scope.$watchCollection(function() {
|
||||
if (isArray(ngModelCtrl.$viewValue)) {
|
||||
return ngModelCtrl.$viewValue.map(function(value) {
|
||||
return ngOptions.getTrackByValue(value);
|
||||
});
|
||||
}
|
||||
}, function() {
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -512,11 +565,6 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// We will re-render the option elements if the option values or labels change
|
||||
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
|
||||
|
||||
// We also need to watch to see if the internals of the model changes, since
|
||||
// ngModel only watches for object identity change
|
||||
if (ngOptions.trackBy) {
|
||||
scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true);
|
||||
}
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
*
|
||||
* @scope
|
||||
* @priority 1200
|
||||
* @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
|
||||
* @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
|
||||
* On child elements add:
|
||||
*
|
||||
* * `ngSwitchWhen`: the case statement to match against. If match then this
|
||||
@@ -60,7 +60,7 @@
|
||||
<div ng-controller="ExampleController">
|
||||
<select ng-model="selection" ng-options="item for item in items">
|
||||
</select>
|
||||
<tt>selection={{selection}}</tt>
|
||||
<code>selection={{selection}}</code>
|
||||
<hr/>
|
||||
<div class="animate-switch-container"
|
||||
ng-switch on="selection">
|
||||
|
||||
@@ -127,12 +127,6 @@ var SelectController =
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by`
|
||||
* in an `ngOptions` expression, however, deep equality checks will be performed.
|
||||
* </div>
|
||||
*
|
||||
*/
|
||||
var selectDirective = function() {
|
||||
|
||||
|
||||
+45
-27
@@ -9,34 +9,14 @@ var JSON_ENDS = {
|
||||
};
|
||||
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
||||
|
||||
function paramSerializerFactory(jQueryMode) {
|
||||
|
||||
function serializeValue(v) {
|
||||
if (isObject(v)) {
|
||||
return isDate(v) ? v.toISOString() : toJson(v);
|
||||
}
|
||||
return v;
|
||||
function serializeValue(v) {
|
||||
if (isObject(v)) {
|
||||
return isDate(v) ? v.toISOString() : toJson(v);
|
||||
}
|
||||
|
||||
return function paramSerializer(params) {
|
||||
if (!params) return '';
|
||||
var parts = [];
|
||||
forEachSorted(params, function(value, key) {
|
||||
if (value === null || isUndefined(value)) return;
|
||||
if (isArray(value) || isObject(value) && jQueryMode) {
|
||||
forEach(value, function(v, k) {
|
||||
var keySuffix = jQueryMode ? '[' + (!isArray(value) ? k : '') + ']' : '';
|
||||
parts.push(encodeUriQuery(key + keySuffix) + '=' + encodeUriQuery(serializeValue(v)));
|
||||
});
|
||||
} else {
|
||||
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
|
||||
}
|
||||
});
|
||||
|
||||
return parts.length > 0 ? parts.join('&') : '';
|
||||
};
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
function $HttpParamSerializerProvider() {
|
||||
/**
|
||||
* @ngdoc service
|
||||
@@ -51,7 +31,22 @@ function $HttpParamSerializerProvider() {
|
||||
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
|
||||
* */
|
||||
this.$get = function() {
|
||||
return paramSerializerFactory(false);
|
||||
return function ngParamSerializer(params) {
|
||||
if (!params) return '';
|
||||
var parts = [];
|
||||
forEachSorted(params, function(value, key) {
|
||||
if (value === null || isUndefined(value)) return;
|
||||
if (isArray(value)) {
|
||||
forEach(value, function(v, k) {
|
||||
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
|
||||
});
|
||||
} else {
|
||||
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
|
||||
}
|
||||
});
|
||||
|
||||
return parts.join('&');
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,7 +59,30 @@ function $HttpParamSerializerJQLikeProvider() {
|
||||
* Alternative $http params serializer that follows jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
|
||||
* */
|
||||
this.$get = function() {
|
||||
return paramSerializerFactory(true);
|
||||
return function jQueryLikeParamSerializer(params) {
|
||||
if (!params) return '';
|
||||
var parts = [];
|
||||
serialize(params, '', true);
|
||||
return parts.join('&');
|
||||
|
||||
function serialize(toSerialize, prefix, topLevel) {
|
||||
if (toSerialize === null || isUndefined(toSerialize)) return;
|
||||
if (isArray(toSerialize)) {
|
||||
forEach(toSerialize, function(value) {
|
||||
serialize(value, prefix + '[]');
|
||||
});
|
||||
} else if (isObject(toSerialize) && !isDate(toSerialize)) {
|
||||
forEachSorted(toSerialize, function(value, key) {
|
||||
serialize(value, prefix +
|
||||
(topLevel ? '' : '[') +
|
||||
key +
|
||||
(topLevel ? '' : ']'));
|
||||
});
|
||||
} else {
|
||||
parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -414,11 +414,19 @@ var locationPrototype = {
|
||||
*
|
||||
* Return host of current url.
|
||||
*
|
||||
* Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
|
||||
* var host = $location.host();
|
||||
* // => "example.com"
|
||||
*
|
||||
* // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
|
||||
* host = $location.host();
|
||||
* // => "example.com"
|
||||
* host = location.host;
|
||||
* // => "example.com:8080"
|
||||
* ```
|
||||
*
|
||||
* @return {string} host of current url.
|
||||
|
||||
+5
-3
@@ -140,9 +140,11 @@
|
||||
* provide a progress indication, before the promise is resolved or rejected.
|
||||
*
|
||||
* This method *returns a new promise* which is resolved or rejected via the return value of the
|
||||
* `successCallback`, `errorCallback`. It also notifies via the return value of the
|
||||
* `notifyCallback` method. The promise cannot be resolved or rejected from the notifyCallback
|
||||
* method.
|
||||
* `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
|
||||
* with the value which is resolved in that promise using
|
||||
* [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
|
||||
* It also notifies via the return value of the `notifyCallback` method. The promise cannot be
|
||||
* resolved or rejected from the notifyCallback method.
|
||||
*
|
||||
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
|
||||
*
|
||||
|
||||
+41
-3
@@ -10,7 +10,7 @@ function $$RAFProvider() { //rAF
|
||||
$window.webkitCancelRequestAnimationFrame;
|
||||
|
||||
var rafSupported = !!requestAnimationFrame;
|
||||
var raf = rafSupported
|
||||
var rafFn = rafSupported
|
||||
? function(fn) {
|
||||
var id = requestAnimationFrame(fn);
|
||||
return function() {
|
||||
@@ -24,8 +24,46 @@ function $$RAFProvider() { //rAF
|
||||
};
|
||||
};
|
||||
|
||||
raf.supported = rafSupported;
|
||||
queueFn.supported = rafSupported;
|
||||
|
||||
return raf;
|
||||
var cancelLastRAF;
|
||||
var taskCount = 0;
|
||||
var taskQueue = [];
|
||||
return queueFn;
|
||||
|
||||
function flush() {
|
||||
for (var i = 0; i < taskQueue.length; i++) {
|
||||
var task = taskQueue[i];
|
||||
if (task) {
|
||||
taskQueue[i] = null;
|
||||
task();
|
||||
}
|
||||
}
|
||||
taskCount = taskQueue.length = 0;
|
||||
}
|
||||
|
||||
function queueFn(asyncFn) {
|
||||
var index = taskQueue.length;
|
||||
|
||||
taskCount++;
|
||||
taskQueue.push(asyncFn);
|
||||
|
||||
if (index === 0) {
|
||||
cancelLastRAF = rafFn(flush);
|
||||
}
|
||||
|
||||
return function cancelQueueFn() {
|
||||
if (index >= 0) {
|
||||
taskQueue[index] = null;
|
||||
index = null;
|
||||
|
||||
if (--taskCount === 0 && cancelLastRAF) {
|
||||
cancelLastRAF();
|
||||
cancelLastRAF = null;
|
||||
taskQueue.length = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"isElement": false,
|
||||
|
||||
"ELEMENT_NODE": false,
|
||||
"NG_ANIMATE_CLASSNAME": false,
|
||||
"NG_ANIMATE_CHILDREN_DATA": false,
|
||||
|
||||
"assertArg": false,
|
||||
@@ -36,6 +37,7 @@
|
||||
"packageStyles": false,
|
||||
"removeFromArray": false,
|
||||
"stripCommentsFromElement": false,
|
||||
"extractElementNode": false
|
||||
"extractElementNode": false,
|
||||
"getDomNode": false
|
||||
}
|
||||
}
|
||||
|
||||
+37
-43
@@ -39,16 +39,11 @@
|
||||
* return {
|
||||
* enter: function(element, doneFn) {
|
||||
* var height = element[0].offsetHeight;
|
||||
* var animation = $animateCss(element, {
|
||||
* return $animateCss(element, {
|
||||
* from: { height:'0px' },
|
||||
* to: { height:height + 'px' },
|
||||
* duration: 1 // one second
|
||||
* });
|
||||
*
|
||||
* // if no possible animation can be triggered due
|
||||
* // to the combination of options then `animation`
|
||||
* // will be returned as undefined
|
||||
* animation.start().done(doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]);
|
||||
@@ -71,18 +66,13 @@
|
||||
* return {
|
||||
* enter: function(element, doneFn) {
|
||||
* var height = element[0].offsetHeight;
|
||||
* var animation = $animateCss(element, {
|
||||
* return $animateCss(element, {
|
||||
* addClass: 'red large-text pulse-twice',
|
||||
* easing: 'ease-out',
|
||||
* from: { height:'0px' },
|
||||
* to: { height:height + 'px' },
|
||||
* duration: 1 // one second
|
||||
* });
|
||||
*
|
||||
* // if no possible animation can be triggered due
|
||||
* // to the combination of options then `animation`
|
||||
* // will be returned as undefined
|
||||
* animation.start().done(doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]);
|
||||
@@ -122,10 +112,11 @@
|
||||
* styles using the `from` and `to` properties.
|
||||
*
|
||||
* ```js
|
||||
* var animation = $animateCss(element, {
|
||||
* var animator = $animateCss(element, {
|
||||
* from: { background:'red' },
|
||||
* to: { background:'blue' }
|
||||
* });
|
||||
* animator.start();
|
||||
* ```
|
||||
*
|
||||
* ```css
|
||||
@@ -158,10 +149,10 @@
|
||||
* added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
|
||||
*
|
||||
* ```js
|
||||
* var animation = $animateCss(element, { ... });
|
||||
* var animator = $animateCss(element, { ... });
|
||||
* ```
|
||||
*
|
||||
* Now what do the contents of our `animation` variable look like:
|
||||
* Now what do the contents of our `animator` variable look like:
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
@@ -178,26 +169,14 @@
|
||||
* applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
|
||||
* and that changing them will not reconfigure the parameters of the animation.
|
||||
*
|
||||
* By calling `animation.start()` we do get back a promise, however, due to the nature of animations we may not want to tap into the default behaviour of
|
||||
* animations (since they cause a digest to occur which may slow down the animation performance-wise). Therefore instead of calling `then` to capture when
|
||||
* the animation ends be sure to call `done(callback)` (this is the recommended way to use `$animateCss` within JavaScript-animations).
|
||||
* ### runner.done() vs runner.then()
|
||||
* It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
|
||||
* runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
|
||||
* Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
|
||||
* unless you really need a digest to kick off afterwards.
|
||||
*
|
||||
* The example below should put this into perspective:
|
||||
*
|
||||
* ```js
|
||||
* var animation = $animateCss(element, { ... });
|
||||
*
|
||||
* // remember that if there is no CSS animation detected on the element
|
||||
* // then the value returned from $animateCss will be null
|
||||
* if (animation) {
|
||||
* animation.start().done(function() {
|
||||
* // yaay the animation is over
|
||||
* doneCallback();
|
||||
* });
|
||||
* } else {
|
||||
* doneCallback();
|
||||
* }
|
||||
* ```
|
||||
* Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
|
||||
* (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). Check the [animation code above](#usage) to see how this works.
|
||||
*
|
||||
* @param {DOMElement} element the element that will be animated
|
||||
* @param {object} options the animation-related options that will be applied during the animation
|
||||
@@ -223,7 +202,7 @@
|
||||
* `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
|
||||
* `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
|
||||
*
|
||||
* @return {null|object} an object with a start method and details about the animation. If no animation is detected then a value of `null` will be returned.
|
||||
* @return {object} an object with start and end methods and details about the animation.
|
||||
*
|
||||
* * `start` - The method to start the animation. This will return a `Promise` when called.
|
||||
* * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
|
||||
@@ -472,7 +451,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
return stagger || {};
|
||||
}
|
||||
|
||||
var bod = $document[0].body;
|
||||
var bod = getDomNode($document).body;
|
||||
var cancelLastRAFRequest;
|
||||
var rafWaitQueue = [];
|
||||
function waitUntilQuiet(callback) {
|
||||
@@ -521,7 +500,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
function init(element, options) {
|
||||
var node = element[0];
|
||||
var node = getDomNode(element);
|
||||
options = prepareAnimationOptions(options);
|
||||
|
||||
var temporaryStyles = [];
|
||||
@@ -538,8 +517,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
var maxDurationTime;
|
||||
|
||||
if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
|
||||
close();
|
||||
return;
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
var method = options.event && isArray(options.event)
|
||||
@@ -586,8 +564,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
// there is no way we can trigger an animation since no styles and
|
||||
// no classes are being applied which would then trigger a transition
|
||||
if (!hasToStyles && !setupClasses) {
|
||||
close();
|
||||
return false;
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
var cacheKey, stagger;
|
||||
@@ -682,8 +659,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
if (maxDuration === 0 && !flags.recalculateTimingStyles) {
|
||||
close();
|
||||
return false;
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
// we need to recalculate the delay value since we used a pre-emptive negative
|
||||
@@ -711,6 +687,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
|
||||
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
|
||||
return {
|
||||
$$willAnimate: true,
|
||||
end: endFn,
|
||||
start: function() {
|
||||
if (animationClosed) return;
|
||||
@@ -790,6 +767,23 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
function closeAndReturnNoopAnimator() {
|
||||
runner = new $$AnimateRunner({
|
||||
end: endFn,
|
||||
cancel: cancelFn
|
||||
});
|
||||
|
||||
close();
|
||||
|
||||
return {
|
||||
$$willAnimate: false,
|
||||
start: function() {
|
||||
return runner;
|
||||
},
|
||||
end: endFn
|
||||
};
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (animationClosed) return;
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
$$animationProvider.drivers.push('$$animateCssDriver');
|
||||
|
||||
var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
|
||||
var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-animate-anchor';
|
||||
var NG_ANIMATE_ANCHOR_SUFFIX = '-anchor';
|
||||
var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
|
||||
|
||||
var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
|
||||
var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
|
||||
@@ -16,8 +15,8 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
// only browsers that support these properties can render animations
|
||||
if (!$sniffer.animations && !$sniffer.transitions) return noop;
|
||||
|
||||
var bodyNode = $document[0].body;
|
||||
var rootNode = $rootElement[0];
|
||||
var bodyNode = getDomNode($document).body;
|
||||
var rootNode = getDomNode($rootElement);
|
||||
|
||||
var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
|
||||
|
||||
@@ -44,15 +43,13 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
}
|
||||
|
||||
function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
|
||||
var clone = jqLite(outAnchor[0].cloneNode(true));
|
||||
var startingClasses = filterCssClasses(clone.attr('class') || '');
|
||||
var anchorClasses = pendClasses(classes, NG_ANIMATE_ANCHOR_SUFFIX);
|
||||
var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
|
||||
var startingClasses = filterCssClasses(getClassVal(clone));
|
||||
|
||||
outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
|
||||
inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
|
||||
|
||||
clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
|
||||
clone.addClass(anchorClasses);
|
||||
|
||||
rootBodyElement.append(clone);
|
||||
|
||||
@@ -113,7 +110,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
function calculateAnchorStyles(anchor) {
|
||||
var styles = {};
|
||||
|
||||
var coords = anchor[0].getBoundingClientRect();
|
||||
var coords = getDomNode(anchor).getBoundingClientRect();
|
||||
|
||||
// we iterate directly since safari messes up and doesn't return
|
||||
// all the keys for the coods object when iterated
|
||||
@@ -133,22 +130,36 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
}
|
||||
|
||||
function prepareOutAnimation() {
|
||||
return $animateCss(clone, {
|
||||
var animator = $animateCss(clone, {
|
||||
addClass: NG_OUT_ANCHOR_CLASS_NAME,
|
||||
delay: true,
|
||||
from: calculateAnchorStyles(outAnchor)
|
||||
});
|
||||
|
||||
// read the comment within `prepareRegularAnimation` to understand
|
||||
// why this check is necessary
|
||||
return animator.$$willAnimate ? animator : null;
|
||||
}
|
||||
|
||||
function getClassVal(element) {
|
||||
return element.attr('class') || '';
|
||||
}
|
||||
|
||||
function prepareInAnimation() {
|
||||
var endingClasses = filterCssClasses(inAnchor.attr('class'));
|
||||
var classes = getUniqueValues(endingClasses, startingClasses);
|
||||
return $animateCss(clone, {
|
||||
var endingClasses = filterCssClasses(getClassVal(inAnchor));
|
||||
var toAdd = getUniqueValues(endingClasses, startingClasses);
|
||||
var toRemove = getUniqueValues(startingClasses, endingClasses);
|
||||
|
||||
var animator = $animateCss(clone, {
|
||||
to: calculateAnchorStyles(inAnchor),
|
||||
addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + classes,
|
||||
removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + startingClasses,
|
||||
addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
|
||||
removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
|
||||
delay: true
|
||||
});
|
||||
|
||||
// read the comment within `prepareRegularAnimation` to understand
|
||||
// why this check is necessary
|
||||
return animator.$$willAnimate ? animator : null;
|
||||
}
|
||||
|
||||
function end() {
|
||||
@@ -229,7 +240,13 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
options.onDone = animationDetails.domOperation;
|
||||
}
|
||||
|
||||
return $animateCss(element, options);
|
||||
var animator = $animateCss(element, options);
|
||||
|
||||
// the driver lookup code inside of $$animation attempts to spawn a
|
||||
// driver one by one until a driver returns a.$$willAnimate animator object.
|
||||
// $animateCss will always return an object, however, it will pass in
|
||||
// a flag as a hint as to whether an animation was detected or not
|
||||
return animator.$$willAnimate ? animator : null;
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
||||
@@ -145,9 +145,20 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
|
||||
args.push(options);
|
||||
|
||||
var value = fn.apply(fn, args);
|
||||
if (value) {
|
||||
if (isFunction(value.start)) {
|
||||
value = value.start();
|
||||
}
|
||||
|
||||
// optional onEnd / onCancel callback
|
||||
return isFunction(value) ? value : noop;
|
||||
if (value instanceof $$AnimateRunner) {
|
||||
value.done(onDone);
|
||||
} else if (isFunction(value)) {
|
||||
// optional onEnd / onCancel callback
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return noop;
|
||||
}
|
||||
|
||||
function groupEventedAnimations(element, event, options, animations, fnName) {
|
||||
|
||||
@@ -117,7 +117,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
function findCallbacks(element, event) {
|
||||
var targetNode = element[0];
|
||||
var targetNode = getDomNode(element);
|
||||
|
||||
var matches = [];
|
||||
var entries = callbackRegistry[event];
|
||||
@@ -198,7 +198,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
// (bool) - Global setter
|
||||
bool = animationsEnabled = !!element;
|
||||
} else {
|
||||
var node = element.length ? element[0] : element;
|
||||
var node = getDomNode(element);
|
||||
var recordExists = disabledElementsLookup.get(node);
|
||||
|
||||
if (argCount === 1) {
|
||||
@@ -221,11 +221,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
};
|
||||
|
||||
function queueAnimation(element, event, options) {
|
||||
var node, parent;
|
||||
element = stripCommentsFromElement(element);
|
||||
var node = element[0];
|
||||
if (element) {
|
||||
node = getDomNode(element);
|
||||
parent = element.parent();
|
||||
}
|
||||
|
||||
options = prepareAnimationOptions(options);
|
||||
var parent = element.parent();
|
||||
|
||||
// we create a fake runner with a working promise.
|
||||
// These methods will become available after the digest has passed
|
||||
@@ -235,7 +238,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
// a jqLite wrapper that contains only comment nodes... If this
|
||||
// happens then there is no way we can perform an animation
|
||||
if (!node) {
|
||||
runner.end();
|
||||
close();
|
||||
return runner;
|
||||
}
|
||||
|
||||
@@ -408,7 +411,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
close(!status);
|
||||
var animationDetails = activeAnimationsLookup.get(node);
|
||||
if (animationDetails && animationDetails.counter === counter) {
|
||||
clearElementAnimationState(element);
|
||||
clearElementAnimationState(getDomNode(element));
|
||||
}
|
||||
notifyProgress(runner, event, 'close', {});
|
||||
});
|
||||
@@ -435,7 +438,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
function closeChildAnimations(element) {
|
||||
var node = element[0];
|
||||
var node = getDomNode(element);
|
||||
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
|
||||
forEach(children, function(child) {
|
||||
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
|
||||
@@ -454,19 +457,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
function clearElementAnimationState(element) {
|
||||
element = element.length ? element[0] : element;
|
||||
element.removeAttribute(NG_ANIMATE_ATTR_NAME);
|
||||
activeAnimationsLookup.remove(element);
|
||||
var node = getDomNode(element);
|
||||
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
|
||||
activeAnimationsLookup.remove(node);
|
||||
}
|
||||
|
||||
function isMatchingElement(a,b) {
|
||||
a = a.length ? a[0] : a;
|
||||
b = b.length ? b[0] : b;
|
||||
return a === b;
|
||||
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
|
||||
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
|
||||
}
|
||||
|
||||
function closeParentClassBasedAnimations(startingElement) {
|
||||
var parentNode = startingElement[0];
|
||||
var parentNode = getDomNode(startingElement);
|
||||
do {
|
||||
if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;
|
||||
|
||||
@@ -492,7 +493,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
function areAnimationsAllowed(element, parent, event) {
|
||||
function areAnimationsAllowed(element, parentElement, event) {
|
||||
var bodyElementDetected = false;
|
||||
var rootElementDetected = false;
|
||||
var parentAnimationDetected = false;
|
||||
@@ -500,17 +501,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
|
||||
var parentHost = element.data(NG_ANIMATE_PIN_DATA);
|
||||
if (parentHost) {
|
||||
parent = parentHost;
|
||||
parentElement = parentHost;
|
||||
}
|
||||
|
||||
while (parent && parent.length) {
|
||||
while (parentElement && parentElement.length) {
|
||||
if (!rootElementDetected) {
|
||||
// angular doesn't want to attempt to animate elements outside of the application
|
||||
// therefore we need to ensure that the rootElement is an ancestor of the current element
|
||||
rootElementDetected = isMatchingElement(parent, $rootElement);
|
||||
rootElementDetected = isMatchingElement(parentElement, $rootElement);
|
||||
}
|
||||
|
||||
var parentNode = parent[0];
|
||||
var parentNode = parentElement[0];
|
||||
if (parentNode.nodeType !== ELEMENT_NODE) {
|
||||
// no point in inspecting the #document element
|
||||
break;
|
||||
@@ -525,7 +526,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
if (isUndefined(animateChildren) || animateChildren === true) {
|
||||
var value = parent.data(NG_ANIMATE_CHILDREN_DATA);
|
||||
var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
|
||||
if (isDefined(value)) {
|
||||
animateChildren = value;
|
||||
}
|
||||
@@ -537,11 +538,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (!rootElementDetected) {
|
||||
// angular doesn't want to attempt to animate elements outside of the application
|
||||
// therefore we need to ensure that the rootElement is an ancestor of the current element
|
||||
rootElementDetected = isMatchingElement(parent, $rootElement);
|
||||
rootElementDetected = isMatchingElement(parentElement, $rootElement);
|
||||
if (!rootElementDetected) {
|
||||
parentHost = parent.data(NG_ANIMATE_PIN_DATA);
|
||||
parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
|
||||
if (parentHost) {
|
||||
parent = parentHost;
|
||||
parentElement = parentHost;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,10 +550,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (!bodyElementDetected) {
|
||||
// we also need to ensure that the element is or will be apart of the body element
|
||||
// otherwise it is pointless to even issue an animation to be rendered
|
||||
bodyElementDetected = isMatchingElement(parent, bodyElement);
|
||||
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
|
||||
}
|
||||
|
||||
parent = parent.parent();
|
||||
parentElement = parentElement.parent();
|
||||
}
|
||||
|
||||
var allowAnimation = !parentAnimationDetected || animateChildren;
|
||||
@@ -563,14 +564,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
details = details || {};
|
||||
details.state = state;
|
||||
|
||||
element = element.length ? element[0] : element;
|
||||
element.setAttribute(NG_ANIMATE_ATTR_NAME, state);
|
||||
var node = getDomNode(element);
|
||||
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
|
||||
|
||||
var oldValue = activeAnimationsLookup.get(element);
|
||||
var oldValue = activeAnimationsLookup.get(node);
|
||||
var newValue = oldValue
|
||||
? extend(oldValue, details)
|
||||
: details;
|
||||
activeAnimationsLookup.put(element, newValue);
|
||||
activeAnimationsLookup.put(node, newValue);
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
||||
+16
-13
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
var NG_ANIMATE_CLASSNAME = 'ng-animate';
|
||||
var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
|
||||
|
||||
var drivers = this.drivers = [];
|
||||
@@ -62,7 +61,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
event: event,
|
||||
structural: isStructural,
|
||||
options: options,
|
||||
start: start,
|
||||
beforeStart: beforeStart,
|
||||
close: close
|
||||
});
|
||||
|
||||
@@ -88,15 +87,19 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
animationQueue.length = 0;
|
||||
|
||||
forEach(groupAnimations(animations), function(animationEntry) {
|
||||
var startFn = animationEntry.start;
|
||||
var closeFn = animationEntry.close;
|
||||
// it's important that we apply the `ng-animate` CSS class and the
|
||||
// temporary classes before we do any driver invoking since these
|
||||
// CSS classes may be required for proper CSS detection.
|
||||
animationEntry.beforeStart();
|
||||
|
||||
var operation = invokeFirstDriver(animationEntry);
|
||||
var startAnimation = operation && operation.start; /// TODO(matsko): only recognize operation.start()
|
||||
if (!startAnimation) {
|
||||
var triggerAnimationStart = operation && operation.start; /// TODO(matsko): only recognize operation.start()
|
||||
|
||||
var closeFn = animationEntry.close;
|
||||
if (!triggerAnimationStart) {
|
||||
closeFn();
|
||||
} else {
|
||||
startFn();
|
||||
var animationRunner = startAnimation();
|
||||
var animationRunner = triggerAnimationStart();
|
||||
animationRunner.done(function(status) {
|
||||
closeFn(!status);
|
||||
});
|
||||
@@ -128,7 +131,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
var refLookup = {};
|
||||
forEach(animations, function(animation, index) {
|
||||
var element = animation.element;
|
||||
var node = element[0];
|
||||
var node = getDomNode(element);
|
||||
var event = animation.event;
|
||||
var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
|
||||
var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
|
||||
@@ -173,9 +176,9 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (!anchorGroups[lookupKey]) {
|
||||
var group = anchorGroups[lookupKey] = {
|
||||
// TODO(matsko): double-check this code
|
||||
start: function() {
|
||||
fromAnimation.start();
|
||||
toAnimation.start();
|
||||
beforeStart: function() {
|
||||
fromAnimation.beforeStart();
|
||||
toAnimation.beforeStart();
|
||||
},
|
||||
close: function() {
|
||||
fromAnimation.close();
|
||||
@@ -241,7 +244,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
function beforeStart() {
|
||||
element.addClass(NG_ANIMATE_CLASSNAME);
|
||||
if (tempClasses) {
|
||||
$$jqLite.addClass(element, tempClasses);
|
||||
|
||||
+90
-44
@@ -224,6 +224,35 @@
|
||||
*
|
||||
* Stagger animations are currently only supported within CSS-defined animations.
|
||||
*
|
||||
* ### The `ng-animate` CSS class
|
||||
*
|
||||
* When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
|
||||
* This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
|
||||
*
|
||||
* Therefore, animations can be applied to an element using this temporary class directly via CSS.
|
||||
*
|
||||
* ```css
|
||||
* .zipper.ng-animate {
|
||||
* transition:0.5s linear all;
|
||||
* }
|
||||
* .zipper.ng-enter {
|
||||
* opacity:0;
|
||||
* }
|
||||
* .zipper.ng-enter.ng-enter-active {
|
||||
* opacity:1;
|
||||
* }
|
||||
* .zipper.ng-leave {
|
||||
* opacity:1;
|
||||
* }
|
||||
* .zipper.ng-leave.ng-leave-active {
|
||||
* opacity:0;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
|
||||
* the CSS class once an animation has completed.)
|
||||
*
|
||||
*
|
||||
* ## JavaScript-based Animations
|
||||
*
|
||||
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
|
||||
@@ -334,17 +363,12 @@
|
||||
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
|
||||
* return {
|
||||
* enter: function(element, doneFn) {
|
||||
* var animation = $animateCss(element, {
|
||||
* event: 'enter'
|
||||
* });
|
||||
*
|
||||
* if (animation) {
|
||||
* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
|
||||
* var runner = animation.start();
|
||||
* runner.done(doneFn);
|
||||
* } else { //no CSS animation was detected
|
||||
* doneFn();
|
||||
* }
|
||||
* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
|
||||
* var runner = $animateCss(element, {
|
||||
* event: 'enter',
|
||||
* structural: true
|
||||
* }).start();
|
||||
* runner.done(doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
@@ -360,18 +384,14 @@
|
||||
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
|
||||
* return {
|
||||
* enter: function(element, doneFn) {
|
||||
* var animation = $animateCss(element, {
|
||||
* var runner = $animateCss(element, {
|
||||
* event: 'enter',
|
||||
* addClass: 'maroon-setting',
|
||||
* from: { height:0 },
|
||||
* to: { height: 200 }
|
||||
* });
|
||||
* }).start();
|
||||
*
|
||||
* if (animation) {
|
||||
* animation.start().done(doneFn);
|
||||
* } else {
|
||||
* doneFn();
|
||||
* }
|
||||
* runner.done(doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
@@ -399,10 +419,12 @@
|
||||
* called `ng-animate-ref`.
|
||||
*
|
||||
* Let's say for example we have two views that are managed by `ng-view` and we want to show
|
||||
* that there is a relationship between two components situated in different views. By using the
|
||||
* that there is a relationship between two components situated in within these views. By using the
|
||||
* `ng-animate-ref` attribute we can identify that the two components are paired together and we
|
||||
* can then attach an animation, which is triggered when the view changes.
|
||||
*
|
||||
* Say for example we have the following template code:
|
||||
*
|
||||
* ```html
|
||||
* <!-- index.html -->
|
||||
* <div ng-view class="view-animation">
|
||||
@@ -410,46 +432,70 @@
|
||||
*
|
||||
* <!-- home.html -->
|
||||
* <a href="#/banner-page">
|
||||
* <img src="./banner.jpg" ng-animate-ref="banner">
|
||||
* <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
|
||||
* </a>
|
||||
*
|
||||
* <!-- banner-page.html -->
|
||||
* <img src="./banner.jpg" ng-animate-ref="banner">
|
||||
* <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
|
||||
* ```
|
||||
*
|
||||
* Now, when the view changes (once the link is clicked), ngAnimate will examine the
|
||||
* HTML contents to see if there is a match reference between any components in the view
|
||||
* that is leaving and the view that is entering. It will then attempt to trigger a CSS
|
||||
* animation on the `.view-animation-anchor` CSS class (notice how `.view-animation` is
|
||||
* a shared CSS class on the ng-view element? This means that view-animation will apply to
|
||||
* both the enter and leave animations).
|
||||
* that is leaving and the view that is entering. It will scan both the view which is being
|
||||
* removed (leave) and inserted (enter) to see if there are any paired DOM elements that
|
||||
* contain a matching ref value.
|
||||
*
|
||||
* The two images match since they share the same ref value. ngAnimate will now apply a
|
||||
* suffixed version of each of the shared CSS classes with `-anchor`. Therefore we will
|
||||
* have a shared class of `view-animation-anchor` which we can use to setup our transition animation.
|
||||
* The two images match since they share the same ref value. ngAnimate will now create a
|
||||
* transport element (which is a clone of the first image element) and it will then attempt
|
||||
* to animate to the position of the second image element in the next view. For the animation to
|
||||
* work a special CSS class called `ng-anchor` will be added to the transported element.
|
||||
*
|
||||
* We can now attach a transition onto the `.view-animation-anchor` CSS class and then
|
||||
* We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
|
||||
* ngAnimate will handle the entire transition for us as well as the addition and removal of
|
||||
* any changes of CSS classes between the elements:
|
||||
*
|
||||
* ```css
|
||||
* .view-animation-anchor {
|
||||
* .banner.ng-anchor {
|
||||
* /* this animation will last for 1 second since there are
|
||||
* two phases to the animation (an `in` and an `out` phase) */
|
||||
* transition:0.5s linear all;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* There are two stages for an anchor animation: `out` and `in`. The `out` stage happens first and that
|
||||
* is when the element is animated away from its origin. Once that animation is over then the `in` stage
|
||||
* occurs which animates the element to its destination. The reason why there are two animations is to
|
||||
* give enough time for the enter animation on the new element to be ready.
|
||||
* We also **must** include animations for the views that are being entered and removed
|
||||
* (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
|
||||
*
|
||||
* ```css
|
||||
* .view-animation.ng-enter, .view-animation.ng-leave {
|
||||
* transition:0.5s linear all;
|
||||
* position:fixed;
|
||||
* left:0;
|
||||
* top:0;
|
||||
* width:100%;
|
||||
* }
|
||||
* .view-animation.ng-enter {
|
||||
* transform:translateX(100%);
|
||||
* }
|
||||
* .view-animation.ng-leave,
|
||||
* .view-animation.ng-enter.ng-enter-active {
|
||||
* transform:translateX(0%);
|
||||
* }
|
||||
* .view-animation.ng-leave.ng-leave-active {
|
||||
* transform:translateX(-100%);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
|
||||
* an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
|
||||
* from its origin. Once that animation is over then the `in` stage occurs which animates the
|
||||
* element to its destination. The reason why there are two animations is to give enough time
|
||||
* for the enter animation on the new element to be ready.
|
||||
*
|
||||
* The example above sets up a transition for both the in and out phases, but we can also target the out or
|
||||
* in phases directly via `ng-anchor-out` and `ng-anchor-in`.
|
||||
*
|
||||
* ```css
|
||||
* .view-animation-anchor.ng-anchor-out {
|
||||
* .banner.ng-anchor-out {
|
||||
* transition: 0.5s linear all;
|
||||
*
|
||||
* /* the scale will be applied during the out animation,
|
||||
@@ -457,7 +503,7 @@
|
||||
* transform: scale(1.2);
|
||||
* }
|
||||
*
|
||||
* .view-animation-anchor.ng-anchor-in {
|
||||
* .banner.ng-anchor-in {
|
||||
* transition: 1s linear all;
|
||||
* }
|
||||
* ```
|
||||
@@ -551,21 +597,21 @@
|
||||
width:100%;
|
||||
min-height:500px;
|
||||
}
|
||||
.view.ng-enter {
|
||||
.view.ng-enter, .view.ng-leave,
|
||||
.record.ng-anchor {
|
||||
transition:0.5s linear all;
|
||||
}
|
||||
.view.ng-enter {
|
||||
transform:translateX(100%);
|
||||
}
|
||||
.view.ng-enter.ng-enter-active {
|
||||
.view.ng-enter.ng-enter-active, .view.ng-leave {
|
||||
transform:translateX(0%);
|
||||
}
|
||||
.view.ng-leave {
|
||||
transition:0.5s linear all;
|
||||
}
|
||||
.view.ng-leave.ng-leave-active {
|
||||
transform:translateX(-100%);
|
||||
}
|
||||
.view-anchor {
|
||||
transition:0.5s linear all;
|
||||
.record.ng-anchor-out {
|
||||
background:red;
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
@@ -601,7 +647,7 @@
|
||||
* imagine we have a greeting box that shows and hides itself when the data changes
|
||||
*
|
||||
* ```html
|
||||
* <greeing-box active="onOrOff">Hi there</greeting-box>
|
||||
* <greeting-box active="onOrOff">Hi there</greeting-box>
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
|
||||
+26
-10
@@ -16,6 +16,7 @@ var isElement = angular.isElement;
|
||||
var ELEMENT_NODE = 1;
|
||||
var COMMENT_NODE = 8;
|
||||
|
||||
var NG_ANIMATE_CLASSNAME = 'ng-animate';
|
||||
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
|
||||
|
||||
var isPromiseLike = function(p) {
|
||||
@@ -72,19 +73,30 @@ function removeFromArray(arr, val) {
|
||||
}
|
||||
|
||||
function stripCommentsFromElement(element) {
|
||||
if (element instanceof jqLite) {
|
||||
switch (element.length) {
|
||||
case 0:
|
||||
return [];
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// there is no point of stripping anything if the element
|
||||
// is the only element within the jqLite wrapper.
|
||||
// (it's important that we retain the element instance.)
|
||||
if (element[0].nodeType === ELEMENT_NODE) {
|
||||
return element;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return jqLite(extractElementNode(element));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.nodeType === ELEMENT_NODE) {
|
||||
return jqLite(element);
|
||||
}
|
||||
if (element.length === 0) return [];
|
||||
|
||||
// there is no point of stripping anything if the element
|
||||
// is the only element within the jqLite wrapper.
|
||||
// (it's important that we retain the element instance.)
|
||||
if (element.length === 1) {
|
||||
return element[0].nodeType === ELEMENT_NODE && element;
|
||||
} else {
|
||||
return jqLite(extractElementNode(element));
|
||||
}
|
||||
}
|
||||
|
||||
function extractElementNode(element) {
|
||||
@@ -234,3 +246,7 @@ function resolveElementClasses(existing, toAdd, toRemove) {
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
function getDomNode(element) {
|
||||
return (element instanceof angular.element) ? element[0] : element;
|
||||
}
|
||||
|
||||
@@ -581,8 +581,8 @@ angular.module('ngResource', ['ng']).
|
||||
if (angular.isArray(data) !== (!!action.isArray)) {
|
||||
throw $resourceMinErr('badcfg',
|
||||
'Error in resource configuration for action `{0}`. Expected response to ' +
|
||||
'contain an {1} but got an {2}', name, action.isArray ? 'array' : 'object',
|
||||
angular.isArray(data) ? 'array' : 'object');
|
||||
'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
|
||||
angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
|
||||
}
|
||||
// jshint +W018
|
||||
if (action.isArray) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/* global ngTouch: false */
|
||||
/* global ngTouch: false,
|
||||
nodeName_: false
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -66,7 +68,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
// double-tapping, and then fire a click event.
|
||||
//
|
||||
// This delay sucks and makes mobile apps feel unresponsive.
|
||||
// So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
|
||||
// So we detect touchstart, touchcancel and touchend ourselves and determine when
|
||||
// the user has tapped on something.
|
||||
//
|
||||
// What happens when the browser then generates a click event?
|
||||
@@ -78,7 +80,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
// So the sequence for a tap is:
|
||||
// - global touchstart: Sets an "allowable region" at the point touched.
|
||||
// - element's touchstart: Starts a touch
|
||||
// (- touchmove or touchcancel ends the touch, no click follows)
|
||||
// (- touchcancel ends the touch, no click follows)
|
||||
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
|
||||
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
|
||||
// - preventGhostClick() removes the allowable region the global touchstart created.
|
||||
@@ -142,7 +144,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
lastLabelClickCoordinates = null;
|
||||
}
|
||||
// remember label click coordinates to prevent click busting of trigger click event on input
|
||||
if (event.target.tagName.toLowerCase() === 'label') {
|
||||
if (nodeName_(event.target) === 'label') {
|
||||
lastLabelClickCoordinates = [x, y];
|
||||
}
|
||||
|
||||
@@ -158,7 +160,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
event.preventDefault();
|
||||
|
||||
// Blur focused form elements
|
||||
event.target && event.target.blur();
|
||||
event.target && event.target.blur && event.target.blur();
|
||||
}
|
||||
|
||||
|
||||
@@ -229,10 +231,6 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
touchStartY = e.clientY;
|
||||
});
|
||||
|
||||
element.on('touchmove', function(event) {
|
||||
resetState();
|
||||
});
|
||||
|
||||
element.on('touchcancel', function(event) {
|
||||
resetState();
|
||||
});
|
||||
|
||||
@@ -22,3 +22,6 @@
|
||||
/* global -ngTouch */
|
||||
var ngTouch = angular.module('ngTouch', []);
|
||||
|
||||
function nodeName_(element) {
|
||||
return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName));
|
||||
}
|
||||
|
||||
@@ -370,6 +370,21 @@ describe('angular', function() {
|
||||
expect(copy(undefined, [1,2,3])).toEqual([]);
|
||||
expect(copy({0: 1, 1: 2}, [1,2,3])).toEqual([1,2]);
|
||||
});
|
||||
|
||||
it('should copy objects with no prototype parent', function() {
|
||||
var obj = extend(Object.create(null), {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
});
|
||||
var dest = copy(obj);
|
||||
|
||||
expect(Object.getPrototypeOf(dest)).toBe(null);
|
||||
expect(dest.a).toBe(1);
|
||||
expect(dest.b).toBe(2);
|
||||
expect(dest.c).toBe(3);
|
||||
expect(Object.keys(dest)).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extend", function() {
|
||||
@@ -651,6 +666,38 @@ describe('angular', function() {
|
||||
it('should return false when comparing an object and a Date', function() {
|
||||
expect(equals({}, new Date())).toBe(false);
|
||||
});
|
||||
|
||||
it('should safely compare objects with no prototype parent', function() {
|
||||
var o1 = extend(Object.create(null), {
|
||||
a: 1, b: 2, c: 3
|
||||
});
|
||||
var o2 = extend(Object.create(null), {
|
||||
a: 1, b: 2, c: 3
|
||||
});
|
||||
expect(equals(o1, o2)).toBe(true);
|
||||
o2.c = 2;
|
||||
expect(equals(o1, o2)).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should safely compare objects which shadow Object.prototype.hasOwnProperty', function() {
|
||||
/* jshint -W001 */
|
||||
var o1 = {
|
||||
hasOwnProperty: true,
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
};
|
||||
var o2 = {
|
||||
hasOwnProperty: true,
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
};
|
||||
expect(equals(o1, o2)).toBe(true);
|
||||
o1.hasOwnProperty = function() {};
|
||||
expect(equals(o1, o2)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -980,6 +1027,42 @@ describe('angular', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should safely iterate through objects with no prototype parent', function() {
|
||||
var obj = extend(Object.create(null), {
|
||||
a: 1, b: 2, c: 3
|
||||
});
|
||||
var log = [];
|
||||
var self = {};
|
||||
forEach(obj, function(val, key, collection) {
|
||||
expect(this).toBe(self);
|
||||
expect(collection).toBe(obj);
|
||||
log.push(key + '=' + val);
|
||||
}, self);
|
||||
expect(log.length).toBe(3);
|
||||
expect(log).toEqual(['a=1', 'b=2', 'c=3']);
|
||||
});
|
||||
|
||||
|
||||
it('should safely iterate through objects which shadow Object.prototype.hasOwnProperty', function() {
|
||||
/* jshint -W001 */
|
||||
var obj = {
|
||||
hasOwnProperty: true,
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
};
|
||||
var log = [];
|
||||
var self = {};
|
||||
forEach(obj, function(val, key, collection) {
|
||||
expect(this).toBe(self);
|
||||
expect(collection).toBe(obj);
|
||||
log.push(key + '=' + val);
|
||||
}, self);
|
||||
expect(log.length).toBe(4);
|
||||
expect(log).toEqual(['hasOwnProperty=true', 'a=1', 'b=2', 'c=3']);
|
||||
});
|
||||
|
||||
|
||||
describe('ES spec api compliance', function() {
|
||||
|
||||
function testForEachSpec(expectedSize, collection) {
|
||||
|
||||
@@ -211,6 +211,21 @@ describe('$compile', function() {
|
||||
});
|
||||
inject(function($compile) {});
|
||||
});
|
||||
it('should throw an exception if a directive name has leading or trailing whitespace', function() {
|
||||
module(function() {
|
||||
function assertLeadingOrTrailingWhitespaceInDirectiveName(name) {
|
||||
expect(function() {
|
||||
directive(name, function() { });
|
||||
}).toThrowMinErr(
|
||||
'$compile','baddir', 'Directive name \'' + name + '\' is invalid. ' +
|
||||
"The name should not contain leading or trailing whitespaces");
|
||||
}
|
||||
assertLeadingOrTrailingWhitespaceInDirectiveName(' leadingWhitespaceDirectiveName');
|
||||
assertLeadingOrTrailingWhitespaceInDirectiveName('trailingWhitespaceDirectiveName ');
|
||||
assertLeadingOrTrailingWhitespaceInDirectiveName(' leadingAndTrailingWhitespaceDirectiveName ');
|
||||
});
|
||||
inject(function($compile) {});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,32 @@ describe('ngClass', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should add new and remove old classes with same names as Object.prototype properties dynamically', inject(function($rootScope, $compile) {
|
||||
/* jshint -W001 */
|
||||
element = $compile('<div class="existing" ng-class="dynClass"></div>')($rootScope);
|
||||
$rootScope.dynClass = { watch: true, hasOwnProperty: true, isPrototypeOf: true };
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('existing')).toBe(true);
|
||||
expect(element.hasClass('watch')).toBe(true);
|
||||
expect(element.hasClass('hasOwnProperty')).toBe(true);
|
||||
expect(element.hasClass('isPrototypeOf')).toBe(true);
|
||||
|
||||
$rootScope.dynClass.watch = false;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('existing')).toBe(true);
|
||||
expect(element.hasClass('watch')).toBe(false);
|
||||
expect(element.hasClass('hasOwnProperty')).toBe(true);
|
||||
expect(element.hasClass('isPrototypeOf')).toBe(true);
|
||||
|
||||
delete $rootScope.dynClass;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('existing')).toBe(true);
|
||||
expect(element.hasClass('watch')).toBe(false);
|
||||
expect(element.hasClass('hasOwnProperty')).toBe(false);
|
||||
expect(element.hasClass('isPrototypeOf')).toBe(false);
|
||||
}));
|
||||
|
||||
|
||||
it('should support adding multiple classes via an array', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div class="existing" ng-class="[\'A\', \'B\']"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
@@ -180,6 +180,47 @@ describe('ngOptions', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should not include properties with non-numeric keys in array-like collections when using array syntax', function() {
|
||||
createSelect({
|
||||
'ng-model':'selected',
|
||||
'ng-options':'value for value in values'
|
||||
});
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values = { 0: 'X', 1: 'Y', 2: 'Z', 'a': 'A', length: 3};
|
||||
scope.selected = scope.values[1];
|
||||
});
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(options.eq(0)).toEqualOption('X');
|
||||
expect(options.eq(1)).toEqualOption('Y');
|
||||
expect(options.eq(2)).toEqualOption('Z');
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should include properties with non-numeric keys in array-like collections when using object syntax', function() {
|
||||
createSelect({
|
||||
'ng-model':'selected',
|
||||
'ng-options':'value for (key, value) in values'
|
||||
});
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values = { 0: 'X', 1: 'Y', 2: 'Z', 'a': 'A', length: 3};
|
||||
scope.selected = scope.values[1];
|
||||
});
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(5);
|
||||
expect(options.eq(0)).toEqualOption('X');
|
||||
expect(options.eq(1)).toEqualOption('Y');
|
||||
expect(options.eq(2)).toEqualOption('Z');
|
||||
expect(options.eq(3)).toEqualOption('A');
|
||||
expect(options.eq(4)).toEqualOption(3);
|
||||
});
|
||||
|
||||
|
||||
it('should render an object', function() {
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
@@ -480,6 +521,30 @@ describe('ngOptions', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should update the label if only the property has changed', function() {
|
||||
// ng-options="value.name for value in values"
|
||||
// ng-model="selected"
|
||||
createSingleSelect();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
});
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.eq(0).prop('label')).toEqual('A');
|
||||
expect(options.eq(1).prop('label')).toEqual('B');
|
||||
expect(options.eq(2).prop('label')).toEqual('C');
|
||||
|
||||
|
||||
scope.$apply('values[0].name = "X"');
|
||||
|
||||
options = element.find('option');
|
||||
expect(options.eq(0).prop('label')).toEqual('X');
|
||||
|
||||
});
|
||||
|
||||
|
||||
// bug fix #9714
|
||||
it('should select the matching option when the options are updated', function() {
|
||||
|
||||
@@ -776,6 +841,54 @@ describe('ngOptions', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should re-render if an item in an array source is added/removed', function() {
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
'multiple': true,
|
||||
'ng-options': 'item.id as item.label for item in arr'
|
||||
});
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected = [10];
|
||||
});
|
||||
expect(element).toEqualSelectValue([10], true);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected.push(20);
|
||||
});
|
||||
expect(element).toEqualSelectValue([10, 20], true);
|
||||
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected.shift();
|
||||
});
|
||||
expect(element).toEqualSelectValue([20], true);
|
||||
});
|
||||
|
||||
|
||||
it('should handle a options containing circular references', function() {
|
||||
scope.arr[0].ref = scope.arr[0];
|
||||
scope.selected = [scope.arr[0]];
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
'multiple': true,
|
||||
'ng-options': 'item as item.label for item in arr'
|
||||
});
|
||||
expect(element).toEqualSelectValue([scope.arr[0]], true);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected.push(scope.arr[1]);
|
||||
});
|
||||
expect(element).toEqualSelectValue([scope.arr[0], scope.arr[1]], true);
|
||||
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected.pop();
|
||||
});
|
||||
expect(element).toEqualSelectValue([scope.arr[0]], true);
|
||||
});
|
||||
|
||||
|
||||
it('should support single select with object source', function() {
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
@@ -899,10 +1012,9 @@ describe('ngOptions', function() {
|
||||
|
||||
expect(element.val()).toEqual(['10']);
|
||||
|
||||
// Update the properties on the object in the selected array, rather than replacing the whole object
|
||||
// Update the tracked property on the object in the selected array, rather than replacing the whole object
|
||||
scope.$apply(function() {
|
||||
scope.selected[0].id = 20;
|
||||
scope.selected[0].label = 'new twenty';
|
||||
});
|
||||
|
||||
// The value of the select should change since the id property changed
|
||||
@@ -1042,7 +1154,7 @@ describe('ngOptions', function() {
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should setup equality watches on ngModel changes if using trackBy', function() {
|
||||
it('should re-render if the tracked property of the model is changed when using trackBy', function() {
|
||||
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
@@ -1050,13 +1162,13 @@ describe('ngOptions', function() {
|
||||
});
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected = scope.arr[0];
|
||||
scope.selected = {id: 10, label: 'ten'};
|
||||
});
|
||||
|
||||
spyOn(element.controller('ngModel'), '$render');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected.label = 'changed';
|
||||
scope.arr[0].id = 20;
|
||||
});
|
||||
|
||||
// update render due to equality watch
|
||||
@@ -1064,7 +1176,7 @@ describe('ngOptions', function() {
|
||||
|
||||
});
|
||||
|
||||
it('should not setup equality watches on ngModel changes if not using trackBy', function() {
|
||||
it('should not re-render if a property of the model is changed when not using trackBy', function() {
|
||||
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
@@ -1085,6 +1197,40 @@ describe('ngOptions', function() {
|
||||
expect(element.controller('ngModel').$render).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should handle options containing circular references (single)', function() {
|
||||
scope.arr[0].ref = scope.arr[0];
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
'ng-options': 'item for item in arr track by item.id'
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
scope.$apply(function() {
|
||||
scope.selected = scope.arr[0];
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('should handle options containing circular references (multiple)', function() {
|
||||
scope.arr[0].ref = scope.arr[0];
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
'multiple': true,
|
||||
'ng-options': 'item for item in arr track by item.id'
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
scope.$apply(function() {
|
||||
scope.selected = [scope.arr[0]];
|
||||
});
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.selected.push(scope.arr[1]);
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1084,7 +1084,7 @@ describe('ngRepeat', function() {
|
||||
beforeEach(function() {
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
'<li ng-repeat="item in items">{{key}}:{{val}}|></li>' +
|
||||
'<li ng-repeat="item in items">{{item}}</li>' +
|
||||
'</ul>')(scope);
|
||||
a = {};
|
||||
b = {};
|
||||
|
||||
+9
-1
@@ -1992,6 +1992,8 @@ describe('$http param serializers', function() {
|
||||
it('should serialize objects', function() {
|
||||
expect(defSer({foo: 'foov', bar: 'barv'})).toEqual('bar=barv&foo=foov');
|
||||
expect(jqrSer({foo: 'foov', bar: 'barv'})).toEqual('bar=barv&foo=foov');
|
||||
expect(defSer({someDate: new Date('2014-07-15T17:30:00.000Z')})).toEqual('someDate=2014-07-15T17:30:00.000Z');
|
||||
expect(jqrSer({someDate: new Date('2014-07-15T17:30:00.000Z')})).toEqual('someDate=2014-07-15T17:30:00.000Z');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -2010,10 +2012,16 @@ describe('$http param serializers', function() {
|
||||
expect(decodeURIComponent(jqrSer({a: 'b', foo: ['bar', 'baz']}))).toEqual('a=b&foo[]=bar&foo[]=baz');
|
||||
});
|
||||
|
||||
it('should serialize objects by repeating param name with [kay] suffix', function() {
|
||||
it('should serialize objects by repeating param name with [key] suffix', function() {
|
||||
expect(jqrSer({a: 'b', foo: {'bar': 'barv', 'baz': 'bazv'}})).toEqual('a=b&foo%5Bbar%5D=barv&foo%5Bbaz%5D=bazv');
|
||||
//a=b&foo[bar]=barv&foo[baz]=bazv
|
||||
});
|
||||
|
||||
it('should serialize nested objects by repeating param name with [key] suffix', function() {
|
||||
expect(jqrSer({a: ['b', {c: 'd'}], e: {f: 'g', 'h': ['i', 'j']}})).toEqual(
|
||||
'a%5B%5D=b&a%5B%5D%5Bc%5D=d&e%5Bf%5D=g&e%5Bh%5D%5B%5D=i&e%5Bh%5D%5B%5D=j');
|
||||
//a[]=b&a[][c]=d&e[f]=g&e[h][]=i&e[h][]=j
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -31,6 +31,46 @@ describe('$$rAF', function() {
|
||||
expect(present).toBe(true);
|
||||
}));
|
||||
|
||||
it('should only consume only one RAF if multiple async functions are registered before the first frame kicks in', inject(function($$rAF) {
|
||||
if (!$$rAF.supported) return;
|
||||
|
||||
//we need to create our own injector to work around the ngMock overrides
|
||||
var rafLog = [];
|
||||
var injector = createInjector(['ng', function($provide) {
|
||||
$provide.value('$window', {
|
||||
location: window.location,
|
||||
history: window.history,
|
||||
webkitRequestAnimationFrame: function(fn) {
|
||||
rafLog.push(fn);
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
$$rAF = injector.get('$$rAF');
|
||||
|
||||
var log = [];
|
||||
function logFn() {
|
||||
log.push(log.length);
|
||||
}
|
||||
|
||||
$$rAF(logFn);
|
||||
$$rAF(logFn);
|
||||
$$rAF(logFn);
|
||||
|
||||
expect(log).toEqual([]);
|
||||
expect(rafLog.length).toBe(1);
|
||||
|
||||
rafLog[0]();
|
||||
|
||||
expect(log).toEqual([0,1,2]);
|
||||
expect(rafLog.length).toBe(1);
|
||||
|
||||
$$rAF(logFn);
|
||||
|
||||
expect(log).toEqual([0,1,2]);
|
||||
expect(rafLog.length).toBe(2);
|
||||
}));
|
||||
|
||||
describe('$timeout fallback', function() {
|
||||
it("it should use a $timeout incase native rAF isn't suppored", function() {
|
||||
var timeoutSpy = jasmine.createSpy('callback');
|
||||
|
||||
@@ -58,6 +58,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
});
|
||||
|
||||
return {
|
||||
$$willAnimate: true,
|
||||
start: function() {
|
||||
return runner;
|
||||
}
|
||||
@@ -124,7 +125,9 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
it("should not return anything if no animation is detected", function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$animateCss', noop);
|
||||
$provide.value('$animateCss', function() {
|
||||
return { $$willAnimate: false };
|
||||
});
|
||||
});
|
||||
inject(function() {
|
||||
var runner = driver({
|
||||
@@ -151,6 +154,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
$provide.factory('$animateCss', function($q, $$AnimateRunner) {
|
||||
return function() {
|
||||
return {
|
||||
$$willAnimate: true,
|
||||
start: function() {
|
||||
return new $$AnimateRunner({
|
||||
end: function() {
|
||||
@@ -190,6 +194,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
var type = options.event || 'anchor';
|
||||
closeLog[type] = closeLog[type] || [];
|
||||
return {
|
||||
$$willAnimate: true,
|
||||
start: function() {
|
||||
return new $$AnimateRunner({
|
||||
end: function() {
|
||||
@@ -252,6 +257,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
$provide.factory('$animateCss', function($$AnimateRunner) {
|
||||
return function(element, details) {
|
||||
return {
|
||||
$$willAnimate: true,
|
||||
start: function() {
|
||||
animationLog.push([element, details.event]);
|
||||
return new $$AnimateRunner();
|
||||
@@ -429,14 +435,13 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
$provide.factory('$animateCss', function($$AnimateRunner) {
|
||||
return function(element, options) {
|
||||
var addClass = (options.addClass || '').trim();
|
||||
if (addClass === expectedClass) {
|
||||
return {
|
||||
start: function() {
|
||||
animationStarted = addClass;
|
||||
return runner = new $$AnimateRunner();
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
$$willAnimate: addClass === expectedClass,
|
||||
start: function() {
|
||||
animationStarted = addClass;
|
||||
return runner = new $$AnimateRunner();
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -599,7 +604,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
expect(anchorDetails.event).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should add the `ng-animate-anchor` class to the cloned anchor element",
|
||||
it("should add the `ng-anchor` class to the cloned anchor element",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
@@ -620,7 +625,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}).start();
|
||||
|
||||
var clonedAnchor = captureLog.pop().element;
|
||||
expect(clonedAnchor).toHaveClass('ng-animate-anchor');
|
||||
expect(clonedAnchor).toHaveClass('ng-anchor');
|
||||
}));
|
||||
|
||||
it("should add and remove the `ng-animate-shim` class on the in anchor element during the animation",
|
||||
@@ -802,9 +807,9 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var outAnimation = captureLog.pop();
|
||||
var clonedAnchor = outAnimation.element;
|
||||
var details = outAnimation.args[1];
|
||||
var inAnimation = captureLog.pop();
|
||||
var clonedAnchor = inAnimation.element;
|
||||
var details = inAnimation.args[1];
|
||||
|
||||
var addedClasses = details.addClass.split(' ');
|
||||
var removedClasses = details.removeClass.split(' ');
|
||||
@@ -818,6 +823,11 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
expect(removedClasses).not.toContain('brown');
|
||||
expect(removedClasses).not.toContain('black');
|
||||
|
||||
expect(removedClasses).not.toContain('red');
|
||||
expect(removedClasses).not.toContain('blue');
|
||||
|
||||
inAnimation.runner.end();
|
||||
|
||||
expect(clonedAnchor).toHaveClass('red');
|
||||
expect(clonedAnchor).toHaveClass('blue');
|
||||
}));
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
duration: 10,
|
||||
to: { 'background': 'red' }
|
||||
});
|
||||
expect(animator).toBeFalsy();
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
}));
|
||||
|
||||
describe('when active', function() {
|
||||
@@ -316,6 +316,96 @@ describe("ngAnimate $animateCss", function() {
|
||||
options = { event: 'enter', structural: true };
|
||||
}));
|
||||
|
||||
it("should always return an object even if no animation is detected",
|
||||
inject(function($animateCss) {
|
||||
|
||||
ss.addRule('.some-animation', 'background:red;');
|
||||
|
||||
element.addClass('some-animation');
|
||||
var animator = $animateCss(element, options);
|
||||
|
||||
expect(animator).toBeTruthy();
|
||||
expect(isFunction(animator.start)).toBeTruthy();
|
||||
expect(animator.end).toBeTruthy();
|
||||
expect(animator.$$willAnimate).toBe(false);
|
||||
}));
|
||||
|
||||
it("should close the animation immediately, but still return an animator object if no animation is detected",
|
||||
inject(function($animateCss) {
|
||||
|
||||
ss.addRule('.another-fake-animation', 'background:blue;');
|
||||
|
||||
element.addClass('another-fake-animation');
|
||||
var animator = $animateCss(element, {
|
||||
event: 'enter',
|
||||
structural: true
|
||||
});
|
||||
|
||||
expect(element).not.toHaveClass('ng-enter');
|
||||
expect(isFunction(animator.start)).toBeTruthy();
|
||||
}));
|
||||
|
||||
they("should close the animation, but still accept $prop callbacks if no animation is detected",
|
||||
['done', 'then'], function(method) {
|
||||
|
||||
inject(function($animateCss, $$rAF, $rootScope) {
|
||||
ss.addRule('.the-third-fake-animation', 'background:green;');
|
||||
|
||||
element.addClass('another-fake-animation');
|
||||
var animator = $animateCss(element, {
|
||||
event: 'enter',
|
||||
structural: true
|
||||
});
|
||||
|
||||
var done = false;
|
||||
animator.start()[method](function() {
|
||||
done = true;
|
||||
});
|
||||
|
||||
expect(done).toBe(false);
|
||||
$$rAF.flush();
|
||||
if (method === 'then') {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
expect(done).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
they("should close the animation, but still accept recognize runner.$prop if no animation is detected",
|
||||
['done(cancel)', 'catch'], function(method) {
|
||||
|
||||
inject(function($animateCss, $$rAF, $rootScope) {
|
||||
ss.addRule('.the-third-fake-animation', 'background:green;');
|
||||
|
||||
element.addClass('another-fake-animation');
|
||||
var animator = $animateCss(element, {
|
||||
event: 'enter',
|
||||
structural: true
|
||||
});
|
||||
|
||||
var cancelled = false;
|
||||
var runner = animator.start();
|
||||
|
||||
if (method === 'catch') {
|
||||
runner.catch(function() {
|
||||
cancelled = true;
|
||||
});
|
||||
} else {
|
||||
runner.done(function(status) {
|
||||
cancelled = status === false;
|
||||
});
|
||||
}
|
||||
|
||||
expect(cancelled).toBe(false);
|
||||
runner.cancel();
|
||||
|
||||
if (method === 'catch') {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
expect(cancelled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the highest transition duration value detected in the CSS class", inject(function($animateCss) {
|
||||
ss.addRule('.ng-enter', 'transition:1s linear all;' +
|
||||
'transition-duration:10s, 15s, 20s;');
|
||||
@@ -1602,8 +1692,10 @@ describe("ngAnimate $animateCss", function() {
|
||||
event: 'enter',
|
||||
structural: true
|
||||
};
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
expect(animator).toBeFalsy();
|
||||
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should apply a transition and keyframe duration directly if both transitions and keyframe classes are detected",
|
||||
@@ -1667,7 +1759,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
};
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
expect(animator).toBeFalsy();
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should override the delay value present in the CSS class",
|
||||
@@ -2075,7 +2167,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
expect(element.css('width')).toBe('25px');
|
||||
}));
|
||||
|
||||
it("should apply the union of from and to styles to the element if no animation is run",
|
||||
it("should apply the union of from and to styles to the element if no animation will be run",
|
||||
inject(function($animateCss, $rootElement) {
|
||||
|
||||
var options = {
|
||||
@@ -2087,7 +2179,9 @@ describe("ngAnimate $animateCss", function() {
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
|
||||
expect(animator).toBeFalsy();
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
animator.start();
|
||||
|
||||
expect(element.css('width')).toBe('15px');
|
||||
expect(element.css('height')).toBe('50px');
|
||||
}));
|
||||
@@ -2240,7 +2334,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
};
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
expect(animator).toBeFalsy();
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should apply a transition if [from] styles are provided with a class that is added",
|
||||
@@ -2265,7 +2359,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
};
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
expect(animator).toBeTruthy();
|
||||
expect(animator.$$willAnimate).toBeTruthy();
|
||||
}));
|
||||
|
||||
it("should not apply an inline transition if no styles are provided",
|
||||
@@ -2279,7 +2373,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
};
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
expect(animator).toBeFalsy();
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should apply a transition duration if the existing transition duration's property value is not 'all'",
|
||||
@@ -2437,10 +2531,11 @@ describe("ngAnimate $animateCss", function() {
|
||||
'</svg>');
|
||||
var child = element.find('rect');
|
||||
|
||||
$animateCss(child, {
|
||||
var animator = $animateCss(child, {
|
||||
removeClass: 'class-of-doom',
|
||||
duration: 0
|
||||
});
|
||||
animator.start();
|
||||
|
||||
var className = child[0].getAttribute('class');
|
||||
expect(className).toBe('');
|
||||
|
||||
@@ -544,6 +544,66 @@ describe("ngAnimate $$animateJs", function() {
|
||||
});
|
||||
});
|
||||
|
||||
they("$prop should asynchronously render the $prop animation when a start/end animator object is returned",
|
||||
allEvents, function(event) {
|
||||
|
||||
inject(function($$rAF, $$AnimateRunner) {
|
||||
var runner;
|
||||
animations[event] = function(element, a, b, c) {
|
||||
return {
|
||||
start: function() {
|
||||
log.push('start ' + event);
|
||||
return runner = new $$AnimateRunner();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
runAnimation(event, function() {
|
||||
log.push('complete');
|
||||
});
|
||||
|
||||
if (event === 'leave') {
|
||||
expect(log).toEqual(['start leave']);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
expect(log).toEqual(['start leave', 'dom leave', 'complete']);
|
||||
} else {
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event]);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event, 'complete']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
they("$prop should asynchronously render the $prop animation when an instance of $$AnimateRunner is returned",
|
||||
allEvents, function(event) {
|
||||
|
||||
inject(function($$rAF, $$AnimateRunner) {
|
||||
var runner;
|
||||
animations[event] = function(element, a, b, c) {
|
||||
log.push('start ' + event);
|
||||
return runner = new $$AnimateRunner();
|
||||
};
|
||||
|
||||
runAnimation(event, function() {
|
||||
log.push('complete');
|
||||
});
|
||||
|
||||
if (event === 'leave') {
|
||||
expect(log).toEqual(['start leave']);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
expect(log).toEqual(['start leave', 'dom leave', 'complete']);
|
||||
} else {
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event]);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event, 'complete']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
they("$prop should asynchronously reject the before animation if the callback function is called with false", otherEvents, function(event) {
|
||||
inject(function($$rAF, $rootScope) {
|
||||
var beforeMethod = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
|
||||
|
||||
@@ -148,6 +148,27 @@ describe("animations", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw a minErr if a regex value is used which partially contains or fully matches the `ng-animate` CSS class', function() {
|
||||
module(function($animateProvider) {
|
||||
assertError(/ng-animate/, true);
|
||||
assertError(/first ng-animate last/, true);
|
||||
assertError(/ng-animate-special/, false);
|
||||
assertError(/first ng-animate-special last/, false);
|
||||
assertError(/first ng-animate ng-animate-special last/, true);
|
||||
|
||||
function assertError(regex, bool) {
|
||||
var expectation = expect(function() {
|
||||
$animateProvider.classNameFilter(regex);
|
||||
});
|
||||
|
||||
var message = '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "ng-animate" CSS class.';
|
||||
|
||||
bool ? expectation.toThrowMinErr('$animate', 'nongcls', message)
|
||||
: expectation.not.toThrowMinErr('$animate', 'nongcls', message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete the leave DOM operation in case the classNameFilter fails', function() {
|
||||
module(function($animateProvider) {
|
||||
$animateProvider.classNameFilter(/memorable-animation/);
|
||||
@@ -245,6 +266,47 @@ describe("animations", function() {
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should not attempt to perform an animation on a text node element',
|
||||
inject(function($rootScope, $animate) {
|
||||
|
||||
element.html('hello there');
|
||||
var textNode = jqLite(element[0].firstChild);
|
||||
|
||||
$animate.addClass(textNode, 'some-class');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should perform the leave domOperation if a text node is used',
|
||||
inject(function($rootScope, $animate) {
|
||||
|
||||
element.html('hello there');
|
||||
var textNode = jqLite(element[0].firstChild);
|
||||
var parentNode = textNode[0].parentNode;
|
||||
|
||||
$animate.leave(textNode);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(textNode[0].parentNode).not.toBe(parentNode);
|
||||
}));
|
||||
|
||||
it('should perform the leave domOperation if a comment node is used',
|
||||
inject(function($rootScope, $animate, $document) {
|
||||
|
||||
var doc = $document[0];
|
||||
|
||||
element.html('hello there');
|
||||
var commentNode = jqLite(doc.createComment('test comment'));
|
||||
var parentNode = element[0];
|
||||
parentNode.appendChild(commentNode[0]);
|
||||
|
||||
$animate.leave(commentNode);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(commentNode[0].parentNode).not.toBe(parentNode);
|
||||
}));
|
||||
|
||||
it('enter() should issue an enter animation and fire the DOM operation right away before the animation kicks off', inject(function($animate, $rootScope) {
|
||||
expect(parent.children().length).toBe(0);
|
||||
|
||||
@@ -530,6 +592,32 @@ describe("animations", function() {
|
||||
expect(itsOver).toBe(true);
|
||||
}));
|
||||
|
||||
it('should immediately end a parent class-based form animation if a structural child is active',
|
||||
inject(function($rootScope, $animate, $rootElement, $$rAF, $$AnimateRunner) {
|
||||
|
||||
parent.remove();
|
||||
element.remove();
|
||||
|
||||
parent = jqLite('<form></form>');
|
||||
$rootElement.append(parent);
|
||||
|
||||
element = jqLite('<input type="text" name="myInput" />');
|
||||
|
||||
$animate.addClass(parent, 'abc');
|
||||
$rootScope.$digest();
|
||||
|
||||
// we do this since the old runner was already closed
|
||||
overriddenAnimationRunner = new $$AnimateRunner();
|
||||
|
||||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(parent.attr('data-ng-animate')).toBeFalsy();
|
||||
expect(element.attr('data-ng-animate')).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should not end a pre-digest parent animation if it does not have any classes to add/remove',
|
||||
inject(function($rootScope, $animate, $$rAF) {
|
||||
|
||||
|
||||
@@ -734,6 +734,29 @@ describe('$$animation', function() {
|
||||
expect(element).not.toHaveClass('ng-animate');
|
||||
}));
|
||||
|
||||
|
||||
it('should apply the `ng-animate` and temporary CSS classes before the driver is invoked', function() {
|
||||
var capturedElementClasses;
|
||||
|
||||
module(function($provide) {
|
||||
$provide.factory('mockedTestDriver', function() {
|
||||
return function(details) {
|
||||
capturedElementClasses = details.element.attr('class');
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($$animation, $rootScope) {
|
||||
$$animation(element, 'enter', {
|
||||
tempClasses: 'temp-class-name'
|
||||
});
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedElementClasses).toMatch(/\bng-animate\b/);
|
||||
expect(capturedElementClasses).toMatch(/\btemp-class-name\b/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should perform the DOM operation at the end of the animation if the driver doesn\'t run it already',
|
||||
inject(function($$animation, $rootScope) {
|
||||
|
||||
|
||||
@@ -1327,7 +1327,7 @@ describe('resource', function() {
|
||||
expect(successSpy).not.toHaveBeenCalled();
|
||||
expect(failureSpy).toHaveBeenCalled();
|
||||
expect(failureSpy.mostRecentCall.args[0]).toMatch(
|
||||
/^\[\$resource:badcfg\] Error in resource configuration for action `query`\. Expected response to contain an array but got an object/
|
||||
/^\[\$resource:badcfg\] Error in resource configuration for action `query`\. Expected response to contain an array but got an object \(Request: GET \/Customer\/123\)/
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1344,7 +1344,7 @@ describe('resource', function() {
|
||||
expect(successSpy).not.toHaveBeenCalled();
|
||||
expect(failureSpy).toHaveBeenCalled();
|
||||
expect(failureSpy.mostRecentCall.args[0]).toMatch(
|
||||
/^\[\$resource:badcfg\] Error in resource configuration for action `get`\. Expected response to contain an object but got an array/
|
||||
/^\[\$resource:badcfg\] Error in resource configuration for action `get`\. Expected response to contain an object but got an array \(Request: GET \/Customer\/123\)/
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('ngClick (touch)', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should not click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) {
|
||||
it('should not prevent click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) {
|
||||
element = $compile('<div ng-click="tapped = true"></div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
$rootScope.$digest();
|
||||
@@ -140,11 +140,11 @@ describe('ngClick (touch)', function() {
|
||||
browserTrigger(element, 'touchmove');
|
||||
browserTrigger(element, 'touchend',{
|
||||
keys: [],
|
||||
x: 400,
|
||||
y: 400
|
||||
x: 15,
|
||||
y: 15
|
||||
});
|
||||
|
||||
expect($rootScope.tapped).toBeUndefined();
|
||||
expect($rootScope.tapped).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should add the CSS class while the element is held down, and then remove it', inject(function($rootScope, $compile, $rootElement) {
|
||||
@@ -171,6 +171,18 @@ describe('ngClick (touch)', function() {
|
||||
expect($rootScope.tapped).toBe(true);
|
||||
}));
|
||||
|
||||
it('should click when target element is an SVG', inject(
|
||||
function($rootScope, $compile, $rootElement) {
|
||||
element = $compile('<svg ng-click="tapped = true"></svg>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
$rootScope.$digest();
|
||||
|
||||
browserTrigger(element, 'touchstart');
|
||||
browserTrigger(element, 'touchend');
|
||||
browserTrigger(element, 'click', {x:1, y:1});
|
||||
|
||||
expect($rootScope.tapped).toEqual(true);
|
||||
}));
|
||||
|
||||
describe('the clickbuster', function() {
|
||||
var element1, element2;
|
||||
|
||||
Reference in New Issue
Block a user