Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76df116574 | |||
| 440c122556 | |||
| f7114d0c1c | |||
| 2958cd308b | |||
| 9f7c5ceba7 | |||
| 2f32614378 | |||
| 8b33de6fd0 | |||
| d40749caab | |||
| e13224b2df | |||
| 36a14e65d1 | |||
| 3228d3b499 | |||
| 6d173aeb5d | |||
| d0ceeaa37e | |||
| 301663e734 | |||
| dca5fa7b81 | |||
| d557875a8d | |||
| b146af1127 | |||
| cea8e75144 | |||
| b3a9bd3ae0 | |||
| 31a5b8353a | |||
| 301e7aae24 | |||
| f2e2b31ece | |||
| 331cac233f | |||
| 7e3557e96b | |||
| d435464c51 | |||
| c2031b1e9e | |||
| d17fbc3862 | |||
| 0db5b21b1d | |||
| c260e73863 | |||
| e5ad6d6ecd | |||
| bd9bc3f828 | |||
| 2b8baf7e10 | |||
| b8e8c5587f | |||
| 37dd419478 | |||
| 8a433f3cc5 | |||
| 1476810a2c | |||
| 833ea05abf | |||
| ca5fcc6f7a | |||
| 2408f2ded5 | |||
| 1bf1a6203c | |||
| b7117afa2f | |||
| 3ae79c0105 | |||
| 2b64f6e318 | |||
| 23c8a90d22 | |||
| 0504395c14 | |||
| 3831de8a05 | |||
| 40abdaf407 | |||
| 3422cbac80 | |||
| 08035545ed | |||
| 9580bc2c2e | |||
| 5a9dde1c27 | |||
| 3a17799098 | |||
| 1a7e9de8d8 | |||
| 299b3e7e01 | |||
| 54cae0f1d0 | |||
| 4af7cdaf4d | |||
| 593b18c66a | |||
| f2e1a930aa | |||
| babc20b43d | |||
| ba90261b75 | |||
| fc21db8a15 | |||
| b4bdec35cb | |||
| 933591d69c | |||
| cf9331ac66 | |||
| 02977c5bab | |||
| 9f5ac048d7 | |||
| 408f89d8e6 | |||
| 7fda214c4f | |||
| eb6cb785df | |||
| aa798f1236 | |||
| 5a60302389 | |||
| 034fade3e8 | |||
| e24f22bdb1 | |||
| 371c1e19d8 | |||
| b5e00cf615 | |||
| 5765061652 | |||
| b146cae02c | |||
| 3353afbb59 | |||
| 40cb57c8f6 | |||
| f06f28e018 | |||
| f3b088a4e4 | |||
| ef1a9d2cda | |||
| 9c9c6b3fe4 | |||
| 51d6774286 | |||
| e079111b33 | |||
| e1132f53b0 | |||
| ab4b632dbf | |||
| 1b704071c8 | |||
| 647d93338f | |||
| 1334b8c832 | |||
| d2a9a163fb | |||
| e24d968276 | |||
| a01ce6b81c | |||
| c66b4b6a13 | |||
| 66ceecc295 | |||
| 349742b3f0 | |||
| 2ff7edfdd1 | |||
| 1e5e527c84 | |||
| 1c76bf7e94 | |||
| 6018f5da3f | |||
| 3616b9b07c | |||
| d224fe8172 | |||
| e9bf93d510 | |||
| 2e721a7914 | |||
| 1eb6036d29 | |||
| 4836dacae6 | |||
| 0e2ac3cd70 | |||
| 0f9fd2f642 | |||
| 3e42b22b0e | |||
| deb3cb4dae | |||
| b43fa3bb30 | |||
| 521c12c265 | |||
| 7f5051bb2a | |||
| 25623b709f | |||
| e4f23c4d25 | |||
| 8928d02345 | |||
| 5bb2636aac | |||
| c95e38c603 | |||
| a3c3bf3332 |
@@ -0,0 +1,5 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# JS files must always use LF for tools to work
|
||||
*.js eol=lf
|
||||
+2
-9
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"excludeFiles": ["src/ngLocale/**"],
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowMultipleLineStrings": true,
|
||||
"disallowNewlineBeforeBlockStatements": true,
|
||||
@@ -12,7 +11,6 @@
|
||||
"disallowSpacesInAnonymousFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInCallExpression": true,
|
||||
"disallowSpacesInFunctionDeclaration": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
@@ -20,11 +18,6 @@
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"requireSpaceBeforeKeywords": [
|
||||
"else",
|
||||
"while",
|
||||
"catch"
|
||||
],
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"disallowTrailingComma": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
@@ -40,9 +33,9 @@
|
||||
"afterConsequent": true,
|
||||
"beforeAlternate": true
|
||||
},
|
||||
"requireSpacesInForStatement": true,
|
||||
"requireSpacesInFunction": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"validateLineBreaks": "LF"
|
||||
"validateLineBreaks": "LF",
|
||||
"validateParameterSeparator": ", "
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,10 +4,10 @@
|
||||
// that correct the existing code base issues and make the new check pass.
|
||||
|
||||
{
|
||||
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
|
||||
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"validateJSDoc": {
|
||||
"checkParamNames": true,
|
||||
"requireParamTypes": true
|
||||
|
||||
+6
-99
@@ -1,102 +1,3 @@
|
||||
<a name="1.4.0-beta.3"></a>
|
||||
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- do not initialize optional '&' binding if attribute not specified
|
||||
([6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
|
||||
[#6404](https://github.com/angular/angular.js/issues/6404), [#9216](https://github.com/angular/angular.js/issues/9216))
|
||||
- respect return value from controller constructor
|
||||
([62d514b0](https://github.com/angular/angular.js/commit/62d514b06937cc7dd86e973ea11165c88343b42d))
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:**
|
||||
- handle null targets at assign
|
||||
([2e5a7e52](https://github.com/angular/angular.js/commit/2e5a7e52a0385575bbb55a801471b009afafeca3))
|
||||
- remove references to last arguments to a fn call
|
||||
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **a:** don't reload if there is only a name attribute
|
||||
([d729fcf0](https://github.com/angular/angular.js/commit/d729fcf030be1d3ef37196d36ea3bf3249ee3318),
|
||||
[#6273](https://github.com/angular/angular.js/issues/6273), [#10880](https://github.com/angular/angular.js/issues/10880))
|
||||
- **angular.copy:** support copying `TypedArray`s
|
||||
([aa0f6449](https://github.com/angular/angular.js/commit/aa0f64496a66d2a5d1a4d033f2eb075a8b084a78),
|
||||
[#10745](https://github.com/angular/angular.js/issues/10745))
|
||||
- **filter:** format timezone correctly in the case that UTC timezone is used
|
||||
([8c469191](https://github.com/angular/angular.js/commit/8c46919199090a05634789774124b38983430c76),
|
||||
[#9359](https://github.com/angular/angular.js/issues/9359))
|
||||
- **ngRoute:** dont duplicate optional params into query
|
||||
([27bf2ce4](https://github.com/angular/angular.js/commit/27bf2ce40c5adfb1494d69c9d0ac9cf433834a12),
|
||||
[#10689](https://github.com/angular/angular.js/issues/10689))
|
||||
- **ngScenario:** allow ngScenario to handle lazy-loaded and manually bootstrapped applications
|
||||
([c69caa7b](https://github.com/angular/angular.js/commit/c69caa7beee4e920f8f587eb3e943be99864a14f),
|
||||
[#10723](https://github.com/angular/angular.js/issues/10723))
|
||||
- **validators:** maxlength should use viewValue for $isEmpty
|
||||
([bfcf9946](https://github.com/angular/angular.js/commit/bfcf9946e16d21b55dde50d4d21c71c898b10215),
|
||||
[#10898](https://github.com/angular/angular.js/issues/10898))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** allow using bindToController as object, support both new/isolate scopes
|
||||
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
|
||||
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
|
||||
- **filter:** support conversion to timezone other than UTC
|
||||
([c6d8512a](https://github.com/angular/angular.js/commit/c6d8512a1d7345516d1bd9a039d81821b9518bff),
|
||||
[#10858](https://github.com/angular/angular.js/issues/10858))
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$scope:** Add a property $$watchersCount to scope
|
||||
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
|
||||
- **$parse** new and more performant parser
|
||||
([0d42426](https://github.com/angular/angular.js/commit/0d424263ead16635afb582affab2b147f8e71626))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
|
||||
Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
|
||||
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.
|
||||
|
||||
|
||||
<a name="1.3.12"></a>
|
||||
# 1.3.12 outlandish-knitting (2015-02-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([632b2ddd](https://github.com/angular/angular.js/commit/632b2ddd34c07b3b5a207bd83ca3a5e6e613e63b),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:** remove references to last arguments to a fn call
|
||||
([7caad220](https://github.com/angular/angular.js/commit/7caad2205a6e9927890192a3638f55532bdaaf75),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **ngRoute:** dont duplicate optional params into query
|
||||
([f41ca4a5](https://github.com/angular/angular.js/commit/f41ca4a53ed53f172fb334911be56e42aad58794),
|
||||
[#10689](https://github.com/angular/angular.js/issues/10689))
|
||||
- **ngScenario:** Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
|
||||
([0bcd0872](https://github.com/angular/angular.js/commit/0bcd0872d8d2e37e6cb7aa5bc5cb0c742b4294f9),
|
||||
[#10723](https://github.com/angular/angular.js/issues/10723))
|
||||
- **validators:** maxlength should use viewValue for $isEmpty
|
||||
([abd8e2a9](https://github.com/angular/angular.js/commit/abd8e2a9eb2d21ac67989c2f7b64c4c6547a1585),
|
||||
[#10898](https://github.com/angular/angular.js/issues/10898))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([6ec59460](https://github.com/angular/angular.js/commit/6ec5946094ee92b820bbacc886fa2367715e60b4))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.2"></a>
|
||||
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
|
||||
|
||||
@@ -112,6 +13,9 @@ is marked as optional and the attribute is not specified, no function will be ad
|
||||
- **filterFilter:** throw error if input is not an array
|
||||
([cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
|
||||
[#9992](https://github.com/angular/angular.js/issues/9992), [#10352](https://github.com/angular/angular.js/issues/10352))
|
||||
- **form:** ignore properties in $error prototype chain
|
||||
([31a5b835](https://github.com/angular/angular.js/commit/31a5b8353ae5f1a5cb283322829880995e877833),
|
||||
[#10469](https://github.com/angular/angular.js/issues/10469), [#10727](https://github.com/angular/angular.js/issues/10727))
|
||||
- **htmlAnchorDirective:**
|
||||
- remove "element !== target element" check
|
||||
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
|
||||
@@ -143,6 +47,9 @@ Closes #10352
|
||||
- **$location:** don't rewrite when link is shift-clicked
|
||||
([939ca37c](https://github.com/angular/angular.js/commit/939ca37cfe5f6fc35b09b6705caabd1fcc3cf9d3),
|
||||
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
|
||||
- **form:** ignore properties in $error prototype chain
|
||||
([adf91fe6](https://github.com/angular/angular.js/commit/adf91fe6ee77a84e8159c9a95e36f65276fe67bd),
|
||||
[#10469](https://github.com/angular/angular.js/issues/10469), [#10727](https://github.com/angular/angular.js/issues/10727))
|
||||
- **htmlAnchorDirective:**
|
||||
- remove "element !== target element" check
|
||||
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
Vendored
+1
@@ -65,6 +65,7 @@ var angularFiles = {
|
||||
'src/ng/directive/ngList.js',
|
||||
'src/ng/directive/ngModel.js',
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngOptions.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
|
||||
/* globals angular, benchmarkSteps */
|
||||
|
||||
var app = angular.module('ngOptionsBenchmark', []);
|
||||
|
||||
app.config(function($compileProvider) {
|
||||
if ($compileProvider.debugInfoEnabled) {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.controller('DataController', function($scope, $element) {
|
||||
$scope.items = [];
|
||||
$scope.count = 10000;
|
||||
|
||||
function changeOptions() {
|
||||
$scope.items = [];
|
||||
for (var i = 0; i < $scope.count; ++i) {
|
||||
$scope.items.push({
|
||||
id: i,
|
||||
label: 'item-' + i,
|
||||
group: 'group-' + i % 100
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var selectElement = $element.find('select');
|
||||
console.log(selectElement);
|
||||
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'add-options',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.count = 10000;
|
||||
changeOptions();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-model-1',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.x = $scope.items[1000];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-model-2',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.x = $scope.items[10];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'remove-options',
|
||||
fn: function() {
|
||||
$scope.count = 100;
|
||||
changeOptions();
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'add-options',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.count = 10000;
|
||||
changeOptions();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-view-1',
|
||||
fn: function() {
|
||||
selectElement.val('2000');
|
||||
selectElement.triggerHandler('change');
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'set-view-2',
|
||||
fn: function() {
|
||||
selectElement.val('1000');
|
||||
selectElement.triggerHandler('change');
|
||||
}
|
||||
});
|
||||
});
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [ {
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
},
|
||||
{
|
||||
src: 'app.js',
|
||||
}]
|
||||
});
|
||||
};
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
<div ng-app="ngOptionsBenchmark" ng-cloak>
|
||||
<div ng-controller="DataController">
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
Tests the execution of ng-options for rendering during model and option updates.
|
||||
</p>
|
||||
<select ng-model="x" ng-options="a as a.label group by a.group for a in items track by a.id"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -415,7 +415,7 @@ iframe.example {
|
||||
|
||||
.main-body-grid .side-navigation {
|
||||
position:relative;
|
||||
padding-bottom:120px;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
@@ -631,7 +631,6 @@ ul.events > li {
|
||||
}
|
||||
.main-body-grid .side-navigation {
|
||||
display:block!important;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
display:none!important;
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
<p class="pull-right"><a back-to-top>Back to top</a></p>
|
||||
|
||||
<p>
|
||||
Super-powered by Google ©2010-2014
|
||||
Super-powered by Google ©2010-2015
|
||||
( <a id="version"
|
||||
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
|
||||
ng-bind-template="v{{version}}">
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $controller:ctrlfmt
|
||||
@fullName Badly formed controller string
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$controller $controller} service is called
|
||||
with a string that does not match the supported controller string formats.
|
||||
|
||||
Supported formats:
|
||||
|
||||
1. `__name__`
|
||||
2. `__name__ as __identifier__`
|
||||
|
||||
N'either `__name__` or `__identifier__` may contain spaces.
|
||||
|
||||
Example of incorrect usage that leads to this error:
|
||||
```html
|
||||
<!-- unclosed ng-controller attribute messes up the format -->
|
||||
<div ng-controller="myController>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
// does not match `__name__` or `__name__ as __identifier__`
|
||||
var myCtrl = $controller("mY contRoller", { $scope: newScope });
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
directive("myDirective", function() {
|
||||
return {
|
||||
// does not match `__name__` or `__name__ as __identifier__`
|
||||
controller: "mY contRoller",
|
||||
link: function() {}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
To fix the examples above, ensure that the controller string matches the supported
|
||||
formats, and that any html attributes which are used as controller expressions are
|
||||
closed.
|
||||
|
||||
|
||||
Please consult the {@link ng.$controller $controller} service api docs to learn more.
|
||||
@@ -35,7 +35,7 @@ URL of the subcontext:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<base href="/subapp">
|
||||
<base href="/subapp/">
|
||||
...
|
||||
</head>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@ngdoc error
|
||||
@name filter:notarray
|
||||
@fullName Not an array
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.filter filter} is not used with an array.
|
||||
Filter must be used with an array so a subset of items can be returned.
|
||||
The array can be initialized asynchronously so null or undefined won't throw this error.
|
||||
@@ -37,7 +37,6 @@ Currently, ngAria interfaces with the following directives:
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
* {@link guide/accessibility#ngmessages ngMessages}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
@@ -210,14 +209,10 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
|
||||
already.
|
||||
|
||||
For `ng-click`, keypress will also be bound to `div` and `li` elements. You can turn this
|
||||
functionality on or off with the `bindKeypress` configuration option.
|
||||
|
||||
For `ng-dblclick`, you must manually add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access.
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
should also bind `ng-keypress`.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
@@ -228,6 +223,7 @@ Becomes:
|
||||
```html
|
||||
<div ng-click="toggleMenu()" tabindex="0"></div>
|
||||
```
|
||||
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
|
||||
|
||||
<h2 id="ngmessages">ngMessages</h2>
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
|
||||
A Form is a collection of controls for the purpose of grouping related controls together.
|
||||
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input
|
||||
before submitting a form. This provides a better user experience than server-side validation alone
|
||||
because the user gets instant feedback on how to correct the error. Keep in mind that while
|
||||
client-side validation plays an important role in providing good user experience, it can easily
|
||||
be circumvented and thus can not be trusted. Server-side validation is still necessary for a
|
||||
secure application.
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input.
|
||||
This provides a better user experience, because the user gets instant feedback on how to
|
||||
correct the error. Keep in mind that while client-side validation plays an important role
|
||||
in providing good user experience, it can easily be circumvented and thus can not be trusted.
|
||||
Server-side validation is still necessary for a secure application.
|
||||
|
||||
|
||||
# Simple form
|
||||
|
||||
@@ -102,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
## Learning Resources
|
||||
|
||||
###Books
|
||||
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
|
||||
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) by Brad Green and Shyam Seshadri
|
||||
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
|
||||
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
|
||||
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
|
||||
|
||||
@@ -197,14 +197,14 @@ Then Angular applies configuration blocks in the same order they were registered
|
||||
## Run Blocks
|
||||
|
||||
Run blocks are the closest thing in Angular to the main method. A run block is the code which
|
||||
needs to run to kickstart the application. It is executed after all of the service have been
|
||||
needs to run to kickstart the application. It is executed after all of the services have been
|
||||
configured and the injector has been created. Run blocks typically contain code which is hard
|
||||
to unit-test, and for this reason should be declared in isolated modules, so that they can be
|
||||
ignored in the unit-tests.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Modules can list other modules as their dependencies. Depending on a module implies that required
|
||||
Modules can list other modules as their dependencies. Depending on a module implies that the required
|
||||
module needs to be loaded before the requiring module is loaded. In other words the configuration
|
||||
blocks of the required modules execute before the configuration blocks of the requiring module.
|
||||
The same is true for the run blocks. Each module can only be loaded once, even if multiple other
|
||||
|
||||
@@ -10,13 +10,13 @@ There are a few things you might consider when running your AngularJS applicatio
|
||||
|
||||
## Disabling Debug Data
|
||||
|
||||
By default AngularJS attaches information about binding and scopes to DOM nodes,
|
||||
and adds CSS classes to data-bound elements:
|
||||
By default AngularJS attaches information about scopes to DOM nodes, and adds CSS classes
|
||||
to data-bound elements. The information that is not included is:
|
||||
|
||||
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
|
||||
`ng-binding` are attached to the corresponding element.
|
||||
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
|
||||
`ng-class` is attached to the corresponding element.
|
||||
|
||||
- Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
|
||||
Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
|
||||
CSS class are attached to the corresponding element. These scope references can then be accessed via
|
||||
`element.scope()` and `element.isolateScope()`.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ templating systems.
|
||||
### Do I need to worry about security holes in AngularJS?
|
||||
|
||||
Like any other technology, AngularJS is not impervious to attack. Angular does, however, provide
|
||||
built-in protection from basic security holes including cross-site scripting and HTML injection
|
||||
built-in protection from basic security holes, including cross-site scripting and HTML injection
|
||||
attacks. AngularJS does round-trip escaping on all strings for you and even offers XSRF protection
|
||||
for server-side communication.
|
||||
|
||||
@@ -52,7 +52,7 @@ Yes. See instructions in {@link downloading}.
|
||||
|
||||
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera 15,
|
||||
IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari). See {@link guide/ie Internet
|
||||
Explorer Compatibility} for more details in supporting legacy IE browsers.
|
||||
Explorer Compatibility} for more details on supporting legacy IE browsers.
|
||||
|
||||
|
||||
### What's Angular's performance like?
|
||||
@@ -61,8 +61,8 @@ The startup time heavily depends on your network connection, state of the cache,
|
||||
available hardware, but typically we measure bootstrap time in tens or hundreds of milliseconds.
|
||||
|
||||
The runtime performance will vary depending on the number and complexity of bindings on the page
|
||||
as well as the speed of your backend (for apps that fetch data from the backend). Just for an
|
||||
illustration we typically build snappy apps with hundreds or thousands of active bindings.
|
||||
as well as the speed of your backend (for apps that fetch data from the backend). For an
|
||||
illustration, we typically build snappy apps with hundreds or thousands of active bindings.
|
||||
|
||||
|
||||
### How big is the angular.js file that I need to include?
|
||||
@@ -88,7 +88,7 @@ but we don't guarantee that.
|
||||
|
||||
### What is testability like in Angular?
|
||||
|
||||
Very testable and designed this way from ground up. It has an integrated dependency injection
|
||||
Very testable and designed this way from the ground up. It has an integrated dependency injection
|
||||
framework, provides mocks for many heavy dependencies (server-side communication). See
|
||||
{@link ngMock} for details.
|
||||
|
||||
@@ -189,7 +189,7 @@ Then whenever a value on a scope changes, all `$watch`es observing that element
|
||||
|
||||
Sometimes, usually when you're writing a custom directive, you will have to define your own `$watch` on a scope value to make the directive react to changes.
|
||||
|
||||
On the flip side, sometimes you change a scope value in some code but the app doesn't react to it.
|
||||
On the flip side, sometimes you change a scope value in some code, but the app doesn't react to it.
|
||||
Angular checks for scope variable changes after pieces of your code have finished running; for example, when `ng-click` calls a function on your scope, Angular will check for changes and react.
|
||||
However, some code is outside of Angular and you'll have to call `scope.$apply()` yourself to trigger the update.
|
||||
This is most commonly seen in event handlers in custom directives.
|
||||
|
||||
@@ -11,7 +11,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html/#/phones`
|
||||
and the phone list appears in the browser.
|
||||
* When you click on a phone link the url changes to one specific to that phone and the stub of a
|
||||
* When you click on a phone link, the url changes to that specific phone and the stub of a
|
||||
phone detail page is displayed.
|
||||
|
||||
<div doc-tutorial-reset="7"></div>
|
||||
|
||||
@@ -288,5 +288,5 @@ learn how to improve this application with animations.
|
||||
<ul doc-tutorial-nav="11"></ul>
|
||||
|
||||
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
|
||||
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
|
||||
[bower]: http://bower.io/
|
||||
|
||||
@@ -2912,12 +2912,16 @@ goog.i18n.DateTimeSymbols_my = {
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
|
||||
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
|
||||
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
|
||||
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
|
||||
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
|
||||
'ဒီဇင်ဘာ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
|
||||
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
|
||||
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
|
||||
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
|
||||
@@ -2941,7 +2945,7 @@ goog.i18n.DateTimeSymbols_my = {
|
||||
'တတိယ သုံးလပတ်',
|
||||
'စတုတ္ထ သုံးလပတ်'],
|
||||
AMPMS: ['နံနက်', 'ညနေ'],
|
||||
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
|
||||
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
|
||||
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
|
||||
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
|
||||
@@ -14493,12 +14493,16 @@ goog.i18n.DateTimeSymbols_my_MM = {
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
|
||||
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
|
||||
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
|
||||
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
|
||||
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
|
||||
'ဒီဇင်ဘာ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
|
||||
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
|
||||
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
|
||||
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
|
||||
@@ -14522,7 +14526,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
|
||||
'တတိယ သုံးလပတ်',
|
||||
'စတုတ္ထ သုံးလပတ်'],
|
||||
AMPMS: ['နံနက်', 'ညနေ'],
|
||||
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
|
||||
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
|
||||
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
|
||||
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
|
||||
@@ -2112,7 +2112,7 @@ goog.i18n.NumberFormatSymbols_lt = {
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0\u00A0%',
|
||||
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
|
||||
DEF_CURRENCY_CODE: 'EUR'
|
||||
DEF_CURRENCY_CODE: 'LTL'
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ var getTaggedVersion = function() {
|
||||
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
|
||||
version.codeName = getCodeName(tag);
|
||||
version.full = version.version;
|
||||
version.branch = 'v' + currentPackage.branchVersion.replace('*', 'x');
|
||||
version.branch = 'v' + currentPackage.branchPattern.replace('*', 'x');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -130,13 +130,17 @@ var getCdnVersion = function() {
|
||||
return semver.satisfies(tag, currentPackage.branchVersion);
|
||||
})
|
||||
.reverse()
|
||||
.tap(function(versions) {
|
||||
console.log(versions);
|
||||
})
|
||||
.reduce(function(cdnVersion, version) {
|
||||
if (!cdnVersion) {
|
||||
// Note: need to use shell.exec and curl here
|
||||
// as version-infos returns its result synchronously...
|
||||
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js '+
|
||||
'--head --write-out "%{http_code}" -o /dev/null -silent',
|
||||
{silent: true});
|
||||
{silent: false});
|
||||
console.log('http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js');
|
||||
if ( cdnResult.code === 0 ) {
|
||||
var statusCode = cdnResult.output.trim();
|
||||
if (statusCode === '200') {
|
||||
@@ -161,7 +165,7 @@ var getSnapshotVersion = function() {
|
||||
|
||||
if ( !version ) {
|
||||
// a snapshot version before the first tag on the branch
|
||||
version = semver(currentPackage.branchVersion.replace('*','0-beta.1'));
|
||||
version = semver(currentPackage.branchPattern.replace('*','0-beta.1'));
|
||||
}
|
||||
|
||||
// We need to clone to ensure that we are not modifying another version
|
||||
|
||||
Generated
+51
-75
@@ -1395,66 +1395,66 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.8",
|
||||
"version": "0.10.7",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.7.1"
|
||||
},
|
||||
"change-case": {
|
||||
"version": "2.2.0",
|
||||
"version": "2.1.5",
|
||||
"dependencies": {
|
||||
"camel-case": {
|
||||
"version": "1.1.1"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"constant-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"dot-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"is-lower-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"is-upper-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "1.1.1"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"param-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"pascal-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"path-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"sentence-case": {
|
||||
"version": "1.1.1"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"snake-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"swap-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"title-case": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "1.1.1"
|
||||
"version": "1.0.3"
|
||||
},
|
||||
"upper-case-first": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "1.2.3"
|
||||
"version": "1.2.2"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.9.1"
|
||||
"version": "1.7.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.11",
|
||||
@@ -1626,7 +1626,7 @@
|
||||
}
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.2"
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"cookie-jar": {
|
||||
"version": "0.2.0"
|
||||
@@ -2342,32 +2342,26 @@
|
||||
},
|
||||
"grunt-jasmine-node": {
|
||||
"version": "0.1.0",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
|
||||
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
|
||||
},
|
||||
"grunt-jscs": {
|
||||
"version": "1.2.0",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"hooker": {
|
||||
"version": "0.2.3"
|
||||
},
|
||||
"jscs": {
|
||||
"version": "1.10.0",
|
||||
"version": "1.6.2",
|
||||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "1.0.3"
|
||||
"version": "0.6.2"
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.5.1"
|
||||
"version": "2.3.0"
|
||||
},
|
||||
"esprima": {
|
||||
"version": "1.2.3"
|
||||
},
|
||||
"esprima-harmony-jscs": {
|
||||
"version": "1.1.0-regex-token-fix"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.9.1"
|
||||
"version": "1.2.2"
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2"
|
||||
@@ -2376,22 +2370,11 @@
|
||||
"version": "4.0.6",
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "3.0.5"
|
||||
"version": "3.0.4"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.3.1",
|
||||
"dependencies": {
|
||||
@@ -2403,61 +2386,54 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "2.0.1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"vow-fs": {
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.2",
|
||||
"dependencies": {
|
||||
"node-uuid": {
|
||||
"version": "1.4.2"
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"vow": {
|
||||
"version": "0.4.4"
|
||||
},
|
||||
"vow-queue": {
|
||||
"version": "0.4.1"
|
||||
"version": "0.3.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "4.3.5",
|
||||
"version": "3.2.8",
|
||||
"dependencies": {
|
||||
"inflight": {
|
||||
"version": "1.0.4",
|
||||
"minimatch": {
|
||||
"version": "0.2.14",
|
||||
"dependencies": {
|
||||
"wrappy": {
|
||||
"version": "1.0.1"
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"once": {
|
||||
"version": "1.3.1",
|
||||
"dependencies": {
|
||||
"wrappy": {
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.6",
|
||||
"version": "2.4.4",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -2465,12 +2441,12 @@
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "1.2.0"
|
||||
"version": "1.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vow": {
|
||||
"version": "0.4.8"
|
||||
"version": "0.4.5"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"branchVersion": "1.3.*",
|
||||
"branchVersion": "^1.4.0-beta.0",
|
||||
"branchPattern": "1.4.*",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -24,7 +25,7 @@
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~1.2.0",
|
||||
"grunt-jscs": "~0.7.1",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
|
||||
@@ -17,7 +17,7 @@ ARG_DEFS=(
|
||||
)
|
||||
|
||||
function checkVersionNumber() {
|
||||
BRANCH_PATTERN=$(readJsonProp "package.json" "branchVersion")
|
||||
BRANCH_PATTERN=$(readJsonProp "package.json" "branchPattern")
|
||||
if [[ $VERSION_NUMBER != $BRANCH_PATTERN ]]; then
|
||||
echo "version-number needs to match $BRANCH_PATTERN on this branch"
|
||||
usage
|
||||
|
||||
+1
-2
@@ -28,13 +28,12 @@
|
||||
"manualUppercase": false,
|
||||
"isArrayLike": false,
|
||||
"forEach": false,
|
||||
"sortedKeys": false,
|
||||
"forEachSorted": false,
|
||||
"reverseParams": false,
|
||||
"nextUid": false,
|
||||
"setHashKey": false,
|
||||
"extend": false,
|
||||
"int": false,
|
||||
"toInt": false,
|
||||
"inherit": false,
|
||||
"noop": false,
|
||||
"identity": false,
|
||||
|
||||
+7
-15
@@ -22,13 +22,12 @@
|
||||
nodeName_: true,
|
||||
isArrayLike: true,
|
||||
forEach: true,
|
||||
sortedKeys: true,
|
||||
forEachSorted: true,
|
||||
reverseParams: true,
|
||||
nextUid: true,
|
||||
setHashKey: true,
|
||||
extend: true,
|
||||
int: true,
|
||||
toInt: true,
|
||||
inherit: true,
|
||||
noop: true,
|
||||
identity: true,
|
||||
@@ -272,12 +271,8 @@ function forEach(obj, iterator, context) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function sortedKeys(obj) {
|
||||
return Object.keys(obj).sort();
|
||||
}
|
||||
|
||||
function forEachSorted(obj, iterator, context) {
|
||||
var keys = sortedKeys(obj);
|
||||
var keys = Object.keys(obj).sort();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
iterator.call(context, obj[keys[i]], keys[i]);
|
||||
}
|
||||
@@ -317,7 +312,8 @@ function nextUid() {
|
||||
function setHashKey(obj, h) {
|
||||
if (h) {
|
||||
obj.$$hashKey = h;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
delete obj.$$hashKey;
|
||||
}
|
||||
}
|
||||
@@ -356,7 +352,7 @@ function extend(dst) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
function int(str) {
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
||||
@@ -626,7 +622,7 @@ function isElement(node) {
|
||||
function makeMap(str) {
|
||||
var obj = {}, items = str.split(","), i;
|
||||
for (i = 0; i < items.length; i++)
|
||||
obj[items[i]] = true;
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1407,12 +1403,8 @@ function bootstrap(element, modules, config) {
|
||||
forEach(extraModules, function(module) {
|
||||
modules.push(module);
|
||||
});
|
||||
return doBootstrap();
|
||||
doBootstrap();
|
||||
};
|
||||
|
||||
if (isFunction(angular.resumeDeferredBootstrap)) {
|
||||
angular.resumeDeferredBootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, document, undefined) {
|
||||
|
||||
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
|
||||
var args = [],
|
||||
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
|
||||
$inject = annotate(fn, strictDi, serviceName),
|
||||
length, i,
|
||||
key;
|
||||
|
||||
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
invoke: invoke,
|
||||
instantiate: instantiate,
|
||||
get: getService,
|
||||
annotate: createInjector.$$annotate,
|
||||
annotate: annotate,
|
||||
has: function(name) {
|
||||
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
+18
-13
@@ -33,28 +33,33 @@
|
||||
function minErr(module, ErrorConstructor) {
|
||||
ErrorConstructor = ErrorConstructor || Error;
|
||||
return function() {
|
||||
var code = arguments[0],
|
||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = arguments[1],
|
||||
templateArgs = arguments,
|
||||
var SKIP_INDEXES = 2;
|
||||
|
||||
message, i;
|
||||
var templateArgs = arguments,
|
||||
code = templateArgs[0],
|
||||
message = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = templateArgs[1],
|
||||
paramPrefix, i;
|
||||
|
||||
message = prefix + template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1), arg;
|
||||
message += template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1),
|
||||
shiftedIndex = index + SKIP_INDEXES;
|
||||
|
||||
if (index + 2 < templateArgs.length) {
|
||||
return toDebugString(templateArgs[index + 2]);
|
||||
if (shiftedIndex < templateArgs.length) {
|
||||
return toDebugString(templateArgs[shiftedIndex]);
|
||||
}
|
||||
|
||||
return match;
|
||||
});
|
||||
|
||||
message = message + '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
|
||||
message += '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
|
||||
(module ? module + '/' : '') + code;
|
||||
for (i = 2; i < arguments.length; i++) {
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
|
||||
encodeURIComponent(toDebugString(arguments[i]));
|
||||
|
||||
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
|
||||
encodeURIComponent(toDebugString(templateArgs[i]));
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
|
||||
@@ -159,13 +159,13 @@ function $CacheFactoryProvider() {
|
||||
* @returns {*} the value stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
if (isUndefined(value)) return;
|
||||
if (capacity < Number.MAX_VALUE) {
|
||||
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
|
||||
|
||||
refresh(lruEntry);
|
||||
}
|
||||
|
||||
if (isUndefined(value)) return;
|
||||
if (!(key in data)) size++;
|
||||
data[key] = value;
|
||||
|
||||
|
||||
+3
-2
@@ -477,7 +477,7 @@
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* **Best Practice**: if you intend to add and remove transcluded content manually in your directive
|
||||
* (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
|
||||
* (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
|
||||
* then you are also responsible for calling `$destroy` on the transclusion scope.
|
||||
* </div>
|
||||
*
|
||||
@@ -2155,7 +2155,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
afterTemplateChildLinkFn,
|
||||
beforeTemplateCompileNode = $compileNode[0],
|
||||
origAsyncDirective = directives.shift(),
|
||||
derivedSyncDirective = inherit(origAsyncDirective, {
|
||||
// The fact that we have to copy and patch the directive seems wrong!
|
||||
derivedSyncDirective = extend({}, origAsyncDirective, {
|
||||
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
|
||||
}),
|
||||
templateUrl = (isFunction(origAsyncDirective.templateUrl))
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var $controllerMinErr = minErr('$controller');
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $controllerProvider
|
||||
@@ -89,12 +87,7 @@ function $ControllerProvider() {
|
||||
}
|
||||
|
||||
if (isString(expression)) {
|
||||
match = expression.match(CNTRL_REG);
|
||||
if (!match) {
|
||||
throw $controllerMinErr('ctrlfmt',
|
||||
"Badly formed controller string '{0}'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.", expression);
|
||||
}
|
||||
match = expression.match(CNTRL_REG),
|
||||
constructor = match[1],
|
||||
identifier = identifier || match[3];
|
||||
expression = controllers.hasOwnProperty(constructor)
|
||||
|
||||
@@ -341,22 +341,34 @@
|
||||
|
||||
var ngAttributeAliasDirectives = {};
|
||||
|
||||
|
||||
// boolean attrs are evaluated
|
||||
forEach(BOOLEAN_ATTR, function(propName, attrName) {
|
||||
// binding to multiple is not supported
|
||||
if (propName == "multiple") return;
|
||||
|
||||
function defaultLinkFn(scope, element, attr) {
|
||||
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
|
||||
attr.$set(attrName, !!value);
|
||||
});
|
||||
}
|
||||
|
||||
var normalized = directiveNormalize('ng-' + attrName);
|
||||
var linkFn = defaultLinkFn;
|
||||
|
||||
if (propName === 'checked') {
|
||||
linkFn = function(scope, element, attr) {
|
||||
// ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
|
||||
if (attr.ngModel !== attr[normalized]) {
|
||||
defaultLinkFn(scope, element, attr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ngAttributeAliasDirectives[normalized] = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 100,
|
||||
link: function(scope, element, attr) {
|
||||
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
|
||||
attr.$set(attrName, !!value);
|
||||
});
|
||||
}
|
||||
link: linkFn
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -492,19 +492,19 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
alias = controller.$name;
|
||||
|
||||
if (alias) {
|
||||
setter(scope, null, alias, controller, alias);
|
||||
setter(scope, alias, controller, alias);
|
||||
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
|
||||
if (alias === newValue) return;
|
||||
setter(scope, null, alias, undefined, alias);
|
||||
setter(scope, alias, undefined, alias);
|
||||
alias = newValue;
|
||||
setter(scope, null, alias, controller, alias);
|
||||
setter(scope, alias, controller, alias);
|
||||
parentFormCtrl.$$renameControl(controller, alias);
|
||||
});
|
||||
}
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
if (alias) {
|
||||
setter(scope, null, alias, undefined, alias);
|
||||
setter(scope, alias, undefined, alias);
|
||||
}
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ function classDirective(name, selector) {
|
||||
|
||||
function arrayClasses(classVal) {
|
||||
if (isArray(classVal)) {
|
||||
return classVal;
|
||||
return classVal.join(' ').split(' ');
|
||||
} else if (isString(classVal)) {
|
||||
return classVal.split(' ');
|
||||
} else if (isObject(classVal)) {
|
||||
|
||||
@@ -285,10 +285,10 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
|
||||
* committed value then `$render()` is called to update the input control.
|
||||
* * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
|
||||
* the `$viewValue` are different to last time.
|
||||
* the `$viewValue` are different from last time.
|
||||
*
|
||||
* Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
|
||||
* `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue`
|
||||
* `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
|
||||
* or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
|
||||
* invoked if you only change a property on the objects.
|
||||
*/
|
||||
@@ -305,7 +305,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
*
|
||||
* The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
|
||||
*
|
||||
* You can override this for input directives whose concept of being empty is different to the
|
||||
* You can override this for input directives whose concept of being empty is different from the
|
||||
* default. The `checkboxInputType` directive does this because in its case a value of `false`
|
||||
* implies empty.
|
||||
*
|
||||
@@ -954,7 +954,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
|
||||
* function that returns a representation of the model when called with zero arguments, and sets
|
||||
* the internal state of a model when called with an argument. It's sometimes useful to use this
|
||||
* for models that have an internal representation that's different than what the model exposes
|
||||
* for models that have an internal representation that's different from what the model exposes
|
||||
* to the view.
|
||||
*
|
||||
* <div class="alert alert-success">
|
||||
@@ -1070,7 +1070,7 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
* takes place when a timer expires; this timer will be reset after another change takes place.
|
||||
*
|
||||
* Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
|
||||
* be different than the value in the actual model. This means that if you update the model you
|
||||
* be different from the value in the actual model. This means that if you update the model you
|
||||
* should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
|
||||
* order to make sure it is synchronized with the model and that any debounced action is canceled.
|
||||
*
|
||||
@@ -1215,7 +1215,7 @@ var ngModelOptionsDirective = function() {
|
||||
restrict: 'A',
|
||||
controller: ['$scope', '$attrs', function($scope, $attrs) {
|
||||
var that = this;
|
||||
this.$options = $scope.$eval($attrs.ngModelOptions);
|
||||
this.$options = copy($scope.$eval($attrs.ngModelOptions));
|
||||
// Allow adding/overriding bound events
|
||||
if (this.$options.updateOn !== undefined) {
|
||||
this.$options.updateOnDefault = false;
|
||||
|
||||
@@ -0,0 +1,612 @@
|
||||
'use strict';
|
||||
|
||||
/* global jqLiteRemove */
|
||||
|
||||
var ngOptionsMinErr = minErr('ngOptions');
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngOptions
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
||||
* elements for the `<select>` element using the array or object obtained by evaluating the
|
||||
* `ngOptions` comprehension expression.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
|
||||
* increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
|
||||
* to a non-string value. This is because an option element can only be bound to string values at
|
||||
* present.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
* directive.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* 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:** `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/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
|
||||
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
|
||||
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
*
|
||||
* ### `select` **`as`** and **`track by`**
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
|
||||
* </div>
|
||||
*
|
||||
* Consider the following example:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
* }, {
|
||||
* id: 2,
|
||||
* label: 'bLabel',
|
||||
* subItem: { name: 'bSubItem' }
|
||||
* }];
|
||||
*
|
||||
* $scope.selected = { name: 'aSubItem' };
|
||||
* ```
|
||||
*
|
||||
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
|
||||
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
|
||||
* following:
|
||||
*
|
||||
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
|
||||
* 2. Apply **`track by`** to the already selected value in `ngModel`.
|
||||
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
|
||||
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
|
||||
* selected" option.
|
||||
*
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} required The control is considered valid only if value is entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
* `required` when you want to data-bind to the `required` attribute.
|
||||
* @param {comprehension_expression=} ngOptions in one of the following forms:
|
||||
*
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
||||
* (for including a filter with `track by`)
|
||||
* * for object data sources:
|
||||
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`group by`** `group`
|
||||
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
|
||||
* * `value`: local variable which will refer to each item in the `array` or each property value
|
||||
* of `object` during iteration.
|
||||
* * `key`: local variable which will refer to a property name in `object` during iteration.
|
||||
* * `label`: The result of this expression will be the label for `<option>` element. The
|
||||
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
|
||||
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
|
||||
* element. If not specified, `select` expression will default to `value`.
|
||||
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
||||
* DOM element.
|
||||
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
||||
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
* even when the options are recreated (e.g. reloaded from the server).
|
||||
*
|
||||
* @example
|
||||
<example module="selectExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('selectExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.colors = [
|
||||
{name:'black', shade:'dark'},
|
||||
{name:'white', shade:'light'},
|
||||
{name:'red', shade:'dark'},
|
||||
{name:'blue', shade:'dark'},
|
||||
{name:'yellow', shade:'light'}
|
||||
];
|
||||
$scope.myColor = $scope.colors[2]; // red
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<ul>
|
||||
<li ng-repeat="color in colors">
|
||||
Name: <input ng-model="color.name">
|
||||
[<a href ng-click="colors.splice($index, 1)">X</a>]
|
||||
</li>
|
||||
<li>
|
||||
[<a href ng-click="colors.push({})">add</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
Color (null not allowed):
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors"></select><br>
|
||||
|
||||
Color (null allowed):
|
||||
<span class="nullable">
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors">
|
||||
<option value="">-- choose color --</option>
|
||||
</select>
|
||||
</span><br/>
|
||||
|
||||
Color grouped by shade:
|
||||
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
|
||||
</select><br/>
|
||||
|
||||
|
||||
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
|
||||
<hr/>
|
||||
Currently selected: {{ {selected_color:myColor} }}
|
||||
<div style="border:solid 1px black; height:20px"
|
||||
ng-style="{'background-color':myColor.name}">
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ng-options', function() {
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
|
||||
element.all(by.model('myColor')).first().click();
|
||||
element.all(by.css('select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
|
||||
element(by.css('.nullable select[ng-model="myColor"]')).click();
|
||||
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
// jshint maxlen: false
|
||||
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
|
||||
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
|
||||
// 1: value expression (valueFn)
|
||||
// 2: label expression (displayFn)
|
||||
// 3: group by expression (groupByFn)
|
||||
// 4: array item variable name
|
||||
// 5: object item key variable name
|
||||
// 6: object item value variable name
|
||||
// 7: collection expression
|
||||
// 8: track by expression
|
||||
// jshint maxlen: 100
|
||||
|
||||
|
||||
var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
function parseOptionsExpression(optionsExp, selectElement, scope) {
|
||||
|
||||
var match = optionsExp.match(NG_OPTIONS_REGEXP);
|
||||
if (!(match)) {
|
||||
throw ngOptionsMinErr('iexp',
|
||||
"Expected expression in form of " +
|
||||
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
|
||||
" but got '{0}'. Element: {1}",
|
||||
optionsExp, startingTag(selectElement));
|
||||
}
|
||||
|
||||
// Extract the parts from the ngOptions expression
|
||||
|
||||
// The variable name for the value of the item in the collection
|
||||
var valueName = match[4] || match[6];
|
||||
// The variable name for the key of the item in the collection
|
||||
var keyName = match[5];
|
||||
|
||||
// An expression that generates the viewValue for an option if there is a label expression
|
||||
var selectAs = / as /.test(match[0]) && match[1];
|
||||
// An expression that is used to track the id of each object in the options collection
|
||||
var trackBy = match[8];
|
||||
// An expression that generates the viewValue for an option if there is no label expression
|
||||
var valueFn = $parse(match[2] ? match[1] : valueName);
|
||||
var selectAsFn = selectAs && $parse(selectAs);
|
||||
var viewValueFn = selectAsFn || valueFn;
|
||||
var trackByFn = trackBy && $parse(trackBy);
|
||||
|
||||
// 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 displayFn = $parse(match[2] || match[1]);
|
||||
var groupByFn = $parse(match[3] || '');
|
||||
var valuesFn = $parse(match[7]);
|
||||
|
||||
var locals = {};
|
||||
var getLocals = keyName ? function(value, key) {
|
||||
locals[keyName] = key;
|
||||
locals[valueName] = value;
|
||||
return locals;
|
||||
} : function(value) {
|
||||
locals[valueName] = value;
|
||||
return locals;
|
||||
};
|
||||
|
||||
|
||||
function Option(selectValue, viewValue, label, group) {
|
||||
this.selectValue = selectValue;
|
||||
this.viewValue = viewValue;
|
||||
this.label = label;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
return {
|
||||
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
|
||||
// that only runs the handler once if anything changes
|
||||
var watchedArray = [];
|
||||
values = values || [];
|
||||
|
||||
Object.keys(values).forEach(function getWatchable(key) {
|
||||
var locals = getLocals(values[key], key);
|
||||
var label = displayFn(scope, locals);
|
||||
var selectValue = getTrackByValue(values[key], locals);
|
||||
watchedArray.push(selectValue);
|
||||
watchedArray.push(label);
|
||||
});
|
||||
return watchedArray;
|
||||
}),
|
||||
|
||||
getOptions: function() {
|
||||
|
||||
var optionItems = [];
|
||||
var selectValueMap = {};
|
||||
|
||||
// The option values were already computed in the `getWatchables` fn,
|
||||
// which must have been called to trigger `getOptions`
|
||||
var optionValues = valuesFn(scope) || [];
|
||||
|
||||
var keys = Object.keys(optionValues);
|
||||
keys.forEach(function getOption(key) {
|
||||
|
||||
// Ignore "angular" properties that start with $ or $$
|
||||
if (key.charAt(0) === '$') return;
|
||||
|
||||
var value = optionValues[key];
|
||||
var locals = getLocals(value, key);
|
||||
var viewValue = viewValueFn(scope, locals);
|
||||
var selectValue = getTrackByValue(viewValue, locals);
|
||||
var label = displayFn(scope, locals);
|
||||
var group = groupByFn(scope, locals);
|
||||
var optionItem = new Option(selectValue, viewValue, label, group);
|
||||
|
||||
optionItems.push(optionItem);
|
||||
selectValueMap[selectValue] = optionItem;
|
||||
});
|
||||
|
||||
return {
|
||||
items: optionItems,
|
||||
selectValueMap: selectValueMap,
|
||||
getOptionFromViewValue: function(value) {
|
||||
return selectValueMap[getTrackByValue(value, getLocals(value))];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
var optionTemplate = document.createElement('option'),
|
||||
optGroupTemplate = document.createElement('optgroup');
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
terminal: true,
|
||||
require: ['select', '?ngModel'],
|
||||
link: function(scope, selectElement, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
if (!ngModelCtrl) return;
|
||||
|
||||
var selectCtrl = ctrls[0];
|
||||
var multiple = attr.multiple;
|
||||
|
||||
var emptyOption = selectCtrl.emptyOption;
|
||||
var providedEmptyOption = !!emptyOption;
|
||||
|
||||
var unknownOption = jqLite(optionTemplate.cloneNode(false));
|
||||
unknownOption.val('?');
|
||||
|
||||
var options;
|
||||
var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
|
||||
|
||||
|
||||
var renderEmptyOption = function() {
|
||||
if (!providedEmptyOption) {
|
||||
selectElement.prepend(emptyOption);
|
||||
}
|
||||
selectElement.val('');
|
||||
emptyOption.prop('selected', true); // needed for IE
|
||||
emptyOption.attr('selected', true);
|
||||
};
|
||||
|
||||
var removeEmptyOption = function() {
|
||||
if (!providedEmptyOption) {
|
||||
emptyOption.remove();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var renderUnknownOption = function() {
|
||||
selectElement.prepend(unknownOption);
|
||||
selectElement.val('?');
|
||||
unknownOption.prop('selected', true); // needed for IE
|
||||
unknownOption.attr('selected', true);
|
||||
};
|
||||
|
||||
var removeUnknownOption = function() {
|
||||
unknownOption.remove();
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
||||
var option = options.getOptionFromViewValue(value);
|
||||
|
||||
if (option) {
|
||||
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) {
|
||||
removeEmptyOption();
|
||||
removeUnknownOption();
|
||||
return selectedOption.viewValue;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
// Update the controller methods for multiple selectable options
|
||||
if (multiple) {
|
||||
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
|
||||
options.items.forEach(function(option) {
|
||||
option.element.selected = false;
|
||||
});
|
||||
|
||||
if (value) {
|
||||
value.forEach(function(item) {
|
||||
var option = options.getOptionFromViewValue(item);
|
||||
if (option) option.element.selected = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.readValue = function readNgOptionsMultiple() {
|
||||
var selectedValues = selectElement.val() || [];
|
||||
return selectedValues.map(function(selectedKey) {
|
||||
var option = options.selectValueMap[selectedKey];
|
||||
return option.viewValue;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (providedEmptyOption) {
|
||||
|
||||
// we need to remove it before calling selectElement.empty() because otherwise IE will
|
||||
// remove the label from the element. wtf?
|
||||
emptyOption.remove();
|
||||
|
||||
// compile the element since there might be bindings in it
|
||||
$compile(emptyOption)(scope);
|
||||
|
||||
// remove the class, which is added automatically because we recompile the element and it
|
||||
// becomes the compilation root
|
||||
emptyOption.removeClass('ng-scope');
|
||||
} else {
|
||||
emptyOption = jqLite(optionTemplate.cloneNode(false));
|
||||
}
|
||||
|
||||
// We need to do this here to ensure that the options object is defined
|
||||
// when we first hit it in writeNgOptionsValue
|
||||
updateOptions();
|
||||
|
||||
// We will re-render the option elements if the option values or labels change
|
||||
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
|
||||
function updateOptionElement(option, element) {
|
||||
option.element = element;
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
if (option.label !== element.label) {
|
||||
element.label = option.label;
|
||||
element.textContent = option.label;
|
||||
}
|
||||
}
|
||||
|
||||
function addOrReuseElement(parent, current, type, templateElement) {
|
||||
var element;
|
||||
// Check whether we can reuse the next element
|
||||
if (current && lowercase(current.nodeName) === type) {
|
||||
// The next element is the right type so reuse it
|
||||
element = current;
|
||||
} else {
|
||||
// The next element is not the right type so create a new one
|
||||
element = templateElement.cloneNode(false);
|
||||
if (!current) {
|
||||
// There are no more elements so just append it to the select
|
||||
parent.appendChild(element);
|
||||
} else {
|
||||
// The next element is not a group so insert the new one
|
||||
parent.insertBefore(element, current);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
function removeExcessElements(current) {
|
||||
var next;
|
||||
while (current) {
|
||||
next = current.nextSibling;
|
||||
jqLiteRemove(current);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function skipEmptyAndUnknownOptions(current) {
|
||||
var emptyOption_ = emptyOption && emptyOption[0];
|
||||
var unknownOption_ = unknownOption && unknownOption[0];
|
||||
|
||||
if (emptyOption_ || unknownOption_) {
|
||||
while (current &&
|
||||
(current === emptyOption_ ||
|
||||
current === unknownOption_)) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
function updateOptions() {
|
||||
|
||||
var previousValue = options && selectCtrl.readValue();
|
||||
|
||||
options = ngOptions.getOptions();
|
||||
|
||||
var groupMap = {};
|
||||
var currentElement = selectElement[0].firstChild;
|
||||
|
||||
// Ensure that the empty option is always there if it was explicitly provided
|
||||
if (providedEmptyOption) {
|
||||
selectElement.prepend(emptyOption);
|
||||
}
|
||||
|
||||
currentElement = skipEmptyAndUnknownOptions(currentElement);
|
||||
|
||||
options.items.forEach(function updateOption(option) {
|
||||
var group;
|
||||
var groupElement;
|
||||
var optionElement;
|
||||
|
||||
if (option.group) {
|
||||
|
||||
// This option is to live in a group
|
||||
// See if we have already created this group
|
||||
group = groupMap[option.group];
|
||||
|
||||
if (!group) {
|
||||
|
||||
// We have not already created this group
|
||||
groupElement = addOrReuseElement(selectElement[0],
|
||||
currentElement,
|
||||
'optgroup',
|
||||
optGroupTemplate);
|
||||
// Move to the next element
|
||||
currentElement = groupElement.nextSibling;
|
||||
|
||||
// Update the label on the group element
|
||||
groupElement.label = option.group;
|
||||
|
||||
// Store it for use later
|
||||
group = groupMap[option.group] = {
|
||||
groupElement: groupElement,
|
||||
currentOptionElement: groupElement.firstChild
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// So now we have a group for this option we add the option to the group
|
||||
optionElement = addOrReuseElement(group.groupElement,
|
||||
group.currentOptionElement,
|
||||
'option',
|
||||
optionTemplate);
|
||||
updateOptionElement(option, optionElement);
|
||||
// Move to the next element
|
||||
group.currentOptionElement = optionElement.nextSibling;
|
||||
|
||||
} else {
|
||||
|
||||
// This option is not in a group
|
||||
optionElement = addOrReuseElement(selectElement[0],
|
||||
currentElement,
|
||||
'option',
|
||||
optionTemplate);
|
||||
updateOptionElement(option, optionElement);
|
||||
// Move to the next element
|
||||
currentElement = optionElement.nextSibling;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Now remove all excess options and group
|
||||
Object.keys(groupMap).forEach(function(key) {
|
||||
removeExcessElements(groupMap[key].currentOptionElement);
|
||||
});
|
||||
removeExcessElements(currentElement);
|
||||
|
||||
ngModelCtrl.$render();
|
||||
|
||||
// Check to see if the value has changed due to the update to the options
|
||||
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
||||
var nextValue = selectCtrl.readValue();
|
||||
if (!equals(previousValue, nextValue)) {
|
||||
ngModelCtrl.$setViewValue(nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -54,6 +54,9 @@
|
||||
* <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
|
||||
* for <span ng-non-bindable>{{numberExpression}}</span>.
|
||||
*
|
||||
* If no rule is defined for a category, then an empty string is displayed and a warning is generated.
|
||||
* Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
|
||||
*
|
||||
* # Configuring ngPluralize with offset
|
||||
* The `offset` attribute allows further customization of pluralized text, which can result in
|
||||
* a better user experience. For example, instead of the message "4 people are viewing this document",
|
||||
@@ -172,7 +175,7 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
|
||||
var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
|
||||
var BRACE = /{}/g,
|
||||
IS_WHEN = /^when(Minus)?(.+)$/;
|
||||
|
||||
@@ -214,9 +217,18 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
|
||||
|
||||
// If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
|
||||
// In JS `NaN !== NaN`, so we have to exlicitly check.
|
||||
if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
|
||||
if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
|
||||
watchRemover();
|
||||
watchRemover = scope.$watch(whensExpFns[count], updateElementText);
|
||||
var whenExpFn = whensExpFns[count];
|
||||
if (isUndefined(whenExpFn)) {
|
||||
if (newVal != null) {
|
||||
$log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
|
||||
}
|
||||
watchRemover = noop;
|
||||
updateElementText();
|
||||
} else {
|
||||
watchRemover = scope.$watch(whenExpFn, updateElementText);
|
||||
}
|
||||
lastCount = count;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
*
|
||||
*
|
||||
* # Iterating over object properties
|
||||
*
|
||||
* It is possible to get `ngRepeat` to iterate over the properties of an object using the following
|
||||
@@ -33,18 +34,19 @@
|
||||
* ```
|
||||
*
|
||||
* You need to be aware that the JavaScript specification does not define what order
|
||||
* it will return the keys for an object. In order to have a guaranteed deterministic order
|
||||
* for the keys, Angular versions up to and including 1.3 **sort the keys alphabetically**.
|
||||
* it will return the keys for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
|
||||
* used to sort the keys alphabetically.)
|
||||
*
|
||||
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
|
||||
* when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
|
||||
* keys in the order in which they were defined, although there are exceptions when keys are deleted
|
||||
* and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
|
||||
*
|
||||
* If this is not desired, the recommended workaround is to convert your object into an array
|
||||
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
|
||||
* do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
|
||||
* or implement a `$watch` on the object yourself.
|
||||
*
|
||||
* In version 1.4 we will remove the sorting, since it seems that browsers generally follow the
|
||||
* strategy of providing keys in the order in which they were defined, although there are exceptions
|
||||
* when keys are deleted and reinstated.
|
||||
*
|
||||
*
|
||||
* # Special repeat start and end points
|
||||
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
|
||||
@@ -358,14 +360,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
|
||||
} else {
|
||||
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
|
||||
// if object, extract keys, sort them and use to determine order of iteration over obj props
|
||||
// if object, extract keys, in enumeration order, unsorted
|
||||
collectionKeys = [];
|
||||
for (var itemKey in collection) {
|
||||
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') {
|
||||
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
|
||||
collectionKeys.push(itemKey);
|
||||
}
|
||||
}
|
||||
collectionKeys.sort();
|
||||
}
|
||||
|
||||
collectionLength = collectionKeys.length;
|
||||
@@ -461,4 +462,3 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
@@ -40,11 +40,10 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
*
|
||||
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
|
||||
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
|
||||
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
|
||||
* with extra animation classes that can be added.
|
||||
* class in CSS:
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide:not(.ng-hide-animate) {
|
||||
* .ng-hide {
|
||||
* /* this is just another form of hiding an element */
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
|
||||
+205
-695
@@ -1,6 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
var ngOptionsMinErr = minErr('ngOptions');
|
||||
var noopNgModelController = { $setViewValue: noop, $render: noop };
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name select.SelectController
|
||||
* @description
|
||||
* The controller for the `<select>` directive. This provides support for reading
|
||||
* and writing the selected value(s) of the control and also coordinates dynamically
|
||||
* added `<option>` elements, perhaps by an `ngRepeat` directive.
|
||||
*/
|
||||
var SelectController =
|
||||
['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
||||
|
||||
var self = this,
|
||||
optionsMap = new HashMap();
|
||||
|
||||
// If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
|
||||
self.ngModelCtrl = noopNgModelController;
|
||||
|
||||
// The "unknown" option is one that is prepended to the list if the viewValue
|
||||
// does not match any of the options. When it is rendered the value of the unknown
|
||||
// option is '? XXX ?' where XXX is the hashKey of the value that is not known.
|
||||
//
|
||||
// We can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
self.unknownOption = jqLite(document.createElement('option'));
|
||||
self.renderUnknownOption = function(val) {
|
||||
var unknownVal = '? ' + hashKey(val) + ' ?';
|
||||
self.unknownOption.val(unknownVal);
|
||||
$element.prepend(self.unknownOption);
|
||||
$element.val(unknownVal);
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
// disable unknown option so that we don't do work when the whole select is being destroyed
|
||||
self.renderUnknownOption = noop;
|
||||
});
|
||||
|
||||
self.removeUnknownOption = function() {
|
||||
if (self.unknownOption.parent()) self.unknownOption.remove();
|
||||
};
|
||||
|
||||
// Here we find the option that represents the "empty" value, i.e. the option with a value
|
||||
// of `""`. This option needs to be accessed (to select it directly) when setting the value
|
||||
// of the select to `""` because IE9 will not automatically select the option.
|
||||
//
|
||||
// Additionally, the `ngOptions` directive uses this option to allow the application developer
|
||||
// to provide their own custom "empty" option when the viewValue does not match any of the
|
||||
// option values.
|
||||
for (var i = 0, children = $element.children(), ii = children.length; i < ii; i++) {
|
||||
if (children[i].value === '') {
|
||||
self.emptyOption = children.eq(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the value of the select control, the implementation of this changes depending
|
||||
// upon whether the select can have multiple values and whether ngOptions is at work.
|
||||
self.readValue = function readSingleValue() {
|
||||
self.removeUnknownOption();
|
||||
return $element.val();
|
||||
};
|
||||
|
||||
|
||||
// Write the value to the select control, the implementation of this changes depending
|
||||
// upon whether the select can have multiple values and whether ngOptions is at work.
|
||||
self.writeValue = function writeSingleValue(value) {
|
||||
if (self.hasOption(value)) {
|
||||
self.removeUnknownOption();
|
||||
$element.val(value);
|
||||
if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
|
||||
} else {
|
||||
if (isUndefined(value) && self.emptyOption) {
|
||||
$element.val('');
|
||||
} else {
|
||||
self.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Tell the select control that an option, with the given value, has been added
|
||||
self.addOption = function(value) {
|
||||
assertNotHasOwnProperty(value, '"option value"');
|
||||
var count = optionsMap.get(value) || 0;
|
||||
optionsMap.put(value, count + 1);
|
||||
};
|
||||
|
||||
// Tell the select control that an option, with the given value, has been removed
|
||||
self.removeOption = function(value) {
|
||||
var count = optionsMap.get(value);
|
||||
if (count) {
|
||||
if (count === 1) {
|
||||
optionsMap.remove(value);
|
||||
} else {
|
||||
optionsMap.put(value, count - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check whether the select control has an option matching the given value
|
||||
self.hasOption = function(value) {
|
||||
return !!optionsMap.get(value);
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name select
|
||||
@@ -9,24 +114,21 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* @description
|
||||
* HTML `SELECT` element with angular data-binding.
|
||||
*
|
||||
* # `ngOptions`
|
||||
*
|
||||
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
||||
* elements for the `<select>` element using the array or object obtained by evaluating the
|
||||
* `ngOptions` comprehension expression.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
|
||||
* increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
|
||||
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing
|
||||
* memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
|
||||
* to a non-string value. This is because an option element can only be bound to string values at
|
||||
* to a non-string value. This is because an option element can only be bound to string values at
|
||||
* present.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
* directive.
|
||||
*
|
||||
* If the viewValue contains a value that doesn't match any of the options then the control
|
||||
* will automatically add an "unknown" option, which it then removes when this is resolved.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
@@ -36,307 +138,61 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
|
||||
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
|
||||
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
*
|
||||
* ### `select` **`as`** and **`track by`**
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
|
||||
* </div>
|
||||
*
|
||||
* Consider the following example:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
* }, {
|
||||
* id: 2,
|
||||
* label: 'bLabel',
|
||||
* subItem: { name: 'bSubItem' }
|
||||
* }];
|
||||
*
|
||||
* $scope.selected = { name: 'aSubItem' };
|
||||
* ```
|
||||
*
|
||||
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
|
||||
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
|
||||
* following:
|
||||
*
|
||||
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
|
||||
* 2. Apply **`track by`** to the already selected value in `ngModel`.
|
||||
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
|
||||
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
|
||||
* selected" option.
|
||||
*
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} required The control is considered valid only if value is entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
* `required` when you want to data-bind to the `required` attribute.
|
||||
* @param {comprehension_expression=} ngOptions in one of the following forms:
|
||||
*
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
||||
* (for including a filter with `track by`)
|
||||
* * for object data sources:
|
||||
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`group by`** `group`
|
||||
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
|
||||
* * `value`: local variable which will refer to each item in the `array` or each property value
|
||||
* of `object` during iteration.
|
||||
* * `key`: local variable which will refer to a property name in `object` during iteration.
|
||||
* * `label`: The result of this expression will be the label for `<option>` element. The
|
||||
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
|
||||
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
|
||||
* element. If not specified, `select` expression will default to `value`.
|
||||
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
||||
* DOM element.
|
||||
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
||||
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
* even when the options are recreated (e.g. reloaded from the server).
|
||||
*
|
||||
* @example
|
||||
<example module="selectExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('selectExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.colors = [
|
||||
{name:'black', shade:'dark'},
|
||||
{name:'white', shade:'light'},
|
||||
{name:'red', shade:'dark'},
|
||||
{name:'blue', shade:'dark'},
|
||||
{name:'yellow', shade:'light'}
|
||||
];
|
||||
$scope.myColor = $scope.colors[2]; // red
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<ul>
|
||||
<li ng-repeat="color in colors">
|
||||
Name: <input ng-model="color.name">
|
||||
[<a href ng-click="colors.splice($index, 1)">X</a>]
|
||||
</li>
|
||||
<li>
|
||||
[<a href ng-click="colors.push({})">add</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
Color (null not allowed):
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors"></select><br>
|
||||
|
||||
Color (null allowed):
|
||||
<span class="nullable">
|
||||
<select ng-model="myColor" ng-options="color.name for color in colors">
|
||||
<option value="">-- choose color --</option>
|
||||
</select>
|
||||
</span><br/>
|
||||
|
||||
Color grouped by shade:
|
||||
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
|
||||
</select><br/>
|
||||
|
||||
|
||||
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
|
||||
<hr/>
|
||||
Currently selected: {{ {selected_color:myColor} }}
|
||||
<div style="border:solid 1px black; height:20px"
|
||||
ng-style="{'background-color':myColor.name}">
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ng-options', function() {
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
|
||||
element.all(by.model('myColor')).first().click();
|
||||
element.all(by.css('select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
|
||||
element(by.css('.nullable select[ng-model="myColor"]')).click();
|
||||
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
|
||||
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
var ngOptionsDirective = valueFn({
|
||||
restrict: 'A',
|
||||
terminal: true
|
||||
});
|
||||
|
||||
// jshint maxlen: false
|
||||
var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
|
||||
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
|
||||
nullModelCtrl = {$setViewValue: noop};
|
||||
// jshint maxlen: 100
|
||||
var selectDirective = function() {
|
||||
var lastView;
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: ['select', '?ngModel'],
|
||||
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
||||
var self = this,
|
||||
optionsMap = {},
|
||||
ngModelCtrl = nullModelCtrl,
|
||||
nullOption,
|
||||
unknownOption;
|
||||
|
||||
|
||||
self.databound = $attrs.ngModel;
|
||||
|
||||
|
||||
self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
|
||||
ngModelCtrl = ngModelCtrl_;
|
||||
nullOption = nullOption_;
|
||||
unknownOption = unknownOption_;
|
||||
};
|
||||
|
||||
|
||||
self.addOption = function(value, element) {
|
||||
assertNotHasOwnProperty(value, '"option value"');
|
||||
optionsMap[value] = true;
|
||||
|
||||
if (ngModelCtrl.$viewValue == value) {
|
||||
$element.val(value);
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
}
|
||||
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
|
||||
// Adding an <option selected="selected"> element to a <select required="required"> should
|
||||
// automatically select the new element
|
||||
if (element && element[0].hasAttribute('selected')) {
|
||||
element[0].selected = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.removeOption = function(value) {
|
||||
if (this.hasOption(value)) {
|
||||
delete optionsMap[value];
|
||||
if (ngModelCtrl.$viewValue === value) {
|
||||
this.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.renderUnknownOption = function(val) {
|
||||
var unknownVal = '? ' + hashKey(val) + ' ?';
|
||||
unknownOption.val(unknownVal);
|
||||
$element.prepend(unknownOption);
|
||||
$element.val(unknownVal);
|
||||
unknownOption.prop('selected', true); // needed for IE
|
||||
};
|
||||
|
||||
|
||||
self.hasOption = function(value) {
|
||||
return optionsMap.hasOwnProperty(value);
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
// disable unknown option so that we don't do work when the whole select is being destroyed
|
||||
self.renderUnknownOption = noop;
|
||||
});
|
||||
}],
|
||||
|
||||
controller: SelectController,
|
||||
link: function(scope, element, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
if (!ctrls[1]) return;
|
||||
var ngModelCtrl = ctrls[1];
|
||||
if (!ngModelCtrl) return;
|
||||
|
||||
var selectCtrl = ctrls[0],
|
||||
ngModelCtrl = ctrls[1],
|
||||
multiple = attr.multiple,
|
||||
optionsExp = attr.ngOptions,
|
||||
nullOption = false, // if false, user will not be able to select it (used by ngOptions)
|
||||
emptyOption,
|
||||
renderScheduled = false,
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
optionTemplate = jqLite(document.createElement('option')),
|
||||
optGroupTemplate =jqLite(document.createElement('optgroup')),
|
||||
unknownOption = optionTemplate.clone();
|
||||
var selectCtrl = ctrls[0];
|
||||
|
||||
// find "null" option
|
||||
for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
|
||||
if (children[i].value === '') {
|
||||
emptyOption = nullOption = children.eq(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
selectCtrl.ngModelCtrl = ngModelCtrl;
|
||||
|
||||
selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
|
||||
// We delegate rendering to the `writeValue` method, which can be changed
|
||||
// if the select can have multiple selected values or if the options are being
|
||||
// generated by `ngOptions`
|
||||
ngModelCtrl.$render = function() {
|
||||
selectCtrl.writeValue(ngModelCtrl.$viewValue);
|
||||
};
|
||||
|
||||
// required validator
|
||||
if (multiple) {
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
};
|
||||
}
|
||||
|
||||
if (optionsExp) setupAsOptions(scope, element, ngModelCtrl);
|
||||
else if (multiple) setupAsMultiple(scope, element, ngModelCtrl);
|
||||
else setupAsSingle(scope, element, ngModelCtrl, selectCtrl);
|
||||
|
||||
|
||||
////////////////////////////
|
||||
|
||||
|
||||
|
||||
function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) {
|
||||
ngModelCtrl.$render = function() {
|
||||
var viewValue = ngModelCtrl.$viewValue;
|
||||
|
||||
if (selectCtrl.hasOption(viewValue)) {
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
selectElement.val(viewValue);
|
||||
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
|
||||
} else {
|
||||
if (isUndefined(viewValue) && emptyOption) {
|
||||
selectElement.val('');
|
||||
} else {
|
||||
selectCtrl.renderUnknownOption(viewValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectElement.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
ngModelCtrl.$setViewValue(selectElement.val());
|
||||
});
|
||||
// When the selected item(s) changes we delegate getting the value of the select control
|
||||
// to the `readValue` method, which can be changed if the select can have multiple
|
||||
// selected values or if the options are being generated by `ngOptions`
|
||||
element.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(selectCtrl.readValue());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function setupAsMultiple(scope, selectElement, ctrl) {
|
||||
var lastView;
|
||||
ctrl.$render = function() {
|
||||
var items = new HashMap(ctrl.$viewValue);
|
||||
forEach(selectElement.find('option'), function(option) {
|
||||
// If the select allows multiple values then we need to modify how we read and write
|
||||
// values from and to the control; also what it means for the value to be empty and
|
||||
// we have to add an extra watch since ngModel doesn't work well with arrays - it
|
||||
// doesn't trigger rendering if only an item in the array changes.
|
||||
if (attr.multiple) {
|
||||
|
||||
// Read value now needs to check each option to see if it is selected
|
||||
selectCtrl.readValue = function readMultipleValue() {
|
||||
var array = [];
|
||||
forEach(element.find('option'), function(option) {
|
||||
if (option.selected) {
|
||||
array.push(option.value);
|
||||
}
|
||||
});
|
||||
return array;
|
||||
};
|
||||
|
||||
// Write value now needs to set the selected property of each matching option
|
||||
selectCtrl.writeValue = function writeMultipleValue(value) {
|
||||
var items = new HashMap(value);
|
||||
forEach(element.find('option'), function(option) {
|
||||
option.selected = isDefined(items.get(option.value));
|
||||
});
|
||||
};
|
||||
@@ -344,400 +200,45 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// we have to do it on each watch since ngModel watches reference, but
|
||||
// we need to work of an array, so we need to see if anything was inserted/removed
|
||||
scope.$watch(function selectMultipleWatch() {
|
||||
if (!equals(lastView, ctrl.$viewValue)) {
|
||||
lastView = shallowCopy(ctrl.$viewValue);
|
||||
ctrl.$render();
|
||||
if (!equals(lastView, ngModelCtrl.$viewValue)) {
|
||||
lastView = shallowCopy(ngModelCtrl.$viewValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
});
|
||||
|
||||
selectElement.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
var array = [];
|
||||
forEach(selectElement.find('option'), function(option) {
|
||||
if (option.selected) {
|
||||
array.push(option.value);
|
||||
}
|
||||
});
|
||||
ctrl.$setViewValue(array);
|
||||
});
|
||||
});
|
||||
}
|
||||
// If we are a multiple select then value is now a collection
|
||||
// so the meaning of $isEmpty changes
|
||||
ngModelCtrl.$isEmpty = function(value) {
|
||||
return !value || value.length === 0;
|
||||
};
|
||||
|
||||
function setupAsOptions(scope, selectElement, ctrl) {
|
||||
var match;
|
||||
|
||||
if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) {
|
||||
throw ngOptionsMinErr('iexp',
|
||||
"Expected expression in form of " +
|
||||
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
|
||||
" but got '{0}'. Element: {1}",
|
||||
optionsExp, startingTag(selectElement));
|
||||
}
|
||||
|
||||
var displayFn = $parse(match[2] || match[1]),
|
||||
valueName = match[4] || match[6],
|
||||
selectAs = / as /.test(match[0]) && match[1],
|
||||
selectAsFn = selectAs ? $parse(selectAs) : null,
|
||||
keyName = match[5],
|
||||
groupByFn = $parse(match[3] || ''),
|
||||
valueFn = $parse(match[2] ? match[1] : valueName),
|
||||
valuesFn = $parse(match[7]),
|
||||
track = match[8],
|
||||
trackFn = track ? $parse(match[8]) : null,
|
||||
trackKeysCache = {},
|
||||
// This is an array of array of existing option groups in DOM.
|
||||
// We try to reuse these if possible
|
||||
// - optionGroupsCache[0] is the options with no option group
|
||||
// - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
|
||||
optionGroupsCache = [[{element: selectElement, label:''}]],
|
||||
//re-usable object to represent option's locals
|
||||
locals = {};
|
||||
|
||||
if (nullOption) {
|
||||
// compile the element since there might be bindings in it
|
||||
$compile(nullOption)(scope);
|
||||
|
||||
// remove the class, which is added automatically because we recompile the element and it
|
||||
// becomes the compilation root
|
||||
nullOption.removeClass('ng-scope');
|
||||
|
||||
// we need to remove it before calling selectElement.empty() because otherwise IE will
|
||||
// remove the label from the element. wtf?
|
||||
nullOption.remove();
|
||||
}
|
||||
|
||||
// clear contents, we'll add what's needed based on the model
|
||||
selectElement.empty();
|
||||
|
||||
selectElement.on('change', selectionChanged);
|
||||
|
||||
ctrl.$render = render;
|
||||
|
||||
scope.$watchCollection(valuesFn, scheduleRendering);
|
||||
scope.$watchCollection(getLabels, scheduleRendering);
|
||||
|
||||
if (multiple) {
|
||||
scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
function callExpression(exprFn, key, value) {
|
||||
locals[valueName] = value;
|
||||
if (keyName) locals[keyName] = key;
|
||||
return exprFn(scope, locals);
|
||||
}
|
||||
|
||||
function selectionChanged() {
|
||||
scope.$apply(function() {
|
||||
var collection = valuesFn(scope) || [];
|
||||
var viewValue;
|
||||
if (multiple) {
|
||||
viewValue = [];
|
||||
forEach(selectElement.val(), function(selectedKey) {
|
||||
selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
|
||||
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
|
||||
});
|
||||
} else {
|
||||
var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
|
||||
viewValue = getViewValue(selectedKey, collection[selectedKey]);
|
||||
}
|
||||
ctrl.$setViewValue(viewValue);
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function getViewValue(key, value) {
|
||||
if (key === '?') {
|
||||
return undefined;
|
||||
} else if (key === '') {
|
||||
return null;
|
||||
} else {
|
||||
var viewValueFn = selectAsFn ? selectAsFn : valueFn;
|
||||
return callExpression(viewValueFn, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
var values = valuesFn(scope);
|
||||
var toDisplay;
|
||||
if (values && isArray(values)) {
|
||||
toDisplay = new Array(values.length);
|
||||
for (var i = 0, ii = values.length; i < ii; i++) {
|
||||
toDisplay[i] = callExpression(displayFn, i, values[i]);
|
||||
}
|
||||
return toDisplay;
|
||||
} else if (values) {
|
||||
// TODO: Add a test for this case
|
||||
toDisplay = {};
|
||||
for (var prop in values) {
|
||||
if (values.hasOwnProperty(prop)) {
|
||||
toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toDisplay;
|
||||
}
|
||||
|
||||
function createIsSelectedFn(viewValue) {
|
||||
var selectedSet;
|
||||
if (multiple) {
|
||||
if (trackFn && isArray(viewValue)) {
|
||||
|
||||
selectedSet = new HashMap([]);
|
||||
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
|
||||
// tracking by key
|
||||
selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
|
||||
}
|
||||
} else {
|
||||
selectedSet = new HashMap(viewValue);
|
||||
}
|
||||
} else if (trackFn) {
|
||||
viewValue = callExpression(trackFn, null, viewValue);
|
||||
}
|
||||
|
||||
return function isSelected(key, value) {
|
||||
var compareValueFn;
|
||||
if (trackFn) {
|
||||
compareValueFn = trackFn;
|
||||
} else if (selectAsFn) {
|
||||
compareValueFn = selectAsFn;
|
||||
} else {
|
||||
compareValueFn = valueFn;
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
|
||||
} else {
|
||||
return viewValue === callExpression(compareValueFn, key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRendering() {
|
||||
if (!renderScheduled) {
|
||||
scope.$$postDigest(render);
|
||||
renderScheduled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A new labelMap is created with each render.
|
||||
* This function is called for each existing option with added=false,
|
||||
* and each new option with added=true.
|
||||
* - Labels that are passed to this method twice,
|
||||
* (once with added=true and once with added=false) will end up with a value of 0, and
|
||||
* will cause no change to happen to the corresponding option.
|
||||
* - Labels that are passed to this method only once with added=false will end up with a
|
||||
* value of -1 and will eventually be passed to selectCtrl.removeOption()
|
||||
* - Labels that are passed to this method only once with added=true will end up with a
|
||||
* value of 1 and will eventually be passed to selectCtrl.addOption()
|
||||
*/
|
||||
function updateLabelMap(labelMap, label, added) {
|
||||
labelMap[label] = labelMap[label] || 0;
|
||||
labelMap[label] += (added ? 1 : -1);
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderScheduled = false;
|
||||
|
||||
// Temporary location for the option groups before we render them
|
||||
var optionGroups = {'':[]},
|
||||
optionGroupNames = [''],
|
||||
optionGroupName,
|
||||
optionGroup,
|
||||
option,
|
||||
existingParent, existingOptions, existingOption,
|
||||
viewValue = ctrl.$viewValue,
|
||||
values = valuesFn(scope) || [],
|
||||
keys = keyName ? sortedKeys(values) : values,
|
||||
key,
|
||||
value,
|
||||
groupLength, length,
|
||||
groupIndex, index,
|
||||
labelMap = {},
|
||||
selected,
|
||||
isSelected = createIsSelectedFn(viewValue),
|
||||
anySelected = false,
|
||||
lastElement,
|
||||
element,
|
||||
label,
|
||||
optionId;
|
||||
|
||||
trackKeysCache = {};
|
||||
|
||||
// We now build up the list of options we need (we merge later)
|
||||
for (index = 0; length = keys.length, index < length; index++) {
|
||||
key = index;
|
||||
if (keyName) {
|
||||
key = keys[index];
|
||||
if (key.charAt(0) === '$') continue;
|
||||
}
|
||||
value = values[key];
|
||||
|
||||
optionGroupName = callExpression(groupByFn, key, value) || '';
|
||||
if (!(optionGroup = optionGroups[optionGroupName])) {
|
||||
optionGroup = optionGroups[optionGroupName] = [];
|
||||
optionGroupNames.push(optionGroupName);
|
||||
}
|
||||
|
||||
selected = isSelected(key, value);
|
||||
anySelected = anySelected || selected;
|
||||
|
||||
label = callExpression(displayFn, key, value); // what will be seen by the user
|
||||
|
||||
// doing displayFn(scope, locals) || '' overwrites zero values
|
||||
label = isDefined(label) ? label : '';
|
||||
optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
|
||||
if (trackFn) {
|
||||
trackKeysCache[optionId] = key;
|
||||
}
|
||||
|
||||
optionGroup.push({
|
||||
// either the index into array or key from object
|
||||
id: optionId,
|
||||
label: label,
|
||||
selected: selected // determine if we should be selected
|
||||
});
|
||||
}
|
||||
if (!multiple) {
|
||||
if (nullOption || viewValue === null) {
|
||||
// insert null option if we have a placeholder, or the model is null
|
||||
optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
|
||||
} else if (!anySelected) {
|
||||
// option could not be found, we have to insert the undefined item
|
||||
optionGroups[''].unshift({id:'?', label:'', selected:true});
|
||||
}
|
||||
}
|
||||
|
||||
// Now we need to update the list of DOM nodes to match the optionGroups we computed above
|
||||
for (groupIndex = 0, groupLength = optionGroupNames.length;
|
||||
groupIndex < groupLength;
|
||||
groupIndex++) {
|
||||
// current option group name or '' if no group
|
||||
optionGroupName = optionGroupNames[groupIndex];
|
||||
|
||||
// list of options for that group. (first item has the parent)
|
||||
optionGroup = optionGroups[optionGroupName];
|
||||
|
||||
if (optionGroupsCache.length <= groupIndex) {
|
||||
// we need to grow the optionGroups
|
||||
existingParent = {
|
||||
element: optGroupTemplate.clone().attr('label', optionGroupName),
|
||||
label: optionGroup.label
|
||||
};
|
||||
existingOptions = [existingParent];
|
||||
optionGroupsCache.push(existingOptions);
|
||||
selectElement.append(existingParent.element);
|
||||
} else {
|
||||
existingOptions = optionGroupsCache[groupIndex];
|
||||
existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
|
||||
|
||||
// update the OPTGROUP label if not the same.
|
||||
if (existingParent.label != optionGroupName) {
|
||||
existingParent.element.attr('label', existingParent.label = optionGroupName);
|
||||
}
|
||||
}
|
||||
|
||||
lastElement = null; // start at the beginning
|
||||
for (index = 0, length = optionGroup.length; index < length; index++) {
|
||||
option = optionGroup[index];
|
||||
if ((existingOption = existingOptions[index + 1])) {
|
||||
// reuse elements
|
||||
lastElement = existingOption.element;
|
||||
if (existingOption.label !== option.label) {
|
||||
updateLabelMap(labelMap, existingOption.label, false);
|
||||
updateLabelMap(labelMap, option.label, true);
|
||||
lastElement.text(existingOption.label = option.label);
|
||||
lastElement.prop('label', existingOption.label);
|
||||
}
|
||||
if (existingOption.id !== option.id) {
|
||||
lastElement.val(existingOption.id = option.id);
|
||||
}
|
||||
// lastElement.prop('selected') provided by jQuery has side-effects
|
||||
if (lastElement[0].selected !== option.selected) {
|
||||
lastElement.prop('selected', (existingOption.selected = option.selected));
|
||||
if (msie) {
|
||||
// See #7692
|
||||
// The selected item wouldn't visually update on IE without this.
|
||||
// Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well
|
||||
lastElement.prop('selected', existingOption.selected);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// grow elements
|
||||
|
||||
// if it's a null option
|
||||
if (option.id === '' && nullOption) {
|
||||
// put back the pre-compiled element
|
||||
element = nullOption;
|
||||
} else {
|
||||
// jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
|
||||
// in this version of jQuery on some browser the .text() returns a string
|
||||
// rather then the element.
|
||||
(element = optionTemplate.clone())
|
||||
.val(option.id)
|
||||
.prop('selected', option.selected)
|
||||
.attr('selected', option.selected)
|
||||
.prop('label', option.label)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
existingOptions.push(existingOption = {
|
||||
element: element,
|
||||
label: option.label,
|
||||
id: option.id,
|
||||
selected: option.selected
|
||||
});
|
||||
updateLabelMap(labelMap, option.label, true);
|
||||
if (lastElement) {
|
||||
lastElement.after(element);
|
||||
} else {
|
||||
existingParent.element.append(element);
|
||||
}
|
||||
lastElement = element;
|
||||
}
|
||||
}
|
||||
// remove any excessive OPTIONs in a group
|
||||
index++; // increment since the existingOptions[0] is parent element not OPTION
|
||||
while (existingOptions.length > index) {
|
||||
option = existingOptions.pop();
|
||||
updateLabelMap(labelMap, option.label, false);
|
||||
option.element.remove();
|
||||
}
|
||||
}
|
||||
// remove any excessive OPTGROUPs from select
|
||||
while (optionGroupsCache.length > groupIndex) {
|
||||
// remove all the labels in the option group
|
||||
optionGroup = optionGroupsCache.pop();
|
||||
for (index = 1; index < optionGroup.length; ++index) {
|
||||
updateLabelMap(labelMap, optionGroup[index].label, false);
|
||||
}
|
||||
optionGroup[0].element.remove();
|
||||
}
|
||||
forEach(labelMap, function(count, label) {
|
||||
if (count > 0) {
|
||||
selectCtrl.addOption(label);
|
||||
} else if (count < 0) {
|
||||
selectCtrl.removeOption(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
|
||||
// The option directive is purely designed to communicate the existence (or lack of)
|
||||
// of dynamically created (and destroyed) option elements to their containing select
|
||||
// directive via its controller.
|
||||
var optionDirective = ['$interpolate', function($interpolate) {
|
||||
var nullSelectCtrl = {
|
||||
addOption: noop,
|
||||
removeOption: noop
|
||||
};
|
||||
|
||||
function chromeHack(optionElement) {
|
||||
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
|
||||
// Adding an <option selected="selected"> element to a <select required="required"> should
|
||||
// automatically select the new element
|
||||
if (optionElement[0].hasAttribute('selected')) {
|
||||
optionElement[0].selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 100,
|
||||
compile: function(element, attr) {
|
||||
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
if (isUndefined(attr.value)) {
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (!interpolateFn) {
|
||||
@@ -746,30 +247,39 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
}
|
||||
|
||||
return function(scope, element, attr) {
|
||||
|
||||
// This is an optimization over using ^^ since we don't want to have to search
|
||||
// all the way to the root of the DOM for every single option element
|
||||
var selectCtrlName = '$selectController',
|
||||
parent = element.parent(),
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
if (!selectCtrl || !selectCtrl.databound) {
|
||||
selectCtrl = nullSelectCtrl;
|
||||
}
|
||||
// Only update trigger option updates if this is an option within a `select`
|
||||
// that also has `ngModel` attached
|
||||
if (selectCtrl && selectCtrl.ngModelCtrl) {
|
||||
|
||||
if (interpolateFn) {
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
selectCtrl.addOption(newVal, element);
|
||||
if (interpolateFn) {
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
selectCtrl.addOption(newVal, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value, element);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,12 +60,12 @@ var maxlengthDirective = function() {
|
||||
|
||||
var maxlength = -1;
|
||||
attr.$observe('maxlength', function(value) {
|
||||
var intVal = int(value);
|
||||
var intVal = toInt(value);
|
||||
maxlength = isNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
|
||||
return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -80,7 +80,7 @@ var minlengthDirective = function() {
|
||||
|
||||
var minlength = 0;
|
||||
attr.$observe('minlength', function(value) {
|
||||
minlength = int(value) || 0;
|
||||
minlength = toInt(value) || 0;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
|
||||
@@ -124,7 +124,13 @@
|
||||
*/
|
||||
function filterFilter() {
|
||||
return function(array, expression, comparator) {
|
||||
if (!isArray(array)) return array;
|
||||
if (!isArray(array)) {
|
||||
if (array == null) {
|
||||
return array;
|
||||
} else {
|
||||
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
|
||||
}
|
||||
}
|
||||
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
|
||||
+20
-12
@@ -82,6 +82,8 @@ function currencyFilter($locale) {
|
||||
*
|
||||
* If the input is not a number an empty string is returned.
|
||||
*
|
||||
* If the input is an infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
|
||||
*
|
||||
* @param {number|string} number Number to format.
|
||||
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
|
||||
* If this is not provided then the fraction size is computed from the current locale's number
|
||||
@@ -138,16 +140,22 @@ function numberFilter($locale) {
|
||||
|
||||
var DECIMAL_SEP = '.';
|
||||
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (!isFinite(number) || isObject(number)) return '';
|
||||
if (isObject(number)) return '';
|
||||
|
||||
var isNegative = number < 0;
|
||||
number = Math.abs(number);
|
||||
|
||||
var isInfinity = number === Infinity;
|
||||
if (!isInfinity && !isFinite(number)) return '';
|
||||
|
||||
var numStr = number + '',
|
||||
formatedText = '',
|
||||
hasExponent = false,
|
||||
parts = [];
|
||||
|
||||
var hasExponent = false;
|
||||
if (numStr.indexOf('e') !== -1) {
|
||||
if (isInfinity) formatedText = '\u221e';
|
||||
|
||||
if (!isInfinity && numStr.indexOf('e') !== -1) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
number = 0;
|
||||
@@ -157,7 +165,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasExponent) {
|
||||
if (!isInfinity && !hasExponent) {
|
||||
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
|
||||
|
||||
// determine fractionSize if it is not specified
|
||||
@@ -429,13 +437,13 @@ function dateFilter($locale) {
|
||||
timeSetter = match[8] ? date.setUTCHours : date.setHours;
|
||||
|
||||
if (match[9]) {
|
||||
tzHour = int(match[9] + match[10]);
|
||||
tzMin = int(match[9] + match[11]);
|
||||
tzHour = toInt(match[9] + match[10]);
|
||||
tzMin = toInt(match[9] + match[11]);
|
||||
}
|
||||
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
var h = int(match[4] || 0) - tzHour;
|
||||
var m = int(match[5] || 0) - tzMin;
|
||||
var s = int(match[6] || 0);
|
||||
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
|
||||
var h = toInt(match[4] || 0) - tzHour;
|
||||
var m = toInt(match[5] || 0) - tzMin;
|
||||
var s = toInt(match[6] || 0);
|
||||
var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
@@ -452,14 +460,14 @@ function dateFilter($locale) {
|
||||
format = format || 'mediumDate';
|
||||
format = $locale.DATETIME_FORMATS[format] || format;
|
||||
if (isString(date)) {
|
||||
date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date);
|
||||
date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
|
||||
}
|
||||
|
||||
if (isNumber(date)) {
|
||||
date = new Date(date);
|
||||
}
|
||||
|
||||
if (!isDate(date)) {
|
||||
if (!isDate(date) || !isFinite(date.getTime())) {
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
* @param {string|number} limit The length of the returned array or string. If the `limit` number
|
||||
* is positive, `limit` number of items from the beginning of the source array/string are copied.
|
||||
* If the number is negative, `limit` number of items from the end of the source array/string
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
|
||||
* the input will be returned unchanged.
|
||||
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
|
||||
* had less than `limit` elements.
|
||||
*
|
||||
@@ -88,20 +89,16 @@
|
||||
*/
|
||||
function limitToFilter() {
|
||||
return function(input, limit) {
|
||||
if (isNumber(input)) input = input.toString();
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
if (Math.abs(Number(limit)) === Infinity) {
|
||||
limit = Number(limit);
|
||||
} else {
|
||||
limit = int(limit);
|
||||
limit = toInt(limit);
|
||||
}
|
||||
if (isNaN(limit)) return input;
|
||||
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit > 0 ? input.slice(0, limit) : input.slice(limit);
|
||||
} else {
|
||||
return isString(input) ? "" : [];
|
||||
}
|
||||
if (isNumber(input)) input = input.toString();
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit);
|
||||
};
|
||||
}
|
||||
|
||||
+5
-5
@@ -347,7 +347,7 @@ function $HttpProvider() {
|
||||
* To add or overwrite these defaults, simply add or remove a property from these configuration
|
||||
* objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
|
||||
* with the lowercased HTTP method name as the key, e.g.
|
||||
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
|
||||
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
|
||||
*
|
||||
* The defaults can also be set at runtime via the `$http.defaults` object in the same
|
||||
* fashion. For example:
|
||||
@@ -615,7 +615,7 @@ function $HttpProvider() {
|
||||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||||
* - **headers** – `{Object}` – Map of strings or functions which return strings representing
|
||||
* HTTP headers to send to the server. If the return value of a function is null, the
|
||||
* header will not be sent.
|
||||
* header will not be sent. Functions accept a config object as an argument.
|
||||
* - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
|
||||
* - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
|
||||
* - **transformRequest** –
|
||||
@@ -834,12 +834,12 @@ function $HttpProvider() {
|
||||
: $q.reject(resp);
|
||||
}
|
||||
|
||||
function executeHeaderFns(headers) {
|
||||
function executeHeaderFns(headers, config) {
|
||||
var headerContent, processedHeaders = {};
|
||||
|
||||
forEach(headers, function(headerFn, header) {
|
||||
if (isFunction(headerFn)) {
|
||||
headerContent = headerFn();
|
||||
headerContent = headerFn(config);
|
||||
if (headerContent != null) {
|
||||
processedHeaders[header] = headerContent;
|
||||
}
|
||||
@@ -873,7 +873,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
// execute if header value is a function for merged headers
|
||||
return executeHeaderFns(reqHeaders);
|
||||
return executeHeaderFns(reqHeaders, shallowCopy(config));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
|
||||
locationObj.$$protocol = parsedUrl.protocol;
|
||||
locationObj.$$host = parsedUrl.hostname;
|
||||
locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
|
||||
locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
|
||||
}
|
||||
|
||||
|
||||
@@ -879,7 +879,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
|
||||
if ($location.absUrl() != initialUrl) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
|
||||
+7
-13
@@ -664,7 +664,7 @@ Parser.prototype = {
|
||||
}, {
|
||||
assign: function(scope, value, locals) {
|
||||
var o = object(scope, locals);
|
||||
if (!o) object.assign(scope, o = {}, locals);
|
||||
if (!o) object.assign(scope, o = {});
|
||||
return getter.assign(o, value);
|
||||
}
|
||||
});
|
||||
@@ -690,7 +690,7 @@ Parser.prototype = {
|
||||
var key = ensureSafeMemberName(indexFn(self, locals), expression);
|
||||
// prevent overwriting of Function.constructor which would break ensureSafeObject check
|
||||
var o = ensureSafeObject(obj(self, locals), expression);
|
||||
if (!o) obj.assign(self, o = {}, locals);
|
||||
if (!o) obj.assign(self, o = {});
|
||||
return o[key] = value;
|
||||
}
|
||||
});
|
||||
@@ -728,11 +728,6 @@ Parser.prototype = {
|
||||
? fn.apply(context, args)
|
||||
: fn(args[0], args[1], args[2], args[3], args[4]);
|
||||
|
||||
if (args) {
|
||||
// Free-up the memory (arguments of the last function call).
|
||||
args.length = 0;
|
||||
}
|
||||
|
||||
return ensureSafeObject(v, expressionText);
|
||||
};
|
||||
},
|
||||
@@ -805,19 +800,18 @@ Parser.prototype = {
|
||||
// Parser helper functions
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
function setter(obj, locals, path, setValue, fullExp) {
|
||||
function setter(obj, path, setValue, fullExp) {
|
||||
ensureSafeObject(obj, fullExp);
|
||||
ensureSafeObject(locals, fullExp);
|
||||
|
||||
var element = path.split('.'), key;
|
||||
for (var i = 0; element.length > 1; i++) {
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
|
||||
var propertyObj = ensureSafeObject(obj[key], fullExp);
|
||||
if (!propertyObj) {
|
||||
propertyObj = {};
|
||||
obj[key] = propertyObj;
|
||||
}
|
||||
obj = ensureSafeObject(propertyObj, fullExp);
|
||||
obj = propertyObj;
|
||||
}
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
ensureSafeObject(obj[key], fullExp);
|
||||
@@ -944,8 +938,8 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
|
||||
fn.sharedGetter = true;
|
||||
fn.assign = function(self, value, locals) {
|
||||
return setter(self, locals, path, value, path);
|
||||
fn.assign = function(self, value) {
|
||||
return setter(self, path, value, path);
|
||||
};
|
||||
getterFnCache[path] = fn;
|
||||
return fn;
|
||||
|
||||
+8
-7
@@ -302,24 +302,24 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
}
|
||||
|
||||
function processQueue(state) {
|
||||
var fn, promise, pending;
|
||||
var fn, deferred, pending;
|
||||
|
||||
pending = state.pending;
|
||||
state.processScheduled = false;
|
||||
state.pending = undefined;
|
||||
for (var i = 0, ii = pending.length; i < ii; ++i) {
|
||||
promise = pending[i][0];
|
||||
deferred = pending[i][0];
|
||||
fn = pending[i][state.status];
|
||||
try {
|
||||
if (isFunction(fn)) {
|
||||
promise.resolve(fn(state.value));
|
||||
deferred.resolve(fn(state.value));
|
||||
} else if (state.status === 1) {
|
||||
promise.resolve(state.value);
|
||||
deferred.resolve(state.value);
|
||||
} else {
|
||||
promise.reject(state.value);
|
||||
deferred.reject(state.value);
|
||||
}
|
||||
} catch (e) {
|
||||
promise.reject(e);
|
||||
deferred.reject(e);
|
||||
exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,8 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
'qcycle',
|
||||
"Expected promise to be resolved with value other than itself '{0}'",
|
||||
val));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.$$resolve(val);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ function $SnifferProvider() {
|
||||
this.$get = ['$window', '$document', function($window, $document) {
|
||||
var eventSupport = {},
|
||||
android =
|
||||
int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
|
||||
toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
|
||||
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
|
||||
document = $document[0] || {},
|
||||
vendorPrefix,
|
||||
@@ -45,8 +45,8 @@ function $SnifferProvider() {
|
||||
animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
|
||||
|
||||
if (android && (!transitions || !animations)) {
|
||||
transitions = isString(document.body.style.webkitTransition);
|
||||
animations = isString(document.body.style.webkitAnimation);
|
||||
transitions = isString(bodyStyle.webkitTransition);
|
||||
animations = isString(bodyStyle.webkitAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ var $compileMinErr = minErr('$compile');
|
||||
* @param {string} tpl The HTTP request template URL
|
||||
* @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
|
||||
*
|
||||
* @return {Promise} the HTTP Promise for the given.
|
||||
* @return {Promise} a promise for the HTTP response data of the given URL.
|
||||
*
|
||||
* @property {number} totalPendingRequests total amount of pending template requests being downloaded.
|
||||
*/
|
||||
@@ -44,12 +44,14 @@ function $TemplateRequestProvider() {
|
||||
handleRequestFn.totalPendingRequests--;
|
||||
})
|
||||
.then(function(response) {
|
||||
$templateCache.put(tpl, response.data);
|
||||
return response.data;
|
||||
}, handleError);
|
||||
|
||||
function handleError(resp) {
|
||||
if (!ignoreRequestError) {
|
||||
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
|
||||
throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
|
||||
tpl, resp.status, resp.statusText);
|
||||
}
|
||||
return $q.reject(resp);
|
||||
}
|
||||
|
||||
+13
-3
@@ -4,6 +4,7 @@
|
||||
function $TimeoutProvider() {
|
||||
this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
|
||||
function($rootScope, $browser, $q, $$q, $exceptionHandler) {
|
||||
|
||||
var deferreds = {};
|
||||
|
||||
|
||||
@@ -16,15 +17,18 @@ function $TimeoutProvider() {
|
||||
* block and delegates any exceptions to
|
||||
* {@link ng.$exceptionHandler $exceptionHandler} service.
|
||||
*
|
||||
* The return value of registering a timeout function is a promise, which will be resolved when
|
||||
* the timeout is reached and the timeout function is executed.
|
||||
* The return value of calling `$timeout` is a promise, which will be resolved when
|
||||
* the delay has passed and the timeout function, if provided, is executed.
|
||||
*
|
||||
* To cancel a timeout request, call `$timeout.cancel(promise)`.
|
||||
*
|
||||
* In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
|
||||
* synchronously flush the queue of deferred functions.
|
||||
*
|
||||
* @param {function()} fn A function, whose execution should be delayed.
|
||||
* If you only want a promise that will be resolved after some specified delay
|
||||
* then you can call `$timeout` without the `fn` function.
|
||||
*
|
||||
* @param {function()=} fn A function, whose execution should be delayed.
|
||||
* @param {number=} [delay=0] Delay in milliseconds.
|
||||
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
|
||||
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
|
||||
@@ -33,6 +37,12 @@ function $TimeoutProvider() {
|
||||
*
|
||||
*/
|
||||
function timeout(fn, delay, invokeApply) {
|
||||
if (!isFunction(fn)) {
|
||||
invokeApply = delay;
|
||||
delay = fn;
|
||||
fn = noop;
|
||||
}
|
||||
|
||||
var skipApply = (isDefined(invokeApply) && !invokeApply),
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise,
|
||||
|
||||
@@ -1327,7 +1327,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
} else if (lastAnimation.event == 'setClass') {
|
||||
animationsToCancel.push(lastAnimation);
|
||||
cleanup(element, className);
|
||||
} else if (runningAnimations[className]) {
|
||||
}
|
||||
else if (runningAnimations[className]) {
|
||||
var current = runningAnimations[className];
|
||||
if (current.event == animationEvent) {
|
||||
skipAnimation = true;
|
||||
|
||||
+2
-10
@@ -100,8 +100,7 @@ function $AriaProvider() {
|
||||
* - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
|
||||
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
|
||||
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `<div>` and
|
||||
* `<li>` elements with ng-click
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on ng-click
|
||||
*
|
||||
* @description
|
||||
* Enables/disables various ARIA attributes
|
||||
@@ -304,18 +303,11 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
compile: function(elem, attr) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
function isNodeOneOf(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress && isNodeOneOf(elem, ['DIV', 'LI'])) {
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
|
||||
@@ -160,7 +160,7 @@ angular.module('ngCookies', ['ng']).
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value.
|
||||
* @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
|
||||
*/
|
||||
get: function(key) {
|
||||
var value = $cookies[key];
|
||||
|
||||
Vendored
+1
-1
@@ -81,7 +81,7 @@ $provide.value("$locale", {
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
"CURRENCY_SYM": "\u20ac",
|
||||
"CURRENCY_SYM": "Lt",
|
||||
"DECIMAL_SEP": ",",
|
||||
"GROUP_SEP": "\u00a0",
|
||||
"PATTERNS": [
|
||||
|
||||
Vendored
+1
-1
@@ -81,7 +81,7 @@ $provide.value("$locale", {
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
"CURRENCY_SYM": "\u20ac",
|
||||
"CURRENCY_SYM": "Lt",
|
||||
"DECIMAL_SEP": ",",
|
||||
"GROUP_SEP": "\u00a0",
|
||||
"PATTERNS": [
|
||||
|
||||
Vendored
+14
-14
@@ -40,26 +40,26 @@ $provide.value("$locale", {
|
||||
"\u1005\u1014\u1031"
|
||||
],
|
||||
"SHORTMONTH": [
|
||||
"\u1007\u1014\u103a",
|
||||
"\u1016\u1031",
|
||||
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
|
||||
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
|
||||
"\u1019\u1010\u103a",
|
||||
"\u1027\u1015\u103c\u102e",
|
||||
"\u1019\u1031",
|
||||
"\u1007\u103d\u1014\u103a",
|
||||
"\u1007\u1030",
|
||||
"\u1029",
|
||||
"\u1005\u1000\u103a",
|
||||
"\u1021\u1031\u102c\u1000\u103a",
|
||||
"\u1014\u102d\u102f",
|
||||
"\u1012\u102e"
|
||||
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
|
||||
"\u1029\u1002\u102f\u1010\u103a",
|
||||
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
|
||||
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
|
||||
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
|
||||
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
|
||||
],
|
||||
"fullDate": "EEEE, dd MMMM y",
|
||||
"longDate": "d MMMM y",
|
||||
"medium": "d MMM y HH:mm:ss",
|
||||
"mediumDate": "d MMM y",
|
||||
"fullDate": "EEEE, y MMMM dd",
|
||||
"longDate": "y MMMM d",
|
||||
"medium": "y MMM d HH:mm:ss",
|
||||
"mediumDate": "y MMM d",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "dd-MM-yy HH:mm",
|
||||
"shortDate": "dd-MM-yy",
|
||||
"short": "yy/MM/dd HH:mm",
|
||||
"shortDate": "yy/MM/dd",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
Vendored
+14
-14
@@ -40,26 +40,26 @@ $provide.value("$locale", {
|
||||
"\u1005\u1014\u1031"
|
||||
],
|
||||
"SHORTMONTH": [
|
||||
"\u1007\u1014\u103a",
|
||||
"\u1016\u1031",
|
||||
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
|
||||
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
|
||||
"\u1019\u1010\u103a",
|
||||
"\u1027\u1015\u103c\u102e",
|
||||
"\u1019\u1031",
|
||||
"\u1007\u103d\u1014\u103a",
|
||||
"\u1007\u1030",
|
||||
"\u1029",
|
||||
"\u1005\u1000\u103a",
|
||||
"\u1021\u1031\u102c\u1000\u103a",
|
||||
"\u1014\u102d\u102f",
|
||||
"\u1012\u102e"
|
||||
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
|
||||
"\u1029\u1002\u102f\u1010\u103a",
|
||||
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
|
||||
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
|
||||
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
|
||||
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
|
||||
],
|
||||
"fullDate": "EEEE, dd MMMM y",
|
||||
"longDate": "d MMMM y",
|
||||
"medium": "d MMM y HH:mm:ss",
|
||||
"mediumDate": "d MMM y",
|
||||
"fullDate": "EEEE, y MMMM dd",
|
||||
"longDate": "y MMMM d",
|
||||
"medium": "y MMM d HH:mm:ss",
|
||||
"mediumDate": "y MMM d",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "dd-MM-yy HH:mm",
|
||||
"shortDate": "dd-MM-yy",
|
||||
"short": "yy/MM/dd HH:mm",
|
||||
"shortDate": "yy/MM/dd",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
*
|
||||
* However, including generic messages may not be useful enough to match all input fields, therefore,
|
||||
* `ngMessages` provides the ability to override messages defined in the remote template by redefining
|
||||
* then within the directive container.
|
||||
* them within the directive container.
|
||||
*
|
||||
* ```html
|
||||
* <!-- a generic template of error messages known as "my-custom-messages" -->
|
||||
@@ -109,7 +109,7 @@
|
||||
*
|
||||
* Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS
|
||||
* class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no
|
||||
* animations present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
|
||||
* messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
|
||||
* hook into the animations whenever these classes are added/removed.
|
||||
*
|
||||
* Let's say that our HTML code for our messages container looks like so:
|
||||
@@ -380,7 +380,7 @@ angular.module('ngMessages', [])
|
||||
});
|
||||
}
|
||||
},
|
||||
detach: function(now) {
|
||||
detach: function() {
|
||||
if (element) {
|
||||
$animate.leave(element);
|
||||
element = null;
|
||||
|
||||
Vendored
+54
-68
@@ -574,20 +574,20 @@ function jsonStringToDate(string) {
|
||||
tzHour = 0,
|
||||
tzMin = 0;
|
||||
if (match[9]) {
|
||||
tzHour = int(match[9] + match[10]);
|
||||
tzMin = int(match[9] + match[11]);
|
||||
tzHour = toInt(match[9] + match[10]);
|
||||
tzMin = toInt(match[9] + match[11]);
|
||||
}
|
||||
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
date.setUTCHours(int(match[4] || 0) - tzHour,
|
||||
int(match[5] || 0) - tzMin,
|
||||
int(match[6] || 0),
|
||||
int(match[7] || 0));
|
||||
date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
|
||||
date.setUTCHours(toInt(match[4] || 0) - tzHour,
|
||||
toInt(match[5] || 0) - tzMin,
|
||||
toInt(match[6] || 0),
|
||||
toInt(match[7] || 0));
|
||||
return date;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
function int(str) {
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
||||
@@ -1227,8 +1227,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new backend definition.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
@@ -1273,8 +1273,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1287,8 +1287,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1301,8 +1301,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1315,8 +1315,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
@@ -1331,8 +1331,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
@@ -1347,8 +1347,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
@@ -1363,8 +1363,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* Creates a new request expectation.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
@@ -1402,8 +1402,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for GET requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1416,8 +1416,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1430,8 +1430,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
@@ -1444,8 +1444,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for POST requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
@@ -1461,8 +1461,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for PUT requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
@@ -1478,8 +1478,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
@@ -1495,8 +1495,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @description
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives an url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
@@ -1906,8 +1906,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* Creates a new backend definition.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
@@ -1934,8 +1934,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
@@ -1949,8 +1949,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
@@ -1964,8 +1964,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
@@ -1979,8 +1979,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
@@ -1995,8 +1995,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
@@ -2011,8 +2011,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for PATCH requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
@@ -2027,8 +2027,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
* @description
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
@@ -2127,32 +2127,18 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
|
||||
if (window.jasmine || window.mocha) {
|
||||
|
||||
var currentSpec = null,
|
||||
annotatedFunctions = [],
|
||||
isSpecRunning = function() {
|
||||
return !!currentSpec;
|
||||
};
|
||||
|
||||
angular.mock.$$annotate = angular.injector.$$annotate;
|
||||
angular.injector.$$annotate = function(fn) {
|
||||
if (typeof fn === 'function' && !fn.$inject) {
|
||||
annotatedFunctions.push(fn);
|
||||
}
|
||||
return angular.mock.$$annotate.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
(window.beforeEach || window.setup)(function() {
|
||||
annotatedFunctions = [];
|
||||
currentSpec = this;
|
||||
});
|
||||
|
||||
(window.afterEach || window.teardown)(function() {
|
||||
var injector = currentSpec.$injector;
|
||||
|
||||
annotatedFunctions.forEach(function(fn) {
|
||||
delete fn.$inject;
|
||||
});
|
||||
|
||||
angular.forEach(currentSpec.$modules, function(module) {
|
||||
if (module && module.$$hashKey) {
|
||||
module.$$hashKey = undefined;
|
||||
|
||||
+11
-5
@@ -477,15 +477,21 @@ function $RouteProvider() {
|
||||
* definitions will be interpolated into the location's path, while
|
||||
* remaining properties will be treated as query params.
|
||||
*
|
||||
* @param {!Object<string, string>} newParams mapping of URL parameter names to values
|
||||
* @param {Object} newParams mapping of URL parameter names to values
|
||||
*/
|
||||
updateParams: function(newParams) {
|
||||
if (this.current && this.current.$$route) {
|
||||
var searchParams = {}, self=this;
|
||||
|
||||
angular.forEach(Object.keys(newParams), function(key) {
|
||||
if (!self.current.pathParams[key]) searchParams[key] = newParams[key];
|
||||
});
|
||||
|
||||
newParams = angular.extend({}, this.current.params, newParams);
|
||||
$location.path(interpolate(this.current.$$route.originalPath, newParams));
|
||||
// interpolate modifies newParams, only query params are left
|
||||
$location.search(newParams);
|
||||
} else {
|
||||
$location.search(angular.extend({}, $location.search(), searchParams));
|
||||
}
|
||||
else {
|
||||
throw $routeMinErr('norout', 'Tried updating route when with no current route');
|
||||
}
|
||||
}
|
||||
@@ -601,8 +607,8 @@ function $RouteProvider() {
|
||||
return $q.all(locals);
|
||||
}
|
||||
}).
|
||||
// after route change
|
||||
then(function(locals) {
|
||||
// after route change
|
||||
if (nextRoute == $route.current) {
|
||||
if (nextRoute) {
|
||||
nextRoute.locals = locals;
|
||||
|
||||
@@ -271,14 +271,14 @@ function htmlParser(html, handler) {
|
||||
}
|
||||
}
|
||||
var index, chars, match, stack = [], last = html, text;
|
||||
stack.last = function() { return stack[stack.length - 1]; };
|
||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
||||
|
||||
while (html) {
|
||||
text = '';
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if (!stack.last() || !specialElements[stack.last()]) {
|
||||
if (!stack.last() || !specialElements[ stack.last() ]) {
|
||||
|
||||
// Comment
|
||||
if (html.indexOf("<!--") === 0) {
|
||||
@@ -336,8 +336,7 @@ function htmlParser(html, handler) {
|
||||
}
|
||||
|
||||
} else {
|
||||
// IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
|
||||
html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
||||
function(all, text) {
|
||||
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
|
||||
|
||||
@@ -361,17 +360,17 @@ function htmlParser(html, handler) {
|
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) {
|
||||
tagName = angular.lowercase(tagName);
|
||||
if (blockElements[tagName]) {
|
||||
while (stack.last() && inlineElements[stack.last()]) {
|
||||
if (blockElements[ tagName ]) {
|
||||
while (stack.last() && inlineElements[ stack.last() ]) {
|
||||
parseEndTag("", stack.last());
|
||||
}
|
||||
}
|
||||
|
||||
if (optionalEndTagElements[tagName] && stack.last() == tagName) {
|
||||
if (optionalEndTagElements[ tagName ] && stack.last() == tagName) {
|
||||
parseEndTag("", tagName);
|
||||
}
|
||||
|
||||
unary = voidElements[tagName] || !!unary;
|
||||
unary = voidElements[ tagName ] || !!unary;
|
||||
|
||||
if (!unary)
|
||||
stack.push(tagName);
|
||||
@@ -396,13 +395,13 @@ function htmlParser(html, handler) {
|
||||
if (tagName)
|
||||
// Find the closest opened tag of the same type
|
||||
for (pos = stack.length - 1; pos >= 0; pos--)
|
||||
if (stack[pos] == tagName)
|
||||
if (stack[ pos ] == tagName)
|
||||
break;
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (i = stack.length - 1; i >= pos; i--)
|
||||
if (handler.end) handler.end(stack[i]);
|
||||
if (handler.end) handler.end(stack[ i ]);
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
|
||||
@@ -68,31 +68,19 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
|
||||
try {
|
||||
var $window = self.getWindow_();
|
||||
|
||||
if (!$window.angular) {
|
||||
self.executeAction(loadFn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$window.angular.resumeBootstrap) {
|
||||
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
|
||||
} else {
|
||||
resumeDeferredBootstrap();
|
||||
if ($window.angular) {
|
||||
// Disable animations
|
||||
$window.angular.resumeBootstrap([['$provide', function($provide) {
|
||||
return ['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
}];
|
||||
}]]);
|
||||
}
|
||||
|
||||
self.executeAction(loadFn);
|
||||
} catch (e) {
|
||||
errorFn(e);
|
||||
}
|
||||
|
||||
function resumeDeferredBootstrap() {
|
||||
// Disable animations
|
||||
var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
|
||||
return ['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
}];
|
||||
}]]);
|
||||
self.rootElement = $injector.get('$rootElement')[0];
|
||||
self.executeAction(loadFn);
|
||||
}
|
||||
}).attr('src', url);
|
||||
|
||||
// for IE compatibility set the name *after* setting the frame url
|
||||
@@ -117,14 +105,7 @@ angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
if (!$window.angular) {
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
}
|
||||
|
||||
if (!!this.rootElement) {
|
||||
executeWithElement(this.rootElement);
|
||||
} else {
|
||||
angularInit($window.document, angular.bind(this, executeWithElement));
|
||||
}
|
||||
|
||||
function executeWithElement(element) {
|
||||
angularInit($window.document, function(element) {
|
||||
var $injector = $window.angular.element(element).injector();
|
||||
var $element = _jQuery(element);
|
||||
|
||||
@@ -137,5 +118,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
action.call(self, $window, $element);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ angular.scenario.ObjectModel = function(runner) {
|
||||
});
|
||||
|
||||
function complete(item) {
|
||||
item.endTime = new Date().getTime();
|
||||
item.endTime = Date.now();
|
||||
item.duration = item.endTime - item.startTime;
|
||||
item.status = item.status || 'success';
|
||||
}
|
||||
@@ -188,7 +188,7 @@ angular.scenario.ObjectModel.prototype.getSpec = function(id) {
|
||||
angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
this.startTime = Date.now();
|
||||
this.steps = [];
|
||||
this.fullDefinitionName = (definitionNames || []).join(' ');
|
||||
};
|
||||
@@ -234,7 +234,7 @@ angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) {
|
||||
*/
|
||||
angular.scenario.ObjectModel.Step = function(name) {
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
this.startTime = Date.now();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -304,7 +304,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
var element = windowJquery(this),
|
||||
bindings;
|
||||
if (bindings = element.data('$binding')) {
|
||||
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
|
||||
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
|
||||
binding = bindings[j];
|
||||
|
||||
if (binding.expressions) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, document){
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
if (window.WebKitTransitionEvent) {
|
||||
evnt = new WebKitTransitionEvent(eventType, eventData);
|
||||
evnt.initEvent(eventType, false, true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
evnt = new TransitionEvent(eventType, eventData);
|
||||
}
|
||||
@@ -64,11 +65,13 @@
|
||||
evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
|
||||
}
|
||||
}
|
||||
} else if (/animationend/.test(eventType)) {
|
||||
}
|
||||
else if (/animationend/.test(eventType)) {
|
||||
if (window.WebKitAnimationEvent) {
|
||||
evnt = new WebKitAnimationEvent(eventType, eventData);
|
||||
evnt.initEvent(eventType, false, true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
evnt = new AnimationEvent(eventType, eventData);
|
||||
}
|
||||
@@ -77,7 +80,8 @@
|
||||
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
evnt = document.createEvent('MouseEvents');
|
||||
x = x || 0;
|
||||
y = y || 0;
|
||||
|
||||
+1
-2
@@ -26,12 +26,11 @@
|
||||
"manualUppercase": false,
|
||||
"isArrayLike": false,
|
||||
"forEach": false,
|
||||
"sortedKeys": false,
|
||||
"reverseParams": false,
|
||||
"nextUid": false,
|
||||
"setHashKey": false,
|
||||
"extend": false,
|
||||
"int": false,
|
||||
"toInt": false,
|
||||
"inherit": false,
|
||||
"noop": false,
|
||||
"identity": false,
|
||||
|
||||
@@ -699,13 +699,6 @@ describe('angular', function() {
|
||||
});
|
||||
|
||||
|
||||
describe('sortedKeys', function() {
|
||||
it('should collect keys from object', function() {
|
||||
expect(sortedKeys({c:0, b:0, a:0})).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('encodeUriSegment', function() {
|
||||
it('should correctly encode uri segment and not encode chars defined as pchar set in rfc3986',
|
||||
function() {
|
||||
@@ -1081,26 +1074,6 @@ describe('angular', function() {
|
||||
window.name = originalName;
|
||||
});
|
||||
|
||||
it('should provide injector for deferred bootstrap', function() {
|
||||
var injector;
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
|
||||
injector = angular.bootstrap(element);
|
||||
expect(injector).toBeUndefined();
|
||||
|
||||
injector = angular.resumeBootstrap();
|
||||
expect(injector).toBeDefined();
|
||||
});
|
||||
|
||||
it('should resume deferred bootstrap, if defined', function() {
|
||||
var injector;
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
|
||||
angular.resumeDeferredBootstrap = noop;
|
||||
var spy = spyOn(angular, "resumeDeferredBootstrap");
|
||||
injector = angular.bootstrap(element);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should wait for extra modules', function() {
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
|
||||
@@ -238,11 +238,7 @@ describe('injector', function() {
|
||||
|
||||
|
||||
it('should publish annotate API', function() {
|
||||
expect(angular.mock.$$annotate).toBe(annotate);
|
||||
spyOn(angular.mock, '$$annotate').andCallThrough();
|
||||
function fn() {}
|
||||
injector.annotate(fn);
|
||||
expect(angular.mock.$$annotate).toHaveBeenCalledWith(fn);
|
||||
expect(injector.annotate).toBe(annotate);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -976,7 +972,7 @@ describe('strict-di injector', function() {
|
||||
});
|
||||
});
|
||||
inject(function($injector) {
|
||||
expect(function() {
|
||||
expect (function() {
|
||||
$injector.invoke(function($test2) {});
|
||||
}).toThrowMinErr('$injector', 'strictdi');
|
||||
});
|
||||
@@ -990,7 +986,7 @@ describe('strict-di injector', function() {
|
||||
});
|
||||
});
|
||||
inject(function($injector) {
|
||||
expect(function() {
|
||||
expect (function() {
|
||||
$injector.invoke(['$test', function($test) {}]);
|
||||
}).toThrowMinErr('$injector', 'strictdi');
|
||||
});
|
||||
|
||||
@@ -74,19 +74,9 @@ afterEach(function() {
|
||||
|
||||
|
||||
// copied from Angular.js
|
||||
// we need these two methods here so that we can run module tests with wrapped angular.js
|
||||
function sortedKeys(obj) {
|
||||
var keys = [];
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
return keys.sort();
|
||||
}
|
||||
|
||||
// we need this method here so that we can run module tests with wrapped angular.js
|
||||
function forEachSorted(obj, iterator, context) {
|
||||
var keys = sortedKeys(obj);
|
||||
var keys = Object.keys(obj).sort();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
iterator.call(context, obj[keys[i]], keys[i]);
|
||||
}
|
||||
|
||||
@@ -97,4 +97,11 @@ describe('minErr', function() {
|
||||
var typeMinErr = minErr('type', TypeError);
|
||||
expect(typeMinErr('acode', 'aproblem') instanceof TypeError).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should include a properly formatted error reference URL in the message', function() {
|
||||
// to avoid maintaining the root URL in two locations, we only validate the parameters
|
||||
expect(testError('acode', 'aproblem', 'a', 'b', 'value with space').message)
|
||||
.toMatch(/^[\s\S]*\?p0=a&p1=b&p2=value%20with%20space$/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -395,7 +395,7 @@ describe('browser', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should return a value for an existing cookie', function() {
|
||||
it ('should return a value for an existing cookie', function() {
|
||||
document.cookie = "foo=bar=baz;path=/";
|
||||
expect(browser.cookies().foo).toEqual('bar=baz');
|
||||
});
|
||||
@@ -408,7 +408,7 @@ describe('browser', function() {
|
||||
expect(browser.cookies()['foo']).toBe('"first"');
|
||||
});
|
||||
|
||||
it('should decode cookie values that were encoded by puts', function() {
|
||||
it ('should decode cookie values that were encoded by puts', function() {
|
||||
document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/";
|
||||
expect(browser.cookies()['cookie2=bar;baz']).toEqual('val=ue');
|
||||
});
|
||||
|
||||
+19
-59
@@ -423,7 +423,8 @@ describe('$compile', function() {
|
||||
describe('multiple directives per element', function() {
|
||||
it('should allow multiple directives per element', inject(function($compile, $rootScope, log) {
|
||||
element = $compile(
|
||||
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
|
||||
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')
|
||||
($rootScope);
|
||||
expect(element.text()).toEqual('Hello angular');
|
||||
expect(log).toEqual('L; M; H');
|
||||
}));
|
||||
@@ -606,7 +607,8 @@ describe('$compile', function() {
|
||||
describe('priority', function() {
|
||||
it('should honor priority', inject(function($compile, $rootScope, log) {
|
||||
element = $compile(
|
||||
'<span log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
|
||||
'<span log="L" x-high-log="H" data-medium-log="M"></span>')
|
||||
($rootScope);
|
||||
expect(log).toEqual('L; M; H');
|
||||
}));
|
||||
});
|
||||
@@ -807,7 +809,8 @@ describe('$compile', function() {
|
||||
|
||||
|
||||
it('should compile template when replacing', inject(function($compile, $rootScope, log) {
|
||||
element = $compile('<div><div replace medium-log>ignore</div><div>')($rootScope);
|
||||
element = $compile('<div><div replace medium-log>ignore</div><div>')
|
||||
($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('Replace!');
|
||||
expect(log).toEqual('LOG; HIGH; MEDIUM');
|
||||
@@ -815,7 +818,8 @@ describe('$compile', function() {
|
||||
|
||||
|
||||
it('should compile template when appending', inject(function($compile, $rootScope, log) {
|
||||
element = $compile('<div><div append medium-log>ignore</div><div>')($rootScope);
|
||||
element = $compile('<div><div append medium-log>ignore</div><div>')
|
||||
($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('Append!');
|
||||
expect(log).toEqual('LOG; HIGH; MEDIUM');
|
||||
@@ -824,7 +828,8 @@ describe('$compile', function() {
|
||||
|
||||
it('should merge attributes including style attr', inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')($rootScope);
|
||||
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')
|
||||
($rootScope);
|
||||
var div = element.find('div');
|
||||
expect(div.hasClass('medium-log')).toBe(true);
|
||||
expect(div.hasClass('log')).toBe(true);
|
||||
@@ -836,7 +841,8 @@ describe('$compile', function() {
|
||||
|
||||
it('should not merge attributes if they are the same', inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div><div nomerge class="medium-log" id="myid"></div><div>')($rootScope);
|
||||
'<div><div nomerge class="medium-log" id="myid"></div><div>')
|
||||
($rootScope);
|
||||
var div = element.find('div');
|
||||
expect(div.hasClass('medium-log')).toBe(true);
|
||||
expect(div.hasClass('log')).toBe(true);
|
||||
@@ -1109,29 +1115,6 @@ describe('$compile', function() {
|
||||
expect(element.find('p').text()).toBe('Hello, world!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep prototype properties on directive', function() {
|
||||
module(function() {
|
||||
function DirectiveClass() {
|
||||
this.restrict = 'E';
|
||||
this.template = "<p>{{value}}</p>";
|
||||
}
|
||||
|
||||
DirectiveClass.prototype.compile = function() {
|
||||
return function(scope, element, attrs) {
|
||||
scope.value = "Test Value";
|
||||
};
|
||||
};
|
||||
|
||||
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2051,32 +2034,6 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep prototype properties on sync version of async directive', function() {
|
||||
module(function() {
|
||||
function DirectiveClass() {
|
||||
this.restrict = 'E';
|
||||
this.templateUrl = "test.html";
|
||||
}
|
||||
|
||||
DirectiveClass.prototype.compile = function() {
|
||||
return function(scope, element, attrs) {
|
||||
scope.value = "Test Value";
|
||||
};
|
||||
};
|
||||
|
||||
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope, $httpBackend) {
|
||||
$httpBackend.whenGET('test.html').
|
||||
respond('<p>{{value}}</p>');
|
||||
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
|
||||
$httpBackend.flush();
|
||||
$rootScope.$digest();
|
||||
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -4653,7 +4610,8 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')($rootScope);
|
||||
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')
|
||||
($rootScope);
|
||||
$rootScope.x = 'root';
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;');
|
||||
@@ -4938,7 +4896,8 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')($rootScope);
|
||||
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
|
||||
expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
|
||||
@@ -5769,7 +5728,8 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')($rootScope);
|
||||
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(log).toEqual('compile: <!-- trans: text -->; link; LOG; LOG; HIGH');
|
||||
expect(element.text()).toEqual('1-2;1-3;');
|
||||
@@ -6105,7 +6065,7 @@ describe('$compile', function() {
|
||||
link: function(scope, element, attrs, ctrl, transclude) {
|
||||
|
||||
// We use timeout here to simulate how ng-if works
|
||||
$timeout(function() {
|
||||
$timeout(function() {
|
||||
transclude(function(child) { element.append(child); });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -90,16 +90,7 @@ describe('$controller', function() {
|
||||
var foo = $controller('a.Foo', {$scope: scope});
|
||||
expect(foo).toBeDefined();
|
||||
expect(foo instanceof Foo).toBe(true);
|
||||
}));
|
||||
|
||||
|
||||
it('should throw ctrlfmt if name contains spaces', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl doom');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl doom'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -177,37 +168,5 @@ describe('$controller', function() {
|
||||
}).toThrowMinErr("$controller", "noscp", "Cannot export controller 'a.b.FooCtrl' as 'foo'! No $scope object provided via `locals`.");
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should throw ctrlfmt if identifier contains non-ident characters', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl as foo<bar');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as foo<bar'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
|
||||
|
||||
it('should throw ctrlfmt if identifier contains spaces', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl as foo bar');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as foo bar'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
|
||||
|
||||
it('should throw ctrlfmt if identifier missing after " as "', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl as ');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as '. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
expect(function() {
|
||||
$controller('ctrl as');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,6 +42,22 @@ describe('boolean attr directives', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should not bind checked when ngModel is present', inject(function($rootScope, $compile) {
|
||||
// test for https://github.com/angular/angular.js/issues/10662
|
||||
element = $compile('<input type="checkbox" ng-model="value" ng-false-value="\'false\'" ' +
|
||||
'ng-true-value="\'true\'" ng-checked="value" />')($rootScope);
|
||||
$rootScope.value = 'true';
|
||||
$rootScope.$digest();
|
||||
expect(element[0].checked).toBe(true);
|
||||
browserTrigger(element, 'click');
|
||||
expect(element[0].checked).toBe(false);
|
||||
expect($rootScope.value).toBe('false');
|
||||
browserTrigger(element, 'click');
|
||||
expect(element[0].checked).toBe(true);
|
||||
expect($rootScope.value).toBe('true');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind selected', inject(function($rootScope, $compile) {
|
||||
element = $compile('<select><option value=""></option><option ng-selected="isSelected">Greetings!</option></select>')($rootScope);
|
||||
jqLite(document.body).append(element);
|
||||
|
||||
@@ -1641,9 +1641,9 @@ describe('input', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function() {
|
||||
describe('max', function() {
|
||||
|
||||
it('should invalidate', function() {
|
||||
it('should invalidate', function() {
|
||||
var inputElm = helper.compileInput('<input type="date" ng-model="value" name="alias" max="2019-01-01" />');
|
||||
helper.changeInputValueTo('2019-12-31');
|
||||
expect(inputElm).toBeInvalid();
|
||||
@@ -2169,7 +2169,7 @@ describe('input', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" ng-minlength="min" attr-capture />');
|
||||
helper.attrs.$observe('minlength', function(v) {
|
||||
value = int(helper.attrs.minlength);
|
||||
value = toInt(helper.attrs.minlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
@@ -2215,7 +2215,7 @@ describe('input', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" ng-maxlength="max" attr-capture />');
|
||||
helper.attrs.$observe('maxlength', function(v) {
|
||||
value = int(helper.attrs.maxlength);
|
||||
value = toInt(helper.attrs.maxlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
@@ -2622,116 +2622,3 @@ describe('input', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgModel animations', function() {
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
function findElementAnimations(element, queue) {
|
||||
var node = element[0];
|
||||
var animations = [];
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
var animation = queue[i];
|
||||
if (animation.element[0] == node) {
|
||||
animations.push(animation);
|
||||
}
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
function assertValidAnimation(animation, event, classNameA, classNameB) {
|
||||
expect(animation.event).toBe(event);
|
||||
expect(animation.args[1]).toBe(classNameA);
|
||||
if (classNameB) expect(animation.args[2]).toBe(classNameB);
|
||||
}
|
||||
|
||||
var doc, input, scope, model;
|
||||
beforeEach(inject(function($rootScope, $compile, $rootElement, $animate) {
|
||||
scope = $rootScope.$new();
|
||||
doc = jqLite('<form name="myForm">' +
|
||||
' <input type="text" ng-model="input" name="myInput" />' +
|
||||
'</form>');
|
||||
$rootElement.append(doc);
|
||||
$compile(doc)(scope);
|
||||
$animate.queue = [];
|
||||
|
||||
input = doc.find('input');
|
||||
model = scope.myForm.myInput;
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(input);
|
||||
});
|
||||
|
||||
it('should trigger an animation when invalid', inject(function($animate) {
|
||||
model.$setValidity('required', false);
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when valid', inject(function($animate) {
|
||||
model.$setValidity('required', false);
|
||||
|
||||
$animate.queue = [];
|
||||
|
||||
model.$setValidity('required', true);
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'addClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-valid-required');
|
||||
assertValidAnimation(animations[3], 'removeClass', 'ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when dirty', inject(function($animate) {
|
||||
model.$setViewValue('some dirty value');
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-pristine');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-dirty');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when pristine', inject(function($animate) {
|
||||
model.$setPristine();
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-dirty');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-pristine');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when untouched', inject(function($animate) {
|
||||
model.$setUntouched();
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'setClass', 'ng-untouched');
|
||||
expect(animations[0].args[2]).toBe('ng-touched');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when touched', inject(function($animate) {
|
||||
model.$setTouched();
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'setClass', 'ng-touched', 'ng-untouched');
|
||||
expect(animations[0].args[2]).toBe('ng-untouched');
|
||||
}));
|
||||
|
||||
it('should trigger custom errors as addClass/removeClass when invalid/valid', inject(function($animate) {
|
||||
model.$setValidity('custom-error', false);
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-invalid-custom-error');
|
||||
|
||||
$animate.queue = [];
|
||||
model.$setValidity('custom-error', true);
|
||||
|
||||
animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'addClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation(animations[3], 'removeClass', 'ng-invalid-custom-error');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -88,6 +88,16 @@ describe('ngClass', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should support adding multiple classes via a space delimited string inside an array', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div class="existing" ng-class="[\'A B\', \'C\']"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('existing')).toBeTruthy();
|
||||
expect(element.hasClass('A')).toBeTruthy();
|
||||
expect(element.hasClass('B')).toBeTruthy();
|
||||
expect(element.hasClass('C')).toBeTruthy();
|
||||
}));
|
||||
|
||||
|
||||
it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div class="existing" ng-class="dynClass"></div>')($rootScope);
|
||||
$rootScope.dynClass = 'A';
|
||||
|
||||
@@ -1733,6 +1733,34 @@ describe('ngModelOptions attributes', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should allow sharing options between multiple inputs', function() {
|
||||
$rootScope.options = {updateOn: 'default'};
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="text" ng-model="name1" name="alias1" ' +
|
||||
'ng-model-options="options"' +
|
||||
'/>' +
|
||||
'<input type="text" ng-model="name2" name="alias2" ' +
|
||||
'ng-model-options="options"' +
|
||||
'/>');
|
||||
|
||||
helper.changeGivenInputTo(inputElm.eq(0), 'a');
|
||||
helper.changeGivenInputTo(inputElm.eq(1), 'b');
|
||||
expect($rootScope.name1).toEqual('a');
|
||||
expect($rootScope.name2).toEqual('b');
|
||||
});
|
||||
|
||||
|
||||
it('should hold a copy of the options object', function() {
|
||||
$rootScope.options = {updateOn: 'default'};
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="options"' +
|
||||
'/>');
|
||||
expect($rootScope.options).toEqual({updateOn: 'default'});
|
||||
expect($rootScope.form.alias.$options).not.toBe($rootScope.options);
|
||||
});
|
||||
|
||||
|
||||
it('should allow overriding the model update trigger event on checkboxes', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="checkbox" ng-model="checkbox" ' +
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -83,52 +83,64 @@ describe('ngPluralize', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should show single/plural strings with mal-formed inputs', inject(function($rootScope) {
|
||||
$rootScope.email = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
it('should show single/plural strings with mal-formed inputs', inject(
|
||||
function($log, $rootScope) {
|
||||
$rootScope.email = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
expect($log.debug.logs.shift()).toEqual([
|
||||
"ngPluralize: no rule defined for 'NaN' in {" +
|
||||
"'-1': 'You have negative email. Whohoo!'," +
|
||||
"'0': 'You have no new email'," +
|
||||
"'one': 'You have one new email'," +
|
||||
"'other': 'You have {} new emails'}"
|
||||
]);
|
||||
expect($log.debug.logs.shift()).toEqual([
|
||||
"ngPluralize: no rule defined for 'NaN' in undefined"
|
||||
]);
|
||||
|
||||
$rootScope.email = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
$rootScope.email = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
|
||||
$rootScope.email = undefined;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
$rootScope.email = undefined;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
|
||||
$rootScope.email = 'a3';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
$rootScope.email = 'a3';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
|
||||
$rootScope.email = '011';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have 11 new emails');
|
||||
expect(elementAlt.text()).toBe('You have 11 new emails');
|
||||
$rootScope.email = '011';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have 11 new emails');
|
||||
expect(elementAlt.text()).toBe('You have 11 new emails');
|
||||
|
||||
$rootScope.email = '-011';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have -11 new emails');
|
||||
expect(elementAlt.text()).toBe('You have -11 new emails');
|
||||
$rootScope.email = '-011';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have -11 new emails');
|
||||
expect(elementAlt.text()).toBe('You have -11 new emails');
|
||||
|
||||
$rootScope.email = '1fff';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have one new email');
|
||||
expect(elementAlt.text()).toBe('You have one new email');
|
||||
$rootScope.email = '1fff';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have one new email');
|
||||
expect(elementAlt.text()).toBe('You have one new email');
|
||||
|
||||
$rootScope.email = '0aa22';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have no new email');
|
||||
expect(elementAlt.text()).toBe('You have no new email');
|
||||
$rootScope.email = '0aa22';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have no new email');
|
||||
expect(elementAlt.text()).toBe('You have no new email');
|
||||
|
||||
$rootScope.email = '000001';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have one new email');
|
||||
expect(elementAlt.text()).toBe('You have one new email');
|
||||
}));
|
||||
$rootScope.email = '000001';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have one new email');
|
||||
expect(elementAlt.text()).toBe('You have one new email');
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
|
||||
@@ -144,8 +156,90 @@ describe('ngPluralize', function() {
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
}));
|
||||
|
||||
it('should be able to specify a message for null/undefined values', inject(
|
||||
function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<ng:pluralize count="email"' +
|
||||
"when=\"{'NaN': 'Unspecified email count'," +
|
||||
"'0': ''," +
|
||||
"'one': 'Some text'," +
|
||||
"'other': 'Some text'}\">" +
|
||||
'</ng:pluralize>')($rootScope);
|
||||
|
||||
$rootScope.email = '0';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.email = undefined;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Unspecified email count');
|
||||
|
||||
$rootScope.email = '1';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Some text');
|
||||
|
||||
$rootScope.email = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Unspecified email count');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('undefined rule cases', function() {
|
||||
var $locale, $log;
|
||||
beforeEach(inject(function(_$locale_, _$log_) {
|
||||
$locale = _$locale_;
|
||||
$log = _$log_;
|
||||
}));
|
||||
afterEach(inject(function($log) {
|
||||
$log.reset();
|
||||
}));
|
||||
|
||||
it('should generate a warning when being asked to use a rule that is not defined',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ng:pluralize count="email"' +
|
||||
"when=\"{'0': 'Zero'," +
|
||||
"'one': 'Some text'," +
|
||||
"'other': 'Some text'}\">" +
|
||||
'</ng:pluralize>')($rootScope);
|
||||
$locale.pluralCat = function() {return "few";};
|
||||
|
||||
$rootScope.email = '3';
|
||||
expect($log.debug.logs).toEqual([]);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect($log.debug.logs.shift())
|
||||
.toEqual(["ngPluralize: no rule defined for 'few' in {'0': 'Zero','one': 'Some text','other': 'Some text'}"]);
|
||||
}));
|
||||
|
||||
it('should empty the element content when using a rule that is not defined',
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ng:pluralize count="email"' +
|
||||
"when=\"{'0': 'Zero'," +
|
||||
"'one': 'Some text'," +
|
||||
"'other': 'Some text'}\">" +
|
||||
'</ng:pluralize>')($rootScope);
|
||||
$locale.pluralCat = function(count) {return count === 1 ? "one" : "few";};
|
||||
|
||||
$rootScope.email = '0';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Zero');
|
||||
|
||||
$rootScope.email = '3';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.email = '1';
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Some text');
|
||||
|
||||
$rootScope.email = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('deal with pluralized strings with offset', function() {
|
||||
it('should show single/plural strings with offset', inject(function($rootScope, $compile) {
|
||||
@@ -192,6 +286,11 @@ describe('ngPluralize', function() {
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.');
|
||||
expect(elementAlt.text()).toBe('Igor, Misko and 2 other people are viewing.');
|
||||
|
||||
$rootScope.viewCount = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('');
|
||||
expect(elementAlt.text()).toBe('');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -245,7 +344,6 @@ describe('ngPluralize', function() {
|
||||
|
||||
|
||||
describe('bind-once', function() {
|
||||
|
||||
it('should support for `count` to be a one-time expression',
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
@@ -269,6 +367,11 @@ describe('ngPluralize', function() {
|
||||
expect(element.text()).toBe('You have 3 new emails');
|
||||
expect(elementAlt.text()).toBe('You have 3 new emails');
|
||||
|
||||
$rootScope.email = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have 3 new emails');
|
||||
expect(elementAlt.text()).toBe('You have 3 new emails');
|
||||
|
||||
$rootScope.email = 2;
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('You have 3 new emails');
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('ngRepeat', function() {
|
||||
'</ul>')(scope);
|
||||
scope.items = {age:20, wealth:20, prodname: "Bingo", dogname: "Bingo", codename: "20"};
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('age:20|codename:20|dogname:Bingo|prodname:Bingo|wealth:20|');
|
||||
expect(element.text()).toEqual('age:20|wealth:20|prodname:Bingo|dogname:Bingo|codename:20|');
|
||||
});
|
||||
|
||||
describe('track by', function() {
|
||||
@@ -589,7 +589,7 @@ describe('ngRepeat', function() {
|
||||
'</ul>')(scope);
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('frodo:f:0|misko:m:1|shyam:s:2|');
|
||||
expect(element.text()).toEqual('misko:m:0|shyam:s:1|frodo:f:2|');
|
||||
});
|
||||
|
||||
|
||||
@@ -658,10 +658,10 @@ describe('ngRepeat', function() {
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
|
||||
scope.$digest();
|
||||
expect(element.text()).
|
||||
toEqual('doug:d:true-false-false|' +
|
||||
'frodo:f:false-true-false|' +
|
||||
'misko:m:false-true-false|' +
|
||||
'shyam:s:false-false-true|');
|
||||
toEqual('misko:m:true-false-false|' +
|
||||
'shyam:s:false-true-false|' +
|
||||
'doug:d:false-true-false|' +
|
||||
'frodo:f:false-false-true|');
|
||||
|
||||
delete scope.items.doug;
|
||||
delete scope.items.frodo;
|
||||
@@ -683,15 +683,15 @@ describe('ngRepeat', function() {
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
|
||||
scope.$digest();
|
||||
expect(element.text()).
|
||||
toBe('doug:d:true-false|' +
|
||||
'frodo:f:false-true|' +
|
||||
'misko:m:true-false|' +
|
||||
'shyam:s:false-true|');
|
||||
toBe('misko:m:true-false|' +
|
||||
'shyam:s:false-true|' +
|
||||
'doug:d:true-false|' +
|
||||
'frodo:f:false-true|');
|
||||
|
||||
delete scope.items.frodo;
|
||||
delete scope.items.shyam;
|
||||
scope.$digest();
|
||||
expect(element.text()).toBe('doug:d:true-false|misko:m:false-true|');
|
||||
expect(element.text()).toBe('misko:m:true-false|doug:d:false-true|');
|
||||
});
|
||||
|
||||
|
||||
@@ -702,11 +702,12 @@ describe('ngRepeat', function() {
|
||||
'</ul>')(scope);
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'};
|
||||
scope.$digest();
|
||||
|
||||
expect(element.text()).
|
||||
toEqual('doug:d:true-false-false|' +
|
||||
'frodo:f:false-true-false|' +
|
||||
'misko:m:false-true-false|' +
|
||||
'shyam:s:false-false-true|');
|
||||
toEqual('misko:m:true-false-false|' +
|
||||
'shyam:s:false-true-false|' +
|
||||
'doug:d:false-true-false|' +
|
||||
'frodo:f:false-false-true|');
|
||||
});
|
||||
|
||||
|
||||
@@ -718,10 +719,10 @@ describe('ngRepeat', function() {
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'};
|
||||
scope.$digest();
|
||||
expect(element.text()).
|
||||
toEqual('doug:d:true-false|' +
|
||||
'frodo:f:false-true|' +
|
||||
'misko:m:true-false|' +
|
||||
'shyam:s:false-true|');
|
||||
toEqual('misko:m:true-false|' +
|
||||
'shyam:s:false-true|' +
|
||||
'doug:d:true-false|' +
|
||||
'frodo:f:false-true|');
|
||||
});
|
||||
|
||||
|
||||
|
||||
+326
-1901
File diff suppressed because it is too large
Load Diff
@@ -224,7 +224,7 @@ describe('validators', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="min" attr-capture />');
|
||||
helper.attrs.$observe('minlength', function(v) {
|
||||
value = int(helper.attrs.minlength);
|
||||
value = toInt(helper.attrs.minlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply('min = 5');
|
||||
@@ -254,20 +254,6 @@ describe('validators', function() {
|
||||
expect($rootScope.value).toBe(12345);
|
||||
expect($rootScope.form.input.$error.minlength).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
spyOn(ctrl, '$isEmpty').andCallThrough();
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return value + '678';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -332,7 +318,7 @@ describe('validators', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="max" attr-capture />');
|
||||
helper.attrs.$observe('maxlength', function(v) {
|
||||
value = int(helper.attrs.maxlength);
|
||||
value = toInt(helper.attrs.maxlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply('max = 10');
|
||||
@@ -424,20 +410,6 @@ describe('validators', function() {
|
||||
expect($rootScope.value).toBe(12345);
|
||||
expect($rootScope.form.input.$error.maxlength).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
spyOn(ctrl, '$isEmpty').andCallThrough();
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return value + '678';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -531,19 +503,5 @@ describe('validators', function() {
|
||||
$rootScope.$apply("answer = false");
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
spyOn(ctrl, '$isEmpty').andCallThrough();
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return value + '678';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -271,7 +271,7 @@ describe('Filter: filter', function() {
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
|
||||
// Inherited function proprties
|
||||
// Inherited function properties
|
||||
function Item(text) {
|
||||
this.text = text;
|
||||
}
|
||||
@@ -399,6 +399,35 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should throw an error when is not used with an array', function() {
|
||||
var item = {'not': 'array'};
|
||||
expect(function() { filter(item, {}); }).
|
||||
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"not":"array"}');
|
||||
|
||||
item = Object.create(null);
|
||||
expect(function() { filter(item, {}); }).
|
||||
toThrowMinErr('filter', 'notarray', 'Expected array but received: {}');
|
||||
|
||||
item = {
|
||||
toString: null,
|
||||
valueOf: null
|
||||
};
|
||||
expect(function() { filter(item, {}); }).
|
||||
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"toString":null,"valueOf":null}');
|
||||
});
|
||||
|
||||
|
||||
it('should return undefined when the array is undefined', function() {
|
||||
expect(filter(undefined, {})).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should return null when the value of the array is null', function() {
|
||||
var item = null;
|
||||
expect(filter(item, {})).toBe(null);
|
||||
});
|
||||
|
||||
|
||||
describe('should support comparator', function() {
|
||||
|
||||
it('not consider `object === "[object Object]"` in non-strict comparison', function() {
|
||||
|
||||
@@ -150,10 +150,10 @@ describe('filters', function() {
|
||||
expect(number(Number.NaN)).toEqual('');
|
||||
expect(number({})).toEqual('');
|
||||
expect(number([])).toEqual('');
|
||||
expect(number(+Infinity)).toEqual('');
|
||||
expect(number(-Infinity)).toEqual('');
|
||||
expect(number(+Infinity)).toEqual('∞');
|
||||
expect(number(-Infinity)).toEqual('-∞');
|
||||
expect(number("1234.5678")).toEqual('1,234.568');
|
||||
expect(number(1 / 0)).toEqual("");
|
||||
expect(number(1 / 0)).toEqual('∞');
|
||||
expect(number(1, 2)).toEqual("1.00");
|
||||
expect(number(.1, 2)).toEqual("0.10");
|
||||
expect(number(.01, 2)).toEqual("0.01");
|
||||
@@ -247,6 +247,11 @@ describe('filters', function() {
|
||||
expect(date('')).toEqual('');
|
||||
});
|
||||
|
||||
it('should ignore invalid dates', function() {
|
||||
var invalidDate = new Date('abc');
|
||||
expect(date(invalidDate)).toBe(invalidDate);
|
||||
});
|
||||
|
||||
it('should do basic filter', function() {
|
||||
expect(date(noon)).toEqual(date(noon, 'mediumDate'));
|
||||
expect(date(noon, '')).toEqual(date(noon, 'mediumDate'));
|
||||
|
||||
@@ -34,20 +34,30 @@ describe('Filter: limitTo', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should return an empty array when X cannot be parsed', function() {
|
||||
expect(limitTo(items, 'bogus')).toEqual([]);
|
||||
expect(limitTo(items, 'null')).toEqual([]);
|
||||
expect(limitTo(items, 'undefined')).toEqual([]);
|
||||
expect(limitTo(items, null)).toEqual([]);
|
||||
expect(limitTo(items, undefined)).toEqual([]);
|
||||
it('should return an empty array when X = 0', function() {
|
||||
expect(limitTo(items, 0)).toEqual([]);
|
||||
expect(limitTo(items, '0')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an empty string when X cannot be parsed', function() {
|
||||
expect(limitTo(str, 'bogus')).toEqual("");
|
||||
expect(limitTo(str, 'null')).toEqual("");
|
||||
expect(limitTo(str, 'undefined')).toEqual("");
|
||||
expect(limitTo(str, null)).toEqual("");
|
||||
expect(limitTo(str, undefined)).toEqual("");
|
||||
it('should return entire array when X cannot be parsed', function() {
|
||||
expect(limitTo(items, 'bogus')).toEqual(items);
|
||||
expect(limitTo(items, 'null')).toEqual(items);
|
||||
expect(limitTo(items, 'undefined')).toEqual(items);
|
||||
expect(limitTo(items, null)).toEqual(items);
|
||||
expect(limitTo(items, undefined)).toEqual(items);
|
||||
});
|
||||
|
||||
it('should return an empty string when X = 0', function() {
|
||||
expect(limitTo(str, 0)).toEqual("");
|
||||
expect(limitTo(str, '0')).toEqual("");
|
||||
});
|
||||
|
||||
it('should return entire string when X cannot be parsed', function() {
|
||||
expect(limitTo(str, 'bogus')).toEqual(str);
|
||||
expect(limitTo(str, 'null')).toEqual(str);
|
||||
expect(limitTo(str, 'undefined')).toEqual(str);
|
||||
expect(limitTo(str, null)).toEqual(str);
|
||||
expect(limitTo(str, undefined)).toEqual(str);
|
||||
});
|
||||
|
||||
|
||||
|
||||
+49
-25
@@ -274,63 +274,59 @@ describe('$http', function() {
|
||||
describe('the instance', function() {
|
||||
var $httpBackend, $http, $rootScope;
|
||||
|
||||
beforeEach(inject(['$rootScope', function($rs) {
|
||||
beforeEach(inject(['$httpBackend', '$http', '$rootScope', function($hb, $h, $rs) {
|
||||
$httpBackend = $hb;
|
||||
$http = $h;
|
||||
$rootScope = $rs;
|
||||
|
||||
spyOn($rootScope, '$apply').andCallThrough();
|
||||
}]));
|
||||
|
||||
beforeEach(inject(['$httpBackend', '$http', function($hb, $h) {
|
||||
$httpBackend = $hb;
|
||||
$http = $h;
|
||||
}]));
|
||||
|
||||
it('should throw error if the request configuration is not an object', inject(function($httpBackend, $http) {
|
||||
it('should throw error if the request configuration is not an object', function() {
|
||||
expect(function() {
|
||||
$http('/url');
|
||||
}).toThrowMinErr('$http','badreq', 'Http request configuration must be an object. Received: /url');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should send GET requests if no method specified', inject(function($httpBackend, $http) {
|
||||
it('should send GET requests if no method specified', function() {
|
||||
$httpBackend.expect('GET', '/url').respond('');
|
||||
$http({url: '/url'});
|
||||
}));
|
||||
});
|
||||
|
||||
it('should do basic request', inject(function($httpBackend, $http) {
|
||||
it('should do basic request', function() {
|
||||
$httpBackend.expect('GET', '/url').respond('');
|
||||
$http({url: '/url', method: 'GET'});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should pass data if specified', inject(function($httpBackend, $http) {
|
||||
it('should pass data if specified', function() {
|
||||
$httpBackend.expect('POST', '/url', 'some-data').respond('');
|
||||
$http({url: '/url', method: 'POST', data: 'some-data'});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('params', function() {
|
||||
it('should do basic request with params and encode', inject(function($httpBackend, $http) {
|
||||
it('should do basic request with params and encode', function() {
|
||||
$httpBackend.expect('GET', '/url?a%3D=%3F%26&b=2').respond('');
|
||||
$http({url: '/url', params: {'a=':'?&', b:2}, method: 'GET'});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should merge params if url contains some already', inject(function($httpBackend, $http) {
|
||||
it('should merge params if url contains some already', function() {
|
||||
$httpBackend.expect('GET', '/url?c=3&a=1&b=2').respond('');
|
||||
$http({url: '/url?c=3', params: {a:1, b:2}, method: 'GET'});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should jsonify objects in params map', inject(function($httpBackend, $http) {
|
||||
it('should jsonify objects in params map', function() {
|
||||
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22:3%7D').respond('');
|
||||
$http({url: '/url', params: {a:1, b:{c:3}}, method: 'GET'});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should expand arrays in params map', inject(function($httpBackend, $http) {
|
||||
it('should expand arrays in params map', function() {
|
||||
$httpBackend.expect('GET', '/url?a=1&a=2&a=3').respond('');
|
||||
$http({url: '/url', params: {a: [1,2,3]}, method: 'GET'});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should not encode @ in url params', function() {
|
||||
@@ -763,7 +759,7 @@ describe('$http', function() {
|
||||
$httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should send execute result if header value is function', inject(function() {
|
||||
it('should send execute result if header value is function', function() {
|
||||
var headerConfig = {'Accept': function() { return 'Rewritten'; }};
|
||||
|
||||
function checkHeaders(headers) {
|
||||
@@ -783,7 +779,35 @@ describe('$http', function() {
|
||||
$http({url: '/url', method: 'DELETE', headers: headerConfig});
|
||||
|
||||
$httpBackend.flush();
|
||||
}));
|
||||
});
|
||||
|
||||
it('should expose a config object to header functions', function() {
|
||||
var config = {
|
||||
foo: 'Rewritten',
|
||||
headers: {'Accept': function(config) {
|
||||
return config.foo;
|
||||
}}
|
||||
};
|
||||
|
||||
$httpBackend.expect('GET', '/url', undefined, {Accept: 'Rewritten'}).respond('');
|
||||
$http.get('/url', config);
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should not allow modifications to a config object in header functions', function() {
|
||||
var config = {
|
||||
headers: {'Accept': function(config) {
|
||||
config.foo = 'bar';
|
||||
return 'Rewritten';
|
||||
}}
|
||||
};
|
||||
|
||||
$httpBackend.expect('GET', '/url', undefined, {Accept: 'Rewritten'}).respond('');
|
||||
$http.get('/url', config);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(config.foo).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should check the cache before checking the XSRF cookie', inject(function($browser, $cacheFactory) {
|
||||
var testCache = $cacheFactory('testCache'),
|
||||
|
||||
+2
-12
@@ -673,16 +673,6 @@ describe('$location', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('rewrite hashbang url <> html5 url', function() {
|
||||
beforeEach(initService({html5Mode: true, supportHistory: true}));
|
||||
beforeEach(inject(initBrowser({url:'http://new.com/#', basePath: '/'})));
|
||||
|
||||
it('should not replace browser url if only the empty hash fragment is cleared', inject(function($browser, $location) {
|
||||
expect($browser.url()).toBe('http://new.com/#');
|
||||
expect($location.absUrl()).toBe('http://new.com/');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('wiring', function() {
|
||||
|
||||
beforeEach(initService({html5Mode:false,hashPrefix: '!',supportHistory: true}));
|
||||
@@ -1333,7 +1323,7 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite links when rewriting links is disabled', function() {
|
||||
it ('should not rewrite links when rewriting links is disabled', function() {
|
||||
configureService({linkHref: 'link?a#b', html5Mode: {enabled: true, rewriteLinks:false}, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
@@ -1870,7 +1860,7 @@ describe('$location', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it('should fire $locationChangeSuccess event when change from browser location bar',
|
||||
it ('should fire $locationChangeSuccess event when change from browser location bar',
|
||||
inject(function($log, $location, $browser, $rootScope) {
|
||||
$rootScope.$apply(); // clear initial $locationChangeStart
|
||||
|
||||
|
||||
@@ -488,62 +488,6 @@ describe('parser', function() {
|
||||
expect(scope.b).toEqual(234);
|
||||
});
|
||||
|
||||
it('should allow use of locals in the left side of an assignment', inject(function($rootScope) {
|
||||
$rootScope.a = {};
|
||||
$rootScope.key = "value";
|
||||
var localA = {};
|
||||
|
||||
//getterFn
|
||||
$rootScope.$eval('a.value = 1', {a: localA});
|
||||
expect(localA.value).toBe(1);
|
||||
|
||||
$rootScope.$eval('w.a.value = 2', {w: {a: localA}});
|
||||
expect(localA.value).toBe(2);
|
||||
|
||||
//field access
|
||||
$rootScope.$eval('(a).value = 3', {a: localA});
|
||||
expect(localA.value).toBe(3);
|
||||
|
||||
$rootScope.$eval('{c: {b: a}}.c.b.value = 4', {a: localA});
|
||||
expect(localA.value).toBe(4);
|
||||
|
||||
//object index
|
||||
$rootScope.$eval('a[key] = 5', {a: localA});
|
||||
expect(localA.value).toBe(5);
|
||||
|
||||
$rootScope.$eval('w.a[key] = 6', {w: {a: localA}});
|
||||
expect(localA.value).toBe(6);
|
||||
|
||||
$rootScope.$eval('{c: {b: a}}.c.b[key] = 7', {a: localA});
|
||||
expect(localA.value).toBe(7);
|
||||
|
||||
//Nothing should have touched the $rootScope.a
|
||||
expect($rootScope.a.value).toBeUndefined();
|
||||
}));
|
||||
|
||||
it('should allow use of locals in sub expressions of the left side of an assignment', inject(function($rootScope, $parse) {
|
||||
delete $rootScope.x;
|
||||
$rootScope.$eval('x[a][b] = true', {a: 'foo', b: 'bar'});
|
||||
expect($rootScope.x.foo.bar).toBe(true);
|
||||
|
||||
delete $rootScope.x;
|
||||
$rootScope.$eval('x.foo[b] = true', {b: 'bar'});
|
||||
expect($rootScope.x.foo.bar).toBe(true);
|
||||
|
||||
delete $rootScope.x;
|
||||
$rootScope.$eval('x[a].bar = true', {a: 'foo'});
|
||||
expect($rootScope.x.foo.bar).toBe(true);
|
||||
}));
|
||||
|
||||
it('should ignore locals beyond the root object of an assignment expression', inject(function($rootScope) {
|
||||
var a = {};
|
||||
var locals = {a: a};
|
||||
$rootScope.b = {a: {value: 123}};
|
||||
$rootScope.$eval('b.a.value = 1', locals);
|
||||
expect(a.value).toBeUndefined();
|
||||
expect($rootScope.b.a.value).toBe(1);
|
||||
}));
|
||||
|
||||
it('should evaluate assignments in ternary operator', function() {
|
||||
scope.$eval('a = 1 ? 2 : 3');
|
||||
expect(scope.a).toBe(2);
|
||||
@@ -855,12 +799,6 @@ describe('parser', function() {
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval("c.a = 1", {c: Function.prototype.constructor});
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: c.a');
|
||||
});
|
||||
|
||||
it('should disallow traversing the Function object in a setter: E02', function() {
|
||||
@@ -995,14 +933,6 @@ describe('parser', function() {
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]["keys"](foo)');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Object constructor in assignment locals', function() {
|
||||
expect(function() {
|
||||
scope.$eval("O.constructor.a = 1", {O: Object});
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: O.constructor.a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Window and $element/node', function() {
|
||||
|
||||
@@ -87,9 +87,11 @@ describe('$sniffer', function() {
|
||||
var ua = $window.navigator.userAgent.toLowerCase();
|
||||
if (/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
|
||||
expectedPrefix = 'Webkit';
|
||||
} else if (/firefox/i.test(ua)) {
|
||||
}
|
||||
else if (/firefox/i.test(ua)) {
|
||||
expectedPrefix = 'Moz';
|
||||
} else if (/ie/i.test(ua) || /trident/i.test(ua)) {
|
||||
}
|
||||
else if (/ie/i.test(ua) || /trident/i.test(ua)) {
|
||||
expectedPrefix = 'Ms';
|
||||
}
|
||||
expect($sniffer.vendorPrefix).toBe(expectedPrefix);
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('$templateRequest', function() {
|
||||
}));
|
||||
|
||||
it('should cache the request to prevent extra downloads',
|
||||
inject(function($rootScope, $templateRequest, $httpBackend) {
|
||||
inject(function($rootScope, $templateRequest, $templateCache, $httpBackend) {
|
||||
|
||||
$httpBackend.expectGET('tpl.html').respond('matias');
|
||||
|
||||
@@ -34,12 +34,13 @@ describe('$templateRequest', function() {
|
||||
|
||||
expect(content[0]).toBe('matias');
|
||||
expect(content[1]).toBe('matias');
|
||||
expect($templateCache.get('tpl.html')).toBe('matias');
|
||||
}));
|
||||
|
||||
it('should throw an error when the template is not found',
|
||||
inject(function($rootScope, $templateRequest, $httpBackend) {
|
||||
|
||||
$httpBackend.expectGET('tpl.html').respond(404);
|
||||
$httpBackend.expectGET('tpl.html').respond(404, '', {}, 'Not found');
|
||||
|
||||
$templateRequest('tpl.html');
|
||||
|
||||
@@ -48,7 +49,7 @@ describe('$templateRequest', function() {
|
||||
expect(function() {
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: tpl.html');
|
||||
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: tpl.html (HTTP status: 404 Not found)');
|
||||
}));
|
||||
|
||||
it('should not throw when the template is not found and ignoreRequestError is true',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user