Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3efdeebcb7 | |||
| 16febf8357 | |||
| 0f7c4ca671 | |||
| a3172a285f | |||
| 84c408ce63 | |||
| 40647b179c | |||
| 0421cb4200 | |||
| 6f1050df4f | |||
| 4d16472b91 | |||
| 9e89a31b12 | |||
| e6521e7491 | |||
| 0a7cbb33b0 | |||
| 579242346c | |||
| c42d0a0418 | |||
| 3fbb25e25c | |||
| 6760d7a315 | |||
| 062fbed8fc | |||
| 76e4db6f3d | |||
| 0cd7e8f227 | |||
| ba1b47f85b | |||
| 0a3481e23a | |||
| e33c365144 | |||
| e3ceb50b73 | |||
| 6b5772bbbd | |||
| 6288cf5ca4 | |||
| f6ecf9a3c9 | |||
| a4e6d962d7 | |||
| 7874a4d007 | |||
| 1d50663b38 | |||
| ec3c4f94c7 | |||
| 6b8bbe4d90 | |||
| 7067a8fb0b | |||
| c47abd0dd7 | |||
| 68d71bbc01 | |||
| 334303e485 | |||
| b95fd53c6e | |||
| c77dd040b4 | |||
| dc027f22e5 | |||
| 043500fb27 | |||
| 3ceb6ab477 | |||
| 999fa44616 | |||
| 5f9121ad56 | |||
| b4cf8483d7 | |||
| 8a9816e06b | |||
| c0e10683a6 | |||
| cad5a367c3 | |||
| f2453eabb3 | |||
| aa0b11d794 | |||
| b9fa5c5a67 | |||
| 751f058f30 | |||
| 29274e1d8d | |||
| 23ba287897 | |||
| 8f1e3606dd | |||
| ac56d1c9d9 | |||
| de2919cb9a | |||
| 61943276f0 | |||
| 88ce00a3cf | |||
| a3eb6baf58 | |||
| f4fcaa8757 | |||
| 40e34a924b | |||
| 90f87072e8 | |||
| c785918cbd | |||
| 8425e9fe38 | |||
| 94764ee089 | |||
| 04492ef227 | |||
| c6016a6a85 | |||
| 977e2f55de | |||
| 4184ff8ff7 | |||
| f3d4fe6209 | |||
| 9e5cd92fa9 | |||
| c07f1e1c9f | |||
| fd7bca22e1 | |||
| 8f283fe473 | |||
| cb8061c75c | |||
| b1366c32d4 | |||
| b122194425 | |||
| 089bf5f0e3 | |||
| f6fa7c9c95 | |||
| 938b2e1217 | |||
| dbc6696b68 | |||
| 5d632af926 | |||
| ed9e570a12 | |||
| d7ed885984 | |||
| 4ab16aaaf7 | |||
| 89f435de84 | |||
| bcc6e8d4f6 | |||
| b2137c9fdf | |||
| bee56a82b0 | |||
| bcdbfdfeae | |||
| afbed10feb | |||
| f69ee170ed | |||
| a59976be18 | |||
| 40d1e10520 | |||
| 5bf81bc111 | |||
| 96ad0c7594 | |||
| 717a6705e2 | |||
| 37ac4724ba | |||
| 8c18ef67cf | |||
| dfe6400537 | |||
| f925e8caa6 | |||
| e1254b266d | |||
| fa82a31fa6 | |||
| 4612705ec2 | |||
| 9577702e8d | |||
| a61b65d01b | |||
| fb483d56a7 | |||
| c5c75386e4 | |||
| 2734b9f560 | |||
| dcdbcaf2b5 | |||
| 9a8179d311 | |||
| 44fe7b6dbb | |||
| 95102a5afe | |||
| ae2cdeb2de | |||
| 6a0aff84c4 | |||
| e4181182dd |
+368
@@ -1,3 +1,371 @@
|
||||
<a name="1.2.2"></a>
|
||||
# 1.2.2 consciousness-inertia (2013-11-22)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- ensure keyframe animations are blocked around the reflow
|
||||
([6760d7a3](https://github.com/angular/angular.js/commit/6760d7a315d7ea5cbd4f8ab74b200f754a2041f4),
|
||||
[#5018](https://github.com/angular/angular.js/issues/5018))
|
||||
- ensure transition animations are unblocked before the dom operation occurs
|
||||
([062fbed8](https://github.com/angular/angular.js/commit/062fbed8fc3f7bc55433f8c6915c27520e6f63c5),
|
||||
[#5014](https://github.com/angular/angular.js/issues/5014),
|
||||
[#4265](https://github.com/angular/angular.js/issues/4265))
|
||||
- ensure addClass/removeClass animations do not snap during reflow
|
||||
([76e4db6f](https://github.com/angular/angular.js/commit/76e4db6f3d15199ac1fbe85f9cfa6079a1c4fa56),
|
||||
[#4892](https://github.com/angular/angular.js/issues/4892))
|
||||
- ensure the DOM operation isn't run twice
|
||||
([7067a8fb](https://github.com/angular/angular.js/commit/7067a8fb0b18d5b5489006e1960cee721a88b4d2),
|
||||
[#4949](https://github.com/angular/angular.js/issues/4949))
|
||||
- **$compile:**
|
||||
- secure form[action] & iframe[srcdoc]
|
||||
([0421cb42](https://github.com/angular/angular.js/commit/0421cb4200e672818ed10996e92311404c150c3a),
|
||||
[#4927](https://github.com/angular/angular.js/issues/4927),
|
||||
[#4933](https://github.com/angular/angular.js/issues/4933))
|
||||
- ensure CSS classes are added and removed only when necessary
|
||||
([0cd7e8f2](https://github.com/angular/angular.js/commit/0cd7e8f22721f62b62440bb059ae764ebbe7b42a))
|
||||
- **$httpBackend:** only IE8 and below can't use `script.onload` for JSONP
|
||||
([a3172a28](https://github.com/angular/angular.js/commit/a3172a285fd74b5aa6c8d68a4988c767c06f549c),
|
||||
[#4523](https://github.com/angular/angular.js/issues/4523),
|
||||
[#4527](https://github.com/angular/angular.js/issues/4527),
|
||||
[#4922](https://github.com/angular/angular.js/issues/4922))
|
||||
- **$parse:** allow for new lines in expr when promise unwrapping is on
|
||||
([40647b17](https://github.com/angular/angular.js/commit/40647b179c473f3f470bb1b3237d6f006269582f),
|
||||
[#4718](https://github.com/angular/angular.js/issues/4718))
|
||||
- **$resource:** Always return a resource instance when calling class methods on resources.
|
||||
([f6ecf9a3](https://github.com/angular/angular.js/commit/f6ecf9a3c9090593faf5fa50586c99a56b51c776),
|
||||
[#4545](https://github.com/angular/angular.js/issues/4545),
|
||||
[#5061](https://github.com/angular/angular.js/issues/5061))
|
||||
- **httpBackend:** should not read response data when request is aborted
|
||||
([6f1050df](https://github.com/angular/angular.js/commit/6f1050df4fa885bd59ce85adbef7350ea93911a3),
|
||||
[#4913](https://github.com/angular/angular.js/issues/4913),
|
||||
[#4940](https://github.com/angular/angular.js/issues/4940))
|
||||
- **loader:** expose `$$minErr` to modules such as`ngResource`
|
||||
([9e89a31b](https://github.com/angular/angular.js/commit/9e89a31b129e40c805178535c244899ffafb77d8),
|
||||
[#5050](https://github.com/angular/angular.js/issues/5050))
|
||||
- **ngAnimate:**
|
||||
- correctly retain and restore existing styles during and after animation
|
||||
([c42d0a04](https://github.com/angular/angular.js/commit/c42d0a041890b39fc98afd357ec1307a3a36208d),
|
||||
[#4869](https://github.com/angular/angular.js/issues/4869))
|
||||
- use a fallback CSS property that doesn't break existing styles
|
||||
([1d50663b](https://github.com/angular/angular.js/commit/1d50663b38ba042e8d748ffa6d48cfb5e93cfd7e),
|
||||
[#4902](https://github.com/angular/angular.js/issues/4902),
|
||||
[#5030](https://github.com/angular/angular.js/issues/5030))
|
||||
- **ngClass:** ensure that ngClass only adds/removes the changed classes
|
||||
([6b8bbe4d](https://github.com/angular/angular.js/commit/6b8bbe4d90640542eed5607a8c91f6b977b1d6c0),
|
||||
[#4960](https://github.com/angular/angular.js/issues/4960),
|
||||
[#4944](https://github.com/angular/angular.js/issues/4944))
|
||||
- **ngController:** fix issue with ngInclude on the same element
|
||||
([6288cf5c](https://github.com/angular/angular.js/commit/6288cf5ca471b0615a026fdb4db3ba242c9d8f88),
|
||||
[#4431](https://github.com/angular/angular.js/issues/4431))
|
||||
- **ngInclude:**
|
||||
- Don't throw when the ngInclude element contains content with directives.
|
||||
([0a7cbb33](https://github.com/angular/angular.js/commit/0a7cbb33b06778833a4d99b1868cc07690a827a7))
|
||||
- allow ngInclude to load scripts when jQuery is included
|
||||
([c47abd0d](https://github.com/angular/angular.js/commit/c47abd0dd7490576f4b84ee51ebaca385c1036da),
|
||||
[#3756](https://github.com/angular/angular.js/issues/3756))
|
||||
- **ngMock:** fixes httpBackend expectation with body object
|
||||
([4d16472b](https://github.com/angular/angular.js/commit/4d16472b918a3482942d76f1e273a5aa01f65e83),
|
||||
[#4956](https://github.com/angular/angular.js/issues/4956))
|
||||
- **ngView:** Don't throw when the ngView element contains content with directives.
|
||||
([e6521e74](https://github.com/angular/angular.js/commit/e6521e7491242504250b57dd0ee66af49e653c33),
|
||||
[#5069](https://github.com/angular/angular.js/issues/5069))
|
||||
- **tests:** Correct tests for IE11
|
||||
([57924234](https://github.com/angular/angular.js/commit/579242346c4202ea58fc2cae6df232289cbea0bb),
|
||||
[#5046](https://github.com/angular/angular.js/issues/5046))
|
||||
- **input:** hold listener during text composition
|
||||
([a4e6d962](https://github.com/angular/angular.js/commit/a4e6d962d78b26f5112d48c4f88c1e6234d0cae7),
|
||||
[#4684](https://github.com/angular/angular.js/issues/4684))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.2.1"></a>
|
||||
# 1.2.1 underscore-empathy (2013-11-14)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- accessing controllers of transcluded directives from children
|
||||
([90f87072](https://github.com/angular/angular.js/commit/90f87072e83234ae366cfeb3c281503c31dad738),
|
||||
[#4935](https://github.com/angular/angular.js/issues/4935))
|
||||
- correctly handle interpolated style in replace templates
|
||||
([e1254b26](https://github.com/angular/angular.js/commit/e1254b266dfa2d4e3756e4317152dbdbcabe44be),
|
||||
[#4882](https://github.com/angular/angular.js/issues/4882))
|
||||
- **$resource:** don't use $parse for @dotted.member
|
||||
([9577702e](https://github.com/angular/angular.js/commit/9577702e8d2519c1a60f5ac4058e63bd7b919815))
|
||||
- **bootstrap:** make IE8 happy
|
||||
([a61b65d0](https://github.com/angular/angular.js/commit/a61b65d01b468502fe53d68818949d3fcc9f20f6))
|
||||
- **loader:** don't rely on internal APIs
|
||||
([8425e9fe](https://github.com/angular/angular.js/commit/8425e9fe383c17f6a5589c778658c5fc0570ae8f),
|
||||
[#4437](https://github.com/angular/angular.js/issues/4437), [#4874](https://github.com/angular/angular.js/issues/4874))
|
||||
- **minErr:** remove references to internal APIs
|
||||
([94764ee0](https://github.com/angular/angular.js/commit/94764ee08910726db1db7a1101c3001500306dea))
|
||||
- **ngIf:** don't create multiple elements when changing from a truthy value to another thruthy value
|
||||
([4612705e](https://github.com/angular/angular.js/commit/4612705ec297bc6ba714cb7a98f1be6aff77c4b8),
|
||||
[#4852](https://github.com/angular/angular.js/issues/4852))
|
||||
- **urlUtils:**
|
||||
- make removal of windows drive from path safer
|
||||
([89f435de](https://github.com/angular/angular.js/commit/89f435de847635e3ec339726e6f83cf3f0ee9091),
|
||||
[#4939](https://github.com/angular/angular.js/issues/4939))
|
||||
- return right path for file:// on windows
|
||||
([f925e8ca](https://github.com/angular/angular.js/commit/f925e8caa6c51a7d45ca9ead30601ec2e9d4464c),
|
||||
[#4680](https://github.com/angular/angular.js/issues/4680))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$parse:** revert hiding "private" properties
|
||||
([4ab16aaa](https://github.com/angular/angular.js/commit/4ab16aaaf762e9038803da1f967ac8cb6650727d),
|
||||
[#4926](https://github.com/angular/angular.js/issues/4926), [#4842](https://github.com/angular/angular.js/issues/4842), [#4865](https://github.com/angular/angular.js/issues/4865), [#4859](https://github.com/angular/angular.js/issues/4859), [#4849](https://github.com/angular/angular.js/issues/4849))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.0"></a>
|
||||
# 1.2.0 timely-delivery (2013-11-08)
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
- **animations:**
|
||||
- ensure CSS transitions can work with inherited CSS class definitions
|
||||
([9d69a0a7](https://github.com/angular/angular.js/commit/9d69a0a7c75c937c0a49bb705d31252326b052df))
|
||||
- provide support for staggering animations with CSS
|
||||
([74848307](https://github.com/angular/angular.js/commit/74848307443c00ab07552336c56ddfa1e9ef6eff))
|
||||
- **$parse:** secure expressions by hiding "private" properties
|
||||
([3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e))
|
||||
- **docs:**
|
||||
- provide index pages for each angular module
|
||||
([a7e12b79](https://github.com/angular/angular.js/commit/a7e12b7959212f2fa88fe17d5a045cc9d8b22922))
|
||||
- add forward slash shortcut key for search bar
|
||||
([74912802](https://github.com/angular/angular.js/commit/74912802c644ca929e39a7583cb7a9a05f12e91f))
|
||||
- **jqLite:** expose isolateScope() getter similar to scope()
|
||||
([27e9340b](https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b))
|
||||
- **misc:** add externs file for Closure Compiler
|
||||
([9d0a6977](https://github.com/angular/angular.js/commit/9d0a69772c39bfc751ca2000c3b4b3381e51fe93))
|
||||
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- don't force animations to be enabled
|
||||
([98adc9e0](https://github.com/angular/angular.js/commit/98adc9e0383dc05efad168f30a0725cb67f5eda8))
|
||||
- only apply the fallback property if any transition animations are detected
|
||||
([94700807](https://github.com/angular/angular.js/commit/9470080762aecca5285d0f5cac4ae01540bbad4c))
|
||||
- avoid hanging animations if the active CSS transition class is missing
|
||||
([b89584db](https://github.com/angular/angular.js/commit/b89584db10b63f346cbfd03f67fb92504e5bf362),
|
||||
[#4732](https://github.com/angular/angular.js/issues/4732), [#4490](https://github.com/angular/angular.js/issues/4490))
|
||||
- ensure staggering animations understand multiple delay values
|
||||
([41a2d5b3](https://github.com/angular/angular.js/commit/41a2d5b30f4feb90651eb577cf44852a6d2be72c))
|
||||
- ensure the active class is not applied if cancelled during reflow
|
||||
([e53ff431](https://github.com/angular/angular.js/commit/e53ff431e1472c0b2d5405d267d4e403ca31087e),
|
||||
[#4699](https://github.com/angular/angular.js/issues/4699))
|
||||
- use direct DOM comparison when checking for $rootElement
|
||||
([d434eabe](https://github.com/angular/angular.js/commit/d434eabec3955f8d56c859c93befe711bfa1de27),
|
||||
[#4679](https://github.com/angular/angular.js/issues/4679))
|
||||
- ensure former nodes are fully cleaned up when a follow-up structural animation takes place
|
||||
([7f0767ac](https://github.com/angular/angular.js/commit/7f0767acaba1ec3c8849244a604b0d1c8c376446),
|
||||
[#4435](https://github.com/angular/angular.js/issues/4435))
|
||||
- ensure enable/disable animations work when the document node is used
|
||||
([6818542c](https://github.com/angular/angular.js/commit/6818542c694aec6c811fb2fe2f86f7d16544c39b),
|
||||
[#4669](https://github.com/angular/angular.js/issues/4669))
|
||||
- skip unnecessary addClass/removeClass animations
|
||||
([76b628bc](https://github.com/angular/angular.js/commit/76b628bcb3511210d312ed667e5c14d908a9fed1),
|
||||
[#4401](https://github.com/angular/angular.js/issues/4401), [#2332](https://github.com/angular/angular.js/issues/2332))
|
||||
- ensure animations work properly when the $rootElement is being animated
|
||||
([2623de14](https://github.com/angular/angular.js/commit/2623de1426219dc799f63a3d155911f93fc03461),
|
||||
[#4397](https://github.com/angular/angular.js/issues/4397), [#4231](https://github.com/angular/angular.js/issues/4231))
|
||||
- only cancel class-based animations if the follow-up class contains CSS transition/keyframe animation code
|
||||
([f5289fe8](https://github.com/angular/angular.js/commit/f5289fe84ffc1f2368dae7bd14c420abbe76749e),
|
||||
[#4463](https://github.com/angular/angular.js/issues/4463), [#3784](https://github.com/angular/angular.js/issues/3784))
|
||||
- **$compile:**
|
||||
- don't leak isolate scope state when replaced directive is used multiple times
|
||||
([b5af198f](https://github.com/angular/angular.js/commit/b5af198f0d5b0f2b3ddb31ea12f700f3e0616271))
|
||||
- correct isolate scope distribution to controllers
|
||||
([3fe4491a](https://github.com/angular/angular.js/commit/3fe4491a6bf57ddeb312b8a30cf1706f6f1d2355))
|
||||
- replaced element has isolate scope
|
||||
([97c7a4e3](https://github.com/angular/angular.js/commit/97c7a4e3791d7cb05c3317cc5f0c49ab93810bf6))
|
||||
- only pass isolate scope to children that belong to the isolate directive
|
||||
([d0efd5ee](https://github.com/angular/angular.js/commit/d0efd5eefcc0aaf167c766513e152b74dd31bafe))
|
||||
- make isolate scope truly isolate
|
||||
([909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7),
|
||||
[#1924](https://github.com/angular/angular.js/issues/1924), [#2500](https://github.com/angular/angular.js/issues/2500))
|
||||
- don't instantiate controllers twice for element transclude directives
|
||||
([18ae985c](https://github.com/angular/angular.js/commit/18ae985c3a3147b589c22f6ec21bacad2f578e2b),
|
||||
[#4654](https://github.com/angular/angular.js/issues/4654))
|
||||
- attribute bindings should not break due to terminal directives
|
||||
([79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8),
|
||||
[#4525](https://github.com/angular/angular.js/issues/4525), [#4528](https://github.com/angular/angular.js/issues/4528), [#4649](https://github.com/angular/angular.js/issues/4649))
|
||||
- instantiate controlers when re-entering compilation
|
||||
([faf5b980](https://github.com/angular/angular.js/commit/faf5b980da09da2b4c28f1feab33f87269f9f0ba),
|
||||
[#4434](https://github.com/angular/angular.js/issues/4434), [#4616](https://github.com/angular/angular.js/issues/4616))
|
||||
- **$injector:** allow a constructor function to return a function
|
||||
([c22adbf1](https://github.com/angular/angular.js/commit/c22adbf160f32c1839fbb35382b7a8c6bcec2927))
|
||||
- **$parse:** check function call context to be safe
|
||||
([6d324c76](https://github.com/angular/angular.js/commit/6d324c76f0d3ad7dae69ce01b14e0564938fb15e),
|
||||
[#4417](https://github.com/angular/angular.js/issues/4417))
|
||||
- **angular-mocks:** add inline dependency annotation
|
||||
([6d23591c](https://github.com/angular/angular.js/commit/6d23591c31f2b41097ceaa380af09998e4a62f09),
|
||||
[#4448](https://github.com/angular/angular.js/issues/4448))
|
||||
- **animateSpec:** run digest to enable animations before tests
|
||||
([aea76f0d](https://github.com/angular/angular.js/commit/aea76f0d5c43dc17f1319d0a45d2ce50fddf72e4))
|
||||
- **bootstrap-prettify:** share $animate and $$postDigestQueue with demo apps
|
||||
([1df3da36](https://github.com/angular/angular.js/commit/1df3da361d62726bf1dafe629a7fca845b6a8733))
|
||||
- **csp:**
|
||||
- fix csp auto-detection and stylesheet injection
|
||||
([08f376f2](https://github.com/angular/angular.js/commit/08f376f2ea3d3bb384f10e3c01f7d48ed21ce351),
|
||||
[#917](https://github.com/angular/angular.js/issues/917), [#2963](https://github.com/angular/angular.js/issues/2963), [#4394](https://github.com/angular/angular.js/issues/4394), [#4444](https://github.com/angular/angular.js/issues/4444))
|
||||
- don't inline css in csp mode
|
||||
([a86cf20e](https://github.com/angular/angular.js/commit/a86cf20e67202d614bbcaf038c5e04db94483256)
|
||||
- **docModuleComponents:** implement anchor scroll when content added
|
||||
([eb51b024](https://github.com/angular/angular.js/commit/eb51b024c9b77527420014cdf7dbb292b5b9dd6b),
|
||||
[#4703](https://github.com/angular/angular.js/issues/4703))
|
||||
- **input:** keep track of min/max attars on-the-fly
|
||||
([4b653aea](https://github.com/angular/angular.js/commit/4b653aeac1aca7ac551738870a2446b6810ca0df))
|
||||
- **ngAnimate:** fix cancelChildAnimations throwing exception
|
||||
([b9557b0a](https://github.com/angular/angular.js/commit/b9557b0a86206d938a738ea470736d011dff7e1a),
|
||||
[#4548](https://github.com/angular/angular.js/issues/4548))
|
||||
- **ngClassSpec:** clear animation enable fn from postDigestQueue
|
||||
([ffa9d0a6](https://github.com/angular/angular.js/commit/ffa9d0a6db137cba4090e569b8ed4e25a711314e))
|
||||
- **ngEventDirectives:** parse expression only once during compile phase.
|
||||
([9a828738](https://github.com/angular/angular.js/commit/9a828738cd2e959bc2a198989e96c8e416d28b71))
|
||||
- **ngIf:**
|
||||
- destroy child scope when destroying DOM
|
||||
([9483373c](https://github.com/angular/angular.js/commit/9483373c331343648e079420b3eb1f564d410ff2))
|
||||
- ngIf removes elements dynamically added to it
|
||||
([e19067c9](https://github.com/angular/angular.js/commit/e19067c9bbac3c3bb450c80f73eb5518bd0db1a1))
|
||||
- **ngInclude:** only run anchorScroll after animation is done
|
||||
([d378f550](https://github.com/angular/angular.js/commit/d378f5500ab2eef0779338336c6a95656505ebb8),
|
||||
[#4723](https://github.com/angular/angular.js/issues/4723))
|
||||
- **ngMock:** throw more descriptive errors for $animate.flushNext()
|
||||
([6fb19157](https://github.com/angular/angular.js/commit/6fb191570ee72f087e8bb6b1d8f5eea0f585886c))
|
||||
- **ngModel:** deregister from the form on scope not DOM destruction
|
||||
([8f989d65](https://github.com/angular/angular.js/commit/8f989d652f70fd147f66a18411070c7b939e242e),
|
||||
[#4226](https://github.com/angular/angular.js/issues/4226), [#4779](https://github.com/angular/angular.js/issues/4779))
|
||||
- **ngScenario:** correctly disable animations for end 2 end tests
|
||||
([9d004585](https://github.com/angular/angular.js/commit/9d0045856351e9db48ddf66f66e210d9cc53d24a))
|
||||
- **ngView:**
|
||||
- only run anchorScroll after animation is done
|
||||
([da344daa](https://github.com/angular/angular.js/commit/da344daa4023556f8abbef6d8ad87a16362b5861))
|
||||
- ensure the new view element is placed after the old view element
|
||||
([3f568b22](https://github.com/angular/angular.js/commit/3f568b22f9bec09192588e3cae937db5c2e757f9),
|
||||
[#4362](https://github.com/angular/angular.js/issues/4362))
|
||||
- **ngdocs:**
|
||||
- create mock Doc objects correctly
|
||||
([d4493fda](https://github.com/angular/angular.js/commit/d4493fda2c4c2ff1fdfc264bfb479741abc781c7))
|
||||
- `shortDescription()` should not error if no `description`
|
||||
([4c8fa353](https://github.com/angular/angular.js/commit/4c8fa353245b9c32261860caff18f002d294e19f))
|
||||
- remove the side search bar
|
||||
([6c20ec19](https://github.com/angular/angular.js/commit/6c20ec193f11aa647be1b2ad2ac5b3e7c2894bd7))
|
||||
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:**
|
||||
- due to [d0efd5ee](https://github.com/angular/angular.js/commit/d0efd5eefcc0aaf167c766513e152b74dd31bafe),
|
||||
Child elements that are defined either in the application template or in some other
|
||||
directives template do not get the isolate scope. In theory, nobody should rely on this behavior, as
|
||||
it is very rare - in most cases the isolate directive has a template.
|
||||
|
||||
- due to [909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7),
|
||||
Directives without isolate scope do not get the isolate scope from an isolate directive on the
|
||||
same element. If your code depends on this behavior (non-isolate directive needs to access state
|
||||
from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly.
|
||||
|
||||
**Before**
|
||||
|
||||
```
|
||||
<input ng-model="$parent.value" ng-isolate>
|
||||
|
||||
.directive('ngIsolate', function() {
|
||||
return {
|
||||
scope: {},
|
||||
template: '{{value}}'
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```
|
||||
<input ng-model="value" ng-isolate>
|
||||
|
||||
.directive('ngIsolate', function() {
|
||||
return {
|
||||
scope: {value: '=ngModel'},
|
||||
template: '{{value}}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
Closes [#1924](https://github.com/angular/angular.js/issues/1924) and
|
||||
[#2500](https://github.com/angular/angular.js/issues/2500)
|
||||
|
||||
- due to [79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8),
|
||||
|
||||
Previously, the interpolation priority was `-100` in 1.2.0-rc.2, and `100` before 1.2.0-rc.2.
|
||||
Before this change the binding was setup in the post-linking phase.
|
||||
|
||||
Now the attribute interpolation (binding) executes as a directive with priority 100 and the
|
||||
binding is set up in the pre-linking phase.
|
||||
|
||||
Closes [#4525](https://github.com/angular/angular.js/issues/4525),
|
||||
[#4528](https://github.com/angular/angular.js/issues/4528), and
|
||||
[#4649](https://github.com/angular/angular.js/issues/4649)
|
||||
|
||||
|
||||
- **$parse:** due to [3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e),
|
||||
|
||||
This commit introduces the notion of "private" properties (properties
|
||||
whose names begin and/or end with an underscore) on the scope chain.
|
||||
These properties will not be available to Angular expressions (i.e. {{
|
||||
}} interpolation in templates and strings passed to `$parse`) They are
|
||||
freely available to JavaScript code (as before).
|
||||
|
||||
**Motivation**
|
||||
|
||||
Angular expressions execute in a limited context. They do not have
|
||||
direct access to the global scope, `window`, `document` or the Function
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
sensitive APIs outside of the scope chain (in a closure or your
|
||||
controller.) That's easier said that done for two reasons:
|
||||
|
||||
1. JavaScript does not have a notion of private properties so if you need
|
||||
someone on the scope chain for JavaScript use, you also expose it to
|
||||
Angular expressions
|
||||
2. the new "controller as" syntax that's now in increased usage exposes the
|
||||
entire controller on the scope chain greatly increaing the exposed surface.
|
||||
|
||||
Though Angular expressions are written and controlled by the developer, they:
|
||||
|
||||
1. Typically deal with user input
|
||||
2. Don't get the kind of test coverage that JavaScript code would
|
||||
|
||||
This commit provides a way, via a naming convention, to
|
||||
allow publishing/restricting properties from controllers/scopes to
|
||||
Angular expressions enabling one to only expose those properties that
|
||||
are actually needed by the expressions.
|
||||
|
||||
- **csp:** due to [08f376f2](https://github.com/angular/angular.js/commit/08f376f2ea3d3bb384f10e3c01f7d48ed21ce351),
|
||||
triggering ngCsp directive via `ng:csp` attribute is not supported any more.
|
||||
Please use `data-ng-csp` instead.
|
||||
|
||||
- **jqLite:** due to [27e9340b](https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b),
|
||||
`jqLite.scope()` (connonly used through `angular.element(node).scope()`) does not return the
|
||||
isolate scope on the element that triggered directive with isolate scope. Use
|
||||
`jqLite.isolateScope()` instead.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.2.0-rc.3"></a>
|
||||
# 1.2.0-rc.3 ferocious-twitch (2013-10-14)
|
||||
|
||||
|
||||
Vendored
+1
@@ -66,6 +66,7 @@ angularFiles = {
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
'src/minErr.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
|
||||
|
||||
+4
-4
@@ -15,8 +15,8 @@ ng\:form {
|
||||
* when the active class isn't set, or if the active class doesn't
|
||||
* contain any styles to transition to, then, if ngAnimate is used,
|
||||
* it will appear as if the webpage is broken due to the forever hanging
|
||||
* animations. The clip (!ie) and zoom (ie) CSS properties are used
|
||||
* since they trigger a transition without making the browser
|
||||
* animations. The border-spacing (!ie) and zoom (ie) CSS properties are
|
||||
* used below since they trigger a transition without making the browser
|
||||
* animate anything and they're both highly underused CSS properties */
|
||||
.ng-animate-start { clip:rect(0, auto, auto, 0); -ms-zoom:1.0001; }
|
||||
.ng-animate-active { clip:rect(-1px, auto, auto, 0); -ms-zoom:1; }
|
||||
.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; }
|
||||
.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; }
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ directive.table = function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
if (!attrs.class) {
|
||||
if (!attrs['class']) {
|
||||
element.addClass('table table-bordered table-striped code-table');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
When a directive is declared with `template` (or `templateUrl`) and `replace` mode on, the template
|
||||
must have exactly one root element. That is, the text of the template property or the content
|
||||
referenced by the templateUrl must be contained within a single html element.
|
||||
For example, '<p>blah <em>blah</em> blah</p>' instead of simply 'blah <em>blah</em> blah'.
|
||||
For example, `<p>blah <em>blah</em> blah</p>` instead of simply `blah <em>blah</em> blah`.
|
||||
Otherwise, the replacement operation would result in a single element (the directive) being replaced
|
||||
with multiple elements or nodes, which is unsupported and not commonly needed in practice.
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecprv
|
||||
@fullName Referencing private Field in Expression
|
||||
|
||||
@description
|
||||
|
||||
Occurs when an Angular expression attempts to access a private field.
|
||||
|
||||
Fields with names that begin or end with an underscore are considered
|
||||
private fields. Angular expressions are not allowed to reference such
|
||||
fields on the scope chain. This only applies to Angular expressions
|
||||
(e.g. {{ }} interpolation and calls to `$parse` with a string expression
|
||||
argument) – Javascript itself has no such notion.
|
||||
|
||||
To resolve this error, use an alternate non-private field if available
|
||||
or make the field public (by removing any leading and trailing
|
||||
underscore characters from its name.)
|
||||
|
||||
Example expression that would result in this error:
|
||||
|
||||
```html
|
||||
<div>{{user._private_field}}</div>
|
||||
```
|
||||
|
||||
Background:
|
||||
Though Angular expressions are written and controlled by the developer
|
||||
and are trusted, they do represent an attack surface due to the
|
||||
following two factors:
|
||||
|
||||
- they typically deal with user input which is generally high risk
|
||||
- they often don't get the kind of attention and test coverage that
|
||||
JavaScript code would.
|
||||
|
||||
If these expression were evaluated in a context with full trust, an
|
||||
attacker, though unable to change the expression itself, can feed it
|
||||
unexpected and dangerous input that could result in a security
|
||||
breach/exploit.
|
||||
|
||||
As such, Angular expressions are evaluated in a limited context. They
|
||||
do not have direct access to the global scope, Window, Document, the
|
||||
Function constructor or "private" properties (names beginning or ending
|
||||
with an underscore character) on the scope chain. They should get their
|
||||
work done via public properties and methods exposed on the scope chain
|
||||
(keep in mind that this includes controllers as well as they are
|
||||
published on the scope via the "controller as" syntax.)
|
||||
|
||||
As a best practise, only "publish" properties on the scopes and
|
||||
controllers that must be available to Angular expressions. All other
|
||||
members should either be in closures or be "private" by giving them
|
||||
names with a leading or trailing underscore character.
|
||||
@@ -0,0 +1,27 @@
|
||||
@ngdoc error
|
||||
@name $resource:badmember
|
||||
@fullName Syntax error in param value using @member lookup
|
||||
@description
|
||||
|
||||
Occurs when there is a syntax error when attempting to extract a param
|
||||
value from the data object.
|
||||
|
||||
Here's an example of valid syntax for `params` or `paramsDefault`:
|
||||
|
||||
````javascript
|
||||
{
|
||||
bar: '@foo.bar'
|
||||
}
|
||||
````
|
||||
|
||||
The part following the `@`, `foo.bar` in this case, should be a simple
|
||||
dotted member lookup using only ASCII identifiers. This error occurs
|
||||
when there is an error in that expression. The following are all syntax
|
||||
errors
|
||||
|
||||
| Value | Error |
|
||||
|---------|----------------|
|
||||
| `@` | Empty expression following `@`. |
|
||||
| `@1.a` | `1` is an invalid javascript identifier. |
|
||||
| `@.a` | Leading `.` is invalid. |
|
||||
| `@a[1]` | Only dotted lookups are supported (no index operator) |
|
||||
@@ -350,7 +350,7 @@ Creating local properties on widget scope creates two problems:
|
||||
|
||||
|
||||
To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An
|
||||
isolated scope does not prototypically inherit from the child scope, and therefore we don't have
|
||||
isolated scope does not prototypically inherit from the parent scope, and therefore we don't have
|
||||
to worry about accidentally clobbering any properties.
|
||||
|
||||
However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget
|
||||
|
||||
@@ -12,7 +12,7 @@ For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
|
||||
|{@link concepts#template Template} | HTML with additional markup |
|
||||
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
||||
|{@link concepts#model Model} | the data that is shown to the user and with which the user interacts |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that directives and expressions can access it |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
||||
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
||||
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
||||
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|
||||
@@ -99,8 +99,8 @@ The concept behind this is <a name="databinding">"{@link databinding two-way dat
|
||||
|
||||
# Adding UI logic: Controllers
|
||||
|
||||
Let's add some more logic to the example to
|
||||
allow to enter and calculate the costs in different currencies and also pay the invoice.
|
||||
Let's add some more logic to the example that allows us to enter and calculate the costs in
|
||||
different currencies and also pay the invoice.
|
||||
|
||||
<example module="invoice1">
|
||||
<file name="invoice1.js">
|
||||
|
||||
@@ -200,7 +200,7 @@ previous example.
|
||||
|
||||
Notice that the `SpicyCtrl` Controller now defines just one method called `spicy`, which takes one
|
||||
argument called `spice`. The template then refers to this Controller method and passes in a string
|
||||
constant `'chili'` in the binding for the first button and a model property `spice` (bound to an
|
||||
constant `'chili'` in the binding for the first button and a model property `customSpice` (bound to an
|
||||
input box) in the second button.
|
||||
|
||||
## Scope Inheritance Example
|
||||
|
||||
@@ -13,45 +13,44 @@ Unit testing as the name implies is about testing individual units of code. Unit
|
||||
answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list
|
||||
in the right order?"
|
||||
|
||||
In order to answer such question it is very important that we can isolate the unit of code under test.
|
||||
In order to answer such a question it is very important that we can isolate the unit of code under test.
|
||||
That is because when we are testing the sort function we don't want to be forced into creating
|
||||
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort.
|
||||
|
||||
While
|
||||
this may seem obvious it usually is very difficult to be able to call an individual function on a
|
||||
typical project. The reason is that the developers often mix concerns, and they end up with a
|
||||
piece of code which does everything. It reads the data from XHR, it sorts it and then it
|
||||
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
|
||||
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 (which you can mock out) and we created abstraction which
|
||||
provide dependency injection for your XHR (which you can mock out) and we created abstractions which
|
||||
allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
|
||||
it is easy to write a sort function which sorts some data, so that your test can create a data set,
|
||||
apply the function, and assert that the resulting model is in the correct order. The test does not
|
||||
have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
|
||||
DOM in the right way.
|
||||
have to wait for the XHR response to arrive, create the right kind of test DOM, nor assert that your
|
||||
function has mutated the DOM in the right way.
|
||||
|
||||
## With great power comes great responsibility
|
||||
|
||||
Angular is written with testability in mind, but it still requires that you
|
||||
do the right thing. We tried to make the right thing easy, but Angular is not magic, which means if
|
||||
you don't follow these guidelines you may very well end up with an untestable application.
|
||||
Angular is written with testability in mind, but it still requires that you do the right thing.
|
||||
We tried to make the right thing easy, but Angular is not magic. If you don't follow these guidelines
|
||||
you may very well end up with an untestable application.
|
||||
|
||||
## Dependency Injection
|
||||
There are several ways in which you can get a hold of a dependency:
|
||||
1. You could create it using the `new` operator.
|
||||
2. You could look for it in a well known place, also known as global singleton.
|
||||
3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
|
||||
the registry? Most likely by looking it up in a well known place. See #2)
|
||||
4. You could expect that it be handed to you.
|
||||
There are several ways in which you can get a hold of a dependency. You can:
|
||||
1. Create it using the `new` operator.
|
||||
2. Look for it in a well-known place, also known as a global singleton.
|
||||
3. Ask a registry (also known as service registry) for it. (But how do you get a hold of
|
||||
the registry? Most likely by looking it up in a well known place. See #2.)
|
||||
4. Expect it to be handed to you.
|
||||
|
||||
Out of the four options in the list above, only the last one is testable. Let's look at why:
|
||||
|
||||
### Using the `new` operator
|
||||
|
||||
While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
|
||||
on a constructor permanently binds the call site to the type. For example lets say that we are
|
||||
trying to instantiate an `XHR` so that we can get some data from the server.
|
||||
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
|
||||
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
|
||||
instantiate an `XHR` that will retrieve data from the server.
|
||||
|
||||
<pre>
|
||||
function MyClass() {
|
||||
@@ -64,12 +63,12 @@ function MyClass() {
|
||||
}
|
||||
</pre>
|
||||
|
||||
The issue becomes that in tests, we would very much like to instantiate a `MockXHR` which would
|
||||
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
|
||||
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
|
||||
permanently bound to the actual XHR, and there is no good way to replace it. Yes there is monkey
|
||||
patching. That is a bad idea for many reasons which are outside the scope of this document.
|
||||
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
|
||||
patch, but that is a bad idea for many reasons which are outside the scope of this document.
|
||||
|
||||
The class above is hard to test since we have to resort to monkey patching:
|
||||
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
|
||||
<pre>
|
||||
var oldXHR = XHR;
|
||||
XHR = function MockXHR() {};
|
||||
@@ -81,7 +80,7 @@ XHR = oldXHR; // if you forget this bad things will happen
|
||||
|
||||
|
||||
### Global look-up:
|
||||
Another way to approach the problem is to look for the service in a well known location.
|
||||
Another way to approach the problem is to look for the service in a well-known location.
|
||||
|
||||
<pre>
|
||||
function MyClass() {
|
||||
@@ -95,14 +94,14 @@ function MyClass() {
|
||||
}
|
||||
</pre>
|
||||
|
||||
While no new instance of the dependency is being created, it is fundamentally the same as `new`, in
|
||||
that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
|
||||
While no new dependency instance is created, it is fundamentally the same as `new` in
|
||||
that no way exists to intercept the call to `global.xhr` for testing purposes, other then
|
||||
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
|
||||
order to replace it with call to a mock method. For further explanation why this is bad see: {@link
|
||||
order to replace it with call to a mock method. For further explanation of why this is bad see: {@link
|
||||
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
|
||||
State & Singletons}
|
||||
|
||||
The class above is hard to test since we have to change global state:
|
||||
The class above is hard to test since we have to change the global state:
|
||||
<pre>
|
||||
var oldXHR = global.xhr;
|
||||
global.xhr = function mockXHR() {};
|
||||
@@ -115,7 +114,7 @@ global.xhr = oldXHR; // if you forget this bad things will happen
|
||||
|
||||
### Service Registry:
|
||||
|
||||
It may seem as that this can be solved by having a registry for all of the services, and then
|
||||
It may seem that this can be solved by having a registry of all the services and then
|
||||
having the tests replace the services as needed.
|
||||
|
||||
<pre>
|
||||
@@ -131,12 +130,12 @@ function MyClass() {
|
||||
}
|
||||
</pre>
|
||||
|
||||
However, where does the serviceRegistry come from? if it is:
|
||||
* `new`-ed up, the test has no chance to reset the services for testing
|
||||
* global look-up, then the service returned is global as well (but resetting is easier, since
|
||||
there is only one global variable to be reset).
|
||||
However, where does the serviceRegistry come from? If it is:
|
||||
* `new`-ed up, the test has no chance to reset the services for testing.
|
||||
* a global look-up then the service returned is global as well (but resetting is easier, since
|
||||
only one global variable exists to be reset).
|
||||
|
||||
The class above is hard to test since we have to change global state:
|
||||
The class above is hard to test since we have to change the global state:
|
||||
<pre>
|
||||
var oldServiceLocator = global.serviceLocator;
|
||||
global.serviceLocator.set('xhr', function mockXHR() {});
|
||||
@@ -148,7 +147,7 @@ global.serviceLocator = oldServiceLocator; // if you forget this bad things will
|
||||
|
||||
|
||||
### Passing in Dependencies:
|
||||
Lastly the dependency can be passed in.
|
||||
Last, the dependency can be passed in.
|
||||
|
||||
<pre>
|
||||
function MyClass(xhr) {
|
||||
@@ -161,12 +160,12 @@ function MyClass(xhr) {
|
||||
}
|
||||
</pre>
|
||||
|
||||
This is the preferred way since the code makes no assumptions as to where the `xhr` comes from,
|
||||
rather that whoever created the class was responsible for passing it in. Since the creator of the
|
||||
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
|
||||
instead about whoever created the class responsible for passing it in. Since the creator of the
|
||||
class should be different code than the user of the class, it separates the responsibility of
|
||||
creation from the logic, and that is what dependency-injection is in a nutshell.
|
||||
creation from the logic. This is dependency-injection is in a nutshell.
|
||||
|
||||
The class above is very testable, since in the test we can write:
|
||||
The class above is testable, since in the test we can write:
|
||||
<pre>
|
||||
function xhrMock(args) {...}
|
||||
var myClass = new MyClass(xhrMock);
|
||||
@@ -176,12 +175,12 @@ myClass.doWork();
|
||||
|
||||
Notice that no global variables were harmed in the writing of this test.
|
||||
|
||||
Angular comes with {@link di dependency injection} built in which makes the right thing
|
||||
Angular comes with {@link di dependency injection} built-in, making the right thing
|
||||
easy to do, but you still need to do it if you wish to take advantage of the testability story.
|
||||
|
||||
## Controllers
|
||||
What makes each application unique is its logic, which is what we would like to test. If the logic
|
||||
for your application is mixed in with DOM manipulation, it will be hard to test as in the example
|
||||
What makes each application unique is its logic, and the logic is what we would like to test. If the logic
|
||||
for your application contains DOM manipulation, it will be hard to test. See the example
|
||||
below:
|
||||
|
||||
<pre>
|
||||
@@ -209,7 +208,7 @@ function PasswordCtrl() {
|
||||
}
|
||||
</pre>
|
||||
|
||||
The code above is problematic from a testability point of view, since it requires your test to have the right kind
|
||||
The code above is problematic from a testability point of view since it requires your test to have the right kind
|
||||
of DOM present when the code executes. The test would look like this:
|
||||
|
||||
<pre>
|
||||
@@ -226,8 +225,8 @@ expect(span.text()).toEqual('weak');
|
||||
$('body').html('');
|
||||
</pre>
|
||||
|
||||
In angular the controllers are strictly separated from the DOM manipulation logic which results in
|
||||
a much easier testability story as can be seen in this example:
|
||||
In angular the controllers are strictly separated from the DOM manipulation logic and this results in
|
||||
a much easier testability story as the following example shows:
|
||||
|
||||
<pre>
|
||||
function PasswordCtrl($scope) {
|
||||
@@ -245,7 +244,7 @@ function PasswordCtrl($scope) {
|
||||
}
|
||||
</pre>
|
||||
|
||||
and the test is straight forward
|
||||
and the test is straight forward:
|
||||
|
||||
<pre>
|
||||
var $scope = {};
|
||||
@@ -255,11 +254,11 @@ $scope.grade();
|
||||
expect($scope.strength).toEqual('weak');
|
||||
</pre>
|
||||
|
||||
Notice that the test is not only much shorter but it is easier to follow what is going on. We say
|
||||
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
|
||||
that such a test tells a story, rather then asserting random bits which don't seem to be related.
|
||||
|
||||
## Filters
|
||||
{@link api/ng.$filterProvider Filters} are functions which transform the data into user readable
|
||||
{@link api/ng.$filterProvider Filters} are functions which transform the data into a user readable
|
||||
format. They are important because they remove the formatting responsibility from the application
|
||||
logic, further simplifying the application logic.
|
||||
|
||||
@@ -282,7 +281,7 @@ you create with directives may be used throughout your application and in many d
|
||||
|
||||
### Simple HTML Element Directive
|
||||
|
||||
Lets start with an angular app with no dependencies.
|
||||
Let's start with an angular app with no dependencies.
|
||||
|
||||
<pre>
|
||||
var app = angular.module('myApp', []);
|
||||
|
||||
@@ -56,9 +56,9 @@ The following also **matches** `ngModel`:
|
||||
Angular **normalizes** an element's tag and attribute name to determine which elements match which
|
||||
directives. We typically refer to directives by their case-sensitive
|
||||
{@link http://en.wikipedia.org/wiki/CamelCase camelCase} **normalized** name (e.g. `ngModel`).
|
||||
However, in the DOM, we refer to directives by case-insensitive forms, typically using
|
||||
{@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited} attributes on DOM elements
|
||||
(e.g. `ng-model`).
|
||||
However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case
|
||||
forms, typically using {@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited}
|
||||
attributes on DOM elements (e.g. `ng-model`).
|
||||
|
||||
The **normalization** process is as follows:
|
||||
|
||||
@@ -94,14 +94,16 @@ Here are some equivalent examples of elements that match `ngBind`:
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** Prefer using the dash-delimited format (e.g. `ng-bind` for `ngBind`).
|
||||
If you want to use an HTML validating tool, you can instead use the `data`-prefixed version (e.g. `data-ng-bind` for `ngBind`).
|
||||
If you want to use an HTML validating tool, you can instead use the `data`-prefixed version (e.g.
|
||||
`data-ng-bind` for `ngBind`).
|
||||
The other forms shown above are accepted for legacy reasons but we advise you to avoid them.
|
||||
</div>
|
||||
|
||||
`$compile` can match directives based on element names, attributes, class names, as well as comments.
|
||||
|
||||
All of the Angular-provided directives match attribute name, tag name, comments, or class name.
|
||||
The following demonstrates the various ways a directive (`myDir` in this case) can be referenced from within a template:
|
||||
The following demonstrates the various ways a directive (`myDir` in this case) can be referenced
|
||||
from within a template:
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
@@ -116,20 +118,22 @@ Doing so generally makes it easier to determine what directives a given element
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** Comment directives were commonly used in places where the DOM API limits (e.g. inside `<table>` elements)
|
||||
to create directives that spanned multiple elements. AngularJS 1.2 introduces
|
||||
{@link api/ng.directive:ngRepeat `ng-repeat-start` and `ng-repeat-end`} as a better solution to this problem.
|
||||
Developers are encouraged to use this over custom comment directives when possible.
|
||||
**Best Practice:** Comment directives were commonly used in places where the DOM API limits the
|
||||
ability to create directives that spanned multiple elements (e.g. inside `<table>` elements).
|
||||
AngularJS 1.2 introduces {@link api/ng.directive:ngRepeat `ng-repeat-start` and `ng-repeat-end`}
|
||||
as a better solution to this problem. Developers are encouraged to use this over custom comment
|
||||
directives when possible.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### Text and attribute bindings
|
||||
|
||||
During the compilation process the {@link api/ng.$compile compiler} matches text and attributes using the
|
||||
{@link api/ng.$interpolate $interpolate} service to see if they contain embedded expressions. These expressions
|
||||
are registered as {@link api/ng.$rootScope.Scope#methods_$watch watches} and will update as part of normal {@link
|
||||
api/ng.$rootScope.Scope#methods_$digest digest} cycle. An example of interpolation is shown below:
|
||||
During the compilation process the {@link api/ng.$compile compiler} matches text and attributes
|
||||
using the {@link api/ng.$interpolate $interpolate} service to see if they contain embedded
|
||||
expressions. These expressions are registered as {@link api/ng.$rootScope.Scope#methods_$watch watches}
|
||||
and will update as part of normal {@link api/ng.$rootScope.Scope#methods_$digest digest} cycle. An
|
||||
example of interpolation is shown below:
|
||||
|
||||
```html
|
||||
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
|
||||
@@ -149,8 +153,8 @@ For example, considering this template:
|
||||
```
|
||||
|
||||
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}}"`.
|
||||
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.
|
||||
|
||||
@@ -170,18 +174,19 @@ For example, we could fix the example above by instead writing:
|
||||
|
||||
## Creating Directives
|
||||
|
||||
First let's talk about the API for registering directives. Much like controllers, directives are registered on
|
||||
modules. To register a directive, you use the `module.directive` API. `module.directive` takes the
|
||||
{@link guide/directive#creating-custom-directives_matching-directives normalized} directive name followed
|
||||
by a **factory function.** This factory function should return
|
||||
an object with the different options to tell `$compile` how the directive should behave when matched.
|
||||
First let's talk about the API for registering directives. Much like controllers, directives are
|
||||
registered on modules. To register a directive, you use the `module.directive` API.
|
||||
`module.directive` takes the
|
||||
{@link guide/directive#creating-custom-directives_matching-directives normalized} directive name
|
||||
followed by a **factory function.** This factory function should return an object with the different
|
||||
options to tell `$compile` how the directive should behave when matched.
|
||||
|
||||
|
||||
The factory function is invoked only once when the
|
||||
{@link api/ng.$compile compiler} matches the directive for the first time. You can
|
||||
perform any initialization work here. The function is invoked using {@link
|
||||
api/AUTO.$injector#methods_invoke $injector.invoke} which
|
||||
makes it injectable just like a controller.
|
||||
{@link api/ng.$compile compiler} matches the directive for the first time. You can perform any
|
||||
initialization work here. The function is invoked using
|
||||
{@link api/AUTO.$injector#methods_invoke $injector.invoke} which makes it injectable just like a
|
||||
controller.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** Prefer using the definition object over returning a function.
|
||||
@@ -204,9 +209,9 @@ For the following examples, we'll use the prefix `my` (e.g. `myCustomer`).
|
||||
|
||||
### Template-expanding directive
|
||||
|
||||
Let's say you have a chunk of your template that represents a customer's information. This template is repeated
|
||||
many times in your code. When you change it in one place, you have to change it in several others. This is a
|
||||
good opportunity to use a directive to simplify your template.
|
||||
Let's say you have a chunk of your template that represents a customer's information. This template
|
||||
is repeated many times in your code. When you change it in one place, you have to change it in
|
||||
several others. This is a good opportunity to use a directive to simplify your template.
|
||||
|
||||
Let's create a directive that simply replaces its contents with a static template:
|
||||
|
||||
@@ -232,21 +237,22 @@ Let's create a directive that simply replaces its contents with a static templat
|
||||
</file>
|
||||
</example>
|
||||
|
||||
Notice that we have bindings in this directive. After `$compile` compiles and links `<div my-customer></div>`,
|
||||
it will try to match directives on the element's children. This means you can compose directives of other directives.
|
||||
We'll see how to do that in {@link
|
||||
guide/directive#creating-custom-directives_demo_creating-directives-that-communicate an example} below.
|
||||
Notice that we have bindings in this directive. After `$compile` compiles and links
|
||||
`<div my-customer></div>`, it will try to match directives on the element's children. This means you
|
||||
can compose directives of other directives. We'll see how to do that in
|
||||
{@link guide/directive#creating-custom-directives_demo_creating-directives-that-communicate an example}
|
||||
below.
|
||||
|
||||
In the example above we in-lined the value of the `template` option, but this will become annoying as the size
|
||||
of your template grows.
|
||||
In the example above we in-lined the value of the `template` option, but this will become annoying
|
||||
as the size of your template grows.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** Unless your template is very small, it's typically better to break it apart into its own
|
||||
HTML file and load it with the `templateUrl` option.
|
||||
**Best Practice:** Unless your template is very small, it's typically better to break it apart into
|
||||
its own HTML file and load it with the `templateUrl` option.
|
||||
</div>
|
||||
|
||||
If you are familiar with `ngInclude`, `templateUrl` works just like it. Here's the same example using `templateUrl`
|
||||
instead:
|
||||
If you are familiar with `ngInclude`, `templateUrl` works just like it. Here's the same example
|
||||
using `templateUrl` instead:
|
||||
|
||||
<example module="docsTemplateUrlDirective">
|
||||
<file name="script.js">
|
||||
@@ -277,8 +283,8 @@ Great! But what if we wanted to have our directive match the tag name `<my-custo
|
||||
If we simply put a `<my-customer>` element into the HMTL, it doesn't work.
|
||||
|
||||
<div class="alert alert-waring">
|
||||
**Note:** When you create a directive, it is restricted to attribute only by default. In order to create
|
||||
directives that are triggered by element name, you need to use the `restrict` option.
|
||||
**Note:** When you create a directive, it is restricted to attribute only by default. In order to
|
||||
create directives that are triggered by element name, you need to use the `restrict` option.
|
||||
</div>
|
||||
|
||||
The `restrict` option is typically set to:
|
||||
@@ -317,28 +323,33 @@ Let's change our directive to use `restrict: 'E'`:
|
||||
</file>
|
||||
</example>
|
||||
|
||||
For more on the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object
|
||||
`restrict`, see the API docs}.
|
||||
For more on the
|
||||
{@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object `restrict`}
|
||||
property, see the
|
||||
{@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object API docs}.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**When should I use an attribute versus an element?**
|
||||
|
||||
Use an element when you are creating a component that is in control of the template. The common case for this
|
||||
is when you are creating a Domain-Specific Language for parts of your template.
|
||||
Use an element when you are creating a component that is in control of the template. The common case
|
||||
for this is when you are creating a Domain-Specific Language for parts of your template.
|
||||
|
||||
Use an attribute when you are decorating an existing element with new functionality.
|
||||
</div>
|
||||
|
||||
Using an element for the `myCustomer` directive is clearly the right choice because you're not decorating an element
|
||||
with some "customer" behavior; you're defining the core behavior of the element as a customer component.
|
||||
Using an element for the `myCustomer` directive is clearly the right choice because you're not
|
||||
decorating an element with some "customer" behavior; you're defining the core behavior of the
|
||||
element as a customer component.
|
||||
|
||||
|
||||
|
||||
### Isolating the Scope of a Directive
|
||||
|
||||
Our `myCustomer` directive above is great, but it has a fatal flaw. We can only use it once within a given scope.
|
||||
Our `myCustomer` directive above is great, but it has a fatal flaw. We can only use it once within a
|
||||
given scope.
|
||||
|
||||
In its current implementation, we'd need to create a different controller each time In order to re-use such a directive:
|
||||
In its current implementation, we'd need to create a different controller each time In order to
|
||||
re-use such a directive:
|
||||
|
||||
<example module="docsScopeProblemExample">
|
||||
<file name="script.js">
|
||||
@@ -379,8 +390,8 @@ In its current implementation, we'd need to create a different controller each t
|
||||
This is clearly not a great solution.
|
||||
|
||||
What we want to be able to do is separate the scope inside a directive from the scope
|
||||
outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an
|
||||
**isolate scope**. To do this, we can use a directive's `scope` option:
|
||||
outside, and then map the outer scope to a directive's inner scope. We can do this by creating what
|
||||
we call an **isolate scope**. To do this, we can use a directive's `scope` option:
|
||||
|
||||
<example module="docsIsolateScopeDirective">
|
||||
<file name="script.js">
|
||||
@@ -393,62 +404,67 @@ outside, and then map the outer scope to a directive's inner scope. We can do th
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
customer: '=customer'
|
||||
customerInfo: '=info'
|
||||
},
|
||||
templateUrl: 'my-customer.html'
|
||||
templateUrl: 'my-customer-iso.html'
|
||||
};
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<my-customer customer="naomi"></my-customer>
|
||||
<my-customer info="naomi"></my-customer>
|
||||
<hr>
|
||||
<my-customer customer="igor"></my-customer>
|
||||
<my-customer info="igor"></my-customer>
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-customer.html">
|
||||
Name: {{customer.name}} Address: {{customer.address}}
|
||||
<file name="my-customer-iso.html">
|
||||
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
|
||||
</file>
|
||||
</example>
|
||||
|
||||
Looking at `index.html`, the first `<my-customer>` element binds the inner scope's `customer` to `naomi`,
|
||||
which we have exposed on our controller's scope. The second binds `customer` to `igor`.
|
||||
Looking at `index.html`, the first `<my-customer>` element binds the `info` attribute to `naomi`,
|
||||
which we have exposed on our controller's scope. The second binds `info` to `igor`.
|
||||
|
||||
Let's take a closer look at the scope option:
|
||||
|
||||
```javascript
|
||||
//...
|
||||
scope: {
|
||||
customer: '=customer'
|
||||
customerInfo: '=info'
|
||||
},
|
||||
//...
|
||||
```
|
||||
|
||||
The property name (`customer`) corresponds to the variable name of the `myCustomer` directive's isolated scope.
|
||||
The value of the property (`=customer`) tells `$compile` to bind to the `customer` attribute.
|
||||
The **scope option** is an object that contains a property for each isolate scope binding. In this
|
||||
case it has just one property:
|
||||
|
||||
- Its name (`customerInfo`) corresponds to the
|
||||
directive's **isolate scope** property `customerInfo`.
|
||||
- Its value (`=info`) tells `$compile` to bind to the `info` attribute.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** These `=attr` attributes in the `scope` option of directives are normalized just like directive names.
|
||||
To bind to the attribute in `<div bind-to-this="thing">`, you'd specify a binding of `=bindToThis`.
|
||||
**Note:** These `=attr` attributes in the `scope` option of directives are normalized just like
|
||||
directive names. To bind to the attribute in `<div bind-to-this="thing">`, you'd specify a binding
|
||||
of `=bindToThis`.
|
||||
</div>
|
||||
|
||||
For cases where the attribute name is the same as the value you want to bind to inside
|
||||
the directive's scope, you can use this shorthand syntax:
|
||||
For cases where the attribute name is the same as the value you want to bind to inside the
|
||||
directive's scope, you can use this shorthand syntax:
|
||||
|
||||
```javascript
|
||||
//...
|
||||
...
|
||||
scope: {
|
||||
// same as '=customer'
|
||||
// same as '=customer'
|
||||
customer: '='
|
||||
},
|
||||
//...
|
||||
...
|
||||
```
|
||||
|
||||
Besides making it possible to bind different data to the scope inside a directive, using an isolated scope has another
|
||||
effect.
|
||||
Besides making it possible to bind different data to the scope inside a directive, using an isolated
|
||||
scope has another effect.
|
||||
|
||||
We can show this by adding another property, `vojta`, to our scope and trying to access it
|
||||
from within our directive's template:
|
||||
We can show this by adding another property, `vojta`, to our scope and trying to access it from
|
||||
within our directive's template:
|
||||
|
||||
<example module="docsIsolationExample">
|
||||
<file name="script.js">
|
||||
@@ -462,7 +478,7 @@ from within our directive's template:
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
customer: '=customer'
|
||||
customerInfo: '=info'
|
||||
},
|
||||
templateUrl: 'my-customer-plus-vojta.html'
|
||||
};
|
||||
@@ -470,11 +486,11 @@ from within our directive's template:
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<my-customer customer="naomi"></my-customer>
|
||||
<my-customer info="naomi"></my-customer>
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-customer-plus-vojta.html">
|
||||
Name: {{customer.name}} Address: {{customer.address}}
|
||||
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
|
||||
<hr>
|
||||
Name: {{vojta.name}} Address: {{vojta.address}}
|
||||
</file>
|
||||
@@ -504,7 +520,8 @@ 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:
|
||||
`link` takes a function with the following signature, `function link(scope, element, attrs) { ... }`
|
||||
where:
|
||||
|
||||
* `scope` is an Angular scope object.
|
||||
* `element` is the jqLite-wrapped element that this directive matches.
|
||||
@@ -565,8 +582,9 @@ if the directive is deleted so we don't introduce a memory leak.
|
||||
</example>
|
||||
|
||||
There are a couple of things to note here.
|
||||
Just like the `module.controller` API, the function argument in `module.directive` is dependency injected.
|
||||
Because of this, we can use `$timeout` and `dateFilter` inside our directive's `link` function.
|
||||
Just like the `module.controller` API, the function argument in `module.directive` is dependency
|
||||
injected. Because of this, we can use `$timeout` and `dateFilter` inside our directive's `link`
|
||||
function.
|
||||
|
||||
We register an event `element.on('$destroy', ...)`. What fires this `$destroy` event?
|
||||
|
||||
@@ -580,8 +598,9 @@ but if you registered a listener on a service, or registered a listener on a DOM
|
||||
being deleted, you'll have to clean it up yourself or you risk introducing a memory leak.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** Directives should clean up after themselves. You can use `element.on('$destroy', ...)`
|
||||
or `scope.$on('$destroy', ...)` to run a clean-up function when the directive is removed.
|
||||
**Best Practice:** Directives should clean up after themselves. You can use
|
||||
`element.on('$destroy', ...)` or `scope.$on('$destroy', ...)` to run a clean-up function when the
|
||||
directive is removed.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -619,11 +638,11 @@ To do this, we need to use the `transclude` option.
|
||||
</file>
|
||||
</example>
|
||||
|
||||
What does this `transclude` option do, exactly? `transclude` makes the contents of a directive with this
|
||||
option have access to the scope **outside** of the directive rather than inside.
|
||||
What does this `transclude` option do, exactly? `transclude` makes the contents of a directive with
|
||||
this option have access to the scope **outside** of the directive rather than inside.
|
||||
|
||||
To illustrate this, see the example below. Notice that we've added a `link` function in `script.js` that
|
||||
redefines `name` as `Jeff`. What do you think the `{{name}}` binding will resolve to now?
|
||||
To illustrate this, see the example below. Notice that we've added a `link` function in `script.js`
|
||||
that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will resolve to now?
|
||||
|
||||
<example module="docsTransclusionExample">
|
||||
<file name="script.js">
|
||||
@@ -669,11 +688,12 @@ pass in each model you wanted to use separately. If you have to pass in each mod
|
||||
use, then you can't really have arbitrary contents, can you?
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** only use `transclude: true` when you want to create a directive that wraps arbitrary content.
|
||||
**Best Practice:** only use `transclude: true` when you want to create a directive that wraps
|
||||
arbitrary content.
|
||||
</div>
|
||||
|
||||
Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their own
|
||||
behavior to it.
|
||||
Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their
|
||||
own behavior to it.
|
||||
|
||||
<example module="docsIsoFnBindExample">
|
||||
<file name="script.js">
|
||||
@@ -700,7 +720,7 @@ behavior to it.
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="dialogIsHidden = true">
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
|
||||
Check out the contents, {{name}}!
|
||||
</my-dialog>
|
||||
</div>
|
||||
@@ -893,5 +913,6 @@ point for creating your own directives.
|
||||
You might also be interested in an in-depth explanation of the compilation process that's
|
||||
available in the {@link guide/compiler compiler guide}.
|
||||
|
||||
The {@link api/ng.$compile `$compile` API} page has a comprehensive list of directive options for reference.
|
||||
The {@link api/ng.$compile `$compile` API} page has a comprehensive list of directive options for
|
||||
reference.
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ http://userguide.icu-project.org/locale ICU } website for more information about
|
||||
**Supported locales in Angular**
|
||||
Angular separates number and datetime format rule sets into different files, each file for a
|
||||
particular locale. You can find a list of currently supported locales {@link
|
||||
https://github.com/angular/angular.js/tree/master/i18n/locale here}
|
||||
https://github.com/angular/angular.js/tree/master/src/ngLocale here}
|
||||
# Providing locale rules to Angular
|
||||
|
||||
There are two approaches to providing locale rules to Angular:
|
||||
|
||||
@@ -8,6 +8,17 @@ This document describes the Internet Explorer (IE) idiosyncrasies when dealing w
|
||||
attributes and tags. Read this document if you are planning on deploying your Angular application
|
||||
on IE v8.0 or earlier.
|
||||
|
||||
The project currently supports and will attempt to fix bugs for IE8 and above. The continuous
|
||||
integration server runs all the tests against IE8. See http://ci.angularjs.org.
|
||||
|
||||
IE7 and below are not tested and the project makes no guarantee that Angular will work on it.
|
||||
A subset of the AngularJS functionality may work. It is up to you to test and decide whether
|
||||
it works for your particular app.
|
||||
|
||||
It is very unlikely that issues specific to IE7 or earlier will be given any time by the core team.
|
||||
[GitHub](https://github.com/angular/angular.js/issues/4974)
|
||||
|
||||
|
||||
# Short Version
|
||||
|
||||
To make your Angular application work on IE please make sure that:
|
||||
@@ -80,7 +91,7 @@ The **important** parts are:
|
||||
IE has issues with element tag names which are not standard HTML tag names. These fall into two
|
||||
categories, and each category has its own fix.
|
||||
|
||||
* If the tag name starts with `my:` prefix than it is considered an XML namespace and must
|
||||
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
|
||||
have corresponding namespace declaration on `<html xmlns:my="ignored">`
|
||||
|
||||
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
|
||||
|
||||
@@ -88,7 +88,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
* **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [Angular Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/)
|
||||
|
||||
## Complimentary Libraries
|
||||
## Complementary Libraries
|
||||
|
||||
This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/).
|
||||
|
||||
@@ -134,6 +134,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
|
||||
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
|
||||
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
|
||||
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/),
|
||||
@@ -164,6 +165,8 @@ The recipe for getting help on your unique issue is to create an example that co
|
||||
|
||||
* **Daily updates:** [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs)
|
||||
|
||||
* **Weekly newsletter:** [ng-newsletter](http://www.ng-newsletter.com/)
|
||||
|
||||
* **Meetups: **[meetup.com](http://www.meetup.com/find/?keywords=angularJS&radius=Infinity&userFreeform=San+Francisco%2C+CA&mcId=z94108&mcName=San+Francisco%2C+CA&sort=member_count&eventFilter=mysugg)
|
||||
|
||||
* **Official news and releases: **[AngularJS Blog](http://blog.angularjs.org/)
|
||||
|
||||
@@ -0,0 +1,651 @@
|
||||
@ngdoc overview
|
||||
@name Migrating from 1.0 to 1.2
|
||||
@description
|
||||
|
||||
|
||||
AngularJS version 1.2 introduces several breaking changes that may require changes to your
|
||||
application's source code.
|
||||
|
||||
Although we try to avoid breaking changes, there are some cases where it is unavoidable.
|
||||
AngularJS 1.2 has undergone a thourough security review to make applications safer by default,
|
||||
which has driven many of these changes. Several new features, especially animations, would not
|
||||
be possible without a few changes. Finally, some outstanding bugs were best fixed by changing
|
||||
an existing API.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<p>**Note:** AngularJS versions 1.1.x are considered "experimental" with breaking changes between minor releases.
|
||||
Version 1.2 is the result of several versions on the 1.1 branch, and has a stable API.</p>
|
||||
|
||||
<p>If you have an application on 1.1 and want to migrate it to 1.2, everything in the guide
|
||||
below should still apply, but you may want to consult the
|
||||
[changelog](https://github.com/angular/angular.js/blob/master/CHANGELOG.md) as well.</p>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">Summary of Breaking Changes</li>
|
||||
<li>{@link guide/migration#ngroute-has-been-moved-into-its-own-module ngRoute has been moved into its own module}</li>
|
||||
<li>{@link guide/migration#templates-no-longer-automatically-unwrap-promises Templates no longer automatically unwrap promises}</li>
|
||||
<li>{@link guide/migration#syntax-for-named-wildcard-parameters-changed-in Syntax for named wildcard parameters changed in <code>$route</code>}</li>
|
||||
<li>{@link guide/migration#you-can-only-bind-one-expression-to You can only bind one expression to <code>*[src]</code> or <code>*[ng-src]</code>}</li>
|
||||
<li>{@link guide/migration#interpolations-inside-dom-event-handlers-are-now-disallowed Interpolations inside DOM event handlers are now disallowed}</li>
|
||||
<li>{@link guide/migration#directives-cannot-end-with--start-or--end Directives cannot end with -start or -end}</li>
|
||||
<li>{@link guide/migration#in-$q,-promisealways-has-been-renamed-promisefinally In $q, promise.always has been renamed promise.finally}</li>
|
||||
<li>{@link guide/migration#ngmobile-is-now-ngtouch ngMobile is now ngTouch}</li>
|
||||
<li>{@link guide/migration#resource$then-has-been-removed resource.$then has been removed}</li>
|
||||
<li>{@link guide/migration#resource-methods-return-the-promise Resource methods return the promise}</li>
|
||||
<li>{@link guide/migration#resource-promises-are-resolved-with-the-resource-instance Resource promises are resolved with the resource instance}</li>
|
||||
<li>{@link guide/migration#$locationsearch-supports-multiple-keys $location.search supports multiple keys}</li>
|
||||
<li>{@link guide/migration#ngbindhtmlunsafe-has-been-removed-and-replaced-by-ngbindhtml ngBindHtmlUnsafe has been removed and replaced by ngBindHtml}</li>
|
||||
<li>{@link guide/migration#form-names-that-are-expressions-are-evaluated Form names that are expressions are evaluated}</li>
|
||||
<li>{@link guide/migration#hasownproperty-disallowed-as-an-input-name hasOwnProperty disallowed as an input name}</li>
|
||||
<li>{@link guide/migration#directives-order-of-postlink-functions-reversed Directives: Order of postLink functions reversed}</li>
|
||||
<li>{@link guide/migration#directive-priority Directive priority}</li>
|
||||
<li>{@link guide/migration#ngscenario ngScenario}</li>
|
||||
<li>{@link guide/migration#nginclude-and-ngview-replace-its-entire-element-on-update ngInclude and ngView replace its entire element on update}</li>
|
||||
<li>{@link guide/migration#urls-are-now-sanitized-against-a-whitelist URLs are now sanitized against a whitelist}</li>
|
||||
<li>{@link guide/migration#isolate-scope-only-exposed-to-directives-with-property Isolate scope only exposed to directives with <code>scope</code> property}</li>
|
||||
<li>{@link guide/migration#change-to-interpolation-priority Change to interpolation priority}</li>
|
||||
<li>{@link guide/migration#underscore-prefixed/suffixed-properties-are-non-bindable Underscore-prefixed/suffixed properties are non-bindable}</li>
|
||||
<li>{@link guide/migration#you-cannot-bind-to-select[multiple] You cannot bind to select[multiple]}</li>
|
||||
<li>{@link guide/migration#uncommon-region-specific-local-files-were-removed-from-i18n Uncommon region-specific local files were removed from i18n}</li>
|
||||
</ul>
|
||||
|
||||
|
||||
## ngRoute has been moved into its own module
|
||||
|
||||
Just like `ngResource`, `ngRoute` is now its own module.
|
||||
|
||||
Applications that use `$route`, `ngView`, and/or `$routeParams` will now need to load an
|
||||
`angular-route.js` file and have their application's module dependency on the `ngRoute` module.
|
||||
|
||||
Before:
|
||||
|
||||
```html
|
||||
<script src="angular.js"></script>
|
||||
```
|
||||
|
||||
```javascript
|
||||
var myApp = angular.module('myApp', ['someOtherModule']);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```html
|
||||
<script src="angular.js"></script>
|
||||
<script src="angular-route.js"></script>
|
||||
```
|
||||
|
||||
```javascript
|
||||
var myApp = angular.module('myApp', ['ngRoute', 'someOtherModule']);
|
||||
```
|
||||
|
||||
See [5599b55b](https://github.com/angular/angular.js/commit/5599b55b04788c2e327d7551a4a699d75516dd21).
|
||||
|
||||
|
||||
## Templates no longer automatically unwrap promises
|
||||
|
||||
`$parse` and templates in general will no longer automatically unwrap promises.
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
$scope.foo = $http({method: 'GET', url: '/someUrl'});
|
||||
```
|
||||
|
||||
```html
|
||||
<p>{{foo}}</p>
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
$http({method: 'GET', url: '/someUrl'})
|
||||
.success(function(data) {
|
||||
$scope.foo = data;
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<p>{{foo}}</p>
|
||||
```
|
||||
|
||||
This feature has been deprecated. If absolutely needed, it can be reenabled for now via the
|
||||
`$parseProvider.unwrapPromises(true)` API.
|
||||
|
||||
See [5dc35b52](https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577),
|
||||
[b6a37d11](https://github.com/angular/angular.js/commit/b6a37d112b3e1478f4d14a5f82faabf700443748).
|
||||
|
||||
|
||||
## Syntax for named wildcard parameters changed in `$route`
|
||||
|
||||
To migrate the code, follow the example below. Here, `*highlight` becomes `:highlight*`
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
$routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit',
|
||||
{controller: noop, templateUrl: 'Chapter.html'});
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
$routeProvider.when('/Book1/:book/Chapter/:chapter/:highlight*/edit',
|
||||
{controller: noop, templateUrl: 'Chapter.html'});
|
||||
```
|
||||
|
||||
See [04cebcc1](https://github.com/angular/angular.js/commit/04cebcc133c8b433a3ac5f72ed19f3631778142b).
|
||||
|
||||
|
||||
## You can only bind one expression to `*[src]` or `*[ng-src]`
|
||||
|
||||
With the exception of `<a>` and `<img>` elements, you cannot bind more than one expression to the
|
||||
`src` attribute of elements.
|
||||
|
||||
This is one of several improvements to security introduces by Angular 1.2.
|
||||
|
||||
Concatenating expressions makes it hard to understand whether some combination of concatenated
|
||||
values are unsafe to use and potentially subject to XSS vulnerabilities. To simplify the task of
|
||||
auditing for XSS issues, we now require that a single expression be used for `*[src/ng-src]`
|
||||
bindings such as bindings for `iframe[src]`, `object[src]`, etc.
|
||||
|
||||
<table class="table table-bordered code-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Examples</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code><img src="{{a}}/{{b}}"></code></td>
|
||||
<td class="success">ok</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><iframe src="{{a}}/{{b}}"></iframe></code></td>
|
||||
<td class="error">bad</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><iframe src="{{a}}"></iframe></code></td>
|
||||
<td class="success">ok</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
To migrate your code, you can combine multiple expressions using a method attached to your scope.
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
scope.baseUrl = 'page';
|
||||
scope.a = 1;
|
||||
scope.b = 2;
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Are a and b properly escaped here? Is baseUrl controlled by user? -->
|
||||
<iframe src="{{baseUrl}}?a={{a}&b={{b}}">
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
var baseUrl = "page";
|
||||
scope.getIframeSrc = function() {
|
||||
|
||||
// One should think about their particular case and sanitize accordingly
|
||||
var qs = ["a", "b"].map(function(value, name) {
|
||||
return encodeURIComponent(name) + "=" +
|
||||
encodeURIComponent(value);
|
||||
}).join("&");
|
||||
|
||||
// `baseUrl` isn't exposed to a user's control, so we don't have to worry about escaping it.
|
||||
return baseUrl + "?" + qs;
|
||||
};
|
||||
```
|
||||
|
||||
```html
|
||||
<iframe src="{{getIframeSrc()}}">
|
||||
```
|
||||
|
||||
See [38deedd6](https://github.com/angular/angular.js/commit/38deedd6e3d806eb8262bb43f26d47245f6c2739).
|
||||
|
||||
|
||||
## Interpolations inside DOM event handlers are now disallowed
|
||||
|
||||
DOM event handlers execute arbitrary Javascript code. Using an interpolation for such handlers
|
||||
means that the interpolated value is a JS string that is evaluated. Storing or generating such
|
||||
strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other
|
||||
Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which
|
||||
makes them much safer.
|
||||
|
||||
To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
JS: scope.foo = 'alert(1)';
|
||||
HTML: <div onclick="{{foo}}">
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
JS: scope.foo = function() { alert(1); }
|
||||
HTML: <div ng-click="foo()">
|
||||
```
|
||||
|
||||
See [39841f2e](https://github.com/angular/angular.js/commit/39841f2ec9b17b3b2920fd1eb548d444251f4f56).
|
||||
|
||||
|
||||
## Directives cannot end with -start or -end
|
||||
|
||||
This change was necessary to enable multi-element directives. The best fix is to rename existing
|
||||
directives so that they don't end with these suffixes.
|
||||
|
||||
See [e46100f7](https://github.com/angular/angular.js/commit/e46100f7097d9a8f174bdb9e15d4c6098395c3f2).
|
||||
|
||||
|
||||
## In $q, promise.always has been renamed promise.finally
|
||||
|
||||
The reason for this change is to align `$q` with the [Q promise
|
||||
library](https://github.com/kriskowal/q), despite the fact that this makes it a bit more difficult
|
||||
to use with non-ES5 browsers, like IE8.
|
||||
|
||||
`finally` also goes well together with the `catch` API that was added to `$q` recently and is part
|
||||
of the [DOM promises standard](http://dom.spec.whatwg.org/).
|
||||
|
||||
To migrate the code follow the example below.
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
$http.get('/foo').always(doSomething);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
$http.get('/foo').finally(doSomething);
|
||||
```
|
||||
|
||||
Or for IE8-compatible code:
|
||||
|
||||
```javascript
|
||||
$http.get('/foo')['finally'](doSomething);
|
||||
```
|
||||
|
||||
See [f078762d](https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c).
|
||||
|
||||
|
||||
## ngMobile is now ngTouch
|
||||
|
||||
Many touch-enabled devices are not mobile devices, so we decided to rename this module to better
|
||||
reflect its concerns.
|
||||
|
||||
To migrate, replace all references to `ngMobile` with `ngTouch` and `angular-mobile.js` with
|
||||
`angular-touch.js`.
|
||||
|
||||
See [94ec84e7](https://github.com/angular/angular.js/commit/94ec84e7b9c89358dc00e4039009af9e287bbd05).
|
||||
|
||||
|
||||
## resource.$then has been removed
|
||||
|
||||
Resource instances do not have a `$then` function anymore. Use the `$promise.then` instead.
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
Resource.query().$then(callback);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
Resource.query().$promise.then(callback);
|
||||
```
|
||||
|
||||
See [05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d).
|
||||
|
||||
|
||||
## Resource methods return the promise
|
||||
|
||||
Methods of a resource instance return the promise rather than the instance itself.
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
resource.$save().chaining = true;
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
resource.$save();
|
||||
resource.chaining = true;
|
||||
```
|
||||
|
||||
See [05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d).
|
||||
|
||||
|
||||
## Resource promises are resolved with the resource instance
|
||||
|
||||
On success, the resource promise is resolved with the resource instance rather than HTTP response object.
|
||||
|
||||
Use interceptor API to access the HTTP response object.
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
Resource.query().$then(function(response) {...});
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
var Resource = $resource('/url', {}, {
|
||||
get: {
|
||||
method: 'get',
|
||||
interceptor: {
|
||||
response: function(response) {
|
||||
// expose response
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
See [05772e15](https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d).
|
||||
|
||||
|
||||
## $location.search supports multiple keys
|
||||
|
||||
{@link api/ng.$location#methods_search `$location.search`} now supports multiple keys with the
|
||||
same value provided that the values are stored in an array.
|
||||
|
||||
Before this change:
|
||||
|
||||
* `parseKeyValue` only took the last key overwriting all the previous keys.
|
||||
* `toKeyValue` joined the keys together in a comma delimited string.
|
||||
|
||||
This was deemed buggy behavior. If your server relied on this behavior then either the server
|
||||
should be fixed, or a simple serialization of the array should be done on the client before
|
||||
passing it to `$location`.
|
||||
|
||||
See [80739409](https://github.com/angular/angular.js/commit/807394095b991357225a03d5fed81fea5c9a1abe).
|
||||
|
||||
|
||||
## ngBindHtmlUnsafe has been removed and replaced by ngBindHtml
|
||||
|
||||
`ngBindHtml` which has been moved from `ngSanitize` module to the core `ng` module.
|
||||
|
||||
`ngBindHtml` provides `ngBindHtmlUnsafe` like
|
||||
behavior (evaluate an expression and innerHTML the result into the DOM) when bound to the result
|
||||
of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanitized via
|
||||
`$sanitize` before being innerHTML'd. If the `$sanitize` service isn't available (`ngSanitize`
|
||||
module is not loaded) and the bound expression evaluates to a value that is not trusted an
|
||||
exception is thrown.
|
||||
|
||||
See [dae69473](https://github.com/angular/angular.js/commit/dae694739b9581bea5dbc53522ec00d87b26ae55).
|
||||
|
||||
|
||||
## Form names that are expressions are evaluated
|
||||
|
||||
If you have form names that will evaluate as an expression:
|
||||
|
||||
```
|
||||
<form name="ctrl.form">
|
||||
```
|
||||
|
||||
And if you are accessing the form from your controller:
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
function($scope) {
|
||||
$scope['ctrl.form'] // form controller instance
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
function($scope) {
|
||||
$scope.ctrl.form // form controller instance
|
||||
}
|
||||
```
|
||||
|
||||
This makes it possible to access a form from a controller using the new "controller as" syntax.
|
||||
Supporting the previous behavior offers no benefit.
|
||||
|
||||
See [8ea802a1](https://github.com/angular/angular.js/commit/8ea802a1d23ad8ecacab892a3a451a308d9c39d7).
|
||||
|
||||
|
||||
## hasOwnProperty disallowed as an input name
|
||||
|
||||
Inputs with name equal to `hasOwnProperty` are not allowed inside form or ngForm directives.
|
||||
|
||||
Before, inputs whose name was "hasOwnProperty" were quietly ignored and not added to the scope.
|
||||
Now a badname exception is thrown. Using "hasOwnProperty" for an input name would be very unusual
|
||||
and bad practice. To migrate, change your input name.
|
||||
|
||||
See [7a586e5c](https://github.com/angular/angular.js/commit/7a586e5c19f3d1ecc3fefef084ce992072ee7f60).
|
||||
|
||||
|
||||
## Directives: Order of postLink functions reversed
|
||||
|
||||
The order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute.
|
||||
|
||||
Previously the compile/link fns executed in order, sorted by priority:
|
||||
|
||||
<table class="table table-bordered table-striped code-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Step</th>
|
||||
<th align="center">Old Sort Order</th>
|
||||
<th align="center">New Sort Order</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Compile Fns</td>
|
||||
<td align="center" colspan="2">High → Low</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td colspan="3">Compile child nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>PreLink Fns</td>
|
||||
<td align="center" colspan="2">High → Low</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td colspan="3">Link child nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>PostLink Fns</td>
|
||||
<td align="center">High → Low</td>
|
||||
<td align="center">**Low → High**</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<small>"High → Low" here refers to the `priority` option of a directive.</small>
|
||||
|
||||
Very few directives in practice rely on the order of postLinking functions (unlike on the order
|
||||
of compile functions), so in the rare case of this change affecting an existing directive, it might
|
||||
be necessary to convert it to a preLinking function or give it negative priority.
|
||||
|
||||
You can look at [the diff of this
|
||||
commit](https://github.com/angular/angular.js/commit/31f190d4d53921d32253ba80d9ebe57d6c1de82b) to see how an internal
|
||||
attribute interpolation directive was adjusted.
|
||||
|
||||
See [31f190d4](https://github.com/angular/angular.js/commit/31f190d4d53921d32253ba80d9ebe57d6c1de82b).
|
||||
|
||||
|
||||
## Directive priority
|
||||
|
||||
the priority of ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView has changed. This could affect directives that explicitly specify their priority.
|
||||
|
||||
In order to make ngRepeat, ngSwitchWhen, ngIf, ngInclude and ngView work together in all common scenarios their directives are being adjusted to achieve the following precendence:
|
||||
|
||||
|
||||
Directive | Old Priority | New Priority
|
||||
-----------------|--------------|-------------
|
||||
ngRepeat | 1000 | 1000
|
||||
ngSwitchWhen | 500 | 800
|
||||
ngIf | 1000 | 600
|
||||
ngInclude | 1000 | 400
|
||||
ngView | 1000 | 400
|
||||
|
||||
See [b7af76b4](https://github.com/angular/angular.js/commit/b7af76b4c5aa77648cc1bfd49935b48583419023).
|
||||
|
||||
|
||||
## ngScenario
|
||||
|
||||
browserTrigger now uses an eventData object instead of direct parameters for mouse events.
|
||||
To migrate, place the `keys`,`x` and `y` parameters inside of an object and place that as the
|
||||
third parameter for the browserTrigger function.
|
||||
|
||||
See [28f56a38](https://github.com/angular/angular.js/commit/28f56a383e9d1ff378e3568a3039e941c7ffb1d8).
|
||||
|
||||
|
||||
## ngInclude and ngView replace its entire element on update
|
||||
|
||||
Previously `ngInclude` and `ngView` only updated its element's content. Now these directives will
|
||||
recreate the element every time a new content is included.
|
||||
|
||||
This ensures that a single rootElement for all the included contents always exists, which makes
|
||||
definition of css styles for animations much easier.
|
||||
|
||||
See [7d69d52a](https://github.com/angular/angular.js/commit/7d69d52acff8578e0f7d6fe57a6c45561a05b182),
|
||||
[aa2133ad](https://github.com/angular/angular.js/commit/aa2133ad818d2e5c27cbd3933061797096356c8a).
|
||||
|
||||
|
||||
## URLs are now sanitized against a whitelist
|
||||
|
||||
A whitelist configured via `$compileProvider` can be used to configure what URLs are considered safe.
|
||||
By default all common protocol prefixes are whitelisted including `data:` URIs with mime types `image/*`.
|
||||
This change sholdn't impact apps that don't contain malicious image links.
|
||||
|
||||
See [1adf29af](https://github.com/angular/angular.js/commit/1adf29af13890d61286840177607edd552a9df97),
|
||||
[3e39ac7e](https://github.com/angular/angular.js/commit/3e39ac7e1b10d4812a44dad2f959a93361cd823b).
|
||||
|
||||
|
||||
## Isolate scope only exposed to directives with `scope` property
|
||||
|
||||
Directives without isolate scope do not get the isolate scope from an isolate directive on the
|
||||
same element. If your code depends on this behavior (non-isolate directive needs to access state
|
||||
from within the isolate scope), change the isolate directive to use scope locals to pass these explicitly.
|
||||
|
||||
**Before**
|
||||
|
||||
```
|
||||
<input ng-model="$parent.value" ng-isolate>
|
||||
|
||||
.directive('ngIsolate', function() {
|
||||
return {
|
||||
scope: {},
|
||||
template: '{{value}}'
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```
|
||||
<input ng-model="value" ng-isolate>
|
||||
|
||||
.directive('ngIsolate', function() {
|
||||
return {
|
||||
scope: {value: '=ngModel'},
|
||||
template: '{{value}}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
See [909cabd3](https://github.com/angular/angular.js/commit/909cabd36d779598763cc358979ecd85bb40d4d7),
|
||||
[#1924](https://github.com/angular/angular.js/issues/1924) and
|
||||
[#2500](https://github.com/angular/angular.js/issues/2500).
|
||||
|
||||
|
||||
## Change to interpolation priority
|
||||
|
||||
Previously, the interpolation priority was `-100` in 1.2.0-rc.2, and `100` before 1.2.0-rc.2.
|
||||
Before this change the binding was setup in the post-linking phase.
|
||||
|
||||
Now the attribute interpolation (binding) executes as a directive with priority 100 and the
|
||||
binding is set up in the pre-linking phase.
|
||||
|
||||
See [79223eae](https://github.com/angular/angular.js/commit/79223eae5022838893342c42dacad5eca83fabe8),
|
||||
[#4525](https://github.com/angular/angular.js/issues/4525),
|
||||
[#4528](https://github.com/angular/angular.js/issues/4528), and
|
||||
[#4649](https://github.com/angular/angular.js/issues/4649)
|
||||
|
||||
## Underscore-prefixed/suffixed properties are non-bindable
|
||||
|
||||
This change introduces the notion of "private" properties (properties
|
||||
whose names begin and/or end with an underscore) on the scope chain.
|
||||
These properties will not be available to Angular expressions (i.e. {{
|
||||
}} interpolation in templates and strings passed to `$parse`) They are
|
||||
freely available to JavaScript code (as before).
|
||||
|
||||
**Motivation**
|
||||
|
||||
Angular expressions execute in a limited context. They do not have
|
||||
direct access to the global scope, `window`, `document` or the Function
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
sensitive APIs outside of the scope chain (in a closure or your
|
||||
controller.) That's easier said that done for two reasons:
|
||||
|
||||
1. JavaScript does not have a notion of private properties so if you need
|
||||
someone on the scope chain for JavaScript use, you also expose it to
|
||||
Angular expressions
|
||||
2. The new `controller as` syntax that's now in increased usage exposes the
|
||||
entire controller on the scope chain greatly increaing the exposed surface.
|
||||
|
||||
Though Angular expressions are written and controlled by the developer, they:
|
||||
|
||||
1. Typically deal with user input
|
||||
2. Don't get the kind of test coverage that JavaScript code would
|
||||
|
||||
This commit provides a way, via a naming convention, to allow restricting properties from
|
||||
controllers/scopes. This means Angular expressions can access only those properties that
|
||||
are actually needed by the expressions.
|
||||
|
||||
See [3d6a89e8](https://github.com/angular/angular.js/commit/3d6a89e8888b14ae5cb5640464e12b7811853c7e).
|
||||
|
||||
|
||||
## You cannot bind to select[multiple]
|
||||
|
||||
Switching between `select[single]` and `select[multiple]` has always been odd due to browser quirks.
|
||||
This feature never worked with two-way data-binding so it's not expected that anyone is using it.
|
||||
|
||||
If you are interested in properly adding this feature, please submit a pull request on Github.
|
||||
|
||||
See [d87fa004](https://github.com/angular/angular.js/commit/d87fa0042375b025b98c40bff05e5f42c00af114).
|
||||
|
||||
|
||||
## Uncommon region-specific local files were removed from i18n
|
||||
|
||||
AngularJS uses the Google Closure library's locale files. The following locales were removed from
|
||||
Closure, so Angular is not able to continue to support them:
|
||||
|
||||
`chr`, `cy`, `el-polyton`, `en-zz`, `fr-rw`, `fr-sn`, `fr-td`, `fr-tg`, `haw`, `it-ch`, `ln-cg`,
|
||||
`mo`, `ms-bn`, `nl-aw`, `nl-be`, `pt-ao`, `pt-gw`, `pt-mz`, `pt-st`, `ro-md`, `ru-md`, `ru-ua`,
|
||||
`sr-cyrl-ba`, `sr-cyrl-me`, `sr-cyrl`, `sr-latn-ba`, `sr-latn-me`, `sr-latn`, `sr-rs`, `sv-fi`,
|
||||
`sw-ke`, `ta-lk`, `tl-ph`, `ur-in`, `zh-hans-hk`, `zh-hans-mo`, `zh-hans-sg`, `zh-hans`,
|
||||
`zh-hant-hk`, `zh-hant-mo`, `zh-hant-tw`, `zh-hant`
|
||||
|
||||
Although these locales were removed from the official AngularJS repository, you can continue to
|
||||
load and use your copy of the locale file provided that you maintain it yourself.
|
||||
|
||||
See [6382e21f](https://github.com/angular/angular.js/commit/6382e21fb28541a2484ac1a241d41cf9fbbe9d2c).
|
||||
|
||||
@@ -228,7 +228,7 @@ myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
|
||||
// let's assume that the UnicornLauncher constructor was also changed to
|
||||
// accept and use the useTinfoilShielding argument
|
||||
return new UnicornLauncher(apiToken, useTinfoilShielding);
|
||||
}]);
|
||||
}];
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -118,21 +118,23 @@ inheritance, and child scopes prototypically inherit from their parents.
|
||||
This example illustrates scopes in application, and prototypical inheritance of properties. The example is followed by
|
||||
a diagram depicting the scope boundaries.
|
||||
|
||||
<div class="show-scope">
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<div class="show-scope-demo">
|
||||
<div ng-controller="GreetCtrl">
|
||||
Hello {{name}}!
|
||||
</div>
|
||||
<div ng-controller="ListCtrl">
|
||||
<ol>
|
||||
<li ng-repeat="name in names">{{name}}</li>
|
||||
<li ng-repeat="name in names">{{name}} from {{department}}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
function GreetCtrl($scope) {
|
||||
function GreetCtrl($scope, $rootScope) {
|
||||
$scope.name = 'World';
|
||||
$rootScope.department = 'Angular';
|
||||
}
|
||||
|
||||
function ListCtrl($scope) {
|
||||
@@ -140,20 +142,19 @@ a diagram depicting the scope boundaries.
|
||||
}
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.show-scope .doc-example-live.ng-scope,
|
||||
.show-scope .doc-example-live .ng-scope {
|
||||
.show-scope-demo.ng-scope,
|
||||
.show-scope-demo .ng-scope {
|
||||
border: 1px solid red;
|
||||
margin: 3px;
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
</div>
|
||||
|
||||
<img class="center" src="img/guide/concepts-scope.png">
|
||||
|
||||
Notice that Angular automatically places `ng-scope` class on elements where scopes are
|
||||
attached. The `<style>` definition in this example highlights in red the new scope locations. The
|
||||
child scopes are necessary because the repeater evaluates `{{employee.name}}` expression, but
|
||||
child scopes are necessary because the repeater evaluates `{{name}}` expression, but
|
||||
depending on which scope the expression is evaluated it produces different result. Similarly the
|
||||
evaluation of `{{department}}` prototypically inherits from root scope, as it is the only place
|
||||
where the `department` property is defined.
|
||||
|
||||
@@ -11,12 +11,12 @@ See the [contributing guidelines](https://github.com/angular/angular.js/blob/mas
|
||||
for how to contribute your own code to AngularJS.
|
||||
|
||||
|
||||
1. <a href="#developing-angularjs_installing-dependencies">Installing Dependencies</a>
|
||||
2. <a href="#developing-angularjs_forking-angular-on-github">Forking Angular on Github</a>
|
||||
3. <a href="#developing-angularjs_building-angularjs">Building AngularJS</a>
|
||||
4. <a href="#developing-angularjs_running-a-local-development-web-server">Running a Local Development Web Server</a>
|
||||
5. <a href="#developing-angularjs_running-the-unit-test-suite">Running the Unit Test Suite</a>
|
||||
6. <a href="#developing-angularjs_running-the-end-to-end-test-suite">Running the End-to-end Test Suite</a>
|
||||
1. {@link #building-and-testing-angularjs_installing-dependencies Installing Dependencies}
|
||||
2. {@link #building-and-testing-angularjs_forking-angular-on-github Forking Angular on Github}
|
||||
3. {@link #building-and-testing-angularjs_building-angularjs Building AngularJS}
|
||||
4. {@link #building-and-testing-angularjs_running-a-local-development-web-server Running a Local Development Web Server}
|
||||
5. {@link #building-and-testing-angularjs_running-the-unit-test-suite Running the Unit Test Suite}
|
||||
6. {@link #building-and-testing-angularjs_running-the-end-to-end-test-suite Running the End-to-end Test Suite}
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
@@ -81,8 +81,8 @@ grunt package
|
||||
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** If you're using Windows you must run your command line with administrative privileges (right click, run as
|
||||
Administrator).
|
||||
**Note:** If you're using Windows, you must use an elevated command prompt (right click, run as
|
||||
Administrator). This is because `grunt package` creates some symbolic links.
|
||||
</div>
|
||||
|
||||
The build output can be located under the `build` directory. It consists of the following files and
|
||||
|
||||
@@ -9,20 +9,20 @@ This way, you don't have to download anything or maintain a local copy.
|
||||
There are two types of angular script URLs you can point to, one for development and one for
|
||||
production:
|
||||
|
||||
* __angular-<version>.js__ — This is the human-readable, non-minified version, suitable for web
|
||||
* __angular.js__ — This is the human-readable, non-minified version, suitable for web
|
||||
development.
|
||||
* __angular-<version>.min.js__ — This is the minified version, which we strongly suggest you use in
|
||||
* __angular.min.js__ — This is the minified version, which we strongly suggest you use in
|
||||
production.
|
||||
|
||||
To point your code to an angular script on the Google CDN server, use the following template. This
|
||||
example points to the minified version 1.0.2:
|
||||
example points to the minified version 1.2.0:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
@@ -58,7 +58,7 @@ for this angular version. Use this file to get everything in a single download.
|
||||
|
||||
* __`angular-mocks.js`__ — This file contains an implementation of mocks that makes
|
||||
testing angular apps even easier. Your unit/integration test harness should load this file after
|
||||
`angular-<version>.js` is loaded.
|
||||
`angular.js` is loaded.
|
||||
|
||||
* __`angular-scenario.js`__ — This file is a very nifty JavaScript file that allows you
|
||||
to write and execute end-to-end tests for angular applications.
|
||||
@@ -68,8 +68,19 @@ to write and execute end-to-end tests for angular applications.
|
||||
contents of this file are copy&pasted into the `index.html` to avoid even the initial request to `angular-loader.min.js`.
|
||||
See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for an example of usage.
|
||||
|
||||
* __`angular-resource.js`__, __`angular-cookies.js`__, etc - extra Angular modules with additional functionality.
|
||||
* __Additional Angular modules:__ optional modules with additional functionality. These files should be loaded
|
||||
after the core `angular.js` file:
|
||||
* __`angular-animate.js`__ - Enable animation support
|
||||
* __`angular-cookies.js`__ - A convenient wrapper for reading and writing browser cookies
|
||||
* __`angular-resource.js`__ - Interaction support with RESTful services via the $resource service
|
||||
* __`angular-route.js`__ - Routing and deeplinking services and directives for angular apps
|
||||
* __`angular-sanitize.js`__ - Functionality to sanitize HTML
|
||||
* __`angular-touch.js`__ - Touch events and other helpers for touch-enabled devices
|
||||
|
||||
|
||||
* __`docs`__ — this directory contains all the files that compose the
|
||||
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
|
||||
our docs, or even more importantly, view the docs offline.
|
||||
|
||||
* __`i18n`__ - this directory contains locale specific `ngLocale` angular modules to override the defaults
|
||||
defined in the `ng` module.
|
||||
@@ -67,7 +67,7 @@ illustration we typically build snappy apps with hundreds or thousands of active
|
||||
|
||||
### How big is the angular.js file that I need to include?
|
||||
|
||||
The size of the file is < 29KB compressed and minified.
|
||||
The size of the file is < 36KB compressed and minified.
|
||||
|
||||
|
||||
### Can I use the open-source Closure Library with Angular?
|
||||
@@ -81,7 +81,7 @@ Yes, Angular can use {@link http://jquery.com/ jQuery} if it's present in your a
|
||||
application is being bootstrapped. If jQuery is not present in your script path, Angular falls back
|
||||
to its own implementation of the subset of jQuery that we call {@link api/angular.element jQLite}.
|
||||
|
||||
Due to a change to use `on()`/`off()` rather than `bind()`\`unbind()`, Angular 1.2 only operates with
|
||||
Due to a change to use `on()`/`off()` rather than `bind()`/`unbind()`, Angular 1.2 only operates with
|
||||
jQuery 1.7.1 or above.
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ directory.</p></li>
|
||||
<p>The tutorial instructions, from now on, assume you are running all commands from the <code>angular-phonecat</code>
|
||||
directory.</p></li>
|
||||
<li><p>You will also need Node.js and Karma to run unit tests, so please verify that you have
|
||||
<a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
|
||||
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed
|
||||
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
|
||||
command in a terminal window:</p></li>
|
||||
<pre>node --version</pre>
|
||||
@@ -85,7 +85,7 @@ to run <code>scripts/web-server.js</code>, a simple bundled http server.</p></l
|
||||
<div class="tab-pane well" id="git-win" title="Git on Windows">
|
||||
<ol>
|
||||
<li><p>You will need Node.js and Karma to run unit tests, so please verify that you have
|
||||
<a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
|
||||
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed
|
||||
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
|
||||
command in a terminal window:</p>
|
||||
<pre>node --version</pre>
|
||||
|
||||
@@ -81,7 +81,7 @@ __`app/js/controllers.js`:__
|
||||
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope) {
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
||||
$scope.phones = [
|
||||
{'name': 'Nexus S',
|
||||
'snippet': 'Fast just got faster with Nexus S.'},
|
||||
@@ -104,7 +104,7 @@ the model and the view. We connected the dots between the presentation, data, an
|
||||
as follows:
|
||||
|
||||
* The {@link api/ng.directive:ngController ngController} directive, located on the `<body>` tag,
|
||||
references the the name of our controller, `PhoneListCtrl` (located in the JavaScript file
|
||||
references the name of our controller, `PhoneListCtrl` (located in the JavaScript file
|
||||
`controllers.js`).
|
||||
|
||||
* The `PhoneListCtrl` controller attaches the phone data to the `$scope` that was injected into our
|
||||
@@ -180,8 +180,8 @@ is available to be injected.
|
||||
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
|
||||
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
|
||||
this tutorial in Jasmine. You can learn about Jasmine on the {@link
|
||||
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
|
||||
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
|
||||
http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link
|
||||
http://pivotal.github.io/jasmine/ Jasmine docs}.
|
||||
|
||||
The angular-seed project is pre-configured to run all unit tests using {@link
|
||||
http://karma-runner.github.io/ Karma}. To run the test, do the following:
|
||||
|
||||
@@ -67,7 +67,7 @@ __`app/js/controllers.js`:__
|
||||
<pre>
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope) {
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope) {
|
||||
$scope.phones = [
|
||||
{'name': 'Nexus S',
|
||||
'snippet': 'Fast just got faster with Nexus S.',
|
||||
@@ -112,11 +112,12 @@ describe('PhoneCat controllers', function() {
|
||||
describe('PhoneListCtrl', function(){
|
||||
var scope, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = {},
|
||||
ctrl = new PhoneListCtrl(scope);
|
||||
});
|
||||
|
||||
beforeEach(module('phonecatApp'));
|
||||
|
||||
beforeEach(inject(function($controller) {
|
||||
scope = {};
|
||||
ctrl = $controller('PhoneListCtrl', {$scope:scope});
|
||||
}));
|
||||
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
expect(scope.phones.length).toBe(3);
|
||||
|
||||
@@ -56,7 +56,7 @@ __`app/js/controllers.js:`__
|
||||
<pre>
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', function PhoneListCtrl($scope, $http) {
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
@@ -79,7 +79,7 @@ json response and parsed it for us!
|
||||
To use a service in angular, you simply declare the names of the dependencies you need as arguments
|
||||
to the controller's constructor function, as follows:
|
||||
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}
|
||||
|
||||
Angular's dependency injector provides services to your controller when the controller is being
|
||||
constructed. The dependency injector also takes care of creating any transitive dependencies the
|
||||
@@ -145,7 +145,7 @@ __`app/js/controllers.js:`__
|
||||
var phonecatApp = angular.module('phonecatApp', []);
|
||||
|
||||
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
function PhoneListCtrl($scope, $http) {
|
||||
function ($scope, $http) {
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
|
||||
@@ -149,7 +149,7 @@ __`app/js/controllers.js`:__
|
||||
var phonecatControllers = angular.module('phonecatControllers', []);
|
||||
|
||||
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
|
||||
function PhoneListCtrl($scope, $http) {
|
||||
function ($scope, $http) {
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
|
||||
@@ -194,7 +194,7 @@ This time, instead of the `ng-repeat` element, let's add it to the element conta
|
||||
In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our
|
||||
animations between view changes.
|
||||
|
||||
__`app/partials/phone-list.html`.__
|
||||
__`app/index.html`.__
|
||||
<pre>
|
||||
<div class="view-container">
|
||||
<div ng-view class="view-frame"></div>
|
||||
|
||||
@@ -34,8 +34,8 @@ describe('ngdoc', function() {
|
||||
var d1 = new Doc('@name a.b.c').parse();
|
||||
var d2 = new Doc('@name a.b.ng-c').parse();
|
||||
var d3 = new Doc('@name some text: more text').parse();
|
||||
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
|
||||
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng-c');
|
||||
expect(ngdoc.metadata([d1])[0].shortName).toEqual('a.b.c');
|
||||
expect(ngdoc.metadata([d2])[0].shortName).toEqual('a.b.ng-c');
|
||||
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('Docs Links', function() {
|
||||
});
|
||||
|
||||
it('should have an "view source" button', function() {
|
||||
spyOn(gruntUtil, 'getVersion').andReturn({cdn: '1.2.299'});
|
||||
spyOn(gruntUtil, 'getVersion').andReturn({full: '1.2.299'});
|
||||
|
||||
expect(doc.html()).
|
||||
toContain('<a href="http://github.com/angular/angular.js/tree/v1.2.299/test.js#L42" class="view-source btn btn-action"><i class="icon-zoom-in"> </i> View source</a>');
|
||||
|
||||
+27
-18
@@ -53,6 +53,7 @@ exports.ngVersions = function() {
|
||||
return expandVersions(sortVersionsNatrually(versions), exports.ngCurrentVersion().full);
|
||||
|
||||
function expandVersions(versions, latestVersion) {
|
||||
var RC_VERSION = /rc\d/;
|
||||
//copy the array to avoid changing the versions param data
|
||||
//the latest version is not on the git tags list, but
|
||||
//docs.angularjs.org will always point to master as of 1.2
|
||||
@@ -63,20 +64,10 @@ exports.ngVersions = function() {
|
||||
var version = versions[i],
|
||||
split = version.split('.'),
|
||||
isMaster = version == latestVersion,
|
||||
isStable = split[1] % 2 == 0;
|
||||
isStable = split[1] % 2 === 0 && !RC_VERSION.test(version);
|
||||
|
||||
var title = 'AngularJS - v' + version;
|
||||
|
||||
//anything that is stable before being unstable is a rc1 version
|
||||
//just like with AngularJS 1.2.0rc1 (even though it's apart of the
|
||||
//1.1.5 API
|
||||
if(isMaster || (isStable && !firstUnstable)) {
|
||||
isStable = false;
|
||||
}
|
||||
else {
|
||||
firstUnstable = firstUnstable || version;
|
||||
}
|
||||
|
||||
var docsPath = version < '1.0.2' ? 'docs-' + version : 'docs';
|
||||
|
||||
var url = isMaster ?
|
||||
@@ -556,10 +547,22 @@ Doc.prototype = {
|
||||
self = this,
|
||||
minerrMsg;
|
||||
|
||||
var gitTagFromFullVersion = function(version) {
|
||||
var match = version.match(/-(\w{7})/);
|
||||
|
||||
if (match) {
|
||||
// git sha
|
||||
return match[1];
|
||||
}
|
||||
|
||||
// git tag
|
||||
return 'v' + version;
|
||||
};
|
||||
|
||||
if (this.section === 'api') {
|
||||
dom.tag('a', {
|
||||
href: 'http://github.com/angular/angular.js/tree/v' +
|
||||
gruntUtil.getVersion().cdn + '/' + self.file + '#L' + self.line,
|
||||
href: 'http://github.com/angular/angular.js/tree/' +
|
||||
gitTagFromFullVersion(gruntUtil.getVersion().full) + '/' + self.file + '#L' + self.line,
|
||||
class: 'view-source btn btn-action' }, function(dom) {
|
||||
dom.tag('i', {class:'icon-zoom-in'}, ' ');
|
||||
dom.text(' View source');
|
||||
@@ -1108,7 +1111,7 @@ function scenarios(docs){
|
||||
function metadata(docs){
|
||||
var pages = [];
|
||||
docs.forEach(function(doc){
|
||||
var path = (doc.name || '').split(/(\.|\:\s*)/);
|
||||
var path = (doc.name || '').split(/(\:\s*)/);
|
||||
for ( var i = 1; i < path.length; i++) {
|
||||
path.splice(i, 1);
|
||||
}
|
||||
@@ -1383,10 +1386,16 @@ function explainModuleInstallation(moduleName){
|
||||
' <script src="angular.js">\n' +
|
||||
' <script src="' + modulePackageFile + '"></pre></code>' +
|
||||
|
||||
'<p>You can also find this file on the [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs), ' +
|
||||
'<a href="http://bower.io/">Bower</a> (as <code>' + modulePackage + '</code>), ' +
|
||||
'and on <a href="http://code.angularjs.org/">code.angularjs.org</a>.</p>' +
|
||||
|
||||
'<p>You can download this file from the following places:</p>' +
|
||||
'<ul>' +
|
||||
'<li>[Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs)<br>' +
|
||||
'e.g. <code>"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/' + modulePackageFile + '"</code></li>' +
|
||||
'<li>[Bower](http://bower.io)<br>' +
|
||||
'e.g. <code>bower install ' + modulePackage + '@X.Y.Z</code></li>' +
|
||||
'<li><a href="http://code.angularjs.org/">code.angularjs.org</a><br>' +
|
||||
'e.g. <code>"//code.angularjs.org/X.Y.Z/' + modulePackageFile + '"</code></li>' +
|
||||
'</ul>' +
|
||||
'<p>where X.Y.Z is the AngularJS version you are running.</p>' +
|
||||
'<p>Then load the module in your application by adding it as a dependent module:</p><pre><code>' +
|
||||
' angular.module(\'app\', [\'' + ngMod + '\']);</pre></code>' +
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ module.exports = function(config, specificOptions) {
|
||||
logLevel: config.LOG_INFO,
|
||||
logColors: true,
|
||||
browsers: ['Chrome'],
|
||||
browserDisconnectTimeout: 5000,
|
||||
browserDisconnectTimeout: 10000,
|
||||
|
||||
|
||||
// config for Travis CI
|
||||
|
||||
+2
-2
@@ -112,7 +112,7 @@ module.exports = {
|
||||
var processed = src
|
||||
.replace(/"NG_VERSION_FULL"/g, NG_VERSION.full)
|
||||
.replace(/"NG_VERSION_MAJOR"/, NG_VERSION.major)
|
||||
.replace(/"NG_VERSION_ MINOR"/, NG_VERSION.minor)
|
||||
.replace(/"NG_VERSION_MINOR"/, NG_VERSION.minor)
|
||||
.replace(/"NG_VERSION_DOT"/, NG_VERSION.dot)
|
||||
.replace(/"NG_VERSION_CDN"/, NG_VERSION.cdn)
|
||||
.replace(/"NG_VERSION_CODENAME"/, NG_VERSION.codename);
|
||||
@@ -168,7 +168,7 @@ module.exports = {
|
||||
var mapFile = minFile + '.map';
|
||||
var mapFileName = mapFile.match(/[^\/]+$/)[0];
|
||||
var errorFileName = file.replace(/\.js$/, '-errors.json');
|
||||
var versionNumber = this.getVersion().number;
|
||||
var versionNumber = this.getVersion().full;
|
||||
shell.exec(
|
||||
'java ' +
|
||||
this.java32flags() + ' ' +
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"version": "1.2.0",
|
||||
"cdnVersion": "1.2.0-rc.3",
|
||||
"codename": "timely-delivery",
|
||||
"version": "1.2.2",
|
||||
"cdnVersion": "1.2.1",
|
||||
"codename": "consciousness-inertia",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -28,9 +28,9 @@
|
||||
"karma-sauce-launcher": "~0.1.1",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"yaml-js": "~0.0.8",
|
||||
"marked": "~0.2.9",
|
||||
"marked": "0.2.9",
|
||||
"rewire": "1.1.3",
|
||||
"grunt-jasmine-node": "~0.1.0",
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
|
||||
+1
-1
@@ -162,4 +162,4 @@
|
||||
"nullFormCtrl": false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-17
@@ -80,7 +80,7 @@
|
||||
-assertArgFn,
|
||||
-assertNotHasOwnProperty,
|
||||
-getter,
|
||||
-getBlockElements
|
||||
-getBlockElements,
|
||||
|
||||
*/
|
||||
|
||||
@@ -1102,26 +1102,38 @@ function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Use this directive to auto-bootstrap an application. Only
|
||||
* one ngApp directive can be used per HTML document. The directive
|
||||
* designates the root of the application and is typically placed
|
||||
* at the root of the page.
|
||||
* Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
|
||||
* designates the **root element** of the application and is typically placed near the root element
|
||||
* of the page - e.g. on the `<body>` or `<html>` tags.
|
||||
*
|
||||
* The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in
|
||||
* an HTML document you must manually bootstrap them using {@link angular.bootstrap}.
|
||||
* Applications cannot be nested.
|
||||
* Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
|
||||
* found in the document will be used to define the root element to auto-bootstrap as an
|
||||
* application. To run multiple applications in an HTML document you must manually bootstrap them using
|
||||
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
|
||||
*
|
||||
* In the example below if the `ngApp` directive were not placed
|
||||
* on the `html` element then the document would not be compiled
|
||||
* and the `{{ 1+2 }}` would not be resolved to `3`.
|
||||
* You can specify an **AngularJS module** to be used as the root module for the application. This
|
||||
* module will be loaded into the {@link AUTO.$injector} when the application is bootstrapped and
|
||||
* should contain the application code needed or have dependencies on other modules that will
|
||||
* contain the code. See {@link angular.module} for more information.
|
||||
*
|
||||
* `ngApp` is the easiest way to bootstrap an application.
|
||||
* In the example below if the `ngApp` directive were not placed on the `html` element then the
|
||||
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
|
||||
* would not be resolved to `3`.
|
||||
*
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
I can add: 1 + 2 = {{ 1+2 }}
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
* `ngApp` is the easiest, and most common, way to bootstrap an application.
|
||||
*
|
||||
<example module="ngAppDemo">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ngAppDemoController">
|
||||
I can add: {{a}} + {{b}} = {{ a+b }}
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
|
||||
$scope.a = 1;
|
||||
$scope.b = 2;
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
*/
|
||||
function angularInit(element, bootstrap) {
|
||||
|
||||
@@ -501,11 +501,11 @@ function annotate(fn) {
|
||||
* @example
|
||||
* Here are some examples of creating value services.
|
||||
* <pre>
|
||||
* $provide.constant('ADMIN_USER', 'admin');
|
||||
* $provide.value('ADMIN_USER', 'admin');
|
||||
*
|
||||
* $provide.constant('RoleLookup', { admin: 0, writer: 1, reader: 2 });
|
||||
* $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
|
||||
*
|
||||
* $provide.constant('halfOf', function(value) {
|
||||
* $provide.value('halfOf', function(value) {
|
||||
* return value / 2;
|
||||
* });
|
||||
* </pre>
|
||||
|
||||
+13
-1
@@ -11,12 +11,18 @@
|
||||
function setupModuleLoader(window) {
|
||||
|
||||
var $injectorMinErr = minErr('$injector');
|
||||
var ngMinErr = minErr('ng');
|
||||
|
||||
function ensure(obj, name, factory) {
|
||||
return obj[name] || (obj[name] = factory());
|
||||
}
|
||||
|
||||
return ensure(ensure(window, 'angular', Object), 'module', function() {
|
||||
var angular = ensure(window, 'angular', Object);
|
||||
|
||||
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
|
||||
angular.$$minErr = angular.$$minErr || minErr;
|
||||
|
||||
return ensure(angular, 'module', function() {
|
||||
/** @type {Object.<string, angular.Module>} */
|
||||
var modules = {};
|
||||
|
||||
@@ -71,6 +77,12 @@ function setupModuleLoader(window) {
|
||||
* @returns {module} new module with the {@link angular.Module} api.
|
||||
*/
|
||||
return function module(name, requires, configFn) {
|
||||
var assertNotHasOwnProperty = function(name, context) {
|
||||
if (name === 'hasOwnProperty') {
|
||||
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
|
||||
}
|
||||
};
|
||||
|
||||
assertNotHasOwnProperty(name, 'module');
|
||||
if (requires && modules.hasOwnProperty(name)) {
|
||||
modules[name] = null;
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
* License: MIT
|
||||
*/
|
||||
'use strict';
|
||||
(
|
||||
(function() {
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
)(window);
|
||||
setupModuleLoader(window);
|
||||
})(window);
|
||||
|
||||
/**
|
||||
* Closure compiler type information
|
||||
|
||||
+7
-7
@@ -35,11 +35,11 @@ function minErr(module) {
|
||||
template = arguments[1],
|
||||
templateArgs = arguments,
|
||||
stringify = function (obj) {
|
||||
if (isFunction(obj)) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (isUndefined(obj)) {
|
||||
} else if (typeof obj === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (!isString(obj)) {
|
||||
} else if (typeof obj !== 'string') {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
return obj;
|
||||
@@ -51,11 +51,11 @@ function minErr(module) {
|
||||
|
||||
if (index + 2 < templateArgs.length) {
|
||||
arg = templateArgs[index + 2];
|
||||
if (isFunction(arg)) {
|
||||
if (typeof arg === 'function') {
|
||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
||||
} else if (isUndefined(arg)) {
|
||||
} else if (typeof arg === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (!isString(arg)) {
|
||||
} else if (typeof arg !== 'string') {
|
||||
return toJson(arg);
|
||||
}
|
||||
return arg;
|
||||
@@ -63,7 +63,7 @@ function minErr(module) {
|
||||
return match;
|
||||
});
|
||||
|
||||
message = message + '\nhttp://errors.angularjs.org/' + version.full + '/' +
|
||||
message = message + '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
|
||||
(module ? module + '/' : '') + code;
|
||||
for (i = 2; i < arguments.length; i++) {
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
||||
|
||||
+8
-7
@@ -99,13 +99,14 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* inserted into the DOM
|
||||
*/
|
||||
enter : function(element, parent, after, done) {
|
||||
var afterNode = after && after[after.length - 1];
|
||||
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
|
||||
// IE does not like undefined so we have to pass null.
|
||||
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
|
||||
forEach(element, function(node) {
|
||||
parentNode.insertBefore(node, afterNextSibling);
|
||||
});
|
||||
if (after) {
|
||||
after.after(element);
|
||||
} else {
|
||||
if (!parent || !parent[0]) {
|
||||
parent = after.parent();
|
||||
}
|
||||
parent.append(element);
|
||||
}
|
||||
done && $timeout(done, 0, false);
|
||||
},
|
||||
|
||||
|
||||
+196
-118
@@ -115,8 +115,9 @@
|
||||
* When there are multiple directives defined on a single DOM element, sometimes it
|
||||
* is necessary to specify the order in which the directives are applied. The `priority` is used
|
||||
* to sort the directives before their `compile` functions get called. Priority is defined as a
|
||||
* number. Directives with greater numerical `priority` are compiled first. The order of directives with
|
||||
* the same priority is undefined. The default priority is `0`.
|
||||
* number. Directives with greater numerical `priority` are compiled first. Pre-link functions
|
||||
* are also run in priority order, but post-link functions are run in reverse order. The order
|
||||
* of directives with the same priority is undefined. The default priority is `0`.
|
||||
*
|
||||
* #### `terminal`
|
||||
* If set to true then the current `priority` will be the last set of directives
|
||||
@@ -177,8 +178,9 @@
|
||||
* * `$scope` - Current scope associated with the element
|
||||
* * `$element` - Current element
|
||||
* * `$attrs` - Current attributes object for the element
|
||||
* * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
|
||||
* `function(cloneLinkingFn)`.
|
||||
* * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope.
|
||||
* The scope can be overridden by an optional first argument.
|
||||
* `function([scope], cloneLinkingFn)`.
|
||||
*
|
||||
*
|
||||
* #### `require`
|
||||
@@ -271,7 +273,7 @@
|
||||
* * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
|
||||
* between all directive compile functions.
|
||||
*
|
||||
* * `transclude` - A transclude linking function: `function(scope, cloneLinkingFn)`.
|
||||
* * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** The template instance and the link instance may be different objects if the template has
|
||||
@@ -280,6 +282,12 @@
|
||||
* should be done in a linking function rather than in a compile function.
|
||||
* </div>
|
||||
*
|
||||
* <div class="alert alert-error">
|
||||
* **Note:** The `transclude` function that is passed to the compile function is deperecated, as it
|
||||
* e.g. does not know about the right outer scope. Please use the transclude function that is passed
|
||||
* to the link function instead.
|
||||
* </div>
|
||||
|
||||
* A compile function can have a return value which can be either a function or an object.
|
||||
*
|
||||
* * returning a (post-link) function - is equivalent to registering the linking function via the
|
||||
@@ -294,7 +302,7 @@
|
||||
* This property is used only if the `compile` property is not defined.
|
||||
*
|
||||
* <pre>
|
||||
* function link(scope, iElement, iAttrs, controller) { ... }
|
||||
* function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
|
||||
* </pre>
|
||||
*
|
||||
* The link function is responsible for registering DOM listeners as well as updating the DOM. It is
|
||||
@@ -315,6 +323,10 @@
|
||||
* element defines a controller. The controller is shared among all the directives, which allows
|
||||
* the directives to use the controllers as a communication channel.
|
||||
*
|
||||
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
|
||||
* The scope can be overridden by an optional first argument. This is the same as the `$transclude`
|
||||
* parameter of directive controllers.
|
||||
* `function([scope], cloneLinkingFn)`.
|
||||
*
|
||||
*
|
||||
* #### Pre-linking function
|
||||
@@ -660,6 +672,24 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name ng.$compile.directive.Attributes#$updateClass
|
||||
* @methodOf ng.$compile.directive.Attributes
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Adds and removes the appropriate CSS class values to the element based on the difference
|
||||
* between the new and old CSS class values (specified as newClasses and oldClasses).
|
||||
*
|
||||
* @param {string} newClasses The current CSS className value
|
||||
* @param {string} oldClasses The former CSS className value
|
||||
*/
|
||||
$updateClass : function(newClasses, oldClasses) {
|
||||
this.$removeClass(tokenDifference(oldClasses, newClasses));
|
||||
this.$addClass(tokenDifference(newClasses, oldClasses));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a normalized attribute on the element in a way such that all directives
|
||||
* can share the attribute. This function properly handles boolean attributes.
|
||||
@@ -670,59 +700,53 @@ function $CompileProvider($provide) {
|
||||
* @param {string=} attrName Optional none normalized name. Defaults to key.
|
||||
*/
|
||||
$set: function(key, value, writeAttr, attrName) {
|
||||
//special case for class attribute addition + removal
|
||||
//so that class changes can tap into the animation
|
||||
//hooks provided by the $animate service
|
||||
if(key == 'class') {
|
||||
value = value || '';
|
||||
var current = this.$$element.attr('class') || '';
|
||||
this.$removeClass(tokenDifference(current, value).join(' '));
|
||||
this.$addClass(tokenDifference(value, current).join(' '));
|
||||
// TODO: decide whether or not to throw an error if "class"
|
||||
//is set through this function since it may cause $updateClass to
|
||||
//become unstable.
|
||||
|
||||
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
||||
normalizedVal,
|
||||
nodeName;
|
||||
|
||||
if (booleanKey) {
|
||||
this.$$element.prop(key, value);
|
||||
attrName = booleanKey;
|
||||
}
|
||||
|
||||
this[key] = value;
|
||||
|
||||
// translate normalized key to actual key
|
||||
if (attrName) {
|
||||
this.$attr[key] = attrName;
|
||||
} else {
|
||||
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
||||
normalizedVal,
|
||||
nodeName;
|
||||
|
||||
if (booleanKey) {
|
||||
this.$$element.prop(key, value);
|
||||
attrName = booleanKey;
|
||||
attrName = this.$attr[key];
|
||||
if (!attrName) {
|
||||
this.$attr[key] = attrName = snake_case(key, '-');
|
||||
}
|
||||
}
|
||||
|
||||
this[key] = value;
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
// translate normalized key to actual key
|
||||
if (attrName) {
|
||||
this.$attr[key] = attrName;
|
||||
} else {
|
||||
attrName = this.$attr[key];
|
||||
if (!attrName) {
|
||||
this.$attr[key] = attrName = snake_case(key, '-');
|
||||
}
|
||||
}
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
// sanitize a[href] and img[src] values
|
||||
if ((nodeName === 'A' && key === 'href') ||
|
||||
(nodeName === 'IMG' && key === 'src')) {
|
||||
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
|
||||
if (!msie || msie >= 8 ) {
|
||||
normalizedVal = urlResolve(value).href;
|
||||
if (normalizedVal !== '') {
|
||||
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
|
||||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
|
||||
this[key] = value = 'unsafe:' + normalizedVal;
|
||||
}
|
||||
// sanitize a[href] and img[src] values
|
||||
if ((nodeName === 'A' && key === 'href') ||
|
||||
(nodeName === 'IMG' && key === 'src')) {
|
||||
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
|
||||
if (!msie || msie >= 8 ) {
|
||||
normalizedVal = urlResolve(value).href;
|
||||
if (normalizedVal !== '') {
|
||||
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
|
||||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
|
||||
this[key] = value = 'unsafe:' + normalizedVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || value === undefined) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
}
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || value === undefined) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,22 +759,6 @@ function $CompileProvider($provide) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
});
|
||||
|
||||
function tokenDifference(str1, str2) {
|
||||
var values = [],
|
||||
tokens1 = str1.split(/\s+/),
|
||||
tokens2 = str2.split(/\s+/);
|
||||
|
||||
outer:
|
||||
for(var i=0;i<tokens1.length;i++) {
|
||||
var token = tokens1[i];
|
||||
for(var j=0;j<tokens2.length;j++) {
|
||||
if(token == tokens2[j]) continue outer;
|
||||
}
|
||||
values.push(token);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -820,7 +828,7 @@ function $CompileProvider($provide) {
|
||||
var compositeLinkFn =
|
||||
compileNodes($compileNodes, transcludeFn, $compileNodes,
|
||||
maxPriority, ignoreDirective, previousCompileContext);
|
||||
return function publicLinkFn(scope, cloneConnectFn){
|
||||
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
|
||||
assertArg(scope, 'scope');
|
||||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
||||
// and sometimes changes the structure of the DOM.
|
||||
@@ -828,6 +836,10 @@ function $CompileProvider($provide) {
|
||||
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
|
||||
: $compileNodes;
|
||||
|
||||
forEach(transcludeControllers, function(instance, name) {
|
||||
$linkNode.data('$' + name + 'Controller', instance);
|
||||
});
|
||||
|
||||
// Attach scope only to non-text nodes.
|
||||
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
|
||||
var node = $linkNode[i];
|
||||
@@ -926,15 +938,7 @@ function $CompileProvider($provide) {
|
||||
childTranscludeFn = nodeLinkFn.transclude;
|
||||
if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement,
|
||||
(function(transcludeFn) {
|
||||
return function(cloneFn) {
|
||||
var transcludeScope = scope.$new();
|
||||
transcludeScope.$$transcluded = true;
|
||||
|
||||
return transcludeFn(transcludeScope, cloneFn).
|
||||
on('$destroy', bind(transcludeScope, transcludeScope.$destroy));
|
||||
};
|
||||
})(childTranscludeFn || transcludeFn)
|
||||
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
|
||||
);
|
||||
} else {
|
||||
nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
|
||||
@@ -946,6 +950,23 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
}
|
||||
|
||||
function createBoundTranscludeFn(scope, transcludeFn) {
|
||||
return function boundTranscludeFn(transcludedScope, cloneFn, controllers) {
|
||||
var scopeCreated = false;
|
||||
|
||||
if (!transcludedScope) {
|
||||
transcludedScope = scope.$new();
|
||||
transcludedScope.$$transcluded = true;
|
||||
scopeCreated = true;
|
||||
}
|
||||
|
||||
var clone = transcludeFn(transcludedScope, cloneFn, controllers);
|
||||
if (scopeCreated) {
|
||||
clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy));
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for directives on the given node and adds them to the directive collection which is
|
||||
@@ -1083,9 +1104,9 @@ function $CompileProvider($provide) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
|
||||
return function(scope, element, attrs, controllers) {
|
||||
return function(scope, element, attrs, controllers, transcludeFn) {
|
||||
element = groupScan(element[0], attrStart, attrEnd);
|
||||
return linkFn(scope, element, attrs, controllers);
|
||||
return linkFn(scope, element, attrs, controllers, transcludeFn);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1122,7 +1143,9 @@ function $CompileProvider($provide) {
|
||||
controllerDirectives = previousCompileContext.controllerDirectives,
|
||||
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
|
||||
templateDirective = previousCompileContext.templateDirective,
|
||||
transcludeDirective = previousCompileContext.transcludeDirective,
|
||||
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
|
||||
hasTranscludeDirective = false,
|
||||
hasElementTranscludeDirective = false,
|
||||
$compileNode = templateAttrs.$$element = jqLite(compileNode),
|
||||
directive,
|
||||
directiveName,
|
||||
@@ -1173,15 +1196,18 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
|
||||
if (directiveValue = directive.transclude) {
|
||||
hasTranscludeDirective = true;
|
||||
|
||||
// Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
|
||||
// This option should only be used by directives that know how to how to safely handle element transclusion,
|
||||
// where the transcluded nodes are added or replaced after linking.
|
||||
if (!directive.$$tlb) {
|
||||
assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
|
||||
transcludeDirective = directive;
|
||||
assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
|
||||
nonTlbTranscludeDirective = directive;
|
||||
}
|
||||
|
||||
if (directiveValue == 'element') {
|
||||
hasElementTranscludeDirective = true;
|
||||
terminalPriority = directive.priority;
|
||||
$template = groupScan(compileNode, attrStart, attrEnd);
|
||||
$compileNode = templateAttrs.$$element =
|
||||
@@ -1197,9 +1223,9 @@ function $CompileProvider($provide) {
|
||||
// - newIsolateScopeDirective or templateDirective - combining templates with
|
||||
// element transclusion doesn't make sense.
|
||||
//
|
||||
// We need only transcludeDirective so that we prevent putting transclusion
|
||||
// We need only nonTlbTranscludeDirective so that we prevent putting transclusion
|
||||
// on the same element more than once.
|
||||
transcludeDirective: transcludeDirective
|
||||
nonTlbTranscludeDirective: nonTlbTranscludeDirective
|
||||
});
|
||||
} else {
|
||||
$template = jqLite(jqLiteClone(compileNode)).contents();
|
||||
@@ -1268,7 +1294,7 @@ function $CompileProvider($provide) {
|
||||
controllerDirectives: controllerDirectives,
|
||||
newIsolateScopeDirective: newIsolateScopeDirective,
|
||||
templateDirective: templateDirective,
|
||||
transcludeDirective: transcludeDirective
|
||||
nonTlbTranscludeDirective: nonTlbTranscludeDirective
|
||||
});
|
||||
ii = directives.length;
|
||||
} else if (directive.compile) {
|
||||
@@ -1292,7 +1318,7 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
|
||||
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
|
||||
nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
|
||||
nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
|
||||
|
||||
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
|
||||
return nodeLinkFn;
|
||||
@@ -1319,7 +1345,7 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
|
||||
|
||||
function getControllers(require, $element) {
|
||||
function getControllers(require, $element, elementControllers) {
|
||||
var value, retrievalMethod = 'data', optional = false;
|
||||
if (isString(require)) {
|
||||
while((value = require.charAt(0)) == '^' || value == '?') {
|
||||
@@ -1329,13 +1355,12 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
optional = optional || value == '?';
|
||||
}
|
||||
value = null;
|
||||
|
||||
value = $element[retrievalMethod]('$' + require + 'Controller');
|
||||
|
||||
if ($element[0].nodeType == 8 && $element[0].$$controller) { // Transclusion comment node
|
||||
value = value || $element[0].$$controller;
|
||||
$element[0].$$controller = null;
|
||||
if (elementControllers && retrievalMethod === 'data') {
|
||||
value = elementControllers[require];
|
||||
}
|
||||
value = value || $element[retrievalMethod]('$' + require + 'Controller');
|
||||
|
||||
if (!value && !optional) {
|
||||
throw $compileMinErr('ctreq',
|
||||
@@ -1346,7 +1371,7 @@ function $CompileProvider($provide) {
|
||||
} else if (isArray(require)) {
|
||||
value = [];
|
||||
forEach(require, function(require) {
|
||||
value.push(getControllers(require, $element));
|
||||
value.push(getControllers(require, $element, elementControllers));
|
||||
});
|
||||
}
|
||||
return value;
|
||||
@@ -1354,7 +1379,7 @@ function $CompileProvider($provide) {
|
||||
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
||||
var attrs, $element, i, ii, linkFn, controller, isolateScope;
|
||||
var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn;
|
||||
|
||||
if (compileNode === linkNode) {
|
||||
attrs = templateAttrs;
|
||||
@@ -1448,14 +1473,14 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
transcludeFn = boundTranscludeFn && controllersBoundTransclude;
|
||||
if (controllerDirectives) {
|
||||
forEach(controllerDirectives, function(directive) {
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: boundTranscludeFn
|
||||
$transclude: transcludeFn
|
||||
}, controllerInstance;
|
||||
|
||||
controller = directive.controller;
|
||||
@@ -1464,16 +1489,16 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
|
||||
controllerInstance = $controller(controller, locals);
|
||||
|
||||
// Directives with element transclusion and a controller need to attach controller
|
||||
// to the comment node created by the compiler, but jQuery .data doesn't support
|
||||
// attaching data to comment nodes so instead we set it directly on the element and
|
||||
// remove it after we read it later.
|
||||
if ($element[0].nodeType == 8) { // Transclusion comment node
|
||||
$element[0].$$controller = controllerInstance;
|
||||
} else {
|
||||
// For directives with element transclusion the element is a comment,
|
||||
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
|
||||
// clean up (http://bugs.jquery.com/ticket/8335).
|
||||
// Instead, we save the controllers for the element in a local hash and attach to .data
|
||||
// later, once we have the actual element.
|
||||
elementControllers[directive.name] = controllerInstance;
|
||||
if (!hasElementTranscludeDirective) {
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance);
|
||||
}
|
||||
|
||||
if (directive.controllerAs) {
|
||||
locals.$scope[directive.controllerAs] = controllerInstance;
|
||||
}
|
||||
@@ -1485,7 +1510,7 @@ function $CompileProvider($provide) {
|
||||
try {
|
||||
linkFn = preLinkFns[i];
|
||||
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
|
||||
linkFn.require && getControllers(linkFn.require, $element));
|
||||
linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e, startingTag($element));
|
||||
}
|
||||
@@ -1505,11 +1530,28 @@ function $CompileProvider($provide) {
|
||||
try {
|
||||
linkFn = postLinkFns[i];
|
||||
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
|
||||
linkFn.require && getControllers(linkFn.require, $element));
|
||||
linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e, startingTag($element));
|
||||
}
|
||||
}
|
||||
|
||||
// This is the function that is injected as `$transclude`.
|
||||
function controllersBoundTransclude(scope, cloneAttachFn) {
|
||||
var transcludeControllers;
|
||||
|
||||
// no scope passed
|
||||
if (arguments.length < 2) {
|
||||
cloneAttachFn = scope;
|
||||
scope = undefined;
|
||||
}
|
||||
|
||||
if (hasElementTranscludeDirective) {
|
||||
transcludeControllers = elementControllers;
|
||||
}
|
||||
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1588,6 +1630,7 @@ function $CompileProvider($provide) {
|
||||
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
|
||||
} else if (key == 'style') {
|
||||
$element.attr('style', $element.attr('style') + ';' + value);
|
||||
dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
|
||||
// `dst` will never contain hasOwnProperty as DOM parser won't let it.
|
||||
// You will get an "InvalidCharacterError: DOM Exception 5" error if you
|
||||
// have an attribute like "has-own-property" or "data-has-own-property", etc.
|
||||
@@ -1618,7 +1661,7 @@ function $CompileProvider($provide) {
|
||||
|
||||
$http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
|
||||
success(function(content) {
|
||||
var compileNode, tempTemplateAttrs, $template;
|
||||
var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
|
||||
|
||||
content = denormalizeTemplate(content);
|
||||
|
||||
@@ -1663,7 +1706,7 @@ function $CompileProvider($provide) {
|
||||
var scope = linkQueue.shift(),
|
||||
beforeTemplateLinkNode = linkQueue.shift(),
|
||||
linkRootElement = linkQueue.shift(),
|
||||
controller = linkQueue.shift(),
|
||||
boundTranscludeFn = linkQueue.shift(),
|
||||
linkNode = $compileNode[0];
|
||||
|
||||
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
|
||||
@@ -1671,9 +1714,13 @@ function $CompileProvider($provide) {
|
||||
linkNode = jqLiteClone(compileNode);
|
||||
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
|
||||
}
|
||||
|
||||
if (afterTemplateNodeLinkFn.transclude) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
|
||||
} else {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
|
||||
controller);
|
||||
childBoundTranscludeFn);
|
||||
}
|
||||
linkQueue = null;
|
||||
}).
|
||||
@@ -1681,14 +1728,14 @@ function $CompileProvider($provide) {
|
||||
throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
|
||||
});
|
||||
|
||||
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
|
||||
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
|
||||
if (linkQueue) {
|
||||
linkQueue.push(scope);
|
||||
linkQueue.push(node);
|
||||
linkQueue.push(rootElement);
|
||||
linkQueue.push(controller);
|
||||
linkQueue.push(boundTranscludeFn);
|
||||
} else {
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1733,10 +1780,15 @@ function $CompileProvider($provide) {
|
||||
|
||||
|
||||
function getTrustedContext(node, attrNormalizedName) {
|
||||
if (attrNormalizedName == "srcdoc") {
|
||||
return $sce.HTML;
|
||||
}
|
||||
var tag = nodeName_(node);
|
||||
// maction[xlink:href] can source SVG. It's not limited to <maction>.
|
||||
if (attrNormalizedName == "xlinkHref" ||
|
||||
(nodeName_(node) != "IMG" && (attrNormalizedName == "src" ||
|
||||
attrNormalizedName == "ngSrc"))) {
|
||||
(tag == "FORM" && attrNormalizedName == "action") ||
|
||||
(tag != "IMG" && (attrNormalizedName == "src" ||
|
||||
attrNormalizedName == "ngSrc"))) {
|
||||
return $sce.RESOURCE_URL;
|
||||
}
|
||||
}
|
||||
@@ -1781,9 +1833,19 @@ function $CompileProvider($provide) {
|
||||
attr[name] = interpolateFn(scope);
|
||||
($$observers[name] || ($$observers[name] = [])).$$inter = true;
|
||||
(attr.$$observers && attr.$$observers[name].$$scope || scope).
|
||||
$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
||||
attr.$set(name, value);
|
||||
});
|
||||
$watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
|
||||
//special case for class attribute addition + removal
|
||||
//so that class changes can tap into the animation
|
||||
//hooks provided by the $animate service. Be sure to
|
||||
//skip animations when the first digest occurs (when
|
||||
//both the new and the old values are the same) since
|
||||
//the CSS classes are the non-interpolated values
|
||||
if(name === 'class' && newValue != oldValue) {
|
||||
attr.$updateClass(newValue, oldValue);
|
||||
} else {
|
||||
attr.$set(name, newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1923,3 +1985,19 @@ function directiveLinkingFn(
|
||||
/* Element */ rootElement,
|
||||
/* function(Function) */ boundTranscludeFn
|
||||
){}
|
||||
|
||||
function tokenDifference(str1, str2) {
|
||||
var values = '',
|
||||
tokens1 = str1.split(/\s+/),
|
||||
tokens2 = str2.split(/\s+/);
|
||||
|
||||
outer:
|
||||
for(var i = 0; i < tokens1.length; i++) {
|
||||
var token = tokens1[i];
|
||||
for(var j = 0; j < tokens2.length; j++) {
|
||||
if(token == tokens2[j]) continue outer;
|
||||
}
|
||||
values += (values.length > 0 ? ' ' : '') + token;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -149,8 +149,11 @@
|
||||
*
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as disabled. (Their presence means true and their absence means false.)
|
||||
* This prevents the Angular compiler from retrieving the binding expression.
|
||||
* If we put an Angular interpolation expression into such an attribute then the
|
||||
* binding information would be lost when the browser removes the attribute.
|
||||
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
@@ -181,8 +184,11 @@
|
||||
* @description
|
||||
* 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.)
|
||||
* This prevents the Angular compiler from retrieving the binding expression.
|
||||
* 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
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -212,8 +218,12 @@
|
||||
* @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.)
|
||||
* This prevents the Angular compiler from retrieving the binding expression.
|
||||
* 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.
|
||||
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -243,8 +253,11 @@
|
||||
* @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.)
|
||||
* This prevents the Angular compiler from retrieving the binding expression.
|
||||
* 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` atttribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -276,8 +289,12 @@
|
||||
* @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.)
|
||||
* This prevents the Angular compiler from retrieving the binding expression.
|
||||
* 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.
|
||||
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
|
||||
@@ -21,7 +21,7 @@ var nullFormCtrl = {
|
||||
* @property {Object} $error Is an object hash, containing references to all invalid controls or
|
||||
* forms, where:
|
||||
*
|
||||
* - keys are validation tokens (error names) — such as `required`, `url` or `email`),
|
||||
* - keys are validation tokens (error names) — such as `required`, `url` or `email`,
|
||||
* - values are arrays of controls or forms that are invalid with given error.
|
||||
*
|
||||
* @description
|
||||
|
||||
@@ -392,8 +392,21 @@ var inputType = {
|
||||
|
||||
|
||||
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
// In composition mode, users are still inputing intermediate text buffer,
|
||||
// hold the listener until composition is done.
|
||||
// More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
|
||||
var composing = false;
|
||||
|
||||
element.on('compositionstart', function() {
|
||||
composing = true;
|
||||
});
|
||||
|
||||
element.on('compositionend', function() {
|
||||
composing = false;
|
||||
});
|
||||
|
||||
var listener = function() {
|
||||
if (composing) return;
|
||||
var value = element.val();
|
||||
|
||||
// By default we will trim the value
|
||||
@@ -840,6 +853,11 @@ var VALID_CLASS = 'ng-valid',
|
||||
* }
|
||||
* ngModel.$formatters.push(formatter);
|
||||
* </pre>
|
||||
*
|
||||
* @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
|
||||
* view value has changed. It is called with no arguments, and its return value is ignored.
|
||||
* This can be used in place of additional $watches against the model value.
|
||||
*
|
||||
* @property {Object} $error An object hash with all errors as keys.
|
||||
*
|
||||
* @property {boolean} $pristine True if user has not interacted with the control yet.
|
||||
@@ -1103,14 +1121,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @methodOf ng.directive:ngModel.NgModelController
|
||||
*
|
||||
* @description
|
||||
* Read a value from view.
|
||||
* Update the view value.
|
||||
*
|
||||
* This method should be called from within a DOM event handler.
|
||||
* For example {@link ng.directive:input input} or
|
||||
* This method should be called when the view value changes, typically from within a DOM event handler.
|
||||
* For example {@link ng.directive:input input} and
|
||||
* {@link ng.directive:select select} directives call it.
|
||||
*
|
||||
* It internally calls all `$parsers` (including validators) and updates the `$modelValue` and the actual model path.
|
||||
* Lastly it calls all registered change listeners.
|
||||
* It will update the $viewValue, then pass this value through each of the functions in `$parsers`,
|
||||
* which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to
|
||||
* `$modelValue` and the **expression** specified in the `ng-model` attribute.
|
||||
*
|
||||
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
|
||||
*
|
||||
* Note that calling this function does not trigger a `$digest`.
|
||||
*
|
||||
* @param {string} value Value from the view.
|
||||
*/
|
||||
|
||||
+21
-15
@@ -138,27 +138,33 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
|
||||
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
|
||||
*
|
||||
* @example
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
<doc:example module="ngBindHtmlExample" deps="angular-sanitize.js" >
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.module('ngBindHtmlExample', ['ngSanitize'])
|
||||
|
||||
.controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) {
|
||||
$scope.myHTML = 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>';
|
||||
}]);
|
||||
</script>
|
||||
Try it here: enter text in text box and watch the greeting change.
|
||||
|
||||
<example module="ngBindHtmlExample" deps="angular-sanitize.js">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ngBindHtmlCtrl">
|
||||
<p ng-bind-html="myHTML"></p>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
angular.module('ngBindHtmlExample', ['ngSanitize'])
|
||||
|
||||
.controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) {
|
||||
$scope.myHTML =
|
||||
'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>';
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="scenario.js">
|
||||
it('should check ng-bind-html', function() {
|
||||
expect(using('.doc-example-live').binding('myHTML')).
|
||||
toBe('I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>');
|
||||
toBe(
|
||||
'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'
|
||||
);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
|
||||
return function(scope, element, attr) {
|
||||
|
||||
+18
-26
@@ -20,11 +20,10 @@ function classDirective(name, selector) {
|
||||
// jshint bitwise: false
|
||||
var mod = $index & 1;
|
||||
if (mod !== old$index & 1) {
|
||||
if (mod === selector) {
|
||||
addClass(scope.$eval(attr[name]));
|
||||
} else {
|
||||
removeClass(scope.$eval(attr[name]));
|
||||
}
|
||||
var classes = flattenClasses(scope.$eval(attr[name]));
|
||||
mod === selector ?
|
||||
attr.$addClass(classes) :
|
||||
attr.$removeClass(classes);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -32,24 +31,17 @@ function classDirective(name, selector) {
|
||||
|
||||
function ngClassWatchAction(newVal) {
|
||||
if (selector === true || scope.$index % 2 === selector) {
|
||||
if (oldVal && !equals(newVal,oldVal)) {
|
||||
removeClass(oldVal);
|
||||
var newClasses = flattenClasses(newVal || '');
|
||||
if(!oldVal) {
|
||||
attr.$addClass(newClasses);
|
||||
} else if(!equals(newVal,oldVal)) {
|
||||
attr.$updateClass(newClasses, flattenClasses(oldVal));
|
||||
}
|
||||
addClass(newVal);
|
||||
}
|
||||
oldVal = copy(newVal);
|
||||
}
|
||||
|
||||
|
||||
function removeClass(classVal) {
|
||||
attr.$removeClass(flattenClasses(classVal));
|
||||
}
|
||||
|
||||
|
||||
function addClass(classVal) {
|
||||
attr.$addClass(flattenClasses(classVal));
|
||||
}
|
||||
|
||||
function flattenClasses(classVal) {
|
||||
if(isArray(classVal)) {
|
||||
return classVal.join(' ');
|
||||
@@ -98,18 +90,18 @@ function classDirective(name, selector) {
|
||||
* @example Example that demonstrates basic bindings via ngClass directive.
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<p ng-class="{strike: strike, bold: bold, red: red}">Map Syntax Example</p>
|
||||
<input type="checkbox" ng-model="bold"> bold
|
||||
<input type="checkbox" ng-model="strike"> strike
|
||||
<input type="checkbox" ng-model="red"> red
|
||||
<p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p>
|
||||
<input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br>
|
||||
<input type="checkbox" ng-model="important"> important (apply "bold" class)<br>
|
||||
<input type="checkbox" ng-model="error"> error (apply "red" class)
|
||||
<hr>
|
||||
<p ng-class="style">Using String Syntax</p>
|
||||
<input type="text" ng-model="style" placeholder="Type: bold strike red">
|
||||
<hr>
|
||||
<p ng-class="[style1, style2, style3]">Using Array Syntax</p>
|
||||
<input ng-model="style1" placeholder="Type: bold"><br>
|
||||
<input ng-model="style2" placeholder="Type: strike"><br>
|
||||
<input ng-model="style3" placeholder="Type: red"><br>
|
||||
<input ng-model="style1" placeholder="Type: bold, strike or red"><br>
|
||||
<input ng-model="style2" placeholder="Type: bold, strike or red"><br>
|
||||
<input ng-model="style3" placeholder="Type: bold, strike or red"><br>
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.strike {
|
||||
@@ -128,10 +120,10 @@ function classDirective(name, selector) {
|
||||
expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/);
|
||||
expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/);
|
||||
|
||||
input('bold').check();
|
||||
input('important').check();
|
||||
expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/);
|
||||
|
||||
input('red').check();
|
||||
input('error').check();
|
||||
expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/);
|
||||
});
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
var ngControllerDirective = [function() {
|
||||
return {
|
||||
scope: true,
|
||||
controller: '@'
|
||||
controller: '@',
|
||||
priority: 500
|
||||
};
|
||||
}];
|
||||
|
||||
+12
-14
@@ -60,7 +60,7 @@
|
||||
}
|
||||
|
||||
/*
|
||||
The transition styles can also be placed on the CSS base class above
|
||||
The transition styles can also be placed on the CSS base class above
|
||||
*/
|
||||
.animate-if.ng-enter, .animate-if.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
@@ -86,22 +86,21 @@ var ngIfDirective = ['$animate', function($animate) {
|
||||
terminal: true,
|
||||
restrict: 'A',
|
||||
$$tlb: true,
|
||||
compile: function (element, attr, transclude) {
|
||||
return function ($scope, $element, $attr) {
|
||||
link: function ($scope, $element, $attr, ctrl, $transclude) {
|
||||
var block, childScope;
|
||||
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
|
||||
|
||||
if (toBoolean(value)) {
|
||||
|
||||
childScope = $scope.$new();
|
||||
transclude(childScope, function (clone) {
|
||||
block = {
|
||||
startNode: clone[0],
|
||||
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
|
||||
};
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
|
||||
if (!childScope) {
|
||||
childScope = $scope.$new();
|
||||
$transclude(childScope, function (clone) {
|
||||
block = {
|
||||
startNode: clone[0],
|
||||
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
|
||||
};
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
if (childScope) {
|
||||
@@ -115,7 +114,6 @@ var ngIfDirective = ['$animate', function($animate) {
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -154,12 +154,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
|
||||
priority: 400,
|
||||
terminal: true,
|
||||
transclude: 'element',
|
||||
compile: function(element, attr, transclusion) {
|
||||
compile: function(element, attr) {
|
||||
var srcExp = attr.ngInclude || attr.src,
|
||||
onloadExp = attr.onload || '',
|
||||
autoScrollExp = attr.autoscroll;
|
||||
|
||||
return function(scope, $element) {
|
||||
return function(scope, $element, $attr, ctrl, $transclude) {
|
||||
var changeCounter = 0,
|
||||
currentScope,
|
||||
currentElement;
|
||||
@@ -188,18 +188,23 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
|
||||
if (thisChangeId !== changeCounter) return;
|
||||
var newScope = scope.$new();
|
||||
|
||||
transclusion(newScope, function(clone) {
|
||||
cleanupLastIncludeContent();
|
||||
// Note: This will also link all children of ng-include that were contained in the original
|
||||
// html. If that content contains controllers, ... they could pollute/change the scope.
|
||||
// However, using ng-include on an element with additional content does not make sense...
|
||||
// Note: We can't remove them in the cloneAttchFn of $transclude as that
|
||||
// function is called before linking the content, which would apply child
|
||||
// directives to non existing elements.
|
||||
var clone = $transclude(newScope, noop);
|
||||
cleanupLastIncludeContent();
|
||||
|
||||
currentScope = newScope;
|
||||
currentElement = clone;
|
||||
currentScope = newScope;
|
||||
currentElement = clone;
|
||||
|
||||
currentElement.html(response);
|
||||
$animate.enter(currentElement, null, $element, afterAnimation);
|
||||
$compile(currentElement.contents())(currentScope);
|
||||
currentScope.$emit('$includeContentLoaded');
|
||||
scope.$eval(onloadExp);
|
||||
});
|
||||
currentElement.html(response);
|
||||
$animate.enter(currentElement, null, $element, afterAnimation);
|
||||
$compile(currentElement.contents())(currentScope);
|
||||
currentScope.$emit('$includeContentLoaded');
|
||||
scope.$eval(onloadExp);
|
||||
}).error(function() {
|
||||
if (thisChangeId === changeCounter) cleanupLastIncludeContent();
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
* other numbers, for example 12, so that instead of showing "12 people are viewing", you can
|
||||
* show "a dozen people are viewing".
|
||||
*
|
||||
* You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
|
||||
* You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
|
||||
* into pluralized strings. In the previous example, Angular will replace `{}` with
|
||||
* <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
|
||||
* for <span ng-non-bindable>{{numberExpression}}</span>.
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
* For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
|
||||
* `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
|
||||
* with the corresponding item in the array by identity. Moving the same object in array would move the DOM
|
||||
* element in the same way ian the DOM.
|
||||
* element in the same way in the DOM.
|
||||
*
|
||||
* For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
|
||||
* case the object identity does not matter. Two objects are considered equivalent as long as their `id`
|
||||
@@ -201,8 +201,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
priority: 1000,
|
||||
terminal: true,
|
||||
$$tlb: true,
|
||||
compile: function(element, attr, linker) {
|
||||
return function($scope, $element, $attr){
|
||||
link: function($scope, $element, $attr, ctrl, $transclude){
|
||||
var expression = $attr.ngRepeat;
|
||||
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
|
||||
trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn,
|
||||
@@ -364,7 +363,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
// jshint bitwise: true
|
||||
|
||||
if (!block.startNode) {
|
||||
linker(childScope, function(clone) {
|
||||
$transclude(childScope, function(clone) {
|
||||
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
|
||||
$animate.enter(clone, null, jqLite(previousNode));
|
||||
previousNode = clone;
|
||||
@@ -377,7 +376,6 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
}
|
||||
lastBlockMap = nextBlockMap;
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -160,10 +160,10 @@ var ngSwitchWhenDirective = ngDirective({
|
||||
transclude: 'element',
|
||||
priority: 800,
|
||||
require: '^ngSwitch',
|
||||
compile: function(element, attrs, transclude) {
|
||||
return function(scope, element, attr, ctrl) {
|
||||
compile: function(element, attrs) {
|
||||
return function(scope, element, attr, ctrl, $transclude) {
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element });
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -172,10 +172,8 @@ var ngSwitchDefaultDirective = ngDirective({
|
||||
transclude: 'element',
|
||||
priority: 800,
|
||||
require: '^ngSwitch',
|
||||
compile: function(element, attrs, transclude) {
|
||||
return function(scope, element, attr, ctrl) {
|
||||
ctrl.cases['?'] = (ctrl.cases['?'] || []);
|
||||
ctrl.cases['?'].push({ transclude: transclude, element: element });
|
||||
};
|
||||
}
|
||||
link: function(scope, element, attr, ctrl, $transclude) {
|
||||
ctrl.cases['?'] = (ctrl.cases['?'] || []);
|
||||
ctrl.cases['?'].push({ transclude: $transclude, element: element });
|
||||
}
|
||||
});
|
||||
|
||||
+11
-5
@@ -324,9 +324,11 @@ function $HttpProvider() {
|
||||
*
|
||||
* # Caching
|
||||
*
|
||||
* To enable caching, set the configuration property `cache` to `true`. When the cache is
|
||||
* enabled, `$http` stores the response from the server in local cache. Next time the
|
||||
* response is served from the cache without sending a request to the server.
|
||||
* To enable caching, set the request configuration `cache` property to `true` (to use default
|
||||
* cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
|
||||
* When the cache is enabled, `$http` stores the response from the server in the specified
|
||||
* cache. The next time the same request is made, the response is served from the cache without
|
||||
* sending a request to the server.
|
||||
*
|
||||
* Note that even if the response is served from cache, delivery of the data is asynchronous in
|
||||
* the same way that real requests are.
|
||||
@@ -335,9 +337,13 @@ function $HttpProvider() {
|
||||
* cache, but the cache is not populated yet, only one request to the server will be made and
|
||||
* the remaining requests will be fulfilled using the response from the first request.
|
||||
*
|
||||
* A custom default cache built with $cacheFactory can be provided in $http.defaults.cache.
|
||||
* To skip it, set configuration property `cache` to `false`.
|
||||
* You can change the default cache to a new object (built with
|
||||
* {@link ng.$cacheFactory `$cacheFactory`}) by updating the
|
||||
* {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set
|
||||
* their `cache` property to `true` will now use this cache object.
|
||||
*
|
||||
* If you set the default cache to `false` then only requests that specify their own custom
|
||||
* cache object will be cached.
|
||||
*
|
||||
* # Interceptors
|
||||
*
|
||||
|
||||
+19
-6
@@ -34,6 +34,8 @@ function $HttpBackendProvider() {
|
||||
}
|
||||
|
||||
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
|
||||
var ABORTED = -1;
|
||||
|
||||
// TODO(vojta): fix the signature
|
||||
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
|
||||
var status;
|
||||
@@ -69,13 +71,19 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
// always async
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
var responseHeaders = xhr.getAllResponseHeaders();
|
||||
var responseHeaders = null,
|
||||
response = null;
|
||||
|
||||
if(status !== ABORTED) {
|
||||
responseHeaders = xhr.getAllResponseHeaders();
|
||||
response = xhr.responseType ? xhr.response : xhr.responseText;
|
||||
}
|
||||
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
|
||||
completeRequest(callback,
|
||||
status || xhr.status,
|
||||
(xhr.responseType ? xhr.response : xhr.responseText),
|
||||
response,
|
||||
responseHeaders);
|
||||
}
|
||||
};
|
||||
@@ -99,7 +107,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
|
||||
|
||||
function timeoutRequest() {
|
||||
status = -1;
|
||||
status = ABORTED;
|
||||
jsonpDone && jsonpDone();
|
||||
xhr && xhr.abort();
|
||||
}
|
||||
@@ -128,6 +136,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
// - adds and immediately removes script elements from the document
|
||||
var script = rawDocument.createElement('script'),
|
||||
doneWrapper = function() {
|
||||
script.onreadystatechange = script.onload = script.onerror = null;
|
||||
rawDocument.body.removeChild(script);
|
||||
if (done) done();
|
||||
};
|
||||
@@ -135,12 +144,16 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
script.type = 'text/javascript';
|
||||
script.src = url;
|
||||
|
||||
if (msie) {
|
||||
if (msie && msie <= 8) {
|
||||
script.onreadystatechange = function() {
|
||||
if (/loaded|complete/.test(script.readyState)) doneWrapper();
|
||||
if (/loaded|complete/.test(script.readyState)) {
|
||||
doneWrapper();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
script.onload = script.onerror = doneWrapper;
|
||||
script.onload = script.onerror = function() {
|
||||
doneWrapper();
|
||||
};
|
||||
}
|
||||
|
||||
rawDocument.body.appendChild(script);
|
||||
|
||||
+8
-8
@@ -22,8 +22,8 @@ function encodePath(path) {
|
||||
return segments.join('/');
|
||||
}
|
||||
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
var parsedUrl = urlResolve(absoluteUrl);
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
|
||||
var parsedUrl = urlResolve(absoluteUrl, appBase);
|
||||
|
||||
locationObj.$$protocol = parsedUrl.protocol;
|
||||
locationObj.$$host = parsedUrl.hostname;
|
||||
@@ -31,12 +31,12 @@ function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
}
|
||||
|
||||
|
||||
function parseAppUrl(relativeUrl, locationObj) {
|
||||
function parseAppUrl(relativeUrl, locationObj, appBase) {
|
||||
var prefixed = (relativeUrl.charAt(0) !== '/');
|
||||
if (prefixed) {
|
||||
relativeUrl = '/' + relativeUrl;
|
||||
}
|
||||
var match = urlResolve(relativeUrl);
|
||||
var match = urlResolve(relativeUrl, appBase);
|
||||
locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
|
||||
match.pathname.substring(1) : match.pathname);
|
||||
locationObj.$$search = parseKeyValue(match.search);
|
||||
@@ -91,7 +91,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
this.$$html5 = true;
|
||||
basePrefix = basePrefix || '';
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
parseAbsoluteUrl(appBase, this);
|
||||
parseAbsoluteUrl(appBase, this, appBase);
|
||||
|
||||
|
||||
/**
|
||||
@@ -106,7 +106,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
appBaseNoFile);
|
||||
}
|
||||
|
||||
parseAppUrl(pathUrl, this);
|
||||
parseAppUrl(pathUrl, this, appBase);
|
||||
|
||||
if (!this.$$path) {
|
||||
this.$$path = '/';
|
||||
@@ -158,7 +158,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
|
||||
parseAbsoluteUrl(appBase, this);
|
||||
parseAbsoluteUrl(appBase, this, appBase);
|
||||
|
||||
|
||||
/**
|
||||
@@ -178,7 +178,7 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
|
||||
hashPrefix);
|
||||
}
|
||||
parseAppUrl(withoutHashUrl, this);
|
||||
parseAppUrl(withoutHashUrl, this, appBase);
|
||||
this.$$compose();
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
*
|
||||
* The main purpose of this service is to simplify debugging and troubleshooting.
|
||||
*
|
||||
* The default is not to log `debug` messages. You can use
|
||||
* The default is to log `debug` messages. You can use
|
||||
* {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
|
||||
*
|
||||
* @example
|
||||
|
||||
+10
-26
@@ -8,23 +8,18 @@ var promiseWarning;
|
||||
// ------------------------------
|
||||
// Angular expressions are generally considered safe because these expressions only have direct
|
||||
// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// obtaining a reference to native JS functions such as the Function constructor, the global Window
|
||||
// or Document object. In addition, many powerful functions for use by JavaScript code are
|
||||
// published on scope that shouldn't be available from within an Angular expression.
|
||||
// obtaining a reference to native JS functions such as the Function constructor.
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
//
|
||||
// {}.toString.constructor(alert("evil JS code"))
|
||||
//
|
||||
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
|
||||
// disallow any "dotted" access to any member named "constructor" or to any member whose name begins
|
||||
// or ends with an underscore. The latter allows one to exclude the private / JavaScript only API
|
||||
// available on the scope and controllers from the context of an Angular expression.
|
||||
// disallow any "dotted" access to any member named "constructor".
|
||||
//
|
||||
// For reflective calls (a[b]), we check that the value of the lookup is not the Function
|
||||
// constructor, Window or DOM node while evaluating the expression, which is a stronger but more
|
||||
// expensive test. Since reflective calls are expensive anyway, this is not such a big deal compared
|
||||
// to static dereferencing.
|
||||
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
|
||||
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
|
||||
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
@@ -38,20 +33,12 @@ var promiseWarning;
|
||||
// In general, it is not possible to access a Window object from an angular expression unless a
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression, allowConstructor) {
|
||||
if (typeof name !== 'string' && toString.apply(name) !== "[object String]") {
|
||||
return name;
|
||||
}
|
||||
if (name === "constructor" && !allowConstructor) {
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
if (name === "constructor") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') {
|
||||
throw $parseMinErr('isecprv',
|
||||
'Referencing private fields in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -735,10 +722,7 @@ Parser.prototype = {
|
||||
|
||||
return extend(function(self, locals) {
|
||||
var o = obj(self, locals),
|
||||
// In the getter, we will not block looking up "constructor" by name in order to support user defined
|
||||
// constructors. However, if value looked up is the Function constructor, we will still block it in the
|
||||
// ensureSafeObject call right after we look up o[i] (a few lines below.)
|
||||
i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */),
|
||||
i = indexFn(self, locals),
|
||||
v, p;
|
||||
|
||||
if (!o) return undefined;
|
||||
@@ -754,7 +738,7 @@ Parser.prototype = {
|
||||
return v;
|
||||
}, {
|
||||
assign: function(self, value, locals) {
|
||||
var key = ensureSafeMemberName(indexFn(self, locals), parser.text);
|
||||
var key = indexFn(self, locals);
|
||||
// prevent overwriting of Function.constructor which would break ensureSafeObject check
|
||||
var safe = ensureSafeObject(obj(self, locals), parser.text);
|
||||
return safe[key] = value;
|
||||
@@ -1033,7 +1017,7 @@ function getterFn(path, options, fullExp) {
|
||||
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
|
||||
(options.unwrapPromises
|
||||
? 'if (s && s.then) {\n' +
|
||||
' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' +
|
||||
' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' +
|
||||
' if (!("$$v" in s)) {\n' +
|
||||
' p=s;\n' +
|
||||
' p.$$v = undefined;\n' +
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@
|
||||
* // Propagate promise resolution to 'then' functions using $apply().
|
||||
* $rootScope.$apply();
|
||||
* expect(resolvedValue).toEqual(123);
|
||||
* });
|
||||
* }));
|
||||
* </pre>
|
||||
*/
|
||||
function $QProvider() {
|
||||
|
||||
+10
-14
@@ -199,8 +199,7 @@ function $SceDelegateProvider() {
|
||||
return resourceUrlBlacklist;
|
||||
};
|
||||
|
||||
this.$get = ['$log', '$document', '$injector', function(
|
||||
$log, $document, $injector) {
|
||||
this.$get = ['$injector', function($injector) {
|
||||
|
||||
var htmlSanitizer = function htmlSanitizer(html) {
|
||||
throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
|
||||
@@ -427,10 +426,10 @@ function $SceDelegateProvider() {
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
* <input ng-model="userHtml">
|
||||
* <div ng-bind-html="{{userHtml}}">
|
||||
* <div ng-bind-html="userHtml">
|
||||
* </pre>
|
||||
*
|
||||
* Notice that `ng-bind-html` is bound to `{{userHtml}}` controlled by the user. With SCE
|
||||
* Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
|
||||
* disabled, this application allows the user to render arbitrary HTML into the DIV.
|
||||
* In a more realistic example, one may be rendering user comments, blog articles, etc. via
|
||||
* bindings. (HTML is just one example of a context where rendering user controlled input creates
|
||||
@@ -731,18 +730,15 @@ function $SceProvider() {
|
||||
* sce.js and sceSpecs.js would need to be aware of this detail.
|
||||
*/
|
||||
|
||||
this.$get = ['$parse', '$document', '$sceDelegate', function(
|
||||
$parse, $document, $sceDelegate) {
|
||||
this.$get = ['$parse', '$sniffer', '$sceDelegate', function(
|
||||
$parse, $sniffer, $sceDelegate) {
|
||||
// Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
|
||||
// the "expression(javascript expression)" syntax which is insecure.
|
||||
if (enabled && msie) {
|
||||
var documentMode = $document[0].documentMode;
|
||||
if (documentMode !== undefined && documentMode < 8) {
|
||||
throw $sceMinErr('iequirks',
|
||||
'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
|
||||
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
|
||||
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
|
||||
}
|
||||
if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) {
|
||||
throw $sceMinErr('iequirks',
|
||||
'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
|
||||
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
|
||||
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
|
||||
}
|
||||
|
||||
var sce = copy(SCE_CONTEXTS);
|
||||
|
||||
+4
-2
@@ -22,6 +22,7 @@ function $SnifferProvider() {
|
||||
int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
|
||||
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
|
||||
document = $document[0] || {},
|
||||
documentMode = document.documentMode,
|
||||
vendorPrefix,
|
||||
vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
|
||||
bodyStyle = document.body && document.body.style,
|
||||
@@ -66,7 +67,7 @@ function $SnifferProvider() {
|
||||
// jshint +W018
|
||||
hashchange: 'onhashchange' in $window &&
|
||||
// IE8 compatible mode lies
|
||||
(!document.documentMode || document.documentMode > 7),
|
||||
(!documentMode || documentMode > 7),
|
||||
hasEvent: function(event) {
|
||||
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
|
||||
// it. In particular the event is not fired when backspace or delete key are pressed or
|
||||
@@ -84,7 +85,8 @@ function $SnifferProvider() {
|
||||
vendorPrefix: vendorPrefix,
|
||||
transitions : transitions,
|
||||
animations : animations,
|
||||
msie : msie
|
||||
msie : msie,
|
||||
msieDocumentMode: documentMode
|
||||
};
|
||||
}];
|
||||
}
|
||||
|
||||
+48
-6
@@ -7,8 +7,14 @@
|
||||
// exactly the behavior needed here. There is little value is mocking these out for this
|
||||
// service.
|
||||
var urlParsingNode = document.createElement("a");
|
||||
/*
|
||||
Matches paths for file protocol on windows,
|
||||
such as /C:/foo/bar, and captures only /foo/bar.
|
||||
*/
|
||||
var windowsFilePathExp = /^\/?.*?:(\/.*)/;
|
||||
var originUrl = urlResolve(window.location.href, true);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Implementation Notes for non-IE browsers
|
||||
@@ -27,7 +33,7 @@ var originUrl = urlResolve(window.location.href, true);
|
||||
* browsers. However, the parsed components will not be set if the URL assigned did not specify
|
||||
* them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
|
||||
* work around that by performing the parsing in a 2nd step by taking a previously normalized
|
||||
* URL (e.g. by assining to a.href) and assigning it a.href again. This correctly populates the
|
||||
* URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
|
||||
* properties such as protocol, hostname, port, etc.
|
||||
*
|
||||
* IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one
|
||||
@@ -61,8 +67,10 @@ var originUrl = urlResolve(window.location.href, true);
|
||||
* | pathname | The pathname, beginning with "/"
|
||||
*
|
||||
*/
|
||||
function urlResolve(url) {
|
||||
var href = url;
|
||||
function urlResolve(url, base) {
|
||||
var href = url,
|
||||
pathname;
|
||||
|
||||
if (msie) {
|
||||
// Normalize before parse. Refer Implementation Notes on why this is
|
||||
// done in two steps on IE.
|
||||
@@ -72,7 +80,22 @@ function urlResolve(url) {
|
||||
|
||||
urlParsingNode.setAttribute('href', href);
|
||||
|
||||
// $$urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
|
||||
/*
|
||||
* In Windows, on an anchor node on documents loaded from
|
||||
* the filesystem, the browser will return a pathname
|
||||
* prefixed with the drive name ('/C:/path') when a
|
||||
* pathname without a drive is set:
|
||||
* * a.setAttribute('href', '/foo')
|
||||
* * a.pathname === '/C:/foo' //true
|
||||
*
|
||||
* Inside of Angular, we're always using pathnames that
|
||||
* do not include drive names for routing.
|
||||
*/
|
||||
|
||||
pathname = removeWindowsDriveName(urlParsingNode.pathname, url, base);
|
||||
pathname = (pathname.charAt(0) === '/') ? pathname : '/' + pathname;
|
||||
|
||||
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
|
||||
return {
|
||||
href: urlParsingNode.href,
|
||||
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
|
||||
@@ -81,8 +104,7 @@ function urlResolve(url) {
|
||||
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
|
||||
hostname: urlParsingNode.hostname,
|
||||
port: urlParsingNode.port,
|
||||
pathname: urlParsingNode.pathname && urlParsingNode.pathname.charAt(0) === '/' ?
|
||||
urlParsingNode.pathname : '/' + urlParsingNode.pathname
|
||||
pathname: pathname
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,3 +121,23 @@ function urlIsSameOrigin(requestUrl) {
|
||||
return (parsed.protocol === originUrl.protocol &&
|
||||
parsed.host === originUrl.host);
|
||||
}
|
||||
|
||||
function removeWindowsDriveName (path, url, base) {
|
||||
var firstPathSegmentMatch;
|
||||
|
||||
//Get the relative path from the input URL.
|
||||
if (url.indexOf(base) === 0) {
|
||||
url = url.replace(base, '');
|
||||
}
|
||||
|
||||
/*
|
||||
* The input URL intentionally contains a
|
||||
* first path segment that ends with a colon.
|
||||
*/
|
||||
if (windowsFilePathExp.exec(url)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
firstPathSegmentMatch = windowsFilePathExp.exec(path);
|
||||
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
|
||||
}
|
||||
+90
-33
@@ -159,7 +159,7 @@
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations
|
||||
* Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
|
||||
* on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
|
||||
* are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
|
||||
* will also be reset if more than 10ms has passed after the last animation has been fired.
|
||||
@@ -222,7 +222,7 @@
|
||||
* JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
|
||||
* a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
|
||||
* the element's CSS class attribute value and then run the matching animation event function (if found).
|
||||
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
|
||||
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
|
||||
* be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
|
||||
*
|
||||
* Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
|
||||
@@ -510,6 +510,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @function
|
||||
*
|
||||
* @param {boolean=} value If provided then set the animation on or off.
|
||||
* @param {jQuery/jqLite element=} element If provided then the element will be used to represent the enable/disable operation
|
||||
* @return {boolean} Current animation state.
|
||||
*
|
||||
* @description
|
||||
@@ -548,7 +549,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
and the onComplete callback will be fired once the animation is fully complete.
|
||||
*/
|
||||
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
|
||||
var classes = (element.attr('class') || '') + ' ' + className;
|
||||
var currentClassName = element.attr('class') || '';
|
||||
var classes = currentClassName + ' ' + className;
|
||||
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
|
||||
if (!parentElement) {
|
||||
parentElement = afterElement ? afterElement.parent() : element.parent();
|
||||
@@ -563,7 +565,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
//the animation if any matching animations are not found at all.
|
||||
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
|
||||
if (animationsDisabled(element, parentElement) || matches.length === 0) {
|
||||
domOperation();
|
||||
fireDOMOperation();
|
||||
closeAnimation();
|
||||
return;
|
||||
}
|
||||
@@ -596,27 +598,48 @@ angular.module('ngAnimate', ['ng'])
|
||||
//this would mean that an animation was not allowed so let the existing
|
||||
//animation do it's thing and close this one early
|
||||
if(animations.length === 0) {
|
||||
domOperation();
|
||||
fireDOMOperation();
|
||||
fireDoneCallbackAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
//this value will be searched for class-based CSS className lookup. Therefore,
|
||||
//we prefix and suffix the current className value with spaces to avoid substring
|
||||
//lookups of className tokens
|
||||
var futureClassName = ' ' + currentClassName + ' ';
|
||||
if(ngAnimateState.running) {
|
||||
//if an animation is currently running on the element then lets take the steps
|
||||
//to cancel that animation and fire any required callbacks
|
||||
$timeout.cancel(ngAnimateState.closeAnimationTimeout);
|
||||
cleanup(element);
|
||||
cancelAnimations(ngAnimateState.animations);
|
||||
(ngAnimateState.done || noop)(true);
|
||||
|
||||
//if the class is removed during the reflow then it will revert the styles temporarily
|
||||
//back to the base class CSS styling causing a jump-like effect to occur. This check
|
||||
//here ensures that the domOperation is only performed after the reflow has commenced
|
||||
if(ngAnimateState.beforeComplete) {
|
||||
(ngAnimateState.done || noop)(true);
|
||||
} else if(isClassBased && !ngAnimateState.structural) {
|
||||
//class-based animations will compare element className values after cancelling the
|
||||
//previous animation to see if the element properties already contain the final CSS
|
||||
//class and if so then the animation will be skipped. Since the domOperation will
|
||||
//be performed only after the reflow is complete then our element's className value
|
||||
//will be invalid. Therefore the same string manipulation that would occur within the
|
||||
//DOM operation will be performed below so that the class comparison is valid...
|
||||
futureClassName = ngAnimateState.event == 'removeClass' ?
|
||||
futureClassName.replace(ngAnimateState.className, '') :
|
||||
futureClassName + ngAnimateState.className + ' ';
|
||||
}
|
||||
}
|
||||
|
||||
//There is no point in perform a class-based animation if the element already contains
|
||||
//(on addClass) or doesn't contain (on removeClass) the className being animated.
|
||||
//The reason why this is being called after the previous animations are cancelled
|
||||
//is so that the CSS classes present on the element can be properly examined.
|
||||
if((animationEvent == 'addClass' && element.hasClass(className)) ||
|
||||
(animationEvent == 'removeClass' && !element.hasClass(className))) {
|
||||
domOperation();
|
||||
var classNameToken = ' ' + className + ' ';
|
||||
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
|
||||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
|
||||
fireDOMOperation();
|
||||
fireDoneCallbackAsync();
|
||||
return;
|
||||
}
|
||||
@@ -627,6 +650,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
|
||||
element.data(NG_ANIMATE_STATE, {
|
||||
running:true,
|
||||
event:animationEvent,
|
||||
className:className,
|
||||
structural:!isClassBased,
|
||||
animations:animations,
|
||||
done:onBeforeAnimationsComplete
|
||||
@@ -637,7 +662,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
|
||||
|
||||
function onBeforeAnimationsComplete(cancelled) {
|
||||
domOperation();
|
||||
fireDOMOperation();
|
||||
if(cancelled === true) {
|
||||
closeAnimation();
|
||||
return;
|
||||
@@ -695,6 +720,15 @@ angular.module('ngAnimate', ['ng'])
|
||||
doneCallback && $timeout(doneCallback, 0, false);
|
||||
}
|
||||
|
||||
//it is less complicated to use a flag than managing and cancelling
|
||||
//timeouts containing multiple callbacks.
|
||||
function fireDOMOperation() {
|
||||
if(!fireDOMOperation.hasBeenRun) {
|
||||
fireDOMOperation.hasBeenRun = true;
|
||||
domOperation();
|
||||
}
|
||||
}
|
||||
|
||||
function closeAnimation() {
|
||||
if(!closeAnimation.hasBeenRun) {
|
||||
closeAnimation.hasBeenRun = true;
|
||||
@@ -737,10 +771,10 @@ angular.module('ngAnimate', ['ng'])
|
||||
function cancelAnimations(animations) {
|
||||
var isCancelledFlag = true;
|
||||
forEach(animations, function(animation) {
|
||||
if(!animations['beforeComplete']) {
|
||||
if(!animations.beforeComplete) {
|
||||
(animation.beforeEnd || noop)(isCancelledFlag);
|
||||
}
|
||||
if(!animations['afterComplete']) {
|
||||
if(!animations.afterComplete) {
|
||||
(animation.afterEnd || noop)(isCancelledFlag);
|
||||
}
|
||||
});
|
||||
@@ -842,13 +876,6 @@ angular.module('ngAnimate', ['ng'])
|
||||
}, 10, false);
|
||||
}
|
||||
|
||||
function applyStyle(node, style) {
|
||||
var oldStyle = node.getAttribute('style') || '';
|
||||
var newStyle = (oldStyle.length > 0 ? '; ' : '') + style;
|
||||
node.setAttribute('style', newStyle);
|
||||
return oldStyle;
|
||||
}
|
||||
|
||||
function getElementAnimationDetails(element, cacheKey) {
|
||||
var data = cacheKey ? lookupCache[cacheKey] : null;
|
||||
if(!data) {
|
||||
@@ -967,7 +994,9 @@ angular.module('ngAnimate', ['ng'])
|
||||
if(timings.transitionDuration > 0) {
|
||||
element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
|
||||
activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
|
||||
node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
|
||||
blockTransitions(element);
|
||||
} else {
|
||||
blockKeyframeAnimations(element);
|
||||
}
|
||||
|
||||
forEach(className.split(' '), function(klass, i) {
|
||||
@@ -987,6 +1016,25 @@ angular.module('ngAnimate', ['ng'])
|
||||
return true;
|
||||
}
|
||||
|
||||
function blockTransitions(element) {
|
||||
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
|
||||
}
|
||||
|
||||
function blockKeyframeAnimations(element) {
|
||||
element[0].style[ANIMATION_PROP] = 'none 0s';
|
||||
}
|
||||
|
||||
function unblockTransitions(element) {
|
||||
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
|
||||
if(node.style[prop] && node.style[prop].length > 0) {
|
||||
node.style[prop] = '';
|
||||
}
|
||||
}
|
||||
|
||||
function unblockKeyframeAnimations(element) {
|
||||
element[0].style[ANIMATION_PROP] = '';
|
||||
}
|
||||
|
||||
function animateRun(element, className, activeAnimationComplete) {
|
||||
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
|
||||
if(!element.hasClass(className) || !data) {
|
||||
@@ -1002,20 +1050,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
|
||||
var startTime = Date.now();
|
||||
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
|
||||
var formerStyle;
|
||||
var ii = data.ii;
|
||||
|
||||
var applyFallbackStyle, style = '';
|
||||
var applyFallbackStyle, style = '', appliedStyles = [];
|
||||
if(timings.transitionDuration > 0) {
|
||||
node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
|
||||
|
||||
var propertyStyle = timings.transitionPropertyStyle;
|
||||
if(propertyStyle.indexOf('all') == -1) {
|
||||
applyFallbackStyle = true;
|
||||
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip';
|
||||
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'border-spacing';
|
||||
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
|
||||
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-property');
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-duration');
|
||||
}
|
||||
} else {
|
||||
unblockKeyframeAnimations(element);
|
||||
}
|
||||
|
||||
if(ii > 0) {
|
||||
@@ -1027,16 +1076,19 @@ angular.module('ngAnimate', ['ng'])
|
||||
|
||||
style += CSS_PREFIX + 'transition-delay: ' +
|
||||
prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
|
||||
appliedStyles.push(CSS_PREFIX + 'transition-delay');
|
||||
}
|
||||
|
||||
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
|
||||
style += CSS_PREFIX + 'animation-delay: ' +
|
||||
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
|
||||
appliedStyles.push(CSS_PREFIX + 'animation-delay');
|
||||
}
|
||||
}
|
||||
|
||||
if(style.length > 0) {
|
||||
formerStyle = applyStyle(node, style);
|
||||
if(appliedStyles.length > 0) {
|
||||
var oldStyle = node.getAttribute('style') || '';
|
||||
node.setAttribute('style', oldStyle + ' ' + style);
|
||||
}
|
||||
|
||||
element.on(css3AnimationEvents, onAnimationProgress);
|
||||
@@ -1049,10 +1101,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
element.off(css3AnimationEvents, onAnimationProgress);
|
||||
element.removeClass(activeClassName);
|
||||
animateClose(element, className);
|
||||
if(formerStyle != null) {
|
||||
formerStyle.length > 0 ?
|
||||
node.setAttribute('style', formerStyle) :
|
||||
node.removeAttribute('style');
|
||||
for (var i in appliedStyles) {
|
||||
node.style.removeProperty(appliedStyles[i]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1116,6 +1166,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
//happen in the first place
|
||||
var cancel = preReflowCancellation;
|
||||
afterReflow(function() {
|
||||
unblockTransitions(element);
|
||||
//once the reflow is complete then we point cancel to
|
||||
//the new cancellation function which will remove all of the
|
||||
//animation properties from the active animation
|
||||
@@ -1179,7 +1230,10 @@ angular.module('ngAnimate', ['ng'])
|
||||
beforeAddClass : function(element, className, animationCompleted) {
|
||||
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
|
||||
if(cancellationMethod) {
|
||||
afterReflow(animationCompleted);
|
||||
afterReflow(function() {
|
||||
unblockTransitions(element);
|
||||
animationCompleted();
|
||||
});
|
||||
return cancellationMethod;
|
||||
}
|
||||
animationCompleted();
|
||||
@@ -1192,7 +1246,10 @@ angular.module('ngAnimate', ['ng'])
|
||||
beforeRemoveClass : function(element, className, animationCompleted) {
|
||||
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
|
||||
if(cancellationMethod) {
|
||||
afterReflow(animationCompleted);
|
||||
afterReflow(function() {
|
||||
unblockTransitions(element);
|
||||
animationCompleted();
|
||||
});
|
||||
return cancellationMethod;
|
||||
}
|
||||
animationCompleted();
|
||||
|
||||
Vendored
+2
-17
@@ -1572,7 +1572,7 @@ function MockHttpExpectation(method, url, data, headers) {
|
||||
if (angular.isUndefined(data)) return true;
|
||||
if (data && angular.isFunction(data.test)) return data.test(d);
|
||||
if (data && angular.isFunction(data)) return data(d);
|
||||
if (data && !angular.isString(data)) return angular.toJson(data) == d;
|
||||
if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
|
||||
return data == d;
|
||||
};
|
||||
|
||||
@@ -1657,21 +1657,6 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
|
||||
$browser.defer.flush(delay);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngMock.$timeout#flushNext
|
||||
* @methodOf ngMock.$timeout
|
||||
* @description
|
||||
*
|
||||
* Flushes the next timeout in the queue and compares it to the provided delay
|
||||
*
|
||||
* @param {number=} expectedDelay the delay value that will be asserted against the delay of the
|
||||
* next timeout function
|
||||
*/
|
||||
$delegate.flushNext = function(expectedDelay) {
|
||||
$browser.defer.flushNext(expectedDelay);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngMock.$timeout#verifyNoPendingTasks
|
||||
@@ -2127,4 +2112,4 @@ angular.mock.clearDataCache = function() {
|
||||
}
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
})(window);
|
||||
|
||||
+29
-61
@@ -2,6 +2,28 @@
|
||||
|
||||
var $resourceMinErr = angular.$$minErr('$resource');
|
||||
|
||||
// Helper functions and regex to lookup a dotted path on an object
|
||||
// stopping at undefined/null. The path must be composed of ASCII
|
||||
// identifiers (just like $parse)
|
||||
var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
|
||||
|
||||
function isValidDottedPath(path) {
|
||||
return (path != null && path !== '' && path !== 'hasOwnProperty' &&
|
||||
MEMBER_NAME_REGEX.test('.' + path));
|
||||
}
|
||||
|
||||
function lookupDottedPath(obj, path) {
|
||||
if (!isValidDottedPath(path)) {
|
||||
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
|
||||
}
|
||||
var keys = path.split('.');
|
||||
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
|
||||
var key = keys[i];
|
||||
obj = (obj !== null) ? obj[key] : undefined;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngResource
|
||||
@@ -129,7 +151,7 @@ var $resourceMinErr = angular.$$minErr('$resource');
|
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
||||
* object results in no rendering, once the data arrives from the server then the object is
|
||||
* populated with the data and the view automatically re-renders itself showing the new data. This
|
||||
* means that in most case one never has to write a callback function for the action methods.
|
||||
* means that in most cases one never has to write a callback function for the action methods.
|
||||
*
|
||||
* The action methods on the class object or instance object can be invoked with the following
|
||||
* parameters:
|
||||
@@ -231,61 +253,10 @@ var $resourceMinErr = angular.$$minErr('$resource');
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
* # Buzz client
|
||||
|
||||
Let's look at what a buzz client created with the `$resource` service looks like:
|
||||
<doc:example>
|
||||
<doc:source jsfiddle="false">
|
||||
<script>
|
||||
function BuzzController($resource) {
|
||||
this.userId = 'googlebuzz';
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt:'json', callback:'JSON_CALLBACK'},
|
||||
{
|
||||
get:{method:'JSONP', params:{visibility:'@self'}},
|
||||
replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BuzzController.prototype = {
|
||||
fetch: function() {
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
|
||||
}
|
||||
};
|
||||
BuzzController.$inject = ['$resource'];
|
||||
</script>
|
||||
|
||||
<div ng-controller="BuzzController">
|
||||
<input ng-model="userId"/>
|
||||
<button ng-click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div ng-repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href ng-click="expandReplies(item)" style="float: right;">Expand replies:
|
||||
{{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
factory('$resource', ['$http', '$parse', '$q', function($http, $parse, $q) {
|
||||
factory('$resource', ['$http', '$q', function($http, $q) {
|
||||
|
||||
var DEFAULT_ACTIONS = {
|
||||
'get': {method:'GET'},
|
||||
'save': {method:'POST'},
|
||||
@@ -297,10 +268,7 @@ angular.module('ngResource', ['ng']).
|
||||
forEach = angular.forEach,
|
||||
extend = angular.extend,
|
||||
copy = angular.copy,
|
||||
isFunction = angular.isFunction,
|
||||
getter = function(obj, path) {
|
||||
return $parse(path)(obj);
|
||||
};
|
||||
isFunction = angular.isFunction;
|
||||
|
||||
/**
|
||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
||||
@@ -415,7 +383,7 @@ angular.module('ngResource', ['ng']).
|
||||
forEach(actionParams, function(value, key){
|
||||
if (isFunction(value)) { value = value(); }
|
||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
|
||||
getter(data, value.substr(1)) : value;
|
||||
lookupDottedPath(data, value.substr(1)) : value;
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
@@ -471,7 +439,7 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
/* jshint +W086 */ /* (purposefully fall through case statements) */
|
||||
|
||||
var isInstanceCall = data instanceof Resource;
|
||||
var isInstanceCall = this instanceof Resource;
|
||||
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
|
||||
var httpConfig = {};
|
||||
var responseInterceptor = action.interceptor && action.interceptor.response ||
|
||||
@@ -554,7 +522,7 @@ angular.module('ngResource', ['ng']).
|
||||
if (isFunction(params)) {
|
||||
error = success; success = params; params = {};
|
||||
}
|
||||
var result = Resource[name](params, this, success, error);
|
||||
var result = Resource[name].call(this, params, this, success, error);
|
||||
return result.$promise || result;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -173,8 +173,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
|
||||
terminal: true,
|
||||
priority: 400,
|
||||
transclude: 'element',
|
||||
compile: function(element, attr, linker) {
|
||||
return function(scope, $element, attr) {
|
||||
link: function(scope, $element, attr, ctrl, $transclude) {
|
||||
var currentScope,
|
||||
currentElement,
|
||||
autoScrollExp = attr.autoscroll,
|
||||
@@ -200,42 +199,47 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
|
||||
|
||||
if (template) {
|
||||
var newScope = scope.$new();
|
||||
linker(newScope, function(clone) {
|
||||
clone.html(template);
|
||||
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
|
||||
if (angular.isDefined(autoScrollExp)
|
||||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
|
||||
$anchorScroll();
|
||||
}
|
||||
});
|
||||
|
||||
cleanupLastView();
|
||||
|
||||
var link = $compile(clone.contents()),
|
||||
current = $route.current;
|
||||
|
||||
currentScope = current.scope = newScope;
|
||||
currentElement = clone;
|
||||
|
||||
if (current.controller) {
|
||||
locals.$scope = currentScope;
|
||||
var controller = $controller(current.controller, locals);
|
||||
if (current.controllerAs) {
|
||||
currentScope[current.controllerAs] = controller;
|
||||
}
|
||||
clone.data('$ngControllerController', controller);
|
||||
clone.children().data('$ngControllerController', controller);
|
||||
// Note: This will also link all children of ng-view that were contained in the original
|
||||
// html. If that content contains controllers, ... they could pollute/change the scope.
|
||||
// However, using ng-view on an element with additional content does not make sense...
|
||||
// Note: We can't remove them in the cloneAttchFn of $transclude as that
|
||||
// function is called before linking the content, which would apply child
|
||||
// directives to non existing elements.
|
||||
var clone = $transclude(newScope, angular.noop);
|
||||
clone.html(template);
|
||||
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
|
||||
if (angular.isDefined(autoScrollExp)
|
||||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
|
||||
$anchorScroll();
|
||||
}
|
||||
|
||||
link(currentScope);
|
||||
currentScope.$emit('$viewContentLoaded');
|
||||
currentScope.$eval(onloadExp);
|
||||
});
|
||||
|
||||
cleanupLastView();
|
||||
|
||||
var link = $compile(clone.contents()),
|
||||
current = $route.current;
|
||||
|
||||
currentScope = current.scope = newScope;
|
||||
currentElement = clone;
|
||||
|
||||
if (current.controller) {
|
||||
locals.$scope = currentScope;
|
||||
var controller = $controller(current.controller, locals);
|
||||
if (current.controllerAs) {
|
||||
currentScope[current.controllerAs] = controller;
|
||||
}
|
||||
clone.data('$ngControllerController', controller);
|
||||
clone.children().data('$ngControllerController', controller);
|
||||
}
|
||||
|
||||
link(currentScope);
|
||||
currentScope.$emit('$viewContentLoaded');
|
||||
currentScope.$eval(onloadExp);
|
||||
} else {
|
||||
cleanupLastView();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
*
|
||||
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
|
||||
*
|
||||
* ## Example
|
||||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
|
||||
*
|
||||
* {@installModule route}
|
||||
*
|
||||
* <div doc-module-components="ngRoute"></div>
|
||||
@@ -24,8 +27,12 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Used for configuring routes. See {@link ngRoute.$route $route} for an example.
|
||||
* Used for configuring routes.
|
||||
*
|
||||
* ## Example
|
||||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
|
||||
*
|
||||
* ## Dependencies
|
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
||||
*/
|
||||
function $RouteProvider(){
|
||||
|
||||
@@ -78,4 +78,8 @@ describe('module loader', function() {
|
||||
window.angular.module('hasOwnProperty', []);
|
||||
}).toThrowMinErr('ng','badname', "hasOwnProperty is not a valid module name");
|
||||
});
|
||||
|
||||
it('should expose `$$minErr` on the `angular` object', function() {
|
||||
expect(window.angular.$$minErr).toEqual(jasmine.any(Function));
|
||||
})
|
||||
});
|
||||
|
||||
+289
-4
@@ -504,6 +504,14 @@ describe('$compile', function() {
|
||||
expect(element).toBe(attr.$$element);
|
||||
}
|
||||
}));
|
||||
directive('replaceWithInterpolatedStyle', valueFn({
|
||||
replace: true,
|
||||
template: '<div style="width:{{1+1}}px">Replace with interpolated style!</div>',
|
||||
compile: function(element, attr) {
|
||||
attr.$set('compiled', 'COMPILED');
|
||||
expect(element).toBe(attr.$$element);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
|
||||
@@ -581,13 +589,22 @@ describe('$compile', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should handle interpolated css from replacing directive', inject(
|
||||
it('should handle interpolated css class from replacing directive', inject(
|
||||
function($compile, $rootScope) {
|
||||
element = $compile('<div replace-with-interpolated-class></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element).toHaveClass('class_2');
|
||||
}));
|
||||
|
||||
if (!msie || msie > 11) {
|
||||
// style interpolation not working on IE (including IE11).
|
||||
it('should handle interpolated css style from replacing directive', inject(
|
||||
function($compile, $rootScope) {
|
||||
element = $compile('<div replace-with-interpolated-style></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.css('width')).toBe('2px');
|
||||
}));
|
||||
}
|
||||
|
||||
it('should merge interpolated css class', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div class="one {{cls}} three" replace></div>')($rootScope);
|
||||
@@ -3421,6 +3438,113 @@ describe('$compile', function() {
|
||||
expect(log).toEqual('pre(); post(unicorn!)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy the directive controller to all clones', function() {
|
||||
var transcludeCtrl, cloneCount = 2;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'content',
|
||||
controller: function($transclude) {
|
||||
transcludeCtrl = this;
|
||||
},
|
||||
link: function(scope, el, attr, ctrl, $transclude) {
|
||||
var i;
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
$transclude(cloneAttach);
|
||||
}
|
||||
|
||||
function cloneAttach(clone) {
|
||||
el.append(clone);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div transclude><span></span></div>')($rootScope);
|
||||
var children = element.children(), i;
|
||||
expect(transcludeCtrl).toBeDefined();
|
||||
|
||||
expect(element.data('$transcludeController')).toBe(transcludeCtrl);
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
expect(children.eq(i).data('$transcludeController')).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide the $transclude controller local as 5th argument to the pre and post-link function', function() {
|
||||
var ctrlTransclude, preLinkTransclude, postLinkTransclude;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'content',
|
||||
controller: function($transclude) {
|
||||
ctrlTransclude = $transclude;
|
||||
},
|
||||
compile: function() {
|
||||
return {
|
||||
pre: function(scope, el, attr, ctrl, $transclude) {
|
||||
preLinkTransclude = $transclude;
|
||||
},
|
||||
post: function(scope, el, attr, ctrl, $transclude) {
|
||||
postLinkTransclude = $transclude;
|
||||
}
|
||||
};
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div transclude></div>')($rootScope);
|
||||
expect(ctrlTransclude).toBeDefined();
|
||||
expect(ctrlTransclude).toBe(preLinkTransclude);
|
||||
expect(ctrlTransclude).toBe(postLinkTransclude);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow an optional scope argument in $transclude', function() {
|
||||
var capturedChildCtrl;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'content',
|
||||
link: function(scope, element, attr, ctrl, $transclude) {
|
||||
$transclude(scope, function(clone) {
|
||||
element.append(clone);
|
||||
});
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div transclude>{{$id}}</div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe($rootScope.$id);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should expose the directive controller to transcluded children', function() {
|
||||
var capturedChildCtrl;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'content',
|
||||
controller: function() {
|
||||
},
|
||||
link: function(scope, element, attr, ctrl, $transclude) {
|
||||
$transclude(function(clone) {
|
||||
element.append(clone);
|
||||
});
|
||||
}
|
||||
}));
|
||||
directive('child', valueFn({
|
||||
require: '^transclude',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
capturedChildCtrl = ctrl;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div transclude><div child></div></div>')($rootScope);
|
||||
expect(capturedChildCtrl).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3454,7 +3578,6 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should only allow one element transclusion per element', function() {
|
||||
module(function() {
|
||||
directive('first', valueFn({
|
||||
@@ -3603,8 +3726,101 @@ describe('$compile', function() {
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow to access $transclude in the same directive', function() {
|
||||
var _$transclude;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'element',
|
||||
controller: function($transclude) {
|
||||
_$transclude = $transclude;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div transclude></div>')($rootScope);
|
||||
expect(_$transclude).toBeDefined()
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy the directive controller to all clones', function() {
|
||||
var transcludeCtrl, cloneCount = 2;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'element',
|
||||
controller: function() {
|
||||
transcludeCtrl = this;
|
||||
},
|
||||
link: function(scope, el, attr, ctrl, $transclude) {
|
||||
var i;
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
$transclude(cloneAttach);
|
||||
}
|
||||
|
||||
function cloneAttach(clone) {
|
||||
el.after(clone);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div><div transclude></div></div>')($rootScope);
|
||||
var children = element.children(), i;
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should expose the directive controller to transcluded children', function() {
|
||||
var capturedTranscludeCtrl;
|
||||
module(function() {
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'element',
|
||||
controller: function() {
|
||||
},
|
||||
link: function(scope, element, attr, ctrl, $transclude) {
|
||||
$transclude(scope, function(clone) {
|
||||
element.after(clone);
|
||||
});
|
||||
}
|
||||
}));
|
||||
directive('child', valueFn({
|
||||
require: '^transclude',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
capturedTranscludeCtrl = ctrl;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
element = $compile('<div transclude><div child></div></div>')($rootScope);
|
||||
expect(capturedTranscludeCtrl).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow access to $transclude in a templateUrl directive', function() {
|
||||
var transclude;
|
||||
module(function() {
|
||||
directive('template', valueFn({
|
||||
templateUrl: 'template.html',
|
||||
replace: true
|
||||
}));
|
||||
directive('transclude', valueFn({
|
||||
transclude: 'content',
|
||||
controller: function($transclude) {
|
||||
transclude = $transclude;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $httpBackend) {
|
||||
$httpBackend.expectGET('template.html').respond('<div transclude></div>');
|
||||
element = $compile('<div template></div>')($rootScope);
|
||||
$httpBackend.flush();
|
||||
expect(transclude).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should safely create transclude comment node and not break with "-->"',
|
||||
inject(function($rootScope) {
|
||||
@@ -4016,6 +4232,76 @@ describe('$compile', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('form[action]', function() {
|
||||
it('should pass through action attribute for the same domain', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
|
||||
$rootScope.testUrl = "different_page";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('action')).toEqual('different_page');
|
||||
}));
|
||||
|
||||
it('should clear out action attribute for a different domain', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
|
||||
$rootScope.testUrl = "http://a.different.domain.example.com";
|
||||
expect(function() { $rootScope.$apply() }).toThrowMinErr(
|
||||
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
|
||||
"loading resource from url not allowed by $sceDelegate policy. URL: " +
|
||||
"http://a.different.domain.example.com");
|
||||
}));
|
||||
|
||||
it('should clear out JS action attribute', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:alert(1);";
|
||||
expect(function() { $rootScope.$apply() }).toThrowMinErr(
|
||||
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
|
||||
"loading resource from url not allowed by $sceDelegate policy. URL: " +
|
||||
"javascript:alert(1);");
|
||||
}));
|
||||
|
||||
it('should clear out non-resource_url action attribute', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()");
|
||||
expect($rootScope.$apply).toThrowMinErr(
|
||||
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
|
||||
"loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()");
|
||||
}));
|
||||
|
||||
it('should pass through $sce.trustAs() values in action attribute', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()");
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('action')).toEqual('javascript:doTrustedStuff()');
|
||||
}));
|
||||
});
|
||||
|
||||
if (!msie || msie >= 11) {
|
||||
describe('iframe[srcdoc]', function() {
|
||||
it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
|
||||
$rootScope.html = '<div onclick="">hello</div>';
|
||||
expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp(
|
||||
/Can't interpolate: {{html}}\n/.source +
|
||||
/[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source));
|
||||
}));
|
||||
|
||||
it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
|
||||
$rootScope.html = $sce.trustAsCss('<div onclick="">hello</div>');
|
||||
expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp(
|
||||
/Can't interpolate: {{html}}\n/.source +
|
||||
/[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source));
|
||||
}));
|
||||
|
||||
it('should set html for trusted values', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
|
||||
$rootScope.html = $sce.trustAsHtml('<div onclick="">hello</div>');
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.attr('srcdoc'))).toEqual('<div onclick="">hello</div>');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
describe('ngAttr* attribute binding', function() {
|
||||
|
||||
it('should bind after digest but not before', inject(function($compile, $rootScope) {
|
||||
@@ -4263,7 +4549,6 @@ describe('$compile', function() {
|
||||
$compile(element)($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
data = $animate.flushNext('removeClass');
|
||||
|
||||
expect(element.hasClass('fire')).toBe(true);
|
||||
|
||||
|
||||
@@ -454,6 +454,20 @@ describe('input', function() {
|
||||
expect(scope.name).toEqual('adam');
|
||||
});
|
||||
|
||||
it('should not update the model between "compositionstart" and "compositionend"', function() {
|
||||
compileInput('<input type="text" ng-model="name" name="alias"" />');
|
||||
changeInputValueTo('a');
|
||||
expect(scope.name).toEqual('a');
|
||||
if (!(msie < 9)) {
|
||||
browserTrigger(inputElm, 'compositionstart');
|
||||
changeInputValueTo('adam');
|
||||
expect(scope.name).toEqual('a');
|
||||
browserTrigger(inputElm, 'compositionend');
|
||||
}
|
||||
changeInputValueTo('adam');
|
||||
expect(scope.name).toEqual('adam');
|
||||
});
|
||||
|
||||
describe('"paste" and "cut" events', function() {
|
||||
beforeEach(function() {
|
||||
// Force browser to report a lack of an 'input' event
|
||||
|
||||
@@ -321,7 +321,6 @@ describe('ngClass animations', function() {
|
||||
$rootScope.val = 'one';
|
||||
$rootScope.$digest();
|
||||
$animate.flushNext('addClass');
|
||||
$animate.flushNext('addClass');
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
$rootScope.val = '';
|
||||
@@ -411,4 +410,46 @@ describe('ngClass animations', function() {
|
||||
expect(enterComplete).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not remove classes if they're going to be added back right after", function() {
|
||||
module('mock.animate');
|
||||
|
||||
inject(function($rootScope, $compile, $animate) {
|
||||
var className;
|
||||
|
||||
$rootScope.one = true;
|
||||
$rootScope.two = true;
|
||||
$rootScope.three = true;
|
||||
|
||||
var element = angular.element('<div ng-class="{one:one, two:two, three:three}"></div>');
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
//this fires twice due to the class observer firing
|
||||
className = $animate.flushNext('addClass').params[1];
|
||||
expect(className).toBe('one two three');
|
||||
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
$rootScope.three = false;
|
||||
$rootScope.$digest();
|
||||
|
||||
className = $animate.flushNext('removeClass').params[1];
|
||||
expect(className).toBe('three');
|
||||
|
||||
expect($animate.queue.length).toBe(0);
|
||||
|
||||
$rootScope.two = false;
|
||||
$rootScope.three = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
className = $animate.flushNext('removeClass').params[1];
|
||||
expect(className).toBe('two');
|
||||
|
||||
className = $animate.flushNext('addClass').params[1];
|
||||
expect(className).toBe('three');
|
||||
|
||||
expect($animate.queue.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,4 +85,59 @@ describe('ngController', function() {
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('Vojta');
|
||||
}));
|
||||
|
||||
|
||||
it('should work with ngInclude on the same element', inject(function($compile, $rootScope, $httpBackend) {
|
||||
$rootScope.GreeterController = function($scope) {
|
||||
$scope.name = 'Vojta';
|
||||
};
|
||||
|
||||
element = $compile('<div><div ng-controller="GreeterController" ng-include="\'url\'"></div></div>')($rootScope);
|
||||
$httpBackend.expect('GET', 'url').respond('{{name}}');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
expect(element.text()).toEqual('Vojta');
|
||||
}));
|
||||
|
||||
|
||||
it('should only instantiate the controller once with ngInclude on the same element',
|
||||
inject(function($compile, $rootScope, $httpBackend) {
|
||||
|
||||
var count = 0;
|
||||
|
||||
$rootScope.CountController = function($scope) {
|
||||
count += 1;
|
||||
};
|
||||
|
||||
element = $compile('<div><div ng-controller="CountController" ng-include="url"></div></div>')($rootScope);
|
||||
|
||||
$httpBackend.expect('GET', 'first').respond('first');
|
||||
$rootScope.url = 'first';
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
|
||||
$httpBackend.expect('GET', 'second').respond('second');
|
||||
$rootScope.url = 'second';
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(count).toBe(1);
|
||||
}));
|
||||
|
||||
|
||||
it('when ngInclude is on the same element, the content included content should get a child scope of the controller',
|
||||
inject(function($compile, $rootScope, $httpBackend) {
|
||||
|
||||
var controllerScope;
|
||||
|
||||
$rootScope.ExposeScopeController = function($scope) {
|
||||
controllerScope = $scope;
|
||||
};
|
||||
|
||||
element = $compile('<div><div ng-controller="ExposeScopeController" ng-include="\'url\'"></div></div>')($rootScope);
|
||||
$httpBackend.expect('GET', 'url').respond('<div ng-init="name=\'Vojta\'"></div>');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
expect(controllerScope.name).toBeUndefined();
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -28,6 +28,22 @@ describe('ngIf', function () {
|
||||
expect(element.children().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not add the element twice if the condition goes from true to true', function () {
|
||||
$scope.hello = 'true1';
|
||||
makeIf('hello');
|
||||
expect(element.children().length).toBe(1);
|
||||
$scope.$apply('hello = "true2"');
|
||||
expect(element.children().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not recreate the element if the condition goes from true to true', function () {
|
||||
$scope.hello = 'true1';
|
||||
makeIf('hello');
|
||||
element.children().data('flag', true);
|
||||
$scope.$apply('hello = "true2"');
|
||||
expect(element.children().data('flag')).toBe(true);
|
||||
});
|
||||
|
||||
it('should create then remove the element if condition changes', function () {
|
||||
$scope.hello = true;
|
||||
makeIf('hello');
|
||||
@@ -132,6 +148,34 @@ describe('ngIf', function () {
|
||||
|
||||
});
|
||||
|
||||
describe('ngIf and transcludes', function() {
|
||||
it('should allow access to directive controller from children when used in a replace template', function() {
|
||||
var controller;
|
||||
module(function($compileProvider) {
|
||||
var directive = $compileProvider.directive;
|
||||
directive('template', valueFn({
|
||||
template: '<div ng-if="true"><span test></span></div>',
|
||||
replace: true,
|
||||
controller: function() {
|
||||
this.flag = true;
|
||||
}
|
||||
}));
|
||||
directive('test', valueFn({
|
||||
require: '^template',
|
||||
link: function(scope, el, attr, ctrl) {
|
||||
controller = ctrl;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div><div template></div></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(controller.flag).toBe(true);
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngIf animations', function () {
|
||||
var body, element, $rootElement;
|
||||
|
||||
|
||||
@@ -312,6 +312,31 @@ describe('ngInclude', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should exec scripts when jQuery is included', inject(function($compile, $rootScope, $httpBackend) {
|
||||
if (!jQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
element = $compile('<div><span ng-include="includeUrl"></span></div>')($rootScope);
|
||||
|
||||
// the element needs to be appended for the script to run
|
||||
element.appendTo(document.body);
|
||||
window._ngIncludeCausesScriptToRun = false;
|
||||
$httpBackend.expect('GET', 'url1').respond('<script>window._ngIncludeCausesScriptToRun = true;</script>');
|
||||
$rootScope.includeUrl = 'url1';
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(window._ngIncludeCausesScriptToRun).toBe(true);
|
||||
|
||||
// IE8 doesn't like deleting properties of window
|
||||
window._ngIncludeCausesScriptToRun = undefined;
|
||||
try {
|
||||
delete window._ngIncludeCausesScriptToRun;
|
||||
} catch (e) {}
|
||||
}));
|
||||
|
||||
|
||||
describe('autoscroll', function() {
|
||||
var autoScrollSpy;
|
||||
|
||||
@@ -439,6 +464,68 @@ describe('ngInclude', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngInclude and transcludes', function() {
|
||||
var element, directive;
|
||||
|
||||
beforeEach(module(function($compileProvider) {
|
||||
element = null;
|
||||
directive = $compileProvider.directive;
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
if (element) {
|
||||
dealoc(element);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow access to directive controller from children when used in a replace template', function() {
|
||||
var controller;
|
||||
module(function() {
|
||||
directive('template', valueFn({
|
||||
template: '<div ng-include="\'include.html\'"></div>',
|
||||
replace: true,
|
||||
controller: function() {
|
||||
this.flag = true;
|
||||
}
|
||||
}));
|
||||
directive('test', valueFn({
|
||||
require: '^template',
|
||||
link: function(scope, el, attr, ctrl) {
|
||||
controller = ctrl;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope, $httpBackend) {
|
||||
$httpBackend.expectGET('include.html').respond('<div><div test></div></div>');
|
||||
element = $compile('<div><div template></div></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
$httpBackend.flush();
|
||||
expect(controller.flag).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should compile it's content correctly (although we remove it later)", function() {
|
||||
var testElement;
|
||||
module(function() {
|
||||
directive('test', function() {
|
||||
return {
|
||||
link: function(scope, element) {
|
||||
testElement = element;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($compile, $rootScope, $httpBackend) {
|
||||
$httpBackend.expectGET('include.html').respond(' ');
|
||||
element = $compile('<div><div ng-include="\'include.html\'"><div test></div></div></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
$httpBackend.flush();
|
||||
expect(testElement[0].nodeName).toBe('DIV');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngInclude animations', function() {
|
||||
var body, element, $rootElement;
|
||||
|
||||
|
||||
@@ -1058,6 +1058,33 @@ describe('ngRepeat', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngRepeat and transcludes', function() {
|
||||
it('should allow access to directive controller from children when used in a replace template', function() {
|
||||
var controller;
|
||||
module(function($compileProvider) {
|
||||
var directive = $compileProvider.directive;
|
||||
directive('template', valueFn({
|
||||
template: '<div ng-repeat="l in [1]"><span test></span></div>',
|
||||
replace: true,
|
||||
controller: function() {
|
||||
this.flag = true;
|
||||
}
|
||||
}));
|
||||
directive('test', valueFn({
|
||||
require: '^template',
|
||||
link: function(scope, el, attr, ctrl) {
|
||||
controller = ctrl;
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div><div template></div></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(controller.flag).toBe(true);
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngRepeat animations', function() {
|
||||
var body, element, $rootElement;
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('$httpBackend', function() {
|
||||
return {};
|
||||
}),
|
||||
body: {
|
||||
appendChild: jasmine.createSpy('body.appendChid').andCallFake(function(script) {
|
||||
appendChild: jasmine.createSpy('body.appendChild').andCallFake(function(script) {
|
||||
fakeDocument.$$scripts.push(script);
|
||||
}),
|
||||
removeChild: jasmine.createSpy('body.removeChild').andCallFake(function(script) {
|
||||
@@ -118,6 +118,25 @@ describe('$httpBackend', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not try to read response data when request is aborted', function() {
|
||||
callback.andCallFake(function(status, response, headers) {
|
||||
expect(status).toBe(-1);
|
||||
expect(response).toBe(null);
|
||||
expect(headers).toBe(null);
|
||||
});
|
||||
$backend('GET', '/url', null, callback, {}, 2000);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
spyOn(xhr, 'abort');
|
||||
|
||||
fakeTimeout.flush();
|
||||
expect(xhr.abort).toHaveBeenCalledOnce();
|
||||
|
||||
xhr.status = 0;
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should abort request on timeout', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(-1);
|
||||
@@ -273,7 +292,7 @@ describe('$httpBackend', function() {
|
||||
script.readyState = 'complete';
|
||||
script.onreadystatechange();
|
||||
} else {
|
||||
script.onload()
|
||||
script.onload();
|
||||
}
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
@@ -294,7 +313,7 @@ describe('$httpBackend', function() {
|
||||
script.readyState = 'complete';
|
||||
script.onreadystatechange();
|
||||
} else {
|
||||
script.onload()
|
||||
script.onload();
|
||||
}
|
||||
|
||||
expect(callbacks[callbackId]).toBeUndefined();
|
||||
@@ -302,6 +321,38 @@ describe('$httpBackend', function() {
|
||||
});
|
||||
|
||||
|
||||
if(msie<=8) {
|
||||
|
||||
it('should attach onreadystatechange handler to the script object', function() {
|
||||
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop);
|
||||
|
||||
expect(fakeDocument.$$scripts[0].onreadystatechange).toEqual(jasmine.any(Function));
|
||||
|
||||
var script = fakeDocument.$$scripts[0];
|
||||
|
||||
script.readyState = 'complete';
|
||||
script.onreadystatechange();
|
||||
|
||||
expect(script.onreadystatechange).toBe(null);
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
it('should attach onload and onerror handlers to the script object', function() {
|
||||
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop);
|
||||
|
||||
expect(fakeDocument.$$scripts[0].onload).toEqual(jasmine.any(Function));
|
||||
expect(fakeDocument.$$scripts[0].onerror).toEqual(jasmine.any(Function));
|
||||
|
||||
var script = fakeDocument.$$scripts[0];
|
||||
script.onload();
|
||||
|
||||
expect(script.onload).toBe(null);
|
||||
expect(script.onerror).toBe(null);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
it('should call callback with status -2 when script fails to load', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(-2);
|
||||
@@ -316,7 +367,7 @@ describe('$httpBackend', function() {
|
||||
script.readyState = 'complete';
|
||||
script.onreadystatechange();
|
||||
} else {
|
||||
script.onload()
|
||||
script.onload();
|
||||
}
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
@@ -10,6 +10,56 @@ describe('$location', function() {
|
||||
jqLite(document).off('click');
|
||||
});
|
||||
|
||||
|
||||
describe('File Protocol', function () {
|
||||
var urlParsingNodePlaceholder;
|
||||
|
||||
beforeEach(inject(function ($sniffer) {
|
||||
if ($sniffer.msie) return;
|
||||
|
||||
urlParsingNodePlaceholder = urlParsingNode;
|
||||
|
||||
//temporarily overriding the DOM element
|
||||
//with output from IE, if not in IE
|
||||
urlParsingNode = {
|
||||
hash : "#/C:/",
|
||||
host : "",
|
||||
hostname : "",
|
||||
href : "file:///C:/base#!/C:/foo",
|
||||
pathname : "/C:/foo",
|
||||
port : "",
|
||||
protocol : "file:",
|
||||
search : "",
|
||||
setAttribute: angular.noop
|
||||
};
|
||||
}));
|
||||
|
||||
afterEach(inject(function ($sniffer) {
|
||||
if ($sniffer.msie) return;
|
||||
//reset urlParsingNode
|
||||
urlParsingNode = urlParsingNodePlaceholder;
|
||||
expect(urlParsingNode.pathname).not.toBe('/C:/foo');
|
||||
}));
|
||||
|
||||
|
||||
it('should not include the drive name in path() on WIN', function (){
|
||||
//See issue #4680 for details
|
||||
url = new LocationHashbangUrl('file:///base', '#!');
|
||||
url.$$parse('file:///base#!/foo?a=b&c#hash');
|
||||
|
||||
expect(url.path()).toBe('/foo');
|
||||
});
|
||||
|
||||
|
||||
it('should include the drive name if it was provided in the input url', function () {
|
||||
url = new LocationHashbangUrl('file:///base', '#!');
|
||||
url.$$parse('file:///base#!/C:/foo?a=b&c#hash');
|
||||
|
||||
expect(url.path()).toBe('/C:/foo');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('NewUrl', function() {
|
||||
beforeEach(function() {
|
||||
url = new LocationHtml5Url('http://www.domain.com:9877/');
|
||||
|
||||
+678
-730
File diff suppressed because it is too large
Load Diff
+13
-20
@@ -29,10 +29,10 @@ describe('SCE', function() {
|
||||
describe('IE8 quirks mode', function() {
|
||||
function runTest(enabled, documentMode, expectException) {
|
||||
module(function($provide) {
|
||||
$provide.value('$document', [{
|
||||
documentMode: documentMode,
|
||||
createElement: function() {}
|
||||
}]);
|
||||
$provide.value('$sniffer', {
|
||||
msie: documentMode,
|
||||
msieDocumentMode: documentMode
|
||||
});
|
||||
$provide.value('$sceDelegate', {trustAs: null, valueOf: null, getTrusted: null});
|
||||
});
|
||||
|
||||
@@ -43,22 +43,15 @@ describe('SCE', function() {
|
||||
return $injector.invoke(sceProvider.$get, sceProvider);
|
||||
}
|
||||
|
||||
var origMsie = $window.msie;
|
||||
try {
|
||||
$window.msie = true;
|
||||
if (expectException) {
|
||||
expect(constructSce).toThrowMinErr(
|
||||
'$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' +
|
||||
'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' +
|
||||
'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' +
|
||||
'information.');
|
||||
} else {
|
||||
// no exception.
|
||||
constructSce();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$window.msie = origMsie;
|
||||
if (expectException) {
|
||||
expect(constructSce).toThrowMinErr(
|
||||
'$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' +
|
||||
'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' +
|
||||
'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' +
|
||||
'information.');
|
||||
} else {
|
||||
// no exception.
|
||||
constructSce();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+10
-2
@@ -334,7 +334,15 @@ describe('$sniffer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true for msie when internet explorer is being used', inject(function($sniffer) {
|
||||
expect($sniffer.msie > 0).toBe(window.navigator.appName == 'Microsoft Internet Explorer');
|
||||
it('should return the internal msie flag', inject(function($sniffer) {
|
||||
expect(isNaN($sniffer.msie)).toBe(isNaN(msie));
|
||||
if (msie) {
|
||||
expect($sniffer.msie).toBe(msie);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should return document.documentMode as msieDocumentMode', function() {
|
||||
var someDocumentMode = 123;
|
||||
expect(sniffer({}, {documentMode: someDocumentMode}).msieDocumentMode).toBe(someDocumentMode);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -420,6 +420,27 @@ describe("ngAnimate", function() {
|
||||
expect(element.children().length).toBe(0);
|
||||
}));
|
||||
|
||||
it("should retain existing styles of the animated element",
|
||||
inject(function($animate, $rootScope, $sniffer, $timeout) {
|
||||
|
||||
element.append(child);
|
||||
child.attr('style', 'width: 20px');
|
||||
|
||||
$animate.addClass(child, 'ng-hide');
|
||||
$animate.leave(child);
|
||||
$rootScope.$digest();
|
||||
|
||||
if($sniffer.transitions) {
|
||||
$timeout.flush();
|
||||
|
||||
//this is to verify that the existing style is appended with a semicolon automatically
|
||||
expect(child.attr('style')).toMatch(/width: 20px;.+?/i);
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
|
||||
}
|
||||
|
||||
expect(child.attr('style')).toMatch(/width: 20px/i);
|
||||
}));
|
||||
|
||||
it("should call the cancel callback when another animation is called on the same element",
|
||||
inject(function($animate, $rootScope, $sniffer, $timeout) {
|
||||
|
||||
@@ -823,7 +844,7 @@ describe("ngAnimate", function() {
|
||||
$timeout.flush();
|
||||
|
||||
//IE removes the -ms- prefix when placed on the style
|
||||
var fallbackProperty = $sniffer.msie ? 'zoom' : 'clip';
|
||||
var fallbackProperty = $sniffer.msie ? 'zoom' : 'border-spacing';
|
||||
var regExp = new RegExp("transition-property:\\s+color\\s*,\\s*" + fallbackProperty + "\\s*;");
|
||||
expect(child2.attr('style') || '').toMatch(regExp);
|
||||
expect(child2.hasClass('ng-animate')).toBe(true);
|
||||
@@ -975,6 +996,35 @@ describe("ngAnimate", function() {
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
||||
it("should NOT overwrite styles with outdated values when animation completes",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
|
||||
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
var style = '-webkit-transition-duration: 1s, 2000ms, 1s;' +
|
||||
'-webkit-transition-property: height, left, opacity;' +
|
||||
'transition-duration: 1s, 2000ms, 1s;' +
|
||||
'transition-property: height, left, opacity;';
|
||||
|
||||
ss.addRule('.ng-hide-add', style);
|
||||
ss.addRule('.ng-hide-remove', style);
|
||||
|
||||
element = $compile(html('<div style="width: 100px">foo</div>'))($rootScope);
|
||||
element.addClass('ng-hide');
|
||||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
var now = Date.now();
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
|
||||
|
||||
element.css('width', '200px');
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 2000, elapsedTime: 2 });
|
||||
expect(element.css('width')).toBe("200px");
|
||||
}));
|
||||
|
||||
it("should animate for the highest duration",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
|
||||
var style = '-webkit-transition:1s linear all 2s;' +
|
||||
@@ -2515,6 +2565,36 @@ describe("ngAnimate", function() {
|
||||
expect(element.hasClass('yellow-add')).toBe(true);
|
||||
}));
|
||||
|
||||
it("should cancel and perform the dom operation only after the reflow has run",
|
||||
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
|
||||
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
ss.addRule('.green-add', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
|
||||
ss.addRule('.red-add', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
|
||||
var element = $compile('<div></div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$animate.addClass(element, 'green');
|
||||
expect(element.hasClass('green-add')).toBe(true);
|
||||
|
||||
$animate.addClass(element, 'red');
|
||||
expect(element.hasClass('red-add')).toBe(true);
|
||||
|
||||
expect(element.hasClass('green')).toBe(false);
|
||||
expect(element.hasClass('red')).toBe(false);
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
expect(element.hasClass('green')).toBe(true);
|
||||
expect(element.hasClass('red')).toBe(true);
|
||||
}));
|
||||
|
||||
it('should enable and disable animations properly on the root element', function() {
|
||||
var count = 0;
|
||||
module(function($animateProvider) {
|
||||
@@ -2599,4 +2679,96 @@ describe("ngAnimate", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should only perform the DOM operation once',
|
||||
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {
|
||||
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
ss.addRule('.base-class', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
|
||||
$animate.enabled(true);
|
||||
|
||||
var element = $compile('<div class="base-class one two"></div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$animate.removeClass(element, 'base-class one two');
|
||||
|
||||
//still true since we're before the reflow
|
||||
expect(element.hasClass('base-class')).toBe(true);
|
||||
|
||||
//this will cancel the remove animation
|
||||
$animate.addClass(element, 'base-class one two');
|
||||
|
||||
//the cancellation was a success and the class was added right away
|
||||
//since there was no successive animation for the after animation
|
||||
expect(element.hasClass('base-class')).toBe(true);
|
||||
|
||||
//the reflow...
|
||||
$timeout.flush();
|
||||
|
||||
//the reflow DOM operation was commenced but it ran before so it
|
||||
//shouldn't run agaun
|
||||
expect(element.hasClass('base-class')).toBe(true);
|
||||
}));
|
||||
|
||||
it('should block and unblock transitions before the dom operation occurs',
|
||||
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) {
|
||||
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
$animate.enabled(true);
|
||||
|
||||
ss.addRule('.cross-animation', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
|
||||
var capturedProperty = 'none';
|
||||
|
||||
var element = $compile('<div class="cross-animation"></div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
var node = element[0];
|
||||
node._setAttribute = node.setAttribute;
|
||||
node.setAttribute = function(prop, val) {
|
||||
if(prop == 'class' && val.indexOf('trigger-class') >= 0) {
|
||||
var propertyKey = ($sniffer.vendorPrefix == 'Webkit' ? '-webkit-' : '') + 'transition-property';
|
||||
capturedProperty = element.css(propertyKey);
|
||||
}
|
||||
node._setAttribute(prop, val);
|
||||
};
|
||||
|
||||
$animate.addClass(element, 'trigger-class');
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
expect(capturedProperty).not.toBe('none');
|
||||
}));
|
||||
|
||||
it('should block and unblock keyframe animations around the reflow operation',
|
||||
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) {
|
||||
|
||||
if (!$sniffer.animations) return;
|
||||
|
||||
$animate.enabled(true);
|
||||
|
||||
ss.addRule('.cross-animation', '-webkit-animation:1s my_animation;' +
|
||||
'animation:1s my_animation;');
|
||||
|
||||
var element = $compile('<div class="cross-animation"></div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
var node = element[0];
|
||||
var animationKey = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation';
|
||||
|
||||
$animate.addClass(element, 'trigger-class');
|
||||
|
||||
expect(node.style[animationKey]).toContain('none');
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
expect(node.style[animationKey]).not.toContain('none');
|
||||
}));
|
||||
});
|
||||
|
||||
Vendored
+51
-5
@@ -1,11 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var msie = +((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
|
||||
|
||||
describe('ngMock', function() {
|
||||
var noop = angular.noop;
|
||||
|
||||
|
||||
describe('TzDate', function() {
|
||||
|
||||
function minutes(min) {
|
||||
@@ -686,10 +683,10 @@ describe('ngMock', function() {
|
||||
expect(d($rootScope)).toMatch(/{"abc":"123"}/);
|
||||
}));
|
||||
|
||||
it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope){
|
||||
it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope, $sniffer){
|
||||
// MS IE8 just doesn't work for this kind of thing, since "for ... in" doesn't return
|
||||
// things like hasOwnProperty even if it is explicitly defined on the actual object!
|
||||
if (msie<=8) return;
|
||||
if ($sniffer.msie<=8) return;
|
||||
$rootScope.hasOwnProperty = 'X';
|
||||
expect(d($rootScope)).toMatch(/Scope\(.*\): \{/);
|
||||
expect(d($rootScope)).toMatch(/hasOwnProperty: "X"/);
|
||||
@@ -944,6 +941,29 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should match data object if specified', function() {
|
||||
hb.when('GET', '/a/b', {a: 1, b: 2}).respond(201, 'content1');
|
||||
hb.when('GET', '/a/b').respond(202, 'content2');
|
||||
|
||||
hb('GET', '/a/b', '{"a":1,"b":2}', function(status, response) {
|
||||
expect(status).toBe(201);
|
||||
expect(response).toBe('content1');
|
||||
});
|
||||
|
||||
hb('GET', '/a/b', '{"b":2,"a":1}', function(status, response) {
|
||||
expect(status).toBe(201);
|
||||
expect(response).toBe('content1');
|
||||
});
|
||||
|
||||
hb('GET', '/a/b', null, function(status, response) {
|
||||
expect(status).toBe(202);
|
||||
expect(response).toBe('content2');
|
||||
});
|
||||
|
||||
hb.flush();
|
||||
});
|
||||
|
||||
|
||||
it('should match only method', function() {
|
||||
hb.when('GET').respond(202, 'c');
|
||||
callback.andCallFake(function(status, response) {
|
||||
@@ -1075,6 +1095,32 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should not throw an exception when parsed body is equal to expected body object', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
|
||||
hb.expect('GET', '/match', {a: 1, b: 2});
|
||||
expect(function() {
|
||||
hb('GET', '/match', '{"a":1,"b":2}', noop, {});
|
||||
}).not.toThrow();
|
||||
|
||||
hb.expect('GET', '/match', {a: 1, b: 2});
|
||||
expect(function() {
|
||||
hb('GET', '/match', '{"b":2,"a":1}', noop, {});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only parsed body differs from expected body object', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', {a: 1, b: 2});
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/match', '{"a":1,"b":3}', noop, {});
|
||||
}).toThrow('Expected GET /match with different data\n' +
|
||||
'EXPECTED: {"a":1,"b":2}\nGOT: {"a":1,"b":3}');
|
||||
});
|
||||
|
||||
|
||||
it("should use when's respond() when no expect() respond is defined", function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(201);
|
||||
|
||||
@@ -31,6 +31,48 @@ describe("resource", function() {
|
||||
$httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
|
||||
describe('isValidDottedPath', function() {
|
||||
it('should support arbitrary dotted names', function() {
|
||||
expect(isValidDottedPath('')).toBe(false);
|
||||
expect(isValidDottedPath('1')).toBe(false);
|
||||
expect(isValidDottedPath('1abc')).toBe(false);
|
||||
expect(isValidDottedPath('.')).toBe(false);
|
||||
expect(isValidDottedPath('$')).toBe(true);
|
||||
expect(isValidDottedPath('a')).toBe(true);
|
||||
expect(isValidDottedPath('A')).toBe(true);
|
||||
expect(isValidDottedPath('a1')).toBe(true);
|
||||
expect(isValidDottedPath('$a')).toBe(true);
|
||||
expect(isValidDottedPath('$1')).toBe(true);
|
||||
expect(isValidDottedPath('$$')).toBe(true);
|
||||
expect(isValidDottedPath('$.$')).toBe(true);
|
||||
expect(isValidDottedPath('.$')).toBe(false);
|
||||
expect(isValidDottedPath('$.')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lookupDottedPath', function() {
|
||||
var data = {a: {b: 'foo', c: null}};
|
||||
|
||||
it('should throw for invalid path', function() {
|
||||
expect(function() {
|
||||
lookupDottedPath(data, '.ckck')
|
||||
}).toThrowMinErr('$resource', 'badmember',
|
||||
'Dotted member path "@.ckck" is invalid.');
|
||||
});
|
||||
|
||||
it('should get dotted paths', function() {
|
||||
expect(lookupDottedPath(data, 'a')).toEqual({b: 'foo', c: null});
|
||||
expect(lookupDottedPath(data, 'a.b')).toBe('foo');
|
||||
expect(lookupDottedPath(data, 'a.c')).toBeNull();
|
||||
});
|
||||
|
||||
it('should skip over null/undefined members', function() {
|
||||
expect(lookupDottedPath(data, 'a.b.c')).toBe(undefined);
|
||||
expect(lookupDottedPath(data, 'a.c.c')).toBe(undefined);
|
||||
expect(lookupDottedPath(data, 'a.b.c.d')).toBe(undefined);
|
||||
expect(lookupDottedPath(data, 'NOT_EXIST')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include a request body when calling $delete', function() {
|
||||
$httpBackend.expect('DELETE', '/fooresource', null).respond({});
|
||||
@@ -189,6 +231,19 @@ describe("resource", function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support @_property lookups with underscores', function() {
|
||||
$httpBackend.expect('GET', '/Order/123').respond({_id: {_key:'123'}, count: 0});
|
||||
var LineItem = $resource('/Order/:_id', {_id: '@_id._key'});
|
||||
var item = LineItem.get({_id: 123});
|
||||
$httpBackend.flush();
|
||||
expect(item).toEqualData({_id: {_key: '123'}, count: 0});
|
||||
$httpBackend.expect('POST', '/Order/123').respond({_id: {_key:'123'}, count: 1});
|
||||
item.$save();
|
||||
$httpBackend.flush();
|
||||
expect(item).toEqualData({_id: {_key: '123'}, count: 1});
|
||||
});
|
||||
|
||||
|
||||
it('should not pass default params between actions', function() {
|
||||
var R = $resource('/Path', {}, {get: {method: 'GET', params: {objId: '1'}}, perform: {method: 'GET'}});
|
||||
|
||||
@@ -478,6 +533,18 @@ describe("resource", function() {
|
||||
expect(person.name).toEqual('misko');
|
||||
});
|
||||
|
||||
it('should return a resource instance when calling a class method with a resource instance', function() {
|
||||
$httpBackend.expect('GET', '/Person/123').respond('{"name":"misko"}');
|
||||
var Person = $resource('/Person/:id');
|
||||
var person = Person.get({id:123});
|
||||
$httpBackend.flush();
|
||||
$httpBackend.expect('POST', '/Person').respond('{"name":"misko2"}');
|
||||
|
||||
var person2 = Person.save(person);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(person2).toEqual(jasmine.any(Person));
|
||||
});
|
||||
|
||||
describe('promise api', function() {
|
||||
|
||||
|
||||
@@ -514,6 +514,76 @@ describe('ngView', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngView and transcludes', function() {
|
||||
var element, directive;
|
||||
|
||||
beforeEach(module('ngRoute', function($compileProvider) {
|
||||
element = null;
|
||||
directive = $compileProvider.directive;
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
if (element) {
|
||||
dealoc(element);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow access to directive controller from children when used in a replace template', function() {
|
||||
var controller;
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/view', {templateUrl: 'view.html'});
|
||||
directive('template', function() {
|
||||
return {
|
||||
template: '<div ng-view></div>',
|
||||
replace: true,
|
||||
controller: function() {
|
||||
this.flag = true;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
directive('test', function() {
|
||||
return {
|
||||
require: '^template',
|
||||
link: function(scope, el, attr, ctrl) {
|
||||
controller = ctrl;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($compile, $rootScope, $httpBackend, $location) {
|
||||
$httpBackend.expectGET('view.html').respond('<div><div test></div></div>');
|
||||
element = $compile('<div><div template></div></div>')($rootScope);
|
||||
$location.url('/view');
|
||||
$rootScope.$apply();
|
||||
$httpBackend.flush();
|
||||
expect(controller.flag).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should compile it's content correctly (although we remove it later)", function() {
|
||||
var testElement;
|
||||
module(function($compileProvider, $routeProvider) {
|
||||
$routeProvider.when('/view', {template: ' '});
|
||||
var directive = $compileProvider.directive;
|
||||
directive('test', function() {
|
||||
return {
|
||||
link: function(scope, element) {
|
||||
testElement = element;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($compile, $rootScope, $location) {
|
||||
element = $compile('<div><div ng-view><div test someAttr></div></div></div>')($rootScope);
|
||||
$location.url('/view');
|
||||
$rootScope.$apply();
|
||||
expect(testElement[0].nodeName).toBe('DIV');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngView animations', function() {
|
||||
var body, element, $rootElement;
|
||||
|
||||
@@ -618,7 +688,6 @@ describe('ngView animations', function() {
|
||||
|
||||
item = $animate.flushNext('enter').element;
|
||||
|
||||
$animate.flushNext('addClass').element;
|
||||
$animate.flushNext('addClass').element;
|
||||
|
||||
expect(item.hasClass('classy')).toBe(true);
|
||||
@@ -638,7 +707,6 @@ describe('ngView animations', function() {
|
||||
$animate.flushNext('enter').element;
|
||||
item = $animate.flushNext('leave').element;
|
||||
|
||||
$animate.flushNext('addClass').element;
|
||||
$animate.flushNext('addClass').element;
|
||||
|
||||
expect(item.hasClass('boring')).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user