Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59205d7809 | |||
| 0cf170df16 | |||
| 05a3b088fd | |||
| 65df5da07a | |||
| 0689c3697f | |||
| 0f53c29954 | |||
| 11bfe85598 | |||
| 93c503fa16 | |||
| 9d4e948e82 | |||
| 26f19d0399 | |||
| 5cd2e2291b | |||
| 69bbbe675e | |||
| 825de1cdf2 | |||
| e5318c61ee | |||
| 67b40a19fb | |||
| 5fbac749c9 | |||
| 0daadeb3d3 | |||
| 6b7625a095 | |||
| ab7e0cd100 | |||
| dee5f7fea5 | |||
| 140d149cee | |||
| 4d65ddddf8 | |||
| abfce5327c | |||
| 944c150e6c | |||
| d8dc53d215 | |||
| 0ec206f5a6 |
+14
-2
@@ -2,6 +2,12 @@ language: node_js
|
||||
node_js:
|
||||
- '0.10'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- bower_components
|
||||
- docs/bower_components
|
||||
|
||||
branches:
|
||||
except:
|
||||
- /^g3_.*$/
|
||||
@@ -27,15 +33,21 @@ env:
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
|
||||
|
||||
install:
|
||||
# Check the size of caches
|
||||
- du -sh ./node_modules ./bower_components/ ./docs/bower_components/ || true
|
||||
# - npm config set registry http://23.251.144.68
|
||||
# Disable the spinner, it looks bad on Travis
|
||||
- npm config set spin false
|
||||
# Log HTTP requests
|
||||
- npm config set loglevel http
|
||||
- time ./scripts/travis/npm-bundle-deps.sh
|
||||
- time npm install
|
||||
- npm install -g npm@2.5
|
||||
# Instal npm dependecies and ensure that npm cache is not stale
|
||||
- scripts/npm/install-dependencies.sh
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
|
||||
@@ -1,3 +1,55 @@
|
||||
<a name="1.4.0-beta.4"></a>
|
||||
# 1.4.0-beta.4 overlyexplosive-poprocks (2015-02-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** prevent page reload if initial url has empty hash at the end
|
||||
([a509e9aa](https://github.com/angular/angular.js/commit/a509e9aa149d0f88cc39f703d539f7ffd4cd6103),
|
||||
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
|
||||
- **$parse:** Initialize elements in an array from left to right
|
||||
([966f6d83](https://github.com/angular/angular.js/commit/966f6d831f9469a917601f9a10604612cd7bd792))
|
||||
- **ngAria:** ensure native controls fire a single click
|
||||
([9d53e5a3](https://github.com/angular/angular.js/commit/9d53e5a38dd369dec82d82e13e078df3d6054c8a),
|
||||
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
|
||||
- **ngMock:** handle cases where injector is created before tests
|
||||
([898714df](https://github.com/angular/angular.js/commit/898714df9ea38f9ef700015ced5ddea52f096b77),
|
||||
[#10967](https://github.com/angular/angular.js/issues/10967))
|
||||
- **sanitize:** handle newline characters inside special tags
|
||||
([cc8755cd](https://github.com/angular/angular.js/commit/cc8755cda6efda0b52954388e8a8d5306e4bfbca),
|
||||
[030a42e7](https://github.com/angular/angular.js/commit/030a42e79dec8a4bb73053762f7a54d797a058f6)
|
||||
[#10943](https://github.com/angular/angular.js/issues/10943))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ng-jq:** adds the ability to force jqLite or a specific jQuery version
|
||||
([09ee82d8](https://github.com/angular/angular.js/commit/09ee82d84dcbea4a6e8d85903af82dcd087a78a7))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.13"></a>
|
||||
# 1.3.13 meticulous-riffleshuffle (2015-02-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** prevent page reload if initial url has empty hash at the end
|
||||
([4b3a590b](https://github.com/angular/angular.js/commit/4b3a590b009d7fdceda7f52e7ba0352a271b3256),
|
||||
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
|
||||
- **ngAria:** ensure native controls fire a single click
|
||||
([69ee593f](https://github.com/angular/angular.js/commit/69ee593fd2cb5f1d7757efbe6b256e4458752fd7),
|
||||
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
|
||||
- **ngMock:** handle cases where injector is created before tests
|
||||
([39ddef68](https://github.com/angular/angular.js/commit/39ddef682971d3b7282bf9d08f6eaf97b7f4bca4),
|
||||
[#10967](https://github.com/angular/angular.js/issues/10967))
|
||||
- **sanitize:** handle newline characters inside special tags
|
||||
([11aedbd7](https://github.com/angular/angular.js/commit/11aedbd741ccddba060a9805adba1779391731da),
|
||||
[ce49d4d6](https://github.com/angular/angular.js/commit/ce49d4d61bd02464b6c6376af8048f6eb09330a8)
|
||||
[#10943](https://github.com/angular/angular.js/issues/10943))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.3"></a>
|
||||
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
|
||||
|
||||
|
||||
+8
-4
@@ -17,10 +17,6 @@ module.exports = function(grunt) {
|
||||
NG_VERSION.cdn = versionInfo.cdnVersion;
|
||||
var dist = 'angular-'+ NG_VERSION.full;
|
||||
|
||||
//global beforeEach
|
||||
util.init();
|
||||
|
||||
|
||||
//config
|
||||
grunt.initConfig({
|
||||
NG_VERSION: NG_VERSION,
|
||||
@@ -285,6 +281,10 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
shell: {
|
||||
"npm-install": {
|
||||
command: path.normalize('scripts/npm/install-dependencies.sh')
|
||||
},
|
||||
|
||||
"promises-aplus-tests": {
|
||||
options: {
|
||||
stdout: false,
|
||||
@@ -311,6 +311,10 @@ module.exports = function(grunt) {
|
||||
}
|
||||
});
|
||||
|
||||
// global beforeEach task
|
||||
if (!process.env.TRAVIS) {
|
||||
grunt.task.run('shell:npm-install');
|
||||
}
|
||||
|
||||
//alias tasks
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
|
||||
@@ -67,7 +67,7 @@ illustration we typically build snappy apps with hundreds or thousands of active
|
||||
|
||||
### How big is the angular.js file that I need to include?
|
||||
|
||||
The size of the file is < 36KB compressed and minified.
|
||||
The size of the file is ~50KB compressed and minified.
|
||||
|
||||
|
||||
### Can I use the open-source Closure Library with Angular?
|
||||
|
||||
@@ -14,13 +14,6 @@ var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP
|
||||
|
||||
module.exports = {
|
||||
|
||||
init: function() {
|
||||
if (!process.env.TRAVIS) {
|
||||
shell.exec('npm install');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
startKarma: function(config, singleRun, done){
|
||||
var browsers = grunt.option('browsers');
|
||||
var reporters = grunt.option('reporters');
|
||||
|
||||
@@ -12,9 +12,9 @@ set -e
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.6-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
|
||||
CONNECT_DOWNLOAD="sc-4.3.6-linux.tar.gz"
|
||||
|
||||
CONNECT_LOG="$LOGS_DIR/sauce-connect"
|
||||
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+6031
-1453
File diff suppressed because it is too large
Load Diff
+6
-1
@@ -5,6 +5,11 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~0.10",
|
||||
"npm": "~2.5"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
@@ -38,7 +43,7 @@
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "vojtajina/karma#socketio_10",
|
||||
"karma": "~0.12.0",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
|
||||
@@ -63,6 +63,21 @@ function prepare {
|
||||
cp $BUILD_DIR/angular-csp.css $TMP_DIR/bower-angular
|
||||
|
||||
|
||||
#
|
||||
# Run local precommit script if there is one
|
||||
#
|
||||
for repo in "${REPOS[@]}"
|
||||
do
|
||||
if [ -f $TMP_DIR/bower-$repo/precommit.sh ]
|
||||
then
|
||||
echo "-- Running precommit.sh script for bower-$repo"
|
||||
cd $TMP_DIR/bower-$repo
|
||||
$TMP_DIR/bower-$repo/precommit.sh
|
||||
cd $SCRIPT_DIR
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
#
|
||||
# update bower.json
|
||||
# tag each repo
|
||||
|
||||
@@ -10,22 +10,17 @@
|
||||
var _ = require('lodash');
|
||||
var sorted = require('sorted-object');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
|
||||
function cleanModule(module, name) {
|
||||
|
||||
// keep `from` and `resolve` properties for git dependencies, delete otherwise
|
||||
if (!(module.resolved && module.resolved.match(/^git:\/\//))) {
|
||||
delete module.from;
|
||||
// keep `resolve` properties for git dependencies, delete otherwise
|
||||
delete module.from;
|
||||
if (!(module.resolved && module.resolved.match(/^git(\+[a-z]+)?:\/\//))) {
|
||||
delete module.resolved;
|
||||
}
|
||||
|
||||
if (name === 'chokidar') {
|
||||
if (module.version === '0.8.1') {
|
||||
delete module.dependencies;
|
||||
}
|
||||
}
|
||||
|
||||
_.forEach(module.dependencies, function(mod, name) {
|
||||
cleanModule(mod, name);
|
||||
});
|
||||
@@ -33,10 +28,11 @@ function cleanModule(module, name) {
|
||||
|
||||
|
||||
console.log('Reading npm-shrinkwrap.json');
|
||||
var shrinkwrap = require('./../npm-shrinkwrap.json');
|
||||
var shrinkwrap = require('../../npm-shrinkwrap.json');
|
||||
|
||||
console.log('Cleaning shrinkwrap object');
|
||||
cleanModule(shrinkwrap, shrinkwrap.name);
|
||||
|
||||
console.log('Writing cleaned npm-shrinkwrap.json');
|
||||
fs.writeFileSync('./npm-shrinkwrap.json', JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
|
||||
var cleanShrinkwrapPath = path.join(__dirname, '..', '..', 'npm-shrinkwrap.clean.json');
|
||||
console.log('Writing cleaned to', cleanShrinkwrapPath);
|
||||
fs.writeFileSync(cleanShrinkwrapPath, JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SHRINKWRAP_FILE=npm-shrinkwrap.json
|
||||
SHRINKWRAP_CACHED_FILE=node_modules/npm-shrinkwrap.cached.json
|
||||
|
||||
if diff -q $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE; then
|
||||
echo 'No shrinkwrap changes detected. npm install will be skipped...';
|
||||
else
|
||||
echo 'Blowing away node_modules and reinstalling npm dependencies...'
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
cp $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE
|
||||
echo 'npm install successful!'
|
||||
fi
|
||||
@@ -159,20 +159,23 @@
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
|
||||
* This directive sets the `disabled` attribute on the element if the
|
||||
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
|
||||
*
|
||||
* A special directive is necessary because we cannot use interpolation inside the `disabled`
|
||||
* attribute. The following example would make the button enabled on Chrome/Firefox
|
||||
* but not on older IEs:
|
||||
*
|
||||
* ```html
|
||||
* <div ng-init="scope = { isDisabled: false }">
|
||||
* <button disabled="{{scope.isDisabled}}">Disabled</button>
|
||||
* <div ng-init="isDisabled = false">
|
||||
* <button disabled="{{isDisabled}}">Disabled</button>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as disabled. (Their presence means true and their absence means false.)
|
||||
* This is because the HTML specification does not require browsers to preserve the values of
|
||||
* boolean attributes such as `disabled` (Their presence means true and their absence means false.)
|
||||
* If we put an Angular interpolation expression into such an attribute then the
|
||||
* binding information would be lost when the browser removes the attribute.
|
||||
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
@@ -191,7 +194,7 @@
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "disabled" will be set on the element
|
||||
* then the `disabled` attribute will be set on the element
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@@ -1250,7 +1250,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
return value;
|
||||
});
|
||||
|
||||
if (attr.min || attr.ngMin) {
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal;
|
||||
ctrl.$validators.min = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
|
||||
@@ -1266,7 +1266,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
});
|
||||
}
|
||||
|
||||
if (attr.max || attr.ngMax) {
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal;
|
||||
ctrl.$validators.max = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
|
||||
|
||||
+14
-15
@@ -244,6 +244,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ngModelGet = parsedNgModel,
|
||||
ngModelSet = parsedNgModelAssign,
|
||||
pendingDebounce = null,
|
||||
parserValid,
|
||||
ctrl = this;
|
||||
|
||||
this.$$setOptions = function(options) {
|
||||
@@ -516,16 +517,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
// the model although neither viewValue nor the model on the scope changed
|
||||
var modelValue = ctrl.$$rawModelValue;
|
||||
|
||||
// Check if the there's a parse error, so we don't unset it accidentially
|
||||
var parserName = ctrl.$$parserName || 'parse';
|
||||
var parserValid = ctrl.$error[parserName] ? false : undefined;
|
||||
|
||||
var prevValid = ctrl.$valid;
|
||||
var prevModelValue = ctrl.$modelValue;
|
||||
|
||||
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
|
||||
|
||||
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
|
||||
ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
|
||||
// If there was no change in validity, don't update the model
|
||||
// This prevents changing an invalid modelValue to undefined
|
||||
if (!allowInvalid && prevValid !== allValid) {
|
||||
@@ -543,12 +540,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
};
|
||||
|
||||
this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
|
||||
this.$$runValidators = function(modelValue, viewValue, doneCallback) {
|
||||
currentValidationRunId++;
|
||||
var localValidationRunId = currentValidationRunId;
|
||||
|
||||
// check parser error
|
||||
if (!processParseErrors(parseValid)) {
|
||||
if (!processParseErrors()) {
|
||||
validationDone(false);
|
||||
return;
|
||||
}
|
||||
@@ -558,21 +555,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
}
|
||||
processAsyncValidators();
|
||||
|
||||
function processParseErrors(parseValid) {
|
||||
function processParseErrors() {
|
||||
var errorKey = ctrl.$$parserName || 'parse';
|
||||
if (parseValid === undefined) {
|
||||
if (parserValid === undefined) {
|
||||
setValidity(errorKey, null);
|
||||
} else {
|
||||
setValidity(errorKey, parseValid);
|
||||
if (!parseValid) {
|
||||
if (!parserValid) {
|
||||
forEach(ctrl.$validators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
forEach(ctrl.$asyncValidators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// Set the parse error last, to prevent unsetting it, should a $validators key == parserName
|
||||
setValidity(errorKey, parserValid);
|
||||
return parserValid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -667,7 +665,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$$parseAndValidate = function() {
|
||||
var viewValue = ctrl.$$lastCommittedViewValue;
|
||||
var modelValue = viewValue;
|
||||
var parserValid = isUndefined(modelValue) ? undefined : true;
|
||||
parserValid = isUndefined(modelValue) ? undefined : true;
|
||||
|
||||
if (parserValid) {
|
||||
for (var i = 0; i < ctrl.$parsers.length; i++) {
|
||||
@@ -693,7 +691,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
|
||||
// This can happen if e.g. $setViewValue is called from inside a parser
|
||||
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
|
||||
ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
|
||||
if (!allowInvalid) {
|
||||
// Note: Don't check ctrl.$valid here, as we could have
|
||||
// external validators (e.g. calculated on the server),
|
||||
@@ -814,6 +812,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
// TODO(perf): why not move this to the action fn?
|
||||
if (modelValue !== ctrl.$modelValue) {
|
||||
ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
|
||||
parserValid = undefined;
|
||||
|
||||
var formatters = ctrl.$formatters,
|
||||
idx = formatters.length;
|
||||
@@ -826,7 +825,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
|
||||
ctrl.$render();
|
||||
|
||||
ctrl.$$runValidators(undefined, modelValue, viewValue, noop);
|
||||
ctrl.$$runValidators(modelValue, viewValue, noop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,55 @@
|
||||
* when keys are deleted and reinstated.
|
||||
*
|
||||
*
|
||||
* # Tracking and Duplicates
|
||||
*
|
||||
* When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
|
||||
*
|
||||
* * When an item is added, a new instance of the template is added to the DOM.
|
||||
* * When an item is removed, its template instance is removed from the DOM.
|
||||
* * When items are reordered, their respective templates are reordered in the DOM.
|
||||
*
|
||||
* By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
|
||||
* there are duplicates, it is not possible to maintain a one-to-one mapping between collection
|
||||
* items and DOM elements.
|
||||
*
|
||||
* If you do need to repeat duplicate items, you can substitute the default tracking behavior
|
||||
* with your own using the `track by` expression.
|
||||
*
|
||||
* For example, you may track items by the index of each item in the collection, using the
|
||||
* special scope property `$index`:
|
||||
* ```html
|
||||
* <div ng-repeat="n in [42, 42, 43, 43] track by $index">
|
||||
* {{n}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* You may use arbitrary expressions in `track by`, including references to custom functions
|
||||
* on the scope:
|
||||
* ```html
|
||||
* <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
|
||||
* {{n}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If you are working with objects that have an identifier property, you can track
|
||||
* by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
|
||||
* will not have to rebuild the DOM elements for items it has already rendered, even if the
|
||||
* JavaScript objects in the collection have been substituted for new ones:
|
||||
* ```html
|
||||
* <div ng-repeat="model in collection track by model.id">
|
||||
* {{model.name}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* When no `track by` expression is provided, it is equivalent to tracking by the built-in
|
||||
* `$id` function, which tracks items by their identity:
|
||||
* ```html
|
||||
* <div ng-repeat="obj in collection track by $id(obj)">
|
||||
* {{obj.prop}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* # 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
|
||||
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
|
||||
@@ -113,12 +162,12 @@
|
||||
*
|
||||
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
|
||||
*
|
||||
* * `variable in expression track by tracking_expression` – You can also provide an optional tracking function
|
||||
* which can be used to associate the objects in the collection with the DOM elements. If no tracking function
|
||||
* is specified the ng-repeat associates elements by identity in the collection. It is an error to have
|
||||
* more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
|
||||
* mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
|
||||
* before specifying a tracking expression.
|
||||
* * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
|
||||
* which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
|
||||
* is specified, ng-repeat associates elements by identity. It is an error to have
|
||||
* more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
|
||||
* mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be
|
||||
* applied before the tracking expression.
|
||||
*
|
||||
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
|
||||
* will be associated by item identity in the array.
|
||||
|
||||
+5
-3
@@ -129,6 +129,7 @@ function $AriaProvider() {
|
||||
* @name $aria
|
||||
*
|
||||
* @description
|
||||
* @priority 200
|
||||
*
|
||||
* The $aria service contains helper methods for applying common
|
||||
* [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
|
||||
@@ -205,6 +206,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
|
||||
link: function(scope, elem, attr, ngModel) {
|
||||
var shape = getShape(attr, elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
|
||||
@@ -217,19 +219,19 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
if (needsTabIndex) {
|
||||
needsTabIndex = false;
|
||||
return function ngAriaRadioReaction(newVal) {
|
||||
var boolVal = newVal === attr.value;
|
||||
var boolVal = (attr.value == ngModel.$viewValue);
|
||||
elem.attr('aria-checked', boolVal);
|
||||
elem.attr('tabindex', 0 - !boolVal);
|
||||
};
|
||||
} else {
|
||||
return function ngAriaRadioReaction(newVal) {
|
||||
elem.attr('aria-checked', newVal === attr.value);
|
||||
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function ngAriaCheckboxReaction(newVal) {
|
||||
elem.attr('aria-checked', !!newVal);
|
||||
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
|
||||
}
|
||||
|
||||
switch (shape) {
|
||||
|
||||
@@ -208,6 +208,7 @@ function shallowClearAndCopy(src, dst) {
|
||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
||||
*
|
||||
*
|
||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
|
||||
* with (httpResponse) argument.
|
||||
*
|
||||
|
||||
@@ -411,7 +411,6 @@ function htmlParser(html, handler) {
|
||||
}
|
||||
|
||||
var hiddenPre=document.createElement("pre");
|
||||
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
|
||||
/**
|
||||
* decodes all entities into regular string
|
||||
* @param value
|
||||
@@ -420,22 +419,10 @@ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
|
||||
function decodeEntities(value) {
|
||||
if (!value) { return ''; }
|
||||
|
||||
// Note: IE8 does not preserve spaces at the start/end of innerHTML
|
||||
// so we must capture them and reattach them afterward
|
||||
var parts = spaceRe.exec(value);
|
||||
var spaceBefore = parts[1];
|
||||
var spaceAfter = parts[3];
|
||||
var content = parts[2];
|
||||
if (content) {
|
||||
hiddenPre.innerHTML=content.replace(/</g,"<");
|
||||
// innerText depends on styling as it doesn't display hidden elements.
|
||||
// Therefore, it's better to use textContent not to cause unnecessary
|
||||
// reflows. However, IE<9 don't support textContent so the innerText
|
||||
// fallback is necessary.
|
||||
content = 'textContent' in hiddenPre ?
|
||||
hiddenPre.textContent : hiddenPre.innerText;
|
||||
}
|
||||
return spaceBefore + content + spaceAfter;
|
||||
hiddenPre.innerHTML = value.replace(/</g,"<");
|
||||
// innerText depends on styling as it doesn't display hidden elements.
|
||||
// Therefore, it's better to use textContent not to cause unnecessary reflows.
|
||||
return hiddenPre.textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1869,12 +1869,17 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
it('should validate even if min value changes on-the-fly', function() {
|
||||
$rootScope.min = 10;
|
||||
$rootScope.min = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" min="{{min}}" />');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('15');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.min = 10;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.min = 20;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeInvalid();
|
||||
@@ -1910,12 +1915,17 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
it('should validate even if the ngMin value changes on-the-fly', function() {
|
||||
$rootScope.min = 10;
|
||||
$rootScope.min = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" ng-min="min" />');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('15');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.min = 10;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.min = 20;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeInvalid();
|
||||
@@ -1952,12 +1962,17 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
it('should validate even if max value changes on-the-fly', function() {
|
||||
$rootScope.max = 10;
|
||||
$rootScope.max = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" max="{{max}}" />');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('5');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.max = 10;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.max = 0;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeInvalid();
|
||||
@@ -1993,12 +2008,17 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
it('should validate even if the ngMax value changes on-the-fly', function() {
|
||||
$rootScope.max = 10;
|
||||
$rootScope.max = undefined;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" ng-max="max" />');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('5');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.max = 10;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.max = 0;
|
||||
$rootScope.$digest();
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
@@ -1221,6 +1221,96 @@ describe('ngModel', function() {
|
||||
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'ab');
|
||||
expect(ctrl.$validators.mock.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should validate correctly when $parser name equals $validator key', function() {
|
||||
|
||||
ctrl.$validators.parserOrValidator = function(value) {
|
||||
switch (value) {
|
||||
case 'allInvalid':
|
||||
case 'parseValid-validatorsInvalid':
|
||||
case 'stillParseValid-validatorsInvalid':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.$validators.validator = function(value) {
|
||||
switch (value) {
|
||||
case 'allInvalid':
|
||||
case 'parseValid-validatorsInvalid':
|
||||
case 'stillParseValid-validatorsInvalid':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.$$parserName = 'parserOrValidator';
|
||||
ctrl.$parsers.push(function(value) {
|
||||
switch (value) {
|
||||
case 'allInvalid':
|
||||
case 'stillAllInvalid':
|
||||
case 'parseInvalid-validatorsValid':
|
||||
case 'stillParseInvalid-validatorsValid':
|
||||
return undefined;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
//Parser and validators are invalid
|
||||
scope.$apply('value = "allInvalid"');
|
||||
expect(scope.value).toBe('allInvalid');
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true, validator: true});
|
||||
|
||||
ctrl.$validate();
|
||||
expect(scope.value).toEqual('allInvalid');
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true, validator: true});
|
||||
|
||||
ctrl.$setViewValue('stillAllInvalid');
|
||||
expect(scope.value).toBeUndefined();
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true});
|
||||
|
||||
ctrl.$validate();
|
||||
expect(scope.value).toBeUndefined();
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true});
|
||||
|
||||
//Parser is valid, validators are invalid
|
||||
scope.$apply('value = "parseValid-validatorsInvalid"');
|
||||
expect(scope.value).toBe('parseValid-validatorsInvalid');
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true, validator: true});
|
||||
|
||||
ctrl.$validate();
|
||||
expect(scope.value).toBe('parseValid-validatorsInvalid');
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true, validator: true});
|
||||
|
||||
ctrl.$setViewValue('stillParseValid-validatorsInvalid');
|
||||
expect(scope.value).toBeUndefined();
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true, validator: true});
|
||||
|
||||
ctrl.$validate();
|
||||
expect(scope.value).toBeUndefined();
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true, validator: true});
|
||||
|
||||
//Parser is invalid, validators are valid
|
||||
scope.$apply('value = "parseInvalid-validatorsValid"');
|
||||
expect(scope.value).toBe('parseInvalid-validatorsValid');
|
||||
expect(ctrl.$error).toEqual({});
|
||||
|
||||
ctrl.$validate();
|
||||
expect(scope.value).toBe('parseInvalid-validatorsValid');
|
||||
expect(ctrl.$error).toEqual({});
|
||||
|
||||
ctrl.$setViewValue('stillParseInvalid-validatorsValid');
|
||||
expect(scope.value).toBeUndefined();
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true});
|
||||
|
||||
ctrl.$validate();
|
||||
expect(scope.value).toBeUndefined();
|
||||
expect(ctrl.$error).toEqual({parserOrValidator: true});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -85,6 +85,28 @@ describe('$aria', function() {
|
||||
expect(element.attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should handle checkbox with string model values using ng(True|False)Value', function() {
|
||||
var element = $compile('<input type="checkbox" ng-model="val" ng-true-value="\'yes\'" ' +
|
||||
'ng-false-value="\'no\'">'
|
||||
)(scope);
|
||||
|
||||
scope.$apply('val="yes"');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
|
||||
scope.$apply('val="no"');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should handle checkbox with integer model values using ngTrueValue', function() {
|
||||
var element = $compile('<input type="checkbox" ng-model="val" ng-true-value="0">')(scope);
|
||||
|
||||
scope.$apply('val=0');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
|
||||
scope.$apply('val=1');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach itself to input type="radio"', function() {
|
||||
var element = $compile('<input type="radio" ng-model="val" value="one">' +
|
||||
'<input type="radio" ng-model="val" value="two">')(scope);
|
||||
@@ -98,6 +120,36 @@ describe('$aria', function() {
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('should handle radios with integer model values', function() {
|
||||
var element = $compile('<input type="radio" ng-model="val" value="0">' +
|
||||
'<input type="radio" ng-model="val" value="1">')(scope);
|
||||
|
||||
scope.$apply('val=0');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('false');
|
||||
|
||||
scope.$apply('val=1');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('should handle radios with boolean model values using ngValue', function() {
|
||||
var element = $compile('<input type="radio" ng-model="val" ng-value="valExp">' +
|
||||
'<input type="radio" ng-model="val" ng-value="valExp2">')(scope);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.valExp = true;
|
||||
scope.valExp2 = false;
|
||||
scope.val = true;
|
||||
});
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('false');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('should attach itself to role="radio"', function() {
|
||||
scope.$apply("val = 'one'");
|
||||
compileInput('<div role="radio" ng-model="val" value="{{val}}"></div>');
|
||||
|
||||
@@ -495,8 +495,7 @@ describe('HTML', function() {
|
||||
});
|
||||
|
||||
describe('decodeEntities', function() {
|
||||
var handler, text,
|
||||
origHiddenPre = window.hiddenPre;
|
||||
var handler, text;
|
||||
|
||||
beforeEach(function() {
|
||||
text = '';
|
||||
@@ -511,37 +510,13 @@ describe('decodeEntities', function() {
|
||||
module('ngSanitize');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
window.hiddenPre = origHiddenPre;
|
||||
it('should unescape text', function() {
|
||||
htmlParser('a<div>&</div>c', handler);
|
||||
expect(text).toEqual('a<div>&</div>c');
|
||||
});
|
||||
|
||||
it('should use innerText if textContent is not available (IE<9)', function() {
|
||||
window.hiddenPre = {
|
||||
innerText: 'INNER_TEXT'
|
||||
};
|
||||
inject(function($sanitize) {
|
||||
htmlParser('<tag>text</tag>', handler);
|
||||
expect(text).toEqual('INNER_TEXT');
|
||||
});
|
||||
});
|
||||
it('should use textContent if available', function() {
|
||||
window.hiddenPre = {
|
||||
textContent: 'TEXT_CONTENT',
|
||||
innerText: 'INNER_TEXT'
|
||||
};
|
||||
inject(function($sanitize) {
|
||||
htmlParser('<tag>text</tag>', handler);
|
||||
expect(text).toEqual('TEXT_CONTENT');
|
||||
});
|
||||
});
|
||||
it('should use textContent even if empty', function() {
|
||||
window.hiddenPre = {
|
||||
textContent: '',
|
||||
innerText: 'INNER_TEXT'
|
||||
};
|
||||
inject(function($sanitize) {
|
||||
htmlParser('<tag>text</tag>', handler);
|
||||
expect(text).toEqual('');
|
||||
});
|
||||
it('should preserve whitespace', function() {
|
||||
htmlParser(' a&b ', handler);
|
||||
expect(text).toEqual(' a&b ');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user