Compare commits
334 Commits
g3_v1_4
...
v1.5.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
| b77e14beea | |||
| a1f461e429 | |||
| 13587193a5 | |||
| a1ff35850c | |||
| 95b3e1ce6c | |||
| b78b12976a | |||
| a31c082de6 | |||
| 5afd54514d | |||
| 72b96ef57a | |||
| 90975db5f9 | |||
| 25bc531802 | |||
| 4e1b36c216 | |||
| d4fa331308 | |||
| a801df719e | |||
| cb74999b17 | |||
| 2764536e8f | |||
| 8ccc0547a8 | |||
| dd14e0c44d | |||
| 7e24590fa3 | |||
| 8b6360338d | |||
| d91cf16796 | |||
| f31c5a3924 | |||
| 16ccac91d0 | |||
| 91b080e6e6 | |||
| feeb19787c | |||
| 98c2db7f9c | |||
| 06aa52efff | |||
| fabc6ab5b0 | |||
| 93c7251f5f | |||
| e1def1b8fe | |||
| e5cab951f4 | |||
| f7eab8d8fe | |||
| 495d40d802 | |||
| d28ae2126e | |||
| 8955cfb646 | |||
| 776972ed9c | |||
| 1358b3ca9b | |||
| e020b8993e | |||
| b2b896f949 | |||
| 3c5827b6f5 | |||
| 0e03644dad | |||
| 112024271b | |||
| bca0a1f786 | |||
| 959f2bbb2d | |||
| 6a47c0d75d | |||
| b0248b7894 | |||
| 6fd41e7f59 | |||
| 3297bbd188 | |||
| 8863836cd4 | |||
| b8fb0c4573 | |||
| 17b700a339 | |||
| 811b20e3b9 | |||
| 5ded3d3e73 | |||
| 986647a968 | |||
| 7b51243be5 | |||
| 798fb18542 | |||
| 99d601a048 | |||
| a6e9174a27 | |||
| dec8a0eb72 | |||
| 98776487a0 | |||
| b3ef5e0852 | |||
| 04efdd5bfa | |||
| 9f5d76e16b | |||
| 525be5b7d4 | |||
| 042e0f1f0a | |||
| 03872983a4 | |||
| 62f79e820f | |||
| e5c26e92cc | |||
| 4bcb307abc | |||
| 0e729e1dd5 | |||
| 9bb184d181 | |||
| a7a053f5be | |||
| 9630159444 | |||
| e3be5d6efa | |||
| c2173c1298 | |||
| 0b94e8a8bf | |||
| 983374c574 | |||
| 05d3ed0d9b | |||
| 1b25f80cd2 | |||
| 6cdbda7cf1 | |||
| c9e6cf9be0 | |||
| c7ed8a33af | |||
| 6988667e5e | |||
| e57cf13d5d | |||
| e4e5677fbd | |||
| 8b6b428271 | |||
| 8f0b482596 | |||
| 6428ed5bb5 | |||
| bc41ad8aa8 | |||
| 6858caf251 | |||
| 20604e7fc4 | |||
| b7b06d8477 | |||
| de9777d819 | |||
| 1b06f33f30 | |||
| ca6e266869 | |||
| 23c4ae522a | |||
| 3112f8e910 | |||
| 21ab82906e | |||
| 1dd206ef85 | |||
| da5db4b1b3 | |||
| 3694390c90 | |||
| 36a3c81177 | |||
| b0e7d548d0 | |||
| e4bb838795 | |||
| 0ea535035a | |||
| e9aba90f7f | |||
| f50b0cb393 | |||
| 8b3bec7e07 | |||
| 6752337629 | |||
| 48ad7486d9 | |||
| 341b834229 | |||
| 7a668cdd7d | |||
| 5a674f3bb9 | |||
| e94b37e20e | |||
| f5ebcbacf8 | |||
| 0812af49bd | |||
| c3ae6ed78e | |||
| c3a2691115 | |||
| 6976d6d8d8 | |||
| b75c0d8d05 | |||
| df6fade6e6 | |||
| 983b059812 | |||
| 25e8c5927c | |||
| 6628b4f1e5 | |||
| 6e18b50a5b | |||
| 77419cf19f | |||
| 193153c3d3 | |||
| 6a0686d434 | |||
| 08c9a5e9e7 | |||
| a72c12bd70 | |||
| 73e38658c4 | |||
| b641181b93 | |||
| 7ffb2d3c17 | |||
| 023b777a56 | |||
| 0c9480de8c | |||
| 0bc275461c | |||
| 0b5ecc64f0 | |||
| b183eae7ae | |||
| 7ddbc9aa35 | |||
| b2a937d425 | |||
| 4473b81cda | |||
| 98528be311 | |||
| 9190d4c3ad | |||
| d9ec9951e2 | |||
| fedafdc677 | |||
| 26c36bb4d1 | |||
| c7a2028ab3 | |||
| 596af70101 | |||
| 96c73a0672 | |||
| 1537651c8c | |||
| d59aeb4e0b | |||
| 551a33db56 | |||
| cd91640146 | |||
| ab5824ee12 | |||
| 5c50723535 | |||
| 546a277d65 | |||
| a5ff651a59 | |||
| ccd47ec904 | |||
| 2e23a3cdbc | |||
| ca7f4a387c | |||
| 898a3fd3b9 | |||
| 9473371343 | |||
| e5275590db | |||
| 78297d252d | |||
| e5e0884eaf | |||
| 551d1c20cf | |||
| f5aa207960 | |||
| 4412fe238f | |||
| 8088284f66 | |||
| 25f1bbaad1 | |||
| bd7b217729 | |||
| 50557a6cd3 | |||
| 1c13a4f45d | |||
| 1a98c0ee34 | |||
| b837fc3116 | |||
| aff74ec87b | |||
| 077ee37942 | |||
| 0efef2385f | |||
| 7da22e6685 | |||
| 92bdd7627f | |||
| 39ebb06baf | |||
| 9cf6b197ab | |||
| 4971ef12d4 | |||
| 7c792f4cc9 | |||
| 4daafd3dbe | |||
| fe11265fdc | |||
| 75292a6cb5 | |||
| 4ff6c85792 | |||
| e26bc2370b | |||
| 33713deeb8 | |||
| 17715fa366 | |||
| 96288d02d3 | |||
| 8be98e4fdf | |||
| cf83b4f445 | |||
| 15bfea8339 | |||
| 9b90c32f31 | |||
| f4bf744516 | |||
| 0d96995fcc | |||
| 2a85a634f8 | |||
| 00d2b2c4cf | |||
| e688f07023 | |||
| 8e2f7d37e4 | |||
| f5bc3ed9b4 | |||
| 11d60af3dc | |||
| 865f6065e7 | |||
| 91c0b364af | |||
| fce07f55e5 | |||
| c8768d12f2 | |||
| af6342d6fb | |||
| a179757fad | |||
| 35eada68c4 | |||
| bfad2a4f4a | |||
| 54e816552f | |||
| 4fc734665e | |||
| b8736e65b0 | |||
| b9bed7d9da | |||
| b2fc39d2dd | |||
| 7c0731edb2 | |||
| 8fe781fbe7 | |||
| 23932a26ff | |||
| 3ca4ca463c | |||
| 395f3ec638 | |||
| 964a901bd8 | |||
| a995ee17ee | |||
| 794d1c1ebe | |||
| 662fb282c1 | |||
| ffb6b2fb56 | |||
| 941c1c35f1 | |||
| 29a05984fe | |||
| 4038aabffa | |||
| 773efd0812 | |||
| 80881949fc | |||
| 2c8d87e064 | |||
| 33c67ce785 | |||
| ad4296d966 | |||
| 469b14a525 | |||
| 1caf0b6bee | |||
| 9f716dd590 | |||
| 914a934b6f | |||
| a4ada8ba9c | |||
| 40c974ab14 | |||
| 06f002b161 | |||
| 8226ff8b8e | |||
| d67e999dfb | |||
| a8e03b3a90 | |||
| 6f3e26c404 | |||
| 1c75ea613d | |||
| c8e1db2050 | |||
| 74ed28665d | |||
| 4ad0ca130d | |||
| b51dd3010d | |||
| 6e6f31943c | |||
| 66fee7e22a | |||
| beea571660 | |||
| 0c8a9a0e1a | |||
| fd83d3724a | |||
| 2fcfd75a14 | |||
| 2d40507547 | |||
| ecf9304811 | |||
| b9ab88776b | |||
| 00bf218304 | |||
| 95fbf168d1 | |||
| 51a27c0f1a | |||
| f02811f0bb | |||
| bcf78ebb18 | |||
| 3050dd1b47 | |||
| 1efdb4745a | |||
| d73f7dff45 | |||
| f047ad2628 | |||
| 049d3def80 | |||
| 00778aa239 | |||
| 6b123a0419 | |||
| 4079eea6b3 | |||
| d277ac2eb8 | |||
| 9deb123d04 | |||
| 1c2d2e8ba0 | |||
| 144bcc84ab | |||
| 693021c449 | |||
| dc818e1165 | |||
| e51174bf13 | |||
| 7f3f3dd3eb | |||
| d077966ff1 | |||
| 0df4ff800a | |||
| 6208b76afa | |||
| 70dac5ae82 | |||
| aafbd94439 | |||
| e732f8e579 | |||
| 9f67da6252 | |||
| b3a3c6a72e | |||
| e8cdabe129 | |||
| 3af71bee75 | |||
| f2724b2bbc | |||
| 38500669f6 | |||
| 862c9d8bb2 | |||
| 240d5896ec | |||
| 64ef084b91 | |||
| 8b27c3f064 | |||
| 8366622bed | |||
| d3de0066b0 | |||
| b7e5133b2e | |||
| 42c97c5db5 | |||
| e1f4f23f78 | |||
| 630280c7fb | |||
| 496a67c10e | |||
| 7dcfe5e03e | |||
| 9e83b8355e | |||
| 03726f7fbd | |||
| 75893ae9e7 | |||
| d9ca245917 | |||
| 3259aabdf3 | |||
| befeeb3689 | |||
| 79577c5d31 | |||
| b71d7c3f3c | |||
| fa4c7b7f1d | |||
| 652b83eb22 | |||
| 20cf7d5e3a | |||
| ded2518756 | |||
| b366f0352a | |||
| 76c2491a31 | |||
| ef3df93afe | |||
| 106f90aafa | |||
| 86e8088b38 | |||
| a654bdfed9 | |||
| 2c22c57e58 | |||
| a4dfa4d061 | |||
| bc0d8c4eea | |||
| 9b84fcad76 | |||
| f33ce173c9 | |||
| 181fc567d8 | |||
| 94207f8fb6 | |||
| 35a21532b7 | |||
| 1c97a6057b | |||
| 658a865c5b | |||
| f8cf28a788 |
+16
-21
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '0.10'
|
||||
- '4.2'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@@ -15,28 +15,26 @@ branches:
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- JOB=ci-checks
|
||||
- JOB=unit BROWSER_PROVIDER=saucelabs
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit BROWSER_PROVIDER=browserstack
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
|
||||
global:
|
||||
- CXX=g++-4.8 # node 4 likes the G++ v4.8 compiler
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
- BROWSER_STACK_USERNAME=VojtaJina
|
||||
- BROWSER_STACK_ACCESS_KEY=QCQJ1ZpWXpBkSwEdD8ev
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
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"
|
||||
# node 4 likes the G++ v4.8 compiler
|
||||
# see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
install:
|
||||
# Check the size of caches
|
||||
@@ -46,21 +44,18 @@ install:
|
||||
- npm config set spin false
|
||||
# Log HTTP requests
|
||||
- npm config set loglevel http
|
||||
- npm install -g npm@2.5
|
||||
# Instal npm dependecies and ensure that npm cache is not stale
|
||||
- scripts/npm/install-dependencies.sh
|
||||
#- npm install -g npm@2.5
|
||||
# Install npm dependencies and ensure that npm cache is not stale
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./scripts/travis/start_browser_provider.sh
|
||||
- npm install -g grunt-cli
|
||||
- grunt package
|
||||
- ./scripts/travis/wait_for_browser_provider.sh
|
||||
- ./scripts/travis/before_build.sh
|
||||
|
||||
script:
|
||||
- ./scripts/travis/build.sh
|
||||
|
||||
after_script:
|
||||
- ./scripts/travis/tear_down_browser_provider.sh
|
||||
- ./scripts/travis/print_logs.sh
|
||||
|
||||
notifications:
|
||||
|
||||
+974
-59
File diff suppressed because it is too large
Load Diff
+10
-2
@@ -71,7 +71,7 @@ chances of your issue being dealt with quickly:
|
||||
* **Angular Version(s)** - is it a regression?
|
||||
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
|
||||
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
|
||||
[JSFiddle][jsfiddle]) or an unambiguous set of steps.
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
@@ -123,13 +123,19 @@ Before you submit your pull request consider the following guidelines:
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular test suite to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
* Commit your changes to your branch (e.g. `my-fix-branch`).
|
||||
* Push the changes to your GitHub repository (this will update your Pull Request).
|
||||
|
||||
If the PR gets too outdated we may ask you to rebase and force push to update the PR:
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
|
||||
*WARNING. Squashing or reverting commits and forced push thereafter may remove GitHub comments
|
||||
on code that were previously made by you and others in your commits.*
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
#### After your pull request is merged
|
||||
@@ -187,6 +193,8 @@ We have very precise rules over how our git commit messages can be formatted. T
|
||||
readable messages** that are easy to follow when looking through the **project history**. But also,
|
||||
we use the git commit messages to **generate the AngularJS change log**.
|
||||
|
||||
The commit message formatting can be added using a typical git workflow or through the use of a CLI wizard ([Commitizen](https://github.com/commitizen/cz-cli)). To use the wizard, run `npm run commit` in your terminal after staging your changes in git.
|
||||
|
||||
### Commit Message Format
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||
format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
+14
-12
@@ -162,7 +162,7 @@ module.exports = function(grunt) {
|
||||
'!src/angular.bind.js' // we ignore this file since contains an early return statement
|
||||
],
|
||||
options: {
|
||||
config: ".jscsrc"
|
||||
config: '.jscsrc'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -231,9 +231,9 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-aria.js',
|
||||
src: util.wrap(files['angularModules']['ngAria'], 'module')
|
||||
},
|
||||
"promises-aplus-adapter": {
|
||||
'promises-aplus-adapter': {
|
||||
dest:'tmp/promises-aplus-adapter++.js',
|
||||
src:['src/ng/q.js','lib/promises-aplus/promises-aplus-test-adapter.js']
|
||||
src:['src/ng/q.js', 'lib/promises-aplus/promises-aplus-test-adapter.js']
|
||||
}
|
||||
},
|
||||
|
||||
@@ -253,7 +253,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
|
||||
"ddescribe-iit": {
|
||||
'ddescribe-iit': {
|
||||
files: [
|
||||
'src/**/*.js',
|
||||
'test/**/*.js',
|
||||
@@ -274,7 +274,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
|
||||
"merge-conflict": {
|
||||
'merge-conflict': {
|
||||
files: [
|
||||
'src/**/*',
|
||||
'test/**/*',
|
||||
@@ -304,11 +304,11 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
shell: {
|
||||
"npm-install": {
|
||||
command: path.normalize('scripts/npm/install-dependencies.sh')
|
||||
'npm-install': {
|
||||
command: 'node scripts/npm/check-node-modules.js'
|
||||
},
|
||||
|
||||
"promises-aplus-tests": {
|
||||
'promises-aplus-tests': {
|
||||
options: {
|
||||
stdout: false,
|
||||
stderr: true,
|
||||
@@ -339,8 +339,10 @@ module.exports = function(grunt) {
|
||||
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']);
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package', 'test:unit', 'test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
|
||||
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
|
||||
@@ -350,11 +352,11 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
|
||||
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
|
||||
grunt.registerTask('test:e2e', 'Alias for test:protractor', ['test:protractor']);
|
||||
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter','shell:promises-aplus-tests']);
|
||||
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter', 'shell:promises-aplus-tests']);
|
||||
|
||||
grunt.registerTask('minify', ['bower','clean', 'build', 'minall']);
|
||||
grunt.registerTask('minify', ['bower', 'clean', 'build', 'minall']);
|
||||
grunt.registerTask('webserver', ['connect:devserver']);
|
||||
grunt.registerTask('package', ['bower','clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
|
||||
grunt.registerTask('package', ['bower', 'validate-angular-files', 'clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
|
||||
grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint', 'jscs']);
|
||||
grunt.registerTask('default', ['package']);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
Copyright (c) 2010-2016 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
|
||||
|
||||
@@ -21,7 +21,7 @@ piece of cake. Best of all?? It makes development fun!
|
||||
|
||||
Building AngularJS
|
||||
---------
|
||||
[Once you have your environment setup](http://docs.angularjs.org/misc/contribute) just run:
|
||||
[Once you have your environment set up](http://docs.angularjs.org/misc/contribute) just run:
|
||||
|
||||
grunt package
|
||||
|
||||
|
||||
Vendored
+4
-3
@@ -14,6 +14,7 @@ var angularFiles = {
|
||||
|
||||
'src/ng/anchorScroll.js',
|
||||
'src/ng/animate.js',
|
||||
'src/ng/animateRunner.js',
|
||||
'src/ng/animateCss.js',
|
||||
'src/ng/browser.js',
|
||||
'src/ng/cacheFactory.js',
|
||||
@@ -33,6 +34,7 @@ var angularFiles = {
|
||||
'src/ng/q.js',
|
||||
'src/ng/raf.js',
|
||||
'src/ng/rootScope.js',
|
||||
'src/ng/rootElement.js',
|
||||
'src/ng/sanitizeUri.js',
|
||||
'src/ng/sce.js',
|
||||
'src/ng/sniffer.js',
|
||||
@@ -84,7 +86,7 @@ var angularFiles = {
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
'stringify.js',
|
||||
'src/stringify.js',
|
||||
'src/minErr.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
@@ -92,7 +94,6 @@ var angularFiles = {
|
||||
'angularModules': {
|
||||
'ngAnimate': [
|
||||
'src/ngAnimate/shared.js',
|
||||
'src/ngAnimate/body.js',
|
||||
'src/ngAnimate/rafScheduler.js',
|
||||
'src/ngAnimate/animateChildrenDirective.js',
|
||||
'src/ngAnimate/animateCss.js',
|
||||
@@ -100,8 +101,8 @@ var angularFiles = {
|
||||
'src/ngAnimate/animateJs.js',
|
||||
'src/ngAnimate/animateJsDriver.js',
|
||||
'src/ngAnimate/animateQueue.js',
|
||||
'src/ngAnimate/animateRunner.js',
|
||||
'src/ngAnimate/animation.js',
|
||||
'src/ngAnimate/ngAnimateSwap.js',
|
||||
'src/ngAnimate/module.js'
|
||||
],
|
||||
'ngCookies': [
|
||||
|
||||
@@ -124,7 +124,7 @@ h1,h2,h3,h4,h5,h6 {
|
||||
font-size:1.2em;
|
||||
padding:0;
|
||||
margin:0;
|
||||
border-bottom:1px soild #aaa;
|
||||
border-bottom:1px solid #aaa;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
@@ -315,8 +315,13 @@ iframe.example {
|
||||
color:white;
|
||||
}
|
||||
|
||||
.search-results-group .search-results {
|
||||
padding: 0 5px 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.search-results-frame > .search-results-group:first-child > .search-results {
|
||||
border-right:1px solid #050505;
|
||||
border-right:1px solid #222;
|
||||
}
|
||||
|
||||
.search-results-group.col-group-api { width:30%; }
|
||||
@@ -325,10 +330,57 @@ iframe.example {
|
||||
.search-results-group.col-group-misc,
|
||||
.search-results-group.col-group-error { width:15%; float: right; }
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group.col-group-api .search-results {
|
||||
-moz-column-count: 2;
|
||||
-ms-column-count: 2;
|
||||
-webkit-column-count: 2;
|
||||
column-count: 2;
|
||||
/* Prevent bullets in the second column from being hidden in Chrome and IE */
|
||||
-webkit-column-gap: 2em;
|
||||
-ms-column-gap: 2em;
|
||||
column-gap: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results-group .search-result {
|
||||
word-wrap: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
hyphens: auto;
|
||||
-ms-column-break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
-moz-column-break-inside: avoid; /* Unsupported */
|
||||
column-break-inside: avoid;
|
||||
text-indent: -0.65em; /* Make sure line wrapped words are aligned vertically */
|
||||
}
|
||||
|
||||
@supports (-moz-column-count: 2) {
|
||||
.search-results-group .search-result {
|
||||
/* Prevents column breaks inside words in FF, but has adverse effects in IE11 and Chrome */
|
||||
overflow: hidden;
|
||||
padding-left: 1em; /* In FF the list item bullet is otherwise hidden */
|
||||
margin-left: -1em; /* offset the padding left */
|
||||
}
|
||||
}
|
||||
|
||||
.search-result:before {
|
||||
content: "\002D\00A0"; /* Dash and non-breaking space as List item type */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-results-group.col-group-api .search-result {
|
||||
width:48%;
|
||||
display:inline-block;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group.col-group-api .search-result {
|
||||
width:auto;
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
|
||||
.search-close {
|
||||
@@ -589,6 +641,12 @@ ul.events > li {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.table > tbody > tr.head > td,
|
||||
.table > tbody > tr.head > th {
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 769px) and (max-width: 991px) {
|
||||
.main-body-grid {
|
||||
margin-top: 160px;
|
||||
@@ -682,6 +740,11 @@ ul.events > li {
|
||||
padding-bottom:60px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.search-results-frame > .search-results-group:first-child > .search-results {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.search-results-group {
|
||||
float:none!important;
|
||||
display:block!important;
|
||||
@@ -689,14 +752,42 @@ ul.events > li {
|
||||
border:0!important;
|
||||
padding:0!important;
|
||||
}
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group .search-results {
|
||||
-moz-column-count: 2;
|
||||
-ms-column-count: 2;
|
||||
-webkit-column-count: 2;
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results-group .search-result {
|
||||
display:inline-block!important;
|
||||
padding:0 5px;
|
||||
width:auto!important;
|
||||
text-indent: initial;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.search-results-group .search-result:after {
|
||||
content:", ";
|
||||
}
|
||||
|
||||
.search-results-group .search-result:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group .search-result {
|
||||
display: list-item !important;
|
||||
}
|
||||
|
||||
.search-results-group .search-result:after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
padding-bottom:0px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* global importScripts, onmessage: true, postMessage, lunr */
|
||||
|
||||
// Load up the lunr library
|
||||
importScripts('../components/lunr.js-0.4.2/lunr.min.js');
|
||||
importScripts('../components/lunr.js-0.5.12/lunr.min.js');
|
||||
|
||||
// Create the lunr index - the docs should be an array of object, each object containing
|
||||
// the path and search terms for a page
|
||||
|
||||
@@ -24,7 +24,26 @@ angular.module('examples', [])
|
||||
|
||||
|
||||
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
|
||||
return function(exampleFolder, clickEvent) {
|
||||
|
||||
var COPYRIGHT = 'Copyright ' + (new Date()).getFullYear() + ' Google Inc. All Rights Reserved.\n'
|
||||
+ 'Use of this source code is governed by an MIT-style license that\n'
|
||||
+ 'can be found in the LICENSE file at http://angular.io/license';
|
||||
var COPYRIGHT_JS_CSS = '\n\n/*\n' + COPYRIGHT + '\n*/';
|
||||
var COPYRIGHT_HTML = '\n\n<!-- \n' + COPYRIGHT + '\n-->';
|
||||
function getCopyright(filename) {
|
||||
switch (filename.substr(filename.lastIndexOf('.'))) {
|
||||
case '.html':
|
||||
return COPYRIGHT_HTML;
|
||||
case '.js':
|
||||
case '.css':
|
||||
return COPYRIGHT_JS_CSS;
|
||||
case '.md':
|
||||
return COPYRIGHT;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return function(exampleFolder, clickEvent) {
|
||||
|
||||
var exampleName = 'AngularJS Example';
|
||||
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
|
||||
@@ -67,7 +86,7 @@ angular.module('examples', [])
|
||||
var postData = {};
|
||||
|
||||
angular.forEach(files, function(file) {
|
||||
postData['files[' + file.name + ']'] = file.content;
|
||||
postData['files[' + file.name + ']'] = file.content + getCopyright(file.name);
|
||||
});
|
||||
|
||||
postData['tags[0]'] = "angularjs";
|
||||
|
||||
@@ -11,7 +11,15 @@ angular.module('search', [])
|
||||
var MIN_SEARCH_LENGTH = 2;
|
||||
if(q.length >= MIN_SEARCH_LENGTH) {
|
||||
docsSearch(q).then(function(hits) {
|
||||
var results = {};
|
||||
// Make sure the areas are always in the same order
|
||||
var results = {
|
||||
api: [],
|
||||
guide: [],
|
||||
tutorial: [],
|
||||
error: [],
|
||||
misc: []
|
||||
};
|
||||
|
||||
angular.forEach(hits, function(hit) {
|
||||
var area = hit.area;
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "AngularJS-docs-app",
|
||||
"dependencies": {
|
||||
"jquery": "2.1.1",
|
||||
"lunr.js": "0.4.3",
|
||||
"lunr.js": "0.5.12",
|
||||
"open-sans-fontface": "1.0.4",
|
||||
"google-code-prettify": "1.0.1",
|
||||
"bootstrap": "3.1.1"
|
||||
|
||||
@@ -170,4 +170,8 @@ module.exports = new Package('angularjs', [
|
||||
jqueryDeployment,
|
||||
productionDeployment
|
||||
];
|
||||
})
|
||||
|
||||
.config(function(generateKeywordsProcessor) {
|
||||
generateKeywordsProcessor.docTypesToIgnore = ['componentGroup'];
|
||||
});
|
||||
|
||||
@@ -16,9 +16,11 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
ignoreWordsFile: undefined,
|
||||
areasToSearch: ['api', 'guide', 'misc', 'error', 'tutorial'],
|
||||
propertiesToIgnore: [],
|
||||
docTypesToIgnore: [],
|
||||
$validate: {
|
||||
ignoreWordsFile: { },
|
||||
areasToSearch: { presence: true },
|
||||
docTypesToIgnore: { },
|
||||
propertiesToIgnore: { }
|
||||
},
|
||||
$runAfter: ['memberDocsProcessor'],
|
||||
@@ -28,6 +30,7 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
// Keywords to ignore
|
||||
var wordsToIgnore = [];
|
||||
var propertiesToIgnore;
|
||||
var docTypesToIgnore;
|
||||
var areasToSearch;
|
||||
|
||||
// Keywords start with "ng:" or one of $, _ or a letter
|
||||
@@ -47,6 +50,8 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
areasToSearch = _.indexBy(this.areasToSearch);
|
||||
propertiesToIgnore = _.indexBy(this.propertiesToIgnore);
|
||||
log.debug('Properties to ignore', propertiesToIgnore);
|
||||
docTypesToIgnore = _.indexBy(this.docTypesToIgnore);
|
||||
log.debug('Doc types to ignore', docTypesToIgnore);
|
||||
|
||||
var ignoreWordsMap = _.indexBy(wordsToIgnore);
|
||||
|
||||
@@ -78,34 +83,36 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
|
||||
// We are only interested in docs that live in the right area
|
||||
docs = _.filter(docs, function(doc) { return areasToSearch[doc.area]; });
|
||||
docs = _.filter(docs, function(doc) { return !docTypesToIgnore[doc.docType]; });
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
|
||||
var words = [];
|
||||
var keywordMap = _.clone(ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
// Search each top level property of the document for search terms
|
||||
_.forEach(doc, function(value, key) {
|
||||
var words = [];
|
||||
var keywordMap = _.clone(ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
if ( _.isString(value) && !propertiesToIgnore[key] ) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
// Search each top level property of the document for search terms
|
||||
_.forEach(doc, function(value, key) {
|
||||
|
||||
if ( key === 'methods' || key === 'properties' || key === 'events' ) {
|
||||
_.forEach(value, function(member) {
|
||||
extractWords(member.name, members, membersMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
if ( _.isString(value) && !propertiesToIgnore[key] ) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
|
||||
if ( key === 'methods' || key === 'properties' || key === 'events' ) {
|
||||
_.forEach(value, function(member) {
|
||||
extractWords(member.name, members, membersMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: _.sortBy(words).join(' '),
|
||||
members: _.sortBy(members).join(' ')
|
||||
};
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: _.sortBy(words).join(' '),
|
||||
members: _.sortBy(members).join(' ')
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -147,13 +147,13 @@
|
||||
<div class="search-results-container" ng-show="hasResults">
|
||||
<div class="container">
|
||||
<div class="search-results-frame">
|
||||
<div ng-repeat="(key, value) in results" class="search-results-group" ng-class="colClassName + ' col-group-' + key">
|
||||
<div ng-repeat="(key, value) in results track by key" class="search-results-group" ng-class="colClassName + ' col-group-' + key" ng-show="value.length > 0">
|
||||
<h4 class="search-results-group-heading">{{ key }}</h4>
|
||||
<div class="search-results">
|
||||
<div ng-repeat="item in value" class="search-result">
|
||||
- <a ng-click="hideResults()" ng-href="{{ item.path }}">{{ item.name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="search-results">
|
||||
<!-- Do not insert a line break between li and a. Chrome will insert an actual line-break, which breaks the list item view.
|
||||
TODO: use a html minifier instead -->
|
||||
<li ng-repeat="item in value" class="search-result"><a ng-click="hideResults()" ng-href="{{ item.path }}">{{ item.name }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="hideResults()" class="search-close">
|
||||
@@ -220,7 +220,7 @@
|
||||
<p class="pull-right"><a back-to-top>Back to top</a></p>
|
||||
|
||||
<p>
|
||||
Super-powered by Google ©2010-2015
|
||||
Super-powered by Google ©2010-2016
|
||||
( <a id="version"
|
||||
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
|
||||
ng-bind-template="v{{version}}" title="Changelog of this version of Angular JS">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{# Be aware that we need these extra new lines here or marked will not realise that the <div>
|
||||
{# Be aware that we need these extra new lines here or marked will not realize that the <div>
|
||||
is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
<div>
|
||||
@@ -24,5 +24,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Be aware that we need these extra new lines here or marked will not realise that the <div>
|
||||
{# Be aware that we need these extra new lines here or marked will not realize that the <div>
|
||||
above is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
@@ -8,7 +8,7 @@ but the required directive controller is not present on the current DOM element
|
||||
|
||||
To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element.
|
||||
|
||||
If the required controller is expected to be on a ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
If the required controller is expected to be on an ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
|
||||
If the required controller is optionally requested, use `?` or `^?` to specify that.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $location:nobase
|
||||
@fullName $location in HTML5 mode requires a <base> tag to be present!
|
||||
@fullName $location in HTML5 mode requires a `<base>` tag to be present!
|
||||
@description
|
||||
|
||||
If you configure {@link ng.$location `$location`} to use
|
||||
@@ -15,7 +15,7 @@ $locationProvider.html5Mode({
|
||||
});
|
||||
```
|
||||
|
||||
Note that removing the requirement for a <base> tag will have adverse side effects when resolving
|
||||
Note that removing the requirement for a `<base>` tag will have adverse side effects when resolving
|
||||
relative paths with `$location` in IE9.
|
||||
|
||||
The base URL is then used to resolve all relative URLs throughout the application regardless of the
|
||||
|
||||
@@ -14,3 +14,32 @@ perform this check - it's up to the developer to not expose such sensitive and p
|
||||
directly on the scope chain.
|
||||
|
||||
To resolve this error, avoid Window access.
|
||||
|
||||
### Common CoffeeScript Issue
|
||||
|
||||
Be aware that if you are using CoffeeScript, it automatically returns the value of the last statement in a
|
||||
function. So for instance
|
||||
|
||||
```coffeescript
|
||||
scope.foo = ->
|
||||
window.open 'https://example.com'
|
||||
```
|
||||
|
||||
compiles to something like
|
||||
|
||||
```js
|
||||
scope.foo = function() {
|
||||
return window.open('https://example.com');
|
||||
};
|
||||
```
|
||||
|
||||
You can see that this function will return the result of calling `window.open`, which is a `Window`
|
||||
object.
|
||||
|
||||
You can avoid this by explicitly returning something else from the function:
|
||||
|
||||
```coffeescript
|
||||
scope.foo = ->
|
||||
window.open 'https://example.com'
|
||||
return true;
|
||||
```
|
||||
|
||||
@@ -100,7 +100,7 @@ To resolve this type of issue, either fix the api to be always synchronous or as
|
||||
your callback handler to always run asynchronously by using the `$timeout` service.
|
||||
|
||||
```
|
||||
function MyController($scope, thirdPartyComponent) {
|
||||
function MyController($scope, $timeout, thirdPartyComponent) {
|
||||
thirdPartyComponent.getData(function(someData) {
|
||||
$timeout(function() {
|
||||
$scope.someData = someData;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $sanitize:badparse
|
||||
@fullName Parsing Error while Sanitizing
|
||||
@description
|
||||
|
||||
This error occurs when the HTML string passed to '$sanitize' can't be parsed by the sanitizer.
|
||||
The error contains part of the html string that can't be parsed.
|
||||
|
||||
The parser is more strict than a typical browser parser, so it's possible that some obscure input would produce this error despite the string being recognized as valid HTML by a browser.
|
||||
|
||||
If a valid html code results in this error, please file a bug.
|
||||
@@ -0,0 +1,10 @@
|
||||
@ngdoc error
|
||||
@name $sanitize:noinert
|
||||
@fullName Can't create an inert html document
|
||||
@description
|
||||
|
||||
This error occurs when `$sanitize` sanitizer determines that `document.implementation.createHTMLDocument ` api is not supported by the current browser.
|
||||
|
||||
This api is necessary for safe parsing of HTML strings into DOM trees and without it the sanitizer can't sanitize the input.
|
||||
|
||||
The api is present in all supported browsers including IE 9.0, so the presence of this error usually indicates that Angular's `$sanitize` is being used on an unsupported platform.
|
||||
@@ -0,0 +1,13 @@
|
||||
@ngdoc error
|
||||
@name $sanitize:uinput
|
||||
@fullName Failed to sanitize html because the input is unstable
|
||||
@description
|
||||
|
||||
This error occurs when `$sanitize` sanitizer tries to check the input for possible mXSS payload and the verification
|
||||
errors due to the input mutating indefinitely. This could be a sign that the payload contains code exploiting an mXSS
|
||||
vulnerability in the browser.
|
||||
|
||||
mXSS attack exploit browser bugs that cause some browsers parse a certain html strings into DOM, which once serialized
|
||||
doesn't match the original input. These browser bugs can be exploited by attackers to create payload which looks
|
||||
harmless to sanitizers, but due to mutations caused by the browser are turned into dangerous code once processed after
|
||||
sanitization.
|
||||
@@ -0,0 +1,16 @@
|
||||
@ngdoc error
|
||||
@name linky:notstring
|
||||
@fullName Not a string
|
||||
@description
|
||||
|
||||
This error occurs when {@link ngSanitize.linky linky} is used with a non-empty, non-string value:
|
||||
```html
|
||||
<div ng-bind-html="42 | linky"></div>
|
||||
```
|
||||
|
||||
`linky` is supposed to be used with string values only, and therefore assumes that several methods
|
||||
(such as `.match()`) are available on the passed in value.
|
||||
The value can be initialized asynchronously and therefore null or undefined won't throw this error.
|
||||
|
||||
If you want to pass non-string values to `linky` (e.g. Objects whose `.toString()` should be
|
||||
utilized), you need to manually convert them to strings.
|
||||
@@ -0,0 +1,52 @@
|
||||
@ngdoc error
|
||||
@name orderBy:notarray
|
||||
@fullName Value is not array-like
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.orderBy orderBy} is not passed an array-like value:
|
||||
```html
|
||||
<div ng-repeat="(key, value) in myObj | orderBy:someProp">
|
||||
{{ key }} : {{ value }}
|
||||
</div>
|
||||
```
|
||||
|
||||
`orderBy` must be used with an array-like value so a subset of items can be returned.
|
||||
The array can be initialized asynchronously and therefore `null` or `undefined` won't throw this error.
|
||||
|
||||
To use `orderBy` to order the properties of an object, you can create your own array based on that object:
|
||||
```js
|
||||
angular.module('aModule', [])
|
||||
.controller('aController', function($scope) {
|
||||
var myObj = {
|
||||
one: {id: 1, name: 'Some thing'},
|
||||
two: {id: 2, name: 'Another thing'},
|
||||
three: {id: 3, name: 'A third thing'}
|
||||
};
|
||||
|
||||
$scope.arrFromMyObj = Object.keys(myObj).map(function(key) {
|
||||
return myObj[key];
|
||||
});
|
||||
});
|
||||
```
|
||||
That can be used as:
|
||||
```html
|
||||
<label>
|
||||
Order by:
|
||||
<select ng-model="orderProp" ng-options="prop for prop in ['id', 'name']"></select>
|
||||
</label>
|
||||
<div ng-repeat="item in arrFromMyObj | orderBy:orderProp">
|
||||
[{{ item.id }}] {{ item.name }}
|
||||
</div>
|
||||
```
|
||||
|
||||
You could as well convert the object to an array using a filter such as
|
||||
[toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter):
|
||||
```html
|
||||
<label>
|
||||
Order by:
|
||||
<select ng-model="orderProp" ng-options="prop for prop in ['id', 'name']"></select>
|
||||
</label>
|
||||
<div ng-repeat="item in myObj | toArray:false | orderBy:orderProp">
|
||||
[{{ item.id }}] {{ item.name }}
|
||||
</div>
|
||||
```
|
||||
@@ -356,15 +356,15 @@ legacy browsers and hashbang links in modern browser:
|
||||
|
||||
### Example
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents the address bar of the browser.
|
||||
Here you can see two `$location` instances that show the difference between **Html5 mode** and **Html5 Fallback mode**.
|
||||
Note that to simulate different levels of browser support, the `$location` instances are connected to
|
||||
a fakeBrowser service, which you don't have to set up in actual projects.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
Note that when you type hashbang url into the first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
= on page reload.
|
||||
|
||||
In these examples we use `<base href="/base/index.html" />`
|
||||
In these examples we use `<base href="/base/index.html" />`. The inputs represent the address bar of the browser.
|
||||
|
||||
#### Browser in HTML5 mode
|
||||
<example module="html5-mode" name="location-html5-mode">
|
||||
@@ -389,6 +389,7 @@ In these examples we use `<base href="/base/index.html" />`
|
||||
<file name="app.js">
|
||||
angular.module('html5-mode', ['fake-browser', 'address-bar'])
|
||||
|
||||
// Configure the fakeBrowser. Do not set these values in actual projects.
|
||||
.constant('initUrl', 'http://www.example.com/base/path?a=b#h')
|
||||
.constant('baseHref', '/base/index.html')
|
||||
.value('$sniffer', { history: true })
|
||||
@@ -538,6 +539,7 @@ In these examples we use `<base href="/base/index.html" />`
|
||||
<file name="app.js">
|
||||
angular.module('hashbang-mode', ['fake-browser', 'address-bar'])
|
||||
|
||||
// Configure the fakeBrowser. Do not set these values in actual projects.
|
||||
.constant('initUrl', 'http://www.example.com/base/index.html#!/path?a=b#h')
|
||||
.constant('baseHref', '/base/index.html')
|
||||
.value('$sniffer', { history: false })
|
||||
@@ -769,8 +771,8 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Navigation outside the app</td>
|
||||
<td>Use lower level API</td>
|
||||
<th>Navigation outside the app</td>
|
||||
<th>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -784,8 +786,8 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Read access</td>
|
||||
<td>Change to</td>
|
||||
<th>Read access</td>
|
||||
<th>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
@@ -274,6 +274,37 @@ myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
}]);
|
||||
```
|
||||
|
||||
## Preventing flicker before an animation starts
|
||||
|
||||
When nesting elements with structural animations such as `ngIf` into elements that have class-based
|
||||
animations such as `ngClass`, it sometimes happens that before the actual animation starts, there is a brief flicker or flash of content
|
||||
where the animated element is briefly visible.
|
||||
|
||||
To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as an animation is initialized,
|
||||
but removed before the actual animation starts (after waiting for a $digest). This class is only added for *structural*
|
||||
animations (`enter`, `move`, and `leave`).
|
||||
|
||||
Here's an example where you might see flickering:
|
||||
|
||||
```html
|
||||
<div ng-class="{red: myProp}">
|
||||
<div ng-class="{blue: myProp}">
|
||||
<div class="message" ng-if="myProp"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
It is possible that during the `enter` event, the `.message` div will be briefly visible before it starts animating.
|
||||
In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
|
||||
|
||||
```css
|
||||
.message.ng-enter-prepare {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Other animation styles ... */
|
||||
```
|
||||
|
||||
## More about animations
|
||||
|
||||
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
|
||||
|
||||
@@ -76,7 +76,7 @@ stores/updates the value of the input field into/from a variable.
|
||||
The second kind of new markup are the double curly braces `{{ expression | filter }}`:
|
||||
When the compiler encounters this markup, it will replace it with the evaluated value of the markup.
|
||||
An <a name="expression">{@link expression expression}</a> in a template is a JavaScript-like code snippet that allows
|
||||
to read and write variables. Note that those variables are not global variables.
|
||||
Angular to read and write variables. Note that those variables are not global variables.
|
||||
Just like variables in a JavaScript function live in a scope,
|
||||
Angular provides a <a name="scope">{@link scope scope}</a> for the variables accessible to expressions.
|
||||
The values that are stored in variables on the scope are referred to as the <a name="model"></a>*model*
|
||||
@@ -334,9 +334,9 @@ The following example shows how this is done with Angular:
|
||||
var refresh = function() {
|
||||
var url = YAHOO_FINANCE_URL_PATTERN.
|
||||
replace('PAIRS', 'USD' + currencies.join('","USD'));
|
||||
return $http.jsonp(url).success(function(data) {
|
||||
return $http.jsonp(url).then(function(response) {
|
||||
var newUsdToForeignRates = {};
|
||||
angular.forEach(data.query.results.rate, function(rate) {
|
||||
angular.forEach(response.data.query.results.rate, function(rate) {
|
||||
var currency = rate.id.substring(3,6);
|
||||
newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
|
||||
});
|
||||
@@ -348,8 +348,7 @@ The following example shows how this is done with Angular:
|
||||
|
||||
return {
|
||||
currencies: currencies,
|
||||
convert: convert,
|
||||
refresh: refresh
|
||||
convert: convert
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
@@ -43,8 +43,7 @@ mirrors the process of compiling source code in
|
||||
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
|
||||
determines when to use a given directive.
|
||||
|
||||
Similar to the terminology used when an [element **matches** a selector]
|
||||
(https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
Similar to the terminology used when an [element **matches** a selector](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
directive when the directive is part of its declaration.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
|
||||
@@ -142,63 +141,6 @@ directives when possible.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### Text and attribute bindings
|
||||
|
||||
During the compilation process the {@link ng.$compile compiler} matches text and attributes
|
||||
using the {@link ng.$interpolate $interpolate} service to see if they contain embedded
|
||||
expressions. These expressions are registered as {@link ng.$rootScope.Scope#$watch watches}
|
||||
and will update as part of normal {@link ng.$rootScope.Scope#$digest digest} cycle. An
|
||||
example of interpolation is shown below:
|
||||
|
||||
```html
|
||||
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
|
||||
```
|
||||
|
||||
|
||||
### `ngAttr` attribute bindings
|
||||
|
||||
Web browsers are sometimes picky about what values they consider valid for attributes.
|
||||
|
||||
For example, considering this template:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle cx="{{cx}}"></circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
We would expect Angular to be able to bind to this, but when we check the console we see
|
||||
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
|
||||
restrictions, you cannot simply write `cx="{{cx}}"`.
|
||||
|
||||
With `ng-attr-cx` you can work around this problem.
|
||||
|
||||
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
|
||||
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
|
||||
you to bind to attributes that would otherwise be eagerly processed by browsers
|
||||
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
|
||||
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
|
||||
results in `undefined`, the attribute is removed and not added to the element.
|
||||
|
||||
For example, we could fix the example above by instead writing:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle ng-attr-cx="{{cx}}"></circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes), such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind to is naturally camelcased.
|
||||
|
||||
For example, to bind to `viewBox`, we can write:
|
||||
|
||||
```html
|
||||
<svg ng-attr-view_box="{{viewBox}}">
|
||||
</svg>
|
||||
```
|
||||
|
||||
|
||||
## Creating Directives
|
||||
|
||||
First let's talk about the {@link ng.$compileProvider#directive API for registering directives}. Much like
|
||||
@@ -588,14 +530,24 @@ want to reuse throughout your app.
|
||||
In this example we will build a directive that displays the current time.
|
||||
Once a second, it updates the DOM to reflect the current time.
|
||||
|
||||
Directives that want to modify the DOM typically use the `link` option.
|
||||
`link` takes a function with the following signature, `function link(scope, element, attrs) { ... }`
|
||||
where:
|
||||
Directives that want to modify the DOM typically use the `link` option to register DOM listeners
|
||||
as well as update the DOM. It is executed after the template has been cloned and is where
|
||||
directive logic will be put.
|
||||
|
||||
`link` takes a function with the following signature,
|
||||
`function link(scope, element, attrs, controller, transcludeFn) { ... }`, where:
|
||||
|
||||
* `scope` is an Angular scope object.
|
||||
* `element` is the jqLite-wrapped element that this directive matches.
|
||||
* `attrs` is a hash object with key-value pairs of normalized attribute names and their
|
||||
corresponding attribute values.
|
||||
* `controller` is the directive's required controller instance(s) or its own controller (if any).
|
||||
The exact value depends on the directive's require property.
|
||||
* `transcludeFn` is a transclude linking function pre-bound to the correct transclusion scope.
|
||||
|
||||
<div class="alert alert-info">
|
||||
For more details on the `link` option refer to the {@link ng.$compile#-link- `$compile` API} page.
|
||||
</div>
|
||||
|
||||
In our `link` function, we want to update the displayed time once a second, or whenever a user
|
||||
changes the time formatting string that our directive binds to. We will use the `$interval` service
|
||||
@@ -903,7 +855,7 @@ to which tab is active.
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {},
|
||||
controller: function($scope) {
|
||||
controller: ['$scope', function($scope) {
|
||||
var panes = $scope.panes = [];
|
||||
|
||||
$scope.select = function(pane) {
|
||||
@@ -919,7 +871,7 @@ to which tab is active.
|
||||
}
|
||||
panes.push(pane);
|
||||
};
|
||||
},
|
||||
}],
|
||||
templateUrl: 'my-tabs.html'
|
||||
};
|
||||
})
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
# Angular Expressions
|
||||
|
||||
Angular expressions are JavaScript-like code snippets that are usually placed in bindings such as
|
||||
`{{ expression }}`.
|
||||
Angular expressions are JavaScript-like code snippets that are mainly placed in
|
||||
interpolation bindings such as `<span title="{{ attrBinding }}">{{ textBinding }}</span>`,
|
||||
but also used directly in directive attributes such as `ng-click="functionExpression()"`.
|
||||
|
||||
For example, these are valid expressions in Angular:
|
||||
|
||||
@@ -35,7 +36,9 @@ Angular expressions are like JavaScript expressions with the following differenc
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
|
||||
* **No Object Creation With New Operator:** You cannot use `new` operator in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` operators in an Angular expression.
|
||||
|
||||
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
|
||||
displaying it.
|
||||
@@ -113,6 +116,9 @@ This restriction is intentional. It prevents accidental access to the global sta
|
||||
Instead use services like `$window` and `$location` in functions called from expressions. Such services
|
||||
provide mockable access to globals.
|
||||
|
||||
It is possible to access the context object using the identifier `this` and the locals object using the
|
||||
identifier `$locals`.
|
||||
|
||||
<example module="expressionExample">
|
||||
<file name="index.html">
|
||||
<div class="example2" ng-controller="ExampleController">
|
||||
@@ -280,7 +286,7 @@ result is a non-undefined value (see value stabilization algorithm below).
|
||||
</example>
|
||||
|
||||
|
||||
### Why this feature
|
||||
### Reasons for using one-time binding
|
||||
|
||||
The main purpose of one-time binding expression is to provide a way to create a binding
|
||||
that gets deregistered and frees up resources once the binding is stabilized.
|
||||
|
||||
@@ -383,7 +383,7 @@ In the following example we create two directives:
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, elm, attrs, ctrl) {
|
||||
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
||||
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
||||
|
||||
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
@ngdoc overview
|
||||
@name Interpolation
|
||||
@sortOrder 275
|
||||
@description
|
||||
|
||||
# Interpolation and data-binding
|
||||
|
||||
Interpolation markup with embedded @link {guide/expressions expressions} is used by Angular to
|
||||
provide data-binding to text nodes and attribute values.
|
||||
|
||||
An example of interpolation is shown below:
|
||||
|
||||
```html
|
||||
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
|
||||
```
|
||||
|
||||
### How text and attribute bindings work
|
||||
|
||||
During the compilation process the {@link ng.$compile compiler} uses the {@link ng.$interpolate $interpolate}
|
||||
service to see if text nodes and element attributes contain interpolation markup with embedded expressions.
|
||||
|
||||
If that is the case, the compiler adds an interpolateDirective to the node and
|
||||
registers {@link ng.$rootScope.Scope#$watch watches} on the computed interpolation function,
|
||||
which will update the corresponding text nodes or attribute values as part of the
|
||||
normal {@link ng.$rootScope.Scope#$digest digest} cycle.
|
||||
|
||||
Note that the interpolateDirective has a priority of 100 and sets up the watch in the preLink function.
|
||||
|
||||
### Binding to boolean attributes
|
||||
|
||||
Attributes such as `disabled` are called `boolean` attributes, because their presence means `true` and
|
||||
their absence means `false`. We cannot use normal attribute bindings with them, because the HTML
|
||||
specification does not require browsers to preserve the values of boolean attributes. This means that
|
||||
if we put an Angular interpolation expression into such an attribute then the binding information
|
||||
would be lost, because the browser ignores the attribute value.
|
||||
|
||||
In the following example, the interpolation information would be ignored and the browser would simply
|
||||
interpret the attribute as present, meaning that the button would always be disabled.
|
||||
|
||||
```html
|
||||
Disabled: <input type="checkbox" ng-model="isDisabled" />
|
||||
<button disabled="{{isDisabled}}">Disabled</button>
|
||||
```
|
||||
|
||||
For this reason, Angular provides special `ng`-prefixed directives for the following boolean attributes:
|
||||
{@link ngDisabled `disabled`}, [@link ngRequired `required`}, [@link ngSelected `selected`},
|
||||
{@link ngChecked `checked`}, {@link ngReadonly `readOnly`} , and [@link ngOpen `open`}.
|
||||
|
||||
These directives take an expression inside the attribute, and set the corresponding boolean attribute
|
||||
to true when the expression evaluates to truthy.
|
||||
|
||||
```html
|
||||
Disabled: <input type="checkbox" ng-model="isDisabled" />
|
||||
<button ng-disabled="isDisabled">Disabled</button>
|
||||
```
|
||||
|
||||
### `ngAttr` for binding to arbitrary attributes
|
||||
|
||||
Web browsers are sometimes picky about what values they consider valid for attributes.
|
||||
|
||||
For example, considering this template:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle cx="{{cx}}"></circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
We would expect Angular to be able to bind to this, but when we check the console we see
|
||||
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
|
||||
restrictions, you cannot simply write `cx="{{cx}}"`.
|
||||
|
||||
With `ng-attr-cx` you can work around this problem.
|
||||
|
||||
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
|
||||
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
|
||||
you to bind to attributes that would otherwise be eagerly processed by browsers
|
||||
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
|
||||
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
|
||||
results in `undefined`, the attribute is removed and not added to the element.
|
||||
|
||||
For example, we could fix the example above by instead writing:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle ng-attr-cx="{{cx}}"></circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes),
|
||||
such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind
|
||||
to is naturally camelcased.
|
||||
|
||||
For example, to bind to `viewBox`, we can write:
|
||||
|
||||
```html
|
||||
<svg ng-attr-view_box="{{viewBox}}">
|
||||
</svg>
|
||||
```
|
||||
|
||||
The following attributes are also known to cause problems when used with normal bindings:
|
||||
|
||||
- **size** in `<select>` elements (see [Github issue 1619](https://github.com/angular/angular.js/issues/1619))
|
||||
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [Github issue 5025](https://github.com/angular/angular.js/issues/5025))
|
||||
|
||||
|
||||
### Embedding interpolation markup inside expressions
|
||||
|
||||
Angular directives take either expressions or interpolation markup with embedded expressions. So the
|
||||
following example which embeds interpolation inside an expression is a bad practice:
|
||||
|
||||
```html
|
||||
<div ng-show="form{{$index}}.$invalid"></div>
|
||||
```
|
||||
|
||||
You should instead delegate the computation of complex expressions to the scope, like this:
|
||||
|
||||
```html
|
||||
<div ng-show="getForm($index).$invalid"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
function getForm() {
|
||||
return $scope['form' + $index];
|
||||
}
|
||||
```
|
||||
|
||||
You can also access the `scope` with `this` in your templates:
|
||||
|
||||
```html
|
||||
<div ng-show="this['form' + $index].$invalid"></div>
|
||||
```
|
||||
|
||||
#### Why mixing interpolation and expressions is bad practice:
|
||||
|
||||
- It increases the complexity of the markup
|
||||
- There is no guarantee that it works for every directive, because interpolation itself is a directive.
|
||||
If another directive accesses attribute data before interpolation has run, it will get the raw
|
||||
interpolation markup and not data.
|
||||
- It impacts performance, as interpolation adds another watcher to the scope.
|
||||
- Since this is not recommended usage, we do not test for this, and changes to
|
||||
Angular core may break your code.
|
||||
@@ -170,6 +170,25 @@ other inline messages situated as children within the `ngMessages` container dir
|
||||
Depending on where the `ngMessagesInclude` directive is placed it will be prioritized inline with the other messages
|
||||
before and after it.
|
||||
|
||||
Also due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
it is no longer possible to use interpolation inside the `ngMessages` attribute expression. This technique
|
||||
is generally not recommended, and can easily break when a directive implementation changes. In cases
|
||||
where a simple expression is not possible, you can delegate accessing the object to a function:
|
||||
|
||||
```html
|
||||
<div ng-messages="ctrl.form['field_{{$index}}'].$error">...</div>
|
||||
```
|
||||
would become
|
||||
```html
|
||||
<div ng-messages="ctrl.getMessages($index)">...</div>
|
||||
```
|
||||
where `ctrl.getMessages()`
|
||||
```javascript
|
||||
ctrl.getMessages = function($index) {
|
||||
return ctrl.form['field_' + $index].$error;
|
||||
}
|
||||
```
|
||||
|
||||
### ngOptions
|
||||
|
||||
The `ngOptions` directive has also been refactored and as a result some long-standing bugs
|
||||
@@ -189,6 +208,10 @@ But in practice this is not what people want and so this change iterates over pr
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
Also due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
setting the ngOptions attribute expression after the element is compiled, will no longer trigger the ngOptions behavior.
|
||||
This worked previously because the ngOptions logic was part of the select directive, while
|
||||
it is now implemented in the ngOptions directive itself.
|
||||
|
||||
### select
|
||||
|
||||
@@ -473,10 +496,9 @@ This change also makes our forEach behave more like Array#forEach.
|
||||
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before, you will have to
|
||||
manually do this yourself now.
|
||||
|
||||
`toJson()` will no longer strip properties starting with a single `$`. If you relied on
|
||||
`toJson()`'s stripping these types of properties before, you will have to do it manually now.
|
||||
It will still strip properties starting with `$$` though.
|
||||
|
||||
|
||||
|
||||
@@ -561,6 +583,36 @@ After:
|
||||
};
|
||||
});
|
||||
|
||||
- due to [531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
`$observe` no longer registers on undefined attributes. For example, if you were using `$observe` on
|
||||
an absent optional attribute to set a default value, the following would not work anymore:
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
```
|
||||
|
||||
```js
|
||||
// Link function for directive myDir
|
||||
link: function(scope, element, attr) {
|
||||
attr.$observe('myAttr', function(newVal) {
|
||||
scope.myValue = newVal ? newVal : 'myDefaultValue';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Instead, check if the attribute is set before registering the observer:
|
||||
|
||||
```js
|
||||
link: function(scope, element, attr) {
|
||||
if (attr.myAttr) {
|
||||
// register the observer
|
||||
} else {
|
||||
// set the default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -633,8 +685,15 @@ $scope.resetWithCancel = function (e) {
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
- {@link input[checkbox] `input[checkbox]`} now supports constant expressions in `ngTrueValue` and
|
||||
`ngFalseValue`, making it now possible to e.g. use boolean and integer values. Previously, these attributes would
|
||||
always be treated as strings, whereas they are now parsed as expressions, and will throw if an expression
|
||||
is non-constant. To convert non-constant strings into constant expressions, simply wrap them in an
|
||||
extra pair of quotes, like so:
|
||||
|
||||
`<input type="checkbox" ng-model="..." ng-true-value="'truthyValue'">`
|
||||
|
||||
See [c90cefe1614](https://github.com/angular/angular.js/commit/c90cefe16142d973a123e945fc9058e8a874c357)
|
||||
|
||||
|
||||
## Scopes and Digests (`$scope`)
|
||||
|
||||
@@ -44,7 +44,7 @@ and {@link angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
|
||||
|
||||
## Strict DI Mode
|
||||
|
||||
Using strict di mode in your production application will throw errors when a injectable
|
||||
Using strict di mode in your production application will throw errors when an injectable
|
||||
function is not
|
||||
{@link di#dependency-annotation annotated explicitly}. Strict di mode is intended to help
|
||||
you make sure that your code will work when minified. However, it also will force you to
|
||||
|
||||
@@ -391,7 +391,7 @@ implementing custom event callbacks, or when working with third-party library ca
|
||||
5. The {@link ng.$rootScope.Scope#$watch $watch} list is a set of expressions
|
||||
which may have changed since last iteration. If a change is detected then the `$watch`
|
||||
function is called which typically updates the DOM with the new value.
|
||||
6. Once the Angular {@link ng.$rootScope.Scope#$digest $digest} loop finishes
|
||||
6. Once the Angular {@link ng.$rootScope.Scope#$digest $digest} loop finishes,
|
||||
the execution leaves the Angular and JavaScript context. This is followed by the browser
|
||||
re-rendering the DOM to reflect any changes.
|
||||
|
||||
@@ -419,4 +419,4 @@ user enters text into the text field.
|
||||
which in turn updates the DOM.
|
||||
6. Angular exits the execution context, which in turn exits the `keydown` event and with it
|
||||
the JavaScript execution context.
|
||||
7. The browser re-renders the view with update text.
|
||||
7. The browser re-renders the view with the updated text.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
JavaScript is a dynamically typed language which comes with great power of expression, but it also
|
||||
comes with almost no help from the compiler. For this reason we feel very strongly that any code
|
||||
written in JavaScript needs to come with a strong set of tests. We have built many features into
|
||||
Angular which makes testing your Angular applications easy. So there is no excuse for not testing.
|
||||
Angular which make testing your Angular applications easy. With Angular, there is no excuse for not testing.
|
||||
|
||||
## Separation of Concerns
|
||||
|
||||
@@ -20,13 +20,13 @@ related pieces such as the DOM elements, or making any XHR calls to fetch the da
|
||||
|
||||
While this may seem obvious it can be very difficult to call an individual function on a
|
||||
typical project. The reason is that the developers often mix concerns resulting in a
|
||||
piece of code which does everything. It makes an XHR request, it sorts the response data and then it
|
||||
piece of code which does everything. It makes an XHR request, it sorts the response data, and then it
|
||||
manipulates the DOM.
|
||||
|
||||
With Angular we try to make it easy for you to do the right thing, and so we
|
||||
provide dependency injection for your XHR requests, which can be mocked, and we provide abstractions which
|
||||
allow you to test your model without having to resort to manipulating the DOM. The test can then
|
||||
assert that the data has been sorted without having to create or look at the state of the DOM or
|
||||
With Angular, we try to make it easy for you to do the right thing. For your XHR requests, we
|
||||
provide dependency injection, so your requests can be simulated. For the DOM, we abstract it, so you can
|
||||
test your model without having to manipulate the DOM directly. Your tests can then
|
||||
assert that the data has been sorted without having to create or look at the state of the DOM or to
|
||||
wait for any XHR requests to return data. The individual sort function can be tested in isolation.
|
||||
|
||||
## With great power comes great responsibility
|
||||
@@ -359,7 +359,7 @@ element, to which it can then insert the transcluded content into its template.
|
||||
|
||||
Before compilation:
|
||||
```html
|
||||
<div translude-directive>
|
||||
<div transclude-directive>
|
||||
Some transcluded content
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ a few git commands.
|
||||
|
||||
### Install Git
|
||||
|
||||
You can download and install Git from http://git-scm.com/download. Once installed you should have
|
||||
You can download and install Git from http://git-scm.com/download. Once installed, you should have
|
||||
access to the `git` command line tool. The main commands that you will need to use are:
|
||||
|
||||
- `git clone ...` : clone a remote repository onto your local machine
|
||||
@@ -123,7 +123,7 @@ npm --version
|
||||
</a>.
|
||||
</div>
|
||||
|
||||
Once you have Node.js installed on your machine you can download the tool dependencies by running:
|
||||
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
@@ -198,7 +198,7 @@ http://localhost:8000/app/index.html
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
To serve the web app on a different ip address or port, edit the "start" script within package.json.
|
||||
To serve the web app on a different IP address or port, edit the "start" script within package.json.
|
||||
You can use `-a` to set the address and `-p` to set the port.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
<ul doc-tutorial-nav="0"></ul>
|
||||
|
||||
|
||||
You are now ready to build the AngularJS phonecat app. In this step, you will become familiar
|
||||
with the most important source code files, learn how to start the development servers bundled with
|
||||
In this step of the tutorial, you will become familiar with the most important source code files of
|
||||
the AngularJS phonecat app. You will also learn how to start the development servers bundled with
|
||||
angular-seed, and run the application in the browser.
|
||||
|
||||
Before you continue, make sure you have set up your development environment and installed all necessary
|
||||
dependencies, as described in {@link index#get-started Get Started}.
|
||||
|
||||
In `angular-phonecat` directory, run this command:
|
||||
In the `angular-phonecat` directory, run this command:
|
||||
|
||||
```
|
||||
git checkout -f step-0
|
||||
|
||||
@@ -61,7 +61,7 @@ by the value of the expressions.
|
||||
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
|
||||
__controller__ to the <body> tag. At this point:
|
||||
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}` denote
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) denote
|
||||
bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
|
||||
controller.
|
||||
|
||||
@@ -132,6 +132,8 @@ The "Angular way" of separating controller from the view, makes it easy to test
|
||||
developed. If our controller is available on the global namespace then we could simply instantiate it
|
||||
with a mock `scope` object:
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
|
||||
```js
|
||||
describe('PhoneListCtrl', function(){
|
||||
|
||||
@@ -195,8 +197,19 @@ to ensure that Karma and its necessary plugins are installed. You can do this by
|
||||
|
||||
To run the tests, and then watch the files for changes: `npm test`.
|
||||
|
||||
* Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
|
||||
the background. Karma will use this browser for test execution.
|
||||
* Karma will start new instances of Chrome and Firefox browsers automatically. Just ignore them and
|
||||
let them run in the background. Karma will use these browsers for test execution.
|
||||
* If you only have one of the browsers installed on your machine (either Chrome or Firefox), make
|
||||
sure to update the karma configuration file before running the test. Locate the configuration file
|
||||
in `test/karma.conf.js`, then update the `browsers` property.
|
||||
|
||||
E.g. if you only have Chrome installed:
|
||||
<pre>
|
||||
...
|
||||
browsers: ['Chrome'],
|
||||
...
|
||||
</pre>
|
||||
|
||||
* You should see the following or similar output in the terminal:
|
||||
|
||||
<pre>
|
||||
@@ -250,7 +263,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
|
||||
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
@@ -65,7 +65,7 @@ phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
|
||||
`$http` makes an HTTP GET request to our web server, asking for `phones/phones.json` (the url is
|
||||
relative to our `index.html` file). The server responds by providing the data in the json file.
|
||||
(The response might just as well have been dynamically generated by a backend server. To the
|
||||
browser and our app they both look the same. For the sake of simplicity we used a json file in this
|
||||
browser and our app, they both look the same. For the sake of simplicity, we used a json file in this
|
||||
tutorial.)
|
||||
|
||||
The `$http` service returns a {@link ng.$q promise object} with a `success`
|
||||
@@ -114,7 +114,7 @@ as strings, which will not get minified. There are two ways to provide these inj
|
||||
|
||||
* Create a `$inject` property on the controller function which holds an array of strings.
|
||||
Each string in the array is the name of the service to inject for the corresponding parameter.
|
||||
In our example we would write:
|
||||
In our example, we would write:
|
||||
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
|
||||
@@ -59,8 +59,8 @@ the element attribute.
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
ng.directive:ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the Angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid URL `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
an invalid URL `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had
|
||||
only specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ We are using [Bower][bower] to install client-side dependencies. This step upda
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.4.0"
|
||||
"angular-route": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "~1.4.0"` tells bower to install a version of the
|
||||
The new dependency `"angular-route": "1.4.x"` tells bower to install a version of the
|
||||
angular-route component that is compatible with version 1.4.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ phone in the phone list.
|
||||
* When you click on a phone on the list, the phone details page with phone-specific information
|
||||
is displayed.
|
||||
|
||||
To implement the phone details view we used {@link ng.$http $http} to fetch our data, and we
|
||||
fleshed out the `phone-detail.html` view template.
|
||||
To implement the phone details view we are going to use {@link ng.$http $http} to fetch our data,
|
||||
and then flesh out the `phone-detail.html` view template.
|
||||
|
||||
<div doc-tutorial-reset="8"></div>
|
||||
|
||||
|
||||
@@ -32,17 +32,18 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"angular": "1.4.x",
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0"
|
||||
"angular-route": "1.4.x",
|
||||
"angular-resource": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "~1.3.0"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.3.x. We must ask bower to download
|
||||
The new dependency `"angular-resource": "1.4.x"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.4.x. We must ask bower to download
|
||||
and install this dependency. We can do this by running:
|
||||
|
||||
```
|
||||
|
||||
@@ -36,20 +36,20 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0",
|
||||
"angular": "1.4.x",
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"angular-animate": "~1.3.0"
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "1.4.x",
|
||||
"angular-resource": "1.4.x",
|
||||
"angular-animate": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "~1.3.0"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.3.x.
|
||||
* `"jquery": "2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
* `"angular-animate": "1.4.x"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.4.x.
|
||||
* `"jquery": "~2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd
|
||||
party libraries.
|
||||
|
||||
@@ -111,7 +111,7 @@ __`app/index.html`.__
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.3; jQuery 1.x is
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.4; jQuery 1.x is
|
||||
not officially supported.
|
||||
Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and
|
||||
animations will not work as expected.
|
||||
@@ -239,9 +239,9 @@ The name of the starting class is the name of the event that is fired (like `ent
|
||||
The active class name is the same as the starting class's but with an `-active` suffix.
|
||||
This two-class CSS naming convention allows the developer to craft an animation, beginning to end.
|
||||
|
||||
In our example above, elements expand from a height of **0** to **120 pixels** when items are added or moved,
|
||||
around and collapsing the items before removing them from the list.
|
||||
There's also a nice fade-in and fade-out effect that also occurs at the same time. All of this is handled
|
||||
In our example above, elements are expanded from a height of **0** to **120 pixels** when they're added to the
|
||||
list and are collapsed back down to **0 pixels** before being removed from the list.
|
||||
There's also a nice fade-in and fade-out effect that occurs at the same time. All of this is handled
|
||||
by the CSS transition declarations at the top of the example code above.
|
||||
|
||||
Although most modern browsers have good support for [CSS transitions](http://caniuse.com/#feat=css-transitions)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* using the --for_closure flag.
|
||||
* File generated from CLDR ver. 27.0.1
|
||||
*
|
||||
* This file coveres those locales that are not covered in
|
||||
* This file covers those locales that are not covered in
|
||||
* "numberformatsymbols.js".
|
||||
*
|
||||
* Before checkin, this file could have been manually edited. This is
|
||||
|
||||
@@ -11,6 +11,8 @@ function newTestLocaleInfo() {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
STANDALONEMONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
@@ -180,6 +182,8 @@ describe("extractDateTimeSymbols", function() {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
STANDALONEMONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
|
||||
'août', 'septembre', 'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
|
||||
@@ -27,6 +27,7 @@ describe("convertNumberData", function() {
|
||||
describe("convertDatetimeData", function() {
|
||||
var convert = converter.convertDatetimeData,
|
||||
dataObj = { MONTHS: ['Enero', 'Pebrero'],
|
||||
STANDALONEMONTHS: ['Enero', 'Pebrero'],
|
||||
SHORTMONTHS: ['Ene', 'Peb'],
|
||||
WEEKDAYS: ['Linggo', 'Lunes'],
|
||||
SHORTWEEKDAYS: ['Lin', 'Lun'],
|
||||
@@ -37,6 +38,7 @@ describe("convertDatetimeData", function() {
|
||||
it('should convert empty datetime obj', function() {
|
||||
var processedData = convert(dataObj);
|
||||
expect(processedData.MONTH).toEqual(['Enero', 'Pebrero']);
|
||||
expect(processedData.STANDALONEMONTH).toEqual(['Enero', 'Pebrero']);
|
||||
expect(processedData.SHORTMONTH).toEqual(['Ene', 'Peb']);
|
||||
expect(processedData.DAY).toEqual(['Linggo', 'Lunes']);
|
||||
expect(processedData.SHORTDAY).toEqual(['Lin', 'Lun']);
|
||||
|
||||
@@ -39,6 +39,7 @@ function convertDatetimeData(dataObj) {
|
||||
|
||||
datetimeFormats.MONTH = dataObj.MONTHS;
|
||||
datetimeFormats.SHORTMONTH = dataObj.SHORTMONTHS;
|
||||
datetimeFormats.STANDALONEMONTH = dataObj.STANDALONEMONTHS;
|
||||
datetimeFormats.DAY = dataObj.WEEKDAYS;
|
||||
datetimeFormats.SHORTDAY = dataObj.SHORTWEEKDAYS;
|
||||
datetimeFormats.AMPMS = dataObj.AMPMS;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var PATTERN_SEP = ';',
|
||||
DIGIT = '#';
|
||||
|
||||
/**
|
||||
* main funciton for parser
|
||||
* main function for parser
|
||||
* @param str {string} pattern to be parsed (e.g. #,##0.###).
|
||||
*/
|
||||
function parsePattern(pattern) {
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '43'
|
||||
version: '45'
|
||||
},
|
||||
'SL_Firefox': {
|
||||
base: 'SauceLabs',
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Browserstack tunnel"
|
||||
echo "TODO: implement me"
|
||||
exit 1
|
||||
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var glob = require("glob");
|
||||
var _ = require('lodash');
|
||||
var files = require('../../angularFiles').files;
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('validate-angular-files', function() {
|
||||
var combinedFiles = _.clone(files.angularModules);
|
||||
combinedFiles.ng = files.angularSrc;
|
||||
combinedFiles.angularLoader = files.angularLoader;
|
||||
|
||||
var errorsDetected = false;
|
||||
var directories = [];
|
||||
var detectedFiles = {};
|
||||
|
||||
for (var section in combinedFiles) {
|
||||
var sectionFiles = combinedFiles[section];
|
||||
|
||||
if (section != 'angularLoader') {
|
||||
directories.push('src/' + section);
|
||||
}
|
||||
|
||||
grunt.log.debug('Validating ' + sectionFiles.length + ' files from the "' + section + '" module.');
|
||||
|
||||
sectionFiles.forEach(function(file) {
|
||||
detectedFiles[file] = true;
|
||||
|
||||
if (!fs.existsSync(file)) {
|
||||
grunt.log.error(file + ' does not exist in the local file structure.');
|
||||
errorsDetected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
directories.forEach(function(directory) {
|
||||
glob.sync(directory + '/**/*').forEach(function(filePath) {
|
||||
if (!fs.lstatSync(filePath).isDirectory()) {
|
||||
var fileName = path.basename(filePath);
|
||||
var isHiddenFile = fileName[0] == '.';
|
||||
if (!isHiddenFile && !detectedFiles[filePath]) {
|
||||
grunt.log.error(filePath + ' exists in the local file structure but isn\'t used by any module.');
|
||||
errorsDetected = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (errorsDetected) {
|
||||
throw new Error('Not all files were properly detected in the local file structure.');
|
||||
} else {
|
||||
grunt.log.ok('All files were detected successfully!');
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,309 +0,0 @@
|
||||
/*
|
||||
* HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
|
||||
endTag = /^<\/(\w+)[^>]*>/,
|
||||
attr = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var special = makeMap("script,style");
|
||||
|
||||
var htmlParser = this.htmlParser = function( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){
|
||||
return this[ this.length - 1 ];
|
||||
};
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !special[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") == 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( html.indexOf("</") == 0 ) {
|
||||
match = html.match( endTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( endTag, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( html.indexOf("<") == 0 ) {
|
||||
match = html.match( startTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( startTag, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
|
||||
text = text.replace(/<!--(.*?)-->/g, "$1")
|
||||
.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last )
|
||||
throw "Parse Error: " + html;
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
if ( block[ tagName ] ) {
|
||||
while ( stack.last() && inline[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( closeSelf[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = empty[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = [];
|
||||
|
||||
rest.replace(attr, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
// If no tag name is provided, clean shop
|
||||
if ( !tagName )
|
||||
var pos = 0;
|
||||
|
||||
// Find the closest opened tag of the same type
|
||||
else
|
||||
for ( var pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( var i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.HTMLtoXML = function( html ) {
|
||||
var results = "";
|
||||
|
||||
htmlParser(html, {
|
||||
start: function( tag, attrs, unary ) {
|
||||
results += "<" + tag;
|
||||
|
||||
for ( var i = 0; i < attrs.length; i++ )
|
||||
results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';
|
||||
|
||||
results += (unary ? "/" : "") + ">";
|
||||
},
|
||||
end: function( tag ) {
|
||||
results += "</" + tag + ">";
|
||||
},
|
||||
chars: function( text ) {
|
||||
results += text;
|
||||
},
|
||||
comment: function( text ) {
|
||||
results += "<!--" + text + "-->";
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
this.HTMLtoDOM = function( html, doc ) {
|
||||
// There can be only one of these elements
|
||||
var one = makeMap("html,head,body,title");
|
||||
|
||||
// Enforce a structure for the document
|
||||
var structure = {
|
||||
link: "head",
|
||||
base: "head"
|
||||
};
|
||||
|
||||
if ( !doc ) {
|
||||
if ( typeof DOMDocument != "undefined" )
|
||||
doc = new DOMDocument();
|
||||
else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
|
||||
doc = document.implementation.createDocument("", "", null);
|
||||
else if ( typeof ActiveX != "undefined" )
|
||||
doc = new ActiveXObject("Msxml.DOMDocument");
|
||||
|
||||
} else
|
||||
doc = doc.ownerDocument ||
|
||||
doc.getOwnerDocument && doc.getOwnerDocument() ||
|
||||
doc;
|
||||
|
||||
var elems = [],
|
||||
documentElement = doc.documentElement ||
|
||||
doc.getDocumentElement && doc.getDocumentElement();
|
||||
|
||||
// If we're dealing with an empty document then we
|
||||
// need to pre-populate it with the HTML document structure
|
||||
if ( !documentElement && doc.createElement ) (function(){
|
||||
var html = doc.createElement("html");
|
||||
var head = doc.createElement("head");
|
||||
head.appendChild( doc.createElement("title") );
|
||||
html.appendChild( head );
|
||||
html.appendChild( doc.createElement("body") );
|
||||
doc.appendChild( html );
|
||||
})();
|
||||
|
||||
// Find all the unique elements
|
||||
if ( doc.getElementsByTagName )
|
||||
for ( var i in one )
|
||||
one[ i ] = doc.getElementsByTagName( i )[0];
|
||||
|
||||
// If we're working with a document, inject contents into
|
||||
// the body element
|
||||
var curParentNode = one.body;
|
||||
|
||||
htmlParser( html, {
|
||||
start: function( tagName, attrs, unary ) {
|
||||
// If it's a pre-built element, then we can ignore
|
||||
// its construction
|
||||
if ( one[ tagName ] ) {
|
||||
curParentNode = one[ tagName ];
|
||||
return;
|
||||
}
|
||||
|
||||
var elem = doc.createElement( tagName );
|
||||
|
||||
for ( var attr in attrs )
|
||||
elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
|
||||
|
||||
if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
|
||||
one[ structure[ tagName ] ].appendChild( elem );
|
||||
|
||||
else if ( curParentNode && curParentNode.appendChild )
|
||||
curParentNode.appendChild( elem );
|
||||
|
||||
if ( !unary ) {
|
||||
elems.push( elem );
|
||||
curParentNode = elem;
|
||||
}
|
||||
},
|
||||
end: function( tag ) {
|
||||
elems.length -= 1;
|
||||
|
||||
// Init the new parentNode
|
||||
curParentNode = elems[ elems.length - 1 ];
|
||||
},
|
||||
chars: function( text ) {
|
||||
curParentNode.appendChild( doc.createTextNode( text ) );
|
||||
},
|
||||
comment: function( text ) {
|
||||
// create comment node
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
function makeMap(str){
|
||||
var obj = {}, items = str.split(",");
|
||||
for ( var i = 0; i < items.length; i++ )
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
})();
|
||||
@@ -11,8 +11,7 @@ set -e
|
||||
# Curl and run this script as part of your .travis.yml before_script section:
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.7-linux.tar.gz"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.13-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-4.3.7-linux.tar.gz"
|
||||
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Sauce Connect tunnel"
|
||||
|
||||
killall sc
|
||||
|
||||
while [[ -n `ps -ef | grep "sauce-connect-" | grep -v "grep"` ]]; do
|
||||
printf "."
|
||||
sleep .5
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Sauce Connect tunnel has been shut down"
|
||||
@@ -178,7 +178,7 @@ var getSnapshotVersion = function() {
|
||||
// last release was a non beta release. Increment the patch level to
|
||||
// indicate the next release that we will be doing.
|
||||
// E.g. last release was 1.3.0, then the snapshot will be
|
||||
// 1.3.1-build.1, which is lesser than 1.3.1 accorind the semver!
|
||||
// 1.3.1-build.1, which is lesser than 1.3.1 according to the semver!
|
||||
|
||||
// If the last release was a beta release we don't update the
|
||||
// beta number by purpose, as otherwise the semver comparison
|
||||
|
||||
+5204
-870
File diff suppressed because it is too large
Load Diff
Generated
+8431
-1728
File diff suppressed because it is too large
Load Diff
+29
-16
@@ -1,18 +1,23 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.4.0-beta.0",
|
||||
"branchPattern": "1.4.*",
|
||||
"distTag": "latest",
|
||||
"branchVersion": "^1.5.0-beta.2",
|
||||
"branchPattern": "1.5.*",
|
||||
"distTag": "beta",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~0.10",
|
||||
"node": "<5",
|
||||
"npm": "~2.5"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/npm/check-node-modules.js --purge",
|
||||
"postinstall": "node scripts/npm/copy-npm-shrinkwrap.js",
|
||||
"commit": "git-cz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
@@ -20,9 +25,12 @@
|
||||
"browserstacktunnel-wrapper": "~1.3.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"cheerio": "^0.17.0",
|
||||
"commitizen": "^2.3.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"dgeni-packages": "^0.11.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-bump": "~0.0.13",
|
||||
"grunt-contrib-clean": "~0.6.0",
|
||||
@@ -32,7 +40,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": "^2.1.0",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
@@ -46,15 +54,15 @@
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "0.12.32",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-jasmine": "0.1.5",
|
||||
"karma-junit-reporter": "0.2.2",
|
||||
"karma-ng-scenario": "0.1.0",
|
||||
"karma-sauce-launcher": "0.2.10",
|
||||
"karma-script-launcher": "0.1.0",
|
||||
"karma": "^0.13.19",
|
||||
"karma-browserstack-launcher": "^0.1.8",
|
||||
"karma-chrome-launcher": "^0.2.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-jasmine": "^0.1.6",
|
||||
"karma-junit-reporter": "^0.3.8",
|
||||
"karma-ng-scenario": "^0.1.0",
|
||||
"karma-sauce-launcher": "^0.3.0",
|
||||
"karma-script-launcher": "^0.1.0",
|
||||
"load-grunt-tasks": "~0.6.0",
|
||||
"lodash": "~2.4.1",
|
||||
"marked": "~0.3.0",
|
||||
@@ -76,5 +84,10 @@
|
||||
"url": "https://github.com/angular/angular.js/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"dependencies": {}
|
||||
"dependencies": {},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ config.specs = [
|
||||
];
|
||||
|
||||
config.capabilities = {
|
||||
browserName: 'chrome',
|
||||
browserName: 'chrome'
|
||||
};
|
||||
|
||||
exports.config = config;
|
||||
|
||||
@@ -14,8 +14,9 @@ function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
BUILD_DIR=$(resolveDir ../../build)
|
||||
NEW_VERSION=$(cat $BUILD_DIR/version.txt)
|
||||
PROJECT_DIR=$(resolveDir ../..)
|
||||
# get the npm dist-tag from a custom property (distTag) in package.json
|
||||
DIST_TAG=$(readJsonProp "package.json" "distTag")
|
||||
DIST_TAG=$(readJsonProp "$PROJECT_DIR/package.json" "distTag")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ echo "#################################"
|
||||
# Enable tracing and exit on first failure
|
||||
set -xe
|
||||
|
||||
scripts/jenkins/set-node-version.sh
|
||||
|
||||
# This is the default set of browsers to use on the CI server unless overridden via env variable
|
||||
if [[ -z "$BROWSERS" ]]
|
||||
then
|
||||
@@ -19,6 +21,7 @@ rm -f angular.js.size
|
||||
|
||||
|
||||
# BUILD #
|
||||
npm install -g grunt-cli
|
||||
npm install --color false
|
||||
grunt ci-checks package --no-color
|
||||
|
||||
@@ -4,9 +4,7 @@ echo "#################################"
|
||||
echo "#### Update master ##############"
|
||||
echo "#################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
"[--no-test=(true|false)]"
|
||||
)
|
||||
ARG_DEFS=()
|
||||
|
||||
function init {
|
||||
if [[ ! $VERBOSE ]]; then
|
||||
@@ -17,14 +15,7 @@ function init {
|
||||
|
||||
function build {
|
||||
cd ../..
|
||||
|
||||
if [[ $NO_TEST == "true" ]]; then
|
||||
npm install --color false
|
||||
grunt ci-checks package --no-color
|
||||
else
|
||||
./jenkins_build.sh
|
||||
fi
|
||||
|
||||
scripts/jenkins/build.sh
|
||||
cd $SCRIPT_DIR
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,10 @@ function init {
|
||||
}
|
||||
|
||||
function build {
|
||||
./set-node-version.sh
|
||||
cd ../..
|
||||
|
||||
npm install -g grunt-cli
|
||||
npm install --color false
|
||||
grunt ci-checks package --no-color
|
||||
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install nvm for this shell
|
||||
source ~/.nvm/nvm.sh
|
||||
|
||||
# Use node.js at 4.2.x
|
||||
nvm install 4.2
|
||||
@@ -0,0 +1,74 @@
|
||||
// Implementation based on:
|
||||
// https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Constants
|
||||
var PROJECT_ROOT = path.join(__dirname, '../../');
|
||||
var NODE_MODULES_DIR = 'node_modules';
|
||||
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
|
||||
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
var purgeIfStale = process.argv.indexOf('--purge') !== -1;
|
||||
|
||||
process.chdir(PROJECT_ROOT);
|
||||
checkNodeModules(purgeIfStale);
|
||||
}
|
||||
|
||||
function checkNodeModules(purgeIfStale) {
|
||||
var nodeModulesOk = compareMarkerFiles(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE);
|
||||
|
||||
if (nodeModulesOk) {
|
||||
console.log(':-) npm dependencies are looking good!');
|
||||
} else if (purgeIfStale) {
|
||||
console.log(':-( npm dependencies are stale or in an unknown state!');
|
||||
console.log(' Purging \'' + NODE_MODULES_DIR + '\'...');
|
||||
deleteDirSync(NODE_MODULES_DIR);
|
||||
} else {
|
||||
var separator = new Array(81).join('!');
|
||||
|
||||
console.warn(separator);
|
||||
console.warn(':-( npm dependencies are stale or in an unknown state!');
|
||||
console.warn('You can rebuild the dependencies by running `npm install`.');
|
||||
console.warn(separator);
|
||||
}
|
||||
|
||||
return nodeModulesOk;
|
||||
}
|
||||
|
||||
function compareMarkerFiles(markerFilePath, cachedMarkerFilePath) {
|
||||
if (!fs.existsSync(cachedMarkerFilePath)) return false;
|
||||
|
||||
var opts = {encoding: 'utf-8'};
|
||||
var markerContent = fs.readFileSync(markerFilePath, opts);
|
||||
var cachedMarkerContent = fs.readFileSync(cachedMarkerFilePath, opts);
|
||||
|
||||
return markerContent === cachedMarkerContent;
|
||||
}
|
||||
|
||||
// Custom implementation of `rm -rf` that works consistently across OSes
|
||||
function deleteDirSync(path) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.readdirSync(path).forEach(deleteDirOrFileSync);
|
||||
fs.rmdirSync(path);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function deleteDirOrFileSync(subpath) {
|
||||
var curPath = path + '/' + subpath;
|
||||
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteDirSync(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Constants
|
||||
var PROJECT_ROOT = path.join(__dirname, '../../');
|
||||
var NODE_MODULES_DIR = 'node_modules';
|
||||
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
|
||||
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
process.chdir(PROJECT_ROOT);
|
||||
copyFile(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE, onCopied);
|
||||
}
|
||||
|
||||
// Implementation based on:
|
||||
// https://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js#answer-21995878
|
||||
function copyFile(srcPath, dstPath, callback) {
|
||||
var callbackCalled = false;
|
||||
|
||||
if (!fs.existsSync(srcPath)) {
|
||||
done(new Error('Missing source file: ' + srcPath));
|
||||
return;
|
||||
}
|
||||
|
||||
var rs = fs.createReadStream(srcPath);
|
||||
rs.on('error', done);
|
||||
|
||||
var ws = fs.createWriteStream(dstPath);
|
||||
ws.on('error', done);
|
||||
ws.on('finish', done);
|
||||
|
||||
rs.pipe(ws);
|
||||
|
||||
// Helpers
|
||||
function done(err) {
|
||||
if (callback && !callbackCalled) {
|
||||
callbackCalled = true;
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCopied(err) {
|
||||
if (err) {
|
||||
var separator = new Array(81).join('!');
|
||||
|
||||
console.error(separator);
|
||||
console.error(
|
||||
'Failed to copy `' + NPM_SHRINKWRAP_FILE + '` to `' + NPM_SHRINKWRAP_CACHED_FILE + '`:');
|
||||
console.error(err);
|
||||
console.error(separator);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/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
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p $LOGS_DIR
|
||||
|
||||
if [ $JOB != "ci-checks" ]; then
|
||||
echo "start_browser_provider"
|
||||
./scripts/travis/start_browser_provider.sh
|
||||
fi
|
||||
|
||||
npm install -g grunt-cli
|
||||
|
||||
if [ $JOB != "ci-checks" ]; then
|
||||
grunt package
|
||||
echo "wait_for_browser_provider"
|
||||
./scripts/travis/wait_for_browser_provider.sh
|
||||
fi
|
||||
@@ -5,7 +5,9 @@ set -e
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
|
||||
|
||||
if [ $JOB = "unit" ]; then
|
||||
if [ $JOB = "ci-checks" ]; then
|
||||
grunt ci-checks
|
||||
elif [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS"
|
||||
else
|
||||
@@ -14,7 +16,6 @@ if [ $JOB = "unit" ]; then
|
||||
|
||||
grunt test:promises-aplus
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
elif [ $JOB = "docs-e2e" ]; then
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
@@ -31,5 +32,5 @@ elif [ $JOB = "e2e" ]; then
|
||||
export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS"
|
||||
grunt test:travis-protractor --specs "$TARGET_SPECS"
|
||||
else
|
||||
echo "Unknown job type. Please set JOB=unit or JOB=e2e-*."
|
||||
echo "Unknown job type. Please set JOB=ci-checks, JOB=unit or JOB=e2e-*."
|
||||
fi
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Has to be run from project root directory.
|
||||
|
||||
./lib/${BROWSER_PROVIDER}/teardown_tunnel.sh
|
||||
@@ -94,8 +94,6 @@
|
||||
"VALIDITY_STATE_PROPERTY": false,
|
||||
"reloadWithDebugInfo": false,
|
||||
|
||||
"skipDestroyOnNextJQueryCleanData": true,
|
||||
|
||||
"NODE_TYPE_ELEMENT": false,
|
||||
"NODE_TYPE_ATTRIBUTE": false,
|
||||
"NODE_TYPE_TEXT": false,
|
||||
|
||||
+148
-102
@@ -198,20 +198,25 @@ msie = document.documentMode;
|
||||
* String ...)
|
||||
*/
|
||||
function isArrayLike(obj) {
|
||||
if (obj == null || isWindow(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `null`, `undefined` and `window` are not array-like
|
||||
if (obj == null || isWindow(obj)) return false;
|
||||
|
||||
// arrays, strings and jQuery/jqLite objects are array like
|
||||
// * jqLite is either the jQuery or jqLite constructor function
|
||||
// * we have to check the existence of jqLite first as this method is called
|
||||
// via the forEach method when constructing the jqLite object in the first place
|
||||
if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
|
||||
|
||||
// Support: iOS 8.2 (not reproducible in simulator)
|
||||
// "length" in obj used to prevent JIT error (gh-11508)
|
||||
var length = "length" in Object(obj) && obj.length;
|
||||
|
||||
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
|
||||
return true;
|
||||
}
|
||||
// NodeList objects (with `item` method) and
|
||||
// other objects with suitable length characteristics are array-like
|
||||
return isNumber(length) &&
|
||||
(length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function');
|
||||
|
||||
return isString(obj) || isArray(obj) || length === 0 ||
|
||||
typeof length === 'number' && length > 0 && (length - 1) in obj;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +361,10 @@ function baseExtend(dst, objs, deep) {
|
||||
dst[key] = new Date(src.valueOf());
|
||||
} else if (isRegExp(src)) {
|
||||
dst[key] = new RegExp(src);
|
||||
} else if (src.nodeName) {
|
||||
dst[key] = src.cloneNode(true);
|
||||
} else if (isElement(src)) {
|
||||
dst[key] = src.clone();
|
||||
} else {
|
||||
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
|
||||
baseExtend(dst[key], [src], true);
|
||||
@@ -471,7 +480,7 @@ identity.$inject = [];
|
||||
function valueFn(value) {return function() {return value;};}
|
||||
|
||||
function hasCustomToString(obj) {
|
||||
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
|
||||
return isFunction(obj.toString) && obj.toString !== toString;
|
||||
}
|
||||
|
||||
|
||||
@@ -670,9 +679,13 @@ function isPromiseLike(obj) {
|
||||
}
|
||||
|
||||
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
|
||||
function isTypedArray(value) {
|
||||
return TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
}
|
||||
|
||||
function isArrayBuffer(obj) {
|
||||
return toString.call(obj) === '[object ArrayBuffer]';
|
||||
}
|
||||
|
||||
|
||||
@@ -794,100 +807,138 @@ function arrayRemove(array, value) {
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
function copy(source, destination, stackSource, stackDest) {
|
||||
if (isWindow(source) || isScope(source)) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
if (isTypedArray(destination)) {
|
||||
throw ngMinErr('cpta',
|
||||
"Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
function copy(source, destination) {
|
||||
var stackSource = [];
|
||||
var stackDest = [];
|
||||
|
||||
if (!destination) {
|
||||
destination = source;
|
||||
if (isObject(source)) {
|
||||
var index;
|
||||
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
|
||||
return stackDest[index];
|
||||
}
|
||||
|
||||
// TypedArray, Date and RegExp have specific copy functionality and must be
|
||||
// pushed onto the stack before returning.
|
||||
// Array and other objects create the base object and recurse to copy child
|
||||
// objects. The array/object will be pushed onto the stack when recursed.
|
||||
if (isArray(source)) {
|
||||
return copy(source, [], stackSource, stackDest);
|
||||
} else if (isTypedArray(source)) {
|
||||
destination = new source.constructor(source);
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isRegExp(source)) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isFunction(source.cloneNode)) {
|
||||
destination = source.cloneNode(true);
|
||||
} else {
|
||||
var emptyObject = Object.create(getPrototypeOf(source));
|
||||
return copy(source, emptyObject, stackSource, stackDest);
|
||||
}
|
||||
|
||||
if (stackDest) {
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
}
|
||||
if (destination) {
|
||||
if (isTypedArray(destination) || isArrayBuffer(destination)) {
|
||||
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
} else {
|
||||
if (source === destination) throw ngMinErr('cpi',
|
||||
"Can't copy! Source and destination are identical.");
|
||||
|
||||
stackSource = stackSource || [];
|
||||
stackDest = stackDest || [];
|
||||
|
||||
if (isObject(source)) {
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
if (source === destination) {
|
||||
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
|
||||
}
|
||||
|
||||
// Empty the destination object
|
||||
if (isArray(destination)) {
|
||||
destination.length = 0;
|
||||
} else {
|
||||
forEach(destination, function(value, key) {
|
||||
if (key !== '$$hashKey') {
|
||||
delete destination[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
return copyRecurse(source, destination);
|
||||
}
|
||||
|
||||
return copyElement(source);
|
||||
|
||||
function copyRecurse(source, destination) {
|
||||
var h = destination.$$hashKey;
|
||||
var result, key;
|
||||
if (isArray(source)) {
|
||||
destination.length = 0;
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
destination.push(copy(source[i], null, stackSource, stackDest));
|
||||
for (var i = 0, ii = source.length; i < ii; i++) {
|
||||
destination.push(copyElement(source[i]));
|
||||
}
|
||||
} else if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var h = destination.$$hashKey;
|
||||
if (isArray(destination)) {
|
||||
destination.length = 0;
|
||||
} else {
|
||||
forEach(destination, function(value, key) {
|
||||
delete destination[key];
|
||||
});
|
||||
}
|
||||
if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
}
|
||||
setHashKey(destination,h);
|
||||
}
|
||||
setHashKey(destination, h);
|
||||
return destination;
|
||||
}
|
||||
|
||||
function copyElement(source) {
|
||||
// Simple values
|
||||
if (!isObject(source)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Already copied values
|
||||
var index = stackSource.indexOf(source);
|
||||
if (index !== -1) {
|
||||
return stackDest[index];
|
||||
}
|
||||
|
||||
if (isWindow(source) || isScope(source)) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
|
||||
var needsRecurse = false;
|
||||
var destination = copyType(source);
|
||||
|
||||
if (destination === undefined) {
|
||||
destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
|
||||
needsRecurse = true;
|
||||
}
|
||||
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
|
||||
return needsRecurse
|
||||
? copyRecurse(source, destination)
|
||||
: destination;
|
||||
}
|
||||
|
||||
function copyType(source) {
|
||||
switch (toString.call(source)) {
|
||||
case '[object Int8Array]':
|
||||
case '[object Int16Array]':
|
||||
case '[object Int32Array]':
|
||||
case '[object Float32Array]':
|
||||
case '[object Float64Array]':
|
||||
case '[object Uint8Array]':
|
||||
case '[object Uint8ClampedArray]':
|
||||
case '[object Uint16Array]':
|
||||
case '[object Uint32Array]':
|
||||
return new source.constructor(copyElement(source.buffer));
|
||||
|
||||
case '[object ArrayBuffer]':
|
||||
//Support: IE10
|
||||
if (!source.slice) {
|
||||
var copied = new ArrayBuffer(source.byteLength);
|
||||
new Uint8Array(copied).set(new Uint8Array(source));
|
||||
return copied;
|
||||
}
|
||||
return source.slice(0);
|
||||
|
||||
case '[object Boolean]':
|
||||
case '[object Number]':
|
||||
case '[object String]':
|
||||
case '[object Date]':
|
||||
return new source.constructor(source.valueOf());
|
||||
|
||||
case '[object RegExp]':
|
||||
var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
re.lastIndex = source.lastIndex;
|
||||
return re;
|
||||
}
|
||||
|
||||
if (isFunction(source.cloneNode)) {
|
||||
return source.cloneNode(true);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1663,7 +1714,6 @@ function snake_case(name, separator) {
|
||||
}
|
||||
|
||||
var bindJQueryFired = false;
|
||||
var skipDestroyOnNextJQueryCleanData;
|
||||
function bindJQuery() {
|
||||
var originalCleanData;
|
||||
|
||||
@@ -1697,15 +1747,11 @@ function bindJQuery() {
|
||||
originalCleanData = jQuery.cleanData;
|
||||
jQuery.cleanData = function(elems) {
|
||||
var events;
|
||||
if (!skipDestroyOnNextJQueryCleanData) {
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jQuery._data(elem, "events");
|
||||
if (events && events.$destroy) {
|
||||
jQuery(elem).triggerHandler('$destroy');
|
||||
}
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jQuery._data(elem, "events");
|
||||
if (events && events.$destroy) {
|
||||
jQuery(elem).triggerHandler('$destroy');
|
||||
}
|
||||
} else {
|
||||
skipDestroyOnNextJQueryCleanData = false;
|
||||
}
|
||||
originalCleanData(elems);
|
||||
};
|
||||
|
||||
@@ -56,11 +56,14 @@
|
||||
$AnchorScrollProvider,
|
||||
$AnimateProvider,
|
||||
$CoreAnimateCssProvider,
|
||||
$$CoreAnimateJsProvider,
|
||||
$$CoreAnimateQueueProvider,
|
||||
$$CoreAnimateRunnerProvider,
|
||||
$$AnimateRunnerFactoryProvider,
|
||||
$$AnimateAsyncRunFactoryProvider,
|
||||
$BrowserProvider,
|
||||
$CacheFactoryProvider,
|
||||
$ControllerProvider,
|
||||
$DateProvider,
|
||||
$DocumentProvider,
|
||||
$ExceptionHandlerProvider,
|
||||
$FilterProvider,
|
||||
@@ -72,6 +75,7 @@
|
||||
$HttpParamSerializerProvider,
|
||||
$HttpParamSerializerJQLikeProvider,
|
||||
$HttpBackendProvider,
|
||||
$xhrFactoryProvider,
|
||||
$LocationProvider,
|
||||
$LogProvider,
|
||||
$ParseProvider,
|
||||
@@ -215,8 +219,10 @@ function publishExternalAPI(angular) {
|
||||
$anchorScroll: $AnchorScrollProvider,
|
||||
$animate: $AnimateProvider,
|
||||
$animateCss: $CoreAnimateCssProvider,
|
||||
$$animateJs: $$CoreAnimateJsProvider,
|
||||
$$animateQueue: $$CoreAnimateQueueProvider,
|
||||
$$AnimateRunner: $$CoreAnimateRunnerProvider,
|
||||
$$AnimateRunner: $$AnimateRunnerFactoryProvider,
|
||||
$$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
|
||||
$browser: $BrowserProvider,
|
||||
$cacheFactory: $CacheFactoryProvider,
|
||||
$controller: $ControllerProvider,
|
||||
@@ -230,6 +236,7 @@ function publishExternalAPI(angular) {
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
$httpBackend: $HttpBackendProvider,
|
||||
$xhrFactory: $xhrFactoryProvider,
|
||||
$location: $LocationProvider,
|
||||
$log: $LogProvider,
|
||||
$parse: $ParseProvider,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, document, undefined) {
|
||||
|
||||
+69
-37
@@ -62,17 +62,23 @@
|
||||
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
|
||||
*/
|
||||
|
||||
var ARROW_ARG = /^([^\(]+?)=>/;
|
||||
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
|
||||
var FN_ARG_SPLIT = /,/;
|
||||
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
var $injectorMinErr = minErr('$injector');
|
||||
|
||||
function extractArgs(fn) {
|
||||
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
|
||||
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
|
||||
return args;
|
||||
}
|
||||
|
||||
function anonFn(fn) {
|
||||
// For anonymous functions, showing at the very least the function signature can help in
|
||||
// debugging.
|
||||
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
|
||||
args = fnText.match(FN_ARGS);
|
||||
var args = extractArgs(fn);
|
||||
if (args) {
|
||||
return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
|
||||
}
|
||||
@@ -81,7 +87,6 @@ function anonFn(fn) {
|
||||
|
||||
function annotate(fn, strictDi, name) {
|
||||
var $inject,
|
||||
fnText,
|
||||
argDecl,
|
||||
last;
|
||||
|
||||
@@ -96,8 +101,7 @@ function annotate(fn, strictDi, name) {
|
||||
throw $injectorMinErr('strictdi',
|
||||
'{0} is not using explicit annotation and cannot be invoked in strict mode', name);
|
||||
}
|
||||
fnText = fn.toString().replace(STRIP_COMMENTS, '');
|
||||
argDecl = fnText.match(FN_ARGS);
|
||||
argDecl = extractArgs(fn);
|
||||
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
|
||||
arg.replace(FN_ARG, function(all, underscore, name) {
|
||||
$inject.push(name);
|
||||
@@ -589,7 +593,7 @@ function annotate(fn, strictDi, name) {
|
||||
* @description
|
||||
*
|
||||
* Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
|
||||
* intercepts the creation of a service, allowing it to override or modify the behaviour of the
|
||||
* intercepts the creation of a service, allowing it to override or modify the behavior of the
|
||||
* service. The object returned by the decorator may be the original service, or a new service
|
||||
* object which replaces or wraps and delegates to the original service.
|
||||
*
|
||||
@@ -638,14 +642,19 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
|
||||
})),
|
||||
instanceCache = {},
|
||||
instanceInjector = (instanceCache.$injector =
|
||||
protoInstanceInjector =
|
||||
createInternalInjector(instanceCache, function(serviceName, caller) {
|
||||
var provider = providerInjector.get(serviceName + providerSuffix, caller);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
|
||||
}));
|
||||
return instanceInjector.invoke(
|
||||
provider.$get, provider, undefined, serviceName);
|
||||
}),
|
||||
instanceInjector = protoInstanceInjector;
|
||||
|
||||
|
||||
forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
|
||||
providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
|
||||
var runBlocks = loadModules(modulesToLoad);
|
||||
instanceInjector = protoInstanceInjector.get('$injector');
|
||||
instanceInjector.strictDi = strictDi;
|
||||
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
|
||||
|
||||
return instanceInjector;
|
||||
|
||||
@@ -795,48 +804,71 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function injectionArgs(fn, locals, serviceName) {
|
||||
var args = [],
|
||||
$inject = createInjector.$$annotate(fn, strictDi, serviceName);
|
||||
|
||||
for (var i = 0, length = $inject.length; i < length; i++) {
|
||||
var key = $inject[i];
|
||||
if (typeof key !== 'string') {
|
||||
throw $injectorMinErr('itkn',
|
||||
'Incorrect injection token! Expected service name as string, got {0}', key);
|
||||
}
|
||||
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
|
||||
getService(key, serviceName));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function isClass(func) {
|
||||
// IE 9-11 do not support classes and IE9 leaks with the code below.
|
||||
if (msie <= 11) {
|
||||
return false;
|
||||
}
|
||||
// Workaround for MS Edge.
|
||||
// Check https://connect.microsoft.com/IE/Feedback/Details/2211653
|
||||
return typeof func === 'function'
|
||||
&& /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func));
|
||||
}
|
||||
|
||||
function invoke(fn, self, locals, serviceName) {
|
||||
if (typeof locals === 'string') {
|
||||
serviceName = locals;
|
||||
locals = null;
|
||||
}
|
||||
|
||||
var args = [],
|
||||
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
|
||||
length, i,
|
||||
key;
|
||||
|
||||
for (i = 0, length = $inject.length; i < length; i++) {
|
||||
key = $inject[i];
|
||||
if (typeof key !== 'string') {
|
||||
throw $injectorMinErr('itkn',
|
||||
'Incorrect injection token! Expected service name as string, got {0}', key);
|
||||
}
|
||||
args.push(
|
||||
locals && locals.hasOwnProperty(key)
|
||||
? locals[key]
|
||||
: getService(key, serviceName)
|
||||
);
|
||||
}
|
||||
var args = injectionArgs(fn, locals, serviceName);
|
||||
if (isArray(fn)) {
|
||||
fn = fn[length];
|
||||
fn = fn[fn.length - 1];
|
||||
}
|
||||
|
||||
// http://jsperf.com/angularjs-invoke-apply-vs-switch
|
||||
// #5388
|
||||
return fn.apply(self, args);
|
||||
if (!isClass(fn)) {
|
||||
// http://jsperf.com/angularjs-invoke-apply-vs-switch
|
||||
// #5388
|
||||
return fn.apply(self, args);
|
||||
} else {
|
||||
args.unshift(null);
|
||||
/*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here.
|
||||
return new (Function.prototype.bind.apply(fn, args));
|
||||
/*jshint +W058 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function instantiate(Type, locals, serviceName) {
|
||||
// Check if Type is annotated and use just the given function at n-1 as parameter
|
||||
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
|
||||
var returnedValue = invoke(Type, instance, locals, serviceName);
|
||||
|
||||
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
|
||||
var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
|
||||
var args = injectionArgs(Type, locals, serviceName);
|
||||
// Empty object at position 0 is ignored for invocation with `new`, but required.
|
||||
args.unshift(null);
|
||||
/*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here.
|
||||
return new (Function.prototype.bind.apply(ctor, args));
|
||||
/*jshint +W058 */
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
invoke: invoke,
|
||||
instantiate: instantiate,
|
||||
|
||||
+84
-44
@@ -33,16 +33,22 @@
|
||||
*
|
||||
* If jQuery is available, `angular.element` is an alias for the
|
||||
* [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
|
||||
* delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
|
||||
* delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
|
||||
*
|
||||
* <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.</div>
|
||||
* jqLite is a tiny, API-compatible subset of jQuery that allows
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.
|
||||
*
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
|
||||
* {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
|
||||
* specific version of jQuery if multiple versions exist on the page.
|
||||
*
|
||||
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
|
||||
* jqLite; they are never raw DOM references.</div>
|
||||
* <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or
|
||||
* jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
|
||||
*
|
||||
* <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
|
||||
* by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
|
||||
* or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
|
||||
*
|
||||
* ## Angular's jqLite
|
||||
* jqLite provides only the following jQuery methods:
|
||||
@@ -55,7 +61,8 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
|
||||
* As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`detach()`](http://api.jquery.com/detach/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
@@ -189,6 +196,12 @@ function jqLiteHasData(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function jqLiteCleanData(nodes) {
|
||||
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
||||
jqLiteRemoveData(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function jqLiteBuildFragment(html, context) {
|
||||
var tmp, tag, wrap,
|
||||
fragment = context.createDocumentFragment(),
|
||||
@@ -241,6 +254,14 @@ function jqLiteParseHTML(html, context) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
|
||||
var jqLiteContains = Node.prototype.contains || function(arg) {
|
||||
// jshint bitwise: false
|
||||
return !!(this.compareDocumentPosition(arg) & 16);
|
||||
// jshint bitwise: true
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////
|
||||
function JQLite(element) {
|
||||
if (element instanceof JQLite) {
|
||||
@@ -299,17 +320,23 @@ function jqLiteOff(element, type, fn, unsupported) {
|
||||
delete events[type];
|
||||
}
|
||||
} else {
|
||||
forEach(type.split(' '), function(type) {
|
||||
if (isDefined(fn)) {
|
||||
var listenerFns = events[type];
|
||||
arrayRemove(listenerFns || [], fn);
|
||||
if (listenerFns && listenerFns.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListenerFn(element, type, handle);
|
||||
delete events[type];
|
||||
var removeHandler = function(type) {
|
||||
var listenerFns = events[type];
|
||||
if (isDefined(fn)) {
|
||||
arrayRemove(listenerFns || [], fn);
|
||||
}
|
||||
if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
|
||||
removeEventListenerFn(element, type, handle);
|
||||
delete events[type];
|
||||
}
|
||||
};
|
||||
|
||||
forEach(type.split(' '), function(type) {
|
||||
removeHandler(type);
|
||||
if (MOUSE_EVENT_MAP[type]) {
|
||||
removeHandler(MOUSE_EVENT_MAP[type]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -477,7 +504,7 @@ function jqLiteRemove(element, keepData) {
|
||||
function jqLiteDocumentLoaded(action, win) {
|
||||
win = win || window;
|
||||
if (win.document.readyState === 'complete') {
|
||||
// Force the action to be run async for consistent behaviour
|
||||
// Force the action to be run async for consistent behavior
|
||||
// from the action's point of view
|
||||
// i.e. it will definitely not be in a $apply
|
||||
win.setTimeout(action);
|
||||
@@ -563,7 +590,8 @@ function getAliasedAttrName(name) {
|
||||
forEach({
|
||||
data: jqLiteData,
|
||||
removeData: jqLiteRemoveData,
|
||||
hasData: jqLiteHasData
|
||||
hasData: jqLiteHasData,
|
||||
cleanData: jqLiteCleanData
|
||||
}, function(fn, name) {
|
||||
JQLite[name] = fn;
|
||||
});
|
||||
@@ -764,6 +792,9 @@ function createEventHandler(element, events) {
|
||||
return event.immediatePropagationStopped === true;
|
||||
};
|
||||
|
||||
// Some events have special handlers that wrap the real handler
|
||||
var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
|
||||
|
||||
// Copy event handlers in case event handlers array is modified during execution.
|
||||
if ((eventFnsLength > 1)) {
|
||||
eventFns = shallowCopy(eventFns);
|
||||
@@ -771,7 +802,7 @@ function createEventHandler(element, events) {
|
||||
|
||||
for (var i = 0; i < eventFnsLength; i++) {
|
||||
if (!event.isImmediatePropagationStopped()) {
|
||||
eventFns[i].call(element, event);
|
||||
handlerWrapper(element, event, eventFns[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -782,6 +813,22 @@ function createEventHandler(element, events) {
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
function defaultHandlerWrapper(element, event, handler) {
|
||||
handler.call(element, event);
|
||||
}
|
||||
|
||||
function specialMouseHandlerWrapper(target, event, handler) {
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
var related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if (!related || (related !== target && !jqLiteContains.call(target, related))) {
|
||||
handler.call(target, event);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Functions iterating traversal.
|
||||
// These functions chain results into a single
|
||||
@@ -810,35 +857,28 @@ forEach({
|
||||
var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
|
||||
var i = types.length;
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
var addHandler = function(type, specialHandlerWrapper, noEventListener) {
|
||||
var eventFns = events[type];
|
||||
|
||||
if (!eventFns) {
|
||||
events[type] = [];
|
||||
|
||||
if (type === 'mouseenter' || type === 'mouseleave') {
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
|
||||
jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
|
||||
var target = this, related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if (!related || (related !== target && !target.contains(related))) {
|
||||
handle(event, type);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (type !== '$destroy') {
|
||||
addEventListenerFn(element, type, handle);
|
||||
}
|
||||
eventFns = events[type] = [];
|
||||
eventFns.specialHandlerWrapper = specialHandlerWrapper;
|
||||
if (type !== '$destroy' && !noEventListener) {
|
||||
addEventListenerFn(element, type, handle);
|
||||
}
|
||||
eventFns = events[type];
|
||||
}
|
||||
|
||||
eventFns.push(fn);
|
||||
};
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
if (MOUSE_EVENT_MAP[type]) {
|
||||
addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
|
||||
addHandler(type, undefined, true);
|
||||
} else {
|
||||
addHandler(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+15
-2
@@ -76,7 +76,7 @@ function setupModuleLoader(window) {
|
||||
* unspecified then the module is being retrieved for further configuration.
|
||||
* @param {Function=} configFn Optional configuration function for the module. Same as
|
||||
* {@link angular.Module#config Module#config()}.
|
||||
* @returns {module} new module with the {@link angular.Module} api.
|
||||
* @returns {angular.Module} new module with the {@link angular.Module} api.
|
||||
*/
|
||||
return function module(name, requires, configFn) {
|
||||
var assertNotHasOwnProperty = function(name, context) {
|
||||
@@ -188,7 +188,7 @@ function setupModuleLoader(window) {
|
||||
* @param {string} name constant name
|
||||
* @param {*} object Constant value.
|
||||
* @description
|
||||
* Because the constant are fixed, they get applied before other provide methods.
|
||||
* Because the constants are fixed, they get applied before other provide methods.
|
||||
* See {@link auto.$provide#constant $provide.constant()}.
|
||||
*/
|
||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||
@@ -282,6 +282,19 @@ function setupModuleLoader(window) {
|
||||
*/
|
||||
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#component
|
||||
* @module ng
|
||||
* @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
|
||||
* @param {Object} options Component definition object (a simplified
|
||||
* {@link ng.$compile#directive-definition-object directive definition object})
|
||||
*
|
||||
* @description
|
||||
* See {@link ng.$compileProvider#component $compileProvider.component()}.
|
||||
*/
|
||||
component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#config
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
'use strict';
|
||||
(function() {
|
||||
function isFunction(value) {return typeof value === 'function';};
|
||||
function isFunction(value) {return typeof value === 'function';};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
|
||||
@@ -41,7 +41,7 @@ function $AnchorScrollProvider() {
|
||||
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
|
||||
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
|
||||
* in the
|
||||
* [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
|
||||
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
|
||||
*
|
||||
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
|
||||
* match any anchor whenever it changes. This can be disabled by calling
|
||||
|
||||
+27
-28
@@ -53,27 +53,8 @@ function prepareAnimateOptions(options) {
|
||||
: {};
|
||||
}
|
||||
|
||||
var $$CoreAnimateRunnerProvider = function() {
|
||||
this.$get = ['$q', '$$rAF', function($q, $$rAF) {
|
||||
function AnimateRunner() {}
|
||||
AnimateRunner.all = noop;
|
||||
AnimateRunner.chain = noop;
|
||||
AnimateRunner.prototype = {
|
||||
end: noop,
|
||||
cancel: noop,
|
||||
resume: noop,
|
||||
pause: noop,
|
||||
complete: noop,
|
||||
then: function(pass, fail) {
|
||||
return $q(function(resolve) {
|
||||
$$rAF(function() {
|
||||
resolve();
|
||||
});
|
||||
}).then(pass, fail);
|
||||
}
|
||||
};
|
||||
return AnimateRunner;
|
||||
}];
|
||||
var $$CoreAnimateJsProvider = function() {
|
||||
this.$get = function() {};
|
||||
};
|
||||
|
||||
// this is prefixed with Core since it conflicts with
|
||||
@@ -101,7 +82,12 @@ var $$CoreAnimateQueueProvider = function() {
|
||||
addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
|
||||
}
|
||||
|
||||
return new $$AnimateRunner(); // jshint ignore:line
|
||||
var runner = new $$AnimateRunner(); // jshint ignore:line
|
||||
|
||||
// since there are no animations to run the runner needs to be
|
||||
// notified that the animation call is complete.
|
||||
runner.complete();
|
||||
return runner;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,7 +271,7 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* when an animation is detected (and animations are enabled), $animate will do the heavy lifting
|
||||
* to ensure that animation runs with the triggered DOM operation.
|
||||
*
|
||||
* By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
|
||||
* By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
|
||||
* included and only when it is active then the animation hooks that `$animate` triggers will be
|
||||
* functional. Once active then all structural `ng-` directives will trigger animations as they perform
|
||||
* their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
|
||||
@@ -566,17 +552,30 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @kind function
|
||||
*
|
||||
* @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
|
||||
* If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
|
||||
* on the provided styles. For example, if a transition animation is set for the given className then the provided from and
|
||||
* to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
|
||||
* will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
|
||||
* If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
|
||||
* on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and
|
||||
* `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
|
||||
* style in `to`, the style in `from` is applied immediately, and no animation is run.
|
||||
* If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
|
||||
* method (or as part of the `options` parameter):
|
||||
*
|
||||
* ```js
|
||||
* ngModule.animation('.my-inline-animation', function() {
|
||||
* return {
|
||||
* animate : function(element, from, to, done, options) {
|
||||
* //animation
|
||||
* done();
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {DOMElement} element the element which the CSS styles will be applied to
|
||||
* @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
|
||||
* @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
|
||||
* @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
|
||||
* this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
|
||||
* (Note that if no animation is detected then this value will not be appplied to the element.)
|
||||
* (Note that if no animation is detected then this value will not be applied to the element.)
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
|
||||
+21
-32
@@ -12,43 +12,32 @@
|
||||
* Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
|
||||
*/
|
||||
var $CoreAnimateCssProvider = function() {
|
||||
this.$get = ['$$rAF', '$q', function($$rAF, $q) {
|
||||
this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
|
||||
|
||||
var RAFPromise = function() {};
|
||||
RAFPromise.prototype = {
|
||||
done: function(cancel) {
|
||||
this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
|
||||
},
|
||||
end: function() {
|
||||
this.done();
|
||||
},
|
||||
cancel: function() {
|
||||
this.done(true);
|
||||
},
|
||||
getPromise: function() {
|
||||
if (!this.defer) {
|
||||
this.defer = $q.defer();
|
||||
}
|
||||
return this.defer.promise;
|
||||
},
|
||||
then: function(f1,f2) {
|
||||
return this.getPromise().then(f1,f2);
|
||||
},
|
||||
'catch': function(f1) {
|
||||
return this.getPromise()['catch'](f1);
|
||||
},
|
||||
'finally': function(f1) {
|
||||
return this.getPromise()['finally'](f1);
|
||||
return function(element, initialOptions) {
|
||||
// all of the animation functions should create
|
||||
// a copy of the options data, however, if a
|
||||
// parent service has already created a copy then
|
||||
// we should stick to using that
|
||||
var options = initialOptions || {};
|
||||
if (!options.$$prepared) {
|
||||
options = copy(options);
|
||||
}
|
||||
|
||||
// there is no point in applying the styles since
|
||||
// there is no animation that goes on at all in
|
||||
// this version of $animateCss.
|
||||
if (options.cleanupStyles) {
|
||||
options.from = options.to = null;
|
||||
}
|
||||
};
|
||||
|
||||
return function(element, options) {
|
||||
if (options.from) {
|
||||
element.css(options.from);
|
||||
options.from = null;
|
||||
}
|
||||
|
||||
var closed, runner = new RAFPromise();
|
||||
/* jshint newcap: false */
|
||||
var closed, runner = new $$AnimateRunner();
|
||||
return {
|
||||
start: run,
|
||||
end: run
|
||||
@@ -56,16 +45,16 @@ var $CoreAnimateCssProvider = function() {
|
||||
|
||||
function run() {
|
||||
$$rAF(function() {
|
||||
close();
|
||||
applyAnimationContents();
|
||||
if (!closed) {
|
||||
runner.done();
|
||||
runner.complete();
|
||||
}
|
||||
closed = true;
|
||||
});
|
||||
return runner;
|
||||
}
|
||||
|
||||
function close() {
|
||||
function applyAnimationContents() {
|
||||
if (options.addClass) {
|
||||
element.addClass(options.addClass);
|
||||
options.addClass = null;
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
'use strict';
|
||||
|
||||
var $$AnimateAsyncRunFactoryProvider = function() {
|
||||
this.$get = ['$$rAF', function($$rAF) {
|
||||
var waitQueue = [];
|
||||
|
||||
function waitForTick(fn) {
|
||||
waitQueue.push(fn);
|
||||
if (waitQueue.length > 1) return;
|
||||
$$rAF(function() {
|
||||
for (var i = 0; i < waitQueue.length; i++) {
|
||||
waitQueue[i]();
|
||||
}
|
||||
waitQueue = [];
|
||||
});
|
||||
}
|
||||
|
||||
return function() {
|
||||
var passed = false;
|
||||
waitForTick(function() {
|
||||
passed = true;
|
||||
});
|
||||
return function(callback) {
|
||||
passed ? callback() : waitForTick(callback);
|
||||
};
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
var $$AnimateRunnerFactoryProvider = function() {
|
||||
this.$get = ['$q', '$sniffer', '$$animateAsyncRun',
|
||||
function($q, $sniffer, $$animateAsyncRun) {
|
||||
|
||||
var INITIAL_STATE = 0;
|
||||
var DONE_PENDING_STATE = 1;
|
||||
var DONE_COMPLETE_STATE = 2;
|
||||
|
||||
AnimateRunner.chain = function(chain, callback) {
|
||||
var index = 0;
|
||||
|
||||
next();
|
||||
function next() {
|
||||
if (index === chain.length) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
chain[index](function(response) {
|
||||
if (response === false) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AnimateRunner.all = function(runners, callback) {
|
||||
var count = 0;
|
||||
var status = true;
|
||||
forEach(runners, function(runner) {
|
||||
runner.done(onProgress);
|
||||
});
|
||||
|
||||
function onProgress(response) {
|
||||
status = status && response;
|
||||
if (++count === runners.length) {
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function AnimateRunner(host) {
|
||||
this.setHost(host);
|
||||
|
||||
this._doneCallbacks = [];
|
||||
this._runInAnimationFrame = $$animateAsyncRun();
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
AnimateRunner.prototype = {
|
||||
setHost: function(host) {
|
||||
this.host = host || {};
|
||||
},
|
||||
|
||||
done: function(fn) {
|
||||
if (this._state === DONE_COMPLETE_STATE) {
|
||||
fn();
|
||||
} else {
|
||||
this._doneCallbacks.push(fn);
|
||||
}
|
||||
},
|
||||
|
||||
progress: noop,
|
||||
|
||||
getPromise: function() {
|
||||
if (!this.promise) {
|
||||
var self = this;
|
||||
this.promise = $q(function(resolve, reject) {
|
||||
self.done(function(status) {
|
||||
status === false ? reject() : resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this.promise;
|
||||
},
|
||||
|
||||
then: function(resolveHandler, rejectHandler) {
|
||||
return this.getPromise().then(resolveHandler, rejectHandler);
|
||||
},
|
||||
|
||||
'catch': function(handler) {
|
||||
return this.getPromise()['catch'](handler);
|
||||
},
|
||||
|
||||
'finally': function(handler) {
|
||||
return this.getPromise()['finally'](handler);
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
if (this.host.pause) {
|
||||
this.host.pause();
|
||||
}
|
||||
},
|
||||
|
||||
resume: function() {
|
||||
if (this.host.resume) {
|
||||
this.host.resume();
|
||||
}
|
||||
},
|
||||
|
||||
end: function() {
|
||||
if (this.host.end) {
|
||||
this.host.end();
|
||||
}
|
||||
this._resolve(true);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
if (this.host.cancel) {
|
||||
this.host.cancel();
|
||||
}
|
||||
this._resolve(false);
|
||||
},
|
||||
|
||||
complete: function(response) {
|
||||
var self = this;
|
||||
if (self._state === INITIAL_STATE) {
|
||||
self._state = DONE_PENDING_STATE;
|
||||
self._runInAnimationFrame(function() {
|
||||
self._resolve(response);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_resolve: function(response) {
|
||||
if (this._state !== DONE_COMPLETE_STATE) {
|
||||
forEach(this._doneCallbacks, function(fn) {
|
||||
fn(response);
|
||||
});
|
||||
this._doneCallbacks.length = 0;
|
||||
this._state = DONE_COMPLETE_STATE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return AnimateRunner;
|
||||
}];
|
||||
};
|
||||
@@ -67,10 +67,10 @@
|
||||
$scope.keys = [];
|
||||
$scope.cache = $cacheFactory('cacheId');
|
||||
$scope.put = function(key, value) {
|
||||
if (isUndefined($scope.cache.get(key))) {
|
||||
if (angular.isUndefined($scope.cache.get(key))) {
|
||||
$scope.keys.push(key);
|
||||
}
|
||||
$scope.cache.put(key, isUndefined(value) ? null : value);
|
||||
$scope.cache.put(key, angular.isUndefined(value) ? null : value);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
@@ -93,9 +93,9 @@ function $CacheFactoryProvider() {
|
||||
|
||||
var size = 0,
|
||||
stats = extend({}, options, {id: cacheId}),
|
||||
data = {},
|
||||
data = createMap(),
|
||||
capacity = (options && options.capacity) || Number.MAX_VALUE,
|
||||
lruHash = {},
|
||||
lruHash = createMap(),
|
||||
freshEnd = null,
|
||||
staleEnd = null;
|
||||
|
||||
@@ -223,6 +223,8 @@ function $CacheFactoryProvider() {
|
||||
delete lruHash[key];
|
||||
}
|
||||
|
||||
if (!(key in data)) return;
|
||||
|
||||
delete data[key];
|
||||
size--;
|
||||
},
|
||||
@@ -237,9 +239,9 @@ function $CacheFactoryProvider() {
|
||||
* Clears the cache object of any entries.
|
||||
*/
|
||||
removeAll: function() {
|
||||
data = {};
|
||||
data = createMap();
|
||||
size = 0;
|
||||
lruHash = {};
|
||||
lruHash = createMap();
|
||||
freshEnd = staleEnd = null;
|
||||
},
|
||||
|
||||
@@ -399,4 +401,3 @@ function $TemplateCacheProvider() {
|
||||
return $cacheFactory('templates');
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
+495
-144
@@ -128,7 +128,7 @@
|
||||
* When this property is set to true, the HTML compiler will collect DOM nodes between
|
||||
* nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
|
||||
* together as the directive elements. It is recommended that this feature be used on directives
|
||||
* which are not strictly behavioural (such as {@link ngClick}), and which
|
||||
* which are not strictly behavioral (such as {@link ngClick}), and which
|
||||
* do not manipulate or replace child nodes (such as {@link ngInclude}).
|
||||
*
|
||||
* #### `priority`
|
||||
@@ -212,9 +212,22 @@
|
||||
*
|
||||
*
|
||||
* #### `bindToController`
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
|
||||
* This property is used to bind scope properties directly to the controller. It can be either
|
||||
* `true` or an object hash with the same format as the `scope` property. Additionally, a controller
|
||||
* alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller
|
||||
* definition: `controller: 'myCtrl as myAlias'`.
|
||||
*
|
||||
* When an isolate scope is used for a directive (see above), `bindToController: true` will
|
||||
* allow a component to have its properties bound to the controller, rather than to scope. When the controller
|
||||
* is instantiated, the initial values of the isolate scope bindings are already available.
|
||||
* is instantiated, the initial values of the isolate scope bindings will be available if the controller is not an ES6 class.
|
||||
*
|
||||
* It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
|
||||
* This will set up the scope bindings to the controller directly. Note that `scope` can still be used
|
||||
* to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
|
||||
* scope (useful for component directives).
|
||||
*
|
||||
* If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
|
||||
*
|
||||
*
|
||||
* #### `controller`
|
||||
* Controller constructor function. The controller is instantiated before the
|
||||
@@ -226,10 +239,10 @@
|
||||
* * `$element` - Current element
|
||||
* * `$attrs` - Current attributes object for the element
|
||||
* * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
|
||||
* `function([scope], cloneLinkingFn, futureParentElement)`.
|
||||
* * `scope`: optional argument to override the scope.
|
||||
* * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
|
||||
* * `futureParentElement`:
|
||||
* `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
|
||||
* * `scope`: (optional) override the scope.
|
||||
* * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
|
||||
* * `futureParentElement` (optional):
|
||||
* * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
|
||||
* * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
|
||||
* * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
|
||||
@@ -237,7 +250,10 @@
|
||||
* as those elements need to created and cloned in a special way when they are defined outside their
|
||||
* usual containers (e.g. like `<svg>`).
|
||||
* * See also the `directive.templateNamespace` property.
|
||||
*
|
||||
* * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
|
||||
* then the default translusion is provided.
|
||||
* The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
|
||||
* `true` if the specified slot contains content (i.e. one or more DOM nodes).
|
||||
*
|
||||
* #### `require`
|
||||
* Require another directive and inject its controller as the fourth argument to the linking function. The
|
||||
@@ -337,14 +353,6 @@
|
||||
* The contents are compiled and provided to the directive as a **transclusion function**. See the
|
||||
* {@link $compile#transclusion Transclusion} section below.
|
||||
*
|
||||
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element or the entire element:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
*
|
||||
*
|
||||
* #### `compile`
|
||||
*
|
||||
@@ -474,6 +482,34 @@
|
||||
* Testing Transclusion Directives}.
|
||||
* </div>
|
||||
*
|
||||
* There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element, the entire element or multiple parts of the element contents:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
* * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
|
||||
*
|
||||
* **Mult-slot transclusion** is declared by providing an object for the `transclude` property.
|
||||
*
|
||||
* This object is a map where the keys are the name of the slot to fill and the value is an element selector
|
||||
* used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
|
||||
* and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* If the element selector is prefixed with a `?` then that slot is optional.
|
||||
*
|
||||
* For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
|
||||
* the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
|
||||
*
|
||||
* Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
|
||||
* in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
|
||||
* `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
|
||||
* injectable into the directive's controller.
|
||||
*
|
||||
*
|
||||
* #### Transclusion Functions
|
||||
*
|
||||
* When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
|
||||
@@ -494,7 +530,7 @@
|
||||
* content and the `scope` is the newly created transclusion scope, to which the clone is bound.
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
|
||||
* **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
|
||||
* since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
|
||||
* </div>
|
||||
*
|
||||
@@ -526,7 +562,7 @@
|
||||
* </div>
|
||||
*
|
||||
* The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
|
||||
* automatically destroy their transluded clones as necessary so you do not need to worry about this if
|
||||
* automatically destroy their transcluded clones as necessary so you do not need to worry about this if
|
||||
* you are simply using {@link ngTransclude} to inject the transclusion into your directive.
|
||||
*
|
||||
*
|
||||
@@ -551,19 +587,19 @@
|
||||
*
|
||||
* The `$parent` scope hierarchy will look like this:
|
||||
*
|
||||
* ```
|
||||
* - $rootScope
|
||||
* - isolate
|
||||
* - transclusion
|
||||
* ```
|
||||
```
|
||||
- $rootScope
|
||||
- isolate
|
||||
- transclusion
|
||||
```
|
||||
*
|
||||
* but the scopes will inherit prototypically from different scopes to their `$parent`.
|
||||
*
|
||||
* ```
|
||||
* - $rootScope
|
||||
* - transclusion
|
||||
* - isolate
|
||||
* ```
|
||||
```
|
||||
- $rootScope
|
||||
- transclusion
|
||||
- isolate
|
||||
```
|
||||
*
|
||||
*
|
||||
* ### Attributes
|
||||
@@ -844,8 +880,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
|
||||
* will match as <code>ng-bind</code>), or an object map of directives where the keys are the
|
||||
* names and the values are the factories.
|
||||
* @param {Function|Array} directiveFactory An injectable directive factory function. See
|
||||
* {@link guide/directive} for more info.
|
||||
* @param {Function|Array} directiveFactory An injectable directive factory function. See the
|
||||
* {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
|
||||
* @returns {ng.$compileProvider} Self for chaining.
|
||||
*/
|
||||
this.directive = function registerDirective(name, directiveFactory) {
|
||||
@@ -892,6 +928,167 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#component
|
||||
* @module ng
|
||||
* @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`)
|
||||
* @param {Object} options Component definition object (a simplified
|
||||
* {@link ng.$compile#directive-definition-object directive definition object}),
|
||||
* with the following properties (all optional):
|
||||
*
|
||||
* - `controller` – `{(string|function()=}` – controller constructor function that should be
|
||||
* associated with newly created scope or the name of a {@link ng.$compile#-controller-
|
||||
* registered controller} if passed as a string. An empty `noop` function by default.
|
||||
* - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
|
||||
* If present, the controller will be published to scope under the `controllerAs` name.
|
||||
* If not present, this will default to be `$ctrl`.
|
||||
* - `template` – `{string=|function()=}` – html template as a string or a function that
|
||||
* returns an html template as a string which should be used as the contents of this component.
|
||||
* Empty string by default.
|
||||
*
|
||||
* If `template` is a function, then it is {@link auto.$injector#invoke injected} with
|
||||
* the following locals:
|
||||
*
|
||||
* - `$element` - Current element
|
||||
* - `$attrs` - Current attributes object for the element
|
||||
*
|
||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
||||
* template that should be used as the contents of this component.
|
||||
*
|
||||
* If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
|
||||
* the following locals:
|
||||
*
|
||||
* - `$element` - Current element
|
||||
* - `$attrs` - Current attributes object for the element
|
||||
*
|
||||
* - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
|
||||
* Component properties are always bound to the component controller and not to the scope.
|
||||
* See {@link ng.$compile#-bindtocontroller- `bindToController`}.
|
||||
* - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
|
||||
* Disabled by default.
|
||||
* - `$...` – `{function()=}` – additional annotations to provide to the directive factory function.
|
||||
*
|
||||
* @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
|
||||
* @description
|
||||
* Register a **component definition** with the compiler. This is a shorthand for registering a special
|
||||
* type of directive, which represents a self-contained UI component in your application. Such components
|
||||
* are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
|
||||
*
|
||||
* Component definitions are very simple and do not require as much configuration as defining general
|
||||
* directives. Component definitions usually consist only of a template and a controller backing it.
|
||||
*
|
||||
* In order to make the definition easier, components enforce best practices like use of `controllerAs`,
|
||||
* `bindToController`. They always have **isolate scope** and are restricted to elements.
|
||||
*
|
||||
* Here are a few examples of how you would usually define components:
|
||||
*
|
||||
* ```js
|
||||
* var myMod = angular.module(...);
|
||||
* myMod.component('myComp', {
|
||||
* template: '<div>My name is {{$ctrl.name}}</div>',
|
||||
* controller: function() {
|
||||
* this.name = 'shahar';
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* myMod.component('myComp', {
|
||||
* template: '<div>My name is {{$ctrl.name}}</div>',
|
||||
* bindings: {name: '@'}
|
||||
* });
|
||||
*
|
||||
* myMod.component('myComp', {
|
||||
* templateUrl: 'views/my-comp.html',
|
||||
* controller: 'MyCtrl as ctrl',
|
||||
* bindings: {name: '@'}
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* <br />
|
||||
* Components are also useful as route templates (e.g. when using
|
||||
* {@link ngRoute ngRoute}):
|
||||
*
|
||||
* ```js
|
||||
* var myMod = angular.module('myMod', ['ngRoute']);
|
||||
*
|
||||
* myMod.component('home', {
|
||||
* template: '<h1>Home</h1><p>Hello, {{ home.user.name }} !</p>',
|
||||
* controller: function() {
|
||||
* this.user = {name: 'world'};
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* myMod.config(function($routeProvider) {
|
||||
* $routeProvider.when('/', {
|
||||
* template: '<home></home>'
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* <br />
|
||||
* When using {@link ngRoute.$routeProvider $routeProvider}, you can often avoid some
|
||||
* boilerplate, by assigning the resolved dependencies directly on the route scope:
|
||||
*
|
||||
* ```js
|
||||
* var myMod = angular.module('myMod', ['ngRoute']);
|
||||
*
|
||||
* myMod.component('home', {
|
||||
* template: '<h1>Home</h1><p>Hello, {{ home.user.name }} !</p>',
|
||||
* bindings: {user: '='}
|
||||
* });
|
||||
*
|
||||
* myMod.config(function($routeProvider) {
|
||||
* $routeProvider.when('/', {
|
||||
* template: '<home user="$resolve.user"></home>',
|
||||
* resolve: {user: function($http) { return $http.get('...'); }}
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* <br />
|
||||
* See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
|
||||
*/
|
||||
this.component = function registerComponent(name, options) {
|
||||
var controller = options.controller || function() {};
|
||||
|
||||
function factory($injector) {
|
||||
function makeInjectable(fn) {
|
||||
if (isFunction(fn) || isArray(fn)) {
|
||||
return function(tElement, tAttrs) {
|
||||
return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
|
||||
};
|
||||
} else {
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
var template = (!options.template && !options.templateUrl ? '' : options.template);
|
||||
return {
|
||||
controller: controller,
|
||||
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
|
||||
template: makeInjectable(template),
|
||||
templateUrl: makeInjectable(options.templateUrl),
|
||||
transclude: options.transclude,
|
||||
scope: {},
|
||||
bindToController: options.bindings || {},
|
||||
restrict: 'E'
|
||||
};
|
||||
}
|
||||
|
||||
// Copy any annotation properties (starting with $) over to the factory function
|
||||
// These could be used by libraries such as the new component router
|
||||
forEach(options, function(val, key) {
|
||||
if (key.charAt(0) === '$') {
|
||||
factory[key] = val;
|
||||
}
|
||||
});
|
||||
|
||||
factory.$inject = ['$injector'];
|
||||
|
||||
return this.directive(name, factory);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -989,6 +1186,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
|
||||
$controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
|
||||
|
||||
var SIMPLE_ATTR_NAME = /^\w/;
|
||||
var specialAttrHolder = document.createElement('div');
|
||||
var Attributes = function(element, attributesToCopy) {
|
||||
if (attributesToCopy) {
|
||||
var keys = Object.keys(attributesToCopy);
|
||||
@@ -1124,7 +1323,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
if ((nodeName === 'a' && key === 'href') ||
|
||||
if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
|
||||
(nodeName === 'img' && key === 'src')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = $$sanitizeUri(value, key === 'src');
|
||||
@@ -1168,7 +1367,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (value === null || isUndefined(value)) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
if (SIMPLE_ATTR_NAME.test(attrName)) {
|
||||
this.$$element.attr(attrName, value);
|
||||
} else {
|
||||
setSpecialAttr(this.$$element[0], attrName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,7 +1402,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* @param {string} key Normalized key. (ie ngAttribute) .
|
||||
* @param {function(interpolatedValue)} fn Function that will be called whenever
|
||||
the interpolated value of the attribute changes.
|
||||
* See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
|
||||
* See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
|
||||
* guide} for more info.
|
||||
* @returns {function()} Returns a deregistration function for this observer.
|
||||
*/
|
||||
$observe: function(key, fn) {
|
||||
@@ -1221,6 +1425,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
};
|
||||
|
||||
function setSpecialAttr(element, attrName, value) {
|
||||
// Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
|
||||
// so we have to jump through some hoops to get such an attribute
|
||||
// https://github.com/angular/angular.js/pull/13318
|
||||
specialAttrHolder.innerHTML = "<span " + attrName + ">";
|
||||
var attributes = specialAttrHolder.firstChild.attributes;
|
||||
var attribute = attributes[0];
|
||||
// We have to remove the attribute from its container element before we can add it to the destination element
|
||||
attributes.removeNamedItem(attribute.name);
|
||||
attribute.value = value;
|
||||
element.attributes.setNamedItem(attribute);
|
||||
}
|
||||
|
||||
function safeAddClass($element, className) {
|
||||
try {
|
||||
@@ -1240,6 +1456,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
||||
},
|
||||
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
||||
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
|
||||
|
||||
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
|
||||
var bindings = $element.data('$binding') || [];
|
||||
@@ -1280,7 +1497,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// We can not compile top level text elements since text nodes can be merged and we will
|
||||
// not be able to attach scope data to them, so we will wrap them in <span>
|
||||
forEach($compileNodes, function(node, index) {
|
||||
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
||||
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */) {
|
||||
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
|
||||
}
|
||||
});
|
||||
@@ -1292,6 +1509,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return function publicLinkFn(scope, cloneConnectFn, options) {
|
||||
assertArg(scope, 'scope');
|
||||
|
||||
if (previousCompileContext && previousCompileContext.needsNewScope) {
|
||||
// A parent directive did a replace and a directive on this element asked
|
||||
// for transclusion, which caused us to lose a layer of element on which
|
||||
// we could hold the new transclusion scope, so we will create it manually
|
||||
// here.
|
||||
scope = scope.$parent.$new();
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
|
||||
transcludeControllers = options.transcludeControllers,
|
||||
@@ -1346,7 +1571,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!node) {
|
||||
return 'html';
|
||||
} else {
|
||||
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
|
||||
return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1437,11 +1662,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (nodeLinkFn.scope) {
|
||||
childScope = scope.$new();
|
||||
compile.$$addScopeInfo(jqLite(node), childScope);
|
||||
var destroyBindings = nodeLinkFn.$$destroyBindings;
|
||||
if (destroyBindings) {
|
||||
nodeLinkFn.$$destroyBindings = null;
|
||||
childScope.$on('$destroyed', destroyBindings);
|
||||
}
|
||||
} else {
|
||||
childScope = scope;
|
||||
}
|
||||
@@ -1460,8 +1680,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = null;
|
||||
}
|
||||
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
|
||||
nodeLinkFn);
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
|
||||
|
||||
} else if (childLinkFn) {
|
||||
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
|
||||
@@ -1486,6 +1705,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
};
|
||||
|
||||
// We need to attach the transclusion slots onto the `boundTranscludeFn`
|
||||
// so that they are available inside the `controllersBoundTransclude` function
|
||||
var boundSlots = boundTranscludeFn.$$slots = createMap();
|
||||
for (var slotName in transcludeFn.$$slots) {
|
||||
if (transcludeFn.$$slots[slotName]) {
|
||||
boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
|
||||
} else {
|
||||
boundSlots[slotName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return boundTranscludeFn;
|
||||
}
|
||||
|
||||
@@ -1530,13 +1760,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
if (directiveIsMultiElement(directiveNName)) {
|
||||
if (ngAttrName === directiveNName + 'Start') {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
|
||||
if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
@@ -1646,6 +1874,37 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A function generator that is used to support both eager and lazy compilation
|
||||
* linking function.
|
||||
* @param eager
|
||||
* @param $compileNodes
|
||||
* @param transcludeFn
|
||||
* @param maxPriority
|
||||
* @param ignoreDirective
|
||||
* @param previousCompileContext
|
||||
* @returns {Function}
|
||||
*/
|
||||
function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
|
||||
if (eager) {
|
||||
return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
}
|
||||
|
||||
var compiled;
|
||||
|
||||
return function() {
|
||||
if (!compiled) {
|
||||
compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
|
||||
// Null out all of these references in order to make them eligible for garbage collection
|
||||
// since this is a potentially long lived closure
|
||||
$compileNodes = transcludeFn = previousCompileContext = null;
|
||||
}
|
||||
|
||||
return compiled.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the directives have been collected, their compile functions are executed. This method
|
||||
* is responsible for inlining directive templates as well as terminating the application
|
||||
@@ -1690,6 +1949,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
replaceDirective = originalReplaceDirective,
|
||||
childTranscludeFn = transcludeFn,
|
||||
linkFn,
|
||||
didScanForMultipleTransclusion = false,
|
||||
mightHaveMultipleTransclusionError = false,
|
||||
directiveValue;
|
||||
|
||||
// executes all directives on the current element
|
||||
@@ -1732,6 +1993,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
directiveName = directive.name;
|
||||
|
||||
// If we encounter a condition that can result in transclusion on the directive,
|
||||
// then scan ahead in the remaining directives for others that may cause a multiple
|
||||
// transclusion error to be thrown during the compilation process. If a matching directive
|
||||
// is found, then we know that when we encounter a transcluded directive, we need to eagerly
|
||||
// compile the `transclude` function rather than doing it lazily in order to throw
|
||||
// exceptions at the correct time
|
||||
if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
|
||||
|| (directive.transclude && !directive.$$tlb))) {
|
||||
var candidateDirective;
|
||||
|
||||
for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) {
|
||||
if ((candidateDirective.transclude && !candidateDirective.$$tlb)
|
||||
|| (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
|
||||
mightHaveMultipleTransclusionError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
didScanForMultipleTransclusion = true;
|
||||
}
|
||||
|
||||
if (!directive.templateUrl && directive.controller) {
|
||||
directiveValue = directive.controller;
|
||||
controllerDirectives = controllerDirectives || createMap();
|
||||
@@ -1761,7 +2043,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compileNode = $compileNode[0];
|
||||
replaceWith(jqCollection, sliceArgs($template), compileNode);
|
||||
|
||||
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
|
||||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
|
||||
replaceDirective && replaceDirective.name, {
|
||||
// Don't pass in:
|
||||
// - controllerDirectives - otherwise we'll create duplicates controllers
|
||||
@@ -1773,9 +2055,69 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
nonTlbTranscludeDirective: nonTlbTranscludeDirective
|
||||
});
|
||||
} else {
|
||||
|
||||
var slots = createMap();
|
||||
|
||||
$template = jqLite(jqLiteClone(compileNode)).contents();
|
||||
|
||||
if (isObject(directiveValue)) {
|
||||
|
||||
// We have transclusion slots,
|
||||
// collect them up, compile them and store their transclusion functions
|
||||
$template = [];
|
||||
|
||||
var slotMap = createMap();
|
||||
var filledSlots = createMap();
|
||||
|
||||
// Parse the element selectors
|
||||
forEach(directiveValue, function(elementSelector, slotName) {
|
||||
// If an element selector starts with a ? then it is optional
|
||||
var optional = (elementSelector.charAt(0) === '?');
|
||||
elementSelector = optional ? elementSelector.substring(1) : elementSelector;
|
||||
|
||||
slotMap[elementSelector] = slotName;
|
||||
|
||||
// We explicitly assign `null` since this implies that a slot was defined but not filled.
|
||||
// Later when calling boundTransclusion functions with a slot name we only error if the
|
||||
// slot is `undefined`
|
||||
slots[slotName] = null;
|
||||
|
||||
// filledSlots contains `true` for all slots that are either optional or have been
|
||||
// filled. This is used to check that we have not missed any required slots
|
||||
filledSlots[slotName] = optional;
|
||||
});
|
||||
|
||||
// Add the matching elements into their slot
|
||||
forEach($compileNode.contents(), function(node) {
|
||||
var slotName = slotMap[directiveNormalize(nodeName_(node))];
|
||||
if (slotName) {
|
||||
filledSlots[slotName] = true;
|
||||
slots[slotName] = slots[slotName] || [];
|
||||
slots[slotName].push(node);
|
||||
} else {
|
||||
$template.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for required slots that were not filled
|
||||
forEach(filledSlots, function(filled, slotName) {
|
||||
if (!filled) {
|
||||
throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
|
||||
}
|
||||
});
|
||||
|
||||
for (var slotName in slots) {
|
||||
if (slots[slotName]) {
|
||||
// Only define a transclusion function if the slot was filled
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compileNode.empty(); // clear contents
|
||||
childTranscludeFn = compile($template, transcludeFn);
|
||||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
|
||||
undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
|
||||
childTranscludeFn.$$slots = slots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1817,8 +2159,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
|
||||
var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
markDirectivesAsIsolate(templateDirectives);
|
||||
if (newIsolateScopeDirective || newScopeDirective) {
|
||||
// The original directive caused the current element to be replaced but this element
|
||||
// also needs to have a new scope, so we need to tell the template directives
|
||||
// that they would need to get their scope from further up, if they require transclusion
|
||||
markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
|
||||
}
|
||||
directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
|
||||
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
|
||||
@@ -1971,10 +2316,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
|
||||
thisLinkFn) {
|
||||
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
|
||||
attrs;
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
||||
var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
|
||||
attrs, removeScopeBindingWatches, removeControllerBindingWatches;
|
||||
|
||||
if (compileNode === linkNode) {
|
||||
attrs = templateAttrs;
|
||||
@@ -1984,8 +2328,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
attrs = new Attributes($element, templateAttrs);
|
||||
}
|
||||
|
||||
controllerScope = scope;
|
||||
if (newIsolateScopeDirective) {
|
||||
isolateScope = scope.$new(true);
|
||||
} else if (newScopeDirective) {
|
||||
controllerScope = scope.$parent;
|
||||
}
|
||||
|
||||
if (boundTranscludeFn) {
|
||||
@@ -1993,6 +2340,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
|
||||
transcludeFn = controllersBoundTransclude;
|
||||
transcludeFn.$$boundTransclude = boundTranscludeFn;
|
||||
// expose the slots on the `$transclude` function
|
||||
transcludeFn.isSlotFilled = function(slotName) {
|
||||
return !!boundTranscludeFn.$$slots[slotName];
|
||||
};
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
@@ -2006,42 +2357,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile.$$addScopeClass($element, true);
|
||||
isolateScope.$$isolateBindings =
|
||||
newIsolateScopeDirective.$$isolateBindings;
|
||||
initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective, isolateScope);
|
||||
}
|
||||
if (elementControllers) {
|
||||
// Initialize bindToController bindings for new/isolate scopes
|
||||
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
|
||||
var bindings;
|
||||
var controllerForBindings;
|
||||
if (scopeDirective && elementControllers[scopeDirective.name]) {
|
||||
bindings = scopeDirective.$$bindings.bindToController;
|
||||
controller = elementControllers[scopeDirective.name];
|
||||
|
||||
if (controller && controller.identifier && bindings) {
|
||||
controllerForBindings = controller;
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controller.instance,
|
||||
bindings, scopeDirective);
|
||||
}
|
||||
removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective);
|
||||
if (removeScopeBindingWatches) {
|
||||
isolateScope.$on('$destroy', removeScopeBindingWatches);
|
||||
}
|
||||
for (i in elementControllers) {
|
||||
controller = elementControllers[i];
|
||||
var controllerResult = controller();
|
||||
}
|
||||
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers and update the element data
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + i + 'Controller', controllerResult);
|
||||
if (controller === controllerForBindings) {
|
||||
// Remove and re-install bindToController bindings
|
||||
thisLinkFn.$$destroyBindings();
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
|
||||
}
|
||||
}
|
||||
// Initialize bindToController bindings
|
||||
for (var name in elementControllers) {
|
||||
var controllerDirective = controllerDirectives[name];
|
||||
var controller = elementControllers[name];
|
||||
var bindings = controllerDirective.$$bindings.bindToController;
|
||||
|
||||
if (controller.identifier && bindings) {
|
||||
removeControllerBindingWatches =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
|
||||
var controllerResult = controller();
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
|
||||
removeControllerBindingWatches && removeControllerBindingWatches();
|
||||
removeControllerBindingWatches =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2080,11 +2423,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// This is the function that is injected as `$transclude`.
|
||||
// Note: all arguments are optional!
|
||||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
|
||||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
|
||||
var transcludeControllers;
|
||||
|
||||
// No scope passed in:
|
||||
if (!isScope(scope)) {
|
||||
slotName = futureParentElement;
|
||||
futureParentElement = cloneAttachFn;
|
||||
cloneAttachFn = scope;
|
||||
scope = undefined;
|
||||
@@ -2096,15 +2439,36 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!futureParentElement) {
|
||||
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
|
||||
}
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
if (slotName) {
|
||||
// slotTranscludeFn can be one of three things:
|
||||
// * a transclude function - a filled slot
|
||||
// * `null` - an optional slot that was not filled
|
||||
// * `undefined` - a slot that was not declared (i.e. invalid)
|
||||
var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
|
||||
if (slotTranscludeFn) {
|
||||
return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
} else if (isUndefined(slotTranscludeFn)) {
|
||||
throw $compileMinErr('noslot',
|
||||
'No parent directive that requires a transclusion with slot name "{0}". ' +
|
||||
'Element: {1}',
|
||||
slotName, startingTag($element));
|
||||
}
|
||||
} else {
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markDirectivesAsIsolate(directives) {
|
||||
// mark all directives as needing isolate scope.
|
||||
// Depending upon the context in which a directive finds itself it might need to have a new isolated
|
||||
// or child scope created. For instance:
|
||||
// * if the directive has been pulled into a template because another directive with a higher priority
|
||||
// asked for element transclusion
|
||||
// * if the directive itself asks for transclusion but it is at the root of a template and the original
|
||||
// element was replaced. See https://github.com/angular/angular.js/issues/12936
|
||||
function markDirectiveScope(directives, isolateScope, newScope) {
|
||||
for (var j = 0, jj = directives.length; j < jj; j++) {
|
||||
directives[j] = inherit(directives[j], {$$isolateScope: true});
|
||||
directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2251,7 +2615,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
|
||||
|
||||
if (isObject(origAsyncDirective.scope)) {
|
||||
markDirectivesAsIsolate(templateDirectives);
|
||||
// the original directive that caused the template to be loaded async required
|
||||
// an isolate scope
|
||||
markDirectiveScope(templateDirectives, true);
|
||||
}
|
||||
directives = templateDirectives.concat(directives);
|
||||
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
|
||||
@@ -2300,7 +2666,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
|
||||
childBoundTranscludeFn, afterTemplateNodeLinkFn);
|
||||
childBoundTranscludeFn);
|
||||
}
|
||||
linkQueue = null;
|
||||
});
|
||||
@@ -2317,8 +2683,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
|
||||
afterTemplateNodeLinkFn);
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2427,7 +2792,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile: function() {
|
||||
return {
|
||||
pre: function attrInterpolatePreLinkFn(scope, element, attr) {
|
||||
var $$observers = (attr.$$observers || (attr.$$observers = {}));
|
||||
var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
|
||||
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
||||
throw $compileMinErr('nodomevents',
|
||||
@@ -2522,41 +2887,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
parent.replaceChild(newNode, firstElementToRemove);
|
||||
}
|
||||
|
||||
// TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
|
||||
// Append all the `elementsToRemove` to a fragment. This will...
|
||||
// - remove them from the DOM
|
||||
// - allow them to still be traversed with .nextSibling
|
||||
// - allow a single fragment.qSA to fetch all elements being removed
|
||||
var fragment = document.createDocumentFragment();
|
||||
fragment.appendChild(firstElementToRemove);
|
||||
for (i = 0; i < removeCount; i++) {
|
||||
fragment.appendChild(elementsToRemove[i]);
|
||||
}
|
||||
|
||||
if (jqLite.hasData(firstElementToRemove)) {
|
||||
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
|
||||
// data here because there's no public interface in jQuery to do that and copying over
|
||||
// event listeners (which is the main use of private data) wouldn't work anyway.
|
||||
jqLite(newNode).data(jqLite(firstElementToRemove).data());
|
||||
jqLite.data(newNode, jqLite.data(firstElementToRemove));
|
||||
|
||||
// Remove data of the replaced element. We cannot just call .remove()
|
||||
// on the element it since that would deallocate scope that is needed
|
||||
// for the new node. Instead, remove the data "manually".
|
||||
if (!jQuery) {
|
||||
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
|
||||
} else {
|
||||
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
|
||||
// the replaced element. The cleanData version monkey-patched by Angular would cause
|
||||
// the scope to be trashed and we do need the very same scope to work with the new
|
||||
// element. However, we cannot just cache the non-patched version and use it here as
|
||||
// that would break if another library patches the method after Angular does (one
|
||||
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
|
||||
// skipped this one time.
|
||||
skipDestroyOnNextJQueryCleanData = true;
|
||||
jQuery.cleanData([firstElementToRemove]);
|
||||
}
|
||||
// Remove $destroy event listeners from `firstElementToRemove`
|
||||
jqLite(firstElementToRemove).off('$destroy');
|
||||
}
|
||||
|
||||
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
|
||||
var element = elementsToRemove[k];
|
||||
jqLite(element).remove(); // must do this way to clean up expando
|
||||
fragment.appendChild(element);
|
||||
delete elementsToRemove[k];
|
||||
}
|
||||
// Cleanup any data/listeners on the elements and children.
|
||||
// This includes invoking the $destroy event on any elements with listeners.
|
||||
jqLite.cleanData(fragment.querySelectorAll('*'));
|
||||
|
||||
// Update the jqLite collection to only contain the `newNode`
|
||||
for (i = 1; i < removeCount; i++) {
|
||||
delete elementsToRemove[i];
|
||||
}
|
||||
elementsToRemove[0] = newNode;
|
||||
elementsToRemove.length = 1;
|
||||
}
|
||||
@@ -2578,9 +2935,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// Set up $watches for isolate scope and controller bindings. This process
|
||||
// only occurs for isolate scopes and new scopes with controllerAs.
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings,
|
||||
directive, newScope) {
|
||||
var onNewScopeDestroyed;
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
|
||||
var removeWatchCollection = [];
|
||||
forEach(bindings, function(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
@@ -2642,14 +2998,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return lastValue = parentValue;
|
||||
};
|
||||
parentValueWatch.$stateful = true;
|
||||
var unwatch;
|
||||
var removeWatch;
|
||||
if (definition.collection) {
|
||||
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
} else {
|
||||
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
}
|
||||
onNewScopeDestroyed = (onNewScopeDestroyed || []);
|
||||
onNewScopeDestroyed.push(unwatch);
|
||||
removeWatchCollection.push(removeWatch);
|
||||
break;
|
||||
|
||||
case '&':
|
||||
@@ -2665,16 +3020,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
|
||||
for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
|
||||
onNewScopeDestroyed[i]();
|
||||
|
||||
return removeWatchCollection.length && function removeWatches() {
|
||||
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
|
||||
removeWatchCollection[i]();
|
||||
}
|
||||
} : noop;
|
||||
if (newScope && destroyBindings !== noop) {
|
||||
newScope.$on('$destroy', destroyBindings);
|
||||
return noop;
|
||||
}
|
||||
return destroyBindings;
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var $controllerMinErr = minErr('$controller');
|
||||
|
||||
|
||||
var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
|
||||
var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
|
||||
function identifierForController(controller, ident) {
|
||||
if (ident && isString(ident)) return ident;
|
||||
if (isString(controller)) {
|
||||
|
||||
+20
-43
@@ -163,20 +163,7 @@
|
||||
* {@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
|
||||
* <!-- See below for an example of ng-disabled being used correctly -->
|
||||
* <div ng-init="isDisabled = false">
|
||||
* <button disabled="{{isDisabled}}">Disabled</button>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* 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.
|
||||
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
@@ -211,15 +198,9 @@
|
||||
* Note that this directive should not be used together with {@link ngModel `ngModel`},
|
||||
* as this can lead to unexpected behavior.
|
||||
*
|
||||
* ### Why do we need `ngChecked`?
|
||||
* A special directive is necessary because we cannot use interpolation inside the `checked`
|
||||
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
|
||||
*
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as checked. (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 `ngChecked` directive solves this problem for the `checked` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
@@ -248,13 +229,12 @@
|
||||
* @priority 100
|
||||
*
|
||||
* @description
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as readonly. (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 `ngReadonly` directive solves this problem for the `readonly` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
* Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy.
|
||||
*
|
||||
* A special directive is necessary because we cannot use interpolation inside the `readOnly`
|
||||
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
@@ -283,13 +263,11 @@
|
||||
* @priority 100
|
||||
*
|
||||
* @description
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as selected. (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 `ngSelected` directive solves this problem for the `selected` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
* Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
|
||||
*
|
||||
* A special directive is necessary because we cannot use interpolation inside the `selected`
|
||||
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
@@ -321,13 +299,12 @@
|
||||
* @priority 100
|
||||
*
|
||||
* @description
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as open. (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 `ngOpen` directive solves this problem for the `open` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
* Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
|
||||
*
|
||||
* A special directive is necessary because we cannot use interpolation inside the `open`
|
||||
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
|
||||
@@ -127,7 +127,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
*
|
||||
* However, if the method is used programmatically, for example by adding dynamically created controls,
|
||||
* or controls that have been previously removed without destroying their corresponding DOM element,
|
||||
* it's the developers responsiblity to make sure the current state propagates to the parent form.
|
||||
* it's the developers responsibility to make sure the current state propagates to the parent form.
|
||||
*
|
||||
* For example, if an input control is added that is already `$dirty` and has `$error` properties,
|
||||
* calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
|
||||
@@ -337,13 +337,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
*
|
||||
* In Angular, forms can be nested. This means that the outer form is valid when all of the child
|
||||
* forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
|
||||
* Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
|
||||
* `<form>` but can be nested. This allows you to have nested forms, which is very useful when
|
||||
* using Angular validation directives in forms that are dynamically generated using the
|
||||
* {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
|
||||
* attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
|
||||
* `ngForm` directive and nest these in an outer `form` element.
|
||||
*
|
||||
* Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to
|
||||
* `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group
|
||||
* of controls needs to be determined.
|
||||
*
|
||||
* # CSS classes
|
||||
* - `ng-valid` is set if the form is valid.
|
||||
|
||||
+27
-19
@@ -11,7 +11,19 @@
|
||||
|
||||
// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
|
||||
var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
|
||||
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
||||
// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
|
||||
// Note: We are being more lenient, because browsers are too.
|
||||
// 1. Scheme
|
||||
// 2. Slashes
|
||||
// 3. Username
|
||||
// 4. Password
|
||||
// 5. Hostname
|
||||
// 6. Port
|
||||
// 7. Path
|
||||
// 8. Query
|
||||
// 9. Fragment
|
||||
// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999
|
||||
var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
||||
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
|
||||
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
|
||||
@@ -44,8 +56,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -332,7 +344,7 @@ var inputType = {
|
||||
*
|
||||
* @description
|
||||
* Input with time validation and transformation. In browsers that do not yet support
|
||||
* the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
||||
* the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
||||
* local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
|
||||
* Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
|
||||
*
|
||||
@@ -679,8 +691,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -777,8 +789,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -876,8 +888,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -1337,11 +1349,7 @@ function badInputChecker(scope, element, attr, ctrl) {
|
||||
if (nativeValidation) {
|
||||
ctrl.$parsers.push(function(value) {
|
||||
var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
|
||||
// Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
|
||||
// - also sets validity.badInput (should only be validity.typeMismatch).
|
||||
// - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
|
||||
// - can ignore this case as we can still read out the erroneous email...
|
||||
return validity.badInput && !validity.typeMismatch ? undefined : value;
|
||||
return validity.badInput || validity.typeMismatch ? undefined : value;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1513,8 +1521,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
||||
* length.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -1552,8 +1560,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
||||
* length.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* value does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
*
|
||||
* * no-inline-style: this stops Angular from injecting CSS styles into the DOM
|
||||
*
|
||||
* * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
|
||||
* * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings
|
||||
*
|
||||
* You can use these values in the following combinations:
|
||||
*
|
||||
@@ -64,7 +64,7 @@
|
||||
* inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
|
||||
*
|
||||
* * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
|
||||
* run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
|
||||
* run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
|
||||
*
|
||||
* * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
|
||||
* styles nor use eval, which is the same as an empty: ng-csp.
|
||||
|
||||
@@ -34,7 +34,13 @@
|
||||
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
|
||||
* make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
|
||||
* @param {string=} onload Expression to evaluate when a new partial is loaded.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** When using onload on SVG elements in IE11, the browser will try to call
|
||||
* a function with the name on the window element, which will usually throw a
|
||||
* "function is undefined" error. To fix this, you can instead use `data-onload` or a
|
||||
* different form that {@link guide/directive#normalization matches} `onload`.
|
||||
* </div>
|
||||
*
|
||||
* @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
|
||||
* $anchorScroll} to scroll the viewport after the content is loaded.
|
||||
*
|
||||
@@ -226,6 +232,8 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
|
||||
//set the 2nd param to true to ignore the template request error so that the inner
|
||||
//contents and scope can be cleaned up.
|
||||
$templateRequest(src, true).then(function(response) {
|
||||
if (scope.$$destroyed) return;
|
||||
|
||||
if (thisChangeId !== changeCounter) return;
|
||||
var newScope = scope.$new();
|
||||
ctrl.template = response;
|
||||
@@ -247,6 +255,8 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
|
||||
currentScope.$emit('$includeContentLoaded', src);
|
||||
scope.$eval(onloadExp);
|
||||
}, function() {
|
||||
if (scope.$$destroyed) return;
|
||||
|
||||
if (thisChangeId === changeCounter) {
|
||||
cleanupLastIncludeContent();
|
||||
scope.$emit('$includeContentError', src);
|
||||
@@ -275,7 +285,7 @@ var ngIncludeFillContentDirective = ['$compile',
|
||||
priority: -400,
|
||||
require: 'ngInclude',
|
||||
link: function(scope, $element, $attr, ctrl) {
|
||||
if (/SVG/.test($element[0].toString())) {
|
||||
if (toString.call($element[0]).match(/SVG/)) {
|
||||
// WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
|
||||
// support innerHTML, so detect this here and try to generate the contents
|
||||
// specially.
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* ### Example - splitting on whitespace
|
||||
* ### Example - splitting on newline
|
||||
* <example name="ngList-directive-newlines">
|
||||
* <file name="index.html">
|
||||
* <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
|
||||
|
||||
+87
-38
@@ -14,7 +14,9 @@ var VALID_CLASS = 'ng-valid',
|
||||
DIRTY_CLASS = 'ng-dirty',
|
||||
UNTOUCHED_CLASS = 'ng-untouched',
|
||||
TOUCHED_CLASS = 'ng-touched',
|
||||
PENDING_CLASS = 'ng-pending';
|
||||
PENDING_CLASS = 'ng-pending',
|
||||
EMPTY_CLASS = 'ng-empty',
|
||||
NOT_EMPTY_CLASS = 'ng-not-empty';
|
||||
|
||||
var ngModelMinErr = minErr('ngModel');
|
||||
|
||||
@@ -22,7 +24,9 @@ var ngModelMinErr = minErr('ngModel');
|
||||
* @ngdoc type
|
||||
* @name ngModel.NgModelController
|
||||
*
|
||||
* @property {string} $viewValue Actual string value in the view.
|
||||
* @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
|
||||
* String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
|
||||
* is set.
|
||||
* @property {*} $modelValue The value in the model that the control is bound to.
|
||||
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
|
||||
the control reads value from the DOM. The functions are called in array order, each passing
|
||||
@@ -316,6 +320,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
return isUndefined(value) || value === '' || value === null || value !== value;
|
||||
};
|
||||
|
||||
this.$$updateEmptyClasses = function(value) {
|
||||
if (ctrl.$isEmpty(value)) {
|
||||
$animate.removeClass($element, NOT_EMPTY_CLASS);
|
||||
$animate.addClass($element, EMPTY_CLASS);
|
||||
} else {
|
||||
$animate.removeClass($element, EMPTY_CLASS);
|
||||
$animate.addClass($element, NOT_EMPTY_CLASS);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var currentValidationRunId = 0;
|
||||
|
||||
/**
|
||||
@@ -433,11 +448,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* which may be caused by a pending debounced event or because the input is waiting for a some
|
||||
* future event.
|
||||
*
|
||||
* If you have an input that uses `ng-model-options` to set up debounced events or events such
|
||||
* as blur you can have a situation where there is a period when the `$viewValue`
|
||||
* is out of synch with the ngModel's `$modelValue`.
|
||||
* If you have an input that uses `ng-model-options` to set up debounced updates or updates that
|
||||
* depend on special events such as blur, you can have a situation where there is a period when
|
||||
* the `$viewValue` is out of sync with the ngModel's `$modelValue`.
|
||||
*
|
||||
* In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
|
||||
* In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update
|
||||
* and reset the input to the last committed view value.
|
||||
*
|
||||
* It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue`
|
||||
* programmatically before these debounced/future events have resolved/occurred, because Angular's
|
||||
* dirty checking mechanism is not able to tell whether the model has actually changed or not.
|
||||
*
|
||||
@@ -450,39 +468,63 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* angular.module('cancel-update-example', [])
|
||||
*
|
||||
* .controller('CancelUpdateController', ['$scope', function($scope) {
|
||||
* $scope.resetWithCancel = function(e) {
|
||||
* $scope.model = {};
|
||||
*
|
||||
* $scope.setEmpty = function(e, value, rollback) {
|
||||
* if (e.keyCode == 27) {
|
||||
* $scope.myForm.myInput1.$rollbackViewValue();
|
||||
* $scope.myValue = '';
|
||||
* }
|
||||
* };
|
||||
* $scope.resetWithoutCancel = function(e) {
|
||||
* if (e.keyCode == 27) {
|
||||
* $scope.myValue = '';
|
||||
* e.preventDefault();
|
||||
* if (rollback) {
|
||||
* $scope.myForm[value].$rollbackViewValue();
|
||||
* }
|
||||
* $scope.model[value] = '';
|
||||
* }
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="CancelUpdateController">
|
||||
* <p>Try typing something in each input. See that the model only updates when you
|
||||
* blur off the input.
|
||||
* </p>
|
||||
* <p>Now see what happens if you start typing then press the Escape key</p>
|
||||
* <p>Both of these inputs are only updated if they are blurred. Hitting escape should
|
||||
* empty them. Follow these steps and observe the difference:</p>
|
||||
* <ol>
|
||||
* <li>Type something in the input. You will see that the model is not yet updated</li>
|
||||
* <li>Press the Escape key.
|
||||
* <ol>
|
||||
* <li> In the first example, nothing happens, because the model is already '', and no
|
||||
* update is detected. If you blur the input, the model will be set to the current view.
|
||||
* </li>
|
||||
* <li> In the second example, the pending update is cancelled, and the input is set back
|
||||
* to the last committed view value (''). Blurring the input does nothing.
|
||||
* </li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
|
||||
* <p id="inputDescription1">With $rollbackViewValue()</p>
|
||||
* <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
|
||||
* ng-keydown="resetWithCancel($event)"><br/>
|
||||
* myValue: "{{ myValue }}"
|
||||
* <div>
|
||||
* <p id="inputDescription1">Without $rollbackViewValue():</p>
|
||||
* <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1"
|
||||
* ng-keydown="setEmpty($event, 'value1')">
|
||||
* value1: "{{ model.value1 }}"
|
||||
* </div>
|
||||
*
|
||||
* <p id="inputDescription2">Without $rollbackViewValue()</p>
|
||||
* <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
|
||||
* ng-keydown="resetWithoutCancel($event)"><br/>
|
||||
* myValue: "{{ myValue }}"
|
||||
* <div>
|
||||
* <p id="inputDescription2">With $rollbackViewValue():</p>
|
||||
* <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2"
|
||||
* ng-keydown="setEmpty($event, 'value2', true)">
|
||||
* value2: "{{ model.value2 }}"
|
||||
* </div>
|
||||
* </form>
|
||||
* </div>
|
||||
* </file>
|
||||
<file name="style.css">
|
||||
div {
|
||||
display: table-cell;
|
||||
}
|
||||
div:nth-child(1) {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
</file>
|
||||
* </example>
|
||||
*/
|
||||
this.$rollbackViewValue = function() {
|
||||
@@ -652,6 +694,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
|
||||
return;
|
||||
}
|
||||
ctrl.$$updateEmptyClasses(viewValue);
|
||||
ctrl.$$lastCommittedViewValue = viewValue;
|
||||
|
||||
// change to dirty
|
||||
@@ -750,7 +793,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* However, custom controls might also pass objects to this method. In this case, we should make
|
||||
* a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
|
||||
* perform a deep watch of objects, it only looks for a change of identity. If you only change
|
||||
* the property of the object then ngModel will not realise that the object has changed and
|
||||
* the property of the object then ngModel will not realize that the object has changed and
|
||||
* will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
|
||||
* not change properties of the copy once it has been passed to `$setViewValue`.
|
||||
* Otherwise you may cause the model value on the scope to change incorrectly.
|
||||
@@ -834,6 +877,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
viewValue = formatters[idx](viewValue);
|
||||
}
|
||||
if (ctrl.$viewValue !== viewValue) {
|
||||
ctrl.$$updateEmptyClasses(viewValue);
|
||||
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
|
||||
ctrl.$render();
|
||||
|
||||
@@ -864,7 +908,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* require.
|
||||
* - Providing validation behavior (i.e. required, number, email, url).
|
||||
* - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
|
||||
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
|
||||
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
|
||||
* `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
|
||||
* - Registering the control with its parent {@link ng.directive:form form}.
|
||||
*
|
||||
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
|
||||
@@ -905,13 +950,16 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* - `ng-touched`: the control has been blurred
|
||||
* - `ng-untouched`: the control hasn't been blurred
|
||||
* - `ng-pending`: any `$asyncValidators` are unfulfilled
|
||||
* - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
|
||||
* by the {@link ngModel.NgModelController#$isEmpty} method
|
||||
* - `ng-not-empty`: the view contains a non-empty value
|
||||
*
|
||||
* Keep in mind that ngAnimate can detect each of these classes when added and removed.
|
||||
*
|
||||
* ## Animation Hooks
|
||||
*
|
||||
* Animations within models are triggered when any of the associated CSS classes are added and removed
|
||||
* on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
|
||||
* on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
|
||||
* `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
|
||||
* The animations that are triggered within ngModel are similar to how they work in ngClass and
|
||||
* animations can be hooked into using CSS transitions, keyframes as well as JS animations.
|
||||
@@ -1139,12 +1187,13 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
</label><br />
|
||||
</form>
|
||||
<pre>user.name = <span ng-bind="user.name"></span></pre>
|
||||
<pre>user.data = <span ng-bind="user.data"></span></pre>
|
||||
</div>
|
||||
</file>
|
||||
<file name="app.js">
|
||||
angular.module('optionsExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.user = { name: 'say', data: '' };
|
||||
$scope.user = { name: 'John', data: '' };
|
||||
|
||||
$scope.cancel = function(e) {
|
||||
if (e.keyCode == 27) {
|
||||
@@ -1159,20 +1208,20 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
var other = element(by.model('user.data'));
|
||||
|
||||
it('should allow custom events', function() {
|
||||
input.sendKeys(' hello');
|
||||
input.sendKeys(' Doe');
|
||||
input.click();
|
||||
expect(model.getText()).toEqual('say');
|
||||
expect(model.getText()).toEqual('John');
|
||||
other.click();
|
||||
expect(model.getText()).toEqual('say hello');
|
||||
expect(model.getText()).toEqual('John Doe');
|
||||
});
|
||||
|
||||
it('should $rollbackViewValue when model changes', function() {
|
||||
input.sendKeys(' hello');
|
||||
expect(input.getAttribute('value')).toEqual('say hello');
|
||||
input.sendKeys(' Doe');
|
||||
expect(input.getAttribute('value')).toEqual('John Doe');
|
||||
input.sendKeys(protractor.Key.ESCAPE);
|
||||
expect(input.getAttribute('value')).toEqual('say');
|
||||
expect(input.getAttribute('value')).toEqual('John');
|
||||
other.click();
|
||||
expect(model.getText()).toEqual('say');
|
||||
expect(model.getText()).toEqual('John');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -1198,7 +1247,7 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
<file name="app.js">
|
||||
angular.module('optionsExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.user = { name: 'say' };
|
||||
$scope.user = { name: 'Igor' };
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -33,19 +33,27 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
*
|
||||
* ## Complex Models (objects or collections)
|
||||
*
|
||||
* **Note:** By default, `ngModel` watches the model by reference, not value. This is important when
|
||||
* binding any input directive to a model that is an object or a collection.
|
||||
* By default, `ngModel` watches the model by reference, not value. This is important to know when
|
||||
* binding the select to a model that is an object or a collection.
|
||||
*
|
||||
* Since this is a common situation for `ngOptions` the directive additionally watches the model using
|
||||
* `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in
|
||||
* the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual
|
||||
* object/collection has not changed identity but only a property on the object or an item in the collection
|
||||
* changes.
|
||||
* One issue occurs if you want to preselect an option. For example, if you set
|
||||
* the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
|
||||
* because the objects are not identical. So by default, you should always reference the item in your collection
|
||||
* for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
|
||||
*
|
||||
* Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
|
||||
* of the item not by reference, but by the result of the `track by` expression. For example, if your
|
||||
* collection items have an id property, you would `track by item.id`.
|
||||
*
|
||||
* A different issue with objects or collections is that ngModel won't detect if an object property or
|
||||
* a collection item changes. For that reason, `ngOptions` additionally watches the model using
|
||||
* `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
|
||||
* This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
|
||||
* has not changed identity, but only a property on the object or an item in the collection changes.
|
||||
*
|
||||
* Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
|
||||
* if the model is an array). This means that changing a property deeper inside the object/collection that the
|
||||
* first level will not trigger a re-rendering.
|
||||
*
|
||||
* if the model is an array). This means that changing a property deeper than the first level inside the
|
||||
* object/collection will not trigger a re-rendering.
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
@@ -58,17 +66,13 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* ### `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.
|
||||
* Be careful when using `select` **`as`** and **`track by`** in the same expression.
|
||||
* </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"></select>
|
||||
* ```
|
||||
* Given this array of items on the $scope:
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* $scope.items = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
@@ -77,20 +81,33 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* 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:
|
||||
* This will work:
|
||||
*
|
||||
* 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.
|
||||
* ```html
|
||||
* <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
|
||||
* ```
|
||||
* ```js
|
||||
* $scope.selected = $scope.items[0];
|
||||
* ```
|
||||
*
|
||||
* but this will not work:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
|
||||
* ```
|
||||
* ```js
|
||||
* $scope.selected = $scope.items[0].subItem;
|
||||
* ```
|
||||
*
|
||||
* In both examples, the **`track by`** expression is applied successfully to each `item` in the
|
||||
* `items` array. Because the selected option has been set programmatically in the controller, the
|
||||
* **`track by`** expression is also applied to the `ngModel` value. In the first example, the
|
||||
* `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
|
||||
* no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
|
||||
* expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
|
||||
* is not matched against any `<option>` and the `<select>` appears as having no selected value.
|
||||
*
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
@@ -392,17 +409,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
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;
|
||||
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
||||
|
||||
var selectCtrl = ctrls[0];
|
||||
var ngModelCtrl = ctrls[1];
|
||||
var multiple = attr.multiple;
|
||||
|
||||
// The emptyOption allows the application developer to provide their own custom "empty"
|
||||
@@ -451,7 +461,6 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
unknownOption.remove();
|
||||
};
|
||||
|
||||
|
||||
// Update the controller methods for multiple selectable options
|
||||
if (!multiple) {
|
||||
|
||||
@@ -579,11 +588,16 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
function updateOptionElement(option, element) {
|
||||
option.element = element;
|
||||
element.disabled = option.disabled;
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
// NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
|
||||
// selects in certain circumstances when multiple selects are next to each other and display
|
||||
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
|
||||
// See https://github.com/angular/angular.js/issues/11314 for more info.
|
||||
// This is unfortunately untestable with unit / e2e tests
|
||||
if (option.label !== element.label) {
|
||||
element.label = option.label;
|
||||
element.textContent = option.label;
|
||||
}
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
}
|
||||
|
||||
function addOrReuseElement(parent, current, type, templateElement) {
|
||||
@@ -621,10 +635,15 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var emptyOption_ = emptyOption && emptyOption[0];
|
||||
var unknownOption_ = unknownOption && unknownOption[0];
|
||||
|
||||
// We cannot rely on the extracted empty option being the same as the compiled empty option,
|
||||
// because the compiled empty option might have been replaced by a comment because
|
||||
// it had an "element" transclusion directive on it (such as ngIf)
|
||||
if (emptyOption_ || unknownOption_) {
|
||||
while (current &&
|
||||
(current === emptyOption_ ||
|
||||
current === unknownOption_)) {
|
||||
current === unknownOption_ ||
|
||||
current.nodeType === NODE_TYPE_COMMENT ||
|
||||
(nodeName_(current) === 'option' && current.value === ''))) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
@@ -653,7 +672,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var groupElement;
|
||||
var optionElement;
|
||||
|
||||
if (option.group) {
|
||||
if (isDefined(option.group)) {
|
||||
|
||||
// This option is to live in a group
|
||||
// See if we have already created this group
|
||||
@@ -714,14 +733,28 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// Check to see if the value has changed due to the update to the options
|
||||
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
||||
var nextValue = selectCtrl.readValue();
|
||||
if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
|
||||
var isNotPrimitive = ngOptions.trackBy || multiple;
|
||||
if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
|
||||
ngModelCtrl.$setViewValue(nextValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
terminal: true,
|
||||
require: ['select', 'ngModel'],
|
||||
link: {
|
||||
pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
|
||||
// Deactivate the SelectController.register method to prevent
|
||||
// option directives from accidentally registering themselves
|
||||
// (and unwanted $destroy handlers etc.)
|
||||
ctrls[0].registerOption = noop;
|
||||
},
|
||||
post: ngOptionsPostLink
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -215,7 +215,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale,
|
||||
}
|
||||
|
||||
// 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.
|
||||
// In JS `NaN !== NaN`, so we have to explicitly check.
|
||||
if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
|
||||
watchRemover();
|
||||
var whenExpFn = whensExpFns[count];
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
* 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
|
||||
* and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
|
||||
*
|
||||
* 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
|
||||
@@ -53,15 +53,21 @@
|
||||
*
|
||||
* # Tracking and Duplicates
|
||||
*
|
||||
* When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
|
||||
* `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
|
||||
* the collection. When a change happens, ngRepeat then 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.
|
||||
* To minimize creation of DOM elements, `ngRepeat` uses a function
|
||||
* to "keep track" of all items in the collection and their corresponding DOM elements.
|
||||
* For example, if an item is added to the collection, ngRepeat will know that all other items
|
||||
* already have DOM elements, and will not re-render them.
|
||||
*
|
||||
* The default tracking function (which tracks items by their identity) 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.
|
||||
@@ -74,7 +80,7 @@
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* You may use arbitrary expressions in `track by`, including references to custom functions
|
||||
* You may also 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)">
|
||||
@@ -82,10 +88,14 @@
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If you are working with objects that have an identifier property, you can track
|
||||
* <div class="alert alert-success">
|
||||
* If you are working with objects that have an identifier property, you should 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:
|
||||
* JavaScript objects in the collection have been substituted for new ones. For large collections,
|
||||
* this significantly improves rendering performance. If you don't have a unique identifier,
|
||||
* `track by $index` can also provide a performance boost.
|
||||
* </div>
|
||||
* ```html
|
||||
* <div ng-repeat="model in collection track by model.id">
|
||||
* {{model.name}}
|
||||
|
||||
@@ -8,66 +8,186 @@
|
||||
* @description
|
||||
* Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
|
||||
*
|
||||
* Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
|
||||
* You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name
|
||||
* as the value of the `ng-transclude` or `ng-transclude-slot` attribute.
|
||||
*
|
||||
* If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing
|
||||
* content of this element will be removed before the transcluded content is inserted.
|
||||
* If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case
|
||||
* that no transcluded content is provided.
|
||||
*
|
||||
* @element ANY
|
||||
*
|
||||
* @example
|
||||
<example module="transcludeExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('transcludeExample', [])
|
||||
.directive('pane', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: { title:'@' },
|
||||
template: '<div style="border: 1px solid black;">' +
|
||||
'<div style="background-color: gray">{{title}}</div>' +
|
||||
'<ng-transclude></ng-transclude>' +
|
||||
'</div>'
|
||||
};
|
||||
})
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.title = 'Lorem Ipsum';
|
||||
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<input ng-model="title" aria-label="title"> <br/>
|
||||
<textarea ng-model="text" aria-label="text"></textarea> <br/>
|
||||
<pane title="{{title}}">{{text}}</pane>
|
||||
</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should have transcluded', function() {
|
||||
var titleElement = element(by.model('title'));
|
||||
titleElement.clear();
|
||||
titleElement.sendKeys('TITLE');
|
||||
var textElement = element(by.model('text'));
|
||||
textElement.clear();
|
||||
textElement.sendKeys('TEXT');
|
||||
expect(element(by.binding('title')).getText()).toEqual('TITLE');
|
||||
expect(element(by.binding('text')).getText()).toEqual('TEXT');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
* @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty
|
||||
* or its value is the same as the name of the attribute then the default slot is used.
|
||||
*
|
||||
* @example
|
||||
* ### Basic transclusion
|
||||
* This example demonstrates basic transclusion of content into a component directive.
|
||||
* <example name="simpleTranscludeExample" module="transcludeExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('transcludeExample', [])
|
||||
* .directive('pane', function(){
|
||||
* return {
|
||||
* restrict: 'E',
|
||||
* transclude: true,
|
||||
* scope: { title:'@' },
|
||||
* template: '<div style="border: 1px solid black;">' +
|
||||
* '<div style="background-color: gray">{{title}}</div>' +
|
||||
* '<ng-transclude></ng-transclude>' +
|
||||
* '</div>'
|
||||
* };
|
||||
* })
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.title = 'Lorem Ipsum';
|
||||
* $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
* }]);
|
||||
* </script>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input ng-model="title" aria-label="title"> <br/>
|
||||
* <textarea ng-model="text" aria-label="text"></textarea> <br/>
|
||||
* <pane title="{{title}}">{{text}}</pane>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should have transcluded', function() {
|
||||
* var titleElement = element(by.model('title'));
|
||||
* titleElement.clear();
|
||||
* titleElement.sendKeys('TITLE');
|
||||
* var textElement = element(by.model('text'));
|
||||
* textElement.clear();
|
||||
* textElement.sendKeys('TEXT');
|
||||
* expect(element(by.binding('title')).getText()).toEqual('TITLE');
|
||||
* expect(element(by.binding('text')).getText()).toEqual('TEXT');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### Transclude fallback content
|
||||
* This example shows how to use `NgTransclude` with fallback content, that
|
||||
* is displayed if no transcluded content is provided.
|
||||
*
|
||||
* <example module="transcludeFallbackContentExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('transcludeFallbackContentExample', [])
|
||||
* .directive('myButton', function(){
|
||||
* return {
|
||||
* restrict: 'E',
|
||||
* transclude: true,
|
||||
* scope: true,
|
||||
* template: '<button style="cursor: pointer;">' +
|
||||
* '<ng-transclude>' +
|
||||
* '<b style="color: red;">Button1</b>' +
|
||||
* '</ng-transclude>' +
|
||||
* '</button>'
|
||||
* };
|
||||
* });
|
||||
* </script>
|
||||
* <!-- fallback button content -->
|
||||
* <my-button id="fallback"></my-button>
|
||||
* <!-- modified button content -->
|
||||
* <my-button id="modified">
|
||||
* <i style="color: green;">Button2</i>
|
||||
* </my-button>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should have different transclude element content', function() {
|
||||
* expect(element(by.id('fallback')).getText()).toBe('Button1');
|
||||
* expect(element(by.id('modified')).getText()).toBe('Button2');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### Multi-slot transclusion
|
||||
* This example demonstrates using multi-slot transclusion in a component directive.
|
||||
* <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample">
|
||||
* <file name="index.html">
|
||||
* <style>
|
||||
* .title, .footer {
|
||||
* background-color: gray
|
||||
* }
|
||||
* </style>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input ng-model="title" aria-label="title"> <br/>
|
||||
* <textarea ng-model="text" aria-label="text"></textarea> <br/>
|
||||
* <pane>
|
||||
* <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
|
||||
* <pane-body><p>{{text}}</p></pane-body>
|
||||
* </pane>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('multiSlotTranscludeExample', [])
|
||||
* .directive('pane', function(){
|
||||
* return {
|
||||
* restrict: 'E',
|
||||
* transclude: {
|
||||
* 'title': '?paneTitle',
|
||||
* 'body': 'paneBody',
|
||||
* 'footer': '?paneFooter'
|
||||
* },
|
||||
* template: '<div style="border: 1px solid black;">' +
|
||||
* '<div class="title" ng-transclude="title">Fallback Title</div>' +
|
||||
* '<div ng-transclude="body"></div>' +
|
||||
* '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
|
||||
* '</div>'
|
||||
* };
|
||||
* })
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.title = 'Lorem Ipsum';
|
||||
* $scope.link = "https://google.com";
|
||||
* $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should have transcluded the title and the body', function() {
|
||||
* var titleElement = element(by.model('title'));
|
||||
* titleElement.clear();
|
||||
* titleElement.sendKeys('TITLE');
|
||||
* var textElement = element(by.model('text'));
|
||||
* textElement.clear();
|
||||
* textElement.sendKeys('TEXT');
|
||||
* expect(element(by.css('.title')).getText()).toEqual('TITLE');
|
||||
* expect(element(by.binding('text')).getText()).toEqual('TEXT');
|
||||
* expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngTranscludeMinErr = minErr('ngTransclude');
|
||||
var ngTranscludeDirective = ngDirective({
|
||||
restrict: 'EAC',
|
||||
link: function($scope, $element, $attrs, controller, $transclude) {
|
||||
|
||||
if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
|
||||
// If the attribute is of the form: `ng-transclude="ng-transclude"`
|
||||
// then treat it like the default
|
||||
$attrs.ngTransclude = '';
|
||||
}
|
||||
|
||||
function ngTranscludeCloneAttachFn(clone) {
|
||||
if (clone.length) {
|
||||
$element.empty();
|
||||
$element.append(clone);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$transclude) {
|
||||
throw minErr('ngTransclude')('orphan',
|
||||
throw ngTranscludeMinErr('orphan',
|
||||
'Illegal use of ngTransclude directive in the template! ' +
|
||||
'No parent directive that requires a transclusion found. ' +
|
||||
'Element: {0}',
|
||||
startingTag($element));
|
||||
}
|
||||
|
||||
$transclude(function(clone) {
|
||||
$element.empty();
|
||||
$element.append(clone);
|
||||
});
|
||||
// If there is no slot name defined or the slot name is not optional
|
||||
// then transclude the slot
|
||||
var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
|
||||
$transclude(ngTranscludeCloneAttachFn, null, slotName);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+78
-62
@@ -2,6 +2,15 @@
|
||||
|
||||
var noopNgModelController = { $setViewValue: noop, $render: 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name select.SelectController
|
||||
@@ -77,6 +86,8 @@ var SelectController =
|
||||
}
|
||||
var count = optionsMap.get(value) || 0;
|
||||
optionsMap.put(value, count + 1);
|
||||
self.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
};
|
||||
|
||||
// Tell the select control that an option, with the given value, has been removed
|
||||
@@ -98,6 +109,39 @@ var SelectController =
|
||||
self.hasOption = function(value) {
|
||||
return !!optionsMap.get(value);
|
||||
};
|
||||
|
||||
|
||||
self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
|
||||
|
||||
if (interpolateValueFn) {
|
||||
// The value attribute is interpolated
|
||||
var oldVal;
|
||||
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
if (isDefined(oldVal)) {
|
||||
self.removeOption(oldVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
self.addOption(newVal, optionElement);
|
||||
});
|
||||
} else if (interpolateTextFn) {
|
||||
// The text content is interpolated
|
||||
optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
optionAttrs.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
self.removeOption(oldVal);
|
||||
}
|
||||
self.addOption(newVal, optionElement);
|
||||
});
|
||||
} else {
|
||||
// The value attribute is static
|
||||
self.addOption(optionAttrs.value, optionElement);
|
||||
}
|
||||
|
||||
optionElement.on('$destroy', function() {
|
||||
self.removeOption(optionAttrs.value);
|
||||
self.ngModelCtrl.$render();
|
||||
});
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
@@ -110,7 +154,7 @@ var SelectController =
|
||||
*
|
||||
* The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
|
||||
* between the scope and the `<select>` control (including setting default values).
|
||||
* Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
|
||||
* It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
|
||||
* {@link ngOptions `ngOptions`} directives.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the value of the selected option will be bound
|
||||
@@ -120,7 +164,7 @@ var SelectController =
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Note that the value of a `select` directive used without `ngOptions` is always a string.
|
||||
* When the model needs to be bound to a non-string value, you must either explictly convert it
|
||||
* When the model needs to be bound to a non-string value, you must either explicitly convert it
|
||||
* using a directive (see example below) or use `ngOptions` to specify the set of options.
|
||||
* This is because an option element can only be bound to string values at present.
|
||||
* </div>
|
||||
@@ -143,6 +187,8 @@ var SelectController =
|
||||
*
|
||||
* @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=} multiple Allows multiple options to be selected. The selected values will be
|
||||
* bound to the model as an array.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not 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
|
||||
@@ -308,7 +354,14 @@ var selectDirective = function() {
|
||||
restrict: 'E',
|
||||
require: ['select', '?ngModel'],
|
||||
controller: SelectController,
|
||||
link: function(scope, element, attr, ctrls) {
|
||||
priority: 1,
|
||||
link: {
|
||||
pre: selectPreLink,
|
||||
post: selectPostLink
|
||||
}
|
||||
};
|
||||
|
||||
function selectPreLink(scope, element, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
@@ -318,13 +371,6 @@ var selectDirective = function() {
|
||||
|
||||
selectCtrl.ngModelCtrl = ngModelCtrl;
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
// 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`
|
||||
@@ -378,7 +424,23 @@ var selectDirective = function() {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function selectPostLink(scope, element, attrs, ctrls) {
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
if (!ngModelCtrl) return;
|
||||
|
||||
var selectCtrl = ctrls[0];
|
||||
|
||||
// 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`.
|
||||
// This must be done in the postLink fn to prevent $render to be called before
|
||||
// all nodes have been linked correctly.
|
||||
ngModelCtrl.$render = function() {
|
||||
selectCtrl.writeValue(ngModelCtrl.$viewValue);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -386,16 +448,6 @@ var selectDirective = function() {
|
||||
// of dynamically created (and destroyed) option elements to their containing select
|
||||
// directive via its controller.
|
||||
var optionDirective = ['$interpolate', function($interpolate) {
|
||||
|
||||
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,
|
||||
@@ -403,12 +455,12 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
|
||||
if (isDefined(attr.value)) {
|
||||
// If the value attribute is defined, check if it contains an interpolation
|
||||
var valueInterpolated = $interpolate(attr.value, true);
|
||||
var interpolateValueFn = $interpolate(attr.value, true);
|
||||
} else {
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (!interpolateFn) {
|
||||
var interpolateTextFn = $interpolate(element.text(), true);
|
||||
if (!interpolateTextFn) {
|
||||
attr.$set('value', element.text());
|
||||
}
|
||||
}
|
||||
@@ -422,44 +474,8 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
function addOption(optionValue) {
|
||||
selectCtrl.addOption(optionValue, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
}
|
||||
|
||||
// Only update trigger option updates if this is an option within a `select`
|
||||
// that also has `ngModel` attached
|
||||
if (selectCtrl && selectCtrl.ngModelCtrl) {
|
||||
|
||||
if (valueInterpolated) {
|
||||
// The value attribute is interpolated
|
||||
var oldVal;
|
||||
attr.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
if (isDefined(oldVal)) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
addOption(newVal);
|
||||
});
|
||||
} else if (interpolateFn) {
|
||||
// The text content is interpolated
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
addOption(newVal);
|
||||
});
|
||||
} else {
|
||||
// The value attribute is static
|
||||
addOption(attr.value);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
});
|
||||
if (selectCtrl) {
|
||||
selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user